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 +-
36 files changed, 1105 insertions(+), 828 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/main/java')
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java
deleted file mode 100644
index 32f7aa7c..00000000
--- 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 00000000..9f88f774
--- /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 00000000..9dc5ff22
--- /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 00000000..82fc0bdf
--- /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 00000000..86507bca
--- /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 ccfc51f0..76a3fff0 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 00000000..269d31e1
--- /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 ffce297d..6da3b81a 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 48e5a590..662f1f92 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 00000000..0bd78b32
--- /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 ddcda3ed..00000000
--- 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 fd4e618b..300425b8 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 00000000..bdd60150
--- /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 00000000..a59051ad
--- /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 3fc26d27..00000000
--- 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 965b0c45..00000000
--- 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 00000000..526dda77
--- /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 00000000..9e433fb0
--- /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 00000000..7c10ac26
--- /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 00000000..358828f0
--- /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 00000000..0cda1998
--- /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 00000000..4c357db1
--- /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 00000000..af0cf30b
--- /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 7ec7679c..08e73e6a 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 b107fb61..41d7bfae 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 a58d9085..bc23d01d 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 5d499385..c24e6613 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 39d0333b..5051032d 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 d7c981ac..44f70f8c 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 a61f4ddd..f5dd8a04 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 16833331..a55d2cd3 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 84fe7c88..c135d033 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 a122bd8a..1657d7b3 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 98275b4a..5578325b 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 ff84648c..e8a9cadc 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 8c4a3268..c9808cc9 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) {
--
cgit v1.2.3