From e27d5967029f4f3da8889dd673ba516dcd9f3ac8 Mon Sep 17 00:00:00 2001 From: gegy1000 Date: Sun, 16 Jun 2019 23:49:25 +0200 Subject: Plugin rework along with API rework: Enigma split from EnigmaProject; plugins now provide services configurable via a profile --- src/main/java/cuchaz/enigma/Deobfuscator.java | 435 --------------------- src/main/java/cuchaz/enigma/Enigma.java | 114 ++++++ src/main/java/cuchaz/enigma/EnigmaProfile.java | 51 +++ src/main/java/cuchaz/enigma/EnigmaProject.java | 285 ++++++++++++++ src/main/java/cuchaz/enigma/EnigmaServices.java | 21 + src/main/java/cuchaz/enigma/Main.java | 3 +- .../java/cuchaz/enigma/NoRetryMetadataSystem.java | 38 ++ src/main/java/cuchaz/enigma/ProgressListener.java | 18 +- src/main/java/cuchaz/enigma/SourceProvider.java | 2 +- .../java/cuchaz/enigma/analysis/ClassCache.java | 127 ++++++ .../java/cuchaz/enigma/analysis/ParsedJar.java | 129 ------ .../cuchaz/enigma/analysis/index/JarIndex.java | 22 +- src/main/java/cuchaz/enigma/api/EnigmaPlugin.java | 5 + .../cuchaz/enigma/api/EnigmaPluginContext.java | 9 + .../java/cuchaz/enigma/api/EntryNameProposer.java | 10 - src/main/java/cuchaz/enigma/api/JarProcessor.java | 8 - .../cuchaz/enigma/api/service/EnigmaService.java | 4 + .../enigma/api/service/EnigmaServiceContext.java | 11 + .../enigma/api/service/EnigmaServiceFactory.java | 5 + .../enigma/api/service/EnigmaServiceType.java | 29 ++ .../enigma/api/service/JarIndexerService.java | 10 + .../enigma/api/service/NameProposalService.java | 12 + .../enigma/api/service/ObfuscationTestService.java | 9 + .../enigma/command/CheckMappingsCommand.java | 32 +- src/main/java/cuchaz/enigma/command/Command.java | 20 +- .../cuchaz/enigma/command/DecompileCommand.java | 20 +- .../cuchaz/enigma/command/DeobfuscateCommand.java | 20 +- src/main/java/cuchaz/enigma/gui/ClassSelector.java | 4 +- .../cuchaz/enigma/gui/DecompiledClassSource.java | 38 +- src/main/java/cuchaz/enigma/gui/Gui.java | 45 ++- src/main/java/cuchaz/enigma/gui/GuiController.java | 343 +++++++++------- .../cuchaz/enigma/gui/dialog/ProgressDialog.java | 3 +- .../cuchaz/enigma/gui/dialog/SearchDialog.java | 2 +- .../java/cuchaz/enigma/gui/elements/MenuBar.java | 33 +- .../java/cuchaz/enigma/gui/panels/PanelEditor.java | 6 +- .../enigma/translation/mapping/EntryRemapper.java | 10 +- .../cuchaz/enigma/PackageVisibilityIndexTest.java | 10 +- src/test/java/cuchaz/enigma/TestDeobfed.java | 22 +- src/test/java/cuchaz/enigma/TestDeobfuscator.java | 12 +- src/test/java/cuchaz/enigma/TestInnerClasses.java | 15 +- .../enigma/TestJarIndexConstructorReferences.java | 1 - .../cuchaz/enigma/TestJarIndexInheritanceTree.java | 8 +- src/test/java/cuchaz/enigma/TestSourceIndex.java | 6 +- src/test/java/cuchaz/enigma/TestTranslator.java | 2 +- src/test/java/cuchaz/enigma/TokenChecker.java | 8 +- 45 files changed, 1147 insertions(+), 870 deletions(-) delete mode 100644 src/main/java/cuchaz/enigma/Deobfuscator.java create mode 100644 src/main/java/cuchaz/enigma/Enigma.java create mode 100644 src/main/java/cuchaz/enigma/EnigmaProfile.java create mode 100644 src/main/java/cuchaz/enigma/EnigmaProject.java create mode 100644 src/main/java/cuchaz/enigma/EnigmaServices.java create mode 100644 src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java create mode 100644 src/main/java/cuchaz/enigma/analysis/ClassCache.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/ParsedJar.java create mode 100644 src/main/java/cuchaz/enigma/api/EnigmaPlugin.java create mode 100644 src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java delete mode 100644 src/main/java/cuchaz/enigma/api/EntryNameProposer.java delete mode 100644 src/main/java/cuchaz/enigma/api/JarProcessor.java create mode 100644 src/main/java/cuchaz/enigma/api/service/EnigmaService.java create mode 100644 src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java create mode 100644 src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java create mode 100644 src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java create mode 100644 src/main/java/cuchaz/enigma/api/service/JarIndexerService.java create mode 100644 src/main/java/cuchaz/enigma/api/service/NameProposalService.java create mode 100644 src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java (limited to 'src') diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java deleted file mode 100644 index 32f7aa7..0000000 --- a/src/main/java/cuchaz/enigma/Deobfuscator.java +++ /dev/null @@ -1,435 +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; - -import com.google.common.base.Functions; -import com.google.common.base.Stopwatch; -import com.google.common.collect.Streams; -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 com.strobel.decompiler.DecompilerSettings; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.IndexTreeBuilder; -import cuchaz.enigma.analysis.ParsedJar; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.api.EntryNameProposer; -import cuchaz.enigma.api.JarProcessor; -import cuchaz.enigma.bytecode.translators.SourceFixVisitor; -import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.*; -import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.ClassNode; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.Writer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class Deobfuscator { - - private final ServiceLoader jarProcessors = ServiceLoader.load(JarProcessor.class); - private final ServiceLoader nameProposers = ServiceLoader.load(EntryNameProposer.class); - - private final ParsedJar parsedJar; - private final JarIndex jarIndex; - private final IndexTreeBuilder indexTreeBuilder; - - private final SourceProvider obfSourceProvider; - - private EntryRemapper mapper; - - public Deobfuscator(ParsedJar jar, Consumer listener) { - this.parsedJar = jar; - - // build the jar index - this.jarIndex = JarIndex.empty(); - this.jarIndex.indexJar(this.parsedJar, listener); - - listener.accept("Processing jar"); - this.jarProcessors.forEach(processor -> processor.accept(parsedJar, jarIndex)); - - this.indexTreeBuilder = new IndexTreeBuilder(jarIndex); - - listener.accept("Preparing..."); - - CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(parsedJar); - typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex)); - - this.obfSourceProvider = new SourceProvider(SourceProvider.createSettings(), typeLoader); - - // init mappings - mapper = new EntryRemapper(jarIndex); - } - - public Deobfuscator(JarFile jar, Consumer listener) throws IOException { - this(new ParsedJar(jar), listener); - } - - public Deobfuscator(ParsedJar jar) { - this(jar, (msg) -> { - }); - } - - public Deobfuscator(JarFile jar) throws IOException { - this(jar, (msg) -> { - }); - } - - public Stream getNameProposers() { - return Streams.stream(nameProposers); - } - - public ParsedJar getJar() { - return this.parsedJar; - } - - public JarIndex getJarIndex() { - return this.jarIndex; - } - - public IndexTreeBuilder getIndexTreeBuilder() { - return indexTreeBuilder; - } - - public EntryRemapper getMapper() { - return this.mapper; - } - - public void setMappings(EntryTree mappings) { - setMappings(mappings, ProgressListener.VOID); - } - - public void setMappings(EntryTree mappings, ProgressListener progress) { - if (mappings != null) { - Collection> dropped = dropMappings(mappings, progress); - mapper = new EntryRemapper(jarIndex, mappings); - - DeltaTrackingTree obfToDeobf = mapper.getObfToDeobf(); - for (Entry entry : dropped) { - obfToDeobf.trackChange(entry); - } - } else { - mapper = new EntryRemapper(jarIndex); - } - } - - private Collection> dropMappings(EntryTree mappings, ProgressListener progress) { - // drop mappings that don't match the jar - MappingsChecker checker = new MappingsChecker(jarIndex, mappings); - MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress); - - Map, String> droppedMappings = dropped.getDroppedMappings(); - for (Map.Entry, String> mapping : droppedMappings.entrySet()) { - System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped."); - } - - return droppedMappings.keySet(); - } - - public void getSeparatedClasses(List obfClasses, List deobfClasses) { - for (ClassEntry obfClassEntry : this.jarIndex.getEntryIndex().getClasses()) { - // skip inner classes - if (obfClassEntry.isInnerClass()) { - continue; - } - - // separate the classes - ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry); - if (!deobfClassEntry.equals(obfClassEntry)) { - // if the class has a mapping, clearly it's deobfuscated - deobfClasses.add(obfClassEntry); - } else if (obfClassEntry.getPackageName() != null) { - // also call it deobufscated if it's not in the none package - deobfClasses.add(obfClassEntry); - } else { - // otherwise, assume it's still obfuscated - obfClasses.add(obfClassEntry); - } - } - } - - public SourceProvider getObfSourceProvider() { - return obfSourceProvider; - } - - public void writeSources(Path outputDirectory, ProgressListener progress) { - // get the classes to decompile - Collection classEntries = jarIndex.getEntryIndex().getClasses(); - - Stopwatch stopwatch = Stopwatch.createStarted(); - - try { - Translator deobfuscator = mapper.getDeobfuscator(); - - // deobfuscate everything first - Map translatedNodes = deobfuscateClasses(progress, classEntries, deobfuscator); - - decompileClasses(outputDirectory, progress, translatedNodes); - } finally { - stopwatch.stop(); - - System.out.println("writeSources Done in : " + stopwatch.toString()); - } - } - - private Map deobfuscateClasses(ProgressListener progress, Collection classEntries, Translator translator) { - AtomicInteger count = new AtomicInteger(); - if (progress != null) { - progress.init(classEntries.size(), "Deobfuscating classes..."); - } - - return classEntries.parallelStream() - .map(entry -> { - ClassEntry translatedEntry = translator.translate(entry); - if (progress != null) { - progress.step(count.getAndIncrement(), translatedEntry.toString()); - } - - ClassNode node = parsedJar.getClassNode(entry.getFullName()); - if (node != null) { - ClassNode translatedNode = new ClassNode(); - node.accept(new TranslationClassVisitor(translator, Opcodes.ASM5, translatedNode)); - return translatedNode; - } - - return null; - }) - .filter(Objects::nonNull) - .collect(Collectors.toMap(n -> n.name, Functions.identity())); - } - - private void decompileClasses(Path outputDirectory, ProgressListener progress, Map translatedClasses) { - Collection decompileClasses = translatedClasses.values().stream() - .filter(classNode -> classNode.name.indexOf('$') == -1) - .collect(Collectors.toList()); - - if (progress != null) { - progress.init(decompileClasses.size(), "Decompiling classes..."); - } - - //create a common instance outside the loop as mappings shouldn't be changing while this is happening - CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(translatedClasses::get); - typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex)); - - //synchronized to make sure the parallelStream doesn't CME with the cache - ITypeLoader synchronizedTypeLoader = new SynchronizedTypeLoader(typeLoader); - - MetadataSystem metadataSystem = new Deobfuscator.NoRetryMetadataSystem(synchronizedTypeLoader); - - //ensures methods are loaded on classload and prevents race conditions - metadataSystem.setEagerMethodLoadingEnabled(true); - - DecompilerSettings settings = SourceProvider.createSettings(); - SourceProvider sourceProvider = new SourceProvider(settings, synchronizedTypeLoader, metadataSystem); - - AtomicInteger count = new AtomicInteger(); - - decompileClasses.parallelStream().forEach(translatedNode -> { - if (progress != null) { - progress.step(count.getAndIncrement(), translatedNode.name); - } - - decompileClass(outputDirectory, translatedNode, sourceProvider); - }); - } - - private void decompileClass(Path outputDirectory, ClassNode translatedNode, SourceProvider sourceProvider) { - try { - // get the source - CompilationUnit sourceTree = sourceProvider.getSources(translatedNode.name); - - Path path = outputDirectory.resolve(translatedNode.name.replace('.', '/') + ".java"); - Files.createDirectories(path.getParent()); - - try (Writer writer = Files.newBufferedWriter(path)) { - sourceProvider.writeSource(writer, sourceTree); - } - } catch (Throwable t) { - // don't crash the whole world here, just log the error and keep going - // TODO: set up logback via log4j - System.err.println("Unable to decompile class " + translatedNode.name); - t.printStackTrace(System.err); - } - } - - public void writeTransformedJar(File out, ProgressListener progress) { - Translator deobfuscator = mapper.getDeobfuscator(); - writeTransformedJar(out, progress, (node, visitor) -> { - ClassEntry entry = new ClassEntry(node.name); - node.accept(new TranslationClassVisitor(deobfuscator, Opcodes.ASM5, visitor)); - return deobfuscator.translate(entry).getFullName(); - }); - } - - public void writeTransformedJar(File out, ProgressListener progress, ClassTransformer transformer) { - try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { - if (progress != null) { - progress.init(parsedJar.getClassCount(), "Transforming classes..."); - } - - AtomicInteger count = new AtomicInteger(); - parsedJar.visitNode(node -> { - if (progress != null) { - progress.step(count.getAndIncrement(), node.name); - } - - try { - ClassWriter writer = new ClassWriter(0); - String transformedName = transformer.transform(node, writer); - outJar.putNextEntry(new JarEntry(transformedName.replace('.', '/') + ".class")); - outJar.write(writer.toByteArray()); - outJar.closeEntry(); - } catch (Throwable t) { - throw new Error("Unable to transform class " + node.name, t); - } - }); - } catch (IOException ex) { - throw new Error("Unable to write to Jar file!"); - } - } - - public AccessModifier getModifier(Entry entry) { - EntryMapping mapping = mapper.getDeobfMapping(entry); - if (mapping == null) { - return AccessModifier.UNCHANGED; - } - return mapping.getAccessModifier(); - } - - public void changeModifier(Entry entry, AccessModifier modifier) { - EntryMapping mapping = mapper.getDeobfMapping(entry); - if (mapping != null) { - mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier)); - } else { - mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier)); - } - } - - public boolean isRenamable(Entry obfEntry) { - if (obfEntry instanceof MethodEntry) { - // HACKHACK: Object methods are not obfuscated identifiers - MethodEntry obfMethodEntry = (MethodEntry) obfEntry; - String name = obfMethodEntry.getName(); - String sig = obfMethodEntry.getDesc().toString(); - if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { - return false; - } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { - return false; - } else if (name.equals("finalize") && sig.equals("()V")) { - return false; - } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) { - return false; - } else if (name.equals("hashCode") && sig.equals("()I")) { - return false; - } else if (name.equals("notify") && sig.equals("()V")) { - return false; - } else if (name.equals("notifyAll") && sig.equals("()V")) { - return false; - } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) { - return false; - } else if (name.equals("wait") && sig.equals("()V")) { - return false; - } else if (name.equals("wait") && sig.equals("(J)V")) { - return false; - } else if (name.equals("wait") && sig.equals("(JI)V")) { - return false; - } - } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) { - return false; - } - - return this.jarIndex.getEntryIndex().hasEntry(obfEntry); - } - - public boolean isRenamable(EntryReference, Entry> obfReference) { - return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry()); - } - - public boolean isRemapped(Entry entry) { - EntryResolver resolver = mapper.getObfResolver(); - DeltaTrackingTree mappings = mapper.getObfToDeobf(); - return resolver.resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT).stream() - .anyMatch(mappings::contains); - } - - public void rename(Entry obfEntry, String newName) { - mapper.mapFromObf(obfEntry, new EntryMapping(newName)); - } - - public void removeMapping(Entry obfEntry) { - mapper.removeByObf(obfEntry); - } - - public void markAsDeobfuscated(Entry obfEntry) { - mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName())); - } - - public T deobfuscate(T translatable) { - return mapper.deobfuscate(translatable); - } - - public interface ClassTransformer { - String transform(ClassNode node, ClassVisitor visitor); - } - - public static 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/src/main/java/cuchaz/enigma/Enigma.java b/src/main/java/cuchaz/enigma/Enigma.java new file mode 100644 index 0000000..9f88f77 --- /dev/null +++ b/src/main/java/cuchaz/enigma/Enigma.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * 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; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.api.EnigmaPlugin; +import cuchaz.enigma.api.EnigmaPluginContext; +import cuchaz.enigma.api.service.EnigmaService; +import cuchaz.enigma.api.service.EnigmaServiceFactory; +import cuchaz.enigma.api.service.EnigmaServiceType; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ServiceLoader; + +public class Enigma { + private final EnigmaProfile profile; + private final EnigmaServices services; + + private Enigma(EnigmaProfile profile, EnigmaServices services) { + this.profile = profile; + this.services = services; + } + + public static Enigma create() { + return new Builder().build(); + } + + public static Builder builder() { + return new Builder(); + } + + public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException { + ClassCache classCache = ClassCache.of(path); + JarIndex jarIndex = classCache.index(progress); + + return new EnigmaProject(this, classCache, jarIndex); + } + + public EnigmaProfile getProfile() { + return profile; + } + + public EnigmaServices getServices() { + return services; + } + + public static class Builder { + private EnigmaProfile profile = EnigmaProfile.EMPTY; + private Iterable plugins = ServiceLoader.load(EnigmaPlugin.class); + + private Builder() { + } + + public Builder setProfile(EnigmaProfile profile) { + Preconditions.checkNotNull(profile, "profile cannot be null"); + this.profile = profile; + return this; + } + + public Builder setPlugins(Iterable plugins) { + Preconditions.checkNotNull(plugins, "plugins cannot be null"); + this.plugins = plugins; + return this; + } + + public Enigma build() { + PluginContext pluginContext = new PluginContext(profile); + for (EnigmaPlugin plugin : plugins) { + plugin.init(pluginContext); + } + + EnigmaServices services = pluginContext.buildServices(); + return new Enigma(profile, services); + } + } + + private static class PluginContext implements EnigmaPluginContext { + private final EnigmaProfile profile; + + private final ImmutableMap.Builder, EnigmaService> services = ImmutableMap.builder(); + + PluginContext(EnigmaProfile profile) { + this.profile = profile; + } + + @Override + public void registerService(String id, EnigmaServiceType serviceType, EnigmaServiceFactory factory) { + EnigmaProfile.Service serviceProfile = profile.getServiceProfile(serviceType); + + // if this service type is not configured, or it is configured to use a different service id, skip + if (serviceProfile == null || !serviceProfile.matches(id)) return; + + T service = factory.create(serviceProfile::getArgument); + services.put(serviceType, service); + } + + EnigmaServices buildServices() { + return new EnigmaServices(services.build()); + } + } +} diff --git a/src/main/java/cuchaz/enigma/EnigmaProfile.java b/src/main/java/cuchaz/enigma/EnigmaProfile.java new file mode 100644 index 0000000..9dc5ff2 --- /dev/null +++ b/src/main/java/cuchaz/enigma/EnigmaProfile.java @@ -0,0 +1,51 @@ +package cuchaz.enigma; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import cuchaz.enigma.api.service.EnigmaServiceType; + +import javax.annotation.Nullable; +import java.io.Reader; +import java.util.Map; +import java.util.Optional; + +public final class EnigmaProfile { + public static final EnigmaProfile EMPTY = new EnigmaProfile(ImmutableMap.of()); + + private static final Gson GSON = new Gson(); + + @SerializedName("services") + private final Map serviceProfiles; + + private EnigmaProfile(Map serviceProfiles) { + this.serviceProfiles = serviceProfiles; + } + + public static EnigmaProfile parse(Reader reader) { + return GSON.fromJson(reader, EnigmaProfile.class); + } + + @Nullable + public Service getServiceProfile(EnigmaServiceType serviceType) { + return serviceProfiles.get(serviceType.key); + } + + public static class Service { + private final String id; + private final Map args; + + Service(String id, Map args) { + this.id = id; + this.args = args; + } + + public boolean matches(String id) { + return this.id.equals(id); + } + + public Optional getArgument(String key) { + return Optional.ofNullable(args.get(key)); + } + } +} diff --git a/src/main/java/cuchaz/enigma/EnigmaProject.java b/src/main/java/cuchaz/enigma/EnigmaProject.java new file mode 100644 index 0000000..82fc0bd --- /dev/null +++ b/src/main/java/cuchaz/enigma/EnigmaProject.java @@ -0,0 +1,285 @@ +package cuchaz.enigma; + +import com.google.common.base.Functions; +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.decompiler.DecompilerSettings; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.bytecode.translators.SourceFixVisitor; +import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; +import cuchaz.enigma.translation.Translator; +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; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; + +// TODO: Naming? +public class EnigmaProject { + private final Enigma enigma; + + private final ClassCache classCache; + private final JarIndex jarIndex; + + private EntryRemapper mapper; + + public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex) { + this.enigma = enigma; + this.classCache = classCache; + this.jarIndex = jarIndex; + + this.mapper = EntryRemapper.empty(jarIndex); + } + + public void setMappings(EntryTree mappings) { + if (mappings != null) { + mapper = EntryRemapper.mapped(jarIndex, mappings); + } else { + mapper = EntryRemapper.empty(jarIndex); + } + } + + public Enigma getEnigma() { + return enigma; + } + + public ClassCache getClassCache() { + return classCache; + } + + public JarIndex getJarIndex() { + return jarIndex; + } + + public EntryRemapper getMapper() { + return mapper; + } + + public void dropMappings(ProgressListener progress) { + DeltaTrackingTree mappings = mapper.getObfToDeobf(); + + Collection> dropped = dropMappings(mappings, progress); + for (Entry entry : dropped) { + mappings.trackChange(entry); + } + } + + private Collection> dropMappings(EntryTree mappings, ProgressListener progress) { + // drop mappings that don't match the jar + MappingsChecker checker = new MappingsChecker(jarIndex, mappings); + MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress); + + Map, String> droppedMappings = dropped.getDroppedMappings(); + for (Map.Entry, String> mapping : droppedMappings.entrySet()) { + System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped."); + } + + return droppedMappings.keySet(); + } + + public boolean isRenamable(Entry obfEntry) { + if (obfEntry instanceof MethodEntry) { + // HACKHACK: Object methods are not obfuscated identifiers + MethodEntry obfMethodEntry = (MethodEntry) obfEntry; + String name = obfMethodEntry.getName(); + String sig = obfMethodEntry.getDesc().toString(); + if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { + return false; + } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { + return false; + } else if (name.equals("finalize") && sig.equals("()V")) { + return false; + } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) { + return false; + } else if (name.equals("hashCode") && sig.equals("()I")) { + return false; + } else if (name.equals("notify") && sig.equals("()V")) { + return false; + } else if (name.equals("notifyAll") && sig.equals("()V")) { + return false; + } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) { + return false; + } else if (name.equals("wait") && sig.equals("()V")) { + return false; + } else if (name.equals("wait") && sig.equals("(J)V")) { + return false; + } else if (name.equals("wait") && sig.equals("(JI)V")) { + return false; + } + } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) { + return false; + } + + return this.jarIndex.getEntryIndex().hasEntry(obfEntry); + } + + public boolean isRenamable(EntryReference, Entry> obfReference) { + return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry()); + } + + public JarExport exportRemappedJar(ProgressListener progress) { + Collection classEntries = jarIndex.getEntryIndex().getClasses(); + Translator deobfuscator = mapper.getDeobfuscator(); + + AtomicInteger count = new AtomicInteger(); + progress.init(classEntries.size(), "Deobfuscating classes..."); + + Map compiled = classEntries.parallelStream() + .map(entry -> { + ClassEntry translatedEntry = deobfuscator.translate(entry); + progress.step(count.getAndIncrement(), translatedEntry.toString()); + + ClassNode node = classCache.getClassNode(entry.getFullName()); + if (node != null) { + ClassNode translatedNode = new ClassNode(); + node.accept(new TranslationClassVisitor(deobfuscator, Opcodes.ASM5, translatedNode)); + return translatedNode; + } + + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(n -> n.name, Functions.identity())); + + return new JarExport(jarIndex, compiled); + } + + public static final class JarExport { + private final JarIndex jarIndex; + private final Map compiled; + + JarExport(JarIndex jarIndex, Map compiled) { + this.jarIndex = jarIndex; + this.compiled = compiled; + } + + public void write(Path path, ProgressListener progress) throws IOException { + progress.init(this.compiled.size(), "Writing jar..."); + + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(path))) { + AtomicInteger count = new AtomicInteger(); + + for (ClassNode node : this.compiled.values()) { + progress.step(count.getAndIncrement(), node.name); + + String entryName = node.name.replace('.', '/') + ".class"; + + ClassWriter writer = new ClassWriter(0); + node.accept(writer); + + out.putNextEntry(new JarEntry(entryName)); + out.write(writer.toByteArray()); + out.closeEntry(); + } + } + } + + public SourceExport decompile(ProgressListener progress) { + Collection classes = this.compiled.values().stream() + .filter(classNode -> classNode.name.indexOf('$') == -1) + .collect(Collectors.toList()); + + progress.init(classes.size(), "Decompiling classes..."); + + //create a common instance outside the loop as mappings shouldn't be changing while this is happening + CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(this.compiled::get); + typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex)); + + //synchronized to make sure the parallelStream doesn't CME with the cache + ITypeLoader synchronizedTypeLoader = new SynchronizedTypeLoader(typeLoader); + + MetadataSystem metadataSystem = new NoRetryMetadataSystem(synchronizedTypeLoader); + + //ensures methods are loaded on classload and prevents race conditions + metadataSystem.setEagerMethodLoadingEnabled(true); + + DecompilerSettings settings = SourceProvider.createSettings(); + SourceProvider sourceProvider = new SourceProvider(settings, synchronizedTypeLoader, metadataSystem); + + AtomicInteger count = new AtomicInteger(); + + Collection decompiled = classes.parallelStream() + .map(translatedNode -> { + progress.step(count.getAndIncrement(), translatedNode.name); + + String source = decompileClass(translatedNode, sourceProvider); + return new ClassSource(translatedNode.name, source); + }) + .collect(Collectors.toList()); + + return new SourceExport(decompiled); + } + + private String decompileClass(ClassNode translatedNode, SourceProvider sourceProvider) { + CompilationUnit sourceTree = sourceProvider.getSources(translatedNode.name); + + StringWriter writer = new StringWriter(); + sourceProvider.writeSource(writer, sourceTree); + + return writer.toString(); + } + } + + public static final class SourceExport { + private final Collection decompiled; + + SourceExport(Collection decompiled) { + this.decompiled = decompiled; + } + + public void write(Path path, ProgressListener progress) throws IOException { + progress.init(decompiled.size(), "Writing sources..."); + + int count = 0; + for (ClassSource source : decompiled) { + progress.step(count++, source.name); + + Path sourcePath = source.resolvePath(path); + source.writeTo(sourcePath); + } + } + } + + private static class ClassSource { + private final String name; + private final String source; + + ClassSource(String name, String source) { + this.name = name; + this.source = source; + } + + void writeTo(Path path) throws IOException { + try (BufferedWriter writer = Files.newBufferedWriter(path)) { + writer.write(source); + } + } + + Path resolvePath(Path root) { + return root.resolve(name.replace('.', '/') + ".java"); + } + } +} diff --git a/src/main/java/cuchaz/enigma/EnigmaServices.java b/src/main/java/cuchaz/enigma/EnigmaServices.java new file mode 100644 index 0000000..86507bc --- /dev/null +++ b/src/main/java/cuchaz/enigma/EnigmaServices.java @@ -0,0 +1,21 @@ +package cuchaz.enigma; + +import com.google.common.collect.ImmutableMap; +import cuchaz.enigma.api.service.EnigmaService; +import cuchaz.enigma.api.service.EnigmaServiceType; + +import java.util.Optional; + +public final class EnigmaServices { + private final ImmutableMap, EnigmaService> services; + + EnigmaServices(ImmutableMap, EnigmaService> services) { + this.services = services; + } + + @SuppressWarnings("unchecked") + public Optional get(EnigmaServiceType type) { + EnigmaService service = services.get(type); + return Optional.ofNullable((T) service); + } +} diff --git a/src/main/java/cuchaz/enigma/Main.java b/src/main/java/cuchaz/enigma/Main.java index ccfc51f..76a3fff 100644 --- a/src/main/java/cuchaz/enigma/Main.java +++ b/src/main/java/cuchaz/enigma/Main.java @@ -17,7 +17,6 @@ import cuchaz.enigma.translation.mapping.serde.MappingFormat; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; -import java.util.jar.JarFile; public class Main { @@ -26,7 +25,7 @@ public class Main { // parse command-line args if (args.length >= 1) { - gui.getController().openJar(new JarFile(getFile(args[0]))); + gui.getController().openJar(getFile(args[0]).toPath()); } if (args.length >= 2) { Path mappingsFile = getFile(args[1]).toPath(); diff --git a/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java new file mode 100644 index 0000000..269d31e --- /dev/null +++ b/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java @@ -0,0 +1,38 @@ +package cuchaz.enigma; + +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/src/main/java/cuchaz/enigma/ProgressListener.java b/src/main/java/cuchaz/enigma/ProgressListener.java index ffce297..6da3b81 100644 --- a/src/main/java/cuchaz/enigma/ProgressListener.java +++ b/src/main/java/cuchaz/enigma/ProgressListener.java @@ -1,15 +1,17 @@ package cuchaz.enigma; public interface ProgressListener { - ProgressListener VOID = new ProgressListener() { - @Override - public void init(int totalWork, String title) { - } + static ProgressListener none() { + return new ProgressListener() { + @Override + public void init(int totalWork, String title) { + } - @Override - public void step(int numDone, String message) { - } - }; + @Override + public void step(int numDone, String message) { + } + }; + } void init(int totalWork, String title); diff --git a/src/main/java/cuchaz/enigma/SourceProvider.java b/src/main/java/cuchaz/enigma/SourceProvider.java index 48e5a59..662f1f9 100644 --- a/src/main/java/cuchaz/enigma/SourceProvider.java +++ b/src/main/java/cuchaz/enigma/SourceProvider.java @@ -33,7 +33,7 @@ public class SourceProvider { } public SourceProvider(DecompilerSettings settings, ITypeLoader typeLoader) { - this(settings, typeLoader, new Deobfuscator.NoRetryMetadataSystem(typeLoader)); + this(settings, typeLoader, new NoRetryMetadataSystem(typeLoader)); } public static DecompilerSettings createSettings() { diff --git a/src/main/java/cuchaz/enigma/analysis/ClassCache.java b/src/main/java/cuchaz/enigma/analysis/ClassCache.java new file mode 100644 index 0000000..0bd78b3 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/ClassCache.java @@ -0,0 +1,127 @@ +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.CompiledSource; +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.Opcodes; +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, CompiledSource { + 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, 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(Opcodes.ASM5, 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/src/main/java/cuchaz/enigma/analysis/ParsedJar.java b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java deleted file mode 100644 index ddcda3e..0000000 --- a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java +++ /dev/null @@ -1,129 +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.analysis; - -import com.google.common.io.ByteStreams; -import cuchaz.enigma.CompiledSource; -import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.ClassNode; - -import javax.annotation.Nullable; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarInputStream; - -public class ParsedJar implements CompiledSource { - private final Map classBytes; - private final Map nodeCache = new HashMap<>(); - - public ParsedJar(JarFile jar) throws IOException { - Map uClassBytes = new LinkedHashMap<>(); - try { - // get the jar entries that correspond to classes - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - // is this a class file? - if (entry.getName().endsWith(".class")) { - try (InputStream input = new BufferedInputStream(jar.getInputStream(entry))) { - String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); - uClassBytes.put(path, ByteStreams.toByteArray(input)); - } - } - } - } finally { - jar.close(); - classBytes = Collections.unmodifiableMap(uClassBytes); - } - } - - public ParsedJar(JarInputStream jar) throws IOException { - Map uClassBytes = new LinkedHashMap<>(); - try { - // get the jar entries that correspond to classes - JarEntry entry; - while ((entry = jar.getNextJarEntry()) != null) { - // is this a class file? - if (entry.getName().endsWith(".class")) { - String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); - uClassBytes.put(path, ByteStreams.toByteArray(jar)); - jar.closeEntry(); - } - } - } finally { - jar.close(); - classBytes = Collections.unmodifiableMap(uClassBytes); - } - } - - public void visitReader(Function visitorFunction, int options) { - for (String s : classBytes.keySet()) { - ClassNode nodeCached = nodeCache.get(s); - if (nodeCached != null) { - nodeCached.accept(visitorFunction.apply(s)); - } else { - new ClassReader(classBytes.get(s)).accept(visitorFunction.apply(s), options); - } - } - } - - public void visitNode(Consumer consumer) { - for (String s : classBytes.keySet()) { - consumer.accept(getClassNode(s)); - } - } - - public int getClassCount() { - return classBytes.size(); - } - - @Nullable - @Override - public ClassNode getClassNode(String name) { - return nodeCache.computeIfAbsent(name, (n) -> { - byte[] bytes = classBytes.get(name); - if (bytes == null) { - return null; - } - - ClassReader reader = new ClassReader(bytes); - ClassNode node = new ClassNode(); - - LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Opcodes.ASM5, node); - reader.accept(visitor, 0); - - return node; - }); - } - - public List getClassEntries() { - List entries = new ArrayList<>(classBytes.size()); - for (String s : classBytes.keySet()) { - entries.add(new ClassEntry(s)); - } - return entries; - } - - public Map getClassDataMap() { - return classBytes; - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java index fd4e618..300425b 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java @@ -13,7 +13,8 @@ package cuchaz.enigma.analysis.index; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; -import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.mapping.IndexEntryResolver; import cuchaz.enigma.translation.representation.Lambda; @@ -23,7 +24,6 @@ import org.objectweb.asm.Opcodes; import java.util.Arrays; import java.util.Collection; -import java.util.function.Consumer; public class JarIndex implements JarIndexer { private final EntryIndex entryIndex; @@ -56,23 +56,25 @@ public class JarIndex implements JarIndexer { return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); } - public void indexJar(ParsedJar jar, Consumer progress) { - progress.accept("Indexing entries (1/4)"); - jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE); + public void indexJar(ClassCache classCache, ProgressListener progress) { + progress.init(4, "Indexing jar"); - progress.accept("Indexing entry references (2/4)"); - jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES); + progress.step(1, "Entries"); + classCache.visit(() -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE); - progress.accept("Finding bridge methods (3/4)"); + progress.step(2, "Entry references"); + classCache.visit(() -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES); + + progress.step(3, "Bridge methods"); bridgeMethodIndex.findBridgeMethods(); - progress.accept("Processing index (4/4)"); + progress.step(4, "Processing"); processIndex(this); } @Override public void processIndex(JarIndex index) { - indexers.forEach(indexer -> indexer.processIndex(index)); + indexers.parallelStream().forEach(indexer -> indexer.processIndex(index)); } @Override diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java b/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java new file mode 100644 index 0000000..bdd6015 --- /dev/null +++ b/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java @@ -0,0 +1,5 @@ +package cuchaz.enigma.api; + +public interface EnigmaPlugin { + void init(EnigmaPluginContext ctx); +} diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java b/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java new file mode 100644 index 0000000..a59051a --- /dev/null +++ b/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.api; + +import cuchaz.enigma.api.service.EnigmaService; +import cuchaz.enigma.api.service.EnigmaServiceFactory; +import cuchaz.enigma.api.service.EnigmaServiceType; + +public interface EnigmaPluginContext { + void registerService(String id, EnigmaServiceType serviceType, EnigmaServiceFactory factory); +} diff --git a/src/main/java/cuchaz/enigma/api/EntryNameProposer.java b/src/main/java/cuchaz/enigma/api/EntryNameProposer.java deleted file mode 100644 index 3fc26d2..0000000 --- a/src/main/java/cuchaz/enigma/api/EntryNameProposer.java +++ /dev/null @@ -1,10 +0,0 @@ -package cuchaz.enigma.api; - -import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.util.Optional; - -public interface EntryNameProposer { - Optional proposeName(Entry obfEntry, EntryRemapper remapper); -} diff --git a/src/main/java/cuchaz/enigma/api/JarProcessor.java b/src/main/java/cuchaz/enigma/api/JarProcessor.java deleted file mode 100644 index 965b0c4..0000000 --- a/src/main/java/cuchaz/enigma/api/JarProcessor.java +++ /dev/null @@ -1,8 +0,0 @@ -package cuchaz.enigma.api; - -import cuchaz.enigma.analysis.ParsedJar; -import cuchaz.enigma.analysis.index.JarIndex; - -public interface JarProcessor { - void accept(ParsedJar jar, JarIndex index); -} diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaService.java b/src/main/java/cuchaz/enigma/api/service/EnigmaService.java new file mode 100644 index 0000000..526dda7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/api/service/EnigmaService.java @@ -0,0 +1,4 @@ +package cuchaz.enigma.api.service; + +public interface EnigmaService { +} diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java new file mode 100644 index 0000000..9e433fb --- /dev/null +++ b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.api.service; + +import java.util.Optional; + +public interface EnigmaServiceContext { + static EnigmaServiceContext empty() { + return key -> Optional.empty(); + } + + Optional getArgument(String key); +} diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java new file mode 100644 index 0000000..7c10ac2 --- /dev/null +++ b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java @@ -0,0 +1,5 @@ +package cuchaz.enigma.api.service; + +public interface EnigmaServiceFactory { + T create(EnigmaServiceContext ctx); +} diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java new file mode 100644 index 0000000..358828f --- /dev/null +++ b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java @@ -0,0 +1,29 @@ +package cuchaz.enigma.api.service; + +public final class EnigmaServiceType { + public final String key; + + private EnigmaServiceType(String key) { + this.key = key; + } + + public static EnigmaServiceType create(String key) { + return new EnigmaServiceType<>(key); + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + + if (obj instanceof EnigmaServiceType) { + return ((EnigmaServiceType) obj).key.equals(key); + } + + return false; + } +} diff --git a/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java b/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java new file mode 100644 index 0000000..0cda199 --- /dev/null +++ b/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java @@ -0,0 +1,10 @@ +package cuchaz.enigma.api.service; + +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.index.JarIndex; + +public interface JarIndexerService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("jar_indexer"); + + void acceptJar(ClassCache classCache, JarIndex jarIndex); +} diff --git a/src/main/java/cuchaz/enigma/api/service/NameProposalService.java b/src/main/java/cuchaz/enigma/api/service/NameProposalService.java new file mode 100644 index 0000000..4c357db --- /dev/null +++ b/src/main/java/cuchaz/enigma/api/service/NameProposalService.java @@ -0,0 +1,12 @@ +package cuchaz.enigma.api.service; + +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.util.Optional; + +public interface NameProposalService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("name_proposal"); + + Optional proposeName(Entry obfEntry, EntryRemapper remapper); +} diff --git a/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java b/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java new file mode 100644 index 0000000..af0cf30 --- /dev/null +++ b/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.api.service; + +import cuchaz.enigma.translation.representation.entry.Entry; + +public interface ObfuscationTestService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("obfuscation_test"); + + boolean testDeobfuscated(Entry entry); +} diff --git a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java index 7ec7679..08e73e6 100644 --- a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java +++ b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java @@ -1,6 +1,7 @@ package cuchaz.enigma.command; -import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.ProgressListener; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.translation.mapping.EntryMapping; @@ -8,10 +9,8 @@ import cuchaz.enigma.translation.mapping.serde.MappingFormat; import cuchaz.enigma.translation.mapping.tree.EntryTree; import cuchaz.enigma.translation.representation.entry.ClassEntry; -import java.io.File; import java.nio.file.Path; import java.util.Set; -import java.util.jar.JarFile; import java.util.stream.Collectors; public class CheckMappingsCommand extends Command { @@ -32,26 +31,39 @@ public class CheckMappingsCommand extends Command { @Override public void run(String... args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); + Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath(); Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true)); + Enigma enigma = Enigma.create(); + System.out.println("Reading JAR..."); - Deobfuscator deobfuscator = new Deobfuscator(new JarFile(fileJarIn)); + + EnigmaProject project = enigma.openJar(fileJarIn, ProgressListener.none()); + System.out.println("Reading mappings..."); MappingFormat format = chooseEnigmaFormat(fileMappings); - EntryTree mappings = format.read(fileMappings, ProgressListener.VOID); - deobfuscator.setMappings(mappings); + EntryTree mappings = format.read(fileMappings, ProgressListener.none()); + project.setMappings(mappings); - JarIndex idx = deobfuscator.getJarIndex(); + JarIndex idx = project.getJarIndex(); boolean error = false; for (Set partition : idx.getPackageVisibilityIndex().getPartitions()) { - long packages = partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::getPackageName).distinct().count(); + long packages = partition.stream() + .map(project.getMapper()::deobfuscate) + .map(ClassEntry::getPackageName) + .distinct() + .count(); if (packages > 1) { error = true; - System.err.println("ERROR: Must be in one package:\n" + partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::toString).sorted().collect(Collectors.joining("\n"))); + System.err.println("ERROR: Must be in one package:\n" + partition.stream() + .map(project.getMapper()::deobfuscate) + .map(ClassEntry::toString) + .sorted() + .collect(Collectors.joining("\n")) + ); } } diff --git a/src/main/java/cuchaz/enigma/command/Command.java b/src/main/java/cuchaz/enigma/command/Command.java index b107fb6..41d7bfa 100644 --- a/src/main/java/cuchaz/enigma/command/Command.java +++ b/src/main/java/cuchaz/enigma/command/Command.java @@ -1,6 +1,7 @@ package cuchaz.enigma.command; -import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.ProgressListener; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.serde.MappingFormat; @@ -10,7 +11,6 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.jar.JarFile; public abstract class Command { public final String name; @@ -25,15 +25,21 @@ public abstract class Command { public abstract void run(String... args) throws Exception; - protected static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception { + protected static EnigmaProject openProject(Path fileJarIn, Path fileMappings) throws Exception { + ProgressListener progress = new ConsoleProgressListener(); + + Enigma enigma = Enigma.create(); + System.out.println("Reading jar..."); - Deobfuscator deobfuscator = new Deobfuscator(jar); + EnigmaProject project = enigma.openJar(fileJarIn, progress); + if (fileMappings != null) { System.out.println("Reading mappings..."); - EntryTree mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, new ConsoleProgressListener()); - deobfuscator.setMappings(mappings); + EntryTree mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, progress); + project.setMappings(mappings); } - return deobfuscator; + + return project; } protected static MappingFormat chooseEnigmaFormat(Path path) { diff --git a/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/src/main/java/cuchaz/enigma/command/DecompileCommand.java index a58d908..bc23d01 100644 --- a/src/main/java/cuchaz/enigma/command/DecompileCommand.java +++ b/src/main/java/cuchaz/enigma/command/DecompileCommand.java @@ -1,10 +1,9 @@ package cuchaz.enigma.command; -import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.ProgressListener; -import java.io.File; import java.nio.file.Path; -import java.util.jar.JarFile; public class DecompileCommand extends Command { @@ -24,10 +23,17 @@ public class DecompileCommand extends Command { @Override public void run(String... args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); - File fileJarOut = getWritableFolder(getArg(args, 1, "out folder", true)); + Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath(); + Path fileJarOut = getWritableFolder(getArg(args, 1, "out folder", true)).toPath(); Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); - Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); - deobfuscator.writeSources(fileJarOut.toPath(), new Command.ConsoleProgressListener()); + + EnigmaProject project = openProject(fileJarIn, fileMappings); + + ProgressListener progress = new ConsoleProgressListener(); + + EnigmaProject.JarExport jar = project.exportRemappedJar(progress); + EnigmaProject.SourceExport source = jar.decompile(progress); + + source.write(fileJarOut, progress); } } diff --git a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java index 5d49938..c24e661 100644 --- a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java +++ b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java @@ -1,10 +1,9 @@ package cuchaz.enigma.command; -import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.ProgressListener; -import java.io.File; import java.nio.file.Path; -import java.util.jar.JarFile; public class DeobfuscateCommand extends Command { @@ -24,10 +23,17 @@ public class DeobfuscateCommand extends Command { @Override public void run(String... args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); - File fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)); + Path fileJarIn = getReadablePath(getArg(args, 0, "in jar", true)); + Path fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)).toPath(); Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); - Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); - deobfuscator.writeTransformedJar(fileJarOut, new Command.ConsoleProgressListener()); + + EnigmaProject project = openProject(fileJarIn, fileMappings); + + ProgressListener progress = new ConsoleProgressListener(); + + EnigmaProject.JarExport jar = project.exportRemappedJar(progress); + EnigmaProject.SourceExport source = jar.decompile(progress); + + source.write(fileJarOut, progress); } } diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java index 39d0333..5051032 100644 --- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java @@ -155,7 +155,7 @@ public class ClassSelector extends JTree { return; } - Translator translator = controller.getDeobfuscator().getMapper().getDeobfuscator(); + Translator translator = controller.project.getMapper().getDeobfuscator(); // build the package names Map packages = Maps.newHashMap(); @@ -478,7 +478,7 @@ public class ClassSelector extends JTree { } public void insertNode(ClassEntry obfEntry) { - ClassEntry deobfEntry = controller.getDeobfuscator().deobfuscate(obfEntry); + ClassEntry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry); DefaultTreeModel model = (DefaultTreeModel) getModel(); diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java index d7c981a..44f70f8 100644 --- a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java +++ b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java @@ -1,12 +1,17 @@ package cuchaz.enigma.gui; -import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.EnigmaServices; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.api.service.NameProposalService; import cuchaz.enigma.gui.highlight.TokenHighlightType; import cuchaz.enigma.translation.LocalNameGenerator; import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; import cuchaz.enigma.translation.representation.TypeDescriptor; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.Entry; @@ -34,27 +39,27 @@ public class DecompiledClassSource { return new DecompiledClassSource(classEntry, new SourceIndex(text)); } - public void remapSource(Deobfuscator deobfuscator, Translator translator) { + public void remapSource(EnigmaProject project, Translator translator) { highlightedTokens.clear(); SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); - SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(deobfuscator, token, movedToken, translator)); + SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(project, token, movedToken, translator)); remappedIndex = obfuscatedIndex.remapTo(remapResult); } - private String remapToken(Deobfuscator deobfuscator, Token token, Token movedToken, Translator translator) { + private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) { EntryReference, Entry> reference = obfuscatedIndex.getReference(token); Entry entry = reference.getNameableEntry(); Entry translatedEntry = translator.translate(entry); - if (deobfuscator.isRenamable(reference)) { + if (project.isRenamable(reference)) { if (isDeobfuscated(entry, translatedEntry)) { highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED); return translatedEntry.getSourceRemapName(); } else { - Optional proposedName = proposeName(deobfuscator, entry); + Optional proposedName = proposeName(project, entry); if (proposedName.isPresent()) { highlightToken(movedToken, TokenHighlightType.PROPOSED); return proposedName.get(); @@ -72,13 +77,22 @@ public class DecompiledClassSource { return null; } - private Optional proposeName(Deobfuscator deobfuscator, Entry entry) { - Stream proposals = deobfuscator.getNameProposers() - .map(plugin -> plugin.proposeName(entry, deobfuscator.getMapper())) - .filter(Optional::isPresent) - .map(Optional::get); + private Optional proposeName(EnigmaProject project, Entry entry) { + EnigmaServices services = project.getEnigma().getServices(); - return proposals.findFirst(); + return services.get(NameProposalService.TYPE).flatMap(nameProposalService -> { + EntryResolver resolver = project.getMapper().getObfResolver(); + + Collection> resolved = resolver.resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT); + EntryRemapper mapper = project.getMapper(); + + Stream proposals = resolved.stream() + .map(e -> nameProposalService.proposeName(e, mapper)) + .filter(Optional::isPresent) + .map(Optional::get); + + return proposals.findFirst(); + }); } @Nullable diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index a61f4dd..f5dd8a0 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java @@ -31,9 +31,7 @@ import cuchaz.enigma.gui.panels.PanelIdentifier; import cuchaz.enigma.gui.panels.PanelObf; import cuchaz.enigma.gui.util.History; import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.translation.mapping.AccessModifier; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.mapping.*; import cuchaz.enigma.translation.representation.entry.*; import cuchaz.enigma.utils.Utils; import de.sciss.syntaxpane.DefaultSyntaxKit; @@ -312,13 +310,8 @@ public class Gui { return this.controller; } - public void onStartOpenJar(String message) { + public void onStartOpenJar() { this.classesPanel.removeAll(); - JPanel panel = new JPanel(); - panel.setLayout(new FlowLayout()); - panel.add(new JLabel(message)); - this.classesPanel.add(panel); - redraw(); } @@ -447,7 +440,7 @@ public class Gui { this.cursorReference = reference; - EntryReference, Entry> translatedReference = controller.getDeobfuscator().deobfuscate(reference); + EntryReference, Entry> translatedReference = controller.project.getMapper().deobfuscate(reference); infoPanel.removeAll(); if (translatedReference.entry instanceof ClassEntry) { @@ -509,7 +502,7 @@ public class Gui { } private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) { - if (!getController().entryIsInJar(entry)) + if (!getController().project.isRenamable(entry)) return null; JPanel panel = new JPanel(); panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); @@ -519,8 +512,16 @@ public class Gui { JComboBox combo = new JComboBox<>(AccessModifier.values()); ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); - combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); - combo.addItemListener(getController()::modifierChange); + + EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry); + if (mapping != null) { + combo.setSelectedIndex(mapping.getAccessModifier().ordinal()); + } else { + combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal()); + } + + combo.addItemListener(controller::modifierChange); + panel.add(combo); container.add(panel); @@ -529,6 +530,8 @@ public class Gui { } public void onCaretMove(int pos) { + EntryRemapper mapper = controller.project.getMapper(); + Token token = this.controller.getToken(pos); boolean isToken = token != null; @@ -539,7 +542,7 @@ public class Gui { shouldNavigateOnClick = false; Entry navigationEntry = referenceEntry; if (cursorReference.context == null) { - EntryResolver resolver = controller.getDeobfuscator().getMapper().getObfResolver(); + EntryResolver resolver = mapper.getObfResolver(); navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT); } controller.navigateTo(navigationEntry); @@ -550,8 +553,7 @@ public class Gui { boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry; boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); - boolean isInJar = isToken && this.controller.entryIsInJar(referenceEntry); - boolean isRenamable = isToken && this.controller.getDeobfuscator().isRenamable(cursorReference); + boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference); if (isToken) { showCursorReference(cursorReference); @@ -564,12 +566,12 @@ public class Gui { this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry); - this.popupMenu.openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); + this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference()); this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference()); this.popupMenu.toggleMappingMenu.setEnabled(isRenamable); - if (isToken && this.controller.getDeobfuscator().isRemapped(referenceEntry)) { + if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) { this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); } else { this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); @@ -581,7 +583,7 @@ public class Gui { // init the text box renameTextField = new JTextField(); - EntryReference, Entry> translatedReference = controller.getDeobfuscator().deobfuscate(cursorReference); + EntryReference, Entry> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); renameTextField.setText(translatedReference.getNameableName()); renameTextField.setPreferredSize(new Dimension(360, renameTextField.getPreferredSize().height)); @@ -728,7 +730,10 @@ public class Gui { } public void toggleMapping() { - if (this.controller.getDeobfuscator().isRemapped(cursorReference.entry)) { + Entry obfEntry = cursorReference.entry; + Entry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); + + if (!Objects.equals(obfEntry, deobfEntry)) { this.controller.removeMapping(cursorReference); } else { this.controller.markAsDeobfuscated(cursorReference); diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index 1683333..a55d2cd 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -14,9 +14,13 @@ package cuchaz.enigma.gui; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.CompiledSourceTypeLoader; +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.SourceProvider; import cuchaz.enigma.analysis.*; +import cuchaz.enigma.api.service.ObfuscationTestService; +import cuchaz.enigma.bytecode.translators.SourceFixVisitor; import cuchaz.enigma.config.Config; import cuchaz.enigma.gui.dialog.ProgressDialog; import cuchaz.enigma.gui.util.History; @@ -30,114 +34,150 @@ import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.translation.representation.entry.FieldEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; import cuchaz.enigma.utils.ReadableToken; +import org.objectweb.asm.Opcodes; import javax.annotation.Nullable; import javax.swing.*; import java.awt.event.ItemEvent; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Path; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.jar.JarFile; import java.util.stream.Collectors; +import java.util.stream.Stream; public class GuiController { - private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("decompiler-thread").build()); + private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat("decompiler-thread") + .build() + ); private final Gui gui; - private Deobfuscator deobfuscator; - private DecompiledClassSource currentSource; + public final Enigma enigma; + public EnigmaProject project; + private SourceProvider sourceProvider; + private IndexTreeBuilder indexTreeBuilder; private Path loadedMappingPath; private MappingFormat loadedMappingFormat; + private DecompiledClassSource currentSource; + public GuiController(Gui gui) { this.gui = gui; + // TODO: load and set profile + this.enigma = Enigma.create(); } public boolean isDirty() { - if (deobfuscator == null) { - return false; - } - return deobfuscator.getMapper().isDirty(); + return project != null && project.getMapper().isDirty(); } - public void openJar(final JarFile jar) throws IOException { - this.gui.onStartOpenJar("Loading JAR..."); - this.deobfuscator = new Deobfuscator(jar, this.gui::onStartOpenJar); - this.gui.onFinishOpenJar(jar.getName()); - refreshClasses(); + public void openJar(final Path jarPath) { + this.gui.onStartOpenJar(); + + ProgressDialog.runOffThread(gui.getFrame(), progress -> { + project = enigma.openJar(jarPath, progress); + + indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); + + CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(project.getClassCache()); + typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, project.getJarIndex())); + sourceProvider = new SourceProvider(SourceProvider.createSettings(), typeLoader); + + gui.onFinishOpenJar(jarPath.getFileName().toString()); + + refreshClasses(); + }); } public void closeJar() { - this.deobfuscator = null; + this.project = null; this.gui.onCloseJar(); } public void openMappings(MappingFormat format, Path path) { - if (deobfuscator == null) return; - ProgressDialog.runInThread(this.gui.getFrame(), progress -> { + if (project == null) return; + + gui.setMappingsFile(path); + + ProgressDialog.runOffThread(gui.getFrame(), progress -> { try { EntryTree mappings = format.read(path, progress); - deobfuscator.setMappings(mappings, progress); + project.setMappings(mappings); - gui.setMappingsFile(path); loadedMappingFormat = format; loadedMappingPath = path; refreshClasses(); refreshCurrentClass(); } catch (MappingParseException e) { - JOptionPane.showMessageDialog(this.gui.getFrame(), e.getMessage()); + JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage()); } }); } public void saveMappings(Path path) { - saveMappings(loadedMappingFormat, path); + if (project == null) return; + + saveMappings(path, loadedMappingFormat); } - public void saveMappings(MappingFormat format, Path path) { - if (deobfuscator == null) return; - EntryRemapper mapper = deobfuscator.getMapper(); + public void saveMappings(Path path, MappingFormat format) { + if (project == null) return; + + ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { + EntryRemapper mapper = project.getMapper(); - MappingDelta delta = mapper.takeMappingDelta(); - boolean saveAll = !path.equals(loadedMappingPath); + MappingDelta delta = mapper.takeMappingDelta(); + boolean saveAll = !path.equals(loadedMappingPath); + + loadedMappingFormat = format; + loadedMappingPath = path; - ProgressDialog.runInThread(this.gui.getFrame(), progress -> { if (saveAll) { format.write(mapper.getObfToDeobf(), path, progress); } else { format.write(mapper.getObfToDeobf(), delta, path, progress); } }); - - loadedMappingFormat = format; - loadedMappingPath = path; } public void closeMappings() { - if (deobfuscator == null) return; - this.deobfuscator.setMappings(null); + if (project == null) return; + + project.setMappings(null); + this.gui.setMappingsFile(null); refreshClasses(); refreshCurrentClass(); } - public void exportSource(final File dirOut) { - if (deobfuscator == null) return; - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut.toPath(), progress)); + public void exportSource(final Path path) { + if (project == null) return; + + ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { + EnigmaProject.JarExport jar = project.exportRemappedJar(progress); + EnigmaProject.SourceExport source = jar.decompile(progress); + + source.write(path, progress); + }); } - public void exportJar(final File fileOut) { - if (deobfuscator == null) return; - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeTransformedJar(fileOut, progress)); + public void exportJar(final Path path) { + if (project == null) return; + + ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { + EnigmaProject.JarExport jar = project.exportRemappedJar(progress); + jar.write(path, progress); + }); } public Token getToken(int pos) { @@ -167,85 +207,9 @@ public class GuiController { ); } - public boolean entryIsInJar(Entry entry) { - if (entry == null || deobfuscator == null) return false; - return this.deobfuscator.isRenamable(entry); - } - - public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { - Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, entry); - return ClassInheritanceTreeNode.findNode(rootNode, entry); - } - - public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) { - Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, entry); - } - - public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) { - Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, entry); - return MethodInheritanceTreeNode.findNode(rootNode, entry); - } - - public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) { - Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - List rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, entry); - if (rootNodes.isEmpty()) { - return null; - } - if (rootNodes.size() > 1) { - System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one."); - } - return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry); - } - - public ClassReferenceTreeNode getClassReferences(ClassEntry entry) { - Translator deobfuscator = this.deobfuscator.getMapper().getDeobfuscator(); - ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry); - rootNode.load(this.deobfuscator.getJarIndex(), true); - return rootNode; - } - - public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) { - Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry); - rootNode.load(this.deobfuscator.getJarIndex(), true); - return rootNode; - } - - public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) { - Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry); - rootNode.load(this.deobfuscator.getJarIndex(), true, recursive); - return rootNode; - } - - public void rename(EntryReference, Entry> reference, String newName, boolean refreshClassTree) { - this.deobfuscator.rename(reference.getNameableEntry(), newName); - - if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) - this.gui.moveClassTree(reference, newName); - refreshCurrentClass(reference); - } - - public void removeMapping(EntryReference, Entry> reference) { - this.deobfuscator.removeMapping(reference.getNameableEntry()); - if (reference.entry instanceof ClassEntry) - this.gui.moveClassTree(reference, false, true); - refreshCurrentClass(reference); - } - - public void markAsDeobfuscated(EntryReference, Entry> reference) { - this.deobfuscator.markAsDeobfuscated(reference.getNameableEntry()); - if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) - this.gui.moveClassTree(reference, true, false); - refreshCurrentClass(reference); - } - /** * Navigates to the declaration with respect to navigation history + * * @param entry the entry whose declaration will be navigated to */ public void openDeclaration(Entry entry) { @@ -257,6 +221,7 @@ public class GuiController { /** * Navigates to the reference with respect to navigation history + * * @param reference the reference */ public void openReference(EntryReference, Entry> reference) { @@ -275,12 +240,13 @@ public class GuiController { /** * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. + * * @param reference the reference */ private void setReference(EntryReference, Entry> reference) { // get the reference target class ClassEntry classEntry = reference.getLocationClassEntry(); - if (!this.deobfuscator.isRenamable(classEntry)) { + if (!project.isRenamable(classEntry)) { throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!"); } @@ -294,6 +260,7 @@ public class GuiController { /** * Navigates to the reference without modifying history. Assumes the class is loaded. + * * @param reference */ private void showReference(EntryReference, Entry> reference) { @@ -307,7 +274,7 @@ public class GuiController { } public Collection getTokensForReference(EntryReference, Entry> reference) { - EntryRemapper mapper = this.deobfuscator.getMapper(); + EntryRemapper mapper = this.project.getMapper(); SourceIndex index = this.currentSource.getIndex(); return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) @@ -337,7 +304,7 @@ public class GuiController { } public void navigateTo(Entry entry) { - if (!entryIsInJar(entry)) { + if (!project.isRenamable(entry)) { // entry is not in the jar. Ignore it return; } @@ -345,7 +312,7 @@ public class GuiController { } public void navigateTo(EntryReference, Entry> reference) { - if (!entryIsInJar(reference.getLocationClassEntry())) { + if (!project.isRenamable(reference.getLocationClassEntry())) { return; } openReference(reference); @@ -354,11 +321,38 @@ public class GuiController { private void refreshClasses() { List obfClasses = Lists.newArrayList(); List deobfClasses = Lists.newArrayList(); - this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); + this.addSeparatedClasses(obfClasses, deobfClasses); this.gui.setObfClasses(obfClasses); this.gui.setDeobfClasses(deobfClasses); } + public void addSeparatedClasses(List obfClasses, List deobfClasses) { + EntryRemapper mapper = project.getMapper(); + + Collection classes = project.getJarIndex().getEntryIndex().getClasses(); + Stream visibleClasses = classes.stream() + .filter(entry -> !entry.isInnerClass()); + + visibleClasses.forEach(entry -> { + ClassEntry deobfEntry = mapper.deobfuscate(entry); + + Optional obfService = enigma.getServices().get(ObfuscationTestService.TYPE); + boolean obfuscated = deobfEntry.equals(entry); + + if (obfuscated && obfService.isPresent()) { + if (obfService.get().testDeobfuscated(entry)) { + obfuscated = false; + } + } + + if (obfuscated) { + obfClasses.add(entry); + } else { + deobfClasses.add(entry); + } + }); + } + public void refreshCurrentClass() { refreshCurrentClass(null); } @@ -384,10 +378,10 @@ public class GuiController { DECOMPILER_SERVICE.submit(() -> { try { if (requiresDecompile) { - currentSource = decompileSource(targetClass, deobfuscator.getObfSourceProvider()); + currentSource = decompileSource(targetClass); } - remapSource(deobfuscator.getMapper().getDeobfuscator()); + remapSource(project.getMapper().getDeobfuscator()); callback.run(); } catch (Throwable t) { System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName()); @@ -396,7 +390,7 @@ public class GuiController { }); } - private DecompiledClassSource decompileSource(ClassEntry targetClass, SourceProvider sourceProvider) { + private DecompiledClassSource decompileSource(ClassEntry targetClass) { try { CompilationUnit sourceTree = sourceProvider.getSources(targetClass.getFullName()); if (sourceTree == null) { @@ -410,7 +404,7 @@ public class GuiController { String sourceString = sourceProvider.writeSourceToString(sourceTree); SourceIndex index = SourceIndex.buildIndex(sourceString, sourceTree, true); - index.resolveReferences(deobfuscator.getMapper().getObfResolver()); + index.resolveReferences(project.getMapper().getObfResolver()); return new DecompiledClassSource(targetClass, index); } catch (Throwable t) { @@ -426,20 +420,105 @@ public class GuiController { return; } - currentSource.remapSource(deobfuscator, translator); + currentSource.remapSource(project, translator); gui.setEditorTheme(Config.getInstance().lookAndFeel); gui.setSource(currentSource); } - public Deobfuscator getDeobfuscator() { - return deobfuscator; - } - public void modifierChange(ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { - deobfuscator.changeModifier(gui.cursorReference.entry, (AccessModifier) event.getItem()); + EntryRemapper mapper = project.getMapper(); + Entry entry = gui.cursorReference.entry; + AccessModifier modifier = (AccessModifier) event.getItem(); + + EntryMapping mapping = mapper.getDeobfMapping(entry); + if (mapping != null) { + mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier)); + } else { + mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier)); + } + refreshCurrentClass(); } } + + public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry); + return ClassInheritanceTreeNode.findNode(rootNode, entry); + } + + public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + return this.indexTreeBuilder.buildClassImplementations(translator, entry); + } + + public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry); + return MethodInheritanceTreeNode.findNode(rootNode, entry); + } + + public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + List rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry); + if (rootNodes.isEmpty()) { + return null; + } + if (rootNodes.size() > 1) { + System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one."); + } + return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry); + } + + public ClassReferenceTreeNode getClassReferences(ClassEntry entry) { + Translator deobfuscator = project.getMapper().getDeobfuscator(); + ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry); + rootNode.load(project.getJarIndex(), true); + return rootNode; + } + + public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry); + rootNode.load(project.getJarIndex(), true); + return rootNode; + } + + public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) { + Translator translator = project.getMapper().getDeobfuscator(); + MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry); + rootNode.load(project.getJarIndex(), true, recursive); + return rootNode; + } + + public void rename(EntryReference, Entry> reference, String newName, boolean refreshClassTree) { + Entry entry = reference.getNameableEntry(); + project.getMapper().mapFromObf(entry, new EntryMapping(newName)); + + if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) + this.gui.moveClassTree(reference, newName); + + refreshCurrentClass(reference); + } + + public void removeMapping(EntryReference, Entry> reference) { + project.getMapper().removeByObf(reference.getNameableEntry()); + + if (reference.entry instanceof ClassEntry) + this.gui.moveClassTree(reference, false, true); + refreshCurrentClass(reference); + } + + public void markAsDeobfuscated(EntryReference, Entry> reference) { + EntryRemapper mapper = project.getMapper(); + Entry entry = reference.getNameableEntry(); + mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName())); + + if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) + this.gui.moveClassTree(reference, true, false); + + refreshCurrentClass(reference); + } } diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java index 84fe7c8..c135d03 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java @@ -57,7 +57,7 @@ public class ProgressDialog implements ProgressListener, AutoCloseable { this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); } - public static void runInThread(final JFrame parent, final ProgressRunnable runnable) { + public static void runOffThread(final JFrame parent, final ProgressRunnable runnable) { new Thread(() -> { try (ProgressDialog progress = new ProgressDialog(parent)) { @@ -68,6 +68,7 @@ public class ProgressDialog implements ProgressListener, AutoCloseable { }).start(); } + @Override public void close() { this.frame.dispose(); } diff --git a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java index a122bd8..1657d7b 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java @@ -40,7 +40,7 @@ public class SearchDialog { this.parent = parent; deobfClasses = Lists.newArrayList(); - this.parent.getController().getDeobfuscator().getSeparatedClasses(Lists.newArrayList(), deobfClasses); + this.parent.getController().addSeparatedClasses(Lists.newArrayList(), deobfClasses); deobfClasses.removeIf(ClassEntry::isInnerClass); } diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 98275b4..5578325 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -13,10 +13,11 @@ import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.io.File; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; -import java.util.jar.JarFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; public class MenuBar extends JMenuBar { @@ -43,17 +44,9 @@ public class MenuBar extends JMenuBar { menu.add(item); item.addActionListener(event -> { this.gui.jarFileChooser.setVisible(true); - File file = new File(this.gui.jarFileChooser.getDirectory() + File.separator + this.gui.jarFileChooser.getFile()); - if (file.exists()) { - // load the jar in a separate thread - new Thread(() -> - { - try { - gui.getController().openJar(new JarFile(file)); - } catch (IOException ex) { - throw new Error(ex); - } - }).start(); + Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(this.gui.jarFileChooser.getFile()); + if (Files.exists(path)) { + gui.getController().openJar(path); } }); } @@ -106,7 +99,7 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> { // TODO: Use a specific file chooser for it if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().saveMappings(MappingFormat.ENIGMA_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), MappingFormat.ENIGMA_FILE); this.saveMappingsMenu.setEnabled(true); } }); @@ -118,7 +111,7 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> { // TODO: Use a specific file chooser for it if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().saveMappings(MappingFormat.ENIGMA_DIRECTORY, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), MappingFormat.ENIGMA_DIRECTORY); this.saveMappingsMenu.setEnabled(true); } }); @@ -131,7 +124,7 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> { // TODO: Use a specific file chooser for it if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().saveMappings(MappingFormat.SRG_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), MappingFormat.SRG_FILE); this.saveMappingsMenu.setEnabled(true); } }); @@ -162,7 +155,7 @@ public class MenuBar extends JMenuBar { menu.add(item); item.addActionListener(event -> { if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile()); + this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath()); } }); this.exportSourceMenu = item; @@ -173,8 +166,8 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> { this.gui.exportJarFileChooser.setVisible(true); if (this.gui.exportJarFileChooser.getFile() != null) { - File file = new File(this.gui.exportJarFileChooser.getDirectory() + File.separator + this.gui.exportJarFileChooser.getFile()); - this.gui.getController().exportJar(file); + Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile()); + this.gui.getController().exportJar(path); } }); this.exportJarMenu = item; @@ -202,7 +195,7 @@ public class MenuBar extends JMenuBar { search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK)); menu.add(search); search.addActionListener(event -> { - if (this.gui.getController().getDeobfuscator() != null) { + if (this.gui.getController().project != null) { new SearchDialog(this.gui).show(); } }); diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java index ff84648..e8a9cad 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java @@ -1,6 +1,6 @@ package cuchaz.enigma.gui.panels; -import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.config.Config; import cuchaz.enigma.gui.BrowserCaret; @@ -94,8 +94,8 @@ public class PanelEditor extends JEditorPane { if (!gui.popupMenu.renameMenu.isEnabled()) return; if (!event.isControlDown() && !event.isAltDown()) { - Deobfuscator deobfuscator = gui.getController().getDeobfuscator(); - EntryReference, Entry> reference = deobfuscator.deobfuscate(gui.cursorReference); + EnigmaProject project = gui.getController().project; + EntryReference, Entry> reference = project.getMapper().deobfuscate(gui.cursorReference); Entry entry = reference.getNameableEntry(); String name = String.valueOf(event.getKeyChar()); diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java index 8c4a326..c9808cc 100644 --- a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java +++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java @@ -21,7 +21,7 @@ public class EntryRemapper { private final MappingValidator validator; - public EntryRemapper(JarIndex jarIndex, EntryTree obfToDeobf) { + private EntryRemapper(JarIndex jarIndex, EntryTree obfToDeobf) { this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf); this.obfResolver = jarIndex.getEntryResolver(); @@ -31,8 +31,12 @@ public class EntryRemapper { this.validator = new MappingValidator(obfToDeobf, deobfuscator, jarIndex); } - public EntryRemapper(JarIndex jarIndex) { - this(jarIndex, new HashEntryTree<>()); + public static EntryRemapper mapped(JarIndex index, EntryTree obfToDeobf) { + return new EntryRemapper(index, obfToDeobf); + } + + public static EntryRemapper empty(JarIndex index) { + return new EntryRemapper(index, new HashEntryTree<>()); } public > void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { diff --git a/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java b/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java index ae5d6d2..1dc9748 100644 --- a/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java +++ b/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java @@ -11,13 +11,13 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.analysis.index.PackageVisibilityIndex; import cuchaz.enigma.translation.representation.entry.ClassEntry; import org.junit.Test; -import java.util.jar.JarFile; +import java.nio.file.Paths; import static cuchaz.enigma.TestEntryFactory.newClass; import static org.hamcrest.MatcherAssert.assertThat; @@ -35,10 +35,8 @@ public class PackageVisibilityIndexTest { private final JarIndex jarIndex; public PackageVisibilityIndexTest() throws Exception { - jarIndex = JarIndex.empty(); - ParsedJar jar = new ParsedJar(new JarFile("build/test-obf/packageAccess.jar")); - jarIndex.indexJar(jar, s -> { - }); + ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/packageAccess.jar")); + jarIndex = classCache.index(ProgressListener.none()); } @Test diff --git a/src/test/java/cuchaz/enigma/TestDeobfed.java b/src/test/java/cuchaz/enigma/TestDeobfed.java index 14b1418..3d875df 100644 --- a/src/test/java/cuchaz/enigma/TestDeobfed.java +++ b/src/test/java/cuchaz/enigma/TestDeobfed.java @@ -11,12 +11,12 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.index.JarIndex; import org.junit.BeforeClass; import org.junit.Test; -import java.util.jar.JarFile; +import java.nio.file.Paths; import static cuchaz.enigma.TestEntryFactory.newClass; import static org.hamcrest.MatcherAssert.assertThat; @@ -24,15 +24,16 @@ import static org.hamcrest.Matchers.containsInAnyOrder; public class TestDeobfed { - private static ParsedJar jar; + private static Enigma enigma; + private static ClassCache classCache; private static JarIndex index; @BeforeClass - public static void beforeClass() - throws Exception { - jar = new ParsedJar(new JarFile("build/test-deobf/translation.jar")); - index = JarIndex.empty(); - index.indexJar(jar, s -> {}); + public static void beforeClass() throws Exception { + enigma = Enigma.create(); + + classCache = ClassCache.of(Paths.get("build/test-deobf/translation.jar")); + index = classCache.index(ProgressListener.none()); } @Test @@ -67,8 +68,9 @@ public class TestDeobfed { @Test public void decompile() throws Exception { - Deobfuscator deobfuscator = new Deobfuscator(jar); - SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); + EnigmaProject project = new EnigmaProject(enigma, classCache, index); + + SourceProvider sourceProvider = project.getObfSourceProvider(); sourceProvider.getSources("a"); sourceProvider.getSources("b"); sourceProvider.getSources("c"); diff --git a/src/test/java/cuchaz/enigma/TestDeobfuscator.java b/src/test/java/cuchaz/enigma/TestDeobfuscator.java index e070b66..5b9611c 100644 --- a/src/test/java/cuchaz/enigma/TestDeobfuscator.java +++ b/src/test/java/cuchaz/enigma/TestDeobfuscator.java @@ -23,9 +23,9 @@ import static org.junit.Assert.assertEquals; public class TestDeobfuscator { - private Deobfuscator getDeobfuscator() + private Enigma getDeobfuscator() throws IOException { - return new Deobfuscator(new JarFile("build/test-obf/loneClass.jar")); + return new Enigma(new JarFile("build/test-obf/loneClass.jar")); } @Test @@ -37,10 +37,10 @@ public class TestDeobfuscator { @Test public void getClasses() throws Exception { - Deobfuscator deobfuscator = getDeobfuscator(); + Enigma enigma = getDeobfuscator(); List obfClasses = Lists.newArrayList(); List deobfClasses = Lists.newArrayList(); - deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); + enigma.getSeparatedClasses(obfClasses, deobfClasses); assertEquals(1, obfClasses.size()); assertEquals("a", obfClasses.get(0).getName()); assertEquals(1, deobfClasses.size()); @@ -50,8 +50,8 @@ public class TestDeobfuscator { @Test public void decompileClass() throws Exception { - Deobfuscator deobfuscator = getDeobfuscator(); - SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); + Enigma enigma = getDeobfuscator(); + SourceProvider sourceProvider = enigma.getObfSourceProvider(); sourceProvider.writeSourceToString(sourceProvider.getSources("a")); } } diff --git a/src/test/java/cuchaz/enigma/TestInnerClasses.java b/src/test/java/cuchaz/enigma/TestInnerClasses.java index 8738fd7..b6e4e2d 100644 --- a/src/test/java/cuchaz/enigma/TestInnerClasses.java +++ b/src/test/java/cuchaz/enigma/TestInnerClasses.java @@ -11,11 +11,12 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.translation.representation.entry.ClassEntry; import org.junit.Test; +import java.nio.file.Paths; import java.util.jar.JarFile; import static cuchaz.enigma.TestEntryFactory.newClass; @@ -33,14 +34,14 @@ public class TestInnerClasses { private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a"); private static final ClassEntry ClassTreeLevel3 = newClass("f$a$a$a"); private JarIndex index; - private Deobfuscator deobfuscator; + private Enigma enigma; public TestInnerClasses() throws Exception { - index = JarIndex.empty(); - ParsedJar jar = new ParsedJar(new JarFile("build/test-obf/innerClasses.jar")); - index.indexJar(jar, s -> {}); - deobfuscator = new Deobfuscator(jar); + ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/innerClasses.jar")); + index = classCache.index(ProgressListener.none()); + + enigma = new Enigma(jar); } @Test @@ -79,6 +80,6 @@ public class TestInnerClasses { } private void decompile(ClassEntry classEntry) { - deobfuscator.getObfSourceProvider().getSources(classEntry.getName()); + enigma.getObfSourceProvider().getSources(classEntry.getName()); } } diff --git a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java index c3f3b66..0712ccf 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java @@ -12,7 +12,6 @@ package cuchaz.enigma; import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.ParsedJar; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.MethodDefEntry; diff --git a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java index 36595a3..76e379c 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java @@ -11,8 +11,8 @@ package cuchaz.enigma; +import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.ParsedJar; import cuchaz.enigma.analysis.index.EntryIndex; import cuchaz.enigma.analysis.index.InheritanceIndex; import cuchaz.enigma.analysis.index.JarIndex; @@ -26,8 +26,8 @@ import cuchaz.enigma.translation.representation.entry.MethodEntry; import org.junit.Test; import org.objectweb.asm.Opcodes; +import java.nio.file.Paths; import java.util.Collection; -import java.util.jar.JarFile; import static cuchaz.enigma.TestEntryFactory.*; import static org.hamcrest.MatcherAssert.assertThat; @@ -46,8 +46,8 @@ public class TestJarIndexInheritanceTree { public TestJarIndexInheritanceTree() throws Exception { - index = JarIndex.empty(); - index.indexJar(new ParsedJar(new JarFile("build/test-obf/inheritanceTree.jar")), s -> {}); + ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/inheritanceTree.jar")); + index = classCache.index(ProgressListener.none()); } @Test diff --git a/src/test/java/cuchaz/enigma/TestSourceIndex.java b/src/test/java/cuchaz/enigma/TestSourceIndex.java index ce5d631..8a604f8 100644 --- a/src/test/java/cuchaz/enigma/TestSourceIndex.java +++ b/src/test/java/cuchaz/enigma/TestSourceIndex.java @@ -41,17 +41,17 @@ public class TestSourceIndex { mcJar = new File(mcDir, "versions/1.8.3/1.8.3.jar"); } - Deobfuscator deobfuscator = new Deobfuscator(new JarFile(mcJar)); + Enigma enigma = new Enigma(new JarFile(mcJar)); // get all classes that aren't inner classes Set classEntries = Sets.newHashSet(); - for (ClassEntry obfClassEntry : deobfuscator.getJarIndex().getEntryIndex().getClasses()) { + for (ClassEntry obfClassEntry : enigma.getJarIndex().getEntryIndex().getClasses()) { if (!obfClassEntry.isInnerClass()) { classEntries.add(obfClassEntry); } } - SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); + SourceProvider sourceProvider = enigma.getObfSourceProvider(); for (ClassEntry obfClassEntry : classEntries) { try { CompilationUnit tree = sourceProvider.getSources(obfClassEntry.getName()); diff --git a/src/test/java/cuchaz/enigma/TestTranslator.java b/src/test/java/cuchaz/enigma/TestTranslator.java index b978129..a420afe 100644 --- a/src/test/java/cuchaz/enigma/TestTranslator.java +++ b/src/test/java/cuchaz/enigma/TestTranslator.java @@ -23,7 +23,7 @@ public class TestTranslator { public static void beforeClass() throws Exception { //TODO FIx - //deobfuscator = new Deobfuscator(new JarFile("build/test-obf/translation.jar")); + //deobfuscator = new Enigma(new JarFile("build/test-obf/translation.jar")); //try (InputStream in = TestTranslator.class.getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) { // mappings = new MappingsJsonReader().read(new InputStreamReader(in)); // deobfuscator.setMappings(mappings); diff --git a/src/test/java/cuchaz/enigma/TokenChecker.java b/src/test/java/cuchaz/enigma/TokenChecker.java index c4670a2..9e0c696 100644 --- a/src/test/java/cuchaz/enigma/TokenChecker.java +++ b/src/test/java/cuchaz/enigma/TokenChecker.java @@ -25,16 +25,16 @@ import java.util.jar.JarFile; public class TokenChecker { - private Deobfuscator deobfuscator; + private Enigma enigma; protected TokenChecker(JarFile jarFile) throws IOException { - deobfuscator = new Deobfuscator(jarFile); + enigma = new Enigma(jarFile); } protected String getDeclarationToken(Entry entry) { // decompile the class - SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); + SourceProvider sourceProvider = enigma.getObfSourceProvider(); CompilationUnit tree = sourceProvider.getSources(entry.getContainingClass().getFullName()); // DEBUG // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null ); @@ -52,7 +52,7 @@ public class TokenChecker { @SuppressWarnings("unchecked") protected Collection getReferenceTokens(EntryReference, ? extends Entry> reference) { // decompile the class - SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); + SourceProvider sourceProvider = enigma.getObfSourceProvider(); CompilationUnit tree = sourceProvider.getSources(reference.context.getContainingClass().getFullName()); String source = sourceProvider.writeSourceToString(tree); SourceIndex index = SourceIndex.buildIndex(source, tree, true); -- cgit v1.2.3