From ba7a354efae7d49833c887cf147ac940c975a1fa Mon Sep 17 00:00:00 2001
From: Gegy
Date: Wed, 30 Jan 2019 21:05:32 +0200
Subject: Remap sources (#106)
* Source remapping beginnings
* Fix navigation to remapped classes
* Translate identifier info reference
* Remap local variables with default names in source
* Caching translator
* Fix lack of highlighting for first opened class
* Fix unicode variable names
* Unicode checker shouldn't be checking just alphanumeric
* Fix package tree being built from obf names
* Don't index `this` as method call for method::reference
* Apply proposed names
* Fix source export issues
* Replace unicode var names at bytecode level uniquely
* Drop imports from editor source
* Class selector fixes
* Delta keep track of base mappings to enable lookup of old names
* Optimize source remapping by remapping source with a StringBuffer instead of copying
* Bump version
---
.../cuchaz/enigma/CachingClasspathTypeLoader.java | 1 +
src/main/java/cuchaz/enigma/CommandMain.java | 4 +-
src/main/java/cuchaz/enigma/CompiledSource.java | 10 +
.../cuchaz/enigma/CompiledSourceTypeLoader.java | 125 +++++++++
src/main/java/cuchaz/enigma/Deobfuscator.java | 267 ++++++++----------
.../java/cuchaz/enigma/ITranslatingTypeLoader.java | 19 --
src/main/java/cuchaz/enigma/SourceProvider.java | 97 +++++++
.../java/cuchaz/enigma/SynchronizedTypeLoader.java | 27 +-
.../java/cuchaz/enigma/TranslatingTypeLoader.java | 154 -----------
.../enigma/analysis/DropImportAstTransform.java | 33 +++
.../java/cuchaz/enigma/analysis/ParsedJar.java | 34 ++-
.../java/cuchaz/enigma/analysis/SourceIndex.java | 86 +++---
.../enigma/analysis/SourceIndexClassVisitor.java | 14 +-
.../enigma/analysis/SourceIndexMethodVisitor.java | 64 ++---
.../cuchaz/enigma/analysis/SourceIndexVisitor.java | 9 +-
src/main/java/cuchaz/enigma/analysis/Token.java | 6 +-
.../enigma/analysis/index/BridgeMethodIndex.java | 16 +-
.../cuchaz/enigma/analysis/index/EntryIndex.java | 20 +-
.../enigma/analysis/index/InheritanceIndex.java | 18 +-
.../cuchaz/enigma/analysis/index/JarIndex.java | 22 +-
.../enigma/analysis/index/RemappableIndex.java | 9 -
.../translators/LocalVariableFixVisitor.java | 80 ++++++
.../translators/TranslationClassVisitor.java | 7 +-
.../translators/TranslationMethodVisitor.java | 111 +++-----
src/main/java/cuchaz/enigma/config/Config.java | 5 +-
src/main/java/cuchaz/enigma/config/Themes.java | 17 +-
src/main/java/cuchaz/enigma/gui/ClassSelector.java | 182 +++++++-----
src/main/java/cuchaz/enigma/gui/CodeReader.java | 94 +------
.../cuchaz/enigma/gui/DecompiledClassSource.java | 129 +++++++++
src/main/java/cuchaz/enigma/gui/Gui.java | 102 ++++---
src/main/java/cuchaz/enigma/gui/GuiController.java | 305 +++++++++------------
.../java/cuchaz/enigma/gui/SourceRemapper.java | 64 +++++
.../java/cuchaz/enigma/gui/elements/MenuBar.java | 8 +-
.../enigma/gui/highlight/BoxHighlightPainter.java | 4 +-
.../enigma/gui/highlight/TokenHighlightType.java | 7 +
.../enigma/gui/node/ClassSelectorClassNode.java | 8 +-
.../enigma/translation/LocalNameGenerator.java | 44 +++
.../enigma/translation/mapping/EntryRemapper.java | 125 +--------
.../enigma/translation/mapping/MappingDelta.java | 36 +--
.../translation/mapping/MappingValidator.java | 14 +-
.../mapping/serde/EnigmaMappingsWriter.java | 19 +-
.../translation/mapping/serde/MappingFormat.java | 2 +-
.../translation/mapping/serde/MappingsWriter.java | 6 +-
.../mapping/serde/SrgMappingsWriter.java | 2 +-
.../mapping/tree/DeltaTrackingTree.java | 21 +-
.../enigma/translation/mapping/tree/EntryTree.java | 9 +-
.../translation/mapping/tree/HashEntryTree.java | 22 ++
.../representation/ProcyonEntryFactory.java | 18 +-
.../representation/ReferencedEntryPool.java | 60 ----
.../translation/representation/Signature.java | 9 +-
.../translation/representation/TypeDescriptor.java | 5 +
.../representation/entry/ClassEntry.java | 11 +
.../translation/representation/entry/Entry.java | 4 +
.../representation/entry/LocalVariableEntry.java | 7 +-
54 files changed, 1313 insertions(+), 1259 deletions(-)
create mode 100644 src/main/java/cuchaz/enigma/CompiledSource.java
create mode 100644 src/main/java/cuchaz/enigma/CompiledSourceTypeLoader.java
delete mode 100644 src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java
create mode 100644 src/main/java/cuchaz/enigma/SourceProvider.java
delete mode 100644 src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/DropImportAstTransform.java
delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java
create mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java
create mode 100644 src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
create mode 100644 src/main/java/cuchaz/enigma/gui/SourceRemapper.java
create mode 100644 src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java
create mode 100644 src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java
delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java
(limited to 'src/main/java/cuchaz/enigma')
diff --git a/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java
index fe13321..b2aed84 100644
--- a/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java
+++ b/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java
@@ -16,6 +16,7 @@ public class CachingClasspathTypeLoader extends CachingTypeLoader {
private final ITypeLoader classpathLoader = new ClasspathTypeLoader();
+ @Override
protected byte[] doLoad(String className) {
Buffer parentBuf = new Buffer();
if (classpathLoader.tryLoadType(className, parentBuf)) {
diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java
index a84cd5e..7c0a3d5 100644
--- a/src/main/java/cuchaz/enigma/CommandMain.java
+++ b/src/main/java/cuchaz/enigma/CommandMain.java
@@ -58,7 +58,7 @@ public class CommandMain {
File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true));
Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false));
Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
- deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener());
+ deobfuscator.writeSources(fileJarOut.toPath(), new ConsoleProgressListener());
}
private static void deobfuscate(String[] args) throws Exception {
@@ -66,7 +66,7 @@ public class CommandMain {
File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false));
Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
- deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener());
+ deobfuscator.writeTransformedJar(fileJarOut, new ConsoleProgressListener());
}
private static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception {
diff --git a/src/main/java/cuchaz/enigma/CompiledSource.java b/src/main/java/cuchaz/enigma/CompiledSource.java
new file mode 100644
index 0000000..fc051d3
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/CompiledSource.java
@@ -0,0 +1,10 @@
+package cuchaz.enigma;
+
+import org.objectweb.asm.tree.ClassNode;
+
+import javax.annotation.Nullable;
+
+public interface CompiledSource {
+ @Nullable
+ ClassNode getClassNode(String name);
+}
diff --git a/src/main/java/cuchaz/enigma/CompiledSourceTypeLoader.java b/src/main/java/cuchaz/enigma/CompiledSourceTypeLoader.java
new file mode 100644
index 0000000..b1a8cd5
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/CompiledSourceTypeLoader.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * 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.collect.Lists;
+import com.strobel.assembler.metadata.Buffer;
+import com.strobel.assembler.metadata.ITypeLoader;
+import cuchaz.enigma.translation.representation.entry.ClassEntry;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.Collection;
+import java.util.List;
+
+public class CompiledSourceTypeLoader extends CachingTypeLoader {
+ //Store one instance as the classpath shouldn't change during load
+ private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader();
+
+ private final CompiledSource compiledSource;
+
+ public CompiledSourceTypeLoader(CompiledSource compiledSource) {
+ this.compiledSource = compiledSource;
+ }
+
+ @Override
+ protected byte[] doLoad(String className) {
+ byte[] data = loadType(className);
+ if (data == null) {
+ return loadClasspath(className);
+ }
+
+ return data;
+ }
+
+ private byte[] loadClasspath(String name) {
+ Buffer parentBuf = new Buffer();
+ if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) {
+ return parentBuf.array();
+ }
+ return EMPTY_ARRAY;
+ }
+
+ private byte[] loadType(String className) {
+ ClassEntry entry = new ClassEntry(className);
+
+ // find the class in the jar
+ ClassNode node = findClassNode(entry);
+ if (node == null) {
+ // couldn't find it
+ return null;
+ }
+
+ removeRedundantClassCalls(node);
+
+ ClassWriter writer = new ClassWriter(0);
+ node.accept(writer);
+
+ // we have a transformed class!
+ return writer.toByteArray();
+ }
+
+ private void removeRedundantClassCalls(ClassNode node) {
+ // remove .getClass() calls that are seemingly injected
+ // DUP
+ // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
+ // POP
+ for (MethodNode methodNode : node.methods) {
+ AbstractInsnNode insnNode = methodNode.instructions.getFirst();
+ while (insnNode != null) {
+ if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
+ MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
+ if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) {
+ AbstractInsnNode previous = methodInsnNode.getPrevious();
+ AbstractInsnNode next = methodInsnNode.getNext();
+ if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) {
+ insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction
+ methodNode.instructions.remove(previous);
+ methodNode.instructions.remove(methodInsnNode);
+ methodNode.instructions.remove(next);
+ }
+ }
+ }
+ insnNode = insnNode.getNext();
+ }
+ }
+ }
+
+ private ClassNode findClassNode(ClassEntry entry) {
+ // try to find the class in the jar
+ for (String className : getClassNamesToTry(entry)) {
+ ClassNode node = compiledSource.getClassNode(className);
+ if (node != null) {
+ return node;
+ }
+ }
+
+ // didn't find it ;_;
+ return null;
+ }
+
+ private Collection getClassNamesToTry(ClassEntry entry) {
+ List classNamesToTry = Lists.newArrayList();
+ classNamesToTry.add(entry.getFullName());
+
+ ClassEntry outerClass = entry.getOuterClass();
+ if (outerClass != null) {
+ classNamesToTry.addAll(getClassNamesToTry(outerClass));
+ }
+
+ return classNamesToTry;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java
index 076c546..ef452b0 100644
--- a/src/main/java/cuchaz/enigma/Deobfuscator.java
+++ b/src/main/java/cuchaz/enigma/Deobfuscator.java
@@ -11,36 +11,39 @@
package cuchaz.enigma;
+import com.google.common.base.Functions;
import com.google.common.base.Stopwatch;
-import com.google.common.collect.Lists;
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.DecompilerContext;
import com.strobel.decompiler.DecompilerSettings;
-import com.strobel.decompiler.PlainTextOutput;
-import com.strobel.decompiler.languages.java.JavaOutputVisitor;
-import com.strobel.decompiler.languages.java.ast.AstBuilder;
import com.strobel.decompiler.languages.java.ast.CompilationUnit;
-import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
-import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
-import cuchaz.enigma.analysis.*;
+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.EnigmaPlugin;
+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.ReferencedEntryPool;
import cuchaz.enigma.translation.representation.entry.ClassEntry;
import cuchaz.enigma.translation.representation.entry.Entry;
import cuchaz.enigma.translation.representation.entry.MethodEntry;
-import cuchaz.enigma.utils.Utils;
-import oml.ast.transformers.*;
+import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
-import java.io.*;
+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;
@@ -53,11 +56,12 @@ import java.util.stream.Collectors;
public class Deobfuscator {
private final ServiceLoader plugins = ServiceLoader.load(EnigmaPlugin.class);
- private final ReferencedEntryPool entryPool = new ReferencedEntryPool();
private final ParsedJar parsedJar;
- private final DecompilerSettings settings;
private final JarIndex jarIndex;
private final IndexTreeBuilder indexTreeBuilder;
+
+ private final SourceProvider obfSourceProvider;
+
private EntryRemapper mapper;
public Deobfuscator(ParsedJar jar, Consumer listener) {
@@ -75,15 +79,8 @@ public class Deobfuscator {
this.indexTreeBuilder = new IndexTreeBuilder(jarIndex);
listener.accept("Preparing...");
- // config the decompiler
- this.settings = DecompilerSettings.javaDefaults();
- this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true));
- this.settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true));
- this.settings.setForceExplicitTypeArguments(
- Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true));
- // DEBUG
- this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false));
- this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false));
+
+ this.obfSourceProvider = new SourceProvider(SourceProvider.createSettings(), new CompiledSourceTypeLoader(parsedJar));
// init mappings
mapper = new EntryRemapper(jarIndex);
@@ -93,7 +90,7 @@ public class Deobfuscator {
this(new ParsedJar(jar), listener);
}
- public Deobfuscator(ParsedJar jar) throws IOException {
+ public Deobfuscator(ParsedJar jar) {
this(jar, (msg) -> {
});
}
@@ -128,9 +125,9 @@ public class Deobfuscator {
Collection> dropped = dropMappings(mappings);
mapper = new EntryRemapper(jarIndex, mappings);
- DeltaTrackingTree deobfToObf = mapper.getDeobfToObf();
+ DeltaTrackingTree obfToDeobf = mapper.getObfToDeobf();
for (Entry> entry : dropped) {
- deobfToObf.trackDeletion(entry);
+ obfToDeobf.trackDeletion(entry);
}
} else {
mapper = new EntryRemapper(jarIndex);
@@ -161,7 +158,7 @@ public class Deobfuscator {
ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry);
if (!deobfClassEntry.equals(obfClassEntry)) {
// if the class has a mapping, clearly it's deobfuscated
- deobfClasses.add(deobfClassEntry);
+ deobfClasses.add(obfClassEntry);
} else if (obfClassEntry.getPackageName() != null) {
// also call it deobufscated if it's not in the none package
deobfClasses.add(obfClassEntry);
@@ -172,151 +169,126 @@ public class Deobfuscator {
}
}
- public TranslatingTypeLoader createTypeLoader() {
- return new TranslatingTypeLoader(
- this.parsedJar,
- this.jarIndex,
- this.entryPool,
- this.mapper.getObfuscator(),
- this.mapper.getDeobfuscator()
- );
- }
-
- public CompilationUnit getSourceTree(String className) {
- return getSourceTree(className, createTypeLoader());
+ public SourceProvider getObfSourceProvider() {
+ return obfSourceProvider;
}
- public CompilationUnit getSourceTree(String className, ITranslatingTypeLoader loader) {
- return getSourceTree(className, loader, new NoRetryMetadataSystem(loader));
- }
+ public void writeSources(Path outputDirectory, ProgressListener progress) {
+ // get the classes to decompile
+ Collection classEntries = jarIndex.getEntryIndex().getClasses();
- public CompilationUnit getSourceTree(String className, ITranslatingTypeLoader loader, MetadataSystem metadataSystem) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
- // we don't know if this class name is obfuscated or deobfuscated
- // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
- // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one
+ try {
+ Translator deobfuscator = mapper.getDeobfuscator();
- String deobfClassName = mapper.deobfuscate(new ClassEntry(className)).getFullName();
+ // deobfuscate everything first
+ Map translatedNodes = deobfuscateClasses(progress, classEntries, deobfuscator);
- // set the desc loader
- this.settings.setTypeLoader(loader);
+ decompileClasses(outputDirectory, progress, translatedNodes);
+ } finally {
+ stopwatch.stop();
- // see if procyon can find the desc
- TypeReference type = metadataSystem.lookupType(deobfClassName);
- if (type == null) {
- throw new Error(String.format("Unable to find desc: %s (deobf: %s)\nTried class names: %s",
- className, deobfClassName, loader.getClassNamesToTry(deobfClassName)
- ));
+ System.out.println("writeSources Done in : " + stopwatch.toString());
}
- TypeDefinition resolvedType = type.resolve();
-
- // decompile it!
- DecompilerContext context = new DecompilerContext();
- context.setCurrentType(resolvedType);
- context.setSettings(this.settings);
- AstBuilder builder = new AstBuilder(context);
- builder.addType(resolvedType);
- builder.runTransformations(null);
- runCustomTransforms(builder, context);
- return builder.getCompilationUnit();
}
- public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) {
- return getSourceIndex(sourceTree, source, true);
- }
-
- public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, boolean ignoreBadTokens) {
-
- // build the source index
- SourceIndex index = new SourceIndex(source, ignoreBadTokens);
- sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), index);
-
- EntryResolver resolver = mapper.getDeobfResolver();
-
- Collection tokens = Lists.newArrayList(index.referenceTokens());
-
- // resolve all the classes in the source references
- for (Token token : tokens) {
- EntryReference, Entry>> deobfReference = index.getDeobfReference(token);
- index.replaceDeobfReference(token, resolver.resolveFirstReference(deobfReference, ResolutionStrategy.RESOLVE_CLOSEST));
+ private Map deobfuscateClasses(ProgressListener progress, Collection classEntries, Translator translator) {
+ AtomicInteger count = new AtomicInteger();
+ if (progress != null) {
+ progress.init(classEntries.size(), "Deobfuscating classes...");
}
- return index;
- }
+ 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;
+ }
- public String getSource(CompilationUnit sourceTree) {
- // render the AST into source
- StringWriter buf = new StringWriter();
- sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
- sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), this.settings), null);
- return buf.toString();
+ return null;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toMap(n -> n.name, Functions.identity()));
}
- public void writeSources(File dirOut, ProgressListener progress) {
- // get the classes to decompile
- Set classEntries = jarIndex.getEntryIndex().getClasses().stream()
- .filter(classEntry -> !classEntry.isInnerClass())
- .collect(Collectors.toSet());
+ 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(classEntries.size(), "Decompiling classes...");
+ progress.init(decompileClasses.size(), "Decompiling classes...");
}
//create a common instance outside the loop as mappings shouldn't be changing while this is happening
//synchronized to make sure the parallelStream doesn't CME with the cache
- ITranslatingTypeLoader typeLoader = new SynchronizedTypeLoader(createTypeLoader());
+ ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(translatedClasses::get));
- MetadataSystem metadataSystem = new NoRetryMetadataSystem(typeLoader);
- metadataSystem.setEagerMethodLoadingEnabled(true);//ensures methods are loaded on classload and prevents race conditions
+ MetadataSystem metadataSystem = new Deobfuscator.NoRetryMetadataSystem(typeLoader);
+
+ //ensures methods are loaded on classload and prevents race conditions
+ metadataSystem.setEagerMethodLoadingEnabled(true);
+
+ DecompilerSettings settings = SourceProvider.createSettings();
+ SourceProvider sourceProvider = new SourceProvider(settings, typeLoader, metadataSystem);
- // DEOBFUSCATE ALL THE THINGS!! @_@
- Stopwatch stopwatch = Stopwatch.createStarted();
AtomicInteger count = new AtomicInteger();
- classEntries.parallelStream().forEach(obfClassEntry -> {
- ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry);
+
+ decompileClasses.parallelStream().forEach(translatedNode -> {
if (progress != null) {
- progress.step(count.getAndIncrement(), deobfClassEntry.toString());
+ progress.step(count.getAndIncrement(), translatedNode.name);
}
- try {
- // get the source
- CompilationUnit sourceTree = getSourceTree(obfClassEntry.getName(), typeLoader, metadataSystem);
+ decompileClass(outputDirectory, translatedNode, sourceProvider);
+ });
+ }
- // write the file
- File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
- file.getParentFile().mkdirs();
- try (Writer writer = new BufferedWriter(new FileWriter(file))) {
- sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
- sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null);
- }
- } 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 " + deobfClassEntry + " (" + obfClassEntry + ")");
- t.printStackTrace(System.err);
+ 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);
}
- });
- stopwatch.stop();
- System.out.println("writeSources Done in : " + stopwatch.toString());
- if (progress != null) {
- progress.step(count.get(), "Done:");
+ } 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 writeJar(File out, ProgressListener progress) {
- transformJar(out, progress, createTypeLoader()::transformInto);
+ 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 transformJar(File out, ProgressListener progress, ClassTransformer transformer) {
+ 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 i = new AtomicInteger();
+ AtomicInteger count = new AtomicInteger();
parsedJar.visitNode(node -> {
if (progress != null) {
- progress.step(i.getAndIncrement(), node.name);
+ progress.step(count.getAndIncrement(), node.name);
}
try {
@@ -329,10 +301,6 @@ public class Deobfuscator {
throw new Error("Unable to transform class " + node.name, t);
}
});
-
- if (progress != null) {
- progress.step(i.get(), "Done!");
- }
} catch (IOException ex) {
throw new Error("Unable to write to Jar file!");
}
@@ -355,7 +323,7 @@ public class Deobfuscator {
}
}
- public boolean isObfuscatedIdentifier(Entry> obfEntry) {
+ public boolean isRenamable(Entry> obfEntry) {
if (obfEntry instanceof MethodEntry) {
// HACKHACK: Object methods are not obfuscated identifiers
MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
@@ -389,12 +357,15 @@ public class Deobfuscator {
return this.jarIndex.getEntryIndex().hasEntry(obfEntry);
}
- public boolean isRenameable(EntryReference, Entry>> obfReference) {
- return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry());
+ public boolean isRenamable(EntryReference, Entry>> obfReference) {
+ return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry());
}
- public boolean hasDeobfuscatedName(Entry> obfEntry) {
- return mapper.hasDeobfMapping(obfEntry);
+ 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) {
@@ -409,21 +380,12 @@ public class Deobfuscator {
mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()));
}
- public static void runCustomTransforms(AstBuilder builder, DecompilerContext context) {
- List transformers = Arrays.asList(
- new ObfuscatedEnumSwitchRewriterTransform(context),
- new VarargsFixer(context),
- new RemoveObjectCasts(context),
- new Java8Generics(),
- new InvalidIdentifierFix()
- );
- for (IAstTransform transform : transformers) {
- transform.run(builder.getCompilationUnit());
- }
+ public T deobfuscate(T translatable) {
+ return mapper.deobfuscate(translatable);
}
public interface ClassTransformer {
- String transform(ClassNode node, ClassWriter writer);
+ String transform(ClassNode node, ClassVisitor visitor);
}
public static class NoRetryMetadataSystem extends MetadataSystem {
@@ -448,6 +410,7 @@ public class Deobfuscator {
return result;
}
+ @Override
public synchronized TypeDefinition resolve(final TypeReference type) {
return super.resolve(type);
}
diff --git a/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java
deleted file mode 100644
index 24822dd..0000000
--- a/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package cuchaz.enigma;
-
-import com.strobel.assembler.metadata.ITypeLoader;
-import cuchaz.enigma.translation.representation.entry.ClassEntry;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.tree.ClassNode;
-
-import java.util.List;
-
-/**
- * For delegation of TranslatingTypeLoader without needing the subclass the whole thing
- */
-public interface ITranslatingTypeLoader extends ITypeLoader {
- List getClassNamesToTry(String className);
-
- List getClassNamesToTry(ClassEntry obfClassEntry);
-
- String transformInto(ClassNode node, ClassWriter writer);
-}
diff --git a/src/main/java/cuchaz/enigma/SourceProvider.java b/src/main/java/cuchaz/enigma/SourceProvider.java
new file mode 100644
index 0000000..48e5a59
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/SourceProvider.java
@@ -0,0 +1,97 @@
+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 com.strobel.decompiler.DecompilerContext;
+import com.strobel.decompiler.DecompilerSettings;
+import com.strobel.decompiler.PlainTextOutput;
+import com.strobel.decompiler.languages.java.JavaOutputVisitor;
+import com.strobel.decompiler.languages.java.ast.AstBuilder;
+import com.strobel.decompiler.languages.java.ast.CompilationUnit;
+import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
+import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
+import cuchaz.enigma.utils.Utils;
+import oml.ast.transformers.*;
+
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.List;
+
+public class SourceProvider {
+ private final DecompilerSettings settings;
+
+ private final ITypeLoader typeLoader;
+ private final MetadataSystem metadataSystem;
+
+ public SourceProvider(DecompilerSettings settings, ITypeLoader typeLoader, MetadataSystem metadataSystem) {
+ this.settings = settings;
+ this.typeLoader = typeLoader;
+ this.metadataSystem = metadataSystem;
+ }
+
+ public SourceProvider(DecompilerSettings settings, ITypeLoader typeLoader) {
+ this(settings, typeLoader, new Deobfuscator.NoRetryMetadataSystem(typeLoader));
+ }
+
+ public static DecompilerSettings createSettings() {
+ DecompilerSettings settings = DecompilerSettings.javaDefaults();
+ settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true));
+ settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true));
+ settings.setForceExplicitTypeArguments(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true));
+ settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false));
+ settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false));
+
+ return settings;
+ }
+
+ public CompilationUnit getSources(String name) {
+ TypeReference type = metadataSystem.lookupType(name);
+ if (type == null) {
+ throw new Error(String.format("Unable to find desc: %s", name));
+ }
+
+ TypeDefinition resolvedType = type.resolve();
+
+ settings.setTypeLoader(typeLoader);
+
+ // decompile it!
+ DecompilerContext context = new DecompilerContext();
+ context.setCurrentType(resolvedType);
+ context.setSettings(settings);
+
+ AstBuilder builder = new AstBuilder(context);
+ builder.addType(resolvedType);
+ builder.runTransformations(null);
+ runCustomTransforms(builder, context);
+
+ return builder.getCompilationUnit();
+ }
+
+ public void writeSource(Writer writer, CompilationUnit sourceTree) {
+ // render the AST into source
+ sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
+ sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null);
+ }
+
+ public String writeSourceToString(CompilationUnit sourceTree) {
+ StringWriter writer = new StringWriter();
+ writeSource(writer, sourceTree);
+ return writer.toString();
+ }
+
+ private static void runCustomTransforms(AstBuilder builder, DecompilerContext context) {
+ List transformers = Arrays.asList(
+ new ObfuscatedEnumSwitchRewriterTransform(context),
+ new VarargsFixer(context),
+ new RemoveObjectCasts(context),
+ new Java8Generics(),
+ new InvalidIdentifierFix()
+ );
+ for (IAstTransform transform : transformers) {
+ transform.run(builder.getCompilationUnit());
+ }
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java
index 657bee4..f6eee69 100644
--- a/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java
+++ b/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java
@@ -1,37 +1,18 @@
package cuchaz.enigma;
import com.strobel.assembler.metadata.Buffer;
-import cuchaz.enigma.translation.representation.entry.ClassEntry;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.tree.ClassNode;
-
-import java.util.List;
+import com.strobel.assembler.metadata.ITypeLoader;
/**
* Typeloader with synchronized tryLoadType method
*/
-public class SynchronizedTypeLoader implements ITranslatingTypeLoader {
- private final TranslatingTypeLoader delegate;
+public class SynchronizedTypeLoader implements ITypeLoader {
+ private final ITypeLoader delegate;
- public SynchronizedTypeLoader(TranslatingTypeLoader delegate) {
+ public SynchronizedTypeLoader(ITypeLoader delegate) {
this.delegate = delegate;
}
- @Override
- public List getClassNamesToTry(String className) {
- return delegate.getClassNamesToTry(className);
- }
-
- @Override
- public List getClassNamesToTry(ClassEntry obfClassEntry) {
- return delegate.getClassNamesToTry(obfClassEntry);
- }
-
- @Override
- public String transformInto(ClassNode node, ClassWriter writer) {
- return delegate.transformInto(node, writer);
- }
-
@Override
public synchronized boolean tryLoadType(String internalName, Buffer buffer) {
return delegate.tryLoadType(internalName, buffer);
diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
deleted file mode 100644
index 4c1f695..0000000
--- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
+++ /dev/null
@@ -1,154 +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.collect.Lists;
-import com.strobel.assembler.metadata.Buffer;
-import com.strobel.assembler.metadata.ITypeLoader;
-import cuchaz.enigma.analysis.index.JarIndex;
-import cuchaz.enigma.analysis.ParsedJar;
-import cuchaz.enigma.bytecode.translators.TranslationClassVisitor;
-import cuchaz.enigma.translation.Translator;
-import cuchaz.enigma.translation.representation.ReferencedEntryPool;
-import cuchaz.enigma.translation.representation.entry.ClassEntry;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.util.List;
-
-public class TranslatingTypeLoader extends CachingTypeLoader implements ITranslatingTypeLoader {
- //Store one instance as the classpath shouldnt change during load
- private static final ITypeLoader defaultTypeLoader = new CachingClasspathTypeLoader();
-
- private final ParsedJar jar;
- private final JarIndex jarIndex;
- private final ReferencedEntryPool entryPool;
- private final Translator obfuscatingTranslator;
- private final Translator deobfuscatingTranslator;
-
- public TranslatingTypeLoader(ParsedJar jar, JarIndex jarIndex, ReferencedEntryPool entryPool, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) {
- this.jar = jar;
- this.jarIndex = jarIndex;
- this.entryPool = entryPool;
- this.obfuscatingTranslator = obfuscatingTranslator;
- this.deobfuscatingTranslator = deobfuscatingTranslator;
- }
-
- protected byte[] doLoad(String className) {
- byte[] data = loadType(className);
- if (data == null) {
- // chain to default desc loader
- Buffer parentBuf = new Buffer();
- if (defaultTypeLoader.tryLoadType(className, parentBuf)) {
- return parentBuf.array();
- }
- return EMPTY_ARRAY;//need to return *something* as null means no store
- }
- return data;
- }
-
- private byte[] loadType(String className) {
-
- // NOTE: don't know if class name is obf or deobf
- ClassEntry classEntry = new ClassEntry(className);
- ClassEntry obfClassEntry = this.obfuscatingTranslator.translate(classEntry);
-
- // is this a class we should even know about?
- if (!jarIndex.getEntryIndex().hasClass(obfClassEntry)) {
- return null;
- }
-
- // DEBUG
- //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName()));
-
- // find the class in the jar
- ClassNode node = findClassInJar(obfClassEntry);
- if (node == null) {
- // couldn't find it
- return null;
- }
-
-
- // remove .getClass() calls that are seemingly injected
- // DUP
- // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
- // POP
- for (MethodNode methodNode : node.methods) {
- AbstractInsnNode insnNode = methodNode.instructions.getFirst();
- while (insnNode != null) {
- if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
- MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
- if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) {
- AbstractInsnNode previous = methodInsnNode.getPrevious();
- AbstractInsnNode next = methodInsnNode.getNext();
- if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) {
- insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction
- methodNode.instructions.remove(previous);
- methodNode.instructions.remove(methodInsnNode);
- methodNode.instructions.remove(next);
- }
- }
- }
- insnNode = insnNode.getNext();
- }
- }
-
- ClassWriter writer = new ClassWriter(0);
- transformInto(node, writer);
-
- // we have a transformed class!
- return writer.toByteArray();
- }
-
- private ClassNode findClassInJar(ClassEntry obfClassEntry) {
-
- // try to find the class in the jar
- for (String className : getClassNamesToTry(obfClassEntry)) {
- ClassNode node = this.jar.getClassNode(className);
- if (node != null) {
- return node;
- }
- }
-
- // didn't find it ;_;
- return null;
- }
-
- @Override
- public List getClassNamesToTry(String className) {
- return getClassNamesToTry(this.obfuscatingTranslator.translate(new ClassEntry(className)));
- }
-
- @Override
- public List getClassNamesToTry(ClassEntry obfClassEntry) {
- List classNamesToTry = Lists.newArrayList();
- classNamesToTry.add(obfClassEntry.getFullName());
-
- ClassEntry outerClass = obfClassEntry.getOuterClass();
- if (outerClass != null) {
- classNamesToTry.addAll(getClassNamesToTry(outerClass));
- }
-
- return classNamesToTry;
- }
-
- @Override
- public String transformInto(ClassNode node, ClassWriter writer) {
- node.accept(new TranslationClassVisitor(deobfuscatingTranslator, entryPool, Opcodes.ASM5, writer));
- return deobfuscatingTranslator.translate(new ClassEntry(node.name)).getFullName();
- }
-
-}
diff --git a/src/main/java/cuchaz/enigma/analysis/DropImportAstTransform.java b/src/main/java/cuchaz/enigma/analysis/DropImportAstTransform.java
new file mode 100644
index 0000000..991e91d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/DropImportAstTransform.java
@@ -0,0 +1,33 @@
+package cuchaz.enigma.analysis;
+
+import com.strobel.decompiler.languages.java.ast.AstNode;
+import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
+import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
+import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
+import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
+
+public final class DropImportAstTransform implements IAstTransform {
+ public static final DropImportAstTransform INSTANCE = new DropImportAstTransform();
+
+ private DropImportAstTransform() {
+ }
+
+ @Override
+ public void run(AstNode compilationUnit) {
+ compilationUnit.acceptVisitor(new Visitor(), null);
+ }
+
+ static class Visitor extends DepthFirstAstVisitor {
+ @Override
+ public Void visitPackageDeclaration(PackageDeclaration node, Void data) {
+ node.remove();
+ return null;
+ }
+
+ @Override
+ public Void visitImportDeclaration(ImportDeclaration node, Void data) {
+ node.remove();
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java
index ad3aceb..ddcda3e 100644
--- a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java
+++ b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java
@@ -12,9 +12,12 @@
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;
@@ -28,12 +31,12 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
-public class ParsedJar {
+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<>();;
+ Map uClassBytes = new LinkedHashMap<>();
try {
// get the jar entries that correspond to classes
Enumeration entries = jar.entries();
@@ -93,29 +96,34 @@ public class ParsedJar {
return classBytes.size();
}
- public List getClassEntries() {
- List entries = new ArrayList<>(classBytes.size());
- for (String s : classBytes.keySet()) {
- entries.add(new ClassEntry(s));
- }
- return entries;
- }
-
@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();
- reader.accept(node, 0);
+
+ LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Opcodes.ASM5, node);
+ reader.accept(visitor, 0);
+
return node;
});
}
- public Map getClassDataMap() {
+ 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/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
index abdec92..ed12ce3 100644
--- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
@@ -11,17 +11,24 @@
package cuchaz.enigma.analysis;
-import com.google.common.collect.*;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
import com.strobel.decompiler.languages.Region;
-import com.strobel.decompiler.languages.java.ast.AstNode;
-import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
-import com.strobel.decompiler.languages.java.ast.Identifier;
-import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
+import com.strobel.decompiler.languages.java.ast.*;
+import cuchaz.enigma.gui.SourceRemapper;
+import cuchaz.enigma.translation.mapping.EntryResolver;
+import cuchaz.enigma.translation.mapping.ResolutionStrategy;
import cuchaz.enigma.translation.representation.entry.Entry;
import javax.annotation.Nullable;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
public class SourceIndex {
private static Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$");
@@ -46,6 +53,13 @@ public class SourceIndex {
calculateLineOffsets();
}
+ public static SourceIndex buildIndex(String sourceString, CompilationUnit sourceTree, boolean ignoreBadTokens) {
+ SourceIndex index = new SourceIndex(sourceString, ignoreBadTokens);
+ sourceTree.acceptVisitor(new SourceIndexVisitor(), index);
+
+ return index;
+ }
+
private void calculateLineOffsets() {
// count the lines
this.lineOffsets = Lists.newArrayList();
@@ -57,32 +71,29 @@ public class SourceIndex {
}
}
- public void remap(String source, Map tokenMap) {
- this.source = source;
- calculateLineOffsets();
+ public SourceIndex remapTo(SourceRemapper.Result result) {
+ SourceIndex remapped = new SourceIndex(result.getSource(), ignoreBadTokens);
- for (Entry> entry : Lists.newArrayList(declarationToToken.keySet())) {
- Token token = declarationToToken.get(entry);
- declarationToToken.put(entry, tokenMap.getOrDefault(token, token));
+ for (Map.Entry, Token> entry : declarationToToken.entrySet()) {
+ remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue()));
}
- for (EntryReference, Entry>> ref : referenceToTokens.keySet()) {
- Collection oldTokens = referenceToTokens.get(ref);
- List newTokens = new ArrayList<>(oldTokens.size());
+ for (Map.Entry, Entry>>, Collection> entry : referenceToTokens.asMap().entrySet()) {
+ EntryReference, Entry>> reference = entry.getKey();
+ Collection oldTokens = entry.getValue();
- for (Token token : oldTokens) {
- newTokens.add(tokenMap.getOrDefault(token, token));
- }
+ Collection newTokens = oldTokens.stream()
+ .map(result::getRemappedToken)
+ .collect(Collectors.toList());
- referenceToTokens.replaceValues(ref, newTokens);
+ remapped.referenceToTokens.putAll(reference, newTokens);
}
- TreeMap, Entry>>> tokenToReferenceCopy = new TreeMap<>(tokenToReference);
-
- tokenToReference.clear();
- for (Token token : tokenToReferenceCopy.keySet()) {
- tokenToReference.put(tokenMap.getOrDefault(token, token), tokenToReferenceCopy.get(token));
+ for (Map.Entry, Entry>>> entry : tokenToReference.entrySet()) {
+ remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue());
}
+
+ return remapped;
}
public String getSource() {
@@ -164,20 +175,13 @@ public class SourceIndex {
}
@Nullable
- public EntryReference, Entry>> getDeobfReference(Token token) {
+ public EntryReference, Entry>> getReference(Token token) {
if (token == null) {
return null;
}
return this.tokenToReference.get(token);
}
- public void replaceDeobfReference(Token token, EntryReference, Entry>> newDeobfReference) {
- EntryReference, Entry>> oldDeobfReferences = this.tokenToReference.replace(token, newDeobfReference);
-
- Collection tokens = this.referenceToTokens.removeAll(oldDeobfReferences);
- this.referenceToTokens.putAll(newDeobfReference, tokens);
- }
-
public Iterable referenceTokens() {
return this.tokenToReference.keySet();
}
@@ -190,8 +194,8 @@ public class SourceIndex {
return this.declarationToToken.keySet();
}
- public Token getDeclarationToken(Entry> deobfEntry) {
- return this.declarationToToken.get(deobfEntry);
+ public Token getDeclarationToken(Entry> entry) {
+ return this.declarationToToken.get(entry);
}
public int getLineNumber(int pos) {
@@ -215,4 +219,18 @@ public class SourceIndex {
// line and col are 1-based
return this.lineOffsets.get(line - 1) + col - 1;
}
+
+ public void resolveReferences(EntryResolver resolver) {
+ // resolve all the classes in the source references
+ for (Token token : Lists.newArrayList(referenceToTokens.values())) {
+ EntryReference, Entry>> reference = tokenToReference.get(token);
+ EntryReference, Entry>> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST);
+
+ // replace the reference
+ tokenToReference.replace(token, resolvedReference);
+
+ Collection tokens = referenceToTokens.removeAll(reference);
+ referenceToTokens.putAll(resolvedReference, tokens);
+ }
+ }
}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
index 486603c..a4fe9ee 100644
--- a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
@@ -18,21 +18,17 @@ import com.strobel.assembler.metadata.TypeReference;
import com.strobel.decompiler.languages.TextLocation;
import com.strobel.decompiler.languages.java.ast.*;
import cuchaz.enigma.translation.representation.ProcyonEntryFactory;
-import cuchaz.enigma.translation.representation.ReferencedEntryPool;
import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
import cuchaz.enigma.translation.representation.entry.ClassEntry;
import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
public class SourceIndexClassVisitor extends SourceIndexVisitor {
- private final ReferencedEntryPool entryPool;
private final ProcyonEntryFactory entryFactory;
private ClassDefEntry classEntry;
- public SourceIndexClassVisitor(ReferencedEntryPool entryPool, ClassDefEntry classEntry) {
- super(entryPool);
- this.entryPool = entryPool;
- this.entryFactory = new ProcyonEntryFactory(entryPool);
+ public SourceIndexClassVisitor(ClassDefEntry classEntry) {
+ this.entryFactory = new ProcyonEntryFactory();
this.classEntry = classEntry;
}
@@ -44,7 +40,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
if (!classEntry.equals(this.classEntry)) {
// it's a subtype, recurse
index.addDeclaration(node.getNameToken(), classEntry);
- return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index);
+ return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
}
return recurse(node, index);
@@ -71,7 +67,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
tokenNode = node.getModifiers().firstOrNullObject();
}
index.addDeclaration(tokenNode, methodEntry);
- return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, methodEntry), index);
+ return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index);
}
@Override
@@ -79,7 +75,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def);
index.addDeclaration(node.getNameToken(), methodEntry);
- return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, methodEntry), index);
+ return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index);
}
@Override
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java
index 73db28f..c4785b6 100644
--- a/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java
@@ -17,8 +17,8 @@ import com.strobel.assembler.metadata.*;
import com.strobel.decompiler.ast.Variable;
import com.strobel.decompiler.languages.TextLocation;
import com.strobel.decompiler.languages.java.ast.*;
+import cuchaz.enigma.translation.representation.MethodDescriptor;
import cuchaz.enigma.translation.representation.TypeDescriptor;
-import cuchaz.enigma.translation.representation.*;
import cuchaz.enigma.translation.representation.entry.*;
import java.lang.Error;
@@ -26,16 +26,12 @@ import java.util.HashMap;
import java.util.Map;
public class SourceIndexMethodVisitor extends SourceIndexVisitor {
- private final ReferencedEntryPool entryPool;
-
private final MethodDefEntry methodEntry;
private Multimap unmatchedIdentifier = HashMultimap.create();
private Map> identifierEntryCache = new HashMap<>();
- public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, MethodDefEntry methodEntry) {
- super(entryPool);
- this.entryPool = entryPool;
+ public SourceIndexMethodVisitor(MethodDefEntry methodEntry) {
this.methodEntry = methodEntry;
}
@@ -44,10 +40,10 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
// get the behavior entry
- ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
+ ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
MethodEntry methodEntry = null;
if (ref instanceof MethodReference) {
- methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature());
+ methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature()));
}
if (methodEntry != null) {
// get the node for the token
@@ -80,11 +76,8 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
throw new Error("Expected a field here! got " + ref);
}
- ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
- FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(erasedSignature));
- if (fieldEntry == null) {
- throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getFullName());
- }
+ ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
+ FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature));
index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry);
}
@@ -95,7 +88,7 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
public Void visitSimpleType(SimpleType node, SourceIndex index) {
TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
- ClassEntry classEntry = entryPool.getClass(ref.getInternalName());
+ ClassEntry classEntry = new ClassEntry(ref.getInternalName());
index.addReference(node.getIdentifierToken(), classEntry, this.methodEntry);
}
@@ -108,7 +101,8 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
int parameterIndex = def.getSlot();
if (parameterIndex >= 0) {
- LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, parameterIndex, node.getName(), true);
+ TypeDescriptor parameterType = TypeDescriptor.parse(def.getParameterType());
+ LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(methodEntry, parameterIndex, node.getName(), true, parameterType);
Identifier identifier = node.getNameToken();
// cache the argument entry and the identifier
identifierEntryCache.put(identifier.getName(), localVariableEntry);
@@ -122,11 +116,8 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
if (ref != null) {
- ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
- FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature()));
- if (fieldEntry == null) {
- throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getFullName());
- }
+ ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
+ FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature()));
index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry);
} else
this.checkIdentifier(node, index);
@@ -154,13 +145,11 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
@Override
public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
- if (ref != null) {
- ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
- MethodEntry constructorEntry = entryPool.getMethod(classEntry, "", ref.getErasedSignature());
- if (node.getType() instanceof SimpleType) {
- SimpleType simpleTypeNode = (SimpleType) node.getType();
- index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.methodEntry);
- }
+ if (ref != null && node.getType() instanceof SimpleType) {
+ SimpleType simpleTypeNode = (SimpleType) node.getType();
+ ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
+ MethodEntry constructorEntry = new MethodEntry(classEntry, "", new MethodDescriptor(ref.getErasedSignature()));
+ index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.methodEntry);
}
return recurse(node, index);
@@ -181,7 +170,8 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
if (originalVariable != null) {
int variableIndex = originalVariable.getSlot();
if (variableIndex >= 0) {
- LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, variableIndex, initializer.getName(), false);
+ TypeDescriptor variableType = TypeDescriptor.parse(originalVariable.getVariableType());
+ LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(methodEntry, variableIndex, initializer.getName(), false, variableType);
identifierEntryCache.put(identifier.getName(), localVariableEntry);
addDeclarationToUnmatched(identifier.getName(), index);
index.addDeclaration(identifier, localVariableEntry);
@@ -199,17 +189,19 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
if (ref instanceof MethodReference) {
// get the behavior entry
- ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
- MethodEntry methodEntry = null;
+ ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
+ MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature()));
- methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature());
// get the node for the token
- AstNode tokenNode = node.getMethodNameToken();
- if (tokenNode == null || (tokenNode.getRegion().getBeginLine() == 0 || tokenNode.getRegion().getEndLine() == 0)){
- tokenNode = node.getTarget();
+ AstNode methodNameToken = node.getMethodNameToken();
+ AstNode targetToken = node.getTarget();
+
+ if (methodNameToken != null) {
+ index.addReference(methodNameToken, methodEntry, this.methodEntry);
}
- if (tokenNode != null) {
- index.addReference(tokenNode, methodEntry, this.methodEntry);
+
+ if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) {
+ index.addReference(targetToken, methodEntry.getParent(), this.methodEntry);
}
}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java
index 564830c..75a66a2 100644
--- a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java
@@ -14,23 +14,16 @@ package cuchaz.enigma.analysis;
import com.strobel.assembler.metadata.TypeDefinition;
import com.strobel.decompiler.languages.java.ast.*;
import com.strobel.decompiler.patterns.Pattern;
-import cuchaz.enigma.translation.representation.ReferencedEntryPool;
import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
public class SourceIndexVisitor implements IAstVisitor {
- private final ReferencedEntryPool entryPool;
-
- public SourceIndexVisitor(ReferencedEntryPool entryPool) {
- this.entryPool = entryPool;
- }
-
@Override
public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
ClassDefEntry classEntry = ClassDefEntry.parse(def);
index.addDeclaration(node.getNameToken(), classEntry);
- return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index);
+ return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
}
protected Void recurse(AstNode node, SourceIndex index) {
diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java
index 14fa7ca..12e0aa6 100644
--- a/src/main/java/cuchaz/enigma/analysis/Token.java
+++ b/src/main/java/cuchaz/enigma/analysis/Token.java
@@ -30,12 +30,12 @@ public class Token implements Comparable {
return to.length() - length;
}
- public String rename(String source, String to) {
+ public void rename(StringBuffer source, String to) {
int oldEnd = this.end;
this.text = to;
this.end = this.start + to.length();
- return source.substring(0, this.start) + to + source.substring(oldEnd);
+ source.replace(start, oldEnd, to);
}
public Token move(int offset) {
@@ -64,7 +64,7 @@ public class Token implements Comparable {
}
public boolean equals(Token other) {
- return start == other.start && end == other.end;
+ return start == other.start && end == other.end && text.equals(other.text);
}
@Override
diff --git a/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java b/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
index e1903d9..8f6bd46 100644
--- a/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
@@ -1,7 +1,6 @@
package cuchaz.enigma.analysis.index;
import com.google.common.collect.Maps;
-import cuchaz.enigma.translation.Translator;
import cuchaz.enigma.translation.mapping.EntryResolver;
import cuchaz.enigma.translation.representation.AccessFlags;
import cuchaz.enigma.translation.representation.entry.MethodEntry;
@@ -10,7 +9,7 @@ import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map;
-public class BridgeMethodIndex implements JarIndexer, RemappableIndex {
+public class BridgeMethodIndex implements JarIndexer {
private final EntryIndex entryIndex;
private final ReferenceIndex referenceIndex;
@@ -21,19 +20,6 @@ public class BridgeMethodIndex implements JarIndexer, RemappableIndex {
this.referenceIndex = referenceIndex;
}
- @Override
- public void remap(Translator translator) {
- accessedToBridge = translator.translate(accessedToBridge);
- }
-
- @Override
- public BridgeMethodIndex remapped(Translator translator) {
- BridgeMethodIndex index = new BridgeMethodIndex(entryIndex, referenceIndex);
- index.accessedToBridge = translator.translate(accessedToBridge);
-
- return index;
- }
-
@Override
public void processIndex(EntryResolver resolver) {
// look for access and bridged methods
diff --git a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java
index 55bfbc2..773eaf1 100644
--- a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java
@@ -1,6 +1,5 @@
package cuchaz.enigma.analysis.index;
-import cuchaz.enigma.translation.Translator;
import cuchaz.enigma.translation.representation.AccessFlags;
import cuchaz.enigma.translation.representation.entry.*;
@@ -9,28 +8,11 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
-public class EntryIndex implements JarIndexer, RemappableIndex {
+public class EntryIndex implements JarIndexer {
private Map classes = new HashMap<>();
private Map fields = new HashMap<>();
private Map methods = new HashMap<>();
- @Override
- public void remap(Translator translator) {
- classes = translator.translateKeys(classes);
- fields = translator.translateKeys(fields);
- methods = translator.translateKeys(methods);
- }
-
- @Override
- public EntryIndex remapped(Translator translator) {
- EntryIndex index = new EntryIndex();
- index.classes = translator.translateKeys(classes);
- index.fields = translator.translateKeys(fields);
- index.methods = translator.translateKeys(methods);
-
- return index;
- }
-
@Override
public void indexClass(ClassDefEntry classEntry) {
classes.put(classEntry, classEntry.getAccess());
diff --git a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java
index d165cc8..17bed54 100644
--- a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java
@@ -14,7 +14,6 @@ package cuchaz.enigma.analysis.index;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
-import cuchaz.enigma.translation.Translator;
import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
import cuchaz.enigma.translation.representation.entry.ClassEntry;
@@ -22,25 +21,10 @@ import java.util.Collection;
import java.util.LinkedList;
import java.util.Set;
-public class InheritanceIndex implements JarIndexer, RemappableIndex {
+public class InheritanceIndex implements JarIndexer {
private Multimap classParents = HashMultimap.create();
private Multimap classChildren = HashMultimap.create();
- @Override
- public void remap(Translator translator) {
- classChildren = translator.translate(classChildren);
- classParents = translator.translate(classParents);
- }
-
- @Override
- public InheritanceIndex remapped(Translator translator) {
- InheritanceIndex index = new InheritanceIndex();
- index.classParents = translator.translate(classParents);
- index.classChildren = translator.translate(classChildren);
-
- return index;
- }
-
@Override
public void indexClass(ClassDefEntry classEntry) {
ClassEntry superClass = classEntry.getSuperClass();
diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
index 0880244..9b21cba 100644
--- a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
@@ -14,7 +14,6 @@ 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.translation.Translator;
import cuchaz.enigma.translation.mapping.EntryResolver;
import cuchaz.enigma.translation.mapping.IndexEntryResolver;
import cuchaz.enigma.translation.representation.entry.*;
@@ -25,7 +24,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.function.Consumer;
-public class JarIndex implements JarIndexer, RemappableIndex {
+public class JarIndex implements JarIndexer {
private final EntryIndex entryIndex;
private final InheritanceIndex inheritanceIndex;
private final ReferenceIndex referenceIndex;
@@ -53,25 +52,6 @@ public class JarIndex implements JarIndexer, RemappableIndex {
return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex);
}
- @Override
- public void remap(Translator translator) {
- entryIndex.remap(translator);
- inheritanceIndex.remap(translator);
- bridgeMethodIndex.remap(translator);
- }
-
- @Override
- public JarIndex remapped(Translator translator) {
- EntryIndex entryIndex = this.entryIndex.remapped(translator);
- InheritanceIndex inheritanceIndex = this.inheritanceIndex.remapped(translator);
- BridgeMethodIndex bridgeMethodIndex = this.bridgeMethodIndex.remapped(translator);
-
- JarIndex remappedIndex = new JarIndex(entryIndex, inheritanceIndex, this.referenceIndex, bridgeMethodIndex);
- remappedIndex.methodImplementations.putAll(methodImplementations);
-
- return remappedIndex;
- }
-
public void indexJar(ParsedJar jar, Consumer progress) {
progress.accept("Indexing entries (1/3)");
jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE);
diff --git a/src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java b/src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java
deleted file mode 100644
index 537e772..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package cuchaz.enigma.analysis.index;
-
-import cuchaz.enigma.translation.Translator;
-
-public interface RemappableIndex {
- void remap(Translator translator);
-
- RemappableIndex remapped(Translator translator);
-}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java
new file mode 100644
index 0000000..16dbba1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java
@@ -0,0 +1,80 @@
+package cuchaz.enigma.bytecode.translators;
+
+import com.google.common.base.CharMatcher;
+import cuchaz.enigma.translation.representation.TypeDescriptor;
+import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
+import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.List;
+
+public class LocalVariableFixVisitor extends ClassVisitor {
+ private ClassDefEntry ownerEntry;
+
+ public LocalVariableFixVisitor(int api, ClassVisitor visitor) {
+ super(api, visitor);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature);
+ return new Method(api, methodEntry, super.visitMethod(access, name, descriptor, signature, exceptions));
+ }
+
+ private class Method extends MethodVisitor {
+ private final MethodDefEntry methodEntry;
+ private boolean hasLvt;
+
+ Method(int api, MethodDefEntry methodEntry, MethodVisitor visitor) {
+ super(api, visitor);
+ this.methodEntry = methodEntry;
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
+ hasLvt = true;
+
+ String translatedName = name;
+
+ if (isInvalidName(name)) {
+ int argumentIndex = methodEntry.getArgumentIndex(ownerEntry, index);
+
+ if (argumentIndex >= 0) {
+ List arguments = methodEntry.getDesc().getArgumentDescs();
+ boolean argument = argumentIndex < arguments.size();
+ if (argument) {
+ translatedName = "arg" + (argumentIndex + 1);
+ } else {
+ translatedName = "var" + (argumentIndex + 1);
+ }
+ }
+ }
+
+ super.visitLocalVariable(translatedName, desc, signature, start, end, index);
+ }
+
+ private boolean isInvalidName(String name) {
+ return !CharMatcher.ascii().matchesAllOf(name);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (!hasLvt) {
+ List arguments = methodEntry.getDesc().getArgumentDescs();
+ for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) {
+ super.visitParameter("arg" + (argumentIndex + 1), 0);
+ }
+ }
+
+ super.visitEnd();
+ }
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java
index 53d09bb..e4c41d3 100644
--- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java
@@ -13,7 +13,6 @@ package cuchaz.enigma.bytecode.translators;
import cuchaz.enigma.translation.Translator;
import cuchaz.enigma.translation.representation.MethodDescriptor;
-import cuchaz.enigma.translation.representation.ReferencedEntryPool;
import cuchaz.enigma.translation.representation.TypeDescriptor;
import cuchaz.enigma.translation.representation.entry.*;
import org.objectweb.asm.*;
@@ -22,14 +21,12 @@ import java.util.Arrays;
public class TranslationClassVisitor extends ClassVisitor {
private final Translator translator;
- private final ReferencedEntryPool entryPool;
private ClassDefEntry obfClassEntry;
- public TranslationClassVisitor(Translator translator, ReferencedEntryPool entryPool, int api, ClassVisitor cv) {
+ public TranslationClassVisitor(Translator translator, int api, ClassVisitor cv) {
super(api, cv);
this.translator = translator;
- this.entryPool = entryPool;
}
@Override
@@ -57,7 +54,7 @@ public class TranslationClassVisitor extends ClassVisitor {
MethodDefEntry translatedEntry = translator.translate(entry);
String[] translatedExceptions = new String[exceptions.length];
for (int i = 0; i < exceptions.length; i++) {
- translatedExceptions[i] = translator.translate(entryPool.getClass(exceptions[i])).getFullName();
+ translatedExceptions[i] = translator.translate(new ClassEntry(exceptions[i])).getFullName();
}
MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions);
return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv);
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java
index a5a33e6..c824265 100644
--- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java
@@ -1,16 +1,14 @@
package cuchaz.enigma.bytecode.translators;
+import cuchaz.enigma.translation.LocalNameGenerator;
import cuchaz.enigma.translation.Translator;
-import cuchaz.enigma.translation.mapping.NameValidator;
import cuchaz.enigma.translation.representation.MethodDescriptor;
import cuchaz.enigma.translation.representation.Signature;
import cuchaz.enigma.translation.representation.TypeDescriptor;
import cuchaz.enigma.translation.representation.entry.*;
import org.objectweb.asm.*;
-import java.util.Collection;
import java.util.List;
-import java.util.Locale;
import java.util.stream.Collectors;
public class TranslationMethodVisitor extends MethodVisitor {
@@ -83,40 +81,6 @@ public class TranslationMethodVisitor extends MethodVisitor {
return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
}
- @Override
- public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
- hasParameterMeta = true;
-
- String translatedSignature = translator.translate(Signature.createTypedSignature(signature)).toString();
- int argumentIndex = methodEntry.getArgumentIndex(ownerEntry, index);
-
- if (argumentIndex >= 0) {
- LocalVariableDefEntry entry = new LocalVariableDefEntry(methodEntry, index, name, true, new TypeDescriptor(desc));
- LocalVariableDefEntry translatedEntry = translator.translate(entry);
- String translatedName = translatedEntry.getName();
-
- if (translatedName.equals(entry.getName())) {
- List arguments = methodEntry.getDesc().getArgumentDescs();
- List translatedArguments = arguments.stream()
- .map(translator::translate)
- .collect(Collectors.toList());
-
- boolean argument = argumentIndex < arguments.size();
- if (argument) {
- translatedName = inferArgumentName(argumentIndex, translatedEntry.getDesc(), translatedArguments);
- } else {
- translatedName = inferLocalVariableName(argumentIndex, translatedEntry.getDesc());
- }
- }
-
- super.visitLocalVariable(translatedName, translatedEntry.getDesc().toString(), translatedSignature, start, end, index);
- } else {
- // Handle "this" variable
- TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc));
- super.visitLocalVariable(name, translatedDesc.toString(), translatedSignature, start, end, index);
- }
- }
-
@Override
public void visitTypeInsn(int opcode, String type) {
ClassEntry translatedEntry = translator.translate(new ClassEntry(type));
@@ -153,11 +117,45 @@ public class TranslationMethodVisitor extends MethodVisitor {
}
}
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
+ hasParameterMeta = true;
+
+ String translatedSignature = translator.translate(Signature.createTypedSignature(signature)).toString();
+ int argumentIndex = methodEntry.getArgumentIndex(ownerEntry, index);
+
+ if (argumentIndex >= 0) {
+ LocalVariableDefEntry entry = new LocalVariableDefEntry(methodEntry, index, name, true, new TypeDescriptor(desc));
+ LocalVariableDefEntry translatedEntry = translator.translate(entry);
+ String translatedName = translatedEntry.getName();
+
+ if (translatedName.equals(entry.getName())) {
+ List arguments = methodEntry.getDesc().getArgumentDescs();
+ List translatedArguments = arguments.stream()
+ .map(translator::translate)
+ .collect(Collectors.toList());
+
+ boolean argument = argumentIndex < arguments.size();
+ if (argument) {
+ translatedName = LocalNameGenerator.generateArgumentName(argumentIndex, translatedEntry.getDesc(), translatedArguments);
+ } else {
+ translatedName = LocalNameGenerator.generateLocalVariableName(argumentIndex, translatedEntry.getDesc());
+ }
+ }
+
+ super.visitLocalVariable(translatedName, translatedEntry.getDesc().toString(), translatedSignature, start, end, index);
+ } else {
+ // Handle "this" variable
+ TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc));
+ super.visitLocalVariable(name, translatedDesc.toString(), translatedSignature, start, end, index);
+ }
+ }
+
@Override
public void visitEnd() {
// If we didn't receive any parameter metadata, generate it
if (!hasParameterMeta) {
- List arguments = methodEntry.getDesc().getArgumentDescs();
+ List arguments = translator.translate(methodEntry.getDesc()).getArgumentDescs();
int offset = ((methodEntry.getAccess().getFlags() & Opcodes.ACC_ABSTRACT) != 0) ? 1 : 0;
for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) {
@@ -165,7 +163,7 @@ public class TranslationMethodVisitor extends MethodVisitor {
LocalVariableEntry translatedEntry = translator.translate(entry);
String translatedName = translatedEntry.getName();
if (translatedName.equals(entry.getName())) {
- super.visitParameter(inferArgumentName(argumentIndex, arguments.get(argumentIndex), arguments), 0);
+ super.visitParameter(LocalNameGenerator.generateArgumentName(argumentIndex, arguments.get(argumentIndex), arguments), 0);
} else {
super.visitParameter(translatedName, 0);
}
@@ -175,39 +173,4 @@ public class TranslationMethodVisitor extends MethodVisitor {
}
super.visitEnd();
}
-
- private String inferArgumentName(int index, TypeDescriptor desc, Collection arguments) {
- boolean uniqueType = arguments.stream().filter(desc::equals).count() <= 1;
- String translatedName;
- int nameIndex = index + 1;
- StringBuilder nameBuilder = new StringBuilder(getTypeName(desc));
- if (!uniqueType || NameValidator.isReserved(nameBuilder.toString())) {
- nameBuilder.append(nameIndex);
- }
- translatedName = nameBuilder.toString();
- return translatedName;
- }
-
- private String inferLocalVariableName(int index, TypeDescriptor desc) {
- int nameIndex = index + 1;
- return getTypeName(desc) + nameIndex;
- }
-
- private String getTypeName(TypeDescriptor desc) {
- // Unfortunately each of these have different name getters, so they have different code paths
- if (desc.isPrimitive()) {
- TypeDescriptor.Primitive argCls = desc.getPrimitive();
- return argCls.name().toLowerCase(Locale.ROOT);
- } else if (desc.isArray()) {
- // List types would require this whole block again, so just go with aListx
- return "arr";
- } else if (desc.isType()) {
- String typeName = desc.getTypeEntry().getSimpleName().replace("$", "");
- typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1);
- return typeName;
- } else {
- System.err.println("Encountered invalid argument type descriptor " + desc.toString());
- return "var";
- }
- }
}
diff --git a/src/main/java/cuchaz/enigma/config/Config.java b/src/main/java/cuchaz/enigma/config/Config.java
index 55f867e..6f5a337 100644
--- a/src/main/java/cuchaz/enigma/config/Config.java
+++ b/src/main/java/cuchaz/enigma/config/Config.java
@@ -74,7 +74,6 @@ public class Config {
config.proposedColorOutline = new AlphaColorEntry(0x000000, 0.15f);
config.deobfuscatedColor = new AlphaColorEntry(0xDCFFDC, 1.0f);
config.deobfuscatedColorOutline = new AlphaColorEntry(0x50A050, 1.0f);
- config.otherColorOutline = new AlphaColorEntry(0xB4B4B4, 1.0f);
config.editorBackground = 0xFFFFFF;
config.highlightColor = 0x3333EE;
config.caretColor = 0x000000;
@@ -98,7 +97,6 @@ public class Config {
config.deobfuscatedColorOutline = new AlphaColorEntry(0x50FA7B, 0.5f);
config.proposedColor = new AlphaColorEntry(0x606366, 0.3f);
config.proposedColorOutline = new AlphaColorEntry(0x606366, 0.5f);
- config.otherColorOutline = new AlphaColorEntry(0xB4B4B4, 0.0f);
config.editorBackground = 0x282A36;
config.highlightColor = 0xFF79C6;
config.caretColor = 0xF8F8F2;
@@ -128,7 +126,6 @@ public class Config {
public AlphaColorEntry proposedColorOutline;
public AlphaColorEntry deobfuscatedColor;
public AlphaColorEntry deobfuscatedColorOutline;
- public AlphaColorEntry otherColorOutline;
public Integer editorBackground;
public Integer highlightColor;
@@ -198,12 +195,14 @@ public class Config {
}
private static class IntSerializer implements JsonSerializer {
+ @Override
public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive("#" + Integer.toHexString(src).toUpperCase());
}
}
private static class IntDeserializer implements JsonDeserializer {
+ @Override
public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return (int) Long.parseLong(json.getAsString().replace("#", ""), 16);
}
diff --git a/src/main/java/cuchaz/enigma/config/Themes.java b/src/main/java/cuchaz/enigma/config/Themes.java
index 00324f4..400ea3f 100644
--- a/src/main/java/cuchaz/enigma/config/Themes.java
+++ b/src/main/java/cuchaz/enigma/config/Themes.java
@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableMap;
import cuchaz.enigma.gui.Gui;
import cuchaz.enigma.gui.EnigmaSyntaxKit;
import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
+import cuchaz.enigma.gui.highlight.TokenHighlightType;
import de.sciss.syntaxpane.DefaultSyntaxKit;
import javax.swing.*;
@@ -17,10 +18,11 @@ public class Themes {
}
public static void updateTheme(Gui gui) {
- Config.getInstance().lookAndFeel.apply(Config.getInstance());
- Config.getInstance().lookAndFeel.setGlobalLAF();
+ Config config = Config.getInstance();
+ config.lookAndFeel.apply(config);
+ config.lookAndFeel.setGlobalLAF();
try {
- Config.getInstance().saveConfig();
+ config.saveConfig();
} catch (IOException e) {
e.printStackTrace();
}
@@ -28,12 +30,11 @@ public class Themes {
DefaultSyntaxKit.initKit();
DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName());
gui.boxHighlightPainters = ImmutableMap.of(
- "obfuscated", BoxHighlightPainter.create(Config.getInstance().obfuscatedColor, Config.getInstance().obfuscatedColorOutline),
- "proposed", BoxHighlightPainter.create(Config.getInstance().proposedColor, Config.getInstance().proposedColorOutline),
- "deobfuscated", BoxHighlightPainter.create(Config.getInstance().deobfuscatedColor, Config.getInstance().deobfuscatedColorOutline),
- "other", BoxHighlightPainter.create(null, Config.getInstance().otherColorOutline)
+ TokenHighlightType.OBFUSCATED, BoxHighlightPainter.create(config.obfuscatedColor, config.obfuscatedColorOutline),
+ TokenHighlightType.PROPOSED, BoxHighlightPainter.create(config.proposedColor, config.proposedColorOutline),
+ TokenHighlightType.DEOBFUSCATED, BoxHighlightPainter.create(config.deobfuscatedColor, config.deobfuscatedColorOutline)
);
- gui.setEditorTheme(Config.getInstance().lookAndFeel);
+ gui.setEditorTheme(config.lookAndFeel);
SwingUtilities.updateComponentTreeUI(gui.getFrame());
}
}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index c3b7288..39d0333 100644
--- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -17,9 +17,11 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import cuchaz.enigma.gui.node.ClassSelectorClassNode;
import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
-import cuchaz.enigma.translation.representation.entry.ClassEntry;
import cuchaz.enigma.throwables.IllegalNameException;
+import cuchaz.enigma.translation.Translator;
+import cuchaz.enigma.translation.representation.entry.ClassEntry;
+import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
@@ -27,21 +29,26 @@ import javax.swing.tree.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
-import java.util.List;
public class ClassSelector extends JTree {
public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName);
+
+ private final GuiController controller;
+
private DefaultMutableTreeNode rootNodes;
private ClassSelectionListener selectionListener;
private RenameSelectionListener renameSelectionListener;
private Comparator comparator;
+ private final Map displayedObfToDeobf = new HashMap<>();
+
public ClassSelector(Gui gui, Comparator comparator, boolean isRenamable) {
this.comparator = comparator;
+ this.controller = gui.getController();
// configure the tree control
- setEditable(gui != null);
+ setEditable(true);
setRootVisible(false);
setShowsRootHandles(false);
setModel(null);
@@ -55,66 +62,64 @@ public class ClassSelector extends JTree {
TreePath path = getSelectionPath();
if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent();
- selectionListener.onSelectClass(node.getClassEntry());
+ selectionListener.onSelectClass(node.getObfEntry());
}
}
}
});
- if (gui != null) {
- final JTree tree = this;
+ final JTree tree = this;
- final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree,
+ final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree,
(DefaultTreeCellRenderer) tree.getCellRenderer()) {
- @Override
- public boolean isCellEditable(EventObject event) {
- return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event);
- }
- };
- this.setCellEditor(editor);
- editor.addCellEditorListener(new CellEditorListener() {
- @Override
- public void editingStopped(ChangeEvent e) {
- String data = editor.getCellEditorValue().toString();
- TreePath path = getSelectionPath();
-
- Object realPath = path.getLastPathComponent();
- if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) {
- DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath;
- TreeNode parentNode = node.getParent();
- if (parentNode == null)
- return;
- boolean allowEdit = true;
- for (int i = 0; i < parentNode.getChildCount(); i++) {
- TreeNode childNode = parentNode.getChildAt(i);
- if (childNode != null && childNode.toString().equals(data) && childNode != node) {
- allowEdit = false;
- break;
- }
+ @Override
+ public boolean isCellEditable(EventObject event) {
+ return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event);
+ }
+ };
+ this.setCellEditor(editor);
+ editor.addCellEditorListener(new CellEditorListener() {
+ @Override
+ public void editingStopped(ChangeEvent e) {
+ String data = editor.getCellEditorValue().toString();
+ TreePath path = getSelectionPath();
+
+ Object realPath = path.getLastPathComponent();
+ if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath;
+ TreeNode parentNode = node.getParent();
+ if (parentNode == null)
+ return;
+ boolean allowEdit = true;
+ for (int i = 0; i < parentNode.getChildCount(); i++) {
+ TreeNode childNode = parentNode.getChildAt(i);
+ if (childNode != null && childNode.toString().equals(data) && childNode != node) {
+ allowEdit = false;
+ break;
}
- if (allowEdit && renameSelectionListener != null) {
- Object prevData = node.getUserObject();
- Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data;
- try {
- renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node);
- node.setUserObject(objectData); // Make sure that it's modified
- } catch (IllegalNameException ex) {
- JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION,
- JOptionPane.ERROR_MESSAGE, null, new String[] { "Ok" }, "OK");
- editor.cancelCellEditing();
- }
- } else
- editor.cancelCellEditing();
}
-
+ if (allowEdit && renameSelectionListener != null) {
+ Object prevData = node.getUserObject();
+ Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data;
+ try {
+ renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node);
+ node.setUserObject(objectData); // Make sure that it's modified
+ } catch (IllegalNameException ex) {
+ JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION,
+ JOptionPane.ERROR_MESSAGE, null, new String[]{"Ok"}, "OK");
+ editor.cancelCellEditing();
+ }
+ } else
+ editor.cancelCellEditing();
}
- @Override
- public void editingCanceled(ChangeEvent e) {
- // NOP
- }
- });
- }
+ }
+
+ @Override
+ public void editingCanceled(ChangeEvent e) {
+ // NOP
+ }
+ });
// init defaults
this.selectionListener = null;
this.renameSelectionListener = null;
@@ -142,16 +147,21 @@ public class ClassSelector extends JTree {
}
public void setClasses(Collection classEntries) {
+ displayedObfToDeobf.clear();
+
List state = getExpansionState(this);
if (classEntries == null) {
setModel(null);
return;
}
+ Translator translator = controller.getDeobfuscator().getMapper().getDeobfuscator();
+
// build the package names
Map packages = Maps.newHashMap();
- for (ClassEntry classEntry : classEntries) {
- packages.put(classEntry.getPackageName(), null);
+ for (ClassEntry obfClass : classEntries) {
+ ClassEntry deobfClass = translator.translate(obfClass);
+ packages.put(deobfClass.getPackageName(), null);
}
// sort the packages
@@ -191,20 +201,24 @@ public class ClassSelector extends JTree {
// put the classes into packages
Multimap packagedClassEntries = ArrayListMultimap.create();
- for (ClassEntry classEntry : classEntries) {
- packagedClassEntries.put(classEntry.getPackageName(), classEntry);
+ for (ClassEntry obfClass : classEntries) {
+ ClassEntry deobfClass = translator.translate(obfClass);
+ packagedClassEntries.put(deobfClass.getPackageName(), obfClass);
}
// build the class nodes
for (String packageName : packagedClassEntries.keySet()) {
// sort the class entries
List classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
- classEntriesInPackage.sort(this.comparator);
+ classEntriesInPackage.sort((o1, o2) -> comparator.compare(translator.translate(o1), translator.translate(o2)));
// create the nodes in order
- for (ClassEntry classEntry : classEntriesInPackage) {
+ for (ClassEntry obfClass : classEntriesInPackage) {
+ ClassEntry deobfClass = translator.translate(obfClass);
ClassSelectorPackageNode node = packages.get(packageName);
- node.add(new ClassSelectorClassNode(classEntry));
+ ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfClass, deobfClass);
+ displayedObfToDeobf.put(obfClass, deobfClass);
+ node.add(classNode);
}
}
@@ -324,7 +338,7 @@ public class ClassSelector extends JTree {
}
for (ClassSelectorPackageNode packageNode : packageNodes()) {
if (packageNode.getPackageName().equals(packageName)) {
- expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode }));
+ expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
return;
}
}
@@ -332,14 +346,13 @@ public class ClassSelector extends JTree {
public void expandAll() {
for (ClassSelectorPackageNode packageNode : packageNodes()) {
- expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode }));
+ expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
}
}
public ClassEntry getFirstClass() {
ClassSelectorPackageNode packageNode = packageNodes().get(0);
- if (packageNode != null)
- {
+ if (packageNode != null) {
ClassSelectorClassNode classNode = classNodes(packageNode).get(0);
if (classNode != null) {
return classNode.getClassEntry();
@@ -350,7 +363,7 @@ public class ClassSelector extends JTree {
public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
String packageName = entry.getPackageName();
- if (packageName == null){
+ if (packageName == null) {
packageName = "(none)";
}
for (ClassSelectorPackageNode packageNode : packageNodes()) {
@@ -361,6 +374,11 @@ public class ClassSelector extends JTree {
return null;
}
+ @Nullable
+ public ClassEntry getDisplayedDeobf(ClassEntry obfEntry) {
+ return displayedObfToDeobf.get(obfEntry);
+ }
+
public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) {
ClassSelectorPackageNode packageNode = getPackageNode(entry);
@@ -402,7 +420,7 @@ public class ClassSelector extends JTree {
for (ClassSelectorPackageNode packageNode : packageNodes()) {
for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
if (classNode.getClassEntry().equals(classEntry)) {
- setSelectionPath(new TreePath(new Object[] { getModel().getRoot(), packageNode, classNode }));
+ setSelectionPath(new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode}));
}
}
}
@@ -418,6 +436,9 @@ public class ClassSelector extends JTree {
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i);
if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) {
model.removeNodeFromParent(childNode);
+ if (childNode instanceof ClassSelectorClassNode) {
+ displayedObfToDeobf.remove(((ClassSelectorClassNode) childNode).getObfEntry());
+ }
break;
}
}
@@ -428,13 +449,25 @@ public class ClassSelector extends JTree {
((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode);
}
- public void moveClassTree(ClassEntry oldClassEntry, ClassEntry newClassEntry, ClassSelector otherSelector) {
- if (otherSelector == null)
- removeNode(getPackageNode(oldClassEntry), oldClassEntry);
- insertNode(getOrCreate(newClassEntry), newClassEntry);
+ public void moveClassIn(ClassEntry classEntry) {
+ removeEntry(classEntry);
+ insertNode(classEntry);
}
- public ClassSelectorPackageNode getOrCreate(ClassEntry entry) {
+ public void moveClassOut(ClassEntry classEntry) {
+ removeEntry(classEntry);
+ }
+
+ private void removeEntry(ClassEntry classEntry) {
+ ClassEntry previousDeobf = displayedObfToDeobf.get(classEntry);
+ if (previousDeobf != null) {
+ ClassSelectorPackageNode packageNode = getPackageNode(previousDeobf);
+ removeNode(packageNode, previousDeobf);
+ removeNodeIfEmpty(packageNode);
+ }
+ }
+
+ public ClassSelectorPackageNode getOrCreatePackage(ClassEntry entry) {
DefaultTreeModel model = (DefaultTreeModel) getModel();
ClassSelectorPackageNode newPackageNode = getPackageNode(entry);
if (newPackageNode == null) {
@@ -444,10 +477,15 @@ public class ClassSelector extends JTree {
return newPackageNode;
}
- public void insertNode(ClassSelectorPackageNode packageNode, ClassEntry entry) {
+ public void insertNode(ClassEntry obfEntry) {
+ ClassEntry deobfEntry = controller.getDeobfuscator().deobfuscate(obfEntry);
+ ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry);
+
DefaultTreeModel model = (DefaultTreeModel) getModel();
- ClassSelectorClassNode classNode = new ClassSelectorClassNode(entry);
+ ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfEntry, deobfEntry);
model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode));
+
+ displayedObfToDeobf.put(obfEntry, deobfEntry);
}
public void reload() {
diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java
index 0810043..e119640 100644
--- a/src/main/java/cuchaz/enigma/gui/CodeReader.java
+++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java
@@ -11,58 +11,27 @@
package cuchaz.enigma.gui;
-import com.strobel.decompiler.languages.java.ast.CompilationUnit;
-import cuchaz.enigma.Deobfuscator;
-import cuchaz.enigma.analysis.EntryReference;
-import cuchaz.enigma.analysis.SourceIndex;
import cuchaz.enigma.analysis.Token;
-import cuchaz.enigma.translation.representation.entry.ClassEntry;
-import cuchaz.enigma.translation.representation.entry.Entry;
-import de.sciss.syntaxpane.DefaultSyntaxKit;
import javax.swing.*;
import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
import javax.swing.text.Highlighter.HighlightPainter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class CodeReader extends JEditorPane {
-
private static final long serialVersionUID = 3673180950485748810L;
- private static final Object lock = new Object();
- private SourceIndex sourceIndex;
- private SelectionListener selectionListener;
-
- public CodeReader() {
-
- setEditable(false);
- setContentType("text/java");
-
- // turn off token highlighting (it's wrong most of the time anyway...)
- DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit();
- kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
-
- // hook events
- addCaretListener(event ->
- {
- if (selectionListener != null && sourceIndex != null) {
- Token token = sourceIndex.getReferenceToken(event.getDot());
- if (token != null) {
- selectionListener.onSelect(sourceIndex.getDeobfReference(token));
- } else {
- selectionListener.onSelect(null);
- }
- }
- });
- }
-
// HACKHACK: someday we can update the main GUI to use this code reader
public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
// set the caret position to the token
- editor.setCaretPosition(token.start);
+ Document document = editor.getDocument();
+ int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength());
+
+ editor.setCaretPosition(clampedPosition);
editor.grabFocus();
try {
@@ -101,57 +70,4 @@ public class CodeReader extends JEditorPane {
});
timer.start();
}
-
- public void setSelectionListener(SelectionListener val) {
- selectionListener = val;
- }
-
- public void setCode(String code) {
- // sadly, the java lexer is not thread safe, so we have to serialize all these calls
- synchronized (lock) {
- setText(code);
- }
- }
-
- public SourceIndex getSourceIndex() {
- return sourceIndex;
- }
-
- public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
- decompileClass(classEntry, deobfuscator, null);
- }
-
- public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
- decompileClass(classEntry, deobfuscator, null, callback);
- }
-
- public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
-
- if (classEntry == null) {
- setCode(null);
- return;
- }
-
- setCode("(decompiling...)");
-
- // run decompilation in a separate thread to keep ui responsive
- new Thread(() ->
- {
-
- // decompile it
-
- CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getName());
- String source = deobfuscator.getSource(sourceTree);
- setCode(source);
- sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
-
- if (callback != null) {
- callback.run();
- }
- }).start();
- }
-
- public interface SelectionListener {
- void onSelect(EntryReference, Entry>> reference);
- }
}
diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
new file mode 100644
index 0000000..03f76c9
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
@@ -0,0 +1,129 @@
+package cuchaz.enigma.gui;
+
+import cuchaz.enigma.Deobfuscator;
+import cuchaz.enigma.analysis.EntryReference;
+import cuchaz.enigma.analysis.SourceIndex;
+import cuchaz.enigma.analysis.Token;
+import cuchaz.enigma.api.EnigmaPlugin;
+import cuchaz.enigma.gui.highlight.TokenHighlightType;
+import cuchaz.enigma.translation.LocalNameGenerator;
+import cuchaz.enigma.translation.Translator;
+import cuchaz.enigma.translation.representation.TypeDescriptor;
+import cuchaz.enigma.translation.representation.entry.ClassEntry;
+import cuchaz.enigma.translation.representation.entry.Entry;
+import cuchaz.enigma.translation.representation.entry.FieldEntry;
+import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry;
+
+import javax.annotation.Nullable;
+import java.util.*;
+
+public class DecompiledClassSource {
+ private final ClassEntry classEntry;
+ private final Deobfuscator deobfuscator;
+
+ private final SourceIndex obfuscatedIndex;
+ private SourceIndex remappedIndex;
+
+ private final Map> highlightedTokens = new EnumMap<>(TokenHighlightType.class);
+
+ public DecompiledClassSource(ClassEntry classEntry, Deobfuscator deobfuscator, SourceIndex index) {
+ this.classEntry = classEntry;
+ this.deobfuscator = deobfuscator;
+ this.obfuscatedIndex = index;
+ this.remappedIndex = index;
+ }
+
+ public void remapSource(Translator translator) {
+ highlightedTokens.clear();
+
+ SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens());
+
+ SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(token, movedToken, translator));
+ remappedIndex = obfuscatedIndex.remapTo(remapResult);
+ }
+
+ private String remapToken(Token token, Token movedToken, Translator translator) {
+ EntryReference, Entry>> reference = obfuscatedIndex.getReference(token);
+
+ if (deobfuscator.isRenamable(reference)) {
+ Entry> entry = reference.getNameableEntry();
+ Entry> translatedEntry = translator.translate(entry);
+
+ if (isDeobfuscated(entry, translatedEntry)) {
+ highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED);
+ return translatedEntry.getSourceRemapName();
+ } else {
+ String proposedName = proposeName(entry);
+ if (proposedName != null) {
+ highlightToken(movedToken, TokenHighlightType.PROPOSED);
+ return proposedName;
+ }
+
+ highlightToken(movedToken, TokenHighlightType.OBFUSCATED);
+
+ String defaultName = generateDefaultName(translatedEntry);
+ if (defaultName != null) {
+ return defaultName;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private String proposeName(Entry> entry) {
+ if (entry instanceof FieldEntry) {
+ for (EnigmaPlugin plugin : deobfuscator.getPlugins()) {
+ String owner = entry.getContainingClass().getFullName();
+ String proposal = plugin.proposeFieldName(owner, entry.getName(), ((FieldEntry) entry).getDesc().toString());
+ if (proposal != null) {
+ return proposal;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private String generateDefaultName(Entry> entry) {
+ if (entry instanceof LocalVariableDefEntry) {
+ LocalVariableDefEntry localVariable = (LocalVariableDefEntry) entry;
+
+ int index = localVariable.getIndex();
+ if (localVariable.isArgument()) {
+ List arguments = localVariable.getParent().getDesc().getArgumentDescs();
+ return LocalNameGenerator.generateArgumentName(index, localVariable.getDesc(), arguments);
+ } else {
+ return LocalNameGenerator.generateLocalVariableName(index, localVariable.getDesc());
+ }
+ }
+
+ return null;
+ }
+
+ private boolean isDeobfuscated(Entry> entry, Entry> translatedEntry) {
+ return !entry.getName().equals(translatedEntry.getName());
+ }
+
+ public ClassEntry getEntry() {
+ return classEntry;
+ }
+
+ public SourceIndex getIndex() {
+ return remappedIndex;
+ }
+
+ public Map> getHighlightedTokens() {
+ return highlightedTokens;
+ }
+
+ private void highlightToken(Token token, TokenHighlightType highlightType) {
+ highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token);
+ }
+
+ @Override
+ public String toString() {
+ return remappedIndex.getSource();
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
index d119735..a6e20a2 100644
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -24,7 +24,7 @@ import cuchaz.enigma.gui.filechooser.FileChooserAny;
import cuchaz.enigma.gui.filechooser.FileChooserFolder;
import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
import cuchaz.enigma.gui.highlight.SelectionHighlightPainter;
-import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
+import cuchaz.enigma.gui.highlight.TokenHighlightType;
import cuchaz.enigma.gui.panels.PanelDeobf;
import cuchaz.enigma.gui.panels.PanelEditor;
import cuchaz.enigma.gui.panels.PanelIdentifier;
@@ -44,10 +44,9 @@ import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.*;
-import java.io.IOException;
import java.nio.file.Path;
-import java.util.*;
import java.util.List;
+import java.util.*;
import java.util.function.Function;
public class Gui {
@@ -71,7 +70,7 @@ public class Gui {
private JPanel classesPanel;
private JSplitPane splitClasses;
private PanelIdentifier infoPanel;
- public Map boxHighlightPainters;
+ public Map boxHighlightPainters;
private SelectionHighlightPainter selectionHighlightPainter;
private JTree inheritanceTree;
private JTree implementationsTree;
@@ -320,7 +319,7 @@ public class Gui {
this.frame.setTitle(Constants.NAME + " - " + jarName);
this.classesPanel.removeAll();
this.classesPanel.add(splitClasses);
- setSource(null);
+ setEditorText(null);
// update menu
this.menuBar.closeJarMenu.setEnabled(true);
@@ -342,7 +341,7 @@ public class Gui {
this.frame.setTitle(Constants.NAME);
setObfClasses(null);
setDeobfClasses(null);
- setSource(null);
+ setEditorText(null);
this.classesPanel.removeAll();
// update menu
@@ -373,11 +372,16 @@ public class Gui {
this.menuBar.saveMappingsMenu.setEnabled(path != null);
}
- public void setSource(String source) {
+ public void setEditorText(String source) {
this.editor.getHighlighter().removeAllHighlights();
this.editor.setText(source);
}
+ public void setSource(DecompiledClassSource source) {
+ editor.setText(source.toString());
+ setHighlightedTokens(source.getHighlightedTokens());
+ }
+
public void showToken(final Token token) {
if (token == null) {
throw new IllegalArgumentException("Token cannot be null!");
@@ -401,15 +405,15 @@ public class Gui {
showToken(sortedTokens.get(0));
}
- public void setHighlightedTokens(Map> tokens) {
+ public void setHighlightedTokens(Map> tokens) {
// remove any old highlighters
this.editor.getHighlighter().removeAllHighlights();
if (boxHighlightPainters != null) {
- for (String s : tokens.keySet()) {
- BoxHighlightPainter painter = boxHighlightPainters.get(s);
+ for (TokenHighlightType type : tokens.keySet()) {
+ BoxHighlightPainter painter = boxHighlightPainters.get(type);
if (painter != null) {
- setHighlightedTokens(tokens.get(s), painter);
+ setHighlightedTokens(tokens.get(type), painter);
}
}
}
@@ -435,17 +439,19 @@ public class Gui {
this.reference = reference;
+ EntryReference, Entry>> translatedReference = controller.getDeobfuscator().deobfuscate(reference);
+
infoPanel.removeAll();
- if (reference.entry instanceof ClassEntry) {
- showClassEntry((ClassEntry) this.reference.entry);
- } else if (this.reference.entry instanceof FieldEntry) {
- showFieldEntry((FieldEntry) this.reference.entry);
- } else if (this.reference.entry instanceof MethodEntry) {
- showMethodEntry((MethodEntry) this.reference.entry);
- } else if (this.reference.entry instanceof LocalVariableEntry) {
- showLocalVariableEntry((LocalVariableEntry) this.reference.entry);
+ if (translatedReference.entry instanceof ClassEntry) {
+ showClassEntry((ClassEntry) translatedReference.entry);
+ } else if (translatedReference.entry instanceof FieldEntry) {
+ showFieldEntry((FieldEntry) translatedReference.entry);
+ } else if (translatedReference.entry instanceof MethodEntry) {
+ showMethodEntry((MethodEntry) translatedReference.entry);
+ } else if (translatedReference.entry instanceof LocalVariableEntry) {
+ showLocalVariableEntry((LocalVariableEntry) translatedReference.entry);
} else {
- throw new Error("Unknown entry desc: " + this.reference.entry.getClass().getName());
+ throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName());
}
redraw();
@@ -519,7 +525,7 @@ public class Gui {
Token token = this.controller.getToken(pos);
boolean isToken = token != null;
- reference = this.controller.getDeobfReference(token);
+ reference = this.controller.getReference(token);
Entry> referenceEntry = reference != null ? reference.entry : null;
boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry;
@@ -527,7 +533,7 @@ public class Gui {
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 isRenameable = isToken && this.controller.referenceIsRenameable(reference);
+ boolean isRenameable = isToken && this.controller.getDeobfuscator().isRenamable(reference);
if (isToken) {
showReference(reference);
@@ -544,7 +550,7 @@ public class Gui {
this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation());
this.popupMenu.toggleMappingMenu.setEnabled(isRenameable);
- if (isToken && this.controller.entryHasDeobfuscatedName(referenceEntry)) {
+ if (isToken && this.controller.getDeobfuscator().isRemapped(referenceEntry)) {
this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated");
} else {
this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated");
@@ -576,7 +582,10 @@ public class Gui {
// init the text box
final JTextField text = new JTextField();
- text.setText(reference.getNameableName());
+
+ EntryReference, Entry>> translatedReference = controller.getDeobfuscator().deobfuscate(reference);
+ text.setText(translatedReference.getNameableName());
+
text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
text.addKeyListener(new KeyAdapter() {
@Override
@@ -603,7 +612,7 @@ public class Gui {
int offset = text.getText().lastIndexOf('/') + 1;
// If it's a class and isn't in the default package, assume that it's deobfuscated.
- if (reference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0)
+ if (translatedReference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0)
text.select(offset, text.getText().length());
else
text.selectAll();
@@ -719,7 +728,7 @@ public class Gui {
}
public void toggleMapping() {
- if (this.controller.entryHasDeobfuscatedName(reference.entry)) {
+ if (this.controller.getDeobfuscator().isRemapped(reference.entry)) {
this.controller.removeMapping(reference);
} else {
this.controller.markAsDeobfuscated(reference);
@@ -743,7 +752,7 @@ public class Gui {
callback.apply(response);
}
- public void saveMapping() throws IOException {
+ public void saveMapping() {
if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION)
this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath());
}
@@ -757,13 +766,8 @@ public class Gui {
// ask to save before closing
showDiscardDiag((response) -> {
if (response == JOptionPane.YES_OPTION) {
- try {
- this.saveMapping();
- this.frame.dispose();
-
- } catch (IOException ex) {
- throw new Error(ex);
- }
+ this.saveMapping();
+ this.frame.dispose();
} else if (response == JOptionPane.NO_OPTION)
this.frame.dispose();
@@ -796,47 +800,39 @@ public class Gui {
this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false);
}
- public void moveClassTree(EntryReference, Entry>> deobfReference, String newName) {
- String oldEntry = deobfReference.entry.getContainingClass().getPackageName();
+ public void moveClassTree(EntryReference, Entry>> obfReference, String newName) {
+ String oldEntry = obfReference.entry.getContainingClass().getPackageName();
String newEntry = new ClassEntry(newName).getPackageName();
- moveClassTree(deobfReference, newName, oldEntry == null,
- newEntry == null);
+ moveClassTree(obfReference, oldEntry == null, newEntry == null);
}
// TODO: getExpansionState will *not* actually update itself based on name changes!
- public void moveClassTree(EntryReference, Entry>> deobfReference, String newName, boolean isOldOb, boolean isNewOb) {
- ClassEntry oldEntry = deobfReference.entry.getContainingClass();
- ClassEntry newEntry = new ClassEntry(newName);
+ public void moveClassTree(EntryReference, Entry>> obfReference, boolean isOldOb, boolean isNewOb) {
+ ClassEntry classEntry = obfReference.entry.getContainingClass();
// Ob -> deob
List stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses);
List stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses);
if (isOldOb && !isNewOb) {
- this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, obfPanel.obfClasses);
- ClassSelectorPackageNode packageNode = this.obfPanel.obfClasses.getPackageNode(oldEntry);
- this.obfPanel.obfClasses.removeNode(packageNode, oldEntry);
- this.obfPanel.obfClasses.removeNodeIfEmpty(packageNode);
+ this.deobfPanel.deobfClasses.moveClassIn(classEntry);
+ this.obfPanel.obfClasses.moveClassOut(classEntry);
this.deobfPanel.deobfClasses.reload();
this.obfPanel.obfClasses.reload();
}
// Deob -> ob
else if (isNewOb && !isOldOb) {
- this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, deobfPanel.deobfClasses);
- ClassSelectorPackageNode packageNode = this.deobfPanel.deobfClasses.getPackageNode(oldEntry);
- this.deobfPanel.deobfClasses.removeNode(packageNode, oldEntry);
- this.deobfPanel.deobfClasses.removeNodeIfEmpty(packageNode);
+ this.obfPanel.obfClasses.moveClassIn(classEntry);
+ this.deobfPanel.deobfClasses.moveClassOut(classEntry);
this.deobfPanel.deobfClasses.reload();
this.obfPanel.obfClasses.reload();
}
// Local move
else if (isOldOb) {
- this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, null);
- this.obfPanel.obfClasses.removeNodeIfEmpty(this.obfPanel.obfClasses.getPackageNode(oldEntry));
+ this.obfPanel.obfClasses.moveClassIn(classEntry);
this.obfPanel.obfClasses.reload();
} else {
- this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, null);
- this.deobfPanel.deobfClasses.removeNodeIfEmpty(this.deobfPanel.deobfClasses.getPackageNode(oldEntry));
+ this.deobfPanel.deobfClasses.moveClassIn(classEntry);
this.deobfPanel.deobfClasses.reload();
}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
index fd9e7f0..03e1768 100644
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -11,13 +11,13 @@
package cuchaz.enigma.gui;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.strobel.decompiler.languages.java.ast.CompilationUnit;
import cuchaz.enigma.Deobfuscator;
+import cuchaz.enigma.SourceProvider;
import cuchaz.enigma.analysis.*;
-import cuchaz.enigma.api.EnigmaPlugin;
import cuchaz.enigma.config.Config;
import cuchaz.enigma.gui.dialog.ProgressDialog;
import cuchaz.enigma.throwables.MappingParseException;
@@ -36,16 +36,20 @@ import java.awt.event.ItemEvent;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
-import java.util.*;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
public class GuiController {
+ private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("decompiler-thread").build());
private Deobfuscator deobfuscator;
private Gui gui;
- private SourceIndex index;
- private ClassEntry currentObfClass;
+ private DecompiledClassSource currentSource;
private Deque, Entry>>> referenceStack;
private Path loadedMappingPath;
@@ -54,8 +58,7 @@ public class GuiController {
public GuiController(Gui gui) {
this.gui = gui;
this.deobfuscator = null;
- this.index = null;
- this.currentObfClass = null;
+ this.currentSource = null;
this.referenceStack = Queues.newArrayDeque();
}
@@ -93,7 +96,7 @@ public class GuiController {
public void saveMappings(MappingFormat format, Path path) {
EntryRemapper mapper = deobfuscator.getMapper();
- MappingDelta delta = mapper.takeMappingDelta();
+ MappingDelta delta = mapper.takeMappingDelta();
boolean saveAll = !path.equals(loadedMappingPath);
ProgressDialog.runInThread(this.gui.getFrame(), progress -> {
@@ -116,189 +119,167 @@ public class GuiController {
}
public void exportSource(final File dirOut) {
- ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress));
+ ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut.toPath(), progress));
}
public void exportJar(final File fileOut) {
- ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress));
+ ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeTransformedJar(fileOut, progress));
}
public Token getToken(int pos) {
- if (this.index == null) {
+ if (this.currentSource == null) {
return null;
}
- return this.index.getReferenceToken(pos);
+ return this.currentSource.getIndex().getReferenceToken(pos);
}
@Nullable
- public EntryReference, Entry>> getDeobfReference(Token token) {
- if (this.index == null) {
+ public EntryReference, Entry>> getReference(Token token) {
+ if (this.currentSource == null) {
return null;
}
- return this.index.getDeobfReference(token);
+ return this.currentSource.getIndex().getReference(token);
}
public ReadableToken getReadableToken(Token token) {
- if (this.index == null) {
+ if (this.currentSource == null) {
return null;
}
+ SourceIndex index = this.currentSource.getIndex();
return new ReadableToken(
- this.index.getLineNumber(token.start),
- this.index.getColumnNumber(token.start),
- this.index.getColumnNumber(token.end)
+ index.getLineNumber(token.start),
+ index.getColumnNumber(token.start),
+ index.getColumnNumber(token.end)
);
}
- public boolean entryHasDeobfuscatedName(Entry> deobfEntry) {
- EntryResolver resolver = this.deobfuscator.getMapper().getDeobfResolver();
- Entry> resolvedEntry = resolver.resolveFirstEntry(deobfEntry, ResolutionStrategy.RESOLVE_ROOT);
- return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.getMapper().obfuscate(resolvedEntry));
+ public boolean entryIsInJar(Entry> entry) {
+ if (entry == null) return false;
+ return this.deobfuscator.isRenamable(entry);
}
- public boolean entryIsInJar(Entry> deobfEntry) {
- if (deobfEntry == null) return false;
- return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.getMapper().obfuscate(deobfEntry));
- }
-
- public boolean referenceIsRenameable(EntryReference, Entry>> deobfReference) {
- if (deobfReference == null) return false;
- return this.deobfuscator.isRenameable(this.deobfuscator.getMapper().obfuscate(deobfReference));
- }
-
- public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
- ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry);
+ public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) {
Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
- ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, obfClassEntry);
- return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
+ ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, entry);
+ return ClassInheritanceTreeNode.findNode(rootNode, entry);
}
- public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
- ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry);
+ public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) {
Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
- return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, obfClassEntry);
+ return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, entry);
}
- public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
- MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry);
+ public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) {
Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
- MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, obfMethodEntry);
- return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
+ MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, entry);
+ return MethodInheritanceTreeNode.findNode(rootNode, entry);
}
- public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
- MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry);
+ public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) {
Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
- List rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, obfMethodEntry);
+ List rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, entry);
if (rootNodes.isEmpty()) {
return null;
}
if (rootNodes.size() > 1) {
- System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one.");
+ System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one.");
}
- return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry);
+ return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry);
}
- public ClassReferenceTreeNode getClassReferences(ClassEntry deobfClassEntry) {
- ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry);
+ public ClassReferenceTreeNode getClassReferences(ClassEntry entry) {
Translator deobfuscator = this.deobfuscator.getMapper().getDeobfuscator();
- ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, obfClassEntry);
+ ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry);
rootNode.load(this.deobfuscator.getJarIndex(), true);
return rootNode;
}
- public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
- FieldEntry obfFieldEntry = this.deobfuscator.getMapper().obfuscate(deobfFieldEntry);
+ public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) {
Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
- FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, obfFieldEntry);
+ FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry);
rootNode.load(this.deobfuscator.getJarIndex(), true);
return rootNode;
}
- public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry, boolean recursive) {
- MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry);
+ public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) {
Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
- MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, obfMethodEntry);
+ MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry);
rootNode.load(this.deobfuscator.getJarIndex(), true, recursive);
return rootNode;
}
- public void rename(EntryReference, Entry>> deobfReference, String newName, boolean refreshClassTree) {
- EntryReference, Entry>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference);
- this.deobfuscator.rename(obfReference.getNameableEntry(), newName);
-
- if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass())
- this.gui.moveClassTree(deobfReference, newName);
- refreshCurrentClass(obfReference);
+ 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>> deobfReference) {
- EntryReference, Entry>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference);
- this.deobfuscator.removeMapping(obfReference.getNameableEntry());
- if (deobfReference.entry instanceof ClassEntry)
- this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true);
- refreshCurrentClass(obfReference);
+ 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>> deobfReference) {
- EntryReference, Entry>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference);
- this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
- if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass())
- this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false);
- refreshCurrentClass(obfReference);
+ 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);
}
- public void openDeclaration(Entry> deobfEntry) {
- if (deobfEntry == null) {
+ public void openDeclaration(Entry> entry) {
+ if (entry == null) {
throw new IllegalArgumentException("Entry cannot be null!");
}
- openReference(new EntryReference<>(deobfEntry, deobfEntry.getName()));
+ openReference(new EntryReference<>(entry, entry.getName()));
}
- public void openReference(EntryReference, Entry>> deobfReference) {
- if (deobfReference == null) {
+ public void openReference(EntryReference, Entry>> reference) {
+ if (reference == null) {
throw new IllegalArgumentException("Reference cannot be null!");
}
// get the reference target class
- EntryReference, Entry>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference);
- ClassEntry obfClassEntry = obfReference.getLocationClassEntry();
- if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
- throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
+ ClassEntry classEntry = reference.getLocationClassEntry();
+ if (!this.deobfuscator.isRenamable(classEntry)) {
+ throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!");
}
- if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) {
+
+ if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) {
// deobfuscate the class, then navigate to the reference
- this.currentObfClass = obfClassEntry;
- deobfuscate(this.currentObfClass, obfReference);
+ loadClass(classEntry, () -> showReference(reference));
} else {
- showReference(obfReference);
+ showReference(reference);
}
}
- private void showReference(EntryReference, Entry>> obfReference) {
+ private void showReference(EntryReference, Entry>> reference) {
EntryRemapper mapper = this.deobfuscator.getMapper();
- Collection tokens = mapper.getObfResolver().resolveReference(obfReference, ResolutionStrategy.RESOLVE_ROOT)
+ SourceIndex index = this.currentSource.getIndex();
+ Collection tokens = mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_ROOT)
.stream()
- .map(mapper::deobfuscate)
- .flatMap(reference -> index.getReferenceTokens(reference).stream())
+ .flatMap(r -> index.getReferenceTokens(r).stream())
.collect(Collectors.toList());
if (tokens.isEmpty()) {
// DEBUG
- System.err.println(String.format("WARNING: no tokens found for %s in %s", tokens, this.currentObfClass));
+ System.err.println(String.format("WARNING: no tokens found for %s in %s", tokens, this.currentSource.getEntry()));
} else {
this.gui.showTokens(tokens);
}
}
- public void savePreviousReference(EntryReference, Entry>> deobfReference) {
- this.referenceStack.push(this.deobfuscator.getMapper().obfuscate(deobfReference));
+ public void savePreviousReference(EntryReference, Entry>> reference) {
+ this.referenceStack.push(reference);
}
public void openPreviousReference() {
if (hasPreviousLocation()) {
- openReference(this.deobfuscator.getMapper().deobfuscate(this.referenceStack.pop()));
+ openReference(this.referenceStack.pop());
}
}
@@ -318,97 +299,65 @@ public class GuiController {
refreshCurrentClass(null);
}
- private void refreshCurrentClass(EntryReference, Entry>> obfReference) {
- if (this.currentObfClass != null) {
- deobfuscate(this.currentObfClass, obfReference);
+ private void refreshCurrentClass(EntryReference, Entry>> reference) {
+ if (currentSource != null) {
+ loadClass(currentSource.getEntry(), () -> {
+ if (reference != null) {
+ showReference(reference);
+ }
+ });
}
}
- private void deobfuscate(final ClassEntry classEntry, final EntryReference, Entry>> obfReference) {
+ private void loadClass(ClassEntry classEntry, Runnable callback) {
+ ClassEntry targetClass = classEntry.getOutermostClass();
- this.gui.setSource("(deobfuscating...)");
+ boolean requiresDecompile = currentSource == null || !currentSource.getEntry().equals(targetClass);
+ if (requiresDecompile) {
+ gui.setEditorText("(decompiling...)");
+ }
- // run the deobfuscator in a separate thread so we don't block the GUI event queue
- new Thread(() ->
- {
- // decompile,deobfuscate the bytecode
- CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClass().getFullName());
- if (sourceTree == null) {
- // decompilation of this class is not supported
- gui.setSource("Unable to find class: " + classEntry);
- return;
- }
- String source = deobfuscator.getSource(sourceTree);
- index = deobfuscator.getSourceIndex(sourceTree, source);
-
- String sourceString = index.getSource();
-
- // set the highlighted tokens
- List obfuscatedTokens = Lists.newArrayList();
- List proposedTokens = Lists.newArrayList();
- List deobfuscatedTokens = Lists.newArrayList();
- List otherTokens = Lists.newArrayList();
-
- int offset = 0;
- Map tokenRemap = new HashMap<>();
- boolean remapped = false;
-
- for (Token inToken : index.referenceTokens()) {
- Token token = inToken.move(offset);
-
- EntryReference, Entry>> reference = index.getDeobfReference(inToken);
- if (referenceIsRenameable(reference)) {
- boolean added = false;
-
- if (!entryHasDeobfuscatedName(reference.getNameableEntry())) {
- Entry> obfEntry = deobfuscator.getMapper().obfuscate(reference.getNameableEntry());
- if (obfEntry instanceof FieldEntry) {
- for (EnigmaPlugin plugin : deobfuscator.getPlugins()) {
- String owner = obfEntry.getContainingClass().getFullName();
- String proposal = plugin.proposeFieldName(owner, obfEntry.getName(), ((FieldEntry) obfEntry).getDesc().toString());
- if (proposal != null) {
- proposedTokens.add(token);
- offset += token.getRenameOffset(proposal);
- sourceString = token.rename(sourceString, proposal);
- added = true;
- remapped = true;
- break;
- }
- }
- }
- }
-
- if (!added) {
- if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
- deobfuscatedTokens.add(token);
- } else {
- obfuscatedTokens.add(token);
- }
- }
- } else {
- otherTokens.add(token);
+ DECOMPILER_SERVICE.submit(() -> {
+ try {
+ if (requiresDecompile) {
+ decompileSource(targetClass, deobfuscator.getObfSourceProvider());
}
- tokenRemap.put(inToken, token);
+ remapSource(deobfuscator.getMapper().getDeobfuscator());
+ callback.run();
+ } catch (Throwable t) {
+ System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName());
+ t.printStackTrace(System.err);
}
+ });
+ }
- if (remapped) {
- index.remap(sourceString, tokenRemap);
- }
+ private void decompileSource(ClassEntry targetClass, SourceProvider sourceProvider) {
+ CompilationUnit sourceTree = sourceProvider.getSources(targetClass.getFullName());
+ if (sourceTree == null) {
+ gui.setEditorText("Unable to find class: " + targetClass);
+ return;
+ }
- gui.setSource(sourceString);
- if (obfReference != null) {
- showReference(obfReference);
- }
+ DropImportAstTransform.INSTANCE.run(sourceTree);
+
+ String sourceString = sourceProvider.writeSourceToString(sourceTree);
+
+ SourceIndex index = SourceIndex.buildIndex(sourceString, sourceTree, true);
+ index.resolveReferences(deobfuscator.getMapper().getObfResolver());
+
+ currentSource = new DecompiledClassSource(targetClass, deobfuscator, index);
+ }
+
+ private void remapSource(Translator translator) {
+ if (currentSource == null) {
+ return;
+ }
+
+ currentSource.remapSource(translator);
- gui.setEditorTheme(Config.getInstance().lookAndFeel);
- gui.setHighlightedTokens(ImmutableMap.of(
- "obfuscated", obfuscatedTokens,
- "proposed", proposedTokens,
- "deobfuscated", deobfuscatedTokens,
- "other", otherTokens
- ));
- }).start();
+ gui.setEditorTheme(Config.getInstance().lookAndFeel);
+ gui.setSource(currentSource);
}
public Deobfuscator getDeobfuscator() {
diff --git a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java
new file mode 100644
index 0000000..f38f44e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java
@@ -0,0 +1,64 @@
+package cuchaz.enigma.gui;
+
+import cuchaz.enigma.analysis.Token;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SourceRemapper {
+ private final String source;
+ private final Iterable tokens;
+
+ public SourceRemapper(String source, Iterable tokens) {
+ this.source = source;
+ this.tokens = tokens;
+ }
+
+ public Result remap(Remapper remapper) {
+ StringBuffer remappedSource = new StringBuffer(source);
+ Map remappedTokens = new HashMap<>();
+
+ int accumulatedOffset = 0;
+ for (Token token : tokens) {
+ Token movedToken = token.move(accumulatedOffset);
+
+ String remappedName = remapper.remap(token, movedToken);
+ if (remappedName != null) {
+ accumulatedOffset += movedToken.getRenameOffset(remappedName);
+ movedToken.rename(remappedSource, remappedName);
+ }
+
+ if (!token.equals(movedToken)) {
+ remappedTokens.put(token, movedToken);
+ }
+ }
+
+ return new Result(remappedSource.toString(), remappedTokens);
+ }
+
+ public static class Result {
+ private final String remappedSource;
+ private final Map remappedTokens;
+
+ Result(String remappedSource, Map remappedTokens) {
+ this.remappedSource = remappedSource;
+ this.remappedTokens = remappedTokens;
+ }
+
+ public String getSource() {
+ return remappedSource;
+ }
+
+ public Token getRemappedToken(Token token) {
+ return remappedTokens.getOrDefault(token, token);
+ }
+
+ public boolean isEmpty() {
+ return remappedTokens.isEmpty();
+ }
+ }
+
+ public interface Remapper {
+ String remap(Token token, Token movedToken);
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
index f4f0277..dfbfa65 100644
--- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
+++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
@@ -152,12 +152,8 @@ public class MenuBar extends JMenuBar {
if (this.gui.getController().isDirty()) {
this.gui.showDiscardDiag((response -> {
if (response == JOptionPane.YES_OPTION) {
- try {
- gui.saveMapping();
- this.gui.getController().closeMappings();
- } catch (IOException e) {
- throw new Error(e);
- }
+ gui.saveMapping();
+ this.gui.getController().closeMappings();
} else if (response == JOptionPane.NO_OPTION)
this.gui.getController().closeMappings();
return null;
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
index 10366ce..cef6494 100644
--- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
@@ -34,7 +34,9 @@ public class BoxHighlightPainter implements Highlighter.HighlightPainter {
public static Rectangle getBounds(JTextComponent text, int start, int end) {
try {
// determine the bounds of the text
- Rectangle bounds = text.modelToView(start).union(text.modelToView(end));
+ Rectangle startRect = text.modelToView(start);
+ Rectangle endRect = text.modelToView(end);
+ Rectangle bounds = startRect.union(endRect);
// adjust the box so it looks nice
bounds.x -= 2;
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java
new file mode 100644
index 0000000..ae23f32
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java
@@ -0,0 +1,7 @@
+package cuchaz.enigma.gui.highlight;
+
+public enum TokenHighlightType {
+ OBFUSCATED,
+ DEOBFUSCATED,
+ PROPOSED
+}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
index bf6b178..922f8f2 100644
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
+++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
@@ -17,13 +17,19 @@ import javax.swing.tree.DefaultMutableTreeNode;
public class ClassSelectorClassNode extends DefaultMutableTreeNode {
+ private final ClassEntry obfEntry;
private ClassEntry classEntry;
- public ClassSelectorClassNode(ClassEntry classEntry) {
+ public ClassSelectorClassNode(ClassEntry obfEntry, ClassEntry classEntry) {
+ this.obfEntry = obfEntry;
this.classEntry = classEntry;
this.setUserObject(classEntry);
}
+ public ClassEntry getObfEntry() {
+ return obfEntry;
+ }
+
public ClassEntry getClassEntry() {
return this.classEntry;
}
diff --git a/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java b/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java
new file mode 100644
index 0000000..18c966c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java
@@ -0,0 +1,44 @@
+package cuchaz.enigma.translation;
+
+import cuchaz.enigma.translation.mapping.NameValidator;
+import cuchaz.enigma.translation.representation.TypeDescriptor;
+
+import java.util.Collection;
+import java.util.Locale;
+
+public class LocalNameGenerator {
+ public static String generateArgumentName(int index, TypeDescriptor desc, Collection arguments) {
+ boolean uniqueType = arguments.stream().filter(desc::equals).count() <= 1;
+ String translatedName;
+ int nameIndex = index + 1;
+ StringBuilder nameBuilder = new StringBuilder(getTypeName(desc));
+ if (!uniqueType || NameValidator.isReserved(nameBuilder.toString())) {
+ nameBuilder.append(nameIndex);
+ }
+ translatedName = nameBuilder.toString();
+ return translatedName;
+ }
+
+ public static String generateLocalVariableName(int index, TypeDescriptor desc) {
+ int nameIndex = index + 1;
+ return getTypeName(desc) + nameIndex;
+ }
+
+ private static String getTypeName(TypeDescriptor desc) {
+ // Unfortunately each of these have different name getters, so they have different code paths
+ if (desc.isPrimitive()) {
+ TypeDescriptor.Primitive argCls = desc.getPrimitive();
+ return argCls.name().toLowerCase(Locale.ROOT);
+ } else if (desc.isArray()) {
+ // List types would require this whole block again, so just go with aListx
+ return "arr";
+ } else if (desc.isType()) {
+ String typeName = desc.getTypeEntry().getSimpleName().replace("$", "");
+ typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1);
+ return typeName;
+ } else {
+ System.err.println("Encountered invalid argument type descriptor " + desc.toString());
+ return "var";
+ }
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
index b7d8d17..1203aba 100644
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
+++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
@@ -6,7 +6,6 @@ import cuchaz.enigma.translation.Translatable;
import cuchaz.enigma.translation.Translator;
import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
import cuchaz.enigma.translation.mapping.tree.EntryTree;
-import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
import cuchaz.enigma.translation.representation.entry.Entry;
@@ -14,59 +13,25 @@ import javax.annotation.Nullable;
import java.util.Collection;
public class EntryRemapper {
- private final EntryTree obfToDeobf;
- private final DeltaTrackingTree deobfToObf;
-
- private final JarIndex obfIndex;
+ private final DeltaTrackingTree obfToDeobf;
private final EntryResolver obfResolver;
- private EntryResolver deobfResolver;
-
private final Translator deobfuscator;
- private Translator obfuscator;
private final MappingValidator validator;
- private EntryRemapper(JarIndex jarIndex, EntryTree obfToDeobf, EntryTree deobfToObf) {
- this.obfToDeobf = obfToDeobf;
- this.deobfToObf = new DeltaTrackingTree<>(deobfToObf);
+ public EntryRemapper(JarIndex jarIndex, EntryTree obfToDeobf) {
+ this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf);
- this.obfIndex = jarIndex;
this.obfResolver = jarIndex.getEntryResolver();
this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver);
- rebuildDeobfIndex();
- this.validator = new MappingValidator(this.deobfToObf, deobfuscator, obfResolver);
+ this.validator = new MappingValidator(obfToDeobf, deobfuscator, obfResolver);
}
public EntryRemapper(JarIndex jarIndex) {
- this(jarIndex, new HashEntryTree<>(), new HashEntryTree<>());
- }
-
- public EntryRemapper(JarIndex jarIndex, EntryTree deobfuscationTrees) {
- this(jarIndex, deobfuscationTrees, inverse(deobfuscationTrees));
- }
-
- private static EntryTree inverse(EntryTree tree) {
- Translator translator = new MappingTranslator(tree, VoidEntryResolver.INSTANCE);
- EntryTree inverse = new HashEntryTree<>();
-
- // Naive approach, could operate on the nodes of the tree. However, this runs infrequently.
- Collection> entries = tree.getAllEntries();
- for (Entry> sourceEntry : entries) {
- Entry> targetEntry = translator.translate(sourceEntry);
- inverse.insert(targetEntry, new EntryMapping(sourceEntry.getName()));
- }
-
- return inverse;
- }
-
- private void rebuildDeobfIndex() {
- JarIndex deobfIndex = obfIndex.remapped(deobfuscator);
-
- this.deobfResolver = deobfIndex.getEntryResolver();
- this.obfuscator = new MappingTranslator(deobfToObf, deobfResolver);
+ this(jarIndex, new HashEntryTree<>());
}
public > void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
@@ -76,84 +41,31 @@ public class EntryRemapper {
validator.validateRename(resolvedEntry, deobfMapping.getTargetName());
}
- setObfToDeobf(resolvedEntry, deobfMapping);
+ obfToDeobf.insert(obfuscatedEntry, deobfMapping);
}
-
- // Temporary hack, not very performant
- rebuildDeobfIndex();
- }
-
- public > void mapFromDeobf(E deobfuscatedEntry, @Nullable EntryMapping deobfMapping) {
- E obfuscatedEntry = obfuscate(deobfuscatedEntry);
- mapFromObf(obfuscatedEntry, deobfMapping);
}
public void removeByObf(Entry> obfuscatedEntry) {
mapFromObf(obfuscatedEntry, null);
}
- public void removeByDeobf(Entry> deobfuscatedEntry) {
- mapFromObf(obfuscate(deobfuscatedEntry), null);
- }
-
- private > void setObfToDeobf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
- E prevDeobf = deobfuscate(obfuscatedEntry);
- obfToDeobf.insert(obfuscatedEntry, deobfMapping);
-
- E newDeobf = deobfuscate(obfuscatedEntry);
-
- // Reconstruct the children of this node in the deobf -> obf tree with our new mapping
- // We only need to do this for deobf -> obf because the obf tree is always consistent on the left hand side
- // We lookup by obf, and the obf never changes. This is not the case for deobf so we need to update the tree.
-
- EntryTreeNode node = deobfToObf.findNode(prevDeobf);
- if (node != null) {
- for (EntryTreeNode child : node.getNodesRecursively()) {
- Entry> entry = child.getEntry();
- EntryMapping mapping = new EntryMapping(obfuscate(entry).getName());
-
- deobfToObf.insert(entry.replaceAncestor(prevDeobf, newDeobf), mapping);
- deobfToObf.remove(entry);
- }
- } else {
- deobfToObf.insert(newDeobf, new EntryMapping(obfuscatedEntry.getName()));
- }
- }
-
@Nullable
public EntryMapping getDeobfMapping(Entry> entry) {
return obfToDeobf.get(entry);
}
- @Nullable
- public EntryMapping getObfMapping(Entry> entry) {
- return deobfToObf.get(entry);
- }
-
public boolean hasDeobfMapping(Entry> obfEntry) {
return obfToDeobf.contains(obfEntry);
}
- public boolean hasObfMapping(Entry> deobfEntry) {
- return deobfToObf.contains(deobfEntry);
- }
-
public T deobfuscate(T translatable) {
return deobfuscator.translate(translatable);
}
- public T obfuscate(T translatable) {
- return obfuscator.translate(translatable);
- }
-
public Translator getDeobfuscator() {
return deobfuscator;
}
- public Translator getObfuscator() {
- return obfuscator;
- }
-
public Collection> getObfEntries() {
return obfToDeobf.getAllEntries();
}
@@ -162,40 +74,23 @@ public class EntryRemapper {
return obfToDeobf.getRootEntries();
}
- public Collection> getDeobfEntries() {
- return deobfToObf.getAllEntries();
- }
-
public Collection> getObfChildren(Entry> obfuscatedEntry) {
return obfToDeobf.getChildren(obfuscatedEntry);
}
- public Collection> getDeobfChildren(Entry> deobfuscatedEntry) {
- return deobfToObf.getChildren(deobfuscatedEntry);
- }
-
- public EntryTree getObfToDeobf() {
+ public DeltaTrackingTree getObfToDeobf() {
return obfToDeobf;
}
- public DeltaTrackingTree getDeobfToObf() {
- return deobfToObf;
- }
-
- public MappingDelta takeMappingDelta() {
- MappingDelta delta = deobfToObf.takeDelta();
- return delta.translate(obfuscator, VoidEntryResolver.INSTANCE, deobfToObf);
+ public MappingDelta takeMappingDelta() {
+ return obfToDeobf.takeDelta();
}
public boolean isDirty() {
- return deobfToObf.isDirty();
+ return obfToDeobf.isDirty();
}
public EntryResolver getObfResolver() {
return obfResolver;
}
-
- public EntryResolver getDeobfResolver() {
- return deobfResolver;
- }
}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
index 4fba49d..9f1f468 100644
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
+++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
@@ -6,28 +6,35 @@ import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
import cuchaz.enigma.translation.mapping.tree.EntryTree;
import cuchaz.enigma.translation.representation.entry.Entry;
-public class MappingDelta implements Translatable {
+public class MappingDelta implements Translatable {
public static final Object PLACEHOLDER = new Object();
+ private final EntryTree baseMappings;
+
private final EntryTree