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') diff --git a/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java index fe13321c..b2aed84d 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 a84cd5e7..7c0a3d53 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 00000000..fc051d33 --- /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 00000000..b1a8cd58 --- /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 076c5468..ef452b0f 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 24822dd3..00000000 --- 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 00000000..48e5a590 --- /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 657bee42..f6eee690 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 4c1f695b..00000000 --- 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 00000000..991e91d4 --- /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 ad3aceb0..ddcda3ed 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 abdec92e..ed12ce37 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 486603ce..a4fe9ee9 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 73db28fb..c4785b67 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 564830c6..75a66a2d 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 14fa7ca4..12e0aa6c 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 e1903d9f..8f6bd462 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 55bfbc24..773eaf18 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 d165cc83..17bed54c 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 0880244a..9b21cbae 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 537e7726..00000000 --- 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 00000000..16dbba1b --- /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 53d09bb6..e4c41d32 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 a5a33e69..c824265f 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 55f867ee..6f5a337c 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 00324f47..400ea3f0 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 c3b7288c..39d0333b 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 08100438..e119640a 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 00000000..03f76c90 --- /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 d119735c..a6e20a27 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 fd9e7f0c..03e17682 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 00000000..f38f44ea --- /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 f4f02776..dfbfa650 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 10366ced..cef64943 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 00000000..ae23f324 --- /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 bf6b1788..922f8f24 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 00000000..18c966cd --- /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 b7d8d17e..1203aba0 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 4fba49d5..9f1f468b 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 additions; private final EntryTree deletions; - public MappingDelta(EntryTree additions, EntryTree deletions) { + public MappingDelta(EntryTree baseMappings, EntryTree additions, EntryTree deletions) { + this.baseMappings = baseMappings; this.additions = additions; this.deletions = deletions; } - public MappingDelta() { - this(new HashEntryTree<>(), new HashEntryTree<>()); + public MappingDelta(EntryTree baseMappings) { + this(baseMappings, new HashEntryTree<>(), new HashEntryTree<>()); } - public static MappingDelta added(EntryTree mappings) { + public static MappingDelta added(EntryTree mappings) { EntryTree additions = new HashEntryTree<>(); for (Entry entry : mappings.getAllEntries()) { additions.insert(entry, PLACEHOLDER); } - return new MappingDelta(additions, new HashEntryTree<>()); + return new MappingDelta<>(new HashEntryTree<>(), additions, new HashEntryTree<>()); + } + + public EntryTree getBaseMappings() { + return baseMappings; } public EntryTree getAdditions() { @@ -39,18 +46,11 @@ public class MappingDelta implements Translatable { } @Override - public MappingDelta translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - return new MappingDelta( - translate(translator, additions), - translate(translator, deletions) + public MappingDelta translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return new MappingDelta<>( + translator.translate(baseMappings), + translator.translate(additions), + translator.translate(deletions) ); } - - private EntryTree translate(Translator translator, EntryTree tree) { - EntryTree translatedTree = new HashEntryTree<>(); - for (Entry entry : tree.getAllEntries()) { - translatedTree.insert(translator.translate(entry), PLACEHOLDER); - } - return translatedTree; - } } diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java index 422bf380..9be48c3a 100644 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java +++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java @@ -6,14 +6,15 @@ import cuchaz.enigma.translation.mapping.tree.EntryTree; import cuchaz.enigma.translation.representation.entry.Entry; import java.util.Collection; +import java.util.stream.Collectors; public class MappingValidator { - private final EntryTree deobfToObf; + private final EntryTree obfToDeobf; private final Translator deobfuscator; private final EntryResolver entryResolver; - public MappingValidator(EntryTree deobfToObf, Translator deobfuscator, EntryResolver entryResolver) { - this.deobfToObf = deobfToObf; + public MappingValidator(EntryTree obfToDeobf, Translator deobfuscator, EntryResolver entryResolver) { + this.obfToDeobf = obfToDeobf; this.deobfuscator = deobfuscator; this.entryResolver = entryResolver; } @@ -28,8 +29,11 @@ public class MappingValidator { private void validateUnique(Entry entry, String name) { Entry translatedEntry = deobfuscator.translate(entry); - Collection> siblings = deobfToObf.getSiblings(translatedEntry); - if (!isUnique(translatedEntry, siblings, name)) { + Collection> translatedSiblings = obfToDeobf.getSiblings(entry).stream() + .map(deobfuscator::translate) + .collect(Collectors.toList()); + + if (!isUnique(translatedEntry, translatedSiblings, name)) { throw new IllegalNameException(name, "Name is not unique in " + translatedEntry.getParent() + "!"); } } diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java index 5acb1da2..1d44b6e8 100644 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java @@ -14,10 +14,7 @@ package cuchaz.enigma.translation.mapping.serde; import cuchaz.enigma.ProgressListener; import cuchaz.enigma.translation.MappingTranslator; import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.AccessModifier; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingDelta; -import cuchaz.enigma.translation.mapping.VoidEntryResolver; +import cuchaz.enigma.translation.mapping.*; import cuchaz.enigma.translation.mapping.tree.EntryTree; import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; import cuchaz.enigma.translation.representation.entry.*; @@ -37,7 +34,7 @@ import java.util.stream.Collectors; public enum EnigmaMappingsWriter implements MappingsWriter { FILE { @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress) { + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress) { Collection classes = mappings.getRootEntries().stream() .filter(entry -> entry instanceof ClassEntry) .map(entry -> (ClassEntry) entry) @@ -58,8 +55,8 @@ public enum EnigmaMappingsWriter implements MappingsWriter { }, DIRECTORY { @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress) { - applyDeletions(delta.getDeletions(), path); + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress) { + applyDeletions(delta.getBaseMappings(), delta.getDeletions(), path); Collection classes = delta.getAdditions().getRootEntries().stream() .filter(entry -> entry instanceof ClassEntry) @@ -76,8 +73,8 @@ public enum EnigmaMappingsWriter implements MappingsWriter { try { Path classPath = resolve(path, translator.translate(classEntry)); - Files.deleteIfExists(classPath); Files.createDirectories(classPath.getParent()); + Files.deleteIfExists(classPath); try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(classPath))) { writeRoot(writer, mappings, classEntry); @@ -89,10 +86,12 @@ public enum EnigmaMappingsWriter implements MappingsWriter { }); } - private void applyDeletions(EntryTree deletions, Path root) { + private void applyDeletions(EntryTree baseMappings, EntryTree deletions, Path root) { + Translator oldMappingTranslator = new MappingTranslator(baseMappings, VoidEntryResolver.INSTANCE); + Collection deletedClasses = deletions.getRootEntries().stream() .filter(e -> e instanceof ClassEntry) - .map(e -> (ClassEntry) e) + .map(e -> oldMappingTranslator.translate((ClassEntry) e)) .collect(Collectors.toList()); for (ClassEntry classEntry : deletedClasses) { diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java index 4db16454..622a0e15 100644 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java @@ -28,7 +28,7 @@ public enum MappingFormat { write(mappings, MappingDelta.added(mappings), path, progressListener); } - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progressListener) { + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progressListener) { if (writer == null) { throw new IllegalStateException(name() + " does not support writing"); } diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java index b5196681..77f6ee03 100644 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java @@ -8,5 +8,9 @@ import cuchaz.enigma.translation.mapping.tree.EntryTree; import java.nio.file.Path; public interface MappingsWriter { - void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress); + void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress); + + default void write(EntryTree mappings, Path path, ProgressListener progress) { + write(mappings, MappingDelta.added(mappings), path, progress); + } } diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java index 5ff91413..40be136a 100644 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java @@ -29,7 +29,7 @@ public enum SrgMappingsWriter implements MappingsWriter { INSTANCE; @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress) { + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress) { try { Files.deleteIfExists(path); Files.createFile(path); diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java index 98a01df2..36be5e1e 100644 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java +++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java @@ -1,5 +1,9 @@ package cuchaz.enigma.translation.mapping.tree; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.mapping.MappingDelta; import cuchaz.enigma.translation.representation.entry.Entry; @@ -10,11 +14,13 @@ import java.util.Iterator; public class DeltaTrackingTree implements EntryTree { private final EntryTree delegate; + private EntryTree deltaReference; private EntryTree additions = new HashEntryTree<>(); private EntryTree deletions = new HashEntryTree<>(); public DeltaTrackingTree(EntryTree delegate) { this.delegate = delegate; + this.deltaReference = new HashEntryTree<>(delegate); } public DeltaTrackingTree() { @@ -40,7 +46,7 @@ public class DeltaTrackingTree implements EntryTree { } public void trackAddition(Entry entry) { - deletions.remove(entry); + deletions.insert(entry, MappingDelta.PLACEHOLDER); additions.insert(entry, MappingDelta.PLACEHOLDER); } @@ -81,6 +87,14 @@ public class DeltaTrackingTree implements EntryTree { return delegate.getRootEntries(); } + @Override + public DeltaTrackingTree translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + DeltaTrackingTree translatedTree = new DeltaTrackingTree<>(delegate.translate(translator, resolver, mappings)); + translatedTree.additions = additions.translate(translator, resolver, mappings); + translatedTree.deletions = deletions.translate(translator, resolver, mappings); + return translatedTree; + } + @Override public Collection> getAllEntries() { return delegate.getAllEntries(); @@ -96,13 +110,14 @@ public class DeltaTrackingTree implements EntryTree { return delegate.iterator(); } - public MappingDelta takeDelta() { - MappingDelta delta = new MappingDelta(additions, deletions); + public MappingDelta takeDelta() { + MappingDelta delta = new MappingDelta<>(deltaReference, additions, deletions); resetDelta(); return delta; } private void resetDelta() { + deltaReference = new HashEntryTree<>(delegate); additions = new HashEntryTree<>(); deletions = new HashEntryTree<>(); } diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java index 73fe12d0..4f341f45 100644 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java +++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java @@ -1,12 +1,16 @@ package cuchaz.enigma.translation.mapping.tree; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.representation.entry.Entry; import javax.annotation.Nullable; import java.util.Collection; -public interface EntryTree extends EntryMap, Iterable> { +public interface EntryTree extends EntryMap, Iterable>, Translatable { Collection> getChildren(Entry entry); Collection> getSiblings(Entry entry); @@ -17,4 +21,7 @@ public interface EntryTree extends EntryMap, Iterable> { Collection> getAllNodes(); Collection> getRootEntries(); + + @Override + EntryTree translate(Translator translator, EntryResolver resolver, EntryMap mappings); } diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java index ff88bf94..551fb1c8 100644 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java +++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java @@ -1,5 +1,9 @@ package cuchaz.enigma.translation.mapping.tree; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.representation.entry.Entry; import javax.annotation.Nullable; @@ -9,6 +13,15 @@ import java.util.stream.Collectors; public class HashEntryTree implements EntryTree { private final Map, HashTreeNode> root = new HashMap<>(); + public HashEntryTree() { + } + + public HashEntryTree(EntryTree tree) { + for (EntryTreeNode node : tree.getAllNodes()) { + insert(node.getEntry(), node.getValue()); + } + } + @Override public void insert(Entry entry, T value) { List> path = computePath(entry); @@ -156,4 +169,13 @@ public class HashEntryTree implements EntryTree { public boolean isEmpty() { return root.isEmpty(); } + + @Override + public HashEntryTree translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + HashEntryTree translatedTree = new HashEntryTree<>(); + for (EntryTreeNode node : getAllNodes()) { + translatedTree.insert(translator.translate(node.getEntry()), node.getValue()); + } + return translatedTree; + } } diff --git a/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java index 9c9fa3d3..a9ec5fac 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java +++ b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java @@ -17,29 +17,23 @@ import com.strobel.assembler.metadata.MethodDefinition; import cuchaz.enigma.translation.representation.entry.*; public class ProcyonEntryFactory { - private final ReferencedEntryPool entryPool; - - public ProcyonEntryFactory(ReferencedEntryPool entryPool) { - this.entryPool = entryPool; - } - public FieldEntry getFieldEntry(MemberReference def) { - ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); - return entryPool.getField(classEntry, def.getName(), def.getErasedSignature()); + ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); + return new FieldEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature())); } public FieldDefEntry getFieldDefEntry(FieldDefinition def) { - ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); return new FieldDefEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature()), Signature.createTypedSignature(def.getSignature()), new AccessFlags(def.getModifiers())); } public MethodEntry getMethodEntry(MemberReference def) { - ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); - return entryPool.getMethod(classEntry, def.getName(), def.getErasedSignature()); + ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); + return new MethodEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature())); } public MethodDefEntry getMethodDefEntry(MethodDefinition def) { - ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); return new MethodDefEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature()), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); } } diff --git a/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java b/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java deleted file mode 100644 index 631b3754..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java +++ /dev/null @@ -1,60 +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.translation.representation; - -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import java.util.HashMap; -import java.util.Map; - -public class ReferencedEntryPool { - private final Map classEntries = new HashMap<>(); - private final Map> methodEntries = new HashMap<>(); - private final Map> fieldEntries = new HashMap<>(); - - public ClassEntry getClass(String name) { - // TODO: FIXME - I'm a hack! - if ("[T".equals(name) || "[[T".equals(name) || "[[[T".equals(name)) { - name = name.replaceAll("T", "Ljava/lang/Object;"); - } - - final String computeName = name; - return this.classEntries.computeIfAbsent(name, s -> new ClassEntry(computeName)); - } - - public MethodEntry getMethod(ClassEntry ownerEntry, String name, String desc) { - return getMethod(ownerEntry, name, new MethodDescriptor(desc)); - } - - public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) { - String key = name + desc.toString(); - return getClassMethods(ownerEntry.getFullName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc)); - } - - public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) { - return getField(ownerEntry, name, new TypeDescriptor(desc)); - } - - public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) { - return getClassFields(ownerEntry.getFullName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc)); - } - - private Map getClassMethods(String name) { - return methodEntries.computeIfAbsent(name, s -> new HashMap<>()); - } - - private Map getClassFields(String name) { - return fieldEntries.computeIfAbsent(name, s -> new HashMap<>()); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/Signature.java b/src/main/java/cuchaz/enigma/translation/representation/Signature.java index dc241b7a..424088ab 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/Signature.java +++ b/src/main/java/cuchaz/enigma/translation/representation/Signature.java @@ -3,9 +3,9 @@ package cuchaz.enigma.translation.representation; import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; import cuchaz.enigma.translation.Translatable; import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.EntryMap; import cuchaz.enigma.translation.representation.entry.ClassEntry; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; @@ -78,7 +78,12 @@ public class Signature implements Translatable { @Override public int hashCode() { - return signature.hashCode() | (isType ? 1 : 0) << 16; + int hash = (isType ? 1 : 0) << 16; + if (signature != null) { + hash |= signature.hashCode(); + } + + return hash; } @Override diff --git a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java index f7ba849e..719d693a 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java +++ b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java @@ -13,6 +13,7 @@ package cuchaz.enigma.translation.representation; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; +import com.strobel.assembler.metadata.TypeReference; import cuchaz.enigma.translation.Translatable; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.EntryMapping; @@ -111,6 +112,10 @@ public class TypeDescriptor implements Translatable { return new TypeDescriptor("L" + name + ";"); } + public static TypeDescriptor parse(TypeReference type) { + return new TypeDescriptor(type.getErasedSignature()); + } + @Override public String toString() { return this.desc; diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java index 5904efe6..644658fd 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java @@ -17,6 +17,7 @@ import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.NameValidator; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.List; import java.util.Objects; @@ -125,6 +126,7 @@ public class ClassEntry extends ParentedEntry implements Comparable< return parent; } + @Nonnull public ClassEntry getOutermostClass() { if (parent == null) { return this; @@ -180,6 +182,15 @@ public class ClassEntry extends ParentedEntry implements Comparable< return name; } + @Override + public String getSourceRemapName() { + ClassEntry outerClass = getOuterClass(); + if (outerClass != null) { + return outerClass.getSourceRemapName() + "." + name; + } + return getSimpleName(); + } + @Override public int compareTo(ClassEntry entry) { return name.compareTo(entry.name); diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java index 1a2ca785..227400eb 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java @@ -22,6 +22,10 @@ import java.util.List; public interface Entry

> extends Translatable { String getName(); + default String getSourceRemapName() { + return getName(); + } + @Nullable P getParent(); diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java index df96b599..0c12f1c8 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java @@ -17,11 +17,6 @@ public class LocalVariableEntry extends ParentedEntry implements Co protected final int index; protected final boolean parameter; - @Deprecated - public LocalVariableEntry(MethodEntry parent, int index, String name) { - this(parent, index, name, true); - } - public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter) { super(parent, name); @@ -37,7 +32,7 @@ public class LocalVariableEntry extends ParentedEntry implements Co return MethodEntry.class; } - public boolean isParameter() { + public boolean isArgument() { return this.parameter; } -- cgit v1.2.3