From ed9b5cdfc648e86fd463bfa8d86b94c41671e14c Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 8 Feb 2015 21:29:25 -0500 Subject: switch all classes to new signature/type system --- src/cuchaz/enigma/CommandMain.java | 136 +++ src/cuchaz/enigma/Constants.java | 20 + src/cuchaz/enigma/Deobfuscator.java | 539 +++++++++ src/cuchaz/enigma/Main.java | 51 + src/cuchaz/enigma/TranslatingTypeLoader.java | 211 ++++ src/cuchaz/enigma/Util.java | 104 ++ src/cuchaz/enigma/analysis/Access.java | 43 + .../enigma/analysis/BehaviorReferenceTreeNode.java | 93 ++ .../analysis/ClassImplementationsTreeNode.java | 80 ++ .../enigma/analysis/ClassInheritanceTreeNode.java | 85 ++ src/cuchaz/enigma/analysis/EntryReference.java | 126 +++ src/cuchaz/enigma/analysis/EntryRenamer.java | 171 +++ .../enigma/analysis/FieldReferenceTreeNode.java | 81 ++ src/cuchaz/enigma/analysis/JarClassIterator.java | 137 +++ src/cuchaz/enigma/analysis/JarIndex.java | 734 ++++++++++++ .../analysis/MethodImplementationsTreeNode.java | 100 ++ .../enigma/analysis/MethodInheritanceTreeNode.java | 114 ++ src/cuchaz/enigma/analysis/ReferenceTreeNode.java | 18 + src/cuchaz/enigma/analysis/SourceIndex.java | 173 +++ .../analysis/SourceIndexBehaviorVisitor.java | 164 +++ .../enigma/analysis/SourceIndexClassVisitor.java | 115 ++ src/cuchaz/enigma/analysis/SourceIndexVisitor.java | 452 ++++++++ src/cuchaz/enigma/analysis/Token.java | 56 + src/cuchaz/enigma/analysis/TranslationIndex.java | 227 ++++ src/cuchaz/enigma/analysis/TreeDumpVisitor.java | 512 +++++++++ src/cuchaz/enigma/bytecode/CheckCastIterator.java | 127 +++ src/cuchaz/enigma/bytecode/ClassRenamer.java | 110 ++ src/cuchaz/enigma/bytecode/ClassTranslator.java | 144 +++ src/cuchaz/enigma/bytecode/ConstPoolEditor.java | 263 +++++ src/cuchaz/enigma/bytecode/InfoType.java | 317 ++++++ src/cuchaz/enigma/bytecode/InnerClassWriter.java | 102 ++ .../enigma/bytecode/MethodParameterWriter.java | 53 + .../enigma/bytecode/MethodParametersAttribute.java | 85 ++ .../bytecode/accessors/ClassInfoAccessor.java | 55 + .../bytecode/accessors/ConstInfoAccessor.java | 156 +++ .../accessors/InvokeDynamicInfoAccessor.java | 74 ++ .../bytecode/accessors/MemberRefInfoAccessor.java | 74 ++ .../accessors/MethodHandleInfoAccessor.java | 74 ++ .../bytecode/accessors/MethodTypeInfoAccessor.java | 55 + .../accessors/NameAndTypeInfoAccessor.java | 74 ++ .../bytecode/accessors/StringInfoAccessor.java | 55 + .../bytecode/accessors/Utf8InfoAccessor.java | 28 + src/cuchaz/enigma/convert/ClassIdentity.java | 411 +++++++ src/cuchaz/enigma/convert/ClassMatcher.java | 406 +++++++ src/cuchaz/enigma/convert/ClassMatching.java | 173 +++ src/cuchaz/enigma/convert/ClassNamer.java | 64 ++ src/cuchaz/enigma/gui/AboutDialog.java | 86 ++ src/cuchaz/enigma/gui/BoxHighlightPainter.java | 64 ++ src/cuchaz/enigma/gui/BrowserCaret.java | 45 + src/cuchaz/enigma/gui/ClassListCellRenderer.java | 36 + src/cuchaz/enigma/gui/ClassSelector.java | 164 +++ src/cuchaz/enigma/gui/ClassSelectorClassNode.java | 35 + .../enigma/gui/ClassSelectorPackageNode.java | 33 + src/cuchaz/enigma/gui/CrashDialog.java | 101 ++ .../enigma/gui/DeobfuscatedHighlightPainter.java | 21 + src/cuchaz/enigma/gui/Gui.java | 1165 ++++++++++++++++++++ src/cuchaz/enigma/gui/GuiController.java | 355 ++++++ src/cuchaz/enigma/gui/GuiTricks.java | 36 + .../enigma/gui/ObfuscatedHighlightPainter.java | 21 + src/cuchaz/enigma/gui/OtherHighlightPainter.java | 21 + src/cuchaz/enigma/gui/ProgressDialog.java | 105 ++ src/cuchaz/enigma/gui/ReadableToken.java | 36 + src/cuchaz/enigma/gui/RenameListener.java | 17 + .../enigma/gui/SelectionHighlightPainter.java | 34 + src/cuchaz/enigma/gui/TokenListCellRenderer.java | 38 + src/cuchaz/enigma/mapping/ArgumentEntry.java | 116 ++ src/cuchaz/enigma/mapping/ArgumentMapping.java | 44 + src/cuchaz/enigma/mapping/BehaviorEntry.java | 15 + .../enigma/mapping/BehaviorEntryFactory.java | 57 + src/cuchaz/enigma/mapping/ClassEntry.java | 123 +++ src/cuchaz/enigma/mapping/ClassMapping.java | 405 +++++++ src/cuchaz/enigma/mapping/ClassNameReplacer.java | 5 + src/cuchaz/enigma/mapping/ConstructorEntry.java | 116 ++ src/cuchaz/enigma/mapping/Entry.java | 18 + src/cuchaz/enigma/mapping/EntryPair.java | 22 + src/cuchaz/enigma/mapping/FieldEntry.java | 88 ++ src/cuchaz/enigma/mapping/FieldMapping.java | 43 + .../enigma/mapping/IllegalNameException.java | 44 + src/cuchaz/enigma/mapping/JavassistUtil.java | 83 ++ .../enigma/mapping/MappingParseException.java | 29 + src/cuchaz/enigma/mapping/Mappings.java | 188 ++++ src/cuchaz/enigma/mapping/MappingsReader.java | 175 +++ src/cuchaz/enigma/mapping/MappingsRenamer.java | 237 ++++ src/cuchaz/enigma/mapping/MappingsWriter.java | 88 ++ src/cuchaz/enigma/mapping/MethodEntry.java | 104 ++ src/cuchaz/enigma/mapping/MethodMapping.java | 161 +++ src/cuchaz/enigma/mapping/NameValidator.java | 80 ++ src/cuchaz/enigma/mapping/Signature.java | 109 ++ src/cuchaz/enigma/mapping/SignatureUpdater.java | 94 ++ .../enigma/mapping/TranslationDirection.java | 29 + src/cuchaz/enigma/mapping/Translator.java | 239 ++++ src/cuchaz/enigma/mapping/Type.java | 218 ++++ 92 files changed, 12785 insertions(+) create mode 100644 src/cuchaz/enigma/CommandMain.java create mode 100644 src/cuchaz/enigma/Constants.java create mode 100644 src/cuchaz/enigma/Deobfuscator.java create mode 100644 src/cuchaz/enigma/Main.java create mode 100644 src/cuchaz/enigma/TranslatingTypeLoader.java create mode 100644 src/cuchaz/enigma/Util.java create mode 100644 src/cuchaz/enigma/analysis/Access.java create mode 100644 src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java create mode 100644 src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java create mode 100644 src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java create mode 100644 src/cuchaz/enigma/analysis/EntryReference.java create mode 100644 src/cuchaz/enigma/analysis/EntryRenamer.java create mode 100644 src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java create mode 100644 src/cuchaz/enigma/analysis/JarClassIterator.java create mode 100644 src/cuchaz/enigma/analysis/JarIndex.java create mode 100644 src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java create mode 100644 src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java create mode 100644 src/cuchaz/enigma/analysis/ReferenceTreeNode.java create mode 100644 src/cuchaz/enigma/analysis/SourceIndex.java create mode 100644 src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java create mode 100644 src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java create mode 100644 src/cuchaz/enigma/analysis/SourceIndexVisitor.java create mode 100644 src/cuchaz/enigma/analysis/Token.java create mode 100644 src/cuchaz/enigma/analysis/TranslationIndex.java create mode 100644 src/cuchaz/enigma/analysis/TreeDumpVisitor.java create mode 100644 src/cuchaz/enigma/bytecode/CheckCastIterator.java create mode 100644 src/cuchaz/enigma/bytecode/ClassRenamer.java create mode 100644 src/cuchaz/enigma/bytecode/ClassTranslator.java create mode 100644 src/cuchaz/enigma/bytecode/ConstPoolEditor.java create mode 100644 src/cuchaz/enigma/bytecode/InfoType.java create mode 100644 src/cuchaz/enigma/bytecode/InnerClassWriter.java create mode 100644 src/cuchaz/enigma/bytecode/MethodParameterWriter.java create mode 100644 src/cuchaz/enigma/bytecode/MethodParametersAttribute.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java create mode 100644 src/cuchaz/enigma/convert/ClassIdentity.java create mode 100644 src/cuchaz/enigma/convert/ClassMatcher.java create mode 100644 src/cuchaz/enigma/convert/ClassMatching.java create mode 100644 src/cuchaz/enigma/convert/ClassNamer.java create mode 100644 src/cuchaz/enigma/gui/AboutDialog.java create mode 100644 src/cuchaz/enigma/gui/BoxHighlightPainter.java create mode 100644 src/cuchaz/enigma/gui/BrowserCaret.java create mode 100644 src/cuchaz/enigma/gui/ClassListCellRenderer.java create mode 100644 src/cuchaz/enigma/gui/ClassSelector.java create mode 100644 src/cuchaz/enigma/gui/ClassSelectorClassNode.java create mode 100644 src/cuchaz/enigma/gui/ClassSelectorPackageNode.java create mode 100644 src/cuchaz/enigma/gui/CrashDialog.java create mode 100644 src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java create mode 100644 src/cuchaz/enigma/gui/Gui.java create mode 100644 src/cuchaz/enigma/gui/GuiController.java create mode 100644 src/cuchaz/enigma/gui/GuiTricks.java create mode 100644 src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java create mode 100644 src/cuchaz/enigma/gui/OtherHighlightPainter.java create mode 100644 src/cuchaz/enigma/gui/ProgressDialog.java create mode 100644 src/cuchaz/enigma/gui/ReadableToken.java create mode 100644 src/cuchaz/enigma/gui/RenameListener.java create mode 100644 src/cuchaz/enigma/gui/SelectionHighlightPainter.java create mode 100644 src/cuchaz/enigma/gui/TokenListCellRenderer.java create mode 100644 src/cuchaz/enigma/mapping/ArgumentEntry.java create mode 100644 src/cuchaz/enigma/mapping/ArgumentMapping.java create mode 100644 src/cuchaz/enigma/mapping/BehaviorEntry.java create mode 100644 src/cuchaz/enigma/mapping/BehaviorEntryFactory.java create mode 100644 src/cuchaz/enigma/mapping/ClassEntry.java create mode 100644 src/cuchaz/enigma/mapping/ClassMapping.java create mode 100644 src/cuchaz/enigma/mapping/ClassNameReplacer.java create mode 100644 src/cuchaz/enigma/mapping/ConstructorEntry.java create mode 100644 src/cuchaz/enigma/mapping/Entry.java create mode 100644 src/cuchaz/enigma/mapping/EntryPair.java create mode 100644 src/cuchaz/enigma/mapping/FieldEntry.java create mode 100644 src/cuchaz/enigma/mapping/FieldMapping.java create mode 100644 src/cuchaz/enigma/mapping/IllegalNameException.java create mode 100644 src/cuchaz/enigma/mapping/JavassistUtil.java create mode 100644 src/cuchaz/enigma/mapping/MappingParseException.java create mode 100644 src/cuchaz/enigma/mapping/Mappings.java create mode 100644 src/cuchaz/enigma/mapping/MappingsReader.java create mode 100644 src/cuchaz/enigma/mapping/MappingsRenamer.java create mode 100644 src/cuchaz/enigma/mapping/MappingsWriter.java create mode 100644 src/cuchaz/enigma/mapping/MethodEntry.java create mode 100644 src/cuchaz/enigma/mapping/MethodMapping.java create mode 100644 src/cuchaz/enigma/mapping/NameValidator.java create mode 100644 src/cuchaz/enigma/mapping/Signature.java create mode 100644 src/cuchaz/enigma/mapping/SignatureUpdater.java create mode 100644 src/cuchaz/enigma/mapping/TranslationDirection.java create mode 100644 src/cuchaz/enigma/mapping/Translator.java create mode 100644 src/cuchaz/enigma/mapping/Type.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/CommandMain.java b/src/cuchaz/enigma/CommandMain.java new file mode 100644 index 0000000..1ec2ad2 --- /dev/null +++ b/src/cuchaz/enigma/CommandMain.java @@ -0,0 +1,136 @@ +package cuchaz.enigma; + +import java.io.File; +import java.io.FileReader; +import java.util.jar.JarFile; + +import cuchaz.enigma.Deobfuscator.ProgressListener; +import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsReader; + +public class CommandMain { + + public static class ConsoleProgressListener implements ProgressListener { + + private static final int ReportTime = 5000; // 5s + + private int m_totalWork; + private long m_startTime; + private long m_lastReportTime; + + @Override + public void init(int totalWork, String title) { + m_totalWork = totalWork; + m_startTime = System.currentTimeMillis(); + m_lastReportTime = m_startTime; + System.out.println(title); + } + + @Override + public void onProgress(int numDone, String message) { + + long now = System.currentTimeMillis(); + boolean isLastUpdate = numDone == m_totalWork; + boolean shouldReport = isLastUpdate || now - m_lastReportTime > ReportTime; + + if (shouldReport) { + int percent = numDone*100/m_totalWork; + System.out.println(String.format("\tProgress: %3d%%", percent)); + m_lastReportTime = now; + } + if (isLastUpdate) { + double elapsedSeconds = (now - m_startTime)/1000; + System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); + } + } + } + + public static void main(String[] args) + throws Exception { + + try { + + // process the command + String command = getArg(args, 0, "command"); + if (command.equalsIgnoreCase("deobfuscate")) { + deobfuscate(args); + } else if(command.equalsIgnoreCase("decompile")) { + decompile(args); + } else { + throw new IllegalArgumentException("Command not recognized: " + command); + } + } catch (IllegalArgumentException ex) { + System.out.println(ex.getMessage()); + printHelp(); + } + } + + private static void printHelp() { + System.out.println(String.format("%s - %s", Constants.Name, Constants.Version)); + System.out.println("Usage:"); + System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain "); + System.out.println("\twhere is one of:"); + System.out.println("\t\tdeobfuscate "); + System.out.println("\t\tdecompile "); + } + + private static void decompile(String[] args) + throws Exception { + File fileMappings = getReadableFile(getArg(args, 1, "mappings file")); + File fileJarIn = getReadableFile(getArg(args, 2, "in jar")); + File fileJarOut = getWritableFolder(getArg(args, 3, "out folder")); + Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); + deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener()); + } + + private static void deobfuscate(String[] args) + throws Exception { + File fileMappings = getReadableFile(getArg(args, 1, "mappings file")); + File fileJarIn = getReadableFile(getArg(args, 2, "in jar")); + File fileJarOut = getWritableFile(getArg(args, 3, "out jar")); + Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); + deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener()); + } + + private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar) + throws Exception { + System.out.println("Reading mappings..."); + Mappings mappings = new MappingsReader().read(new FileReader(fileMappings)); + System.out.println("Reading jar..."); + Deobfuscator deobfuscator = new Deobfuscator(jar); + deobfuscator.setMappings(mappings); + return deobfuscator; + } + + private static String getArg(String[] args, int i, String name) { + if (i >= args.length) { + throw new IllegalArgumentException(name + " is required"); + } + return args[i]; + } + + private static File getWritableFile(String path) { + File file = new File(path).getAbsoluteFile(); + File dir = file.getParentFile(); + if (dir == null || !dir.exists()) { + throw new IllegalArgumentException("Cannot write to folder: " + file); + } + return file; + } + + private static File getWritableFolder(String path) { + File dir = new File(path).getAbsoluteFile(); + if (!dir.exists()) { + throw new IllegalArgumentException("Cannot write to folder: " + dir); + } + return dir; + } + + private static File getReadableFile(String path) { + File file = new File(path).getAbsoluteFile(); + if (!file.exists()) { + throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); + } + return file; + } +} diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java new file mode 100644 index 0000000..a1ba2e9 --- /dev/null +++ b/src/cuchaz/enigma/Constants.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma; + +public class Constants { + public static final String Name = "Enigma"; + public static final String Version = "0.6 beta"; + public static final String Url = "http://www.cuchazinteractive.com/enigma"; + public static final int MiB = 1024 * 1024; // 1 mebibyte + public static final int KiB = 1024; // 1 kebibyte + public static final String NonePackage = "none"; +} diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java new file mode 100644 index 0000000..5f61686 --- /dev/null +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -0,0 +1,539 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin.\ + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +import javassist.CtClass; +import javassist.bytecode.Descriptor; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; +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 cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.JarClassIterator; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.analysis.SourceIndex; +import cuchaz.enigma.analysis.SourceIndexVisitor; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.BehaviorEntryFactory; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassMapping; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.FieldMapping; +import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsRenamer; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.MethodMapping; +import cuchaz.enigma.mapping.TranslationDirection; +import cuchaz.enigma.mapping.Translator; + +public class Deobfuscator { + + public interface ProgressListener { + void init(int totalWork, String title); + void onProgress(int numDone, String message); + } + + private JarFile m_jar; + private DecompilerSettings m_settings; + private JarIndex m_jarIndex; + private Mappings m_mappings; + private MappingsRenamer m_renamer; + private Map m_translatorCache; + + public Deobfuscator(JarFile jar) throws IOException { + m_jar = jar; + + // build the jar index + m_jarIndex = new JarIndex(); + m_jarIndex.indexJar(m_jar, true); + + // config the decompiler + m_settings = DecompilerSettings.javaDefaults(); + m_settings.setMergeVariables(true); + m_settings.setForceExplicitImports(true); + m_settings.setForceExplicitTypeArguments(true); + // DEBUG + //m_settings.setShowSyntheticMembers(true); + + // init defaults + m_translatorCache = Maps.newTreeMap(); + + // init mappings + setMappings(new Mappings()); + } + + public String getJarName() { + return m_jar.getName(); + } + + public JarIndex getJarIndex() { + return m_jarIndex; + } + + public Mappings getMappings() { + return m_mappings; + } + + public void setMappings(Mappings val) { + if (val == null) { + val = new Mappings(); + } + + // pass 1: look for any classes that got moved to inner classes + Map renames = Maps.newHashMap(); + for (ClassMapping classMapping : val.classes()) { + // make sure we strip the packages off of obfuscated inner classes + String innerClassName = new ClassEntry(classMapping.getObfName()).getSimpleName(); + String outerClassName = m_jarIndex.getOuterClass(innerClassName); + if (outerClassName != null) { + // build the composite class name + String newName = outerClassName + "$" + innerClassName; + + // add a rename + renames.put(classMapping.getObfName(), newName); + + System.out.println(String.format("Converted class mapping %s to %s", classMapping.getObfName(), newName)); + } + } + for (Map.Entry entry : renames.entrySet()) { + val.renameObfClass(entry.getKey(), entry.getValue()); + } + + // pass 2: look for fields/methods that are actually declared in superclasses + MappingsRenamer renamer = new MappingsRenamer(m_jarIndex, val); + for (ClassMapping classMapping : Lists.newArrayList(val.classes())) { + ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfName()); + + // fields + for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { + FieldEntry fieldEntry = new FieldEntry(obfClassEntry, fieldMapping.getObfName()); + ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(fieldEntry); + if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(fieldEntry.getClassEntry())) { + boolean wasMoved = renamer.moveFieldToObfClass(classMapping, fieldMapping, resolvedObfClassEntry); + if (wasMoved) { + System.out.println(String.format("Moved field %s to class %s", fieldEntry, resolvedObfClassEntry)); + } else { + System.err.println(String.format("WARNING: Would move field %s to class %s but the field was already there. Dropping instead.", fieldEntry, resolvedObfClassEntry)); + } + } + } + + // methods + for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { + // skip constructors + if (methodMapping.isConstructor()) { + continue; + } + + MethodEntry methodEntry = new MethodEntry( + obfClassEntry, + methodMapping.getObfName(), + methodMapping.getObfSignature() + ); + ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(methodEntry); + if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(methodEntry.getClassEntry())) { + boolean wasMoved = renamer.moveMethodToObfClass(classMapping, methodMapping, resolvedObfClassEntry); + if (wasMoved) { + System.out.println(String.format("Moved method %s to class %s", methodEntry, resolvedObfClassEntry)); + } else { + System.err.println(String.format("WARNING: Would move method %s to class %s but the method was already there. Dropping instead.", methodEntry, resolvedObfClassEntry)); + } + } + } + + // TODO: recurse to inner classes? + } + + // drop mappings that don't match the jar + List unknownClasses = Lists.newArrayList(); + for (ClassMapping classMapping : val.classes()) { + checkClassMapping(unknownClasses, classMapping); + } + if (!unknownClasses.isEmpty()) { + throw new Error("Unable to find classes in jar: " + unknownClasses); + } + + m_mappings = val; + m_renamer = renamer; + m_translatorCache.clear(); + } + + private void checkClassMapping(List unknownClasses, ClassMapping classMapping) { + // check the class + ClassEntry classEntry = new ClassEntry(classMapping.getObfName()); + String outerClassName = m_jarIndex.getOuterClass(classEntry.getSimpleName()); + if (outerClassName != null) { + classEntry = new ClassEntry(outerClassName + "$" + classMapping.getObfName()); + } + if (!m_jarIndex.getObfClassEntries().contains(classEntry)) { + unknownClasses.add(classEntry); + } + + // check the fields + for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { + FieldEntry fieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName()); + if (!m_jarIndex.containsObfField(fieldEntry)) { + System.err.println("WARNING: unable to find field " + fieldEntry + ". dropping mapping."); + classMapping.removeFieldMapping(fieldMapping); + } + } + + // check methods + for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { + BehaviorEntry obfBehaviorEntry = BehaviorEntryFactory.createObf(classEntry, methodMapping); + if (!m_jarIndex.containsObfBehavior(obfBehaviorEntry)) { + System.err.println("WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping."); + classMapping.removeMethodMapping(methodMapping); + } + } + + // check inner classes + for (ClassMapping innerClassMapping : classMapping.innerClasses()) { + checkClassMapping(unknownClasses, innerClassMapping); + } + } + + public Translator getTranslator(TranslationDirection direction) { + Translator translator = m_translatorCache.get(direction); + if (translator == null) { + translator = m_mappings.getTranslator(direction, m_jarIndex.getTranslationIndex()); + m_translatorCache.put(direction, translator); + } + return translator; + } + + public void getSeparatedClasses(List obfClasses, List deobfClasses) { + for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) { + // skip inner classes + if (obfClassEntry.isInnerClass()) { + continue; + } + + // separate the classes + ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry); + if (!deobfClassEntry.equals(obfClassEntry)) { + // if the class has a mapping, clearly it's deobfuscated + deobfClasses.add(deobfClassEntry); + } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) { + // also call it deobufscated if it's not in the none package + deobfClasses.add(obfClassEntry); + } else { + // otherwise, assume it's still obfuscated + obfClasses.add(obfClassEntry); + } + } + } + + public CompilationUnit getSourceTree(String obfClassName) { + // is this class deobfuscated? + // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out + // the decompiler only sees the deobfuscated class, so we need to load it by the deobfuscated name + String lookupClassName = obfClassName; + ClassMapping classMapping = m_mappings.getClassByObf(obfClassName); + if (classMapping != null && classMapping.getDeobfName() != null) { + lookupClassName = classMapping.getDeobfName(); + } + + // is this class even in the jar? + if (!m_jarIndex.containsObfClass(new ClassEntry(obfClassName))) { + return null; + } + + // set the type loader + m_settings.setTypeLoader(new TranslatingTypeLoader( + m_jar, + m_jarIndex, + getTranslator(TranslationDirection.Obfuscating), + getTranslator(TranslationDirection.Deobfuscating) + )); + + // decompile it! + TypeDefinition resolvedType = new MetadataSystem(m_settings.getTypeLoader()).lookupType(lookupClassName).resolve(); + DecompilerContext context = new DecompilerContext(); + context.setCurrentType(resolvedType); + context.setSettings(m_settings); + AstBuilder builder = new AstBuilder(context); + builder.addType(resolvedType); + builder.runTransformations(null); + return builder.getCompilationUnit(); + } + + public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) { + // build the source index + SourceIndex index = new SourceIndex(source); + sourceTree.acceptVisitor(new SourceIndexVisitor(), index); + + // DEBUG + // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); + + // resolve all the classes in the source references + for (Token token : index.referenceTokens()) { + EntryReference deobfReference = index.getDeobfReference(token); + + // get the obfuscated entry + Entry obfEntry = obfuscateEntry(deobfReference.entry); + + // try to resolve the class + ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(obfEntry); + if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) { + // change the class of the entry + obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry); + + // save the new deobfuscated reference + deobfReference.entry = deobfuscateEntry(obfEntry); + index.replaceDeobfReference(token, deobfReference); + } + + // DEBUG + // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); + } + + return index; + } + + 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), m_settings), null); + return buf.toString(); + } + + public void writeSources(File dirOut, ProgressListener progress) throws IOException { + // get the classes to decompile + Set classEntries = Sets.newHashSet(); + for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) { + // skip inner classes + if (obfClassEntry.isInnerClass()) { + continue; + } + + classEntries.add(obfClassEntry); + } + + if (progress != null) { + progress.init(classEntries.size(), "Decompiling classes..."); + } + + // DEOBFUSCATE ALL THE THINGS!! @_@ + int i = 0; + for (ClassEntry obfClassEntry : classEntries) { + ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); + if (progress != null) { + progress.onProgress(i++, deobfClassEntry.toString()); + } + + try { + // get the source + String source = getSource(getSourceTree(obfClassEntry.getName())); + + // write the file + File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java"); + file.getParentFile().mkdirs(); + try (FileWriter out = new FileWriter(file)) { + out.write(source); + } + } catch (Throwable t) { + throw new Error("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t); + } + } + if (progress != null) { + progress.onProgress(i, "Done!"); + } + } + + public void writeJar(File out, ProgressListener progress) { + try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { + if (progress != null) { + progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Translating classes..."); + } + + // prep the loader + TranslatingTypeLoader loader = new TranslatingTypeLoader( + m_jar, + m_jarIndex, + getTranslator(TranslationDirection.Obfuscating), + getTranslator(TranslationDirection.Deobfuscating) + ); + + int i = 0; + for (CtClass c : JarClassIterator.classes(m_jar)) { + if (progress != null) { + progress.onProgress(i++, c.getName()); + } + + try { + c = loader.transformClass(c); + outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class")); + outJar.write(c.toBytecode()); + outJar.closeEntry(); + } catch (Throwable t) { + throw new Error("Unable to deobfuscate class " + c.getName(), t); + } + } + if (progress != null) { + progress.onProgress(i, "Done!"); + } + + outJar.close(); + } catch (IOException ex) { + throw new Error("Unable to write to Jar file!"); + } + } + + public T obfuscateEntry(T deobfEntry) { + if (deobfEntry == null) { + return null; + } + return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry); + } + + public T deobfuscateEntry(T obfEntry) { + if (obfEntry == null) { + return null; + } + return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry); + } + + public EntryReference obfuscateReference(EntryReference deobfReference) { + if (deobfReference == null) { + return null; + } + return new EntryReference( + obfuscateEntry(deobfReference.entry), + obfuscateEntry(deobfReference.context), + deobfReference + ); + } + + public EntryReference deobfuscateReference(EntryReference obfReference) { + if (obfReference == null) { + return null; + } + return new EntryReference( + deobfuscateEntry(obfReference.entry), + deobfuscateEntry(obfReference.context), + obfReference + ); + } + + public boolean isObfuscatedIdentifier(Entry obfEntry) { + return m_jarIndex.containsObfEntry(obfEntry); + } + + public boolean isRenameable(EntryReference obfReference) { + return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry()); + } + + // NOTE: these methods are a bit messy... oh well + + public boolean hasDeobfuscatedName(Entry obfEntry) { + Translator translator = getTranslator(TranslationDirection.Deobfuscating); + if (obfEntry instanceof ClassEntry) { + return translator.translate((ClassEntry)obfEntry) != null; + } else if (obfEntry instanceof FieldEntry) { + return translator.translate((FieldEntry)obfEntry) != null; + } else if (obfEntry instanceof MethodEntry) { + return translator.translate((MethodEntry)obfEntry) != null; + } else if (obfEntry instanceof ConstructorEntry) { + // constructors have no names + return false; + } else if (obfEntry instanceof ArgumentEntry) { + return translator.translate((ArgumentEntry)obfEntry) != null; + } else { + throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); + } + } + + public void rename(Entry obfEntry, String newName) { + if (obfEntry instanceof ClassEntry) { + m_renamer.setClassName((ClassEntry)obfEntry, Descriptor.toJvmName(newName)); + } else if (obfEntry instanceof FieldEntry) { + m_renamer.setFieldName((FieldEntry)obfEntry, newName); + } else if (obfEntry instanceof MethodEntry) { + m_renamer.setMethodTreeName((MethodEntry)obfEntry, newName); + } else if (obfEntry instanceof ConstructorEntry) { + throw new IllegalArgumentException("Cannot rename constructors"); + } else if (obfEntry instanceof ArgumentEntry) { + m_renamer.setArgumentName((ArgumentEntry)obfEntry, newName); + } else { + throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); + } + + // clear caches + m_translatorCache.clear(); + } + + public void removeMapping(Entry obfEntry) { + if (obfEntry instanceof ClassEntry) { + m_renamer.removeClassMapping((ClassEntry)obfEntry); + } else if (obfEntry instanceof FieldEntry) { + m_renamer.removeFieldMapping((FieldEntry)obfEntry); + } else if (obfEntry instanceof MethodEntry) { + m_renamer.removeMethodTreeMapping((MethodEntry)obfEntry); + } else if (obfEntry instanceof ConstructorEntry) { + throw new IllegalArgumentException("Cannot rename constructors"); + } else if (obfEntry instanceof ArgumentEntry) { + m_renamer.removeArgumentMapping((ArgumentEntry)obfEntry); + } else { + throw new Error("Unknown entry type: " + obfEntry); + } + + // clear caches + m_translatorCache.clear(); + } + + public void markAsDeobfuscated(Entry obfEntry) { + if (obfEntry instanceof ClassEntry) { + m_renamer.markClassAsDeobfuscated((ClassEntry)obfEntry); + } else if (obfEntry instanceof FieldEntry) { + m_renamer.markFieldAsDeobfuscated((FieldEntry)obfEntry); + } else if (obfEntry instanceof MethodEntry) { + m_renamer.markMethodTreeAsDeobfuscated((MethodEntry)obfEntry); + } else if (obfEntry instanceof ConstructorEntry) { + throw new IllegalArgumentException("Cannot rename constructors"); + } else if (obfEntry instanceof ArgumentEntry) { + m_renamer.markArgumentAsDeobfuscated((ArgumentEntry)obfEntry); + } else { + throw new Error("Unknown entry type: " + obfEntry); + } + + // clear caches + m_translatorCache.clear(); + } +} diff --git a/src/cuchaz/enigma/Main.java b/src/cuchaz/enigma/Main.java new file mode 100644 index 0000000..acae94b --- /dev/null +++ b/src/cuchaz/enigma/Main.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma; + +import java.io.File; +import java.util.jar.JarFile; + +import cuchaz.enigma.gui.Gui; + +public class Main { + + public static void main(String[] args) throws Exception { + Gui gui = new Gui(); + + // parse command-line args + if (args.length >= 1) { + gui.getController().openJar(new JarFile(getFile(args[0]))); + } + if (args.length >= 2) { + gui.getController().openMappings(getFile(args[1])); + } + + // DEBUG + //gui.getController().openDeclaration(new ClassEntry("none/bxq")); + } + + private static File getFile(String path) { + // expand ~ to the home dir + if (path.startsWith("~")) { + // get the home dir + File dirHome = new File(System.getProperty("user.home")); + + // is the path just ~/ or is it ~user/ ? + if (path.startsWith("~/")) { + return new File(dirHome, path.substring(2)); + } else { + return new File(dirHome.getParentFile(), path.substring(1)); + } + } + + return new File(path); + } +} diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java new file mode 100644 index 0000000..cfa03a1 --- /dev/null +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -0,0 +1,211 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import javassist.ByteArrayClassPath; +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.Descriptor; + +import com.google.common.collect.Maps; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ClasspathTypeLoader; +import com.strobel.assembler.metadata.ITypeLoader; + +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.bytecode.ClassRenamer; +import cuchaz.enigma.bytecode.ClassTranslator; +import cuchaz.enigma.bytecode.InnerClassWriter; +import cuchaz.enigma.bytecode.MethodParameterWriter; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Translator; + +public class TranslatingTypeLoader implements ITypeLoader { + + private JarFile m_jar; + private JarIndex m_jarIndex; + private Translator m_obfuscatingTranslator; + private Translator m_deobfuscatingTranslator; + private Map m_cache; + private ClasspathTypeLoader m_defaultTypeLoader; + + public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex) { + this(jar, jarIndex, new Translator(), new Translator()); + } + + public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { + m_jar = jar; + m_jarIndex = jarIndex; + m_obfuscatingTranslator = obfuscatingTranslator; + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_cache = Maps.newHashMap(); + m_defaultTypeLoader = new ClasspathTypeLoader(); + } + + public void clearCache() { + m_cache.clear(); + } + + @Override + public boolean tryLoadType(String deobfClassName, Buffer out) { + // check the cache + byte[] data; + if (m_cache.containsKey(deobfClassName)) { + data = m_cache.get(deobfClassName); + } else { + data = loadType(deobfClassName); + m_cache.put(deobfClassName, data); + } + + if (data == null) { + // chain to default type loader + return m_defaultTypeLoader.tryLoadType(deobfClassName, out); + } + + // send the class to the decompiler + out.reset(data.length); + System.arraycopy(data, 0, out.array(), out.position(), data.length); + out.position(0); + return true; + } + + public CtClass loadClass(String deobfClassName) { + byte[] data = loadType(deobfClassName); + if (data == null) { + return null; + } + + // return a javassist handle for the class + String javaClassFileName = Descriptor.toJavaName(deobfClassName); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data)); + try { + return classPool.get(javaClassFileName); + } catch (NotFoundException ex) { + throw new Error(ex); + } + } + + private byte[] loadType(String deobfClassName) { + ClassEntry deobfClassEntry = new ClassEntry(deobfClassName); + ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry); + + // is this an inner class referenced directly? + String obfOuterClassName = m_jarIndex.getOuterClass(obfClassEntry.getSimpleName()); + if (obfOuterClassName != null) { + // this class doesn't really exist. Reference it by outer$inner instead + System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassName)); + return null; + } + + /* DEBUG + if( !Arrays.asList( "java", "org", "io" ).contains( deobfClassName.split( "/" )[0] ) ) { + System.out.println( String.format( "Looking for %s (%s)", deobfClassEntry.getName(), obfClassEntry.getName() ) ); + } + */ + + // get the jar entry + String classFileName; + if (obfClassEntry.isInnerClass()) { + // use just the inner class name for inner classes + classFileName = obfClassEntry.getInnerClassName(); + } else if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) { + // use the outer class simple name for classes in the none package + classFileName = obfClassEntry.getSimpleName(); + } else { + // otherwise, just use the class name (ie for classes in packages) + classFileName = obfClassEntry.getName(); + } + + JarEntry entry = m_jar.getJarEntry(classFileName + ".class"); + if (entry == null) { + return null; + } + + try { + // read the class file into a buffer + ByteArrayOutputStream data = new ByteArrayOutputStream(); + byte[] buf = new byte[1024 * 1024]; // 1 KiB + InputStream in = m_jar.getInputStream(entry); + while (true) { + int bytesRead = in.read(buf); + if (bytesRead <= 0) { + break; + } + data.write(buf, 0, bytesRead); + } + data.close(); + in.close(); + buf = data.toByteArray(); + + // load the javassist handle to the raw class + String javaClassFileName = Descriptor.toJavaName(classFileName); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, buf)); + CtClass c = classPool.get(javaClassFileName); + + c = transformClass(c); + + // sanity checking + assertClassName(c, deobfClassEntry); + + // DEBUG + //Util.writeClass( c ); + + // we have a transformed class! + return c.toBytecode(); + } catch (IOException | NotFoundException | CannotCompileException ex) { + throw new Error(ex); + } + } + + public CtClass transformClass(CtClass c) throws IOException, NotFoundException, CannotCompileException { + // we moved a lot of classes out of the default package into the none package + // make sure all the class references are consistent + ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); + + // reconstruct inner classes + new InnerClassWriter(m_jarIndex).write(c); + + // re-get the javassist handle since we changed class names + ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName()); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode())); + c = classPool.get(javaClassReconstructedName); + + // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong) + assertClassName(c, obfClassEntry); + + // do all kinds of deobfuscating transformations on the class + new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c); + new ClassTranslator(m_deobfuscatingTranslator).translate(c); + + return c; + } + + private void assertClassName(CtClass c, ClassEntry obfClassEntry) { + String name1 = Descriptor.toJvmName(c.getName()); + assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1); + + String name2 = Descriptor.toJvmName(c.getClassFile().getName()); + assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2); + } +} diff --git a/src/cuchaz/enigma/Util.java b/src/cuchaz/enigma/Util.java new file mode 100644 index 0000000..7f04bda --- /dev/null +++ b/src/cuchaz/enigma/Util.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma; + +import java.awt.Desktop; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.jar.JarFile; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.bytecode.Descriptor; + +import com.google.common.io.CharStreams; + +public class Util { + + public static int combineHashesOrdered(Object... objs) { + return combineHashesOrdered(Arrays.asList(objs)); + } + + public static int combineHashesOrdered(Iterable objs) { + final int prime = 67; + int result = 1; + for (Object obj : objs) { + result *= prime; + if (obj != null) { + result += obj.hashCode(); + } + } + return result; + } + + public static void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException ex) { + // just ignore any further exceptions + } + } + } + + public static void closeQuietly(JarFile jarFile) { + // silly library should implement Closeable... + if (jarFile != null) { + try { + jarFile.close(); + } catch (IOException ex) { + // just ignore any further exceptions + } + } + } + + public static String readStreamToString(InputStream in) throws IOException { + return CharStreams.toString(new InputStreamReader(in, "UTF-8")); + } + + public static String readResourceToString(String path) throws IOException { + InputStream in = Util.class.getResourceAsStream(path); + if (in == null) { + throw new IllegalArgumentException("Resource not found! " + path); + } + return readStreamToString(in); + } + + public static void openUrl(String url) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.browse(new URI(url)); + } catch (IOException ex) { + throw new Error(ex); + } catch (URISyntaxException ex) { + throw new IllegalArgumentException(ex); + } + } + } + + public static void writeClass(CtClass c) { + String name = Descriptor.toJavaName(c.getName()); + File file = new File(name + ".class"); + try (FileOutputStream out = new FileOutputStream(file)) { + out.write(c.toBytecode()); + } catch (IOException | CannotCompileException ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/analysis/Access.java b/src/cuchaz/enigma/analysis/Access.java new file mode 100644 index 0000000..8d3409a --- /dev/null +++ b/src/cuchaz/enigma/analysis/Access.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.lang.reflect.Modifier; + +import javassist.CtBehavior; +import javassist.CtField; + +public enum Access { + + Public, + Protected, + Private; + + public static Access get(CtBehavior behavior) { + return get(behavior.getModifiers()); + } + + public static Access get(CtField field) { + return get(field.getModifiers()); + } + + public static Access get(int modifiers) { + if (Modifier.isPublic(modifiers)) { + return Public; + } else if (Modifier.isProtected(modifiers)) { + return Protected; + } else if (Modifier.isPrivate(modifiers)) { + return Private; + } + // assume public by default + return Public; + } +} diff --git a/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java new file mode 100644 index 0000000..9adac5e --- /dev/null +++ b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.util.Set; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; + +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.Translator; + +public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { + + private static final long serialVersionUID = -3658163700783307520L; + + private Translator m_deobfuscatingTranslator; + private BehaviorEntry m_entry; + private EntryReference m_reference; + private Access m_access; + + public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) { + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_entry = entry; + m_reference = null; + } + + public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_entry = reference.entry; + m_reference = reference; + m_access = access; + } + + @Override + public BehaviorEntry getEntry() { + return m_entry; + } + + @Override + public EntryReference getReference() { + return m_reference; + } + + @Override + public String toString() { + if (m_reference != null) { + return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access); + } + return m_deobfuscatingTranslator.translateEntry(m_entry).toString(); + } + + public void load(JarIndex index, boolean recurse) { + // get all the child nodes + for (EntryReference reference : index.getBehaviorReferences(m_entry)) { + add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry))); + } + + if (recurse && children != null) { + for (Object child : children) { + if (child instanceof BehaviorReferenceTreeNode) { + BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode)child; + + // don't recurse into ancestor + Set ancestors = Sets.newHashSet(); + TreeNode n = (TreeNode)node; + while (n.getParent() != null) { + n = n.getParent(); + if (n instanceof BehaviorReferenceTreeNode) { + ancestors.add( ((BehaviorReferenceTreeNode)n).getEntry()); + } + } + if (ancestors.contains(node.getEntry())) { + continue; + } + + node.load(index, true); + } + } + } + } +} diff --git a/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java new file mode 100644 index 0000000..49aac5f --- /dev/null +++ b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.util.List; + +import javax.swing.tree.DefaultMutableTreeNode; + +import com.google.common.collect.Lists; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Translator; + +public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { + + private static final long serialVersionUID = 3112703459157851912L; + + private Translator m_deobfuscatingTranslator; + private ClassEntry m_entry; + + public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_entry = entry; + } + + public ClassEntry getClassEntry() { + return m_entry; + } + + public String getDeobfClassName() { + return m_deobfuscatingTranslator.translateClass(m_entry.getClassName()); + } + + @Override + public String toString() { + String className = getDeobfClassName(); + if (className == null) { + className = m_entry.getClassName(); + } + return className; + } + + public void load(JarIndex index) { + // get all method implementations + List nodes = Lists.newArrayList(); + for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) { + nodes.add(new ClassImplementationsTreeNode(m_deobfuscatingTranslator, new ClassEntry(implementingClassName))); + } + + // add them to this node + for (ClassImplementationsTreeNode node : nodes) { + this.add(node); + } + } + + public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { + // is this the node? + if (node.m_entry.equals(entry)) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode)node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } +} diff --git a/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java new file mode 100644 index 0000000..3eaa391 --- /dev/null +++ b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.util.List; + +import javax.swing.tree.DefaultMutableTreeNode; + +import com.google.common.collect.Lists; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Translator; + +public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { + + private static final long serialVersionUID = 4432367405826178490L; + + private Translator m_deobfuscatingTranslator; + private String m_obfClassName; + + public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_obfClassName = obfClassName; + } + + public String getObfClassName() { + return m_obfClassName; + } + + public String getDeobfClassName() { + return m_deobfuscatingTranslator.translateClass(m_obfClassName); + } + + @Override + public String toString() { + String deobfClassName = getDeobfClassName(); + if (deobfClassName != null) { + return deobfClassName; + } + return m_obfClassName; + } + + public void load(TranslationIndex ancestries, boolean recurse) { + // get all the child nodes + List nodes = Lists.newArrayList(); + for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) { + nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName())); + } + + // add them to this node + for (ClassInheritanceTreeNode node : nodes) { + this.add(node); + } + + if (recurse) { + for (ClassInheritanceTreeNode node : nodes) { + node.load(ancestries, true); + } + } + } + + public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { + // is this the node? + if (node.getObfClassName().equals(entry.getName())) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode)node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } +} diff --git a/src/cuchaz/enigma/analysis/EntryReference.java b/src/cuchaz/enigma/analysis/EntryReference.java new file mode 100644 index 0000000..bb611df --- /dev/null +++ b/src/cuchaz/enigma/analysis/EntryReference.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.util.Arrays; +import java.util.List; + +import cuchaz.enigma.Util; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.Entry; + +public class EntryReference { + + private static final List ConstructorNonNames = Arrays.asList("this", "super", "static"); + public E entry; + public C context; + + private boolean m_isNamed; + + public EntryReference(E entry, String sourceName) { + this(entry, sourceName, null); + } + + public EntryReference(E entry, String sourceName, C context) { + if (entry == null) { + throw new IllegalArgumentException("Entry cannot be null!"); + } + + this.entry = entry; + this.context = context; + + m_isNamed = sourceName != null && sourceName.length() > 0; + if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) { + m_isNamed = false; + } + } + + public EntryReference(E entry, C context, EntryReference other) { + this.entry = entry; + this.context = context; + m_isNamed = other.m_isNamed; + } + + public ClassEntry getLocationClassEntry() { + if (context != null) { + return context.getClassEntry(); + } + return entry.getClassEntry(); + } + + public boolean isNamed() { + return m_isNamed; + } + + public Entry getNameableEntry() { + if (entry instanceof ConstructorEntry) { + // renaming a constructor really means renaming the class + return entry.getClassEntry(); + } + return entry; + } + + public String getNamableName() { + if (getNameableEntry() instanceof ClassEntry) { + ClassEntry classEntry = (ClassEntry)getNameableEntry(); + if (classEntry.isInnerClass()) { + // make sure we only rename the inner class name + return classEntry.getInnerClassName(); + } + } + + return getNameableEntry().getName(); + } + + @Override + public int hashCode() { + if (context != null) { + return Util.combineHashesOrdered(entry.hashCode(), context.hashCode()); + } + return entry.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof EntryReference) { + return equals((EntryReference)other); + } + return false; + } + + public boolean equals(EntryReference other) { + // check entry first + boolean isEntrySame = entry.equals(other.entry); + if (!isEntrySame) { + return false; + } + + // check caller + if (context == null && other.context == null) { + return true; + } else if (context != null && other.context != null) { + return context.equals(other.context); + } + return false; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(entry); + if (context != null) { + buf.append(" called from "); + buf.append(context); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/analysis/EntryRenamer.java b/src/cuchaz/enigma/analysis/EntryRenamer.java new file mode 100644 index 0000000..b54489c --- /dev/null +++ b/src/cuchaz/enigma/analysis/EntryRenamer.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.MethodEntry; + +public class EntryRenamer { + + public static void renameClassesInSet(Map renames, Set set) { + List entries = Lists.newArrayList(); + for (T val : set) { + entries.add(renameClassesInThing(renames, val)); + } + set.clear(); + set.addAll(entries); + } + + public static void renameClassesInMap(Map renames, Map map) { + // for each key/value pair... + Set> entriesToAdd = Sets.newHashSet(); + for (Map.Entry entry : map.entrySet()) { + entriesToAdd.add(new AbstractMap.SimpleEntry( + renameClassesInThing(renames, entry.getKey()), + renameClassesInThing(renames, entry.getValue()) + )); + } + map.clear(); + for (Map.Entry entry : entriesToAdd) { + map.put(entry.getKey(), entry.getValue()); + } + } + + public static void renameClassesInMultimap(Map renames, Multimap map) { + // for each key/value pair... + Set> entriesToAdd = Sets.newHashSet(); + for (Map.Entry entry : map.entries()) { + entriesToAdd.add(new AbstractMap.SimpleEntry( + renameClassesInThing(renames, entry.getKey()), + renameClassesInThing(renames, entry.getValue()) + )); + } + map.clear(); + for (Map.Entry entry : entriesToAdd) { + map.put(entry.getKey(), entry.getValue()); + } + } + + public static void renameMethodsInMultimap(Map renames, Multimap map) { + // for each key/value pair... + Set> entriesToAdd = Sets.newHashSet(); + for (Map.Entry entry : map.entries()) { + entriesToAdd.add(new AbstractMap.SimpleEntry( + renameMethodsInThing(renames, entry.getKey()), + renameMethodsInThing(renames, entry.getValue()) + )); + } + map.clear(); + for (Map.Entry entry : entriesToAdd) { + map.put(entry.getKey(), entry.getValue()); + } + } + + public static void renameMethodsInMap(Map renames, Map map) { + // for each key/value pair... + Set> entriesToAdd = Sets.newHashSet(); + for (Map.Entry entry : map.entrySet()) { + entriesToAdd.add(new AbstractMap.SimpleEntry( + renameMethodsInThing(renames, entry.getKey()), + renameMethodsInThing(renames, entry.getValue()) + )); + } + map.clear(); + for (Map.Entry entry : entriesToAdd) { + map.put(entry.getKey(), entry.getValue()); + } + } + + @SuppressWarnings("unchecked") + public static T renameMethodsInThing(Map renames, T thing) { + if (thing instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry)thing; + MethodEntry newMethodEntry = renames.get(methodEntry); + if (newMethodEntry != null) { + return (T)new MethodEntry( + methodEntry.getClassEntry(), + newMethodEntry.getName(), + methodEntry.getSignature() + ); + } + return thing; + } else if (thing instanceof ArgumentEntry) { + ArgumentEntry argumentEntry = (ArgumentEntry)thing; + return (T)new ArgumentEntry( + renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()), + argumentEntry.getIndex(), + argumentEntry.getName() + ); + } else if (thing instanceof EntryReference) { + EntryReference reference = (EntryReference)thing; + reference.entry = renameMethodsInThing(renames, reference.entry); + reference.context = renameMethodsInThing(renames, reference.context); + return thing; + } + return thing; + } + + @SuppressWarnings("unchecked") + public static T renameClassesInThing(Map renames, T thing) { + if (thing instanceof String) { + String stringEntry = (String)thing; + if (renames.containsKey(stringEntry)) { + return (T)renames.get(stringEntry); + } + } else if (thing instanceof ClassEntry) { + ClassEntry classEntry = (ClassEntry)thing; + return (T)new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); + } else if (thing instanceof FieldEntry) { + FieldEntry fieldEntry = (FieldEntry)thing; + return (T)new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName()); + } else if (thing instanceof ConstructorEntry) { + ConstructorEntry constructorEntry = (ConstructorEntry)thing; + return (T)new ConstructorEntry( + renameClassesInThing(renames, constructorEntry.getClassEntry()), + constructorEntry.getSignature() + ); + } else if (thing instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry)thing; + return (T)new MethodEntry( + renameClassesInThing(renames, methodEntry.getClassEntry()), + methodEntry.getName(), + methodEntry.getSignature() + ); + } else if (thing instanceof ArgumentEntry) { + ArgumentEntry argumentEntry = (ArgumentEntry)thing; + return (T)new ArgumentEntry( + renameClassesInThing(renames, argumentEntry.getBehaviorEntry()), + argumentEntry.getIndex(), + argumentEntry.getName() + ); + } else if (thing instanceof EntryReference) { + EntryReference reference = (EntryReference)thing; + reference.entry = renameClassesInThing(renames, reference.entry); + reference.context = renameClassesInThing(renames, reference.context); + return thing; + } + + return thing; + } +} diff --git a/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java new file mode 100644 index 0000000..2173eea --- /dev/null +++ b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import javax.swing.tree.DefaultMutableTreeNode; + +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.Translator; + +public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { + + private static final long serialVersionUID = -7934108091928699835L; + + private Translator m_deobfuscatingTranslator; + private FieldEntry m_entry; + private EntryReference m_reference; + private Access m_access; + + public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_entry = entry; + m_reference = null; + } + + private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_entry = reference.entry; + m_reference = reference; + m_access = access; + } + + @Override + public FieldEntry getEntry() { + return m_entry; + } + + @Override + public EntryReference getReference() { + return m_reference; + } + + @Override + public String toString() { + if (m_reference != null) { + return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access); + } + return m_deobfuscatingTranslator.translateEntry(m_entry).toString(); + } + + public void load(JarIndex index, boolean recurse) { + // get all the child nodes + if (m_reference == null) { + for (EntryReference reference : index.getFieldReferences(m_entry)) { + add(new FieldReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry))); + } + } else { + for (EntryReference reference : index.getBehaviorReferences(m_reference.context)) { + add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_reference.context))); + } + } + + if (recurse && children != null) { + for (Object node : children) { + if (node instanceof BehaviorReferenceTreeNode) { + ((BehaviorReferenceTreeNode)node).load(index, true); + } else if (node instanceof FieldReferenceTreeNode) { + ((FieldReferenceTreeNode)node).load(index, true); + } + } + } + } +} diff --git a/src/cuchaz/enigma/analysis/JarClassIterator.java b/src/cuchaz/enigma/analysis/JarClassIterator.java new file mode 100644 index 0000000..72a9912 --- /dev/null +++ b/src/cuchaz/enigma/analysis/JarClassIterator.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import javassist.ByteArrayClassPath; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.Descriptor; + +import com.google.common.collect.Lists; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.mapping.ClassEntry; + +public class JarClassIterator implements Iterator { + + private JarFile m_jar; + private Iterator m_iter; + + public JarClassIterator(JarFile jar) { + m_jar = jar; + + // get the jar entries that correspond to classes + List classEntries = Lists.newArrayList(); + Enumeration entries = m_jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + + // is this a class file? + if (entry.getName().endsWith(".class")) { + classEntries.add(entry); + } + } + m_iter = classEntries.iterator(); + } + + @Override + public boolean hasNext() { + return m_iter.hasNext(); + } + + @Override + public CtClass next() { + JarEntry entry = m_iter.next(); + try { + return getClass(m_jar, entry); + } catch (IOException | NotFoundException ex) { + throw new Error("Unable to load class: " + entry.getName()); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + public static List getClassEntries(JarFile jar) { + List classEntries = Lists.newArrayList(); + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + + // is this a class file? + if (!entry.isDirectory() && entry.getName().endsWith(".class")) { + classEntries.add(getClassEntry(entry)); + } + } + return classEntries; + } + + public static Iterable classes(final JarFile jar) { + return new Iterable() { + @Override + public Iterator iterator() { + return new JarClassIterator(jar); + } + }; + } + + public static CtClass getClass(JarFile jar, ClassEntry classEntry) { + try { + return getClass(jar, new JarEntry(classEntry.getName() + ".class")); + } catch (IOException | NotFoundException ex) { + throw new Error("Unable to load class: " + classEntry.getName()); + } + } + + private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException { + // read the class into a buffer + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buf = new byte[Constants.KiB]; + int totalNumBytesRead = 0; + InputStream in = jar.getInputStream(entry); + while (in.available() > 0) { + int numBytesRead = in.read(buf); + if (numBytesRead < 0) { + break; + } + bos.write(buf, 0, numBytesRead); + + // sanity checking + totalNumBytesRead += numBytesRead; + if (totalNumBytesRead > Constants.MiB) { + throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!"); + } + } + + // get a javassist handle for the class + String className = Descriptor.toJavaName(getClassEntry(entry).getName()); + ClassPool classPool = new ClassPool(); + classPool.appendSystemPath(); + classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray())); + return classPool.get(className); + } + + private static ClassEntry getClassEntry(JarEntry entry) { + return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length())); + } +} diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java new file mode 100644 index 0000000..3aac8bd --- /dev/null +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -0,0 +1,734 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarFile; + +import javassist.CannotCompileException; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtField; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.Descriptor; +import javassist.bytecode.FieldInfo; +import javassist.expr.ConstructorCall; +import javassist.expr.ExprEditor; +import javassist.expr.FieldAccess; +import javassist.expr.MethodCall; +import javassist.expr.NewExpr; + +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.google.common.collect.Sets; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.bytecode.ClassRenamer; +import cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.BehaviorEntryFactory; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Translator; + +public class JarIndex { + + private Set m_obfClassEntries; + private TranslationIndex m_translationIndex; + private Multimap m_interfaces; + private Map m_access; + private Map m_fieldClasses; // TODO: this will become obsolete! + private Multimap m_methodImplementations; + private Multimap> m_behaviorReferences; + private Multimap> m_fieldReferences; + private Multimap m_innerClasses; + private Map m_outerClasses; + private Map m_anonymousClasses; + + public JarIndex() { + m_obfClassEntries = Sets.newHashSet(); + m_translationIndex = new TranslationIndex(); + m_interfaces = HashMultimap.create(); + m_access = Maps.newHashMap(); + m_fieldClasses = Maps.newHashMap(); + m_methodImplementations = HashMultimap.create(); + m_behaviorReferences = HashMultimap.create(); + m_fieldReferences = HashMultimap.create(); + m_innerClasses = HashMultimap.create(); + m_outerClasses = Maps.newHashMap(); + m_anonymousClasses = Maps.newHashMap(); + } + + public void indexJar(JarFile jar, boolean buildInnerClasses) { + + // step 1: read the class names + for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) { + if (classEntry.isInDefaultPackage()) { + // move out of default package + classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName()); + } + m_obfClassEntries.add(classEntry); + } + + // step 2: index field/method/constructor access + for (CtClass c : JarClassIterator.classes(jar)) { + ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); + for (CtField field : c.getDeclaredFields()) { + m_access.put(JavassistUtil.getFieldEntry(field), Access.get(field)); + } + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + m_access.put(JavassistUtil.getBehaviorEntry(behavior), Access.get(behavior)); + } + } + + // step 3: index extends, implements, fields, and methods + for (CtClass c : JarClassIterator.classes(jar)) { + ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); + m_translationIndex.indexClass(c); + String className = Descriptor.toJvmName(c.getName()); + for (String interfaceName : c.getClassFile().getInterfaces()) { + className = Descriptor.toJvmName(className); + interfaceName = Descriptor.toJvmName(interfaceName); + if (className.equals(interfaceName)) { + throw new IllegalArgumentException("Class cannot be its own interface! " + className); + } + m_interfaces.put(className, interfaceName); + } + for (CtField field : c.getDeclaredFields()) { + indexField(field); + } + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + indexBehavior(behavior); + } + } + + // step 4: index field, method, constructor references + for (CtClass c : JarClassIterator.classes(jar)) { + ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + indexBehaviorReferences(behavior); + } + } + + if (buildInnerClasses) { + // step 5: index inner classes and anonymous classes + for (CtClass c : JarClassIterator.classes(jar)) { + ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); + String outerClassName = findOuterClass(c); + if (outerClassName != null) { + String innerClassName = c.getSimpleName(); + m_innerClasses.put(outerClassName, innerClassName); + boolean innerWasAdded = m_outerClasses.put(innerClassName, outerClassName) == null; + assert (innerWasAdded); + + BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassName); + if (enclosingBehavior != null) { + m_anonymousClasses.put(innerClassName, enclosingBehavior); + + // DEBUG + // System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName ); + } else { + // DEBUG + // System.out.println( "INNER: " + outerClassName + "$" + innerClassName ); + } + } + } + + // step 6: update other indices with inner class info + Map renames = Maps.newHashMap(); + for (Map.Entry entry : m_outerClasses.entrySet()) { + renames.put(Constants.NonePackage + "/" + entry.getKey(), entry.getValue() + "$" + entry.getKey()); + } + EntryRenamer.renameClassesInSet(renames, m_obfClassEntries); + m_translationIndex.renameClasses(renames); + EntryRenamer.renameClassesInMultimap(renames, m_interfaces); + EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations); + EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences); + EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences); + EntryRenamer.renameClassesInMap(renames, m_access); + } + } + + private void indexField(CtField field) { + // get the field entry + String className = Descriptor.toJvmName(field.getDeclaringClass().getName()); + FieldEntry fieldEntry = new FieldEntry(new ClassEntry(className), field.getName()); + + // is the field a class type? + if (field.getSignature().startsWith("L")) { + ClassEntry fieldTypeEntry = new ClassEntry(field.getSignature().substring(1, field.getSignature().length() - 1)); + m_fieldClasses.put(fieldEntry, fieldTypeEntry); + } + } + + private void indexBehavior(CtBehavior behavior) { + // get the behavior entry + final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); + if (behaviorEntry instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry)behaviorEntry; + + // index implementation + m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry); + } + // looks like we don't care about constructors here + } + + private void indexBehaviorReferences(CtBehavior behavior) { + // index method calls + final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); + try { + behavior.instrument(new ExprEditor() { + @Override + public void edit(MethodCall call) { + MethodEntry calledMethodEntry = JavassistUtil.getMethodEntry(call); + ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry); + if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) { + calledMethodEntry = new MethodEntry( + resolvedClassEntry, + calledMethodEntry.getName(), + calledMethodEntry.getSignature() + ); + } + EntryReference reference = new EntryReference( + calledMethodEntry, + call.getMethodName(), + behaviorEntry + ); + m_behaviorReferences.put(calledMethodEntry, reference); + } + + @Override + public void edit(FieldAccess call) { + FieldEntry calledFieldEntry = JavassistUtil.getFieldEntry(call); + ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry); + if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) { + calledFieldEntry = new FieldEntry(resolvedClassEntry, call.getFieldName()); + } + EntryReference reference = new EntryReference( + calledFieldEntry, + call.getFieldName(), + behaviorEntry + ); + m_fieldReferences.put(calledFieldEntry, reference); + } + + @Override + public void edit(ConstructorCall call) { + ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call); + EntryReference reference = new EntryReference( + calledConstructorEntry, + call.getMethodName(), + behaviorEntry + ); + m_behaviorReferences.put(calledConstructorEntry, reference); + } + + @Override + public void edit(NewExpr call) { + ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call); + EntryReference reference = new EntryReference( + calledConstructorEntry, + call.getClassName(), + behaviorEntry + ); + m_behaviorReferences.put(calledConstructorEntry, reference); + } + }); + } catch (CannotCompileException ex) { + throw new Error(ex); + } + } + + private String findOuterClass(CtClass c) { + + // inner classes: + // have constructors that can (illegally) set synthetic fields + // the outer class is the only class that calls constructors + + // use the synthetic fields to find the synthetic constructors + for (CtConstructor constructor : c.getDeclaredConstructors()) { + Set syntheticFieldTypes = Sets.newHashSet(); + if (!isIllegalConstructor(syntheticFieldTypes, constructor)) { + continue; + } + + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor); + + // gather the classes from the illegally-set synthetic fields + Set illegallySetClasses = Sets.newHashSet(); + for (String type : syntheticFieldTypes) { + if (type.startsWith("L")) { + ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1)); + if (isSaneOuterClass(outerClassEntry, classEntry)) { + illegallySetClasses.add(outerClassEntry); + } + } + } + + // who calls this constructor? + Set callerClasses = Sets.newHashSet(); + for (EntryReference reference : getBehaviorReferences(constructorEntry)) { + + // make sure it's not a call to super + if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) { + + // is the entry a superclass of the context? + ClassEntry calledClassEntry = reference.entry.getClassEntry(); + ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry()); + if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) { + // it's a super call, skip + continue; + } + } + + if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) { + callerClasses.add(reference.context.getClassEntry()); + } + } + + // do we have an answer yet? + if (callerClasses.isEmpty()) { + if (illegallySetClasses.size() == 1) { + return illegallySetClasses.iterator().next().getName(); + } else { + System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry)); + } + } else { + if (callerClasses.size() == 1) { + return callerClasses.iterator().next().getName(); + } else { + // multiple callers, do the illegally set classes narrow it down? + Set intersection = Sets.newHashSet(callerClasses); + intersection.retainAll(illegallySetClasses); + if (intersection.size() == 1) { + return intersection.iterator().next().getName(); + } else { + System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses)); + } + } + } + } + + return null; + } + + private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { + + // clearly this would be silly + if (outerClassEntry.equals(innerClassEntry)) { + return false; + } + + // is the outer class in the jar? + if (!m_obfClassEntries.contains(outerClassEntry)) { + return false; + } + + return true; + } + + @SuppressWarnings("unchecked") + private boolean isIllegalConstructor(Set syntheticFieldTypes, CtConstructor constructor) { + + // illegal constructors only set synthetic member fields, then call super() + String className = constructor.getDeclaringClass().getName(); + + // collect all the field accesses, constructor calls, and method calls + final List illegalFieldWrites = Lists.newArrayList(); + final List constructorCalls = Lists.newArrayList(); + try { + constructor.instrument(new ExprEditor() { + @Override + public void edit(FieldAccess fieldAccess) { + if (fieldAccess.isWriter() && constructorCalls.isEmpty()) { + illegalFieldWrites.add(fieldAccess); + } + } + + @Override + public void edit(ConstructorCall constructorCall) { + constructorCalls.add(constructorCall); + } + }); + } catch (CannotCompileException ex) { + // we're not compiling anything... this is stupid + throw new Error(ex); + } + + // are there any illegal field writes? + if (illegalFieldWrites.isEmpty()) { + return false; + } + + // are all the writes to synthetic fields? + for (FieldAccess fieldWrite : illegalFieldWrites) { + + // all illegal writes have to be to the local class + if (!fieldWrite.getClassName().equals(className)) { + System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName())); + return false; + } + + // find the field + FieldInfo fieldInfo = null; + for (FieldInfo info : (List)constructor.getDeclaringClass().getClassFile().getFields()) { + if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) { + fieldInfo = info; + break; + } + } + if (fieldInfo == null) { + // field is in a superclass or something, can't be a local synthetic member + return false; + } + + // is this field synthetic? + boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; + if (isSynthetic) { + syntheticFieldTypes.add(fieldInfo.getDescriptor()); + } else { + System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName())); + return false; + } + } + + // we passed all the tests! + return true; + } + + private BehaviorEntry isAnonymousClass(CtClass c, String outerClassName) { + + ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + + // anonymous classes: + // can't be abstract + // have only one constructor + // it's called exactly once by the outer class + // the type the instance is assigned to can't be this type + + // is abstract? + if (Modifier.isAbstract(c.getModifiers())) { + return null; + } + + // is there exactly one constructor? + if (c.getDeclaredConstructors().length != 1) { + return null; + } + CtConstructor constructor = c.getDeclaredConstructors()[0]; + + // is this constructor called exactly once? + ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor); + Collection> references = getBehaviorReferences(constructorEntry); + if (references.size() != 1) { + return null; + } + + // does the caller use this type? + BehaviorEntry caller = references.iterator().next().context; + for (FieldEntry fieldEntry : getReferencedFields(caller)) { + ClassEntry fieldClass = getFieldClass(fieldEntry); + if (fieldClass != null && fieldClass.equals(innerClassEntry)) { + // caller references this type, so it can't be anonymous + return null; + } + } + for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) { + if (behaviorEntry.getSignature().hasClass(innerClassEntry)) { + return null; + } + } + + return caller; + } + + public Set getObfClassEntries() { + return m_obfClassEntries; + } + + public TranslationIndex getTranslationIndex() { + return m_translationIndex; + } + + public Access getAccess(Entry entry) { + return m_access.get(entry); + } + + public ClassEntry getFieldClass(FieldEntry fieldEntry) { + return m_fieldClasses.get(fieldEntry); + } + + public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { + + // get the root node + List ancestry = Lists.newArrayList(); + ancestry.add(obfClassEntry.getName()); + for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) { + ancestry.add(classEntry.getName()); + } + ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( + deobfuscatingTranslator, + ancestry.get(ancestry.size() - 1) + ); + + // expand all children recursively + rootNode.load(m_translationIndex, true); + + return rootNode; + } + + public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { + + // is this even an interface? + if (isInterface(obfClassEntry.getClassName())) { + ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry); + node.load(this); + return node; + } + return null; + } + + public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { + + // travel to the ancestor implementation + ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); + for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { + MethodEntry ancestorMethodEntry = new MethodEntry( + new ClassEntry(ancestorClassEntry), + obfMethodEntry.getName(), + obfMethodEntry.getSignature() + ); + if (containsObfBehavior(ancestorMethodEntry)) { + baseImplementationClassEntry = ancestorClassEntry; + } + } + + // make a root node at the base + MethodEntry methodEntry = new MethodEntry( + baseImplementationClassEntry, + obfMethodEntry.getName(), + obfMethodEntry.getSignature() + ); + MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( + deobfuscatingTranslator, + methodEntry, + containsObfBehavior(methodEntry) + ); + + // expand the full tree + rootNode.load(this, true); + + return rootNode; + } + + public MethodImplementationsTreeNode getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { + + MethodEntry interfaceMethodEntry; + + // is this method on an interface? + if (isInterface(obfMethodEntry.getClassName())) { + interfaceMethodEntry = obfMethodEntry; + } else { + // get the interface class + List methodInterfaces = Lists.newArrayList(); + for (String interfaceName : getInterfaces(obfMethodEntry.getClassName())) { + // is this method defined in this interface? + MethodEntry methodInterface = new MethodEntry( + new ClassEntry(interfaceName), + obfMethodEntry.getName(), + obfMethodEntry.getSignature() + ); + if (containsObfBehavior(methodInterface)) { + methodInterfaces.add(methodInterface); + } + } + if (methodInterfaces.isEmpty()) { + return null; + } + if (methodInterfaces.size() > 1) { + throw new Error("Too many interfaces define this method! This is not yet supported by Enigma!"); + } + interfaceMethodEntry = methodInterfaces.get(0); + } + + MethodImplementationsTreeNode rootNode = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry); + rootNode.load(this); + return rootNode; + } + + public Set getRelatedMethodImplementations(MethodEntry obfMethodEntry) { + Set methodEntries = Sets.newHashSet(); + getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry)); + return methodEntries; + } + + private void getRelatedMethodImplementations(Set methodEntries, MethodInheritanceTreeNode node) { + MethodEntry methodEntry = node.getMethodEntry(); + if (containsObfBehavior(methodEntry)) { + // collect the entry + methodEntries.add(methodEntry); + } + + // look at interface methods too + MethodImplementationsTreeNode implementations = getMethodImplementations(null, methodEntry); + if (implementations != null) { + getRelatedMethodImplementations(methodEntries, implementations); + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode)node.getChildAt(i)); + } + } + + private void getRelatedMethodImplementations(Set methodEntries, MethodImplementationsTreeNode node) { + MethodEntry methodEntry = node.getMethodEntry(); + if (containsObfBehavior(methodEntry)) { + // collect the entry + methodEntries.add(methodEntry); + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode)node.getChildAt(i)); + } + } + + public Collection> getFieldReferences(FieldEntry fieldEntry) { + return m_fieldReferences.get(fieldEntry); + } + + public Collection getReferencedFields(BehaviorEntry behaviorEntry) { + // linear search is fast enough for now + Set fieldEntries = Sets.newHashSet(); + for (EntryReference reference : m_fieldReferences.values()) { + if (reference.context == behaviorEntry) { + fieldEntries.add(reference.entry); + } + } + return fieldEntries; + } + + public Collection> getBehaviorReferences(BehaviorEntry behaviorEntry) { + return m_behaviorReferences.get(behaviorEntry); + } + + public Collection getReferencedBehaviors(BehaviorEntry behaviorEntry) { + // linear search is fast enough for now + Set behaviorEntries = Sets.newHashSet(); + for (EntryReference reference : m_behaviorReferences.values()) { + if (reference.context == behaviorEntry) { + behaviorEntries.add(reference.entry); + } + } + return behaviorEntries; + } + + public Collection getInnerClasses(String obfOuterClassName) { + return m_innerClasses.get(obfOuterClassName); + } + + public String getOuterClass(String obfInnerClassName) { + // make sure we use the right name + if (new ClassEntry(obfInnerClassName).getPackageName() != null) { + throw new IllegalArgumentException("Don't reference obfuscated inner classes using packages: " + obfInnerClassName); + } + return m_outerClasses.get(obfInnerClassName); + } + + public boolean isAnonymousClass(String obfInnerClassName) { + return m_anonymousClasses.containsKey(obfInnerClassName); + } + + public BehaviorEntry getAnonymousClassCaller(String obfInnerClassName) { + return m_anonymousClasses.get(obfInnerClassName); + } + + public Set getInterfaces(String className) { + Set interfaceNames = new HashSet(); + interfaceNames.addAll(m_interfaces.get(className)); + for (ClassEntry ancestor : m_translationIndex.getAncestry(new ClassEntry(className))) { + interfaceNames.addAll(m_interfaces.get(ancestor.getName())); + } + return interfaceNames; + } + + public Set getImplementingClasses(String targetInterfaceName) { + // linear search is fast enough for now + Set classNames = Sets.newHashSet(); + for (Map.Entry entry : m_interfaces.entries()) { + String className = entry.getKey(); + String interfaceName = entry.getValue(); + if (interfaceName.equals(targetInterfaceName)) { + classNames.add(className); + m_translationIndex.getSubclassNamesRecursively(classNames, new ClassEntry(className)); + } + } + return classNames; + } + + public boolean isInterface(String className) { + return m_interfaces.containsValue(className); + } + + public boolean containsObfClass(ClassEntry obfClassEntry) { + return m_obfClassEntries.contains(obfClassEntry); + } + + public boolean containsObfField(FieldEntry obfFieldEntry) { + return m_access.containsKey(obfFieldEntry); + } + + public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { + return m_access.containsKey(obfBehaviorEntry); + } + + public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { + // check the behavior + if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { + return false; + } + + // check the argument + if (obfArgumentEntry.getIndex() >= obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size()) { + return false; + } + + return true; + } + + public boolean containsObfEntry(Entry obfEntry) { + if (obfEntry instanceof ClassEntry) { + return containsObfClass((ClassEntry)obfEntry); + } else if (obfEntry instanceof FieldEntry) { + return containsObfField((FieldEntry)obfEntry); + } else if (obfEntry instanceof BehaviorEntry) { + return containsObfBehavior((BehaviorEntry)obfEntry); + } else if (obfEntry instanceof ArgumentEntry) { + return containsObfArgument((ArgumentEntry)obfEntry); + } else { + throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); + } + } +} diff --git a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java new file mode 100644 index 0000000..1009226 --- /dev/null +++ b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.util.List; + +import javax.swing.tree.DefaultMutableTreeNode; + +import com.google.common.collect.Lists; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Translator; + +public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { + + private static final long serialVersionUID = 3781080657461899915L; + + private Translator m_deobfuscatingTranslator; + private MethodEntry m_entry; + + public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { + if (entry == null) { + throw new IllegalArgumentException("entry cannot be null!"); + } + + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_entry = entry; + } + + public MethodEntry getMethodEntry() { + return m_entry; + } + + public String getDeobfClassName() { + return m_deobfuscatingTranslator.translateClass(m_entry.getClassName()); + } + + public String getDeobfMethodName() { + return m_deobfuscatingTranslator.translate(m_entry); + } + + @Override + public String toString() { + String className = getDeobfClassName(); + if (className == null) { + className = m_entry.getClassName(); + } + + String methodName = getDeobfMethodName(); + if (methodName == null) { + methodName = m_entry.getName(); + } + return className + "." + methodName + "()"; + } + + public void load(JarIndex index) { + // get all method implementations + List nodes = Lists.newArrayList(); + for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) { + MethodEntry methodEntry = new MethodEntry( + new ClassEntry(implementingClassName), + m_entry.getName(), + m_entry.getSignature() + ); + if (index.containsObfBehavior(methodEntry)) { + nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry)); + } + } + + // add them to this node + for (MethodImplementationsTreeNode node : nodes) { + this.add(node); + } + } + + public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) { + // is this the node? + if (node.getMethodEntry().equals(entry)) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode)node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } +} diff --git a/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java new file mode 100644 index 0000000..8718220 --- /dev/null +++ b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.util.List; + +import javax.swing.tree.DefaultMutableTreeNode; + +import com.google.common.collect.Lists; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Translator; + +public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { + + private static final long serialVersionUID = 1096677030991810007L; + + private Translator m_deobfuscatingTranslator; + private MethodEntry m_entry; + private boolean m_isImplemented; + + public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) { + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_entry = entry; + m_isImplemented = isImplemented; + } + + public MethodEntry getMethodEntry() { + return m_entry; + } + + public String getDeobfClassName() { + return m_deobfuscatingTranslator.translateClass(m_entry.getClassName()); + } + + public String getDeobfMethodName() { + return m_deobfuscatingTranslator.translate(m_entry); + } + + public boolean isImplemented() { + return m_isImplemented; + } + + @Override + public String toString() { + String className = getDeobfClassName(); + if (className == null) { + className = m_entry.getClassName(); + } + + if (!m_isImplemented) { + return className; + } else { + String methodName = getDeobfMethodName(); + if (methodName == null) { + methodName = m_entry.getName(); + } + return className + "." + methodName + "()"; + } + } + + public void load(JarIndex index, boolean recurse) { + // get all the child nodes + List nodes = Lists.newArrayList(); + for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(m_entry.getClassEntry())) { + MethodEntry methodEntry = new MethodEntry( + subclassEntry, + m_entry.getName(), + m_entry.getSignature() + ); + nodes.add(new MethodInheritanceTreeNode( + m_deobfuscatingTranslator, + methodEntry, + index.containsObfBehavior(methodEntry) + )); + } + + // add them to this node + for (MethodInheritanceTreeNode node : nodes) { + this.add(node); + } + + if (recurse) { + for (MethodInheritanceTreeNode node : nodes) { + node.load(index, true); + } + } + } + + public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) { + // is this the node? + if (node.getMethodEntry().equals(entry)) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode)node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } +} diff --git a/src/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java new file mode 100644 index 0000000..2b08616 --- /dev/null +++ b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.mapping.Entry; + +public interface ReferenceTreeNode { + E getEntry(); + EntryReference getReference(); +} diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java new file mode 100644 index 0000000..b43ab61 --- /dev/null +++ b/src/cuchaz/enigma/analysis/SourceIndex.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +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.Identifier; + +import cuchaz.enigma.mapping.Entry; + +public class SourceIndex { + + private String m_source; + private TreeMap> m_tokenToReference; + private Multimap,Token> m_referenceToTokens; + private Map m_declarationToToken; + private List m_lineOffsets; + + public SourceIndex(String source) { + m_source = source; + m_tokenToReference = Maps.newTreeMap(); + m_referenceToTokens = HashMultimap.create(); + m_declarationToToken = Maps.newHashMap(); + m_lineOffsets = Lists.newArrayList(); + + // count the lines + m_lineOffsets.add(0); + for (int i = 0; i < source.length(); i++) { + if (source.charAt(i) == '\n') { + m_lineOffsets.add(i + 1); + } + } + } + + public String getSource() { + return m_source; + } + + public Token getToken(AstNode node) { + + // get the text of the node + String name = ""; + if (node instanceof Identifier) { + name = ((Identifier)node).getName(); + } + + // get a token for this node's region + Region region = node.getRegion(); + if (region.getBeginLine() == 0 || region.getEndLine() == 0) { + // DEBUG + System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region)); + return null; + } + Token token = new Token( + toPos(region.getBeginLine(), region.getBeginColumn()), + toPos(region.getEndLine(), region.getEndColumn()), + m_source + ); + if (token.start == 0) { + // DEBUG + System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region)); + return null; + } + + // DEBUG + // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) ); + + // for tokens representing inner classes, make sure we only get the simple name + int pos = name.lastIndexOf('$'); + if (pos >= 0) { + token.end -= pos + 1; + } + + return token; + } + + public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) { + Token token = getToken(node); + if (token != null) { + EntryReference deobfReference = new EntryReference(deobfEntry, token.text, deobfContext); + m_tokenToReference.put(token, deobfReference); + m_referenceToTokens.put(deobfReference, token); + } + } + + public void addDeclaration(AstNode node, Entry deobfEntry) { + Token token = getToken(node); + if (token != null) { + EntryReference reference = new EntryReference(deobfEntry, token.text); + m_tokenToReference.put(token, reference); + m_referenceToTokens.put(reference, token); + m_declarationToToken.put(deobfEntry, token); + } + } + + public Token getReferenceToken(int pos) { + Token token = m_tokenToReference.floorKey(new Token(pos, pos, null)); + if (token != null && token.contains(pos)) { + return token; + } + return null; + } + + public Collection getReferenceTokens(EntryReference deobfReference) { + return m_referenceToTokens.get(deobfReference); + } + + public EntryReference getDeobfReference(Token token) { + if (token == null) { + return null; + } + return m_tokenToReference.get(token); + } + + public void replaceDeobfReference(Token token, EntryReference newDeobfReference) { + EntryReference oldDeobfReference = m_tokenToReference.get(token); + m_tokenToReference.put(token, newDeobfReference); + Collection tokens = m_referenceToTokens.get(oldDeobfReference); + m_referenceToTokens.removeAll(oldDeobfReference); + m_referenceToTokens.putAll(newDeobfReference, tokens); + } + + public Iterable referenceTokens() { + return m_tokenToReference.keySet(); + } + + public Iterable declarationTokens() { + return m_declarationToToken.values(); + } + + public Token getDeclarationToken(Entry deobfEntry) { + return m_declarationToToken.get(deobfEntry); + } + + public int getLineNumber(int pos) { + // line number is 1-based + int line = 0; + for (Integer offset : m_lineOffsets) { + if (offset > pos) { + break; + } + line++; + } + return line; + } + + public int getColumnNumber(int pos) { + // column number is 1-based + return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1; + } + + private int toPos(int line, int col) { + // line and col are 1-based + return m_lineOffsets.get(line - 1) + col - 1; + } +} diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java new file mode 100644 index 0000000..4155128 --- /dev/null +++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.MethodReference; +import com.strobel.assembler.metadata.ParameterDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; +import com.strobel.decompiler.languages.java.ast.IdentifierExpression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.MethodDeclaration; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; +import com.strobel.decompiler.languages.java.ast.SimpleType; +import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; + +import cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Signature; + +public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { + + private BehaviorEntry m_behaviorEntry; + + public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) { + m_behaviorEntry = behaviorEntry; + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + // get the behavior entry + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + BehaviorEntry behaviorEntry = null; + if (ref instanceof MethodReference) { + MethodReference methodRef = (MethodReference)ref; + if (methodRef.isConstructor()) { + behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature())); + } else if (methodRef.isTypeInitializer()) { + behaviorEntry = new ConstructorEntry(classEntry); + } else { + behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getSignature())); + } + } + if (behaviorEntry != null) { + // get the node for the token + AstNode tokenNode = null; + if (node.getTarget() instanceof MemberReferenceExpression) { + tokenNode = ((MemberReferenceExpression)node.getTarget()).getMemberNameToken(); + } else if (node.getTarget() instanceof SuperReferenceExpression) { + tokenNode = node.getTarget(); + } else if (node.getTarget() instanceof ThisReferenceExpression) { + tokenNode = node.getTarget(); + } + if (tokenNode != null) { + index.addReference(tokenNode, behaviorEntry, m_behaviorEntry); + } + } + + return recurse(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + // make sure this is actually a field + if (ref.getSignature().indexOf('(') >= 0) { + throw new Error("Expected a field here! got " + ref); + } + + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName()); + index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = new ClassEntry(ref.getInternalName()); + index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); + ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); + MethodDefinition methodDef = (MethodDefinition)def.getMethod(); + BehaviorEntry behaviorEntry; + if (methodDef.isConstructor()) { + behaviorEntry = new ConstructorEntry(classEntry, new Signature(methodDef.getSignature())); + } else { + behaviorEntry = new MethodEntry(classEntry, methodDef.getName(), new Signature(methodDef.getSignature())); + } + ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName()); + index.addDeclaration(node.getNameToken(), argumentEntry); + + return recurse(node, index); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName()); + index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature())); + if (node.getType() instanceof SimpleType) { + SimpleType simpleTypeNode = (SimpleType)node.getType(); + index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry); + } + } + + return recurse(node, index); + } +} diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java new file mode 100644 index 0000000..7222035 --- /dev/null +++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; +import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration; +import com.strobel.decompiler.languages.java.ast.FieldDeclaration; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MethodDeclaration; +import com.strobel.decompiler.languages.java.ast.SimpleType; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import com.strobel.decompiler.languages.java.ast.VariableInitializer; + +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.BehaviorEntryFactory; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.Signature; + +public class SourceIndexClassVisitor extends SourceIndexVisitor { + + private ClassEntry m_classEntry; + + public SourceIndexClassVisitor(ClassEntry classEntry) { + m_classEntry = classEntry; + } + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + // is this this class, or a subtype? + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassEntry classEntry = new ClassEntry(def.getInternalName()); + if (!classEntry.equals(m_classEntry)) { + // it's a sub-type, recurse + index.addDeclaration(node.getNameToken(), classEntry); + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } + + return recurse(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = new ClassEntry(ref.getInternalName()); + index.addReference(node.getIdentifierToken(), classEntry, m_classEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); + BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(classEntry, def.getName(), def.getSignature()); + AstNode tokenNode = node.getNameToken(); + if (behaviorEntry instanceof ConstructorEntry) { + ConstructorEntry constructorEntry = (ConstructorEntry)behaviorEntry; + if (constructorEntry.isStatic()) { + tokenNode = node.getModifiers().firstOrNullObject(); + } + } + index.addDeclaration(tokenNode, behaviorEntry); + return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); + ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(def.getSignature())); + index.addDeclaration(node.getNameToken(), constructorEntry); + return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName()); + assert (node.getVariables().size() == 1); + VariableInitializer variable = node.getVariables().firstOrNullObject(); + index.addDeclaration(variable.getNameToken(), fieldEntry); + + return recurse(node, index); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { + // treat enum declarations as field declarations + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName()); + index.addDeclaration(node.getNameToken(), fieldEntry); + + return recurse(node, index); + } +} diff --git a/src/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java new file mode 100644 index 0000000..0d5bdc0 --- /dev/null +++ b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java @@ -0,0 +1,452 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.decompiler.languages.java.ast.Annotation; +import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; +import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; +import com.strobel.decompiler.languages.java.ast.ArraySpecifier; +import com.strobel.decompiler.languages.java.ast.AssertStatement; +import com.strobel.decompiler.languages.java.ast.AssignmentExpression; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression; +import com.strobel.decompiler.languages.java.ast.BlockStatement; +import com.strobel.decompiler.languages.java.ast.BreakStatement; +import com.strobel.decompiler.languages.java.ast.CaseLabel; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.CatchClause; +import com.strobel.decompiler.languages.java.ast.ClassOfExpression; +import com.strobel.decompiler.languages.java.ast.Comment; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; +import com.strobel.decompiler.languages.java.ast.ComposedType; +import com.strobel.decompiler.languages.java.ast.ConditionalExpression; +import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; +import com.strobel.decompiler.languages.java.ast.ContinueStatement; +import com.strobel.decompiler.languages.java.ast.DoWhileStatement; +import com.strobel.decompiler.languages.java.ast.EmptyStatement; +import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration; +import com.strobel.decompiler.languages.java.ast.ExpressionStatement; +import com.strobel.decompiler.languages.java.ast.FieldDeclaration; +import com.strobel.decompiler.languages.java.ast.ForEachStatement; +import com.strobel.decompiler.languages.java.ast.ForStatement; +import com.strobel.decompiler.languages.java.ast.GotoStatement; +import com.strobel.decompiler.languages.java.ast.IAstVisitor; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.IdentifierExpression; +import com.strobel.decompiler.languages.java.ast.IfElseStatement; +import com.strobel.decompiler.languages.java.ast.ImportDeclaration; +import com.strobel.decompiler.languages.java.ast.IndexerExpression; +import com.strobel.decompiler.languages.java.ast.InstanceInitializer; +import com.strobel.decompiler.languages.java.ast.InstanceOfExpression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.JavaTokenNode; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.LabelStatement; +import com.strobel.decompiler.languages.java.ast.LabeledStatement; +import com.strobel.decompiler.languages.java.ast.LambdaExpression; +import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.MethodDeclaration; +import com.strobel.decompiler.languages.java.ast.MethodGroupExpression; +import com.strobel.decompiler.languages.java.ast.NewLineNode; +import com.strobel.decompiler.languages.java.ast.NullReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.PackageDeclaration; +import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; +import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression; +import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; +import com.strobel.decompiler.languages.java.ast.ReturnStatement; +import com.strobel.decompiler.languages.java.ast.SimpleType; +import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; +import com.strobel.decompiler.languages.java.ast.SwitchSection; +import com.strobel.decompiler.languages.java.ast.SwitchStatement; +import com.strobel.decompiler.languages.java.ast.SynchronizedStatement; +import com.strobel.decompiler.languages.java.ast.TextNode; +import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ThrowStatement; +import com.strobel.decompiler.languages.java.ast.TryCatchStatement; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration; +import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; +import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression; +import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement; +import com.strobel.decompiler.languages.java.ast.VariableInitializer; +import com.strobel.decompiler.languages.java.ast.WhileStatement; +import com.strobel.decompiler.languages.java.ast.WildcardType; +import com.strobel.decompiler.patterns.Pattern; + +import cuchaz.enigma.mapping.ClassEntry; + +public class SourceIndexVisitor implements IAstVisitor { + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassEntry classEntry = new ClassEntry(def.getInternalName()); + index.addDeclaration(node.getNameToken(), classEntry); + + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } + + protected Void recurse(AstNode node, SourceIndex index) { + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, index); + } + return null; + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitComment(Comment node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitIdentifier(Identifier node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitBlockStatement(BlockStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitBreakStatement(BreakStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitContinueStatement(ContinueStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitLabelStatement(LabelStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitReturnStatement(ReturnStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSwitchSection(SwitchSection node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitCaseLabel(CaseLabel node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitThrowStatement(ThrowStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitCatchClause(CatchClause node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitAnnotation(Annotation node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitNewLine(NewLineNode node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitText(TextNode node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitComposedType(ComposedType node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitWhileStatement(WhileStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitCastExpression(CastExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitForStatement(ForStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitGotoStatement(GotoStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitWildcardType(WildcardType node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitAssertStatement(AssertStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) { + return recurse(node, index); + } +} diff --git a/src/cuchaz/enigma/analysis/Token.java b/src/cuchaz/enigma/analysis/Token.java new file mode 100644 index 0000000..481d2f4 --- /dev/null +++ b/src/cuchaz/enigma/analysis/Token.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +public class Token implements Comparable { + + public int start; + public int end; + public String text; + + public Token(int start, int end) { + this(start, end, null); + } + + public Token(int start, int end, String source) { + this.start = start; + this.end = end; + if (source != null) { + this.text = source.substring(start, end); + } + } + + public boolean contains(int pos) { + return pos >= start && pos <= end; + } + + @Override + public int compareTo(Token other) { + return start - other.start; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Token) { + return equals((Token)other); + } + return false; + } + + public boolean equals(Token other) { + return start == other.start && end == other.end; + } + + @Override + public String toString() { + return String.format("[%d,%d]", start, end); + } +} diff --git a/src/cuchaz/enigma/analysis/TranslationIndex.java b/src/cuchaz/enigma/analysis/TranslationIndex.java new file mode 100644 index 0000000..7597c3a --- /dev/null +++ b/src/cuchaz/enigma/analysis/TranslationIndex.java @@ -0,0 +1,227 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtField; + +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 cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.Translator; + +public class TranslationIndex implements Serializable { + + private static final long serialVersionUID = 738687982126844179L; + + private Map m_superclasses; + private Multimap m_fieldEntries; + private Multimap m_behaviorEntries; + + public TranslationIndex() { + m_superclasses = Maps.newHashMap(); + m_fieldEntries = HashMultimap.create(); + m_behaviorEntries = HashMultimap.create(); + } + + public TranslationIndex(TranslationIndex other, Translator translator) { + + // translate the superclasses + m_superclasses = Maps.newHashMap(); + for (Map.Entry mapEntry : other.m_superclasses.entrySet()) { + m_superclasses.put( + translator.translateEntry(mapEntry.getKey()), + translator.translateEntry(mapEntry.getValue()) + ); + } + + // translate the fields + m_fieldEntries = HashMultimap.create(); + for (Map.Entry mapEntry : other.m_fieldEntries.entries()) { + m_fieldEntries.put( + translator.translateEntry(mapEntry.getKey()), + translator.translateEntry(mapEntry.getValue()) + ); + } + + m_behaviorEntries = HashMultimap.create(); + for (Map.Entry mapEntry : other.m_behaviorEntries.entries()) { + m_behaviorEntries.put( + translator.translateEntry(mapEntry.getKey()), + translator.translateEntry(mapEntry.getValue()) + ); + } + } + + public void indexClass(CtClass c) { + + ClassEntry classEntry = JavassistUtil.getClassEntry(c); + + // add the superclass + ClassEntry superclassEntry = JavassistUtil.getSuperclassEntry(c); + if (!isJre(classEntry) && superclassEntry != null && !isJre(superclassEntry)) { + m_superclasses.put(classEntry, superclassEntry); + } + + // add fields + for (CtField field : c.getDeclaredFields()) { + FieldEntry fieldEntry = JavassistUtil.getFieldEntry(field); + m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry); + } + + // add behaviors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior); + m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); + } + } + + public void renameClasses(Map renames) { + EntryRenamer.renameClassesInMap(renames, m_superclasses); + EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries); + EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries); + } + + public ClassEntry getSuperclass(ClassEntry classEntry) { + return m_superclasses.get(classEntry); + } + + public List getAncestry(ClassEntry classEntry) { + List ancestors = Lists.newArrayList(); + while (classEntry != null) { + classEntry = getSuperclass(classEntry); + if (classEntry != null) { + ancestors.add(classEntry); + } + } + return ancestors; + } + + public List getSubclass(ClassEntry classEntry) { + // linear search is fast enough for now + List subclasses = Lists.newArrayList(); + for (Map.Entry entry : m_superclasses.entrySet()) { + ClassEntry subclass = entry.getKey(); + ClassEntry superclass = entry.getValue(); + if (classEntry.equals(superclass)) { + subclasses.add(subclass); + } + } + return subclasses; + } + + public void getSubclassesRecursively(Set out, ClassEntry classEntry) { + for (ClassEntry subclassEntry : getSubclass(classEntry)) { + out.add(subclassEntry); + getSubclassesRecursively(out, subclassEntry); + } + } + + public void getSubclassNamesRecursively(Set out, ClassEntry classEntry) { + for (ClassEntry subclassEntry : getSubclass(classEntry)) { + out.add(subclassEntry.getName()); + getSubclassNamesRecursively(out, subclassEntry); + } + } + + public boolean entryExists(Entry entry) { + if (entry instanceof FieldEntry) { + return fieldExists((FieldEntry)entry); + } else if (entry instanceof BehaviorEntry) { + return behaviorExists((BehaviorEntry)entry); + } else if (entry instanceof ArgumentEntry) { + return behaviorExists(((ArgumentEntry)entry).getBehaviorEntry()); + } + throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); + } + + public boolean fieldExists(FieldEntry fieldEntry) { + return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); + } + + public boolean behaviorExists(BehaviorEntry behaviorEntry) { + return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); + } + + public ClassEntry resolveEntryClass(Entry entry) { + + if (entry instanceof ClassEntry) { + return (ClassEntry)entry; + } + + // this entry could refer to a method on a class where the method is not actually implemented + // travel up the inheritance tree to find the closest implementation + while (!entryExists(entry)) { + + // is there a parent class? + ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); + if (superclassEntry == null) { + // this is probably a method from a class in a library + // we can't trace the implementation up any higher unless we index the library + return null; + } + + // move up to the parent class + entry = entry.cloneToNewClass(superclassEntry); + } + return entry.getClassEntry(); + } + + private boolean isJre(ClassEntry classEntry) { + String packageName = classEntry.getPackageName(); + return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax")); + } + + public void write(OutputStream out) + throws IOException { + GZIPOutputStream gzipout = new GZIPOutputStream(out); + ObjectOutputStream oout = new ObjectOutputStream(gzipout); + oout.writeObject(m_superclasses); + oout.writeObject(m_fieldEntries); + oout.writeObject(m_behaviorEntries); + gzipout.finish(); + } + + @SuppressWarnings("unchecked") + public void read(InputStream in) + throws IOException { + try { + ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in)); + m_superclasses = (HashMap)oin.readObject(); + m_fieldEntries = (HashMultimap)oin.readObject(); + m_behaviorEntries = (HashMultimap)oin.readObject(); + } catch (ClassNotFoundException ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java new file mode 100644 index 0000000..23f8089 --- /dev/null +++ b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java @@ -0,0 +1,512 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; + +import com.strobel.componentmodel.Key; +import com.strobel.decompiler.languages.java.ast.Annotation; +import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; +import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; +import com.strobel.decompiler.languages.java.ast.ArraySpecifier; +import com.strobel.decompiler.languages.java.ast.AssertStatement; +import com.strobel.decompiler.languages.java.ast.AssignmentExpression; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression; +import com.strobel.decompiler.languages.java.ast.BlockStatement; +import com.strobel.decompiler.languages.java.ast.BreakStatement; +import com.strobel.decompiler.languages.java.ast.CaseLabel; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.CatchClause; +import com.strobel.decompiler.languages.java.ast.ClassOfExpression; +import com.strobel.decompiler.languages.java.ast.Comment; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; +import com.strobel.decompiler.languages.java.ast.ComposedType; +import com.strobel.decompiler.languages.java.ast.ConditionalExpression; +import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; +import com.strobel.decompiler.languages.java.ast.ContinueStatement; +import com.strobel.decompiler.languages.java.ast.DoWhileStatement; +import com.strobel.decompiler.languages.java.ast.EmptyStatement; +import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration; +import com.strobel.decompiler.languages.java.ast.ExpressionStatement; +import com.strobel.decompiler.languages.java.ast.FieldDeclaration; +import com.strobel.decompiler.languages.java.ast.ForEachStatement; +import com.strobel.decompiler.languages.java.ast.ForStatement; +import com.strobel.decompiler.languages.java.ast.GotoStatement; +import com.strobel.decompiler.languages.java.ast.IAstVisitor; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.IdentifierExpression; +import com.strobel.decompiler.languages.java.ast.IfElseStatement; +import com.strobel.decompiler.languages.java.ast.ImportDeclaration; +import com.strobel.decompiler.languages.java.ast.IndexerExpression; +import com.strobel.decompiler.languages.java.ast.InstanceInitializer; +import com.strobel.decompiler.languages.java.ast.InstanceOfExpression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.JavaTokenNode; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.LabelStatement; +import com.strobel.decompiler.languages.java.ast.LabeledStatement; +import com.strobel.decompiler.languages.java.ast.LambdaExpression; +import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.MethodDeclaration; +import com.strobel.decompiler.languages.java.ast.MethodGroupExpression; +import com.strobel.decompiler.languages.java.ast.NewLineNode; +import com.strobel.decompiler.languages.java.ast.NullReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.PackageDeclaration; +import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; +import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression; +import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; +import com.strobel.decompiler.languages.java.ast.ReturnStatement; +import com.strobel.decompiler.languages.java.ast.SimpleType; +import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; +import com.strobel.decompiler.languages.java.ast.SwitchSection; +import com.strobel.decompiler.languages.java.ast.SwitchStatement; +import com.strobel.decompiler.languages.java.ast.SynchronizedStatement; +import com.strobel.decompiler.languages.java.ast.TextNode; +import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ThrowStatement; +import com.strobel.decompiler.languages.java.ast.TryCatchStatement; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration; +import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; +import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression; +import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement; +import com.strobel.decompiler.languages.java.ast.VariableInitializer; +import com.strobel.decompiler.languages.java.ast.WhileStatement; +import com.strobel.decompiler.languages.java.ast.WildcardType; +import com.strobel.decompiler.patterns.Pattern; + +public class TreeDumpVisitor implements IAstVisitor { + + private File m_file; + private Writer m_out; + + public TreeDumpVisitor(File file) { + m_file = file; + m_out = null; + } + + @Override + public Void visitCompilationUnit(CompilationUnit node, Void ignored) { + try { + m_out = new FileWriter(m_file); + recurse(node, ignored); + m_out.close(); + return null; + } catch (IOException ex) { + throw new Error(ex); + } + } + + private Void recurse(AstNode node, Void ignored) { + // show the tree + try { + m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n"); + } catch (IOException ex) { + throw new Error(ex); + } + + // recurse + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, ignored); + } + return null; + } + + private String getText(AstNode node) { + if (node instanceof Identifier) { + return "\"" + ((Identifier)node).getName() + "\""; + } + return ""; + } + + private String dumpUserData(AstNode node) { + StringBuilder buf = new StringBuilder(); + for (Key key : Keys.ALL_KEYS) { + Object val = node.getUserData(key); + if (val != null) { + buf.append(String.format(" [%s=%s]", key, val)); + } + } + return buf.toString(); + } + + private String getIndent(AstNode node) { + StringBuilder buf = new StringBuilder(); + int depth = getDepth(node); + for (int i = 0; i < depth; i++) { + buf.append("\t"); + } + return buf.toString(); + } + + private int getDepth(AstNode node) { + int depth = -1; + while (node != null) { + depth++; + node = node.getParent(); + } + return depth; + } + + // OVERRIDES WE DON'T CARE ABOUT + + @Override + public Void visitInvocationExpression(InvocationExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSimpleType(SimpleType node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitComment(Comment node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitTypeReference(TypeReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitIdentifier(Identifier node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitClassOfExpression(ClassOfExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitBlockStatement(BlockStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitExpressionStatement(ExpressionStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitBreakStatement(BreakStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitContinueStatement(ContinueStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitEmptyStatement(EmptyStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitIfElseStatement(IfElseStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitLabelStatement(LabelStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitLabeledStatement(LabeledStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitReturnStatement(ReturnStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSwitchStatement(SwitchStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSwitchSection(SwitchSection node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitCaseLabel(CaseLabel node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitThrowStatement(ThrowStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitCatchClause(CatchClause node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitAnnotation(Annotation node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitNewLine(NewLineNode node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitVariableInitializer(VariableInitializer node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitText(TextNode node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitImportDeclaration(ImportDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitInitializerBlock(InstanceInitializer node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitArraySpecifier(ArraySpecifier node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitComposedType(ComposedType node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitWhileStatement(WhileStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitCastExpression(CastExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitIndexerExpression(IndexerExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitConditionalExpression(ConditionalExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitForStatement(ForStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitForEachStatement(ForEachStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitGotoStatement(GotoStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitWildcardType(WildcardType node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitAssertStatement(AssertStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitLambdaExpression(LambdaExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) { + return recurse(node, ignored); + } +} diff --git a/src/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/cuchaz/enigma/bytecode/CheckCastIterator.java new file mode 100644 index 0000000..5284557 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/CheckCastIterator.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode; + +import java.util.Iterator; + +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.Opcode; +import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Signature; + +public class CheckCastIterator implements Iterator { + + public static class CheckCast { + + public String className; + public MethodEntry prevMethodEntry; + + public CheckCast(String className, MethodEntry prevMethodEntry) { + this.className = className; + this.prevMethodEntry = prevMethodEntry; + } + } + + private ConstPool m_constants; + private CodeAttribute m_attribute; + private CodeIterator m_iter; + private CheckCast m_next; + + public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode { + m_constants = codeAttribute.getConstPool(); + m_attribute = codeAttribute; + m_iter = m_attribute.iterator(); + + m_next = getNext(); + } + + @Override + public boolean hasNext() { + return m_next != null; + } + + @Override + public CheckCast next() { + CheckCast out = m_next; + try { + m_next = getNext(); + } catch (BadBytecode ex) { + throw new Error(ex); + } + return out; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private CheckCast getNext() throws BadBytecode { + int prevPos = 0; + while (m_iter.hasNext()) { + int pos = m_iter.next(); + int opcode = m_iter.byteAt(pos); + switch (opcode) { + case Opcode.CHECKCAST: + + // get the type of this op code (next two bytes are a classinfo index) + MethodEntry prevMethodEntry = getMethodEntry(prevPos); + if (prevMethodEntry != null) { + return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry); + } + break; + } + prevPos = pos; + } + return null; + } + + private MethodEntry getMethodEntry(int pos) { + switch (m_iter.byteAt(pos)) { + case Opcode.INVOKEVIRTUAL: + case Opcode.INVOKESTATIC: + case Opcode.INVOKEDYNAMIC: + case Opcode.INVOKESPECIAL: { + int index = m_iter.s16bitAt(pos + 1); + return new MethodEntry( + new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))), + m_constants.getMethodrefName(index), + new Signature(m_constants.getMethodrefType(index)) + ); + } + + case Opcode.INVOKEINTERFACE: { + int index = m_iter.s16bitAt(pos + 1); + return new MethodEntry( + new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))), + m_constants.getInterfaceMethodrefName(index), + new Signature(m_constants.getInterfaceMethodrefType(index)) + ); + } + } + return null; + } + + public Iterable casts() { + return new Iterable() { + @Override + public Iterator iterator() { + return CheckCastIterator.this; + } + }; + } +} diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java new file mode 100644 index 0000000..a5fea92 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode; + +import java.util.Map; +import java.util.Set; + +import javassist.ClassMap; +import javassist.CtClass; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.InnerClassesAttribute; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; + +public class ClassRenamer { + + public static void renameClasses(CtClass c, Map map) { + + // build the map used by javassist + ClassMap nameMap = new ClassMap(); + for (Map.Entry entry : map.entrySet()) { + nameMap.put(entry.getKey().getName(), entry.getValue().getName()); + } + + c.replaceClassName(nameMap); + + // replace simple names in the InnerClasses attribute too + ConstPool constants = c.getClassFile().getConstPool(); + InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i = 0; i < attr.tableLength(); i++) { + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i))); + if (attr.innerNameIndex(i) != 0) { + attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnerClassName())); + } + + /* DEBUG + System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); + */ + } + } + } + + public static Set getAllClassEntries(final CtClass c) { + + // get the classes that javassist knows about + final Set entries = Sets.newHashSet(); + ClassMap map = new ClassMap() { + @Override + public Object get(Object obj) { + if (obj instanceof String) { + String str = (String)obj; + + // javassist throws a lot of weird things at this map + // I either have to implement my on class scanner, or just try to filter out the weirdness + // I'm opting to filter out the weirdness for now + + // skip anything with generic arguments + if (str.indexOf('<') >= 0 || str.indexOf('>') >= 0 || str.indexOf(';') >= 0) { + return null; + } + + // convert path/to/class.inner to path/to/class$inner + str = str.replace('.', '$'); + + // remember everything else + entries.add(new ClassEntry(str)); + } + return null; + } + + private static final long serialVersionUID = -202160293602070641L; + }; + c.replaceClassName(map); + + return entries; + } + + public static void moveAllClassesOutOfDefaultPackage(CtClass c, String newPackageName) { + Map map = Maps.newHashMap(); + for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) { + if (classEntry.isInDefaultPackage()) { + map.put(classEntry, new ClassEntry(newPackageName + "/" + classEntry.getName())); + } + } + ClassRenamer.renameClasses(c, map); + } + + public static void moveAllClassesIntoDefaultPackage(CtClass c, String oldPackageName) { + Map map = Maps.newHashMap(); + for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) { + if (classEntry.getPackageName().equals(oldPackageName)) { + map.put(classEntry, new ClassEntry(classEntry.getSimpleName())); + } + } + ClassRenamer.renameClasses(c, map); + } +} diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java new file mode 100644 index 0000000..afd3a77 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode; + +import java.util.Map; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtField; +import javassist.CtMethod; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.SourceFileAttribute; + +import com.google.common.collect.Maps; + +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.BehaviorEntryFactory; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.Type; + +public class ClassTranslator { + + private Translator m_translator; + + public ClassTranslator(Translator translator) { + m_translator = translator; + } + + public void translate(CtClass c) { + + // NOTE: the order of these translations is very important + + // translate all the field and method references in the code by editing the constant pool + ConstPool constants = c.getClassFile().getConstPool(); + ConstPoolEditor editor = new ConstPoolEditor(constants); + for (int i = 1; i < constants.getSize(); i++) { + switch (constants.getTag(i)) { + + case ConstPool.CONST_Fieldref: { + + // translate the name + FieldEntry entry = new FieldEntry( + new ClassEntry(Descriptor.toJvmName(constants.getFieldrefClassName(i))), + constants.getFieldrefName(i) + ); + FieldEntry translatedEntry = m_translator.translateEntry(entry); + + // translate the type + Type type = new Type(constants.getFieldrefType(i)); + Type translatedType = m_translator.translateType(type); + + if (!entry.equals(translatedEntry) || !type.equals(translatedType)) { + editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedType.toString()); + } + } + break; + + case ConstPool.CONST_Methodref: + case ConstPool.CONST_InterfaceMethodref: { + + // translate the name and type + BehaviorEntry entry = BehaviorEntryFactory.create( + Descriptor.toJvmName(editor.getMemberrefClassname(i)), + editor.getMemberrefName(i), + editor.getMemberrefType(i) + ); + BehaviorEntry translatedEntry = m_translator.translateEntry(entry); + + if (!entry.getName().equals(translatedEntry.getName()) || !entry.getSignature().equals(translatedEntry.getSignature())) { + editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString()); + } + } + break; + } + } + + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + + // translate all the fields + for (CtField field : c.getDeclaredFields()) { + + // translate the name + FieldEntry entry = new FieldEntry(classEntry, field.getName()); + String translatedName = m_translator.translate(entry); + if (translatedName != null) { + field.setName(translatedName); + } + + // translate the type + Type translatedType = m_translator.translateType(new Type(field.getFieldInfo().getDescriptor())); + field.getFieldInfo().setDescriptor(translatedType.toString()); + } + + // translate all the methods and constructors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + if (behavior instanceof CtMethod) { + CtMethod method = (CtMethod)behavior; + + // translate the name + MethodEntry entry = JavassistUtil.getMethodEntry(method); + String translatedName = m_translator.translate(entry); + if (translatedName != null) { + method.setName(translatedName); + } + } + + // translate the type + Signature translatedSignature = m_translator.translateSignature(new Signature(behavior.getMethodInfo().getDescriptor())); + behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); + } + + // translate all the class names referenced in the code + // the above code only changed method/field/reference names and types, but not the class names themselves + Map map = Maps.newHashMap(); + for (ClassEntry obfClassEntry : ClassRenamer.getAllClassEntries(c)) { + ClassEntry deobfClassEntry = m_translator.translateEntry(obfClassEntry); + if (!obfClassEntry.equals(deobfClassEntry)) { + map.put(obfClassEntry, deobfClassEntry); + } + } + ClassRenamer.renameClasses(c, map); + + // translate the source file attribute too + ClassEntry deobfClassEntry = map.get(classEntry); + if (deobfClassEntry != null) { + String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOuterClassName()) + ".java"; + c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java new file mode 100644 index 0000000..2dec3b7 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; + +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; +import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; + +public class ConstPoolEditor { + + private static Method m_getItem; + private static Method m_addItem; + private static Method m_addItem0; + private static Field m_items; + private static Field m_cache; + private static Field m_numItems; + private static Field m_objects; + private static Field m_elements; + private static Method m_methodWritePool; + private static Constructor m_constructorPool; + + static { + try { + m_getItem = ConstPool.class.getDeclaredMethod("getItem", int.class); + m_getItem.setAccessible(true); + + m_addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo")); + m_addItem.setAccessible(true); + + m_addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo")); + m_addItem0.setAccessible(true); + + m_items = ConstPool.class.getDeclaredField("items"); + m_items.setAccessible(true); + + m_cache = ConstPool.class.getDeclaredField("itemsCache"); + m_cache.setAccessible(true); + + m_numItems = ConstPool.class.getDeclaredField("numOfItems"); + m_numItems.setAccessible(true); + + m_objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects"); + m_objects.setAccessible(true); + + m_elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements"); + m_elements.setAccessible(true); + + m_methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class); + m_methodWritePool.setAccessible(true); + + m_constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class); + m_constructorPool.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private ConstPool m_pool; + + public ConstPoolEditor(ConstPool pool) { + m_pool = pool; + } + + public void writePool(DataOutputStream out) { + try { + m_methodWritePool.invoke(m_pool, out); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static ConstPool readPool(DataInputStream in) { + try { + return m_constructorPool.newInstance(in); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public String getMemberrefClassname(int memberrefIndex) { + return Descriptor.toJvmName(m_pool.getClassInfo(m_pool.getMemberClass(memberrefIndex))); + } + + public String getMemberrefName(int memberrefIndex) { + return m_pool.getUtf8Info(m_pool.getNameAndTypeName(m_pool.getMemberNameAndType(memberrefIndex))); + } + + public String getMemberrefType(int memberrefIndex) { + return m_pool.getUtf8Info(m_pool.getNameAndTypeDescriptor(m_pool.getMemberNameAndType(memberrefIndex))); + } + + public ConstInfoAccessor getItem(int index) { + try { + Object entry = m_getItem.invoke(m_pool, index); + if (entry == null) { + return null; + } + return new ConstInfoAccessor(entry); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int addItem(Object item) { + try { + return (Integer)m_addItem.invoke(m_pool, item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int addItemForceNew(Object item) { + try { + return (Integer)m_addItem0.invoke(m_pool, item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings("rawtypes") + public void removeLastItem() { + try { + // remove the item from the cache + HashMap cache = getCache(); + if (cache != null) { + Object item = getItem(m_pool.getSize() - 1); + cache.remove(item); + } + + // remove the actual item + // based off of LongVector.addElement() + Object items = m_items.get(m_pool); + Object[][] objects = (Object[][])m_objects.get(items); + int numElements = (Integer)m_elements.get(items) - 1; + int nth = numElements >> 7; + int offset = numElements & (128 - 1); + objects[nth][offset] = null; + + // decrement the number of items + m_elements.set(items, numElements); + m_numItems.set(m_pool, (Integer)m_numItems.get(m_pool) - 1); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings("rawtypes") + public HashMap getCache() { + try { + return (HashMap)m_cache.get(m_pool); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) { + // NOTE: when changing values, we always need to copy-on-write + try { + // get the memberref item + Object item = getItem(memberrefIndex).getItem(); + + // update the cache + HashMap cache = getCache(); + if (cache != null) { + cache.remove(item); + } + + new MemberRefInfoAccessor(item).setNameAndTypeIndex(m_pool.addNameAndTypeInfo(newName, newType)); + + // update the cache + if (cache != null) { + cache.put(item, item); + } + } catch (Exception ex) { + throw new Error(ex); + } + + // make sure the change worked + assert (newName.equals(getMemberrefName(memberrefIndex))); + assert (newType.equals(getMemberrefType(memberrefIndex))); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void changeClassName(int classNameIndex, String newName) { + // NOTE: when changing values, we always need to copy-on-write + try { + // get the class item + Object item = getItem(classNameIndex).getItem(); + + // update the cache + HashMap cache = getCache(); + if (cache != null) { + cache.remove(item); + } + + // add the new name and repoint the name-and-type to it + new ClassInfoAccessor(item).setNameIndex(m_pool.addUtf8Info(newName)); + + // update the cache + if (cache != null) { + cache.put(item, item); + } + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static ConstPool newConstPool() { + // const pool expects the name of a class to initialize itself + // but we want an empty pool + // so give it a bogus name, and then clear the entries afterwards + ConstPool pool = new ConstPool("a"); + + ConstPoolEditor editor = new ConstPoolEditor(pool); + int size = pool.getSize(); + for (int i = 0; i < size - 1; i++) { + editor.removeLastItem(); + } + + // make sure the pool is actually empty + // although, in this case "empty" means one thing in it + // the JVM spec says index 0 should be reserved + assert (pool.getSize() == 1); + assert (editor.getItem(0) == null); + assert (editor.getItem(1) == null); + assert (editor.getItem(2) == null); + assert (editor.getItem(3) == null); + + // also, clear the cache + editor.getCache().clear(); + + return pool; + } + + public String dump() { + StringBuilder buf = new StringBuilder(); + for (int i = 1; i < m_pool.getSize(); i++) { + buf.append(String.format("%4d", i)); + buf.append(" "); + buf.append(getItem(i).toString()); + buf.append("\n"); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/bytecode/InfoType.java b/src/cuchaz/enigma/bytecode/InfoType.java new file mode 100644 index 0000000..deaf623 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/InfoType.java @@ -0,0 +1,317 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; +import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; +import cuchaz.enigma.bytecode.accessors.InvokeDynamicInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MethodHandleInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MethodTypeInfoAccessor; +import cuchaz.enigma.bytecode.accessors.NameAndTypeInfoAccessor; +import cuchaz.enigma.bytecode.accessors.StringInfoAccessor; + +public enum InfoType { + + Utf8Info( 1, 0 ), + IntegerInfo( 3, 0 ), + FloatInfo( 4, 0 ), + LongInfo( 5, 0 ), + DoubleInfo( 6, 0 ), + ClassInfo( 7, 1 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getNameIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag(); + } + }, + StringInfo( 8, 1 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getStringIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + accessor.setStringIndex(remapIndex(map, accessor.getStringIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex()); + return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag(); + } + }, + FieldRefInfo( 9, 2 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getClassIndex()); + gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + accessor.setClassIndex(remapIndex(map, accessor.getClassIndex())); + accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex()); + ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); + return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }, + // same as FieldRefInfo + MethodRefInfo( 10, 2 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + FieldRefInfo.gatherIndexTree(indices, editor, entry); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + FieldRefInfo.remapIndices(map, entry); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + return FieldRefInfo.subIndicesAreValid(entry, pool); + } + }, + // same as FieldRefInfo + InterfaceMethodRefInfo( 11, 2 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + FieldRefInfo.gatherIndexTree(indices, editor, entry); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + FieldRefInfo.remapIndices(map, entry); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + return FieldRefInfo.subIndicesAreValid(entry, pool); + } + }, + NameAndTypeInfo( 12, 1 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getNameIndex()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + MethodHandleInfo( 15, 3 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + gatherIndexTree(indices, editor, accessor.getMethodRefIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex()); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag(); + } + }, + MethodTypeInfo( 16, 1 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + InvokeDynamicInfo( 18, 2 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getBootstrapIndex()); + gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex())); + accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex()); + ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); + return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }; + + private static Map m_types; + + static { + m_types = Maps.newTreeMap(); + for (InfoType type : values()) { + m_types.put(type.getTag(), type); + } + } + + private int m_tag; + private int m_level; + + private InfoType(int tag, int level) { + m_tag = tag; + m_level = level; + } + + public int getTag() { + return m_tag; + } + + public int getLevel() { + return m_level; + } + + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + // by default, do nothing + } + + public void remapIndices(Map map, ConstInfoAccessor entry) { + // by default, do nothing + } + + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + // by default, everything is good + return true; + } + + public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex()); + if (entryCheck == null) { + return false; + } + return entryCheck.getItem().equals(entry.getItem()); + } + + public static InfoType getByTag(int tag) { + return m_types.get(tag); + } + + public static List getByLevel(int level) { + List types = Lists.newArrayList(); + for (InfoType type : values()) { + if (type.getLevel() == level) { + types.add(type); + } + } + return types; + } + + public static List getSortedByLevel() { + List types = Lists.newArrayList(); + types.addAll(getByLevel(0)); + types.addAll(getByLevel(1)); + types.addAll(getByLevel(2)); + types.addAll(getByLevel(3)); + return types; + } + + public static void gatherIndexTree(Collection indices, ConstPoolEditor editor, int index) { + // add own index + indices.add(index); + + // recurse + ConstInfoAccessor entry = editor.getItem(index); + entry.getType().gatherIndexTree(indices, editor, entry); + } + + private static int remapIndex(Map map, int index) { + Integer newIndex = map.get(index); + if (newIndex == null) { + newIndex = index; + } + return newIndex; + } +} diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java new file mode 100644 index 0000000..817500b --- /dev/null +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode; + +import java.util.Collection; + +import javassist.CtClass; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.EnclosingMethodAttribute; +import javassist.bytecode.InnerClassesAttribute; +import cuchaz.enigma.Constants; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; + +public class InnerClassWriter { + + private JarIndex m_jarIndex; + + public InnerClassWriter(JarIndex jarIndex) { + m_jarIndex = jarIndex; + } + + public void write(CtClass c) { + + // is this an inner or outer class? + String obfInnerClassName = new ClassEntry(Descriptor.toJvmName(c.getName())).getSimpleName(); + String obfOuterClassName = m_jarIndex.getOuterClass(obfInnerClassName); + if (obfOuterClassName == null) { + // this is an outer class + obfOuterClassName = Descriptor.toJvmName(c.getName()); + } else { + // this is an inner class, rename it to outer$inner + ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName); + c.setName(obfClassEntry.getName()); + + BehaviorEntry caller = m_jarIndex.getAnonymousClassCaller(obfInnerClassName); + if (caller != null) { + // write the enclosing method attribute + if (caller.getName().equals("")) { + c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName())); + } else { + c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString())); + } + } + } + + // write the inner classes if needed + Collection obfInnerClassNames = m_jarIndex.getInnerClasses(obfOuterClassName); + if (obfInnerClassNames != null && !obfInnerClassNames.isEmpty()) { + writeInnerClasses(c, obfOuterClassName, obfInnerClassNames); + } + } + + private void writeInnerClasses(CtClass c, String obfOuterClassName, Collection obfInnerClassNames) { + InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); + c.getClassFile().addAttribute(attr); + for (String obfInnerClassName : obfInnerClassNames) { + // get the new inner class name + ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName); + + // here's what the JVM spec says about the InnerClasses attribute + // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags ); + + // update the attribute with this inner class + ConstPool constPool = c.getClassFile().getConstPool(); + int innerClassIndex = constPool.addClassInfo(obfClassEntry.getName()); + int outerClassIndex = 0; + int innerClassSimpleNameIndex = 0; + if (!m_jarIndex.isAnonymousClass(obfInnerClassName)) { + outerClassIndex = constPool.addClassInfo(obfClassEntry.getOuterClassName()); + innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getInnerClassName()); + } + + attr.append(innerClassIndex, outerClassIndex, innerClassSimpleNameIndex, c.getClassFile().getAccessFlags() & ~AccessFlag.SUPER); + + /* DEBUG + System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", + obfClassEntry, + attr.outerClass(attr.tableLength() - 1), + attr.innerClass(attr.tableLength() - 1), + attr.innerName(attr.tableLength() - 1), + Constants.NonePackage + "/" + obfInnerClassName, + obfClassEntry.getName() + )); + */ + + // make sure the outer class references only the new inner class names + c.replaceClassName(Constants.NonePackage + "/" + obfInnerClassName, obfClassEntry.getName()); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java new file mode 100644 index 0000000..5d4ca1a --- /dev/null +++ b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode; + +import java.util.ArrayList; +import java.util.List; + +import javassist.CtBehavior; +import javassist.CtClass; +import cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.BehaviorEntryFactory; +import cuchaz.enigma.mapping.Translator; + +public class MethodParameterWriter { + + private Translator m_translator; + + public MethodParameterWriter(Translator translator) { + m_translator = translator; + } + + public void writeMethodArguments(CtClass c) { + + // Procyon will read method arguments from the "MethodParameters" attribute, so write those + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); + + // get the number of arguments + int numParams = behaviorEntry.getSignature().getArgumentTypes().size(); + if (numParams <= 0) { + continue; + } + + // get the list of argument names + List names = new ArrayList(numParams); + for (int i = 0; i < numParams; i++) { + names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, ""))); + } + + // save the mappings to the class + MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java new file mode 100644 index 0000000..bf95956 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javassist.bytecode.AttributeInfo; +import javassist.bytecode.ConstPool; +import javassist.bytecode.MethodInfo; + +public class MethodParametersAttribute extends AttributeInfo { + + private MethodParametersAttribute(ConstPool pool, List parameterNameIndices) { + super(pool, "MethodParameters", writeStruct(parameterNameIndices)); + } + + public static void updateClass(MethodInfo info, List names) { + // add the names to the class const pool + ConstPool constPool = info.getConstPool(); + List parameterNameIndices = new ArrayList(); + for (String name : names) { + if (name != null) { + parameterNameIndices.add(constPool.addUtf8Info(name)); + } else { + parameterNameIndices.add(0); + } + } + + // add the attribute to the method + info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices)); + } + + private static byte[] writeStruct(List parameterNameIndices) { + // JVM 8 Spec says the struct looks like this: + // http://cr.openjdk.java.net/~mr/se/8/java-se-8-fr-spec-01/java-se-8-jvms-fr-diffs.pdf + // uint8 num_params + // for each param: + // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry + // uint16 access_flags -> don't care, just set to 0 + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(buf); + + // NOTE: java hates unsigned integers, so we have to be careful here + // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument + // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte + // if the int is out of range, the byte stream won't look the way we want and weird things will happen + final int SIZEOF_UINT8 = 1; + final int SIZEOF_UINT16 = 2; + final int MAX_UINT8 = (1 << 8) - 1; + final int MAX_UINT16 = (1 << 16) - 1; + + try { + assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8); + out.writeByte(parameterNameIndices.size()); + + for (Integer index : parameterNameIndices) { + assert (index >= 0 && index <= MAX_UINT16); + out.writeShort(index); + + // just write 0 for the access flags + out.writeShort(0); + } + + out.close(); + byte[] data = buf.toByteArray(); + assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16)); + return data; + } catch (IOException ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java new file mode 100644 index 0000000..d76f056 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode.accessors; + +import java.lang.reflect.Field; + +public class ClassInfoAccessor { + + private static Class m_class; + private static Field m_nameIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.ClassInfo"); + m_nameIndex = m_class.getDeclaredField("name"); + m_nameIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public ClassInfoAccessor(Object item) { + m_item = item; + } + + public int getNameIndex() { + try { + return (Integer)m_nameIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameIndex(int val) { + try { + m_nameIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java new file mode 100644 index 0000000..d00c102 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode.accessors; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import cuchaz.enigma.bytecode.InfoType; + +public class ConstInfoAccessor { + + private static Class m_class; + private static Field m_index; + private static Method m_getTag; + + static { + try { + m_class = Class.forName("javassist.bytecode.ConstInfo"); + m_index = m_class.getDeclaredField("index"); + m_index.setAccessible(true); + m_getTag = m_class.getMethod("getTag"); + m_getTag.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private Object m_item; + + public ConstInfoAccessor(Object item) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null!"); + } + m_item = item; + } + + public ConstInfoAccessor(DataInputStream in) throws IOException { + try { + // read the entry + String className = in.readUTF(); + int oldIndex = in.readInt(); + + // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back + // so we have to read it here + in.readByte(); + + Constructor constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class); + constructor.setAccessible(true); + m_item = constructor.newInstance(in, oldIndex); + } catch (IOException ex) { + throw ex; + } catch (Exception ex) { + throw new Error(ex); + } + } + + public Object getItem() { + return m_item; + } + + public int getIndex() { + try { + return (Integer)m_index.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setIndex(int val) { + try { + m_index.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getTag() { + try { + return (Integer)m_getTag.invoke(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public ConstInfoAccessor copy() { + return new ConstInfoAccessor(copyItem()); + } + + public Object copyItem() { + // I don't know of a simpler way to copy one of these silly things... + try { + // serialize the item + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(buf); + write(out); + + // deserialize the item + DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray())); + Object item = new ConstInfoAccessor(in).getItem(); + in.close(); + + return item; + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void write(DataOutputStream out) throws IOException { + try { + out.writeUTF(m_item.getClass().getName()); + out.writeInt(getIndex()); + + Method method = m_item.getClass().getMethod("write", DataOutputStream.class); + method.setAccessible(true); + method.invoke(m_item, out); + } catch (IOException ex) { + throw ex; + } catch (Exception ex) { + throw new Error(ex); + } + } + + @Override + public String toString() { + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + PrintWriter out = new PrintWriter(buf); + Method print = m_item.getClass().getMethod("print", PrintWriter.class); + print.setAccessible(true); + print.invoke(m_item, out); + out.close(); + return buf.toString().replace("\n", ""); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public InfoType getType() { + return InfoType.getByTag(getTag()); + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java new file mode 100644 index 0000000..0d780ea --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode.accessors; + +import java.lang.reflect.Field; + +public class InvokeDynamicInfoAccessor { + + private static Class m_class; + private static Field m_bootstrapIndex; + private static Field m_nameAndTypeIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.InvokeDynamicInfo"); + m_bootstrapIndex = m_class.getDeclaredField("bootstrap"); + m_bootstrapIndex.setAccessible(true); + m_nameAndTypeIndex = m_class.getDeclaredField("nameAndType"); + m_nameAndTypeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public InvokeDynamicInfoAccessor(Object item) { + m_item = item; + } + + public int getBootstrapIndex() { + try { + return (Integer)m_bootstrapIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setBootstrapIndex(int val) { + try { + m_bootstrapIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getNameAndTypeIndex() { + try { + return (Integer)m_nameAndTypeIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameAndTypeIndex(int val) { + try { + m_nameAndTypeIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java new file mode 100644 index 0000000..9fe945f --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode.accessors; + +import java.lang.reflect.Field; + +public class MemberRefInfoAccessor { + + private static Class m_class; + private static Field m_classIndex; + private static Field m_nameAndTypeIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.MemberrefInfo"); + m_classIndex = m_class.getDeclaredField("classIndex"); + m_classIndex.setAccessible(true); + m_nameAndTypeIndex = m_class.getDeclaredField("nameAndTypeIndex"); + m_nameAndTypeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public MemberRefInfoAccessor(Object item) { + m_item = item; + } + + public int getClassIndex() { + try { + return (Integer)m_classIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setClassIndex(int val) { + try { + m_classIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getNameAndTypeIndex() { + try { + return (Integer)m_nameAndTypeIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameAndTypeIndex(int val) { + try { + m_nameAndTypeIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java new file mode 100644 index 0000000..4c95b22 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode.accessors; + +import java.lang.reflect.Field; + +public class MethodHandleInfoAccessor { + + private static Class m_class; + private static Field m_kindIndex; + private static Field m_indexIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.MethodHandleInfo"); + m_kindIndex = m_class.getDeclaredField("refKind"); + m_kindIndex.setAccessible(true); + m_indexIndex = m_class.getDeclaredField("refIndex"); + m_indexIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public MethodHandleInfoAccessor(Object item) { + m_item = item; + } + + public int getTypeIndex() { + try { + return (Integer)m_kindIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + m_kindIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getMethodRefIndex() { + try { + return (Integer)m_indexIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setMethodRefIndex(int val) { + try { + m_indexIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java new file mode 100644 index 0000000..e151117 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode.accessors; + +import java.lang.reflect.Field; + +public class MethodTypeInfoAccessor { + + private static Class m_class; + private static Field m_descriptorIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.MethodTypeInfo"); + m_descriptorIndex = m_class.getDeclaredField("descriptor"); + m_descriptorIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public MethodTypeInfoAccessor(Object item) { + m_item = item; + } + + public int getTypeIndex() { + try { + return (Integer)m_descriptorIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + m_descriptorIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java new file mode 100644 index 0000000..6e82f3e --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode.accessors; + +import java.lang.reflect.Field; + +public class NameAndTypeInfoAccessor { + + private static Class m_class; + private static Field m_nameIndex; + private static Field m_typeIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.NameAndTypeInfo"); + m_nameIndex = m_class.getDeclaredField("memberName"); + m_nameIndex.setAccessible(true); + m_typeIndex = m_class.getDeclaredField("typeDescriptor"); + m_typeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public NameAndTypeInfoAccessor(Object item) { + m_item = item; + } + + public int getNameIndex() { + try { + return (Integer)m_nameIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameIndex(int val) { + try { + m_nameIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getTypeIndex() { + try { + return (Integer)m_typeIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + m_typeIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java new file mode 100644 index 0000000..6665ffe --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode.accessors; + +import java.lang.reflect.Field; + +public class StringInfoAccessor { + + private static Class m_class; + private static Field m_stringIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.StringInfo"); + m_stringIndex = m_class.getDeclaredField("string"); + m_stringIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public StringInfoAccessor(Object item) { + m_item = item; + } + + public int getStringIndex() { + try { + return (Integer)m_stringIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setStringIndex(int val) { + try { + m_stringIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java new file mode 100644 index 0000000..2abf60b --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode.accessors; + +public class Utf8InfoAccessor { + + private static Class m_class; + + static { + try { + m_class = Class.forName("javassist.bytecode.Utf8Info"); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } +} diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java new file mode 100644 index 0000000..1be6123 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -0,0 +1,411 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.convert; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +import javassist.CannotCompileException; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtField; +import javassist.CtMethod; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.Opcode; +import javassist.expr.ConstructorCall; +import javassist.expr.ExprEditor; +import javassist.expr.FieldAccess; +import javassist.expr.MethodCall; +import javassist.expr.NewExpr; + +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multiset; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.Util; +import cuchaz.enigma.analysis.ClassImplementationsTreeNode; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.bytecode.ConstPoolEditor; +import cuchaz.enigma.bytecode.InfoType; +import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; +import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.Signature; + +public class ClassIdentity { + + private ClassEntry m_classEntry; + private SidedClassNamer m_namer; + private Multiset m_fields; + private Multiset m_methods; + private Multiset m_constructors; + private String m_staticInitializer; + private String m_extends; + private Multiset m_implements; + private Multiset m_implementations; + private Multiset m_references; + + public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { + m_namer = namer; + + // stuff from the bytecode + + m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + m_fields = HashMultiset.create(); + for (CtField field : c.getDeclaredFields()) { + m_fields.add(scrubSignature(field.getSignature())); + } + m_methods = HashMultiset.create(); + for (CtMethod method : c.getDeclaredMethods()) { + m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method)); + } + m_constructors = HashMultiset.create(); + for (CtConstructor constructor : c.getDeclaredConstructors()) { + m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor)); + } + m_staticInitializer = ""; + if (c.getClassInitializer() != null) { + m_staticInitializer = getBehaviorSignature(c.getClassInitializer()); + } + m_extends = ""; + if (c.getClassFile().getSuperclass() != null) { + m_extends = scrubClassName(c.getClassFile().getSuperclass()); + } + m_implements = HashMultiset.create(); + for (String interfaceName : c.getClassFile().getInterfaces()) { + m_implements.add(scrubClassName(interfaceName)); + } + + // stuff from the jar index + + m_implementations = HashMultiset.create(); + ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry); + if (implementationsNode != null) { + @SuppressWarnings("unchecked") + Enumeration implementations = implementationsNode.children(); + while (implementations.hasMoreElements()) { + ClassImplementationsTreeNode node = implementations.nextElement(); + m_implementations.add(scrubClassName(node.getClassEntry().getName())); + } + } + + m_references = HashMultiset.create(); + if (useReferences) { + for (CtField field : c.getDeclaredFields()) { + FieldEntry fieldEntry = new FieldEntry(m_classEntry, field.getName()); + for (EntryReference reference : index.getFieldReferences(fieldEntry)) { + addReference(reference); + } + } + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior); + for (EntryReference reference : index.getBehaviorReferences(behaviorEntry)) { + addReference(reference); + } + } + } + } + + private void addReference(EntryReference reference) { + if (reference.context.getSignature() != null) { + m_references.add(String.format("%s_%s", scrubClassName(reference.context.getClassName()), scrubSignature(reference.context.getSignature()))); + } else { + m_references.add(String.format("%s_", scrubClassName(reference.context.getClassName()))); + } + } + + public ClassEntry getClassEntry() { + return m_classEntry; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("class: "); + buf.append(m_classEntry.getName()); + buf.append(" "); + buf.append(hashCode()); + buf.append("\n"); + for (String field : m_fields) { + buf.append("\tfield "); + buf.append(field); + buf.append("\n"); + } + for (String method : m_methods) { + buf.append("\tmethod "); + buf.append(method); + buf.append("\n"); + } + for (String constructor : m_constructors) { + buf.append("\tconstructor "); + buf.append(constructor); + buf.append("\n"); + } + if (m_staticInitializer.length() > 0) { + buf.append("\tinitializer "); + buf.append(m_staticInitializer); + buf.append("\n"); + } + if (m_extends.length() > 0) { + buf.append("\textends "); + buf.append(m_extends); + buf.append("\n"); + } + for (String interfaceName : m_implements) { + buf.append("\timplements "); + buf.append(interfaceName); + buf.append("\n"); + } + for (String implementation : m_implementations) { + buf.append("\timplemented by "); + buf.append(implementation); + buf.append("\n"); + } + for (String reference : m_references) { + buf.append("\treference "); + buf.append(reference); + buf.append("\n"); + } + return buf.toString(); + } + + private String scrubClassName(String className) { + return scrubSignature("L" + Descriptor.toJvmName(className) + ";"); + } + + private String scrubSignature(String signature) { + return scrubSignature(new Signature(signature)); + } + + private String scrubSignature(Signature signature) { + + return new Signature(signature, new ClassNameReplacer() { + + private Map m_classNames = Maps.newHashMap(); + + @Override + public String replace(String className) { + + // classes not in the none package can be passed through + ClassEntry classEntry = new ClassEntry(className); + if (!classEntry.getPackageName().equals(Constants.NonePackage)) { + return className; + } + + // is this class ourself? + if (className.equals(m_classEntry.getName())) { + return "CSelf"; + } + + // try the namer + if (m_namer != null) { + String newName = m_namer.getName(className); + if (newName != null) { + return newName; + } + } + + // otherwise, use local naming + if (!m_classNames.containsKey(className)) { + m_classNames.put(className, getNewClassName()); + } + return m_classNames.get(className); + } + + private String getNewClassName() { + return String.format("C%03d", m_classNames.size()); + } + }).toString(); + } + + private boolean isClassMatchedUniquely(String className) { + return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null; + } + + private String getBehaviorSignature(CtBehavior behavior) { + try { + // does this method have an implementation? + if (behavior.getMethodInfo().getCodeAttribute() == null) { + return "(none)"; + } + + // compute the hash from the opcodes + ConstPool constants = behavior.getMethodInfo().getConstPool(); + final MessageDigest digest = MessageDigest.getInstance("MD5"); + CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); + while (iter.hasNext()) { + int pos = iter.next(); + + // update the hash with the opcode + int opcode = iter.byteAt(pos); + digest.update((byte)opcode); + + switch (opcode) { + case Opcode.LDC: { + int constIndex = iter.byteAt(pos + 1); + updateHashWithConstant(digest, constants, constIndex); + } + break; + + case Opcode.LDC_W: + case Opcode.LDC2_W: { + int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2); + updateHashWithConstant(digest, constants, constIndex); + } + break; + } + } + + // update hash with method and field accesses + behavior.instrument(new ExprEditor() { + @Override + public void edit(MethodCall call) { + updateHashWithString(digest, scrubClassName(call.getClassName())); + updateHashWithString(digest, scrubSignature(call.getSignature())); + if (isClassMatchedUniquely(call.getClassName())) { + updateHashWithString(digest, call.getMethodName()); + } + } + + @Override + public void edit(FieldAccess access) { + updateHashWithString(digest, scrubClassName(access.getClassName())); + updateHashWithString(digest, scrubSignature(access.getSignature())); + if (isClassMatchedUniquely(access.getClassName())) { + updateHashWithString(digest, access.getFieldName()); + } + } + + @Override + public void edit(ConstructorCall call) { + updateHashWithString(digest, scrubClassName(call.getClassName())); + updateHashWithString(digest, scrubSignature(call.getSignature())); + } + + @Override + public void edit(NewExpr expr) { + updateHashWithString(digest, scrubClassName(expr.getClassName())); + } + }); + + // convert the hash to a hex string + return toHex(digest.digest()); + } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) { + throw new Error(ex); + } + } + + private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) { + ConstPoolEditor editor = new ConstPoolEditor(constants); + ConstInfoAccessor item = editor.getItem(index); + if (item.getType() == InfoType.StringInfo) { + updateHashWithString(digest, constants.getStringInfo(index)); + } + // TODO: other constants + } + + private void updateHashWithString(MessageDigest digest, String val) { + try { + digest.update(val.getBytes("UTF8")); + } catch (UnsupportedEncodingException ex) { + throw new Error(ex); + } + } + + private String toHex(byte[] bytes) { + // function taken from: + // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java + final char[] hexArray = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ClassIdentity) { + return equals((ClassIdentity)other); + } + return false; + } + + public boolean equals(ClassIdentity other) { + return m_fields.equals(other.m_fields) + && m_methods.equals(other.m_methods) + && m_constructors.equals(other.m_constructors) + && m_staticInitializer.equals(other.m_staticInitializer) + && m_extends.equals(other.m_extends) + && m_implements.equals(other.m_implements) + && m_implementations.equals(other.m_implementations) + && m_references.equals(other.m_references); + } + + @Override + public int hashCode() { + List objs = Lists.newArrayList(); + objs.addAll(m_fields); + objs.addAll(m_methods); + objs.addAll(m_constructors); + objs.add(m_staticInitializer); + objs.add(m_extends); + objs.addAll(m_implements); + objs.addAll(m_implementations); + objs.addAll(m_references); + return Util.combineHashesOrdered(objs); + } + + public int getMatchScore(ClassIdentity other) { + return getNumMatches(m_fields, other.m_fields) + + getNumMatches(m_methods, other.m_methods) + + getNumMatches(m_constructors, other.m_constructors); + } + + public int getMaxMatchScore() { + return m_fields.size() + m_methods.size() + m_constructors.size(); + } + + public boolean matches(CtClass c) { + // just compare declaration counts + return m_fields.size() == c.getDeclaredFields().length + && m_methods.size() == c.getDeclaredMethods().length + && m_constructors.size() == c.getDeclaredConstructors().length; + } + + private int getNumMatches(Multiset a, Multiset b) { + int numMatches = 0; + for (String val : a) { + if (b.contains(val)) { + numMatches++; + } + } + return numMatches; + } +} diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java new file mode 100644 index 0000000..ccf6b78 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassMatcher.java @@ -0,0 +1,406 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.convert; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarFile; + +import javassist.CtBehavior; +import javassist.CtClass; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +import cuchaz.enigma.TranslatingTypeLoader; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassMapping; +import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.MappingParseException; +import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsReader; +import cuchaz.enigma.mapping.MappingsWriter; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.MethodMapping; + +public class ClassMatcher { + + public static void main(String[] args) throws IOException, MappingParseException { + // TEMP + JarFile sourceJar = new JarFile(new File("input/1.8-pre3.jar")); + JarFile destJar = new JarFile(new File("input/1.8.jar")); + File inMappingsFile = new File("../Enigma Mappings/1.8-pre3.mappings"); + File outMappingsFile = new File("../Enigma Mappings/1.8.mappings"); + + // define a matching to use when the automated system cannot find a match + Map fallbackMatching = Maps.newHashMap(); + fallbackMatching.put("none/ayb", "none/ayf"); + fallbackMatching.put("none/ayd", "none/ayd"); + fallbackMatching.put("none/bgk", "unknown/bgk"); + + // do the conversion + Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); + convertMappings(sourceJar, destJar, mappings, fallbackMatching); + + // write out the converted mappings + FileWriter writer = new FileWriter(outMappingsFile); + new MappingsWriter().write(writer, mappings); + writer.close(); + System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); + } + + private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map fallbackMatching) { + // index jars + System.out.println("Indexing source jar..."); + JarIndex sourceIndex = new JarIndex(); + sourceIndex.indexJar(sourceJar, false); + System.out.println("Indexing dest jar..."); + JarIndex destIndex = new JarIndex(); + destIndex.indexJar(destJar, false); + TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader(sourceJar, sourceIndex); + TranslatingTypeLoader destLoader = new TranslatingTypeLoader(destJar, destIndex); + + // compute the matching + ClassMatching matching = computeMatching(sourceIndex, sourceLoader, destIndex, destLoader); + Map>> matchingIndex = matching.getIndex(); + + // get all the obf class names used in the mappings + Set usedClassNames = mappings.getAllObfClassNames(); + Set allClassNames = Sets.newHashSet(); + for (ClassEntry classEntry : sourceIndex.getObfClassEntries()) { + allClassNames.add(classEntry.getName()); + } + usedClassNames.retainAll(allClassNames); + System.out.println("Used " + usedClassNames.size() + " classes in the mappings"); + + // probabilistically match the non-uniquely-matched source classes + for (Map.Entry> entry : matchingIndex.values()) { + ClassIdentity sourceClass = entry.getKey(); + List destClasses = entry.getValue(); + + // skip classes that are uniquely matched + if (destClasses.size() == 1) { + continue; + } + + // skip classes that aren't used in the mappings + if (!usedClassNames.contains(sourceClass.getClassEntry().getName())) { + continue; + } + + System.out.println("No exact match for source class " + sourceClass.getClassEntry()); + + // find the closest classes + Multimap scoredMatches = ArrayListMultimap.create(); + for (ClassIdentity c : destClasses) { + scoredMatches.put(sourceClass.getMatchScore(c), c); + } + List scores = new ArrayList(scoredMatches.keySet()); + Collections.sort(scores, Collections.reverseOrder()); + printScoredMatches(sourceClass.getMaxMatchScore(), scores, scoredMatches); + + // does the best match have a non-zero score and the same name? + int bestScore = scores.get(0); + Collection bestMatches = scoredMatches.get(bestScore); + if (bestScore > 0 && bestMatches.size() == 1) { + ClassIdentity bestMatch = bestMatches.iterator().next(); + if (bestMatch.getClassEntry().equals(sourceClass.getClassEntry())) { + // use it + System.out.println("\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName()); + destClasses.clear(); + destClasses.add(bestMatch); + } + } + } + + // group the matching into unique and non-unique matches + BiMap matchedClassNames = HashBiMap.create(); + Set unmatchedSourceClassNames = Sets.newHashSet(); + for (String className : usedClassNames) { + // is there a match for this class? + Map.Entry> entry = matchingIndex.get(className); + ClassIdentity sourceClass = entry.getKey(); + List matches = entry.getValue(); + + if (matches.size() == 1) { + // unique match! We're good to go! + matchedClassNames.put(sourceClass.getClassEntry().getName(), matches.get(0).getClassEntry().getName()); + } else { + // no match, check the fallback matching + String fallbackMatch = fallbackMatching.get(className); + if (fallbackMatch != null) { + matchedClassNames.put(sourceClass.getClassEntry().getName(), fallbackMatch); + } else { + unmatchedSourceClassNames.add(className); + } + } + } + + // report unmatched classes + if (!unmatchedSourceClassNames.isEmpty()) { + System.err.println("ERROR: there were unmatched classes!"); + for (String className : unmatchedSourceClassNames) { + System.err.println("\t" + className); + } + return; + } + + // get the class name changes from the matched class names + Map classChanges = Maps.newHashMap(); + for (Map.Entry entry : matchedClassNames.entrySet()) { + if (!entry.getKey().equals(entry.getValue())) { + classChanges.put(entry.getKey(), entry.getValue()); + System.out.println(String.format("Class change: %s -> %s", entry.getKey(), entry.getValue())); + /* DEBUG + System.out.println(String.format("\n%s\n%s", + new ClassIdentity(sourceLoader.loadClass(entry.getKey()), null, sourceIndex, false, false), + new ClassIdentity( destLoader.loadClass(entry.getValue()), null, destIndex, false, false) + )); + */ + } + } + + // sort the changes so classes are renamed in the correct order + // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b + LinkedHashMap orderedClassChanges = Maps.newLinkedHashMap(); + int numChangesLeft = classChanges.size(); + while (!classChanges.isEmpty()) { + Iterator> iter = classChanges.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + if (classChanges.get(entry.getValue()) == null) { + orderedClassChanges.put(entry.getKey(), entry.getValue()); + iter.remove(); + } + } + + // did we remove any changes? + if (numChangesLeft - classChanges.size() > 0) { + // keep going + numChangesLeft = classChanges.size(); + } else { + // can't sort anymore. There must be a loop + break; + } + } + if (classChanges.size() > 0) { + throw new Error(String.format("Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size())); + } + + // convert the mappings in the correct class order + for (Map.Entry entry : orderedClassChanges.entrySet()) { + mappings.renameObfClass(entry.getKey(), entry.getValue()); + } + + // check the method matches + System.out.println("Checking methods..."); + for (ClassMapping classMapping : mappings.classes()) { + ClassEntry classEntry = new ClassEntry(classMapping.getObfName()); + for (MethodMapping methodMapping : classMapping.methods()) { + + // skip constructors + if (methodMapping.getObfName().equals("")) { + continue; + } + + MethodEntry methodEntry = new MethodEntry( + classEntry, + methodMapping.getObfName(), + methodMapping.getObfSignature() + ); + if (!destIndex.containsObfBehavior(methodEntry)) { + System.err.println("WARNING: method doesn't match: " + methodEntry); + + // show the available methods + System.err.println("\tAvailable dest methods:"); + CtClass c = destLoader.loadClass(classMapping.getObfName()); + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior)); + } + + System.err.println("\tAvailable source methods:"); + c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfName())); + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior)); + } + } + } + } + + System.out.println("Done!"); + } + + public static ClassMatching computeMatching(JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader) { + + System.out.println("Matching classes..."); + + ClassMatching matching = null; + for (boolean useReferences : Arrays.asList(false, true)) { + int numMatches = 0; + do { + SidedClassNamer sourceNamer = null; + SidedClassNamer destNamer = null; + if (matching != null) { + // build a class namer + ClassNamer namer = new ClassNamer(matching.getUniqueMatches()); + sourceNamer = namer.getSourceNamer(); + destNamer = namer.getDestNamer(); + + // note the number of matches + numMatches = matching.getUniqueMatches().size(); + } + + // get the entries left to match + Set sourceClassEntries = Sets.newHashSet(); + Set destClassEntries = Sets.newHashSet(); + if (matching == null) { + sourceClassEntries.addAll(sourceIndex.getObfClassEntries()); + destClassEntries.addAll(destIndex.getObfClassEntries()); + matching = new ClassMatching(); + } else { + for (Map.Entry,List> entry : matching.getAmbiguousMatches().entrySet()) { + for (ClassIdentity c : entry.getKey()) { + sourceClassEntries.add(c.getClassEntry()); + matching.removeSource(c); + } + for (ClassIdentity c : entry.getValue()) { + destClassEntries.add(c.getClassEntry()); + matching.removeDest(c); + } + } + for (ClassIdentity c : matching.getUnmatchedSourceClasses()) { + sourceClassEntries.add(c.getClassEntry()); + matching.removeSource(c); + } + for (ClassIdentity c : matching.getUnmatchedDestClasses()) { + destClassEntries.add(c.getClassEntry()); + matching.removeDest(c); + } + } + + // compute a matching for the classes + for (ClassEntry classEntry : sourceClassEntries) { + CtClass c = sourceLoader.loadClass(classEntry.getName()); + ClassIdentity sourceClass = new ClassIdentity(c, sourceNamer, sourceIndex, useReferences); + matching.addSource(sourceClass); + } + for (ClassEntry classEntry : destClassEntries) { + CtClass c = destLoader.loadClass(classEntry.getName()); + ClassIdentity destClass = new ClassIdentity(c, destNamer, destIndex, useReferences); + matching.matchDestClass(destClass); + } + + // TEMP + System.out.println(matching); + } while (matching.getUniqueMatches().size() - numMatches > 0); + } + + // check the class matches + System.out.println("Checking class matches..."); + ClassNamer namer = new ClassNamer(matching.getUniqueMatches()); + SidedClassNamer sourceNamer = namer.getSourceNamer(); + SidedClassNamer destNamer = namer.getDestNamer(); + for (Map.Entry entry : matching.getUniqueMatches().entrySet()) { + + // check source + ClassIdentity sourceClass = entry.getKey(); + CtClass sourceC = sourceLoader.loadClass(sourceClass.getClassEntry().getName()); + assert (sourceC != null) : "Unable to load source class " + sourceClass.getClassEntry(); + assert (sourceClass.matches(sourceC)) : "Source " + sourceClass + " doesn't match " + new ClassIdentity(sourceC, sourceNamer, sourceIndex, false); + + // check dest + ClassIdentity destClass = entry.getValue(); + CtClass destC = destLoader.loadClass(destClass.getClassEntry().getName()); + assert (destC != null) : "Unable to load dest class " + destClass.getClassEntry(); + assert (destClass.matches(destC)) : "Dest " + destClass + " doesn't match " + new ClassIdentity(destC, destNamer, destIndex, false); + } + + // warn about the ambiguous matchings + List,List>> ambiguousMatches = new ArrayList,List>>(matching.getAmbiguousMatches().entrySet()); + Collections.sort(ambiguousMatches, new Comparator,List>>() { + @Override + public int compare(Map.Entry,List> a, Map.Entry,List> b) { + String aName = a.getKey().get(0).getClassEntry().getName(); + String bName = b.getKey().get(0).getClassEntry().getName(); + return aName.compareTo(bName); + } + }); + for (Map.Entry,List> entry : ambiguousMatches) { + System.out.println("Ambiguous matching:"); + System.out.println("\tSource: " + getClassNames(entry.getKey())); + System.out.println("\tDest: " + getClassNames(entry.getValue())); + } + + /* DEBUG + Map.Entry,List> entry = ambiguousMatches.get( 7 ); + for (ClassIdentity c : entry.getKey()) { + System.out.println(c); + } + for(ClassIdentity c : entry.getKey()) { + System.out.println(decompile(sourceLoader, c.getClassEntry())); + } + */ + + return matching; + } + + private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { + int numScoredMatchesShown = 0; + for (int score : scores) { + for (ClassIdentity scoredMatch : scoredMatches.get(score)) { + System.out.println(String.format("\tScore: %3d %3.0f%% %s", score, 100.0 * score / maxScore, scoredMatch.getClassEntry().getName())); + if (numScoredMatchesShown++ > 10) { + return; + } + } + } + } + + private static List getClassNames(Collection classes) { + List out = Lists.newArrayList(); + for (ClassIdentity c : classes) { + out.add(c.getClassEntry().getName()); + } + Collections.sort(out); + return out; + } + + /* DEBUG + private static String decompile(TranslatingTypeLoader loader, ClassEntry classEntry) { + PlainTextOutput output = new PlainTextOutput(); + DecompilerSettings settings = DecompilerSettings.javaDefaults(); + settings.setForceExplicitImports(true); + settings.setShowSyntheticMembers(true); + settings.setTypeLoader(loader); + Decompiler.decompile(classEntry.getName(), output, settings); + return output.toString(); + } + */ +} diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java new file mode 100644 index 0000000..53b6f7f --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassMatching.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.convert; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +public class ClassMatching { + + private Multimap m_sourceClasses; + private Multimap m_matchedDestClasses; + private List m_unmatchedDestClasses; + + public ClassMatching() { + m_sourceClasses = ArrayListMultimap.create(); + m_matchedDestClasses = ArrayListMultimap.create(); + m_unmatchedDestClasses = Lists.newArrayList(); + } + + public void addSource(ClassIdentity c) { + m_sourceClasses.put(c, c); + } + + public void matchDestClass(ClassIdentity destClass) { + Collection matchedSourceClasses = m_sourceClasses.get(destClass); + if (matchedSourceClasses.isEmpty()) { + // no match + m_unmatchedDestClasses.add(destClass); + } else { + // found a match + m_matchedDestClasses.put(destClass, destClass); + + // DEBUG + ClassIdentity sourceClass = matchedSourceClasses.iterator().next(); + assert (sourceClass.hashCode() == destClass.hashCode()); + assert (sourceClass.equals(destClass)); + } + } + + public void removeSource(ClassIdentity sourceClass) { + m_sourceClasses.remove(sourceClass, sourceClass); + } + + public void removeDest(ClassIdentity destClass) { + m_matchedDestClasses.remove(destClass, destClass); + m_unmatchedDestClasses.remove(destClass); + } + + public List getSourceClasses() { + return new ArrayList(m_sourceClasses.values()); + } + + public List getDestClasses() { + List classes = Lists.newArrayList(); + classes.addAll(m_matchedDestClasses.values()); + classes.addAll(m_unmatchedDestClasses); + return classes; + } + + public BiMap getUniqueMatches() { + BiMap uniqueMatches = HashBiMap.create(); + for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { + Collection matchedSourceClasses = m_sourceClasses.get(sourceClass); + Collection matchedDestClasses = m_matchedDestClasses.get(sourceClass); + if (matchedSourceClasses.size() == 1 && matchedDestClasses.size() == 1) { + ClassIdentity matchedSourceClass = matchedSourceClasses.iterator().next(); + ClassIdentity matchedDestClass = matchedDestClasses.iterator().next(); + uniqueMatches.put(matchedSourceClass, matchedDestClass); + } + } + return uniqueMatches; + } + + public BiMap,List> getAmbiguousMatches() { + BiMap,List> ambiguousMatches = HashBiMap.create(); + for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { + Collection matchedSourceClasses = m_sourceClasses.get(sourceClass); + Collection matchedDestClasses = m_matchedDestClasses.get(sourceClass); + if (matchedSourceClasses.size() > 1 && matchedDestClasses.size() > 1) { + ambiguousMatches.put( + new ArrayList(matchedSourceClasses), + new ArrayList(matchedDestClasses) + ); + } + } + return ambiguousMatches; + } + + public int getNumAmbiguousSourceMatches() { + int num = 0; + for (Map.Entry,List> entry : getAmbiguousMatches().entrySet()) { + num += entry.getKey().size(); + } + return num; + } + + public int getNumAmbiguousDestMatches() { + int num = 0; + for (Map.Entry,List> entry : getAmbiguousMatches().entrySet()) { + num += entry.getValue().size(); + } + return num; + } + + public List getUnmatchedSourceClasses() { + List classes = Lists.newArrayList(); + for (ClassIdentity sourceClass : getSourceClasses()) { + if (m_matchedDestClasses.get(sourceClass).isEmpty()) { + classes.add(sourceClass); + } + } + return classes; + } + + public List getUnmatchedDestClasses() { + return new ArrayList(m_unmatchedDestClasses); + } + + public Map>> getIndex() { + Map>> conversion = Maps.newHashMap(); + for (Map.Entry entry : getUniqueMatches().entrySet()) { + conversion.put( + entry.getKey().getClassEntry().getName(), + new AbstractMap.SimpleEntry>(entry.getKey(), Arrays.asList(entry.getValue())) + ); + } + for (Map.Entry,List> entry : getAmbiguousMatches().entrySet()) { + for (ClassIdentity sourceClass : entry.getKey()) { + conversion.put( + sourceClass.getClassEntry().getName(), + new AbstractMap.SimpleEntry>(sourceClass, entry.getValue()) + ); + } + } + for (ClassIdentity sourceClass : getUnmatchedSourceClasses()) { + conversion.put( + sourceClass.getClassEntry().getName(), + new AbstractMap.SimpleEntry>(sourceClass, getUnmatchedDestClasses()) + ); + } + return conversion; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(String.format("%12s%8s%8s\n", "", "Source", "Dest")); + buf.append(String.format("%12s%8d%8d\n", "Classes", getSourceClasses().size(), getDestClasses().size())); + buf.append(String.format("%12s%8d%8d\n", "Unique", getUniqueMatches().size(), getUniqueMatches().size())); + buf.append(String.format("%12s%8d%8d\n", "Ambiguous", getNumAmbiguousSourceMatches(), getNumAmbiguousDestMatches())); + buf.append(String.format("%12s%8d%8d\n", "Unmatched", getUnmatchedSourceClasses().size(), getUnmatchedDestClasses().size())); + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java new file mode 100644 index 0000000..1b6e81c --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassNamer.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.convert; + +import java.util.Map; + +import com.google.common.collect.BiMap; +import com.google.common.collect.Maps; + +public class ClassNamer { + + public interface SidedClassNamer { + String getName(String name); + } + + private Map m_sourceNames; + private Map m_destNames; + + public ClassNamer(BiMap mappings) { + // convert the identity mappings to name maps + m_sourceNames = Maps.newHashMap(); + m_destNames = Maps.newHashMap(); + int i = 0; + for (Map.Entry entry : mappings.entrySet()) { + String name = String.format("M%04d", i++); + m_sourceNames.put(entry.getKey().getClassEntry().getName(), name); + m_destNames.put(entry.getValue().getClassEntry().getName(), name); + } + } + + public String getSourceName(String name) { + return m_sourceNames.get(name); + } + + public String getDestName(String name) { + return m_destNames.get(name); + } + + public SidedClassNamer getSourceNamer() { + return new SidedClassNamer() { + @Override + public String getName(String name) { + return getSourceName(name); + } + }; + } + + public SidedClassNamer getDestNamer() { + return new SidedClassNamer() { + @Override + public String getName(String name) { + return getDestName(name); + } + }; + } +} diff --git a/src/cuchaz/enigma/gui/AboutDialog.java b/src/cuchaz/enigma/gui/AboutDialog.java new file mode 100644 index 0000000..2476b56 --- /dev/null +++ b/src/cuchaz/enigma/gui/AboutDialog.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.Color; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.WindowConstants; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.Util; + +public class AboutDialog { + + public static void show(JFrame parent) { + // init frame + final JFrame frame = new JFrame(Constants.Name + " - About"); + final Container pane = frame.getContentPane(); + pane.setLayout(new FlowLayout()); + + // load the content + try { + String html = Util.readResourceToString("/about.html"); + html = String.format(html, Constants.Name, Constants.Version); + JLabel label = new JLabel(html); + label.setHorizontalAlignment(JLabel.CENTER); + pane.add(label); + } catch (IOException ex) { + throw new Error(ex); + } + + // show the link + String html = "%s"; + html = String.format(html, Constants.Url, Constants.Url); + JButton link = new JButton(html); + link.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + Util.openUrl(Constants.Url); + } + }); + link.setBorderPainted(false); + link.setOpaque(false); + link.setBackground(Color.WHITE); + link.setCursor(new Cursor(Cursor.HAND_CURSOR)); + link.setFocusable(false); + JPanel linkPanel = new JPanel(); + linkPanel.add(link); + pane.add(linkPanel); + + // show ok button + JButton okButton = new JButton("Ok"); + pane.add(okButton); + okButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + frame.dispose(); + } + }); + + // show the frame + pane.doLayout(); + frame.setSize(400, 220); + frame.setResizable(false); + frame.setLocationRelativeTo(parent); + frame.setVisible(true); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + } +} diff --git a/src/cuchaz/enigma/gui/BoxHighlightPainter.java b/src/cuchaz/enigma/gui/BoxHighlightPainter.java new file mode 100644 index 0000000..db7c85b --- /dev/null +++ b/src/cuchaz/enigma/gui/BoxHighlightPainter.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; + +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; + +public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter { + + private Color m_fillColor; + private Color m_borderColor; + + protected BoxHighlightPainter(Color fillColor, Color borderColor) { + m_fillColor = fillColor; + m_borderColor = borderColor; + } + + @Override + public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { + Rectangle bounds = getBounds(text, start, end); + + // fill the area + if (m_fillColor != null) { + g.setColor(m_fillColor); + g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } + + // draw a box around the area + g.setColor(m_borderColor); + g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } + + protected 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)); + + // adjust the box so it looks nice + bounds.x -= 2; + bounds.width += 2; + bounds.y += 1; + bounds.height -= 2; + + return bounds; + } catch (BadLocationException ex) { + // don't care... just return something + return new Rectangle(0, 0, 0, 0); + } + } +} diff --git a/src/cuchaz/enigma/gui/BrowserCaret.java b/src/cuchaz/enigma/gui/BrowserCaret.java new file mode 100644 index 0000000..acee483 --- /dev/null +++ b/src/cuchaz/enigma/gui/BrowserCaret.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.Graphics; +import java.awt.Shape; + +import javax.swing.text.DefaultCaret; +import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; + +public class BrowserCaret extends DefaultCaret { + + private static final long serialVersionUID = 1158977422507969940L; + + private static final Highlighter.HighlightPainter m_selectionPainter = new Highlighter.HighlightPainter() { + @Override + public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) { + // don't paint anything + } + }; + + @Override + public boolean isSelectionVisible() { + return false; + } + + @Override + public boolean isVisible() { + return true; + } + + @Override + public Highlighter.HighlightPainter getSelectionPainter() { + return m_selectionPainter; + } +} diff --git a/src/cuchaz/enigma/gui/ClassListCellRenderer.java b/src/cuchaz/enigma/gui/ClassListCellRenderer.java new file mode 100644 index 0000000..d0f01e6 --- /dev/null +++ b/src/cuchaz/enigma/gui/ClassListCellRenderer.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.Component; + +import javassist.bytecode.Descriptor; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; + +public class ClassListCellRenderer implements ListCellRenderer { + + private DefaultListCellRenderer m_defaultRenderer; + + public ClassListCellRenderer() { + m_defaultRenderer = new DefaultListCellRenderer(); + } + + @Override + public Component getListCellRendererComponent(JList list, String className, int index, boolean isSelected, boolean hasFocus) { + JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, className, index, isSelected, hasFocus); + label.setText(Descriptor.toJavaName(className)); + return label; + } +} diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java new file mode 100644 index 0000000..654bfbe --- /dev/null +++ b/src/cuchaz/enigma/gui/ClassSelector.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +import cuchaz.enigma.mapping.ClassEntry; + +public class ClassSelector extends JTree { + + private static final long serialVersionUID = -7632046902384775977L; + + public interface ClassSelectionListener { + void onSelectClass(ClassEntry classEntry); + } + + public static Comparator ObfuscatedClassEntryComparator; + public static Comparator DeobfuscatedClassEntryComparator; + + static { + ObfuscatedClassEntryComparator = new Comparator() { + @Override + public int compare(ClassEntry a, ClassEntry b) { + if (a.getName().length() != b.getName().length()) { + return a.getName().length() - b.getName().length(); + } + return a.getName().compareTo(b.getName()); + } + }; + + DeobfuscatedClassEntryComparator = new Comparator() { + @Override + public int compare(ClassEntry a, ClassEntry b) { + return a.getName().compareTo(b.getName()); + } + }; + } + + private ClassSelectionListener m_listener; + private Comparator m_comparator; + + public ClassSelector(Comparator comparator) { + m_comparator = comparator; + + // configure the tree control + setRootVisible(false); + setShowsRootHandles(false); + setModel(null); + + // hook events + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (m_listener != null && event.getClickCount() == 2) { + // get the selected node + TreePath path = getSelectionPath(); + if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { + ClassSelectorClassNode node = (ClassSelectorClassNode)path.getLastPathComponent(); + m_listener.onSelectClass(node.getClassEntry()); + } + } + } + }); + + // init defaults + m_listener = null; + } + + public void setListener(ClassSelectionListener val) { + m_listener = val; + } + + public void setClasses(Collection classEntries) { + if (classEntries == null) { + setModel(null); + return; + } + + // build the package names + Map packages = Maps.newHashMap(); + for (ClassEntry classEntry : classEntries) { + packages.put(classEntry.getPackageName(), null); + } + + // sort the packages + List sortedPackageNames = Lists.newArrayList(packages.keySet()); + Collections.sort(sortedPackageNames, new Comparator() { + @Override + public int compare(String a, String b) { + // I can never keep this rule straight when writing these damn things... + // a < b => -1, a == b => 0, a > b => +1 + + String[] aparts = a.split("/"); + String[] bparts = b.split("/"); + for (int i = 0; true; i++) { + if (i >= aparts.length) { + return -1; + } else if (i >= bparts.length) { + return 1; + } + + int result = aparts[i].compareTo(bparts[i]); + if (result != 0) { + return result; + } + } + } + }); + + // create the root node and the package nodes + DefaultMutableTreeNode root = new DefaultMutableTreeNode(); + for (String packageName : sortedPackageNames) { + ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); + packages.put(packageName, node); + root.add(node); + } + + // put the classes into packages + Multimap packagedClassEntries = ArrayListMultimap.create(); + for (ClassEntry classEntry : classEntries) { + packagedClassEntries.put(classEntry.getPackageName(), classEntry); + } + + // build the class nodes + for (String packageName : packagedClassEntries.keySet()) { + // sort the class entries + List classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); + Collections.sort(classEntriesInPackage, m_comparator); + + // create the nodes in order + for (ClassEntry classEntry : classEntriesInPackage) { + ClassSelectorPackageNode node = packages.get(packageName); + node.add(new ClassSelectorClassNode(classEntry)); + } + } + + // finally, update the tree control + setModel(new DefaultTreeModel(root)); + } +} diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java new file mode 100644 index 0000000..66e931b --- /dev/null +++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import javax.swing.tree.DefaultMutableTreeNode; + +import cuchaz.enigma.mapping.ClassEntry; + +public class ClassSelectorClassNode extends DefaultMutableTreeNode { + + private static final long serialVersionUID = -8956754339813257380L; + + private ClassEntry m_classEntry; + + public ClassSelectorClassNode(ClassEntry classEntry) { + m_classEntry = classEntry; + } + + public ClassEntry getClassEntry() { + return m_classEntry; + } + + @Override + public String toString() { + return m_classEntry.getSimpleName(); + } +} diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java new file mode 100644 index 0000000..451d380 --- /dev/null +++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import javax.swing.tree.DefaultMutableTreeNode; + +public class ClassSelectorPackageNode extends DefaultMutableTreeNode { + + private static final long serialVersionUID = -3730868701219548043L; + + private String m_packageName; + + public ClassSelectorPackageNode(String packageName) { + m_packageName = packageName; + } + + public String getPackageName() { + return m_packageName; + } + + @Override + public String toString() { + return m_packageName; + } +} diff --git a/src/cuchaz/enigma/gui/CrashDialog.java b/src/cuchaz/enigma/gui/CrashDialog.java new file mode 100644 index 0000000..360091a --- /dev/null +++ b/src/cuchaz/enigma/gui/CrashDialog.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.PrintWriter; +import java.io.StringWriter; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.WindowConstants; + +import cuchaz.enigma.Constants; + +public class CrashDialog { + + private static CrashDialog m_instance = null; + + private JFrame m_frame; + private JTextArea m_text; + + private CrashDialog(JFrame parent) { + // init frame + m_frame = new JFrame(Constants.Name + " - Crash Report"); + final Container pane = m_frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + JLabel label = new JLabel(Constants.Name + " has crashed! =("); + label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + pane.add(label, BorderLayout.NORTH); + + // report panel + m_text = new JTextArea(); + m_text.setTabSize(2); + pane.add(new JScrollPane(m_text), BorderLayout.CENTER); + + // buttons panel + JPanel buttonsPanel = new JPanel(); + FlowLayout buttonsLayout = new FlowLayout(); + buttonsLayout.setAlignment(FlowLayout.RIGHT); + buttonsPanel.setLayout(buttonsLayout); + buttonsPanel.add(GuiTricks.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work."))); + JButton ignoreButton = new JButton("Ignore"); + ignoreButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + // close (hide) the dialog + m_frame.setVisible(false); + } + }); + buttonsPanel.add(ignoreButton); + JButton exitButton = new JButton("Exit"); + exitButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + // exit enigma + System.exit(1); + } + }); + buttonsPanel.add(exitButton); + pane.add(buttonsPanel, BorderLayout.SOUTH); + + // show the frame + m_frame.setSize(600, 400); + m_frame.setLocationRelativeTo(parent); + m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } + + public static void init(JFrame parent) { + m_instance = new CrashDialog(parent); + } + + public static void show(Throwable ex) { + // get the error report + StringWriter buf = new StringWriter(); + ex.printStackTrace(new PrintWriter(buf)); + String report = buf.toString(); + + // show it! + m_instance.m_text.setText(report); + m_instance.m_frame.doLayout(); + m_instance.m_frame.setVisible(true); + } +} diff --git a/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java new file mode 100644 index 0000000..26a3163 --- /dev/null +++ b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.Color; + +public class DeobfuscatedHighlightPainter extends BoxHighlightPainter { + + public DeobfuscatedHighlightPainter() { + // green ish + super(new Color(220, 255, 220), new Color(80, 160, 80)); + } +} diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java new file mode 100644 index 0000000..ca39c42 --- /dev/null +++ b/src/cuchaz/enigma/gui/Gui.java @@ -0,0 +1,1165 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Vector; +import java.util.jar.JarFile; + +import javax.swing.BorderFactory; +import javax.swing.JEditorPane; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.JTree; +import javax.swing.KeyStroke; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.WindowConstants; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import jsyntaxpane.DefaultSyntaxKit; + +import com.google.common.collect.Lists; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; +import cuchaz.enigma.analysis.ClassImplementationsTreeNode; +import cuchaz.enigma.analysis.ClassInheritanceTreeNode; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.FieldReferenceTreeNode; +import cuchaz.enigma.analysis.MethodImplementationsTreeNode; +import cuchaz.enigma.analysis.MethodInheritanceTreeNode; +import cuchaz.enigma.analysis.ReferenceTreeNode; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; +import cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.IllegalNameException; +import cuchaz.enigma.mapping.MappingParseException; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Signature; + +public class Gui { + + private GuiController m_controller; + + // controls + private JFrame m_frame; + private ClassSelector m_obfClasses; + private ClassSelector m_deobfClasses; + private JEditorPane m_editor; + private JPanel m_classesPanel; + private JSplitPane m_splitClasses; + private JPanel m_infoPanel; + private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter; + private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter; + private OtherHighlightPainter m_otherHighlightPainter; + private SelectionHighlightPainter m_selectionHighlightPainter; + private JTree m_inheritanceTree; + private JTree m_implementationsTree; + private JTree m_callsTree; + private JList m_tokens; + private JTabbedPane m_tabs; + + // dynamic menu items + private JMenuItem m_closeJarMenu; + private JMenuItem m_openMappingsMenu; + private JMenuItem m_saveMappingsMenu; + private JMenuItem m_saveMappingsAsMenu; + private JMenuItem m_closeMappingsMenu; + private JMenuItem m_renameMenu; + private JMenuItem m_showInheritanceMenu; + private JMenuItem m_openEntryMenu; + private JMenuItem m_openPreviousMenu; + private JMenuItem m_showCallsMenu; + private JMenuItem m_showImplementationsMenu; + private JMenuItem m_toggleMappingMenu; + private JMenuItem m_exportSourceMenu; + private JMenuItem m_exportJarMenu; + + // state + private EntryReference m_reference; + private JFileChooser m_jarFileChooser; + private JFileChooser m_mappingsFileChooser; + private JFileChooser m_exportSourceFileChooser; + private JFileChooser m_exportJarFileChooser; + + public Gui() { + + // init frame + m_frame = new JFrame(Constants.Name); + final Container pane = m_frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { + // install a global exception handler to the event thread + CrashDialog.init(m_frame); + Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable ex) { + ex.printStackTrace(System.err); + CrashDialog.show(ex); + } + }); + } + + m_controller = new GuiController(this); + + // init file choosers + m_jarFileChooser = new JFileChooser(); + m_mappingsFileChooser = new JFileChooser(); + m_exportSourceFileChooser = new JFileChooser(); + m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + m_exportJarFileChooser = new JFileChooser(); + + // init obfuscated classes list + m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator); + m_obfClasses.setListener(new ClassSelectionListener() { + @Override + public void onSelectClass(ClassEntry classEntry) { + navigateTo(classEntry); + } + }); + JScrollPane obfScroller = new JScrollPane(m_obfClasses); + JPanel obfPanel = new JPanel(); + obfPanel.setLayout(new BorderLayout()); + obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH); + obfPanel.add(obfScroller, BorderLayout.CENTER); + + // init deobfuscated classes list + m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); + m_deobfClasses.setListener(new ClassSelectionListener() { + @Override + public void onSelectClass(ClassEntry classEntry) { + navigateTo(classEntry); + } + }); + JScrollPane deobfScroller = new JScrollPane(m_deobfClasses); + JPanel deobfPanel = new JPanel(); + deobfPanel.setLayout(new BorderLayout()); + deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH); + deobfPanel.add(deobfScroller, BorderLayout.CENTER); + + // set up classes panel (don't add the splitter yet) + m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel); + m_splitClasses.setResizeWeight(0.3); + m_classesPanel = new JPanel(); + m_classesPanel.setLayout(new BorderLayout()); + m_classesPanel.setPreferredSize(new Dimension(250, 0)); + + // init info panel + m_infoPanel = new JPanel(); + m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0)); + m_infoPanel.setPreferredSize(new Dimension(0, 100)); + m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info")); + clearReference(); + + // init editor + DefaultSyntaxKit.initKit(); + m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter(); + m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter(); + m_otherHighlightPainter = new OtherHighlightPainter(); + m_selectionHighlightPainter = new SelectionHighlightPainter(); + m_editor = new JEditorPane(); + m_editor.setEditable(false); + m_editor.setCaret(new BrowserCaret()); + JScrollPane sourceScroller = new JScrollPane(m_editor); + m_editor.setContentType("text/java"); + m_editor.addCaretListener(new CaretListener() { + @Override + public void caretUpdate(CaretEvent event) { + onCaretMove(event.getDot()); + } + }); + m_editor.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_R: + m_renameMenu.doClick(); + break; + + case KeyEvent.VK_I: + m_showInheritanceMenu.doClick(); + break; + + case KeyEvent.VK_M: + m_showImplementationsMenu.doClick(); + break; + + case KeyEvent.VK_N: + m_openEntryMenu.doClick(); + break; + + case KeyEvent.VK_P: + m_openPreviousMenu.doClick(); + break; + + case KeyEvent.VK_C: + m_showCallsMenu.doClick(); + break; + + case KeyEvent.VK_T: + m_toggleMappingMenu.doClick(); + break; + } + } + }); + + // turn off token highlighting (it's wrong most of the time anyway...) + DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit(); + kit.toggleComponent(m_editor, "jsyntaxpane.components.TokenMarker"); + + // init editor popup menu + JPopupMenu popupMenu = new JPopupMenu(); + m_editor.setComponentPopupMenu(popupMenu); + { + JMenuItem menu = new JMenuItem("Rename"); + menu.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + startRename(); + } + }); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0)); + menu.setEnabled(false); + popupMenu.add(menu); + m_renameMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Show Inheritance"); + menu.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + showInheritance(); + } + }); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0)); + menu.setEnabled(false); + popupMenu.add(menu); + m_showInheritanceMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Show Implementations"); + menu.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + showImplementations(); + } + }); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0)); + menu.setEnabled(false); + popupMenu.add(menu); + m_showImplementationsMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Show Calls"); + menu.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + showCalls(); + } + }); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0)); + menu.setEnabled(false); + popupMenu.add(menu); + m_showCallsMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Go to Declaration"); + menu.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + navigateTo(m_reference.entry); + } + }); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0)); + menu.setEnabled(false); + popupMenu.add(menu); + m_openEntryMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Go to previous"); + menu.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + m_controller.openPreviousReference(); + } + }); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0)); + menu.setEnabled(false); + popupMenu.add(menu); + m_openPreviousMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Mark as deobfuscated"); + menu.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + toggleMapping(); + } + }); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0)); + menu.setEnabled(false); + popupMenu.add(menu); + m_toggleMappingMenu = menu; + } + + // init inheritance panel + m_inheritanceTree = new JTree(); + m_inheritanceTree.setModel(null); + m_inheritanceTree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + // get the selected node + TreePath path = m_inheritanceTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ClassInheritanceTreeNode) { + ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node; + navigateTo(new ClassEntry(classNode.getObfClassName())); + } else if (node instanceof MethodInheritanceTreeNode) { + MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node; + if (methodNode.isImplemented()) { + navigateTo(methodNode.getMethodEntry()); + } + } + } + } + }); + JPanel inheritancePanel = new JPanel(); + inheritancePanel.setLayout(new BorderLayout()); + inheritancePanel.add(new JScrollPane(m_inheritanceTree)); + + // init implementations panel + m_implementationsTree = new JTree(); + m_implementationsTree.setModel(null); + m_implementationsTree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + // get the selected node + TreePath path = m_implementationsTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ClassImplementationsTreeNode) { + ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node; + navigateTo(classNode.getClassEntry()); + } else if (node instanceof MethodImplementationsTreeNode) { + MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node; + navigateTo(methodNode.getMethodEntry()); + } + } + } + }); + JPanel implementationsPanel = new JPanel(); + implementationsPanel.setLayout(new BorderLayout()); + implementationsPanel.add(new JScrollPane(m_implementationsTree)); + + // init call panel + m_callsTree = new JTree(); + m_callsTree.setModel(null); + m_callsTree.addMouseListener(new MouseAdapter() { + @SuppressWarnings("unchecked") + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + // get the selected node + TreePath path = m_callsTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ReferenceTreeNode) { + ReferenceTreeNode referenceNode = ((ReferenceTreeNode)node); + if (referenceNode.getReference() != null) { + navigateTo(referenceNode.getReference()); + } else { + navigateTo(referenceNode.getEntry()); + } + } + } + } + }); + m_tokens = new JList(); + m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller)); + m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + m_tokens.setLayoutOrientation(JList.VERTICAL); + m_tokens.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + Token selected = m_tokens.getSelectedValue(); + if (selected != null) { + showToken(selected); + } + } + } + }); + m_tokens.setPreferredSize(new Dimension(0, 200)); + m_tokens.setMinimumSize(new Dimension(0, 200)); + JSplitPane callPanel = new JSplitPane( + JSplitPane.VERTICAL_SPLIT, + true, + new JScrollPane(m_callsTree), + new JScrollPane(m_tokens) + ); + callPanel.setResizeWeight(1); // let the top side take all the slack + callPanel.resetToPreferredSizes(); + + // layout controls + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new BorderLayout()); + centerPanel.add(m_infoPanel, BorderLayout.NORTH); + centerPanel.add(sourceScroller, BorderLayout.CENTER); + m_tabs = new JTabbedPane(); + m_tabs.setPreferredSize(new Dimension(250, 0)); + m_tabs.addTab("Inheritance", inheritancePanel); + m_tabs.addTab("Implementations", implementationsPanel); + m_tabs.addTab("Call Graph", callPanel); + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs); + splitRight.setResizeWeight(1); // let the left side take all the slack + splitRight.resetToPreferredSizes(); + JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight); + splitCenter.setResizeWeight(0); // let the right side take all the slack + pane.add(splitCenter, BorderLayout.CENTER); + + // init menus + JMenuBar menuBar = new JMenuBar(); + m_frame.setJMenuBar(menuBar); + { + JMenu menu = new JMenu("File"); + menuBar.add(menu); + { + JMenuItem item = new JMenuItem("Open Jar..."); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) { + // load the jar in a separate thread + new Thread() { + @Override + public void run() { + try { + m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile())); + } catch (IOException ex) { + throw new Error(ex); + } + } + }.start(); + } + } + }); + } + { + JMenuItem item = new JMenuItem("Close Jar"); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + m_controller.closeJar(); + } + }); + m_closeJarMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem("Open Mappings..."); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) { + try { + m_controller.openMappings(m_mappingsFileChooser.getSelectedFile()); + } catch (IOException ex) { + throw new Error(ex); + } catch (MappingParseException ex) { + JOptionPane.showMessageDialog(m_frame, ex.getMessage()); + } + } + } + }); + m_openMappingsMenu = item; + } + { + JMenuItem item = new JMenuItem("Save Mappings"); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + try { + m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile()); + } catch (IOException ex) { + throw new Error(ex); + } + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); + m_saveMappingsMenu = item; + } + { + JMenuItem item = new JMenuItem("Save Mappings As..."); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) { + try { + m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile()); + m_saveMappingsMenu.setEnabled(true); + } catch (IOException ex) { + throw new Error(ex); + } + } + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + m_saveMappingsAsMenu = item; + } + { + JMenuItem item = new JMenuItem("Close Mappings"); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + m_controller.closeMappings(); + } + }); + m_closeMappingsMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem("Export Source..."); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) { + m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile()); + } + } + }); + m_exportSourceMenu = item; + } + { + JMenuItem item = new JMenuItem("Export Jar..."); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) { + m_controller.exportJar(m_exportJarFileChooser.getSelectedFile()); + } + } + }); + m_exportJarMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem("Exit"); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + close(); + } + }); + } + } + { + JMenu menu = new JMenu("Help"); + menuBar.add(menu); + { + JMenuItem item = new JMenuItem("About"); + menu.add(item); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + AboutDialog.show(m_frame); + } + }); + } + } + + // init state + onCloseJar(); + + m_frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + close(); + } + }); + + // show the frame + pane.doLayout(); + m_frame.setSize(1024, 576); + m_frame.setMinimumSize(new Dimension(640, 480)); + m_frame.setVisible(true); + m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } + + public JFrame getFrame() { + return m_frame; + } + + public GuiController getController() { + return m_controller; + } + + public void onStartOpenJar() { + m_classesPanel.removeAll(); + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout()); + panel.add(new JLabel("Loading...")); + m_classesPanel.add(panel); + redraw(); + } + + public void onFinishOpenJar(String jarName) { + // update gui + m_frame.setTitle(Constants.Name + " - " + jarName); + m_classesPanel.removeAll(); + m_classesPanel.add(m_splitClasses); + setSource(null); + + // update menu + m_closeJarMenu.setEnabled(true); + m_openMappingsMenu.setEnabled(true); + m_saveMappingsMenu.setEnabled(false); + m_saveMappingsAsMenu.setEnabled(true); + m_closeMappingsMenu.setEnabled(true); + m_exportSourceMenu.setEnabled(true); + m_exportJarMenu.setEnabled(true); + + redraw(); + } + + public void onCloseJar() { + // update gui + m_frame.setTitle(Constants.Name); + setObfClasses(null); + setDeobfClasses(null); + setSource(null); + m_classesPanel.removeAll(); + + // update menu + m_closeJarMenu.setEnabled(false); + m_openMappingsMenu.setEnabled(false); + m_saveMappingsMenu.setEnabled(false); + m_saveMappingsAsMenu.setEnabled(false); + m_closeMappingsMenu.setEnabled(false); + m_exportSourceMenu.setEnabled(false); + m_exportJarMenu.setEnabled(false); + + redraw(); + } + + public void setObfClasses(Collection obfClasses) { + m_obfClasses.setClasses(obfClasses); + } + + public void setDeobfClasses(Collection deobfClasses) { + m_deobfClasses.setClasses(deobfClasses); + } + + public void setMappingsFile(File file) { + m_mappingsFileChooser.setSelectedFile(file); + m_saveMappingsMenu.setEnabled(file != null); + } + + public void setSource(String source) { + m_editor.getHighlighter().removeAllHighlights(); + m_editor.setText(source); + } + + public void showToken(final Token token) { + if (token == null) { + throw new IllegalArgumentException("Token cannot be null!"); + } + + // set the caret position to the token + m_editor.setCaretPosition(token.start); + m_editor.grabFocus(); + + try { + // make sure the token is visible in the scroll window + Rectangle start = m_editor.modelToView(token.start); + Rectangle end = m_editor.modelToView(token.end); + final Rectangle show = start.union(end); + show.grow(start.width * 10, start.height * 6); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + m_editor.scrollRectToVisible(show); + } + }); + } catch (BadLocationException ex) { + throw new Error(ex); + } + + // highlight the token momentarily + final Timer timer = new Timer(200, new ActionListener() { + private int m_counter = 0; + private Object m_highlight = null; + + @Override + public void actionPerformed(ActionEvent event) { + if (m_counter % 2 == 0) { + try { + m_highlight = m_editor.getHighlighter().addHighlight(token.start, token.end, m_selectionHighlightPainter); + } catch (BadLocationException ex) { + // don't care + } + } else if (m_highlight != null) { + m_editor.getHighlighter().removeHighlight(m_highlight); + } + + if (m_counter++ > 6) { + Timer timer = (Timer)event.getSource(); + timer.stop(); + } + } + }); + timer.start(); + + redraw(); + } + + public void showTokens(Collection tokens) { + Vector sortedTokens = new Vector(tokens); + Collections.sort(sortedTokens); + if (sortedTokens.size() > 1) { + // sort the tokens and update the tokens panel + m_tokens.setListData(sortedTokens); + m_tokens.setSelectedIndex(0); + } else { + m_tokens.setListData(new Vector()); + } + + // show the first token + showToken(sortedTokens.get(0)); + } + + public void setHighlightedTokens(Iterable obfuscatedTokens, Iterable deobfuscatedTokens, Iterable otherTokens) { + + // remove any old highlighters + m_editor.getHighlighter().removeAllHighlights(); + + // color things based on the index + if (obfuscatedTokens != null) { + setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter); + } + if (deobfuscatedTokens != null) { + setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter); + } + if (otherTokens != null) { + setHighlightedTokens(otherTokens, m_otherHighlightPainter); + } + + redraw(); + } + + private void setHighlightedTokens(Iterable tokens, Highlighter.HighlightPainter painter) { + for (Token token : tokens) { + try { + m_editor.getHighlighter().addHighlight(token.start, token.end, painter); + } catch (BadLocationException ex) { + throw new IllegalArgumentException(ex); + } + } + } + + private void clearReference() { + m_infoPanel.removeAll(); + JLabel label = new JLabel("No identifier selected"); + GuiTricks.unboldLabel(label); + label.setHorizontalAlignment(JLabel.CENTER); + m_infoPanel.add(label); + + redraw(); + } + + private void showReference(EntryReference reference) { + if (reference == null) { + clearReference(); + return; + } + + m_reference = reference; + + m_infoPanel.removeAll(); + if (reference.entry instanceof ClassEntry) { + showClassEntry((ClassEntry)m_reference.entry); + } else if (m_reference.entry instanceof FieldEntry) { + showFieldEntry((FieldEntry)m_reference.entry); + } else if (m_reference.entry instanceof MethodEntry) { + showMethodEntry((MethodEntry)m_reference.entry); + } else if (m_reference.entry instanceof ConstructorEntry) { + showConstructorEntry((ConstructorEntry)m_reference.entry); + } else if (m_reference.entry instanceof ArgumentEntry) { + showArgumentEntry((ArgumentEntry)m_reference.entry); + } else { + throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName()); + } + + redraw(); + } + + private void showClassEntry(ClassEntry entry) { + addNameValue(m_infoPanel, "Class", entry.getName()); + } + + private void showFieldEntry(FieldEntry entry) { + addNameValue(m_infoPanel, "Field", entry.getName()); + addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName()); + } + + private void showMethodEntry(MethodEntry entry) { + addNameValue(m_infoPanel, "Method", entry.getName()); + addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(m_infoPanel, "Signature", entry.getSignature().toString()); + } + + private void showConstructorEntry(ConstructorEntry entry) { + addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName()); + addNameValue(m_infoPanel, "Signature", entry.getSignature().toString()); + } + + private void showArgumentEntry(ArgumentEntry entry) { + addNameValue(m_infoPanel, "Argument", entry.getName()); + addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName()); + addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex())); + } + + private void addNameValue(JPanel container, String name, String value) { + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); + container.add(panel); + + JLabel label = new JLabel(name + ":", JLabel.RIGHT); + label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); + panel.add(label); + + panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT))); + } + + private void onCaretMove(int pos) { + + Token token = m_controller.getToken(pos); + boolean isToken = token != null; + + m_reference = m_controller.getDeobfReference(token); + boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry; + boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry; + boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry; + boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry; + boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry); + boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference); + + if (isToken) { + showReference(m_reference); + } else { + clearReference(); + } + + m_renameMenu.setEnabled(isRenameable && isToken); + m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); + m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); + m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); + m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); + m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation()); + m_toggleMappingMenu.setEnabled(isRenameable && isToken); + + if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) { + m_toggleMappingMenu.setText("Reset to obfuscated"); + } else { + m_toggleMappingMenu.setText("Mark as deobfuscated"); + } + } + + private void navigateTo(Entry entry) { + if (!m_controller.entryIsInJar(entry)) { + // entry is not in the jar. Ignore it + return; + } + if (m_reference != null) { + m_controller.savePreviousReference(m_reference); + } + m_controller.openDeclaration(entry); + } + + private void navigateTo(EntryReference reference) { + if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) { + // reference is not in the jar. Ignore it + return; + } + if (m_reference != null) { + m_controller.savePreviousReference(m_reference); + } + m_controller.openReference(reference); + } + + private void startRename() { + + // init the text box + final JTextField text = new JTextField(); + text.setText(m_reference.getNamableName()); + text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); + text.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_ENTER: + finishRename(text, true); + break; + + case KeyEvent.VK_ESCAPE: + finishRename(text, false); + break; + } + } + }); + + // find the label with the name and replace it with the text box + JPanel panel = (JPanel)m_infoPanel.getComponent(0); + panel.remove(panel.getComponentCount() - 1); + panel.add(text); + text.grabFocus(); + text.selectAll(); + + redraw(); + } + + private void finishRename(JTextField text, boolean saveName) { + String newName = text.getText(); + if (saveName && newName != null && newName.length() > 0) { + try { + m_controller.rename(m_reference, newName); + } catch (IllegalNameException ex) { + text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); + text.setToolTipText(ex.getReason()); + GuiTricks.showToolTipNow(text); + } + return; + } + + // abort the rename + JPanel panel = (JPanel)m_infoPanel.getComponent(0); + panel.remove(panel.getComponentCount() - 1); + panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT))); + + m_editor.grabFocus(); + + redraw(); + } + + private void showInheritance() { + + if (m_reference == null) { + return; + } + + m_inheritanceTree.setModel(null); + + if (m_reference.entry instanceof ClassEntry) { + // get the class inheritance + ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry); + + // show the tree at the root + TreePath path = getPathToRoot(classNode); + m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0))); + m_inheritanceTree.expandPath(path); + m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path)); + } else if (m_reference.entry instanceof MethodEntry) { + // get the method inheritance + MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry); + + // show the tree at the root + TreePath path = getPathToRoot(classNode); + m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0))); + m_inheritanceTree.expandPath(path); + m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path)); + } + + m_tabs.setSelectedIndex(0); + redraw(); + } + + private void showImplementations() { + + if (m_reference == null) { + return; + } + + m_implementationsTree.setModel(null); + + if (m_reference.entry instanceof ClassEntry) { + // get the class implementations + ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry); + if (node != null) { + // show the tree at the root + TreePath path = getPathToRoot(node); + m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0))); + m_implementationsTree.expandPath(path); + m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path)); + } + } else if (m_reference.entry instanceof MethodEntry) { + // get the method implementations + MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry); + if (node != null) { + // show the tree at the root + TreePath path = getPathToRoot(node); + m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0))); + m_implementationsTree.expandPath(path); + m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path)); + } + } + + m_tabs.setSelectedIndex(1); + redraw(); + } + + private void showCalls() { + + if (m_reference == null) { + return; + } + + if (m_reference.entry instanceof ClassEntry) { + // look for calls to the default constructor + // TODO: get a list of all the constructors and find calls to all of them + BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, new Signature("()V"))); + m_callsTree.setModel(new DefaultTreeModel(node)); + } else if (m_reference.entry instanceof FieldEntry) { + FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry); + m_callsTree.setModel(new DefaultTreeModel(node)); + } else if (m_reference.entry instanceof MethodEntry) { + BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry); + m_callsTree.setModel(new DefaultTreeModel(node)); + } else if (m_reference.entry instanceof ConstructorEntry) { + BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry); + m_callsTree.setModel(new DefaultTreeModel(node)); + } + + m_tabs.setSelectedIndex(2); + redraw(); + } + + private void toggleMapping() { + if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) { + m_controller.removeMapping(m_reference); + } else { + m_controller.markAsDeobfuscated(m_reference); + } + } + + private TreePath getPathToRoot(TreeNode node) { + List nodes = Lists.newArrayList(); + TreeNode n = node; + do { + nodes.add(n); + n = n.getParent(); + } while (n != null); + Collections.reverse(nodes); + return new TreePath(nodes.toArray()); + } + + private void close() { + if (!m_controller.isDirty()) { + // everything is saved, we can exit safely + m_frame.dispose(); + } else { + // ask to save before closing + String[] options = { "Save and exit", "Discard changes", "Cancel" }; + int response = JOptionPane.showOptionDialog(m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, options[2]); + switch (response) { + case JOptionPane.YES_OPTION: // save and exit + if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) { + try { + m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile()); + m_frame.dispose(); + } catch (IOException ex) { + throw new Error(ex); + } + } + break; + + case JOptionPane.NO_OPTION: + // don't save, exit + m_frame.dispose(); + break; + + // cancel means do nothing + } + } + } + + private void redraw() { + m_frame.validate(); + m_frame.repaint(); + } +} diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java new file mode 100644 index 0000000..61fea9c --- /dev/null +++ b/src/cuchaz/enigma/gui/GuiController.java @@ -0,0 +1,355 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.Deque; +import java.util.List; +import java.util.jar.JarFile; + +import com.google.common.collect.Lists; +import com.google.common.collect.Queues; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.Deobfuscator.ProgressListener; +import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; +import cuchaz.enigma.analysis.ClassImplementationsTreeNode; +import cuchaz.enigma.analysis.ClassInheritanceTreeNode; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.FieldReferenceTreeNode; +import cuchaz.enigma.analysis.MethodImplementationsTreeNode; +import cuchaz.enigma.analysis.MethodInheritanceTreeNode; +import cuchaz.enigma.analysis.SourceIndex; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.MappingParseException; +import cuchaz.enigma.mapping.MappingsReader; +import cuchaz.enigma.mapping.MappingsWriter; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.TranslationDirection; + +public class GuiController { + + private Deobfuscator m_deobfuscator; + private Gui m_gui; + private SourceIndex m_index; + private ClassEntry m_currentObfClass; + private boolean m_isDirty; + private Deque> m_referenceStack; + + public GuiController(Gui gui) { + m_gui = gui; + m_deobfuscator = null; + m_index = null; + m_currentObfClass = null; + m_isDirty = false; + m_referenceStack = Queues.newArrayDeque(); + } + + public boolean isDirty() { + return m_isDirty; + } + + public void openJar(final JarFile jar) throws IOException { + m_gui.onStartOpenJar(); + m_deobfuscator = new Deobfuscator(jar); + m_gui.onFinishOpenJar(m_deobfuscator.getJarName()); + refreshClasses(); + } + + public void closeJar() { + m_deobfuscator = null; + m_gui.onCloseJar(); + } + + public void openMappings(File file) throws IOException, MappingParseException { + FileReader in = new FileReader(file); + m_deobfuscator.setMappings(new MappingsReader().read(in)); + in.close(); + m_isDirty = false; + m_gui.setMappingsFile(file); + refreshClasses(); + refreshCurrentClass(); + } + + public void saveMappings(File file) throws IOException { + FileWriter out = new FileWriter(file); + new MappingsWriter().write(out, m_deobfuscator.getMappings()); + out.close(); + m_isDirty = false; + } + + public void closeMappings() { + m_deobfuscator.setMappings(null); + m_gui.setMappingsFile(null); + refreshClasses(); + refreshCurrentClass(); + } + + public void exportSource(final File dirOut) { + ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() { + @Override + public void run(ProgressListener progress) throws Exception { + m_deobfuscator.writeSources(dirOut, progress); + } + }); + } + + public void exportJar(final File fileOut) { + ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() { + @Override + public void run(ProgressListener progress) { + m_deobfuscator.writeJar(fileOut, progress); + } + }); + } + + public Token getToken(int pos) { + if (m_index == null) { + return null; + } + return m_index.getReferenceToken(pos); + } + + public EntryReference getDeobfReference(Token token) { + if (m_index == null) { + return null; + } + return m_index.getDeobfReference(token); + } + + public ReadableToken getReadableToken(Token token) { + if (m_index == null) { + return null; + } + return new ReadableToken( + m_index.getLineNumber(token.start), + m_index.getColumnNumber(token.start), + m_index.getColumnNumber(token.end) + ); + } + + public boolean entryHasDeobfuscatedName(Entry deobfEntry) { + return m_deobfuscator.hasDeobfuscatedName(m_deobfuscator.obfuscateEntry(deobfEntry)); + } + + public boolean entryIsInJar(Entry deobfEntry) { + return m_deobfuscator.isObfuscatedIdentifier(m_deobfuscator.obfuscateEntry(deobfEntry)); + } + + public boolean referenceIsRenameable(EntryReference deobfReference) { + return m_deobfuscator.isRenameable(m_deobfuscator.obfuscateReference(deobfReference)); + } + + public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { + ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry); + ClassInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getClassInheritance( + m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), + obfClassEntry + ); + return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); + } + + public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { + ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry); + return m_deobfuscator.getJarIndex().getClassImplementations( + m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), + obfClassEntry + ); + } + + public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { + MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry); + MethodInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodInheritance( + m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), + obfMethodEntry + ); + return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); + } + + public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { + MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry); + MethodImplementationsTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodImplementations( + m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), + obfMethodEntry + ); + if (rootNode == null) { + return null; + } + return MethodImplementationsTreeNode.findNode(rootNode, obfMethodEntry); + } + + public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { + FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode( + m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), + obfFieldEntry + ); + rootNode.load(m_deobfuscator.getJarIndex(), true); + return rootNode; + } + + public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { + BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry); + BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode( + m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), + obfBehaviorEntry + ); + rootNode.load(m_deobfuscator.getJarIndex(), true); + return rootNode; + } + + public void rename(EntryReference deobfReference, String newName) { + EntryReference obfReference = m_deobfuscator.obfuscateReference(deobfReference); + m_deobfuscator.rename(obfReference.getNameableEntry(), newName); + m_isDirty = true; + refreshClasses(); + refreshCurrentClass(obfReference); + } + + public void removeMapping(EntryReference deobfReference) { + EntryReference obfReference = m_deobfuscator.obfuscateReference(deobfReference); + m_deobfuscator.removeMapping(obfReference.getNameableEntry()); + m_isDirty = true; + refreshClasses(); + refreshCurrentClass(obfReference); + } + + public void markAsDeobfuscated(EntryReference deobfReference) { + EntryReference obfReference = m_deobfuscator.obfuscateReference(deobfReference); + m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); + m_isDirty = true; + refreshClasses(); + refreshCurrentClass(obfReference); + } + + public void openDeclaration(Entry deobfEntry) { + if (deobfEntry == null) { + throw new IllegalArgumentException("Entry cannot be null!"); + } + openReference(new EntryReference(deobfEntry, deobfEntry.getName())); + } + + public void openReference(EntryReference deobfReference) { + if (deobfReference == null) { + throw new IllegalArgumentException("Reference cannot be null!"); + } + + // get the reference target class + EntryReference obfReference = m_deobfuscator.obfuscateReference(deobfReference); + ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOuterClassEntry(); + if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { + throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); + } + if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) { + // deobfuscate the class, then navigate to the reference + m_currentObfClass = obfClassEntry; + deobfuscate(m_currentObfClass, obfReference); + } else { + showReference(obfReference); + } + } + + private void showReference(EntryReference obfReference) { + EntryReference deobfReference = m_deobfuscator.deobfuscateReference(obfReference); + Collection tokens = m_index.getReferenceTokens(deobfReference); + if (tokens.isEmpty()) { + // DEBUG + System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass)); + } else { + m_gui.showTokens(tokens); + } + } + + public void savePreviousReference(EntryReference deobfReference) { + m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference)); + } + + public void openPreviousReference() { + if (hasPreviousLocation()) { + openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop())); + } + } + + public boolean hasPreviousLocation() { + return !m_referenceStack.isEmpty(); + } + + private void refreshClasses() { + List obfClasses = Lists.newArrayList(); + List deobfClasses = Lists.newArrayList(); + m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); + m_gui.setObfClasses(obfClasses); + m_gui.setDeobfClasses(deobfClasses); + } + + private void refreshCurrentClass() { + refreshCurrentClass(null); + } + + private void refreshCurrentClass(EntryReference obfReference) { + if (m_currentObfClass != null) { + deobfuscate(m_currentObfClass, obfReference); + } + } + + private void deobfuscate(final ClassEntry classEntry, final EntryReference obfReference) { + + m_gui.setSource("(deobfuscating...)"); + + // run the deobfuscator in a separate thread so we don't block the GUI event queue + new Thread() { + @Override + public void run() { + // decompile,deobfuscate the bytecode + CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName()); + if (sourceTree == null) { + // decompilation of this class is not supported + m_gui.setSource("Unable to find class: " + classEntry); + return; + } + String source = m_deobfuscator.getSource(sourceTree); + m_index = m_deobfuscator.getSourceIndex(sourceTree, source); + m_gui.setSource(m_index.getSource()); + if (obfReference != null) { + showReference(obfReference); + } + + // set the highlighted tokens + List obfuscatedTokens = Lists.newArrayList(); + List deobfuscatedTokens = Lists.newArrayList(); + List otherTokens = Lists.newArrayList(); + for (Token token : m_index.referenceTokens()) { + EntryReference reference = m_index.getDeobfReference(token); + if (referenceIsRenameable(reference)) { + if (entryHasDeobfuscatedName(reference.getNameableEntry())) { + deobfuscatedTokens.add(token); + } else { + obfuscatedTokens.add(token); + } + } else { + otherTokens.add(token); + } + } + m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens); + } + }.start(); + } +} diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java new file mode 100644 index 0000000..df9e221 --- /dev/null +++ b/src/cuchaz/enigma/gui/GuiTricks.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.Font; +import java.awt.event.MouseEvent; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.ToolTipManager; + +public class GuiTricks { + + public static JLabel unboldLabel(JLabel label) { + Font font = label.getFont(); + label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); + return label; + } + + public static void showToolTipNow(JComponent component) { + // HACKHACK: trick the tooltip manager into showing the tooltip right now + ToolTipManager manager = ToolTipManager.sharedInstance(); + int oldDelay = manager.getInitialDelay(); + manager.setInitialDelay(0); + manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); + manager.setInitialDelay(oldDelay); + } +} diff --git a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java new file mode 100644 index 0000000..177835f --- /dev/null +++ b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.Color; + +public class ObfuscatedHighlightPainter extends BoxHighlightPainter { + + public ObfuscatedHighlightPainter() { + // red ish + super(new Color(255, 220, 220), new Color(160, 80, 80)); + } +} diff --git a/src/cuchaz/enigma/gui/OtherHighlightPainter.java b/src/cuchaz/enigma/gui/OtherHighlightPainter.java new file mode 100644 index 0000000..4e9c870 --- /dev/null +++ b/src/cuchaz/enigma/gui/OtherHighlightPainter.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.Color; + +public class OtherHighlightPainter extends BoxHighlightPainter { + + public OtherHighlightPainter() { + // grey + super(null, new Color(180, 180, 180)); + } +} diff --git a/src/cuchaz/enigma/gui/ProgressDialog.java b/src/cuchaz/enigma/gui/ProgressDialog.java new file mode 100644 index 0000000..b864fdb --- /dev/null +++ b/src/cuchaz/enigma/gui/ProgressDialog.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; + +import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.WindowConstants; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.Deobfuscator.ProgressListener; + +public class ProgressDialog implements ProgressListener, AutoCloseable { + + private JFrame m_frame; + private JLabel m_title; + private JLabel m_text; + private JProgressBar m_progress; + + public ProgressDialog(JFrame parent) { + + // init frame + m_frame = new JFrame(Constants.Name + " - Operation in progress"); + final Container pane = m_frame.getContentPane(); + FlowLayout layout = new FlowLayout(); + layout.setAlignment(FlowLayout.LEFT); + pane.setLayout(layout); + + m_title = new JLabel(); + pane.add(m_title); + + // set up the progress bar + JPanel panel = new JPanel(); + pane.add(panel); + panel.setLayout(new BorderLayout()); + m_text = GuiTricks.unboldLabel(new JLabel()); + m_progress = new JProgressBar(); + m_text.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + panel.add(m_text, BorderLayout.NORTH); + panel.add(m_progress, BorderLayout.CENTER); + panel.setPreferredSize(new Dimension(360, 50)); + + // show the frame + pane.doLayout(); + m_frame.setSize(400, 120); + m_frame.setResizable(false); + m_frame.setLocationRelativeTo(parent); + m_frame.setVisible(true); + m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } + + public void close() { + m_frame.dispose(); + } + + @Override + public void init(int totalWork, String title) { + m_title.setText(title); + m_progress.setMinimum(0); + m_progress.setMaximum(totalWork); + m_progress.setValue(0); + } + + @Override + public void onProgress(int numDone, String message) { + m_text.setText(message); + m_progress.setValue(numDone); + + // update the frame + m_frame.validate(); + m_frame.repaint(); + } + + public static interface ProgressRunnable { + void run(ProgressListener listener) throws Exception; + } + + public static void runInThread(final JFrame parent, final ProgressRunnable runnable) { + new Thread() { + @Override + public void run() { + try (ProgressDialog progress = new ProgressDialog(parent)) { + runnable.run(progress); + } catch (Exception ex) { + throw new Error(ex); + } + } + }.start(); + } +} diff --git a/src/cuchaz/enigma/gui/ReadableToken.java b/src/cuchaz/enigma/gui/ReadableToken.java new file mode 100644 index 0000000..66bcbc2 --- /dev/null +++ b/src/cuchaz/enigma/gui/ReadableToken.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +public class ReadableToken { + + public int line; + public int startColumn; + public int endColumn; + + public ReadableToken(int line, int startColumn, int endColumn) { + this.line = line; + this.startColumn = startColumn; + this.endColumn = endColumn; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("line "); + buf.append(line); + buf.append(" columns "); + buf.append(startColumn); + buf.append("-"); + buf.append(endColumn); + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/gui/RenameListener.java b/src/cuchaz/enigma/gui/RenameListener.java new file mode 100644 index 0000000..abeda0c --- /dev/null +++ b/src/cuchaz/enigma/gui/RenameListener.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import cuchaz.enigma.mapping.Entry; + +public interface RenameListener { + void rename(Entry obfEntry, String newName); +} diff --git a/src/cuchaz/enigma/gui/SelectionHighlightPainter.java b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java new file mode 100644 index 0000000..5e189d2 --- /dev/null +++ b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.Shape; + +import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; + +public class SelectionHighlightPainter implements Highlighter.HighlightPainter { + + @Override + public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { + // draw a thick border + Graphics2D g2d = (Graphics2D)g; + Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); + g2d.setColor(Color.black); + g2d.setStroke(new BasicStroke(2.0f)); + g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } +} diff --git a/src/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/cuchaz/enigma/gui/TokenListCellRenderer.java new file mode 100644 index 0000000..a49be37 --- /dev/null +++ b/src/cuchaz/enigma/gui/TokenListCellRenderer.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.gui; + +import java.awt.Component; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; + +import cuchaz.enigma.analysis.Token; + +public class TokenListCellRenderer implements ListCellRenderer { + + private GuiController m_controller; + private DefaultListCellRenderer m_defaultRenderer; + + public TokenListCellRenderer(GuiController controller) { + m_controller = controller; + m_defaultRenderer = new DefaultListCellRenderer(); + } + + @Override + public Component getListCellRendererComponent(JList list, Token token, int index, boolean isSelected, boolean hasFocus) { + JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); + label.setText(m_controller.getReadableToken(token).toString()); + return label; + } +} diff --git a/src/cuchaz/enigma/mapping/ArgumentEntry.java b/src/cuchaz/enigma/mapping/ArgumentEntry.java new file mode 100644 index 0000000..aa22265 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ArgumentEntry.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +import cuchaz.enigma.Util; + +public class ArgumentEntry implements Entry, Serializable { + + private static final long serialVersionUID = 4472172468162696006L; + + private BehaviorEntry m_behaviorEntry; + private int m_index; + private String m_name; + + public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) { + if (behaviorEntry == null) { + throw new IllegalArgumentException("Behavior cannot be null!"); + } + if (index < 0) { + throw new IllegalArgumentException("Index must be non-negative!"); + } + if (name == null) { + throw new IllegalArgumentException("Argument name cannot be null!"); + } + + m_behaviorEntry = behaviorEntry; + m_index = index; + m_name = name; + } + + public ArgumentEntry(ArgumentEntry other) { + m_behaviorEntry = (BehaviorEntry)m_behaviorEntry.cloneToNewClass(getClassEntry()); + m_index = other.m_index; + m_name = other.m_name; + } + + public ArgumentEntry(ArgumentEntry other, String newClassName) { + m_behaviorEntry = (BehaviorEntry)other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName)); + m_index = other.m_index; + m_name = other.m_name; + } + + public BehaviorEntry getBehaviorEntry() { + return m_behaviorEntry; + } + + public int getIndex() { + return m_index; + } + + @Override + public String getName() { + return m_name; + } + + @Override + public ClassEntry getClassEntry() { + return m_behaviorEntry.getClassEntry(); + } + + @Override + public String getClassName() { + return m_behaviorEntry.getClassName(); + } + + @Override + public ArgumentEntry cloneToNewClass(ClassEntry classEntry) { + return new ArgumentEntry(this, classEntry.getName()); + } + + public String getMethodName() { + return m_behaviorEntry.getName(); + } + + public Signature getMethodSignature() { + return m_behaviorEntry.getSignature(); + } + + @Override + public int hashCode() { + return Util.combineHashesOrdered( + m_behaviorEntry, + Integer.valueOf(m_index).hashCode(), + m_name.hashCode() + ); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ArgumentEntry) { + return equals((ArgumentEntry)other); + } + return false; + } + + public boolean equals(ArgumentEntry other) { + return m_behaviorEntry.equals(other.m_behaviorEntry) + && m_index == other.m_index + && m_name.equals(other.m_name); + } + + @Override + public String toString() { + return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")"; + } +} diff --git a/src/cuchaz/enigma/mapping/ArgumentMapping.java b/src/cuchaz/enigma/mapping/ArgumentMapping.java new file mode 100644 index 0000000..f4d8e77 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ArgumentMapping.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +public class ArgumentMapping implements Serializable, Comparable { + + private static final long serialVersionUID = 8610742471440861315L; + + private int m_index; + private String m_name; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public ArgumentMapping(int index, String name) { + m_index = index; + m_name = NameValidator.validateArgumentName(name); + } + + public int getIndex() { + return m_index; + } + + public String getName() { + return m_name; + } + + public void setName(String val) { + m_name = NameValidator.validateArgumentName(val); + } + + @Override + public int compareTo(ArgumentMapping other) { + return Integer.compare(m_index, other.m_index); + } +} diff --git a/src/cuchaz/enigma/mapping/BehaviorEntry.java b/src/cuchaz/enigma/mapping/BehaviorEntry.java new file mode 100644 index 0000000..535788f --- /dev/null +++ b/src/cuchaz/enigma/mapping/BehaviorEntry.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public interface BehaviorEntry extends Entry { + Signature getSignature(); +} diff --git a/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java new file mode 100644 index 0000000..61e501b --- /dev/null +++ b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import javassist.CtBehavior; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.bytecode.Descriptor; + +public class BehaviorEntryFactory { + + public static BehaviorEntry create(String className, String name, String signature) { + return create(new ClassEntry(className), name, signature); + } + + public static BehaviorEntry create(ClassEntry classEntry, String name, String signature) { + if (name.equals("")) { + return new ConstructorEntry(classEntry, new Signature(signature)); + } else if (name.equals("")) { + return new ConstructorEntry(classEntry); + } else { + return new MethodEntry(classEntry, name, new Signature(signature)); + } + } + + public static BehaviorEntry create(CtBehavior behavior) { + String className = Descriptor.toJvmName(behavior.getDeclaringClass().getName()); + if (behavior instanceof CtMethod) { + return create(className, behavior.getName(), behavior.getSignature()); + } else if (behavior instanceof CtConstructor) { + CtConstructor constructor = (CtConstructor)behavior; + if (constructor.isClassInitializer()) { + return create(className, "", null); + } else { + return create(className, "", constructor.getSignature()); + } + } else { + throw new IllegalArgumentException("Unable to create BehaviorEntry from " + behavior); + } + } + + public static BehaviorEntry createObf(ClassEntry classEntry, MethodMapping methodMapping) { + return create(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature().toString()); + } + + public static BehaviorEntry createDeobf(ClassEntry classEntry, MethodMapping methodMapping) { + return create(classEntry, methodMapping.getDeobfName(), methodMapping.getObfSignature().toString()); + } +} diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java new file mode 100644 index 0000000..cf41001 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ClassEntry.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +public class ClassEntry implements Entry, Serializable { + + private static final long serialVersionUID = 4235460580973955811L; + + private String m_name; + + public ClassEntry(String className) { + if (className == null) { + throw new IllegalArgumentException("Class name cannot be null!"); + } + if (className.indexOf('.') >= 0) { + throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); + } + + m_name = className; + + if (isInnerClass() && getInnerClassName().indexOf('/') >= 0) { + throw new IllegalArgumentException("Inner class must not have a package: " + className); + } + } + + public ClassEntry(ClassEntry other) { + m_name = other.m_name; + } + + @Override + public String getName() { + return m_name; + } + + @Override + public String getClassName() { + return m_name; + } + + @Override + public ClassEntry getClassEntry() { + return this; + } + + @Override + public ClassEntry cloneToNewClass(ClassEntry classEntry) { + return classEntry; + } + + @Override + public int hashCode() { + return m_name.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ClassEntry) { + return equals((ClassEntry)other); + } + return false; + } + + public boolean equals(ClassEntry other) { + return m_name.equals(other.m_name); + } + + @Override + public String toString() { + return m_name; + } + + public boolean isInnerClass() { + return m_name.lastIndexOf('$') >= 0; + } + + public String getOuterClassName() { + if (isInnerClass()) { + return m_name.substring(0, m_name.lastIndexOf('$')); + } + return m_name; + } + + public String getInnerClassName() { + if (!isInnerClass()) { + throw new Error("This is not an inner class!"); + } + return m_name.substring(m_name.lastIndexOf('$') + 1); + } + + public ClassEntry getOuterClassEntry() { + return new ClassEntry(getOuterClassName()); + } + + public boolean isInDefaultPackage() { + return m_name.indexOf('/') < 0; + } + + public String getPackageName() { + int pos = m_name.lastIndexOf('/'); + if (pos > 0) { + return m_name.substring(0, pos); + } + return null; + } + + public String getSimpleName() { + int pos = m_name.lastIndexOf('/'); + if (pos > 0) { + return m_name.substring(pos + 1); + } + return m_name; + } +} diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java new file mode 100644 index 0000000..e2c3d56 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -0,0 +1,405 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Map; + +import com.google.common.collect.Maps; + +public class ClassMapping implements Serializable, Comparable { + + private static final long serialVersionUID = -5148491146902340107L; + + private String m_obfName; + private String m_deobfName; + private Map m_innerClassesByObf; + private Map m_innerClassesByDeobf; + private Map m_fieldsByObf; + private Map m_fieldsByDeobf; + private Map m_methodsByObf; + private Map m_methodsByDeobf; + + public ClassMapping(String obfName) { + this(obfName, null); + } + + public ClassMapping(String obfName, String deobfName) { + m_obfName = obfName; + m_deobfName = NameValidator.validateClassName(deobfName, false); + m_innerClassesByObf = Maps.newHashMap(); + m_innerClassesByDeobf = Maps.newHashMap(); + m_fieldsByObf = Maps.newHashMap(); + m_fieldsByDeobf = Maps.newHashMap(); + m_methodsByObf = Maps.newHashMap(); + m_methodsByDeobf = Maps.newHashMap(); + } + + public String getObfName() { + return m_obfName; + } + + public String getDeobfName() { + return m_deobfName; + } + + public void setDeobfName(String val) { + m_deobfName = NameValidator.validateClassName(val, false); + } + + //// INNER CLASSES //////// + + public Iterable innerClasses() { + assert (m_innerClassesByObf.size() >= m_innerClassesByDeobf.size()); + return m_innerClassesByObf.values(); + } + + public void addInnerClassMapping(ClassMapping classMapping) { + assert (isSimpleClassName(classMapping.getObfName())); + boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfName(), classMapping) == null; + assert (obfWasAdded); + if (classMapping.getDeobfName() != null) { + assert (isSimpleClassName(classMapping.getDeobfName())); + boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null; + assert (deobfWasAdded); + } + } + + public void removeInnerClassMapping(ClassMapping classMapping) { + boolean obfWasRemoved = m_innerClassesByObf.remove(classMapping.getObfName()) != null; + assert (obfWasRemoved); + if (classMapping.getDeobfName() != null) { + boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (deobfWasRemoved); + } + } + + public ClassMapping getOrCreateInnerClass(String obfName) { + assert (isSimpleClassName(obfName)); + ClassMapping classMapping = m_innerClassesByObf.get(obfName); + if (classMapping == null) { + classMapping = new ClassMapping(obfName); + boolean wasAdded = m_innerClassesByObf.put(obfName, classMapping) == null; + assert (wasAdded); + } + return classMapping; + } + + public ClassMapping getInnerClassByObf(String obfName) { + assert (isSimpleClassName(obfName)); + return m_innerClassesByObf.get(obfName); + } + + public ClassMapping getInnerClassByDeobf(String deobfName) { + assert (isSimpleClassName(deobfName)); + return m_innerClassesByDeobf.get(deobfName); + } + + public ClassMapping getInnerClassByDeobfThenObf(String name) { + ClassMapping classMapping = getInnerClassByDeobf(name); + if (classMapping == null) { + classMapping = getInnerClassByObf(name); + } + return classMapping; + } + + public String getObfInnerClassName(String deobfName) { + assert (isSimpleClassName(deobfName)); + ClassMapping classMapping = m_innerClassesByDeobf.get(deobfName); + if (classMapping != null) { + return classMapping.getObfName(); + } + return null; + } + + public String getDeobfInnerClassName(String obfName) { + assert (isSimpleClassName(obfName)); + ClassMapping classMapping = m_innerClassesByObf.get(obfName); + if (classMapping != null) { + return classMapping.getDeobfName(); + } + return null; + } + + public void setInnerClassName(String obfName, String deobfName) { + assert (isSimpleClassName(obfName)); + ClassMapping classMapping = getOrCreateInnerClass(obfName); + if (classMapping.getDeobfName() != null) { + boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (wasRemoved); + } + classMapping.setDeobfName(deobfName); + if (deobfName != null) { + assert (isSimpleClassName(deobfName)); + boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null; + assert (wasAdded); + } + } + + //// FIELDS //////// + + public Iterable fields() { + assert (m_fieldsByObf.size() == m_fieldsByDeobf.size()); + return m_fieldsByObf.values(); + } + + public boolean containsObfField(String obfName) { + return m_fieldsByObf.containsKey(obfName); + } + + public boolean containsDeobfField(String deobfName) { + return m_fieldsByDeobf.containsKey(deobfName); + } + + public void addFieldMapping(FieldMapping fieldMapping) { + if (m_fieldsByObf.containsKey(fieldMapping.getObfName())) { + throw new Error("Already have mapping for " + m_obfName + "." + fieldMapping.getObfName()); + } + if (m_fieldsByDeobf.containsKey(fieldMapping.getDeobfName())) { + throw new Error("Already have mapping for " + m_deobfName + "." + fieldMapping.getDeobfName()); + } + boolean obfWasAdded = m_fieldsByObf.put(fieldMapping.getObfName(), fieldMapping) == null; + assert (obfWasAdded); + boolean deobfWasAdded = m_fieldsByDeobf.put(fieldMapping.getDeobfName(), fieldMapping) == null; + assert (deobfWasAdded); + assert (m_fieldsByObf.size() == m_fieldsByDeobf.size()); + } + + public void removeFieldMapping(FieldMapping fieldMapping) { + boolean obfWasRemoved = m_fieldsByObf.remove(fieldMapping.getObfName()) != null; + assert (obfWasRemoved); + if (fieldMapping.getDeobfName() != null) { + boolean deobfWasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null; + assert (deobfWasRemoved); + } + } + + public FieldMapping getFieldByObf(String obfName) { + return m_fieldsByObf.get(obfName); + } + + public FieldMapping getFieldByDeobf(String deobfName) { + return m_fieldsByDeobf.get(deobfName); + } + + public String getObfFieldName(String deobfName) { + FieldMapping fieldMapping = m_fieldsByDeobf.get(deobfName); + if (fieldMapping != null) { + return fieldMapping.getObfName(); + } + return null; + } + + public String getDeobfFieldName(String obfName) { + FieldMapping fieldMapping = m_fieldsByObf.get(obfName); + if (fieldMapping != null) { + return fieldMapping.getDeobfName(); + } + return null; + } + + public void setFieldName(String obfName, String deobfName) { + FieldMapping fieldMapping = m_fieldsByObf.get(obfName); + if (fieldMapping == null) { + fieldMapping = new FieldMapping(obfName, deobfName); + boolean obfWasAdded = m_fieldsByObf.put(obfName, fieldMapping) == null; + assert (obfWasAdded); + } else { + boolean wasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null; + assert (wasRemoved); + } + fieldMapping.setDeobfName(deobfName); + if (deobfName != null) { + boolean wasAdded = m_fieldsByDeobf.put(deobfName, fieldMapping) == null; + assert (wasAdded); + } + } + + //// METHODS //////// + + public Iterable methods() { + assert (m_methodsByObf.size() >= m_methodsByDeobf.size()); + return m_methodsByObf.values(); + } + + public boolean containsObfMethod(String obfName, Signature obfSignature) { + return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); + } + + public boolean containsDeobfMethod(String deobfName, Signature deobfSignature) { + return m_methodsByDeobf.containsKey(getMethodKey(deobfName, deobfSignature)); + } + + public void addMethodMapping(MethodMapping methodMapping) { + String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + if (m_methodsByObf.containsKey(obfKey)) { + throw new Error("Already have mapping for " + m_obfName + "." + obfKey); + } + boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null; + assert (wasAdded); + if (methodMapping.getDeobfName() != null) { + String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature()); + if (m_methodsByDeobf.containsKey(deobfKey)) { + throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey); + } + boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null; + assert (deobfWasAdded); + } + assert (m_methodsByObf.size() >= m_methodsByDeobf.size()); + } + + public void removeMethodMapping(MethodMapping methodMapping) { + boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null; + assert (obfWasRemoved); + if (methodMapping.getDeobfName() != null) { + boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + assert (deobfWasRemoved); + } + } + + public MethodMapping getMethodByObf(String obfName, Signature signature) { + return m_methodsByObf.get(getMethodKey(obfName, signature)); + } + + public MethodMapping getMethodByDeobf(String deobfName, Signature signature) { + return m_methodsByDeobf.get(getMethodKey(deobfName, signature)); + } + + private String getMethodKey(String name, Signature signature) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null!"); + } + if (signature == null) { + throw new IllegalArgumentException("signature cannot be null!"); + } + return name + signature; + } + + public void setMethodName(String obfName, Signature obfSignature, String deobfName) { + MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature)); + if (methodMapping == null) { + methodMapping = createMethodMapping(obfName, obfSignature); + } else if (methodMapping.getDeobfName() != null) { + boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + assert (wasRemoved); + } + methodMapping.setDeobfName(deobfName); + if (deobfName != null) { + boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null; + assert (wasAdded); + } + } + + //// ARGUMENTS //////// + + public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { + MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); + if (methodMapping == null) { + methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); + } + methodMapping.setArgumentName(argumentIndex, argumentName); + } + + public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) { + m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex); + } + + private MethodMapping createMethodMapping(String obfName, Signature obfSignature) { + MethodMapping methodMapping = new MethodMapping(obfName, obfSignature); + boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null; + assert (wasAdded); + return methodMapping; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(m_obfName); + buf.append(" <-> "); + buf.append(m_deobfName); + buf.append("\n"); + buf.append("Fields:\n"); + for (FieldMapping fieldMapping : fields()) { + buf.append("\t"); + buf.append(fieldMapping.getObfName()); + buf.append(" <-> "); + buf.append(fieldMapping.getDeobfName()); + buf.append("\n"); + } + buf.append("Methods:\n"); + for (MethodMapping methodMapping : m_methodsByObf.values()) { + buf.append(methodMapping.toString()); + buf.append("\n"); + } + buf.append("Inner Classes:\n"); + for (ClassMapping classMapping : m_innerClassesByObf.values()) { + buf.append("\t"); + buf.append(classMapping.getObfName()); + buf.append(" <-> "); + buf.append(classMapping.getDeobfName()); + buf.append("\n"); + } + return buf.toString(); + } + + @Override + public int compareTo(ClassMapping other) { + // sort by a, b, c, ... aa, ab, etc + if (m_obfName.length() != other.m_obfName.length()) { + return m_obfName.length() - other.m_obfName.length(); + } + return m_obfName.compareTo(other.m_obfName); + } + + public boolean renameObfClass(String oldObfClassName, String newObfClassName) { + + // rename inner classes + for (ClassMapping innerClassMapping : new ArrayList(m_innerClassesByObf.values())) { + if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) { + boolean wasRemoved = m_innerClassesByObf.remove(oldObfClassName) != null; + assert (wasRemoved); + boolean wasAdded = m_innerClassesByObf.put(newObfClassName, innerClassMapping) == null; + assert (wasAdded); + } + } + + // rename method signatures + for (MethodMapping methodMapping : new ArrayList(m_methodsByObf.values())) { + String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { + boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null; + assert (wasRemoved); + boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null; + assert (wasAdded); + } + } + + if (m_obfName.equals(oldObfClassName)) { + // rename this class + m_obfName = newObfClassName; + return true; + } + return false; + } + + public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { + MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); + if (methodMapping != null) { + return methodMapping.containsArgument(name); + } + return false; + } + + public static boolean isSimpleClassName(String name) { + return name.indexOf('/') < 0 && name.indexOf('$') < 0; + } +} diff --git a/src/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/cuchaz/enigma/mapping/ClassNameReplacer.java new file mode 100644 index 0000000..bf984fd --- /dev/null +++ b/src/cuchaz/enigma/mapping/ClassNameReplacer.java @@ -0,0 +1,5 @@ +package cuchaz.enigma.mapping; + +public interface ClassNameReplacer { + String replace(String className); +} diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java new file mode 100644 index 0000000..5f3760f --- /dev/null +++ b/src/cuchaz/enigma/mapping/ConstructorEntry.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +import cuchaz.enigma.Util; + +public class ConstructorEntry implements BehaviorEntry, Serializable { + + private static final long serialVersionUID = -868346075317366758L; + + private ClassEntry m_classEntry; + private Signature m_signature; + + public ConstructorEntry(ClassEntry classEntry) { + this(classEntry, null); + } + + public ConstructorEntry(ClassEntry classEntry, Signature signature) { + if (classEntry == null) { + throw new IllegalArgumentException("Class cannot be null!"); + } + + m_classEntry = classEntry; + m_signature = signature; + } + + public ConstructorEntry(ConstructorEntry other) { + m_classEntry = new ClassEntry(other.m_classEntry); + m_signature = other.m_signature; + } + + public ConstructorEntry(ConstructorEntry other, String newClassName) { + m_classEntry = new ClassEntry(newClassName); + m_signature = other.m_signature; + } + + @Override + public ClassEntry getClassEntry() { + return m_classEntry; + } + + @Override + public String getName() { + if (isStatic()) { + return ""; + } + return ""; + } + + public boolean isStatic() { + return m_signature == null; + } + + @Override + public Signature getSignature() { + return m_signature; + } + + @Override + public String getClassName() { + return m_classEntry.getName(); + } + + @Override + public ConstructorEntry cloneToNewClass(ClassEntry classEntry) { + return new ConstructorEntry(this, classEntry.getName()); + } + + @Override + public int hashCode() { + if (isStatic()) { + return Util.combineHashesOrdered(m_classEntry); + } else { + return Util.combineHashesOrdered(m_classEntry, m_signature); + } + } + + @Override + public boolean equals(Object other) { + if (other instanceof ConstructorEntry) { + return equals((ConstructorEntry)other); + } + return false; + } + + public boolean equals(ConstructorEntry other) { + if (isStatic() != other.isStatic()) { + return false; + } + + if (isStatic()) { + return m_classEntry.equals(other.m_classEntry); + } else { + return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature); + } + } + + @Override + public String toString() { + if (isStatic()) { + return m_classEntry.getName() + "." + getName(); + } else { + return m_classEntry.getName() + "." + getName() + m_signature; + } + } +} diff --git a/src/cuchaz/enigma/mapping/Entry.java b/src/cuchaz/enigma/mapping/Entry.java new file mode 100644 index 0000000..39e1507 --- /dev/null +++ b/src/cuchaz/enigma/mapping/Entry.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public interface Entry { + String getName(); + String getClassName(); + ClassEntry getClassEntry(); + Entry cloneToNewClass(ClassEntry classEntry); +} diff --git a/src/cuchaz/enigma/mapping/EntryPair.java b/src/cuchaz/enigma/mapping/EntryPair.java new file mode 100644 index 0000000..60411c4 --- /dev/null +++ b/src/cuchaz/enigma/mapping/EntryPair.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public class EntryPair { + + public T obf; + public T deobf; + + public EntryPair(T obf, T deobf) { + this.obf = obf; + this.deobf = deobf; + } +} diff --git a/src/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java new file mode 100644 index 0000000..6cc9eb7 --- /dev/null +++ b/src/cuchaz/enigma/mapping/FieldEntry.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +import cuchaz.enigma.Util; + +public class FieldEntry implements Entry, Serializable { + + private static final long serialVersionUID = 3004663582802885451L; + + private ClassEntry m_classEntry; + private String m_name; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public FieldEntry(ClassEntry classEntry, String name) { + if (classEntry == null) { + throw new IllegalArgumentException("Class cannot be null!"); + } + if (name == null) { + throw new IllegalArgumentException("Field name cannot be null!"); + } + + m_classEntry = classEntry; + m_name = name; + } + + public FieldEntry(FieldEntry other) { + m_classEntry = new ClassEntry(other.m_classEntry); + m_name = other.m_name; + } + + public FieldEntry(FieldEntry other, String newClassName) { + m_classEntry = new ClassEntry(newClassName); + m_name = other.m_name; + } + + @Override + public ClassEntry getClassEntry() { + return m_classEntry; + } + + @Override + public String getName() { + return m_name; + } + + @Override + public String getClassName() { + return m_classEntry.getName(); + } + + @Override + public FieldEntry cloneToNewClass(ClassEntry classEntry) { + return new FieldEntry(this, classEntry.getName()); + } + + @Override + public int hashCode() { + return Util.combineHashesOrdered(m_classEntry, m_name); + } + + @Override + public boolean equals(Object other) { + if (other instanceof FieldEntry) { + return equals((FieldEntry)other); + } + return false; + } + + public boolean equals(FieldEntry other) { + return m_classEntry.equals(other.m_classEntry) && m_name.equals(other.m_name); + } + + @Override + public String toString() { + return m_classEntry.getName() + "." + m_name; + } +} diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java new file mode 100644 index 0000000..5f5c270 --- /dev/null +++ b/src/cuchaz/enigma/mapping/FieldMapping.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +public class FieldMapping implements Serializable, Comparable { + + private static final long serialVersionUID = 8610742471440861315L; + + private String m_obfName; + private String m_deobfName; + + public FieldMapping(String obfName, String deobfName) { + m_obfName = obfName; + m_deobfName = NameValidator.validateFieldName(deobfName); + } + + public String getObfName() { + return m_obfName; + } + + public String getDeobfName() { + return m_deobfName; + } + + public void setDeobfName(String val) { + m_deobfName = NameValidator.validateFieldName(val); + } + + @Override + public int compareTo(FieldMapping other) { + return m_obfName.compareTo(other.m_obfName); + } +} diff --git a/src/cuchaz/enigma/mapping/IllegalNameException.java b/src/cuchaz/enigma/mapping/IllegalNameException.java new file mode 100644 index 0000000..aacaf3b --- /dev/null +++ b/src/cuchaz/enigma/mapping/IllegalNameException.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public class IllegalNameException extends RuntimeException { + + private static final long serialVersionUID = -2279910052561114323L; + + private String m_name; + private String m_reason; + + public IllegalNameException(String name) { + this(name, null); + } + + public IllegalNameException(String name, String reason) { + m_name = name; + m_reason = reason; + } + + public String getReason() { + return m_reason; + } + + @Override + public String getMessage() { + StringBuilder buf = new StringBuilder(); + buf.append("Illegal name: "); + buf.append(m_name); + if (m_reason != null) { + buf.append(" because "); + buf.append(m_reason); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/mapping/JavassistUtil.java b/src/cuchaz/enigma/mapping/JavassistUtil.java new file mode 100644 index 0000000..0c446c4 --- /dev/null +++ b/src/cuchaz/enigma/mapping/JavassistUtil.java @@ -0,0 +1,83 @@ +package cuchaz.enigma.mapping; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtField; +import javassist.CtMethod; +import javassist.bytecode.Descriptor; +import javassist.expr.ConstructorCall; +import javassist.expr.FieldAccess; +import javassist.expr.MethodCall; +import javassist.expr.NewExpr; + +public class JavassistUtil { + + public static ClassEntry getClassEntry(CtClass c) { + return new ClassEntry(Descriptor.toJvmName(c.getName())); + } + + public static ClassEntry getSuperclassEntry(CtClass c) { + return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); + } + + public static MethodEntry getMethodEntry(CtMethod method) { + return new MethodEntry( + getClassEntry(method.getDeclaringClass()), + method.getName(), + new Signature(method.getMethodInfo().getDescriptor()) + ); + } + + public static MethodEntry getMethodEntry(MethodCall call) { + return new MethodEntry( + new ClassEntry(Descriptor.toJvmName(call.getClassName())), + call.getMethodName(), + new Signature(call.getSignature()) + ); + } + + public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { + return new ConstructorEntry( + getClassEntry(constructor.getDeclaringClass()), + new Signature(constructor.getMethodInfo().getDescriptor()) + ); + } + + public static ConstructorEntry getConstructorEntry(ConstructorCall call) { + return new ConstructorEntry( + new ClassEntry(Descriptor.toJvmName(call.getClassName())), + new Signature(call.getSignature()) + ); + } + + public static ConstructorEntry getConstructorEntry(NewExpr call) { + return new ConstructorEntry( + new ClassEntry(Descriptor.toJvmName(call.getClassName())), + new Signature(call.getSignature()) + ); + } + + public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { + if (behavior instanceof CtMethod) { + return getMethodEntry((CtMethod)behavior); + } else if (behavior instanceof CtConstructor) { + return getConstructorEntry((CtConstructor)behavior); + } + throw new Error("behavior is neither Method nor Constructor!"); + } + + public static FieldEntry getFieldEntry(CtField field) { + return new FieldEntry( + getClassEntry(field.getDeclaringClass()), + field.getName() + ); + } + + public static FieldEntry getFieldEntry(FieldAccess call) { + return new FieldEntry( + new ClassEntry(Descriptor.toJvmName(call.getClassName())), + call.getFieldName() + ); + } +} diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/cuchaz/enigma/mapping/MappingParseException.java new file mode 100644 index 0000000..1974c22 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingParseException.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public class MappingParseException extends Exception { + + private static final long serialVersionUID = -5487280332892507236L; + + private int m_line; + private String m_message; + + public MappingParseException(int line, String message) { + m_line = line; + m_message = message; + } + + @Override + public String getMessage() { + return "Line " + m_line + ": " + m_message; + } +} diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java new file mode 100644 index 0000000..57d8001 --- /dev/null +++ b/src/cuchaz/enigma/mapping/Mappings.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cuchaz.enigma.analysis.TranslationIndex; + +public class Mappings implements Serializable { + + private static final long serialVersionUID = 4649790259460259026L; + + protected Map m_classesByObf; + protected Map m_classesByDeobf; + + public Mappings() { + m_classesByObf = Maps.newHashMap(); + m_classesByDeobf = Maps.newHashMap(); + } + + public Mappings(Iterable classes) { + this(); + + for (ClassMapping classMapping : classes) { + m_classesByObf.put(classMapping.getObfName(), classMapping); + if (classMapping.getDeobfName() != null) { + m_classesByDeobf.put(classMapping.getDeobfName(), classMapping); + } + } + } + + public Collection classes() { + assert (m_classesByObf.size() >= m_classesByDeobf.size()); + return m_classesByObf.values(); + } + + public void addClassMapping(ClassMapping classMapping) { + if (m_classesByObf.containsKey(classMapping.getObfName())) { + throw new Error("Already have mapping for " + classMapping.getObfName()); + } + boolean obfWasAdded = m_classesByObf.put(classMapping.getObfName(), classMapping) == null; + assert (obfWasAdded); + if (classMapping.getDeobfName() != null) { + if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) { + throw new Error("Already have mapping for " + classMapping.getDeobfName()); + } + boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null; + assert (deobfWasAdded); + } + } + + public void removeClassMapping(ClassMapping classMapping) { + boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfName()) != null; + assert (obfWasRemoved); + if (classMapping.getDeobfName() != null) { + boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (deobfWasRemoved); + } + } + + public ClassMapping getClassByObf(ClassEntry entry) { + return getClassByObf(entry.getName()); + } + + public ClassMapping getClassByObf(String obfName) { + return m_classesByObf.get(obfName); + } + + public ClassMapping getClassByDeobf(ClassEntry entry) { + return getClassByDeobf(entry.getName()); + } + + public ClassMapping getClassByDeobf(String deobfName) { + return m_classesByDeobf.get(deobfName); + } + + public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { + switch (direction) { + case Deobfuscating: + + return new Translator(direction, m_classesByObf, index); + + case Obfuscating: + + // fill in the missing deobf class entries with obf entries + Map classes = Maps.newHashMap(); + for (ClassMapping classMapping : classes()) { + if (classMapping.getDeobfName() != null) { + classes.put(classMapping.getDeobfName(), classMapping); + } else { + classes.put(classMapping.getObfName(), classMapping); + } + } + + // translate the translation index + // NOTE: this isn't actually recursive + TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index)); + + return new Translator(direction, classes, deobfIndex); + + default: + throw new Error("Invalid translation direction!"); + } + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + for (ClassMapping classMapping : m_classesByObf.values()) { + buf.append(classMapping.toString()); + buf.append("\n"); + } + return buf.toString(); + } + + public void renameObfClass(String oldObfName, String newObfName) { + for (ClassMapping classMapping : new ArrayList(classes())) { + if (classMapping.renameObfClass(oldObfName, newObfName)) { + boolean wasRemoved = m_classesByObf.remove(oldObfName) != null; + assert (wasRemoved); + boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null; + assert (wasAdded); + } + } + } + + public Set getAllObfClassNames() { + final Set classNames = Sets.newHashSet(); + for (ClassMapping classMapping : classes()) { + + // add the class name + classNames.add(classMapping.getObfName()); + + // add classes from method signatures + for (MethodMapping methodMapping : classMapping.methods()) { + for (Type type : methodMapping.getObfSignature().types()) { + if (type.hasClass()) { + classNames.add(type.getClassEntry().getClassName()); + } + } + } + } + return classNames; + } + + public boolean containsDeobfClass(String deobfName) { + return m_classesByDeobf.containsKey(deobfName); + } + + public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { + ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName()); + if (classMapping != null) { + return classMapping.containsDeobfField(deobfName); + } + return false; + } + + public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) { + ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName()); + if (classMapping != null) { + return classMapping.containsDeobfMethod(deobfName, deobfSignature); + } + return false; + } + + public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { + ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName()); + if (classMapping != null) { + return classMapping.containsArgument(obfBehaviorEntry, name); + } + return false; + } +} diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java new file mode 100644 index 0000000..adf460e --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsReader.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.Deque; + +import com.google.common.collect.Queues; + +import cuchaz.enigma.Constants; + +public class MappingsReader { + + public Mappings read(Reader in) throws IOException, MappingParseException { + return read(new BufferedReader(in)); + } + + public Mappings read(BufferedReader in) throws IOException, MappingParseException { + Mappings mappings = new Mappings(); + Deque mappingStack = Queues.newArrayDeque(); + + int lineNumber = 0; + String line = null; + while ( (line = in.readLine()) != null) { + lineNumber++; + + // strip comments + int commentPos = line.indexOf('#'); + if (commentPos >= 0) { + line = line.substring(0, commentPos); + } + + // skip blank lines + if (line.trim().length() <= 0) { + continue; + } + + // get the indent of this line + int indent = 0; + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) != '\t') { + break; + } + indent++; + } + + // handle stack pops + while (indent < mappingStack.size()) { + mappingStack.pop(); + } + + String[] parts = line.trim().split("\\s"); + try { + // read the first token + String token = parts[0]; + + if (token.equalsIgnoreCase("CLASS")) { + ClassMapping classMapping; + if (indent == 0) { + // outer class + classMapping = readClass(parts, false); + mappings.addClassMapping(classMapping); + } else if (indent == 1) { + // inner class + if (! (mappingStack.getFirst() instanceof ClassMapping)) { + throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!"); + } + + classMapping = readClass(parts, true); + ((ClassMapping)mappingStack.getFirst()).addInnerClassMapping(classMapping); + } else { + throw new MappingParseException(lineNumber, "Unexpected CLASS entry nesting!"); + } + mappingStack.push(classMapping); + } else if (token.equalsIgnoreCase("FIELD")) { + if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) { + throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!"); + } + ((ClassMapping)mappingStack.getFirst()).addFieldMapping(readField(parts)); + } else if (token.equalsIgnoreCase("METHOD")) { + if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) { + throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!"); + } + MethodMapping methodMapping = readMethod(parts); + ((ClassMapping)mappingStack.getFirst()).addMethodMapping(methodMapping); + mappingStack.push(methodMapping); + } else if (token.equalsIgnoreCase("ARG")) { + if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof MethodMapping)) { + throw new MappingParseException(lineNumber, "Unexpected ARG entry here!"); + } + ((MethodMapping)mappingStack.getFirst()).addArgumentMapping(readArgument(parts)); + } + } catch (ArrayIndexOutOfBoundsException | NumberFormatException ex) { + throw new MappingParseException(lineNumber, "Malformed line!"); + } + } + + return mappings; + } + + private ArgumentMapping readArgument(String[] parts) { + return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); + } + + private ClassMapping readClass(String[] parts, boolean makeSimple) { + if (parts.length == 2) { + String obfName = processName(parts[1], makeSimple); + return new ClassMapping(obfName); + } else { + String obfName = processName(parts[1], makeSimple); + String deobfName = processName(parts[2], makeSimple); + return new ClassMapping(obfName, deobfName); + } + } + + private String processName(String name, boolean makeSimple) { + if (makeSimple) { + return new ClassEntry(name).getSimpleName(); + } else { + return moveClassOutOfDefaultPackage(name, Constants.NonePackage); + } + } + + private String moveClassOutOfDefaultPackage(String className, String newPackageName) { + ClassEntry classEntry = new ClassEntry(className); + if (classEntry.isInDefaultPackage()) { + return newPackageName + "/" + classEntry.getName(); + } + return className; + } + + private FieldMapping readField(String[] parts) { + return new FieldMapping(parts[1], parts[2]); + } + + private MethodMapping readMethod(String[] parts) { + if (parts.length == 3) { + String obfName = parts[1]; + Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[2]), Constants.NonePackage); + return new MethodMapping(obfName, obfSignature); + } else { + String obfName = parts[1]; + String deobfName = parts[2]; + Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[3]), Constants.NonePackage); + if (obfName.equals(deobfName)) { + return new MethodMapping(obfName, obfSignature); + } else { + return new MethodMapping(obfName, obfSignature, deobfName); + } + } + } + + private Signature moveSignatureOutOfDefaultPackage(Signature signature, final String newPackageName) { + return new Signature(signature, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry classEntry = new ClassEntry(className); + if (classEntry.isInDefaultPackage()) { + return newPackageName + "/" + className; + } + return null; + } + }); + } +} diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java new file mode 100644 index 0000000..0a41c2b --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.Set; +import java.util.zip.GZIPOutputStream; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.analysis.JarIndex; + +public class MappingsRenamer { + + private JarIndex m_index; + private Mappings m_mappings; + + public MappingsRenamer(JarIndex index, Mappings mappings) { + m_index = index; + m_mappings = mappings; + } + + public void setClassName(ClassEntry obf, String deobfName) { + deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass()); + ClassEntry targetEntry = new ClassEntry(deobfName); + if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(targetEntry)) { + throw new IllegalNameException(deobfName, "There is already a class with that name"); + } + + ClassMapping classMapping = getOrCreateClassMapping(obf); + + if (obf.isInnerClass()) { + classMapping.setInnerClassName(obf.getInnerClassName(), deobfName); + } else { + if (classMapping.getDeobfName() != null) { + boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (wasRemoved); + } + classMapping.setDeobfName(deobfName); + boolean wasAdded = m_mappings.m_classesByDeobf.put(deobfName, classMapping) == null; + assert (wasAdded); + } + } + + public void removeClassMapping(ClassEntry obf) { + ClassMapping classMapping = getClassMapping(obf); + if (obf.isInnerClass()) { + classMapping.setInnerClassName(obf.getName(), null); + } else { + boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (wasRemoved); + classMapping.setDeobfName(null); + } + } + + public void markClassAsDeobfuscated(ClassEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf); + if (obf.isInnerClass()) { + String innerClassName = Constants.NonePackage + "/" + obf.getInnerClassName(); + classMapping.setInnerClassName(innerClassName, innerClassName); + } else { + classMapping.setDeobfName(obf.getName()); + boolean wasAdded = m_mappings.m_classesByDeobf.put(obf.getName(), classMapping) == null; + assert (wasAdded); + } + } + + public void setFieldName(FieldEntry obf, String deobfName) { + deobfName = NameValidator.validateFieldName(deobfName); + FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName); + if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName) || m_index.containsObfField(targetEntry)) { + throw new IllegalNameException(deobfName, "There is already a field with that name"); + } + + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setFieldName(obf.getName(), deobfName); + } + + public void removeFieldMapping(FieldEntry obf) { + ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setFieldName(obf.getName(), null); + } + + public void markFieldAsDeobfuscated(FieldEntry obf) { + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getName()); + } + + public void setMethodTreeName(MethodEntry obf, String deobfName) { + Set implementations = m_index.getRelatedMethodImplementations(obf); + + deobfName = NameValidator.validateMethodName(deobfName); + for (MethodEntry entry : implementations) { + Signature deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature()); + MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature); + if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) { + String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName()); + throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); + } + } + + for (MethodEntry entry : implementations) { + setMethodName(entry, deobfName); + } + } + + public void setMethodName(MethodEntry obf, String deobfName) { + deobfName = NameValidator.validateMethodName(deobfName); + MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature()); + if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) { + String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName()); + throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); + } + + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); + } + + public void removeMethodTreeMapping(MethodEntry obf) { + for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) { + removeMethodMapping(implementation); + } + } + + public void removeMethodMapping(MethodEntry obf) { + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getSignature(), null); + } + + public void markMethodTreeAsDeobfuscated(MethodEntry obf) { + for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) { + markMethodAsDeobfuscated(implementation); + } + } + + public void markMethodAsDeobfuscated(MethodEntry obf) { + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); + } + + public void setArgumentName(ArgumentEntry obf, String deobfName) { + deobfName = NameValidator.validateArgumentName(deobfName); + // NOTE: don't need to check arguments for name collisions with names determined by Procyon + if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) { + throw new IllegalNameException(deobfName, "There is already an argument with that name"); + } + + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); + } + + public void removeArgumentMapping(ArgumentEntry obf) { + ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); + } + + public void markArgumentAsDeobfuscated(ArgumentEntry obf) { + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); + } + + public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { + classMapping.removeFieldMapping(fieldMapping); + ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); + if (!targetClassMapping.containsObfField(fieldMapping.getObfName())) { + if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName())) { + targetClassMapping.addFieldMapping(fieldMapping); + return true; + } else { + System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName()); + } + } + return false; + } + + public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) { + classMapping.removeMethodMapping(methodMapping); + ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); + if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) { + if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) { + targetClassMapping.addMethodMapping(methodMapping); + return true; + } else { + System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature()); + } + } + return false; + } + + public void write(OutputStream out) throws IOException { + // TEMP: just use the object output for now. We can find a more efficient storage format later + GZIPOutputStream gzipout = new GZIPOutputStream(out); + ObjectOutputStream oout = new ObjectOutputStream(gzipout); + oout.writeObject(this); + gzipout.finish(); + } + + private ClassMapping getClassMapping(ClassEntry obfClassEntry) { + return m_mappings.m_classesByObf.get(obfClassEntry.getOuterClassName()); + } + + private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) { + String obfClassName = obfClassEntry.getOuterClassName(); + ClassMapping classMapping = m_mappings.m_classesByObf.get(obfClassName); + if (classMapping == null) { + classMapping = new ClassMapping(obfClassName); + boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfName(), classMapping) == null; + assert (obfWasAdded); + } + return classMapping; + } + + private ClassMapping getClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) { + ClassMapping classMapping = getClassMapping(obfClassEntry); + if (obfClassEntry.isInDefaultPackage()) { + classMapping = classMapping.getInnerClassByObf(obfClassEntry.getInnerClassName()); + } + return classMapping; + } + + private ClassMapping getOrCreateClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfClassEntry); + if (obfClassEntry.isInnerClass()) { + classMapping = classMapping.getOrCreateInnerClass(obfClassEntry.getInnerClassName()); + } + return classMapping; + } +} diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java new file mode 100644 index 0000000..5ac409f --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsWriter.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class MappingsWriter { + + public void write(Writer out, Mappings mappings) throws IOException { + write(new PrintWriter(out), mappings); + } + + public void write(PrintWriter out, Mappings mappings) throws IOException { + for (ClassMapping classMapping : sorted(mappings.classes())) { + write(out, classMapping, 0); + } + } + + private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { + if (classMapping.getDeobfName() == null) { + out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfName()); + } else { + out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfName(), classMapping.getDeobfName()); + } + + for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { + write(out, innerClassMapping, depth + 1); + } + + for (FieldMapping fieldMapping : sorted(classMapping.fields())) { + write(out, fieldMapping, depth + 1); + } + + for (MethodMapping methodMapping : sorted(classMapping.methods())) { + write(out, methodMapping, depth + 1); + } + } + + private void write(PrintWriter out, FieldMapping fieldMapping, int depth) throws IOException { + out.format("%sFIELD %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName()); + } + + private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { + if (methodMapping.getDeobfName() == null) { + out.format("%sMETHOD %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature()); + } else { + out.format("%sMETHOD %s %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature()); + } + + for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) { + write(out, argumentMapping, depth + 1); + } + } + + private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) throws IOException { + out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName()); + } + + private > List sorted(Iterable classes) { + List out = new ArrayList(); + for (T t : classes) { + out.add(t); + } + Collections.sort(out); + return out; + } + + private String getIndent(int depth) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < depth; i++) { + buf.append("\t"); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/mapping/MethodEntry.java b/src/cuchaz/enigma/mapping/MethodEntry.java new file mode 100644 index 0000000..057e02b --- /dev/null +++ b/src/cuchaz/enigma/mapping/MethodEntry.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +import cuchaz.enigma.Util; + +public class MethodEntry implements BehaviorEntry, Serializable { + + private static final long serialVersionUID = 4770915224467247458L; + + private ClassEntry m_classEntry; + private String m_name; + private Signature m_signature; + + public MethodEntry(ClassEntry classEntry, String name, Signature signature) { + if (classEntry == null) { + throw new IllegalArgumentException("Class cannot be null!"); + } + if (name == null) { + throw new IllegalArgumentException("Method name cannot be null!"); + } + if (signature == null) { + throw new IllegalArgumentException("Method signature cannot be null!"); + } + if (name.startsWith("<")) { + throw new IllegalArgumentException("Don't use MethodEntry for a constructor!"); + } + + m_classEntry = classEntry; + m_name = name; + m_signature = signature; + } + + public MethodEntry(MethodEntry other) { + m_classEntry = new ClassEntry(other.m_classEntry); + m_name = other.m_name; + m_signature = other.m_signature; + } + + public MethodEntry(MethodEntry other, String newClassName) { + m_classEntry = new ClassEntry(newClassName); + m_name = other.m_name; + m_signature = other.m_signature; + } + + @Override + public ClassEntry getClassEntry() { + return m_classEntry; + } + + @Override + public String getName() { + return m_name; + } + + @Override + public Signature getSignature() { + return m_signature; + } + + @Override + public String getClassName() { + return m_classEntry.getName(); + } + + @Override + public MethodEntry cloneToNewClass(ClassEntry classEntry) { + return new MethodEntry(this, classEntry.getName()); + } + + @Override + public int hashCode() { + return Util.combineHashesOrdered(m_classEntry, m_name, m_signature); + } + + @Override + public boolean equals(Object other) { + if (other instanceof MethodEntry) { + return equals((MethodEntry)other); + } + return false; + } + + public boolean equals(MethodEntry other) { + return m_classEntry.equals(other.m_classEntry) + && m_name.equals(other.m_name) + && m_signature.equals(other.m_signature); + } + + @Override + public String toString() { + return m_classEntry.getName() + "." + m_name + m_signature; + } +} diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java new file mode 100644 index 0000000..1704428 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MethodMapping.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; +import java.util.Map; +import java.util.TreeMap; + +public class MethodMapping implements Serializable, Comparable { + + private static final long serialVersionUID = -4409570216084263978L; + + private String m_obfName; + private String m_deobfName; + private Signature m_obfSignature; + private Map m_arguments; + + public MethodMapping(String obfName, Signature obfSignature) { + this(obfName, obfSignature, null); + } + + public MethodMapping(String obfName, Signature obfSignature, String deobfName) { + if (obfName == null) { + throw new IllegalArgumentException("obf name cannot be null!"); + } + if (obfSignature == null) { + throw new IllegalArgumentException("obf signature cannot be null!"); + } + m_obfName = obfName; + m_deobfName = NameValidator.validateMethodName(deobfName); + m_obfSignature = obfSignature; + m_arguments = new TreeMap(); + } + + public String getObfName() { + return m_obfName; + } + + public String getDeobfName() { + return m_deobfName; + } + + public void setDeobfName(String val) { + m_deobfName = NameValidator.validateMethodName(val); + } + + public Signature getObfSignature() { + return m_obfSignature; + } + + public Iterable arguments() { + return m_arguments.values(); + } + + public boolean isConstructor() { + return m_obfName.startsWith("<"); + } + + public void addArgumentMapping(ArgumentMapping argumentMapping) { + boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null; + assert (wasAdded); + } + + public String getObfArgumentName(int index) { + ArgumentMapping argumentMapping = m_arguments.get(index); + if (argumentMapping != null) { + return argumentMapping.getName(); + } + + return null; + } + + public String getDeobfArgumentName(int index) { + ArgumentMapping argumentMapping = m_arguments.get(index); + if (argumentMapping != null) { + return argumentMapping.getName(); + } + + return null; + } + + public void setArgumentName(int index, String name) { + ArgumentMapping argumentMapping = m_arguments.get(index); + if (argumentMapping == null) { + argumentMapping = new ArgumentMapping(index, name); + boolean wasAdded = m_arguments.put(index, argumentMapping) == null; + assert (wasAdded); + } else { + argumentMapping.setName(name); + } + } + + public void removeArgumentName(int index) { + boolean wasRemoved = m_arguments.remove(index) != null; + assert (wasRemoved); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("\t"); + buf.append(m_obfName); + buf.append(" <-> "); + buf.append(m_deobfName); + buf.append("\n"); + buf.append("\t"); + buf.append(m_obfSignature); + buf.append("\n"); + buf.append("\tArguments:\n"); + for (ArgumentMapping argumentMapping : m_arguments.values()) { + buf.append("\t\t"); + buf.append(argumentMapping.getIndex()); + buf.append(" -> "); + buf.append(argumentMapping.getName()); + buf.append("\n"); + } + return buf.toString(); + } + + @Override + public int compareTo(MethodMapping other) { + return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature); + } + + public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { + + // rename obf classes in the signature + Signature newSignature = new Signature(m_obfSignature, new ClassNameReplacer() { + @Override + public String replace(String className) { + if (className.equals(oldObfClassName)) { + return newObfClassName; + } + return null; + } + }); + + if (!newSignature.equals(m_obfSignature)) { + m_obfSignature = newSignature; + return true; + } + return false; + } + + public boolean containsArgument(String name) { + for (ArgumentMapping argumentMapping : m_arguments.values()) { + if (argumentMapping.getName().equals(name)) { + return true; + } + } + return false; + } +} diff --git a/src/cuchaz/enigma/mapping/NameValidator.java b/src/cuchaz/enigma/mapping/NameValidator.java new file mode 100644 index 0000000..35a17f9 --- /dev/null +++ b/src/cuchaz/enigma/mapping/NameValidator.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import javassist.bytecode.Descriptor; + +public class NameValidator { + + private static final Pattern IdentifierPattern; + private static final Pattern ClassPattern; + private static final List ReservedWords = Arrays.asList( + "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", + "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", + "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", + "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", + "long", "strictfp", "volatile", "const", "float", "native", "super", "while" + ); + + static { + + // java allows all kinds of weird characters... + StringBuilder startChars = new StringBuilder(); + StringBuilder partChars = new StringBuilder(); + for (int i = Character.MIN_CODE_POINT; i <= Character.MAX_CODE_POINT; i++) { + if (Character.isJavaIdentifierStart(i)) { + startChars.appendCodePoint(i); + } + if (Character.isJavaIdentifierPart(i)) { + partChars.appendCodePoint(i); + } + } + + String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; + IdentifierPattern = Pattern.compile(identifierRegex); + ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); + } + + public static String validateClassName(String name, boolean packageRequired) { + if (name == null) { + return null; + } + if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal class name"); + } + if (packageRequired && new ClassEntry(name).getPackageName() == null) { + throw new IllegalNameException(name, "Class must be in a package"); + } + return Descriptor.toJvmName(name); + } + + public static String validateFieldName(String name) { + if (name == null) { + return null; + } + if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal identifier"); + } + return name; + } + + public static String validateMethodName(String name) { + return validateFieldName(name); + } + + public static String validateArgumentName(String name) { + return validateFieldName(name); + } +} diff --git a/src/cuchaz/enigma/mapping/Signature.java b/src/cuchaz/enigma/mapping/Signature.java new file mode 100644 index 0000000..ff7f807 --- /dev/null +++ b/src/cuchaz/enigma/mapping/Signature.java @@ -0,0 +1,109 @@ +package cuchaz.enigma.mapping; + +import java.util.List; + +import com.beust.jcommander.internal.Lists; + +import cuchaz.enigma.Util; + +public class Signature { + + private List m_argumentTypes; + private Type m_returnType; + + public Signature(String signature) { + try { + m_argumentTypes = Lists.newArrayList(); + int i=0; + while (i getArgumentTypes() { + return m_argumentTypes; + } + + public Type getReturnType() { + return m_returnType; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + for (Type type : m_argumentTypes) { + buf.append(type.toString()); + } + buf.append(")"); + buf.append(m_returnType.toString()); + return buf.toString(); + } + + public Iterable types() { + List types = Lists.newArrayList(); + types.addAll(m_argumentTypes); + types.add(m_returnType); + return types; + } + + public Iterable classes() { + List out = Lists.newArrayList(); + for (Type type : types()) { + if (type.isClass()) { + out.add(type.getClassEntry()); + } + } + return out; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Signature) { + return equals((Signature)other); + } + return false; + } + + public boolean equals(Signature other) { + return m_argumentTypes.equals(other.m_argumentTypes) && m_returnType.equals(other.m_returnType); + } + + @Override + public int hashCode() { + return Util.combineHashesOrdered(m_argumentTypes.hashCode(), m_returnType.hashCode()); + } + + public boolean hasClass(ClassEntry classEntry) { + for (Type type : types()) { + if (type.hasClass() && type.getClassEntry().equals(classEntry)) { + return true; + } + } + return false; + } +} diff --git a/src/cuchaz/enigma/mapping/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java new file mode 100644 index 0000000..3477cd5 --- /dev/null +++ b/src/cuchaz/enigma/mapping/SignatureUpdater.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +import com.google.common.collect.Lists; + +public class SignatureUpdater { + + public interface ClassNameUpdater { + String update(String className); + } + + public static String update(String signature, ClassNameUpdater updater) { + try { + StringBuilder buf = new StringBuilder(); + + // read the signature character-by-character + StringReader reader = new StringReader(signature); + int i = -1; + while ( (i = reader.read()) != -1) { + char c = (char)i; + + // does this character start a class name? + if (c == 'L') { + // update the class name and add it to the buffer + buf.append('L'); + String className = readClass(reader); + if (className == null) { + throw new IllegalArgumentException("Malformed signature: " + signature); + } + buf.append(updater.update(className)); + buf.append(';'); + } else { + // copy the character into the buffer + buf.append(c); + } + } + + return buf.toString(); + } catch (IOException ex) { + // I'm pretty sure a StringReader will never throw one of these + throw new Error(ex); + } + } + + private static String readClass(StringReader reader) throws IOException { + // read all the characters in the buffer until we hit a ';' + // remember to treat generics correctly + StringBuilder buf = new StringBuilder(); + int depth = 0; + int i = -1; + while ( (i = reader.read()) != -1) { + char c = (char)i; + + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0) { + if (c == ';') { + return buf.toString(); + } else { + buf.append(c); + } + } + } + + return null; + } + + public static List getClasses(String signature) { + final List classNames = Lists.newArrayList(); + update(signature, new ClassNameUpdater() { + @Override + public String update(String className) { + classNames.add(className); + return className; + } + }); + return classNames; + } +} diff --git a/src/cuchaz/enigma/mapping/TranslationDirection.java b/src/cuchaz/enigma/mapping/TranslationDirection.java new file mode 100644 index 0000000..d1b14cd --- /dev/null +++ b/src/cuchaz/enigma/mapping/TranslationDirection.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public enum TranslationDirection { + + Deobfuscating { + @Override + public T choose(T deobfChoice, T obfChoice) { + return deobfChoice; + } + }, + Obfuscating { + @Override + public T choose(T deobfChoice, T obfChoice) { + return obfChoice; + } + }; + + public abstract T choose(T deobfChoice, T obfChoice); +} diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java new file mode 100644 index 0000000..5eba18c --- /dev/null +++ b/src/cuchaz/enigma/mapping/Translator.java @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.util.Map; + +import com.google.common.collect.Maps; + +import cuchaz.enigma.analysis.TranslationIndex; + +public class Translator { + + private TranslationDirection m_direction; + private Map m_classes; + private TranslationIndex m_index; + + public Translator() { + m_direction = null; + m_classes = Maps.newHashMap(); + } + + public Translator(TranslationDirection direction, Map classes, TranslationIndex index) { + m_direction = direction; + m_classes = classes; + m_index = index; + } + + @SuppressWarnings("unchecked") + public T translateEntry(T entry) { + if (entry instanceof ClassEntry) { + return (T)translateEntry((ClassEntry)entry); + } else if (entry instanceof FieldEntry) { + return (T)translateEntry((FieldEntry)entry); + } else if (entry instanceof MethodEntry) { + return (T)translateEntry((MethodEntry)entry); + } else if (entry instanceof ConstructorEntry) { + return (T)translateEntry((ConstructorEntry)entry); + } else if (entry instanceof ArgumentEntry) { + return (T)translateEntry((ArgumentEntry)entry); + } else { + throw new Error("Unknown entry type: " + entry.getClass().getName()); + } + } + + public String translateClass(String className) { + return translate(new ClassEntry(className)); + } + + public String translate(ClassEntry in) { + ClassMapping classMapping = m_classes.get(in.getOuterClassName()); + if (classMapping != null) { + if (in.isInnerClass()) { + // translate the inner class + String translatedInnerClassName = m_direction.choose( + classMapping.getDeobfInnerClassName(in.getInnerClassName()), + classMapping.getObfInnerClassName(in.getInnerClassName()) + ); + if (translatedInnerClassName != null) { + // try to translate the outer name + String translatedOuterClassName = m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName()); + if (translatedOuterClassName != null) { + return translatedOuterClassName + "$" + translatedInnerClassName; + } else { + return in.getOuterClassName() + "$" + translatedInnerClassName; + } + } + } else { + // just return outer + return m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName()); + } + } + return null; + } + + public ClassEntry translateEntry(ClassEntry in) { + + // can we translate the inner class? + String name = translate(in); + if (name != null) { + return new ClassEntry(name); + } + + if (in.isInnerClass()) { + + // guess not. just translate the outer class name then + String outerClassName = translate(in.getOuterClassEntry()); + if (outerClassName != null) { + return new ClassEntry(outerClassName + "$" + in.getInnerClassName()); + } + } + + return in; + } + + public String translate(FieldEntry in) { + + // resolve the class entry + ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in); + if (resolvedClassEntry != null) { + + // look for the class + ClassMapping classMapping = findClassMapping(resolvedClassEntry); + if (classMapping != null) { + + // look for the field + String translatedName = m_direction.choose( + classMapping.getDeobfFieldName(in.getName()), + classMapping.getObfFieldName(in.getName()) + ); + if (translatedName != null) { + return translatedName; + } + } + } + return null; + } + + public FieldEntry translateEntry(FieldEntry in) { + String name = translate(in); + if (name == null) { + name = in.getName(); + } + return new FieldEntry(translateEntry(in.getClassEntry()), name); + } + + public String translate(MethodEntry in) { + + // resolve the class entry + ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in); + if (resolvedClassEntry != null) { + + // look for class + ClassMapping classMapping = findClassMapping(resolvedClassEntry); + if (classMapping != null) { + + // look for the method + MethodMapping methodMapping = m_direction.choose( + classMapping.getMethodByObf(in.getName(), in.getSignature()), + classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature())) + ); + if (methodMapping != null) { + return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName()); + } + } + } + return null; + } + + public MethodEntry translateEntry(MethodEntry in) { + String name = translate(in); + if (name == null) { + name = in.getName(); + } + return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature())); + } + + public ConstructorEntry translateEntry(ConstructorEntry in) { + if (in.isStatic()) { + return new ConstructorEntry(translateEntry(in.getClassEntry())); + } else { + return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature())); + } + } + + public BehaviorEntry translateEntry(BehaviorEntry in) { + if (in instanceof MethodEntry) { + return translateEntry((MethodEntry)in); + } else if (in instanceof ConstructorEntry) { + return translateEntry((ConstructorEntry)in); + } + throw new Error("Wrong entry type!"); + } + + public String translate(ArgumentEntry in) { + + // look for the class + ClassMapping classMapping = findClassMapping(in.getClassEntry()); + if (classMapping != null) { + + // look for the method + MethodMapping methodMapping = m_direction.choose( + classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()), + classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature())) + ); + if (methodMapping != null) { + return m_direction.choose( + methodMapping.getDeobfArgumentName(in.getIndex()), + methodMapping.getObfArgumentName(in.getIndex()) + ); + } + } + return null; + } + + public ArgumentEntry translateEntry(ArgumentEntry in) { + String name = translate(in); + if (name == null) { + name = in.getName(); + } + return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name); + } + + public Type translateType(Type type) { + return new Type(type, new ClassNameReplacer() { + @Override + public String replace(String className) { + return translateClass(className); + } + }); + } + + public Signature translateSignature(Signature signature) { + return new Signature(signature, new ClassNameReplacer() { + @Override + public String replace(String className) { + return translateClass(className); + } + }); + } + + private ClassMapping findClassMapping(ClassEntry classEntry) { + ClassMapping classMapping = m_classes.get(classEntry.getOuterClassName()); + if (classMapping != null && classEntry.isInnerClass()) { + classMapping = m_direction.choose( + classMapping.getInnerClassByObf(classEntry.getInnerClassName()), + classMapping.getInnerClassByDeobfThenObf(classEntry.getInnerClassName()) + ); + } + return classMapping; + } +} diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java new file mode 100644 index 0000000..9f5d52f --- /dev/null +++ b/src/cuchaz/enigma/mapping/Type.java @@ -0,0 +1,218 @@ +package cuchaz.enigma.mapping; + +import java.util.Map; + +import com.google.common.collect.Maps; + +public class Type { + + public enum Primitive { + Byte('B'), + Character('C'), + Short('S'), + Integer('I'), + Long('J'), + Float('F'), + Double('D'), + Boolean('Z'); + + private static final Map m_lookup; + + static { + m_lookup = Maps.newTreeMap(); + for (Primitive val : values()) { + m_lookup.put(val.getCode(), val); + } + } + + public static Primitive get(char code) { + return m_lookup.get(code); + } + + private char m_code; + + private Primitive(char code) { + m_code = code; + } + + public char getCode() { + return m_code; + } + } + + public static String parseFirst(String in) { + + if (in == null || in.length() <= 0) { + throw new IllegalArgumentException("No type to parse, input is empty!"); + } + + // read one type from the input + + char c = in.charAt(0); + + // first check for void + if (c == 'V') { + return "V"; + } + + // then check for primitives + Primitive primitive = Primitive.get(c); + if (primitive != null) { + return in.substring(0, 1); + } + + // then check for classes + if (c == 'L') { + return readClass(in); + } + + // then check for arrays + int dim = countArrayDimension(in); + if (dim > 0) { + String arrayType = Type.parseFirst(in.substring(dim)); + return in.substring(0, dim + arrayType.length()); + } + + throw new IllegalArgumentException("don't know how to parse: " + in); + } + + private String m_name; + + public Type(String name) { + m_name = name; + } + + public Type(ClassEntry classEntry) { + m_name = "L" + classEntry.getClassName() + ";"; + } + + public Type(Type type, ClassNameReplacer replacer) { + m_name = type.m_name; + if (type.isClass()) { + String replacedName = replacer.replace(type.getClassEntry().getClassName()); + if (replacedName != null) { + m_name = "L" + replacedName + ";"; + } + } else if (type.isArray() && type.hasClass()) { + String replacedName = replacer.replace(type.getClassEntry().getClassName()); + if (replacedName != null) { + m_name = Type.getArrayPrefix(type.getArrayDimension()) + "L" + replacedName + ";"; + } + } + } + + @Override + public String toString() { + return m_name; + } + + public boolean isVoid() { + return m_name.length() == 1 && m_name.charAt(0) == 'V'; + } + + public boolean isPrimitive() { + return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null; + } + + public Primitive getPrimitive() { + if (!isPrimitive()) { + throw new IllegalStateException("not a primitive"); + } + return Primitive.get(m_name.charAt(0)); + } + + public boolean isClass() { + return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';'; + } + + public ClassEntry getClassEntry() { + if (isClass()) { + String name = m_name.substring(1, m_name.length() - 1); + + int pos = name.indexOf('<'); + if (pos >= 0) { + // remove the parameters from the class name + name = name.substring(0, pos); + } + + return new ClassEntry(name); + + } else if (isArray() && getArrayType().isClass()) { + return getArrayType().getClassEntry(); + } else { + throw new IllegalStateException("type doesn't have a class"); + } + } + + public boolean isArray() { + return m_name.charAt(0) == '['; + } + + public int getArrayDimension() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return countArrayDimension(m_name); + } + + public Type getArrayType() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return new Type(m_name.substring(getArrayDimension(), m_name.length())); + } + + private static String getArrayPrefix(int dimension) { + StringBuilder buf = new StringBuilder(); + for (int i=0; i') { + depth--; + } else if (depth == 0 && c == ';') { + return buf.toString(); + } + } + return null; + } +} -- cgit v1.2.3 From 31a1a418b04cd3e7b06cb50cb8674a2c25079f6c Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 8 Feb 2015 23:10:26 -0500 Subject: added types to fields --- src/cuchaz/enigma/Deobfuscator.java | 4 +- src/cuchaz/enigma/analysis/EntryRenamer.java | 29 ++++++-- src/cuchaz/enigma/analysis/JarIndex.java | 26 +------- .../analysis/SourceIndexBehaviorVisitor.java | 5 +- .../enigma/analysis/SourceIndexClassVisitor.java | 5 +- src/cuchaz/enigma/bytecode/ClassTranslator.java | 5 +- src/cuchaz/enigma/convert/ClassIdentity.java | 2 +- src/cuchaz/enigma/gui/Gui.java | 1 + src/cuchaz/enigma/mapping/ClassMapping.java | 78 +++++++++++++--------- src/cuchaz/enigma/mapping/FieldEntry.java | 29 +++++--- src/cuchaz/enigma/mapping/FieldMapping.java | 10 ++- src/cuchaz/enigma/mapping/JavassistUtil.java | 6 +- src/cuchaz/enigma/mapping/Mappings.java | 4 +- src/cuchaz/enigma/mapping/MappingsReader.java | 53 ++------------- src/cuchaz/enigma/mapping/MappingsRenamer.java | 14 ++-- src/cuchaz/enigma/mapping/MappingsWriter.java | 2 +- src/cuchaz/enigma/mapping/Translator.java | 6 +- 17 files changed, 136 insertions(+), 143 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 5f61686..818bfd4 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -141,7 +141,7 @@ public class Deobfuscator { // fields for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { - FieldEntry fieldEntry = new FieldEntry(obfClassEntry, fieldMapping.getObfName()); + FieldEntry fieldEntry = new FieldEntry(obfClassEntry, fieldMapping.getObfName(), fieldMapping.getObfType()); ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(fieldEntry); if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(fieldEntry.getClassEntry())) { boolean wasMoved = renamer.moveFieldToObfClass(classMapping, fieldMapping, resolvedObfClassEntry); @@ -206,7 +206,7 @@ public class Deobfuscator { // check the fields for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { - FieldEntry fieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName(), fieldMapping.getObfType()); if (!m_jarIndex.containsObfField(fieldEntry)) { System.err.println("WARNING: unable to find field " + fieldEntry + ". dropping mapping."); classMapping.removeFieldMapping(fieldMapping); diff --git a/src/cuchaz/enigma/analysis/EntryRenamer.java b/src/cuchaz/enigma/analysis/EntryRenamer.java index b54489c..2f27049 100644 --- a/src/cuchaz/enigma/analysis/EntryRenamer.java +++ b/src/cuchaz/enigma/analysis/EntryRenamer.java @@ -21,10 +21,13 @@ import com.google.common.collect.Sets; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassNameReplacer; import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.Type; public class EntryRenamer { @@ -127,7 +130,7 @@ public class EntryRenamer { } @SuppressWarnings("unchecked") - public static T renameClassesInThing(Map renames, T thing) { + public static T renameClassesInThing(final Map renames, T thing) { if (thing instanceof String) { String stringEntry = (String)thing; if (renames.containsKey(stringEntry)) { @@ -138,19 +141,23 @@ public class EntryRenamer { return (T)new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); } else if (thing instanceof FieldEntry) { FieldEntry fieldEntry = (FieldEntry)thing; - return (T)new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName()); + return (T)new FieldEntry( + renameClassesInThing(renames, fieldEntry.getClassEntry()), + fieldEntry.getName(), + renameClassesInThing(renames, fieldEntry.getType()) + ); } else if (thing instanceof ConstructorEntry) { ConstructorEntry constructorEntry = (ConstructorEntry)thing; return (T)new ConstructorEntry( renameClassesInThing(renames, constructorEntry.getClassEntry()), - constructorEntry.getSignature() + renameClassesInThing(renames, constructorEntry.getSignature()) ); } else if (thing instanceof MethodEntry) { MethodEntry methodEntry = (MethodEntry)thing; return (T)new MethodEntry( renameClassesInThing(renames, methodEntry.getClassEntry()), methodEntry.getName(), - methodEntry.getSignature() + renameClassesInThing(renames, methodEntry.getSignature()) ); } else if (thing instanceof ArgumentEntry) { ArgumentEntry argumentEntry = (ArgumentEntry)thing; @@ -164,6 +171,20 @@ public class EntryRenamer { reference.entry = renameClassesInThing(renames, reference.entry); reference.context = renameClassesInThing(renames, reference.context); return thing; + } else if (thing instanceof Signature) { + return (T)new Signature((Signature)thing, new ClassNameReplacer() { + @Override + public String replace(String className) { + return renameClassesInThing(renames, className); + } + }); + } else if (thing instanceof Type) { + return (T)new Type((Type)thing, new ClassNameReplacer() { + @Override + public String replace(String className) { + return renameClassesInThing(renames, className); + } + }); } return thing; diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 3aac8bd..f54beda 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -57,7 +57,6 @@ public class JarIndex { private TranslationIndex m_translationIndex; private Multimap m_interfaces; private Map m_access; - private Map m_fieldClasses; // TODO: this will become obsolete! private Multimap m_methodImplementations; private Multimap> m_behaviorReferences; private Multimap> m_fieldReferences; @@ -70,7 +69,6 @@ public class JarIndex { m_translationIndex = new TranslationIndex(); m_interfaces = HashMultimap.create(); m_access = Maps.newHashMap(); - m_fieldClasses = Maps.newHashMap(); m_methodImplementations = HashMultimap.create(); m_behaviorReferences = HashMultimap.create(); m_fieldReferences = HashMultimap.create(); @@ -114,9 +112,6 @@ public class JarIndex { } m_interfaces.put(className, interfaceName); } - for (CtField field : c.getDeclaredFields()) { - indexField(field); - } for (CtBehavior behavior : c.getDeclaredBehaviors()) { indexBehavior(behavior); } @@ -169,18 +164,6 @@ public class JarIndex { } } - private void indexField(CtField field) { - // get the field entry - String className = Descriptor.toJvmName(field.getDeclaringClass().getName()); - FieldEntry fieldEntry = new FieldEntry(new ClassEntry(className), field.getName()); - - // is the field a class type? - if (field.getSignature().startsWith("L")) { - ClassEntry fieldTypeEntry = new ClassEntry(field.getSignature().substring(1, field.getSignature().length() - 1)); - m_fieldClasses.put(fieldEntry, fieldTypeEntry); - } - } - private void indexBehavior(CtBehavior behavior) { // get the behavior entry final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); @@ -222,7 +205,7 @@ public class JarIndex { FieldEntry calledFieldEntry = JavassistUtil.getFieldEntry(call); ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry); if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) { - calledFieldEntry = new FieldEntry(resolvedClassEntry, call.getFieldName()); + calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry); } EntryReference reference = new EntryReference( calledFieldEntry, @@ -448,8 +431,7 @@ public class JarIndex { // does the caller use this type? BehaviorEntry caller = references.iterator().next().context; for (FieldEntry fieldEntry : getReferencedFields(caller)) { - ClassEntry fieldClass = getFieldClass(fieldEntry); - if (fieldClass != null && fieldClass.equals(innerClassEntry)) { + if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) { // caller references this type, so it can't be anonymous return null; } @@ -475,10 +457,6 @@ public class JarIndex { return m_access.get(entry); } - public ClassEntry getFieldClass(FieldEntry fieldEntry) { - return m_fieldClasses.get(fieldEntry); - } - public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { // get the root node diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java index 4155128..f15a724 100644 --- a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java +++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java @@ -36,6 +36,7 @@ import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.Type; public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { @@ -100,7 +101,7 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { } ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getSignature())); index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry); } @@ -140,7 +141,7 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); if (ref != null) { ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getSignature())); index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry); } diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java index 7222035..e2ff300 100644 --- a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -31,6 +31,7 @@ import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.Type; public class SourceIndexClassVisitor extends SourceIndexVisitor { @@ -94,7 +95,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName(), new Type(def.getSignature())); assert (node.getVariables().size() == 1); VariableInitializer variable = node.getVariables().firstOrNullObject(); index.addDeclaration(variable.getNameToken(), fieldEntry); @@ -107,7 +108,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { // treat enum declarations as field declarations FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName(), new Type(def.getSignature())); index.addDeclaration(node.getNameToken(), fieldEntry); return recurse(node, index); diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java index afd3a77..4dba0d8 100644 --- a/src/cuchaz/enigma/bytecode/ClassTranslator.java +++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java @@ -55,7 +55,8 @@ public class ClassTranslator { // translate the name FieldEntry entry = new FieldEntry( new ClassEntry(Descriptor.toJvmName(constants.getFieldrefClassName(i))), - constants.getFieldrefName(i) + constants.getFieldrefName(i), + new Type(constants.getFieldrefType(i)) ); FieldEntry translatedEntry = m_translator.translateEntry(entry); @@ -94,7 +95,7 @@ public class ClassTranslator { for (CtField field : c.getDeclaredFields()) { // translate the name - FieldEntry entry = new FieldEntry(classEntry, field.getName()); + FieldEntry entry = JavassistUtil.getFieldEntry(field); String translatedName = m_translator.translate(entry); if (translatedName != null) { field.setName(translatedName); diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index 1be6123..bb729a3 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -116,7 +116,7 @@ public class ClassIdentity { m_references = HashMultiset.create(); if (useReferences) { for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = new FieldEntry(m_classEntry, field.getName()); + FieldEntry fieldEntry = JavassistUtil.getFieldEntry(field); for (EntryReference reference : index.getFieldReferences(fieldEntry)) { addReference(reference); } diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java index ca39c42..187ef5b 100644 --- a/src/cuchaz/enigma/gui/Gui.java +++ b/src/cuchaz/enigma/gui/Gui.java @@ -874,6 +874,7 @@ public class Gui { private void showFieldEntry(FieldEntry entry) { addNameValue(m_infoPanel, "Field", entry.getName()); addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(m_infoPanel, "Type", entry.getType().toString()); } private void showMethodEntry(MethodEntry entry) { diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java index e2c3d56..7133265 100644 --- a/src/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -152,78 +152,92 @@ public class ClassMapping implements Serializable, Comparable { return m_fieldsByObf.values(); } - public boolean containsObfField(String obfName) { - return m_fieldsByObf.containsKey(obfName); + public boolean containsObfField(String obfName, Type obfType) { + return m_fieldsByObf.containsKey(getFieldKey(obfName, obfType)); } - public boolean containsDeobfField(String deobfName) { - return m_fieldsByDeobf.containsKey(deobfName); + public boolean containsDeobfField(String deobfName, Type deobfType) { + return m_fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType)); } public void addFieldMapping(FieldMapping fieldMapping) { - if (m_fieldsByObf.containsKey(fieldMapping.getObfName())) { - throw new Error("Already have mapping for " + m_obfName + "." + fieldMapping.getObfName()); + String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + if (m_fieldsByObf.containsKey(obfKey)) { + throw new Error("Already have mapping for " + m_obfName + "." + obfKey); } - if (m_fieldsByDeobf.containsKey(fieldMapping.getDeobfName())) { - throw new Error("Already have mapping for " + m_deobfName + "." + fieldMapping.getDeobfName()); + String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); + if (m_fieldsByDeobf.containsKey(deobfKey)) { + throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey); } - boolean obfWasAdded = m_fieldsByObf.put(fieldMapping.getObfName(), fieldMapping) == null; + boolean obfWasAdded = m_fieldsByObf.put(obfKey, fieldMapping) == null; assert (obfWasAdded); - boolean deobfWasAdded = m_fieldsByDeobf.put(fieldMapping.getDeobfName(), fieldMapping) == null; + boolean deobfWasAdded = m_fieldsByDeobf.put(deobfKey, fieldMapping) == null; assert (deobfWasAdded); assert (m_fieldsByObf.size() == m_fieldsByDeobf.size()); } public void removeFieldMapping(FieldMapping fieldMapping) { - boolean obfWasRemoved = m_fieldsByObf.remove(fieldMapping.getObfName()) != null; + boolean obfWasRemoved = m_fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null; assert (obfWasRemoved); if (fieldMapping.getDeobfName() != null) { - boolean deobfWasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null; + boolean deobfWasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null; assert (deobfWasRemoved); } } - public FieldMapping getFieldByObf(String obfName) { - return m_fieldsByObf.get(obfName); + public FieldMapping getFieldByObf(String obfName, Type obfType) { + return m_fieldsByObf.get(getFieldKey(obfName, obfType)); } - public FieldMapping getFieldByDeobf(String deobfName) { - return m_fieldsByDeobf.get(deobfName); + public FieldMapping getFieldByDeobf(String deobfName, Type obfType) { + return m_fieldsByDeobf.get(getFieldKey(deobfName, obfType)); } - public String getObfFieldName(String deobfName) { - FieldMapping fieldMapping = m_fieldsByDeobf.get(deobfName); + public String getObfFieldName(String deobfName, Type obfType) { + FieldMapping fieldMapping = m_fieldsByDeobf.get(getFieldKey(deobfName, obfType)); if (fieldMapping != null) { return fieldMapping.getObfName(); } return null; } - public String getDeobfFieldName(String obfName) { - FieldMapping fieldMapping = m_fieldsByObf.get(obfName); + public String getDeobfFieldName(String obfName, Type obfType) { + FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType)); if (fieldMapping != null) { return fieldMapping.getDeobfName(); } return null; } - public void setFieldName(String obfName, String deobfName) { - FieldMapping fieldMapping = m_fieldsByObf.get(obfName); + private String getFieldKey(String name, Type type) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null!"); + } + if (type == null) { + throw new IllegalArgumentException("type cannot be null!"); + } + return name + ":" + type; + } + + + public void setFieldName(String obfName, Type obfType, String deobfName) { + FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType)); if (fieldMapping == null) { - fieldMapping = new FieldMapping(obfName, deobfName); - boolean obfWasAdded = m_fieldsByObf.put(obfName, fieldMapping) == null; + fieldMapping = new FieldMapping(obfName, obfType, deobfName); + boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null; assert (obfWasAdded); } else { - boolean wasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null; + boolean wasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null; assert (wasRemoved); } fieldMapping.setDeobfName(deobfName); if (deobfName != null) { - boolean wasAdded = m_fieldsByDeobf.put(deobfName, fieldMapping) == null; + boolean wasAdded = m_fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null; assert (wasAdded); } } + //// METHODS //////// public Iterable methods() { @@ -235,8 +249,8 @@ public class ClassMapping implements Serializable, Comparable { return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); } - public boolean containsDeobfMethod(String deobfName, Signature deobfSignature) { - return m_methodsByDeobf.containsKey(getMethodKey(deobfName, deobfSignature)); + public boolean containsDeobfMethod(String deobfName, Signature obfSignature) { + return m_methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature)); } public void addMethodMapping(MethodMapping methodMapping) { @@ -266,12 +280,12 @@ public class ClassMapping implements Serializable, Comparable { } } - public MethodMapping getMethodByObf(String obfName, Signature signature) { - return m_methodsByObf.get(getMethodKey(obfName, signature)); + public MethodMapping getMethodByObf(String obfName, Signature obfSignature) { + return m_methodsByObf.get(getMethodKey(obfName, obfSignature)); } - public MethodMapping getMethodByDeobf(String deobfName, Signature signature) { - return m_methodsByDeobf.get(getMethodKey(deobfName, signature)); + public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) { + return m_methodsByDeobf.get(getMethodKey(deobfName, obfSignature)); } private String getMethodKey(String name, Signature signature) { diff --git a/src/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java index 6cc9eb7..7517254 100644 --- a/src/cuchaz/enigma/mapping/FieldEntry.java +++ b/src/cuchaz/enigma/mapping/FieldEntry.java @@ -20,28 +20,33 @@ public class FieldEntry implements Entry, Serializable { private ClassEntry m_classEntry; private String m_name; + private Type m_type; // NOTE: this argument order is important for the MethodReader/MethodWriter - public FieldEntry(ClassEntry classEntry, String name) { + public FieldEntry(ClassEntry classEntry, String name, Type type) { if (classEntry == null) { throw new IllegalArgumentException("Class cannot be null!"); } if (name == null) { throw new IllegalArgumentException("Field name cannot be null!"); } + if (type == null) { + throw new IllegalArgumentException("Field type cannot be null!"); + } m_classEntry = classEntry; m_name = name; + m_type = type; } public FieldEntry(FieldEntry other) { - m_classEntry = new ClassEntry(other.m_classEntry); - m_name = other.m_name; + this(other, new ClassEntry(other.m_classEntry)); } - public FieldEntry(FieldEntry other, String newClassName) { - m_classEntry = new ClassEntry(newClassName); + public FieldEntry(FieldEntry other, ClassEntry newClassEntry) { + m_classEntry = newClassEntry; m_name = other.m_name; + m_type = other.m_type; } @Override @@ -59,14 +64,18 @@ public class FieldEntry implements Entry, Serializable { return m_classEntry.getName(); } + public Type getType() { + return m_type; + } + @Override public FieldEntry cloneToNewClass(ClassEntry classEntry) { - return new FieldEntry(this, classEntry.getName()); + return new FieldEntry(this, classEntry); } @Override public int hashCode() { - return Util.combineHashesOrdered(m_classEntry, m_name); + return Util.combineHashesOrdered(m_classEntry, m_name, m_type); } @Override @@ -78,11 +87,13 @@ public class FieldEntry implements Entry, Serializable { } public boolean equals(FieldEntry other) { - return m_classEntry.equals(other.m_classEntry) && m_name.equals(other.m_name); + return m_classEntry.equals(other.m_classEntry) + && m_name.equals(other.m_name) + && m_type.equals(other.m_type); } @Override public String toString() { - return m_classEntry.getName() + "." + m_name; + return m_classEntry.getName() + "." + m_name + ":" + m_type; } } diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java index 5f5c270..14b20dd 100644 --- a/src/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/cuchaz/enigma/mapping/FieldMapping.java @@ -18,10 +18,12 @@ public class FieldMapping implements Serializable, Comparable { private String m_obfName; private String m_deobfName; + private Type m_obfType; - public FieldMapping(String obfName, String deobfName) { + public FieldMapping(String obfName, Type obfType, String deobfName) { m_obfName = obfName; m_deobfName = NameValidator.validateFieldName(deobfName); + m_obfType = obfType; } public String getObfName() { @@ -36,8 +38,12 @@ public class FieldMapping implements Serializable, Comparable { m_deobfName = NameValidator.validateFieldName(val); } + public Type getObfType() { + return m_obfType; + } + @Override public int compareTo(FieldMapping other) { - return m_obfName.compareTo(other.m_obfName); + return (m_obfName + m_obfType).compareTo(other.m_obfName + other.m_obfType); } } diff --git a/src/cuchaz/enigma/mapping/JavassistUtil.java b/src/cuchaz/enigma/mapping/JavassistUtil.java index 0c446c4..0d6ce6a 100644 --- a/src/cuchaz/enigma/mapping/JavassistUtil.java +++ b/src/cuchaz/enigma/mapping/JavassistUtil.java @@ -70,14 +70,16 @@ public class JavassistUtil { public static FieldEntry getFieldEntry(CtField field) { return new FieldEntry( getClassEntry(field.getDeclaringClass()), - field.getName() + field.getName(), + new Type(field.getFieldInfo().getDescriptor()) ); } public static FieldEntry getFieldEntry(FieldAccess call) { return new FieldEntry( new ClassEntry(Descriptor.toJvmName(call.getClassName())), - call.getFieldName() + call.getFieldName(), + new Type(call.getSignature()) ); } } diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java index 57d8001..675fdf1 100644 --- a/src/cuchaz/enigma/mapping/Mappings.java +++ b/src/cuchaz/enigma/mapping/Mappings.java @@ -162,10 +162,10 @@ public class Mappings implements Serializable { return m_classesByDeobf.containsKey(deobfName); } - public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { + public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) { ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName()); if (classMapping != null) { - return classMapping.containsDeobfField(deobfName); + return classMapping.containsDeobfField(deobfName, obfType); } return false; } diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java index adf460e..1e7b6e3 100644 --- a/src/cuchaz/enigma/mapping/MappingsReader.java +++ b/src/cuchaz/enigma/mapping/MappingsReader.java @@ -17,8 +17,6 @@ import java.util.Deque; import com.google.common.collect.Queues; -import cuchaz.enigma.Constants; - public class MappingsReader { public Mappings read(Reader in) throws IOException, MappingParseException { @@ -114,62 +112,21 @@ public class MappingsReader { private ClassMapping readClass(String[] parts, boolean makeSimple) { if (parts.length == 2) { - String obfName = processName(parts[1], makeSimple); - return new ClassMapping(obfName); + return new ClassMapping(parts[1]); } else { - String obfName = processName(parts[1], makeSimple); - String deobfName = processName(parts[2], makeSimple); - return new ClassMapping(obfName, deobfName); + return new ClassMapping(parts[1], parts[2]); } } - private String processName(String name, boolean makeSimple) { - if (makeSimple) { - return new ClassEntry(name).getSimpleName(); - } else { - return moveClassOutOfDefaultPackage(name, Constants.NonePackage); - } - } - - private String moveClassOutOfDefaultPackage(String className, String newPackageName) { - ClassEntry classEntry = new ClassEntry(className); - if (classEntry.isInDefaultPackage()) { - return newPackageName + "/" + classEntry.getName(); - } - return className; - } - private FieldMapping readField(String[] parts) { - return new FieldMapping(parts[1], parts[2]); + return new FieldMapping(parts[1], new Type(parts[3]), parts[2]); } private MethodMapping readMethod(String[] parts) { if (parts.length == 3) { - String obfName = parts[1]; - Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[2]), Constants.NonePackage); - return new MethodMapping(obfName, obfSignature); + return new MethodMapping(parts[1], new Signature(parts[2])); } else { - String obfName = parts[1]; - String deobfName = parts[2]; - Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[3]), Constants.NonePackage); - if (obfName.equals(deobfName)) { - return new MethodMapping(obfName, obfSignature); - } else { - return new MethodMapping(obfName, obfSignature, deobfName); - } + return new MethodMapping(parts[1], new Signature(parts[3]), parts[2]); } } - - private Signature moveSignatureOutOfDefaultPackage(Signature signature, final String newPackageName) { - return new Signature(signature, new ClassNameReplacer() { - @Override - public String replace(String className) { - ClassEntry classEntry = new ClassEntry(className); - if (classEntry.isInDefaultPackage()) { - return newPackageName + "/" + className; - } - return null; - } - }); - } } diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java index 0a41c2b..095e5e9 100644 --- a/src/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java @@ -76,23 +76,23 @@ public class MappingsRenamer { public void setFieldName(FieldEntry obf, String deobfName) { deobfName = NameValidator.validateFieldName(deobfName); - FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName); - if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName) || m_index.containsObfField(targetEntry)) { + FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType()); + if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName, obf.getType()) || m_index.containsObfField(targetEntry)) { throw new IllegalNameException(deobfName, "There is already a field with that name"); } ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), deobfName); + classMapping.setFieldName(obf.getName(), obf.getType(), deobfName); } public void removeFieldMapping(FieldEntry obf) { ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), null); + classMapping.setFieldName(obf.getName(), obf.getType(), null); } public void markFieldAsDeobfuscated(FieldEntry obf) { ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getName()); + classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName()); } public void setMethodTreeName(MethodEntry obf, String deobfName) { @@ -171,8 +171,8 @@ public class MappingsRenamer { public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { classMapping.removeFieldMapping(fieldMapping); ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); - if (!targetClassMapping.containsObfField(fieldMapping.getObfName())) { - if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName())) { + if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) { + if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) { targetClassMapping.addFieldMapping(fieldMapping); return true; } else { diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java index 5ac409f..c7c2cc0 100644 --- a/src/cuchaz/enigma/mapping/MappingsWriter.java +++ b/src/cuchaz/enigma/mapping/MappingsWriter.java @@ -50,7 +50,7 @@ public class MappingsWriter { } private void write(PrintWriter out, FieldMapping fieldMapping, int depth) throws IOException { - out.format("%sFIELD %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName()); + out.format("%sFIELD %s %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString()); } private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java index 5eba18c..759dddf 100644 --- a/src/cuchaz/enigma/mapping/Translator.java +++ b/src/cuchaz/enigma/mapping/Translator.java @@ -112,8 +112,8 @@ public class Translator { // look for the field String translatedName = m_direction.choose( - classMapping.getDeobfFieldName(in.getName()), - classMapping.getObfFieldName(in.getName()) + classMapping.getDeobfFieldName(in.getName(), in.getType()), + classMapping.getObfFieldName(in.getName(), translateType(in.getType())) ); if (translatedName != null) { return translatedName; @@ -128,7 +128,7 @@ public class Translator { if (name == null) { name = in.getName(); } - return new FieldEntry(translateEntry(in.getClassEntry()), name); + return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType())); } public String translate(MethodEntry in) { -- cgit v1.2.3 From 71ec7b53a1b4ecce0623dded1e445818a757b695 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 9 Feb 2015 12:17:26 -0500 Subject: add converter to update old mappings format fix a few decompiler issues too --- src/cuchaz/enigma/MainFormatConverter.java | 120 +++++++++++++++++++++ .../analysis/SourceIndexBehaviorVisitor.java | 12 +-- .../enigma/bytecode/MethodParameterWriter.java | 8 +- src/cuchaz/enigma/mapping/MappingsReader.java | 36 ++++--- 4 files changed, 152 insertions(+), 24 deletions(-) create mode 100644 src/cuchaz/enigma/MainFormatConverter.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/MainFormatConverter.java b/src/cuchaz/enigma/MainFormatConverter.java new file mode 100644 index 0000000..1848144 --- /dev/null +++ b/src/cuchaz/enigma/MainFormatConverter.java @@ -0,0 +1,120 @@ +package cuchaz.enigma; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.jar.JarFile; + +import javassist.CtClass; +import javassist.CtField; + +import com.google.common.collect.Maps; + +import cuchaz.enigma.analysis.JarClassIterator; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassMapping; +import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.FieldMapping; +import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsReader; +import cuchaz.enigma.mapping.MappingsWriter; +import cuchaz.enigma.mapping.Type; + +public class MainFormatConverter { + + public static void main(String[] args) + throws Exception { + + System.out.println("Getting field types from jar..."); + + JarFile jar = new JarFile(System.getProperty("user.home") + "/.minecraft/versions/1.8/1.8.jar"); + Map fieldTypes = Maps.newHashMap(); + for (CtClass c : JarClassIterator.classes(jar)) { + for (CtField field : c.getDeclaredFields()) { + FieldEntry fieldEntry = JavassistUtil.getFieldEntry(field); + fieldTypes.put(getFieldKey(fieldEntry), moveClasssesOutOfDefaultPackage(fieldEntry.getType())); + } + } + + System.out.println("Reading mappings..."); + + File fileMappings = new File("../Enigma Mappings/1.8.mappings"); + MappingsReader mappingsReader = new MappingsReader() { + + @Override + protected FieldMapping readField(String[] parts) { + // assume the void type for now + return new FieldMapping(parts[1], new Type("V"), parts[2]); + } + }; + Mappings mappings = mappingsReader.read(new FileReader(fileMappings)); + + System.out.println("Updating field types..."); + + for (ClassMapping classMapping : mappings.classes()) { + updateFieldsInClass(fieldTypes, classMapping); + } + + System.out.println("Saving mappings..."); + + try (FileWriter writer = new FileWriter(fileMappings)) { + new MappingsWriter().write(writer, mappings); + } + + System.out.println("Done!"); + } + + private static Type moveClasssesOutOfDefaultPackage(Type type) { + return new Type(type, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = new ClassEntry(className); + if (entry.isInDefaultPackage()) { + return Constants.NonePackage + "/" + className; + } + return null; + } + }); + } + + private static void updateFieldsInClass(Map fieldTypes, ClassMapping classMapping) + throws Exception { + + // update the fields + for (FieldMapping fieldMapping : classMapping.fields()) { + setFieldType(fieldTypes, classMapping, fieldMapping); + } + + // recurse + for (ClassMapping innerClassMapping : classMapping.innerClasses()) { + updateFieldsInClass(fieldTypes, innerClassMapping); + } + } + + private static void setFieldType(Map fieldTypes, ClassMapping classMapping, FieldMapping fieldMapping) + throws Exception { + + // get the new type + Type newType = fieldTypes.get(getFieldKey(classMapping, fieldMapping)); + if (newType == null) { + throw new Error("Can't find type for field: " + getFieldKey(classMapping, fieldMapping)); + } + + // hack in the new field type + Field field = fieldMapping.getClass().getDeclaredField("m_obfType"); + field.setAccessible(true); + field.set(fieldMapping, newType); + } + + private static Object getFieldKey(ClassMapping classMapping, FieldMapping fieldMapping) { + return new ClassEntry(classMapping.getObfName()).getSimpleName() + "." + fieldMapping.getObfName(); + } + + private static String getFieldKey(FieldEntry obfFieldEntry) { + return obfFieldEntry.getClassEntry().getSimpleName() + "." + obfFieldEntry.getName(); + } +} diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java index f15a724..b4094d9 100644 --- a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java +++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java @@ -66,11 +66,11 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { if (ref instanceof MethodReference) { MethodReference methodRef = (MethodReference)ref; if (methodRef.isConstructor()) { - behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature())); + behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); } else if (methodRef.isTypeInitializer()) { behaviorEntry = new ConstructorEntry(classEntry); } else { - behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getSignature())); + behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature())); } } if (behaviorEntry != null) { @@ -96,12 +96,12 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); if (ref != null) { // make sure this is actually a field - if (ref.getSignature().indexOf('(') >= 0) { + if (ref.getErasedSignature().indexOf('(') >= 0) { throw new Error("Expected a field here! got " + ref); } ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getSignature())); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry); } @@ -141,7 +141,7 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); if (ref != null) { ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getSignature())); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry); } @@ -153,7 +153,7 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); if (ref != null) { ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature())); + ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); if (node.getType() instanceof SimpleType) { SimpleType simpleTypeNode = (SimpleType)node.getType(); index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry); diff --git a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java index 5d4ca1a..853928c 100644 --- a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java +++ b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java @@ -18,6 +18,7 @@ import javassist.CtClass; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.BehaviorEntryFactory; +import cuchaz.enigma.mapping.Signature; import cuchaz.enigma.mapping.Translator; public class MethodParameterWriter { @@ -35,7 +36,12 @@ public class MethodParameterWriter { BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); // get the number of arguments - int numParams = behaviorEntry.getSignature().getArgumentTypes().size(); + Signature signature = behaviorEntry.getSignature(); + if (signature == null) { + // static initializers have no signatures, or arguments + continue; + } + int numParams = signature.getArgumentTypes().size(); if (numParams <= 0) { continue; } diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java index 1e7b6e3..bfb2731 100644 --- a/src/cuchaz/enigma/mapping/MappingsReader.java +++ b/src/cuchaz/enigma/mapping/MappingsReader.java @@ -19,11 +19,13 @@ import com.google.common.collect.Queues; public class MappingsReader { - public Mappings read(Reader in) throws IOException, MappingParseException { + public Mappings read(Reader in) + throws IOException, MappingParseException { return read(new BufferedReader(in)); } - public Mappings read(BufferedReader in) throws IOException, MappingParseException { + public Mappings read(BufferedReader in) + throws IOException, MappingParseException { Mappings mappings = new Mappings(); Deque mappingStack = Queues.newArrayDeque(); @@ -64,42 +66,41 @@ public class MappingsReader { if (token.equalsIgnoreCase("CLASS")) { ClassMapping classMapping; - if (indent == 0) { + if (indent <= 0) { // outer class classMapping = readClass(parts, false); mappings.addClassMapping(classMapping); - } else if (indent == 1) { + } else { + // inner class - if (! (mappingStack.getFirst() instanceof ClassMapping)) { + if (!(mappingStack.peek() instanceof ClassMapping)) { throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!"); } classMapping = readClass(parts, true); - ((ClassMapping)mappingStack.getFirst()).addInnerClassMapping(classMapping); - } else { - throw new MappingParseException(lineNumber, "Unexpected CLASS entry nesting!"); + ((ClassMapping)mappingStack.peek()).addInnerClassMapping(classMapping); } mappingStack.push(classMapping); } else if (token.equalsIgnoreCase("FIELD")) { - if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) { + if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) { throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!"); } - ((ClassMapping)mappingStack.getFirst()).addFieldMapping(readField(parts)); + ((ClassMapping)mappingStack.peek()).addFieldMapping(readField(parts)); } else if (token.equalsIgnoreCase("METHOD")) { - if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) { + if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) { throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!"); } MethodMapping methodMapping = readMethod(parts); - ((ClassMapping)mappingStack.getFirst()).addMethodMapping(methodMapping); + ((ClassMapping)mappingStack.peek()).addMethodMapping(methodMapping); mappingStack.push(methodMapping); } else if (token.equalsIgnoreCase("ARG")) { - if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof MethodMapping)) { + if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof MethodMapping)) { throw new MappingParseException(lineNumber, "Unexpected ARG entry here!"); } - ((MethodMapping)mappingStack.getFirst()).addArgumentMapping(readArgument(parts)); + ((MethodMapping)mappingStack.peek()).addArgumentMapping(readArgument(parts)); } - } catch (ArrayIndexOutOfBoundsException | NumberFormatException ex) { - throw new MappingParseException(lineNumber, "Malformed line!"); + } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { + throw new MappingParseException(lineNumber, "Malformed line:\n" + line); } } @@ -118,7 +119,8 @@ public class MappingsReader { } } - private FieldMapping readField(String[] parts) { + /* TEMP */ + protected FieldMapping readField(String[] parts) { return new FieldMapping(parts[1], new Type(parts[3]), parts[2]); } -- cgit v1.2.3 From 250fe96550f9ab9179446a70cb15e8a8e23ae790 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 9 Feb 2015 16:33:43 -0500 Subject: Don't automatically move mappings around. We're more interested in stability than backwards compatibility now. Just drop them instead. --- src/cuchaz/enigma/Deobfuscator.java | 87 ++++++------------------------------- 1 file changed, 13 insertions(+), 74 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 818bfd4..b4ac501 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -114,86 +114,20 @@ public class Deobfuscator { val = new Mappings(); } - // pass 1: look for any classes that got moved to inner classes - Map renames = Maps.newHashMap(); - for (ClassMapping classMapping : val.classes()) { - // make sure we strip the packages off of obfuscated inner classes - String innerClassName = new ClassEntry(classMapping.getObfName()).getSimpleName(); - String outerClassName = m_jarIndex.getOuterClass(innerClassName); - if (outerClassName != null) { - // build the composite class name - String newName = outerClassName + "$" + innerClassName; - - // add a rename - renames.put(classMapping.getObfName(), newName); - - System.out.println(String.format("Converted class mapping %s to %s", classMapping.getObfName(), newName)); - } - } - for (Map.Entry entry : renames.entrySet()) { - val.renameObfClass(entry.getKey(), entry.getValue()); - } - - // pass 2: look for fields/methods that are actually declared in superclasses - MappingsRenamer renamer = new MappingsRenamer(m_jarIndex, val); + // drop mappings that don't match the jar for (ClassMapping classMapping : Lists.newArrayList(val.classes())) { - ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfName()); - - // fields - for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { - FieldEntry fieldEntry = new FieldEntry(obfClassEntry, fieldMapping.getObfName(), fieldMapping.getObfType()); - ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(fieldEntry); - if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(fieldEntry.getClassEntry())) { - boolean wasMoved = renamer.moveFieldToObfClass(classMapping, fieldMapping, resolvedObfClassEntry); - if (wasMoved) { - System.out.println(String.format("Moved field %s to class %s", fieldEntry, resolvedObfClassEntry)); - } else { - System.err.println(String.format("WARNING: Would move field %s to class %s but the field was already there. Dropping instead.", fieldEntry, resolvedObfClassEntry)); - } - } + if (!checkClassMapping(classMapping)) { + val.removeClassMapping(classMapping); } - - // methods - for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { - // skip constructors - if (methodMapping.isConstructor()) { - continue; - } - - MethodEntry methodEntry = new MethodEntry( - obfClassEntry, - methodMapping.getObfName(), - methodMapping.getObfSignature() - ); - ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(methodEntry); - if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(methodEntry.getClassEntry())) { - boolean wasMoved = renamer.moveMethodToObfClass(classMapping, methodMapping, resolvedObfClassEntry); - if (wasMoved) { - System.out.println(String.format("Moved method %s to class %s", methodEntry, resolvedObfClassEntry)); - } else { - System.err.println(String.format("WARNING: Would move method %s to class %s but the method was already there. Dropping instead.", methodEntry, resolvedObfClassEntry)); - } - } - } - - // TODO: recurse to inner classes? - } - - // drop mappings that don't match the jar - List unknownClasses = Lists.newArrayList(); - for (ClassMapping classMapping : val.classes()) { - checkClassMapping(unknownClasses, classMapping); - } - if (!unknownClasses.isEmpty()) { - throw new Error("Unable to find classes in jar: " + unknownClasses); } m_mappings = val; - m_renamer = renamer; + m_renamer = new MappingsRenamer(m_jarIndex, val); m_translatorCache.clear(); } - private void checkClassMapping(List unknownClasses, ClassMapping classMapping) { + private boolean checkClassMapping(ClassMapping classMapping) { + // check the class ClassEntry classEntry = new ClassEntry(classMapping.getObfName()); String outerClassName = m_jarIndex.getOuterClass(classEntry.getSimpleName()); @@ -201,7 +135,7 @@ public class Deobfuscator { classEntry = new ClassEntry(outerClassName + "$" + classMapping.getObfName()); } if (!m_jarIndex.getObfClassEntries().contains(classEntry)) { - unknownClasses.add(classEntry); + return false; } // check the fields @@ -224,8 +158,13 @@ public class Deobfuscator { // check inner classes for (ClassMapping innerClassMapping : classMapping.innerClasses()) { - checkClassMapping(unknownClasses, innerClassMapping); + if (!checkClassMapping(innerClassMapping)) { + System.err.println("WARNING: unable to find inner class " + innerClassMapping + ". dropping mapping."); + classMapping.removeInnerClassMapping(innerClassMapping); + } } + + return true; } public Translator getTranslator(TranslationDirection direction) { -- cgit v1.2.3 From af1041731c8c0ce1846ff7e7b6052ed7327a5dbc Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 9 Feb 2015 22:23:45 -0500 Subject: fix translation issues, particularly with fields --- src/cuchaz/enigma/Deobfuscator.java | 25 +-- src/cuchaz/enigma/MainFormatConverter.java | 4 +- src/cuchaz/enigma/analysis/JarIndex.java | 57 +++---- .../analysis/MethodImplementationsTreeNode.java | 1 + .../enigma/analysis/RelatedMethodChecker.java | 96 +++++++++++ .../enigma/analysis/SourceIndexClassVisitor.java | 11 +- src/cuchaz/enigma/analysis/TranslationIndex.java | 10 +- src/cuchaz/enigma/bytecode/ClassTranslator.java | 9 +- .../enigma/bytecode/MethodParameterWriter.java | 4 +- src/cuchaz/enigma/convert/ClassIdentity.java | 6 +- src/cuchaz/enigma/convert/ClassMatcher.java | 6 +- src/cuchaz/enigma/gui/GuiController.java | 9 +- .../enigma/mapping/BehaviorEntryFactory.java | 57 ------- src/cuchaz/enigma/mapping/EntryFactory.java | 184 +++++++++++++++++++++ src/cuchaz/enigma/mapping/JavassistUtil.java | 85 ---------- 15 files changed, 353 insertions(+), 211 deletions(-) create mode 100644 src/cuchaz/enigma/analysis/RelatedMethodChecker.java delete mode 100644 src/cuchaz/enigma/mapping/BehaviorEntryFactory.java create mode 100644 src/cuchaz/enigma/mapping/EntryFactory.java delete mode 100644 src/cuchaz/enigma/mapping/JavassistUtil.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index b4ac501..b54a94a 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -42,16 +42,17 @@ import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.JarClassIterator; import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.analysis.RelatedMethodChecker; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.SourceIndexVisitor; import cuchaz.enigma.analysis.Token; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.BehaviorEntryFactory; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.Mappings; @@ -115,25 +116,27 @@ public class Deobfuscator { } // drop mappings that don't match the jar + RelatedMethodChecker relatedMethodChecker = new RelatedMethodChecker(m_jarIndex); for (ClassMapping classMapping : Lists.newArrayList(val.classes())) { - if (!checkClassMapping(classMapping)) { + if (!checkClassMapping(relatedMethodChecker, classMapping)) { val.removeClassMapping(classMapping); } } + // check for related method inconsistencies + if (relatedMethodChecker.hasProblems()) { + throw new Error("Related methods are inconsistent! Need to fix the mappings manually.\n" + relatedMethodChecker.getReport()); + } + m_mappings = val; m_renamer = new MappingsRenamer(m_jarIndex, val); m_translatorCache.clear(); } - private boolean checkClassMapping(ClassMapping classMapping) { + private boolean checkClassMapping(RelatedMethodChecker relatedMethodChecker, ClassMapping classMapping) { // check the class - ClassEntry classEntry = new ClassEntry(classMapping.getObfName()); - String outerClassName = m_jarIndex.getOuterClass(classEntry.getSimpleName()); - if (outerClassName != null) { - classEntry = new ClassEntry(outerClassName + "$" + classMapping.getObfName()); - } + ClassEntry classEntry = EntryFactory.getObfClassEntry(m_jarIndex, classMapping); if (!m_jarIndex.getObfClassEntries().contains(classEntry)) { return false; } @@ -149,16 +152,18 @@ public class Deobfuscator { // check methods for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { - BehaviorEntry obfBehaviorEntry = BehaviorEntryFactory.createObf(classEntry, methodMapping); + BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); if (!m_jarIndex.containsObfBehavior(obfBehaviorEntry)) { System.err.println("WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping."); classMapping.removeMethodMapping(methodMapping); } + + relatedMethodChecker.checkMethod(classEntry, methodMapping); } // check inner classes for (ClassMapping innerClassMapping : classMapping.innerClasses()) { - if (!checkClassMapping(innerClassMapping)) { + if (!checkClassMapping(relatedMethodChecker, innerClassMapping)) { System.err.println("WARNING: unable to find inner class " + innerClassMapping + ". dropping mapping."); classMapping.removeInnerClassMapping(innerClassMapping); } diff --git a/src/cuchaz/enigma/MainFormatConverter.java b/src/cuchaz/enigma/MainFormatConverter.java index 1848144..d4bb2db 100644 --- a/src/cuchaz/enigma/MainFormatConverter.java +++ b/src/cuchaz/enigma/MainFormatConverter.java @@ -18,7 +18,7 @@ import cuchaz.enigma.mapping.ClassMapping; import cuchaz.enigma.mapping.ClassNameReplacer; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.FieldMapping; -import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsReader; import cuchaz.enigma.mapping.MappingsWriter; @@ -35,7 +35,7 @@ public class MainFormatConverter { Map fieldTypes = Maps.newHashMap(); for (CtClass c : JarClassIterator.classes(jar)) { for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = JavassistUtil.getFieldEntry(field); + FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); fieldTypes.put(getFieldKey(fieldEntry), moveClasssesOutOfDefaultPackage(fieldEntry.getType())); } } diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index f54beda..24d110e 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -42,12 +42,11 @@ import cuchaz.enigma.Constants; import cuchaz.enigma.bytecode.ClassRenamer; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.BehaviorEntryFactory; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.JavassistUtil; import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.Translator; @@ -92,10 +91,10 @@ public class JarIndex { for (CtClass c : JarClassIterator.classes(jar)) { ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); for (CtField field : c.getDeclaredFields()) { - m_access.put(JavassistUtil.getFieldEntry(field), Access.get(field)); + m_access.put(EntryFactory.getFieldEntry(field), Access.get(field)); } for (CtBehavior behavior : c.getDeclaredBehaviors()) { - m_access.put(JavassistUtil.getBehaviorEntry(behavior), Access.get(behavior)); + m_access.put(EntryFactory.getBehaviorEntry(behavior), Access.get(behavior)); } } @@ -166,7 +165,7 @@ public class JarIndex { private void indexBehavior(CtBehavior behavior) { // get the behavior entry - final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); + final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); if (behaviorEntry instanceof MethodEntry) { MethodEntry methodEntry = (MethodEntry)behaviorEntry; @@ -178,12 +177,12 @@ public class JarIndex { private void indexBehaviorReferences(CtBehavior behavior) { // index method calls - final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); + final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); try { behavior.instrument(new ExprEditor() { @Override public void edit(MethodCall call) { - MethodEntry calledMethodEntry = JavassistUtil.getMethodEntry(call); + MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call); ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry); if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) { calledMethodEntry = new MethodEntry( @@ -202,7 +201,7 @@ public class JarIndex { @Override public void edit(FieldAccess call) { - FieldEntry calledFieldEntry = JavassistUtil.getFieldEntry(call); + FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call); ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry); if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) { calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry); @@ -217,7 +216,7 @@ public class JarIndex { @Override public void edit(ConstructorCall call) { - ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call); + ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); EntryReference reference = new EntryReference( calledConstructorEntry, call.getMethodName(), @@ -228,7 +227,7 @@ public class JarIndex { @Override public void edit(NewExpr call) { - ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call); + ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); EntryReference reference = new EntryReference( calledConstructorEntry, call.getClassName(), @@ -256,7 +255,7 @@ public class JarIndex { } ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor); + ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); // gather the classes from the illegally-set synthetic fields Set illegallySetClasses = Sets.newHashSet(); @@ -422,7 +421,7 @@ public class JarIndex { CtConstructor constructor = c.getDeclaredConstructors()[0]; // is this constructor called exactly once? - ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor); + ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); Collection> references = getBehaviorReferences(constructorEntry); if (references.size() != 1) { return null; @@ -520,17 +519,17 @@ public class JarIndex { return rootNode; } - public MethodImplementationsTreeNode getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { + public List getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { - MethodEntry interfaceMethodEntry; + List interfaceMethodEntries = Lists.newArrayList(); // is this method on an interface? if (isInterface(obfMethodEntry.getClassName())) { - interfaceMethodEntry = obfMethodEntry; + interfaceMethodEntries.add(obfMethodEntry); } else { // get the interface class - List methodInterfaces = Lists.newArrayList(); for (String interfaceName : getInterfaces(obfMethodEntry.getClassName())) { + // is this method defined in this interface? MethodEntry methodInterface = new MethodEntry( new ClassEntry(interfaceName), @@ -538,21 +537,20 @@ public class JarIndex { obfMethodEntry.getSignature() ); if (containsObfBehavior(methodInterface)) { - methodInterfaces.add(methodInterface); + interfaceMethodEntries.add(methodInterface); } } - if (methodInterfaces.isEmpty()) { - return null; - } - if (methodInterfaces.size() > 1) { - throw new Error("Too many interfaces define this method! This is not yet supported by Enigma!"); - } - interfaceMethodEntry = methodInterfaces.get(0); } - MethodImplementationsTreeNode rootNode = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry); - rootNode.load(this); - return rootNode; + List nodes = Lists.newArrayList(); + if (!interfaceMethodEntries.isEmpty()) { + for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) { + MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry); + node.load(this); + nodes.add(node); + } + } + return nodes; } public Set getRelatedMethodImplementations(MethodEntry obfMethodEntry) { @@ -569,9 +567,8 @@ public class JarIndex { } // look at interface methods too - MethodImplementationsTreeNode implementations = getMethodImplementations(null, methodEntry); - if (implementations != null) { - getRelatedMethodImplementations(methodEntries, implementations); + for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(null, methodEntry)) { + getRelatedMethodImplementations(methodEntries, implementationsNode); } // recurse diff --git a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java index 1009226..6cafc55 100644 --- a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java +++ b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java @@ -63,6 +63,7 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { } public void load(JarIndex index) { + // get all method implementations List nodes = Lists.newArrayList(); for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) { diff --git a/src/cuchaz/enigma/analysis/RelatedMethodChecker.java b/src/cuchaz/enigma/analysis/RelatedMethodChecker.java new file mode 100644 index 0000000..5bd67a0 --- /dev/null +++ b/src/cuchaz/enigma/analysis/RelatedMethodChecker.java @@ -0,0 +1,96 @@ +package cuchaz.enigma.analysis; + +import java.util.Map; +import java.util.Set; + +import com.beust.jcommander.internal.Maps; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.MethodMapping; + +public class RelatedMethodChecker { + + private JarIndex m_jarIndex; + private Map,String> m_deobfNamesByGroup; + private Map m_deobfNamesByObfMethod; + private Map> m_groupsByObfMethod; + private Set> m_inconsistentGroups; + + public RelatedMethodChecker(JarIndex jarIndex) { + m_jarIndex = jarIndex; + m_deobfNamesByGroup = Maps.newHashMap(); + m_deobfNamesByObfMethod = Maps.newHashMap(); + m_groupsByObfMethod = Maps.newHashMap(); + m_inconsistentGroups = Sets.newHashSet(); + } + + public void checkMethod(ClassEntry classEntry, MethodMapping methodMapping) { + + // TEMP: disable the expensive check for now, maybe we can optimize it later, or just use it for debugging + if (true) return; + + BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); + if (!(obfBehaviorEntry instanceof MethodEntry)) { + // only methods have related implementations + return; + } + MethodEntry obfMethodEntry = (MethodEntry)obfBehaviorEntry; + String deobfName = methodMapping.getDeobfName(); + m_deobfNamesByObfMethod.put(obfMethodEntry, deobfName); + + // have we seen this method's group before? + Set group = m_groupsByObfMethod.get(obfMethodEntry); + if (group == null) { + + // no, compute the group and save the name + group = m_jarIndex.getRelatedMethodImplementations(obfMethodEntry); + m_deobfNamesByGroup.put(group, deobfName); + + assert(group.contains(obfMethodEntry)); + for (MethodEntry relatedMethodEntry : group) { + m_groupsByObfMethod.put(relatedMethodEntry, group); + } + } + + // check the name + if (!sameName(m_deobfNamesByGroup.get(group), deobfName)) { + m_inconsistentGroups.add(group); + } + } + + private boolean sameName(String a, String b) { + if (a == null && b == null) { + return true; + } else if (a != null && b != null) { + return a.equals(b); + } + return false; + } + + public boolean hasProblems() { + return m_inconsistentGroups.size() > 0; + } + + public String getReport() { + StringBuilder buf = new StringBuilder(); + buf.append(m_inconsistentGroups.size()); + buf.append(" groups of methods related by inheritance and/or interfaces have different deobf names!\n"); + for (Set group : m_inconsistentGroups) { + buf.append("\tGroup with "); + buf.append(group.size()); + buf.append(" methods:\n"); + for (MethodEntry methodEntry : group) { + buf.append("\t\t"); + buf.append(methodEntry.toString()); + buf.append(" => "); + buf.append(m_deobfNamesByObfMethod.get(methodEntry)); + buf.append("\n"); + } + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java index e2ff300..d6692f6 100644 --- a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -26,11 +26,10 @@ import com.strobel.decompiler.languages.java.ast.TypeDeclaration; import com.strobel.decompiler.languages.java.ast.VariableInitializer; import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.BehaviorEntryFactory; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.Signature; import cuchaz.enigma.mapping.Type; public class SourceIndexClassVisitor extends SourceIndexVisitor { @@ -69,12 +68,13 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { @Override public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); - BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(classEntry, def.getName(), def.getSignature()); + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(def); AstNode tokenNode = node.getNameToken(); + if (behaviorEntry instanceof ConstructorEntry) { ConstructorEntry constructorEntry = (ConstructorEntry)behaviorEntry; if (constructorEntry.isStatic()) { + // for static initializers, check elsewhere for the token node tokenNode = node.getModifiers().firstOrNullObject(); } } @@ -85,8 +85,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { @Override public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); - ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(def.getSignature())); + ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(def); index.addDeclaration(node.getNameToken(), constructorEntry); return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index); } diff --git a/src/cuchaz/enigma/analysis/TranslationIndex.java b/src/cuchaz/enigma/analysis/TranslationIndex.java index 7597c3a..8651ebd 100644 --- a/src/cuchaz/enigma/analysis/TranslationIndex.java +++ b/src/cuchaz/enigma/analysis/TranslationIndex.java @@ -37,7 +37,7 @@ import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.Translator; public class TranslationIndex implements Serializable { @@ -85,23 +85,23 @@ public class TranslationIndex implements Serializable { public void indexClass(CtClass c) { - ClassEntry classEntry = JavassistUtil.getClassEntry(c); + ClassEntry classEntry = EntryFactory.getClassEntry(c); // add the superclass - ClassEntry superclassEntry = JavassistUtil.getSuperclassEntry(c); + ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c); if (!isJre(classEntry) && superclassEntry != null && !isJre(superclassEntry)) { m_superclasses.put(classEntry, superclassEntry); } // add fields for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = JavassistUtil.getFieldEntry(field); + FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry); } // add behaviors for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior); + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); } } diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java index 4dba0d8..4167731 100644 --- a/src/cuchaz/enigma/bytecode/ClassTranslator.java +++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java @@ -23,10 +23,9 @@ import javassist.bytecode.SourceFileAttribute; import com.google.common.collect.Maps; import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.BehaviorEntryFactory; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.JavassistUtil; import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.Signature; import cuchaz.enigma.mapping.Translator; @@ -74,7 +73,7 @@ public class ClassTranslator { case ConstPool.CONST_InterfaceMethodref: { // translate the name and type - BehaviorEntry entry = BehaviorEntryFactory.create( + BehaviorEntry entry = EntryFactory.getBehaviorEntry( Descriptor.toJvmName(editor.getMemberrefClassname(i)), editor.getMemberrefName(i), editor.getMemberrefType(i) @@ -95,7 +94,7 @@ public class ClassTranslator { for (CtField field : c.getDeclaredFields()) { // translate the name - FieldEntry entry = JavassistUtil.getFieldEntry(field); + FieldEntry entry = EntryFactory.getFieldEntry(field); String translatedName = m_translator.translate(entry); if (translatedName != null) { field.setName(translatedName); @@ -112,7 +111,7 @@ public class ClassTranslator { CtMethod method = (CtMethod)behavior; // translate the name - MethodEntry entry = JavassistUtil.getMethodEntry(method); + MethodEntry entry = EntryFactory.getMethodEntry(method); String translatedName = m_translator.translate(entry); if (translatedName != null) { method.setName(translatedName); diff --git a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java index 853928c..f64ca02 100644 --- a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java +++ b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java @@ -17,7 +17,7 @@ import javassist.CtBehavior; import javassist.CtClass; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.BehaviorEntryFactory; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.Signature; import cuchaz.enigma.mapping.Translator; @@ -33,7 +33,7 @@ public class MethodParameterWriter { // Procyon will read method arguments from the "MethodParameters" attribute, so write those for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); // get the number of arguments Signature signature = behaviorEntry.getSignature(); diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index bb729a3..b514012 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -53,7 +53,7 @@ import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassNameReplacer; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.Signature; public class ClassIdentity { @@ -116,13 +116,13 @@ public class ClassIdentity { m_references = HashMultiset.create(); if (useReferences) { for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = JavassistUtil.getFieldEntry(field); + FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); for (EntryReference reference : index.getFieldReferences(fieldEntry)) { addReference(reference); } } for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior); + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); for (EntryReference reference : index.getBehaviorReferences(behaviorEntry)) { addReference(reference); } diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java index ccf6b78..d70b8eb 100644 --- a/src/cuchaz/enigma/convert/ClassMatcher.java +++ b/src/cuchaz/enigma/convert/ClassMatcher.java @@ -42,7 +42,7 @@ import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; -import cuchaz.enigma.mapping.JavassistUtil; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsReader; @@ -242,13 +242,13 @@ public class ClassMatcher { System.err.println("\tAvailable dest methods:"); CtClass c = destLoader.loadClass(classMapping.getObfName()); for (CtBehavior behavior : c.getDeclaredBehaviors()) { - System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior)); + System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); } System.err.println("\tAvailable source methods:"); c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfName())); for (CtBehavior behavior : c.getDeclaredBehaviors()) { - System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior)); + System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); } } } diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java index 61fea9c..9fa633e 100644 --- a/src/cuchaz/enigma/gui/GuiController.java +++ b/src/cuchaz/enigma/gui/GuiController.java @@ -186,14 +186,17 @@ public class GuiController { public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry); - MethodImplementationsTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodImplementations( + List rootNodes = m_deobfuscator.getJarIndex().getMethodImplementations( m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry ); - if (rootNode == null) { + if (rootNodes.isEmpty()) { return null; } - return MethodImplementationsTreeNode.findNode(rootNode, obfMethodEntry); + if (rootNodes.size() > 1) { + System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one."); + } + return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry); } public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { diff --git a/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java deleted file mode 100644 index 61e501b..0000000 --- a/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ -package cuchaz.enigma.mapping; - -import javassist.CtBehavior; -import javassist.CtConstructor; -import javassist.CtMethod; -import javassist.bytecode.Descriptor; - -public class BehaviorEntryFactory { - - public static BehaviorEntry create(String className, String name, String signature) { - return create(new ClassEntry(className), name, signature); - } - - public static BehaviorEntry create(ClassEntry classEntry, String name, String signature) { - if (name.equals("")) { - return new ConstructorEntry(classEntry, new Signature(signature)); - } else if (name.equals("")) { - return new ConstructorEntry(classEntry); - } else { - return new MethodEntry(classEntry, name, new Signature(signature)); - } - } - - public static BehaviorEntry create(CtBehavior behavior) { - String className = Descriptor.toJvmName(behavior.getDeclaringClass().getName()); - if (behavior instanceof CtMethod) { - return create(className, behavior.getName(), behavior.getSignature()); - } else if (behavior instanceof CtConstructor) { - CtConstructor constructor = (CtConstructor)behavior; - if (constructor.isClassInitializer()) { - return create(className, "", null); - } else { - return create(className, "", constructor.getSignature()); - } - } else { - throw new IllegalArgumentException("Unable to create BehaviorEntry from " + behavior); - } - } - - public static BehaviorEntry createObf(ClassEntry classEntry, MethodMapping methodMapping) { - return create(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature().toString()); - } - - public static BehaviorEntry createDeobf(ClassEntry classEntry, MethodMapping methodMapping) { - return create(classEntry, methodMapping.getDeobfName(), methodMapping.getObfSignature().toString()); - } -} diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java new file mode 100644 index 0000000..dceea29 --- /dev/null +++ b/src/cuchaz/enigma/mapping/EntryFactory.java @@ -0,0 +1,184 @@ +package cuchaz.enigma.mapping; + +import java.util.List; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtField; +import javassist.CtMethod; +import javassist.bytecode.Descriptor; +import javassist.expr.ConstructorCall; +import javassist.expr.FieldAccess; +import javassist.expr.MethodCall; +import javassist.expr.NewExpr; + +import com.beust.jcommander.internal.Lists; +import com.strobel.assembler.metadata.MethodDefinition; + +import cuchaz.enigma.analysis.JarIndex; + +public class EntryFactory { + + public static ClassEntry getClassEntry(CtClass c) { + return new ClassEntry(Descriptor.toJvmName(c.getName())); + } + + public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { + return new ClassEntry(getChainedOuterClassName(jarIndex, classMapping.getObfName())); + } + + private static String getChainedOuterClassName(JarIndex jarIndex, String obfClassName) { + + // lookup the chain of outer classes + List obfOuterClassNames = Lists.newArrayList(); + String checkName = obfClassName; + while (true) { + + // if this class name has a package, then it can't be an inner class + if (!new ClassEntry(checkName).isInDefaultPackage()) { + break; + } + + String obfOuterClassName = jarIndex.getOuterClass(checkName); + if (obfOuterClassName != null) { + obfOuterClassNames.add(obfOuterClassName); + checkName = obfOuterClassName; + } else { + break; + } + } + + // build the chained class name + StringBuilder buf = new StringBuilder(); + for (int i=obfOuterClassNames.size()-1; i>=0; i--) { + buf.append(obfOuterClassNames.get(i)); + buf.append("$"); + } + buf.append(obfClassName); + return buf.toString(); + } + + public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { + return new ClassEntry(classMapping.getDeobfName()); + } + + public static ClassEntry getSuperclassEntry(CtClass c) { + return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); + } + + public static FieldEntry getFieldEntry(CtField field) { + return new FieldEntry( + getClassEntry(field.getDeclaringClass()), + field.getName(), + new Type(field.getFieldInfo().getDescriptor()) + ); + } + + public static FieldEntry getFieldEntry(FieldAccess call) { + return new FieldEntry( + new ClassEntry(Descriptor.toJvmName(call.getClassName())), + call.getFieldName(), + new Type(call.getSignature()) + ); + } + + public static MethodEntry getMethodEntry(CtMethod method) { + return new MethodEntry( + getClassEntry(method.getDeclaringClass()), + method.getName(), + new Signature(method.getMethodInfo().getDescriptor()) + ); + } + + public static MethodEntry getMethodEntry(MethodCall call) { + return new MethodEntry( + new ClassEntry(Descriptor.toJvmName(call.getClassName())), + call.getMethodName(), + new Signature(call.getSignature()) + ); + } + + public static MethodEntry getMethodEntry(MethodDefinition def) { + return new MethodEntry( + new ClassEntry(def.getDeclaringType().getInternalName()), + def.getName(), + new Signature(def.getSignature()) + ); + } + + public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { + if (constructor.isClassInitializer()) { + return new ConstructorEntry( + getClassEntry(constructor.getDeclaringClass()) + ); + } else { + return new ConstructorEntry( + getClassEntry(constructor.getDeclaringClass()), + new Signature(constructor.getMethodInfo().getDescriptor()) + ); + } + } + + public static ConstructorEntry getConstructorEntry(ConstructorCall call) { + return new ConstructorEntry( + new ClassEntry(Descriptor.toJvmName(call.getClassName())), + new Signature(call.getSignature()) + ); + } + + public static ConstructorEntry getConstructorEntry(NewExpr call) { + return new ConstructorEntry( + new ClassEntry(Descriptor.toJvmName(call.getClassName())), + new Signature(call.getSignature()) + ); + } + + public static ConstructorEntry getConstructorEntry(MethodDefinition def) { + if (def.isTypeInitializer()) { + return new ConstructorEntry( + new ClassEntry(def.getDeclaringType().getInternalName()) + ); + } else { + return new ConstructorEntry( + new ClassEntry(def.getDeclaringType().getInternalName()), + new Signature(def.getSignature()) + ); + } + } + + public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { + if (behavior instanceof CtMethod) { + return getMethodEntry((CtMethod)behavior); + } else if (behavior instanceof CtConstructor) { + return getConstructorEntry((CtConstructor)behavior); + } + throw new Error("behavior is neither Method nor Constructor!"); + } + + public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) { + return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature)); + } + + public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) { + if (behaviorName.equals("")) { + return new ConstructorEntry(classEntry, behaviorSignature); + } else if(behaviorName.equals("")) { + return new ConstructorEntry(classEntry); + } else { + return new MethodEntry(classEntry, behaviorName, behaviorSignature); + } + } + + public static BehaviorEntry getBehaviorEntry(MethodDefinition def) { + if (def.isConstructor() || def.isTypeInitializer()) { + return getConstructorEntry(def); + } else { + return getMethodEntry(def); + } + } + + public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) { + return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature()); + } +} diff --git a/src/cuchaz/enigma/mapping/JavassistUtil.java b/src/cuchaz/enigma/mapping/JavassistUtil.java deleted file mode 100644 index 0d6ce6a..0000000 --- a/src/cuchaz/enigma/mapping/JavassistUtil.java +++ /dev/null @@ -1,85 +0,0 @@ -package cuchaz.enigma.mapping; - -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtConstructor; -import javassist.CtField; -import javassist.CtMethod; -import javassist.bytecode.Descriptor; -import javassist.expr.ConstructorCall; -import javassist.expr.FieldAccess; -import javassist.expr.MethodCall; -import javassist.expr.NewExpr; - -public class JavassistUtil { - - public static ClassEntry getClassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getName())); - } - - public static ClassEntry getSuperclassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); - } - - public static MethodEntry getMethodEntry(CtMethod method) { - return new MethodEntry( - getClassEntry(method.getDeclaringClass()), - method.getName(), - new Signature(method.getMethodInfo().getDescriptor()) - ); - } - - public static MethodEntry getMethodEntry(MethodCall call) { - return new MethodEntry( - new ClassEntry(Descriptor.toJvmName(call.getClassName())), - call.getMethodName(), - new Signature(call.getSignature()) - ); - } - - public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { - return new ConstructorEntry( - getClassEntry(constructor.getDeclaringClass()), - new Signature(constructor.getMethodInfo().getDescriptor()) - ); - } - - public static ConstructorEntry getConstructorEntry(ConstructorCall call) { - return new ConstructorEntry( - new ClassEntry(Descriptor.toJvmName(call.getClassName())), - new Signature(call.getSignature()) - ); - } - - public static ConstructorEntry getConstructorEntry(NewExpr call) { - return new ConstructorEntry( - new ClassEntry(Descriptor.toJvmName(call.getClassName())), - new Signature(call.getSignature()) - ); - } - - public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { - if (behavior instanceof CtMethod) { - return getMethodEntry((CtMethod)behavior); - } else if (behavior instanceof CtConstructor) { - return getConstructorEntry((CtConstructor)behavior); - } - throw new Error("behavior is neither Method nor Constructor!"); - } - - public static FieldEntry getFieldEntry(CtField field) { - return new FieldEntry( - getClassEntry(field.getDeclaringClass()), - field.getName(), - new Type(field.getFieldInfo().getDescriptor()) - ); - } - - public static FieldEntry getFieldEntry(FieldAccess call) { - return new FieldEntry( - new ClassEntry(Descriptor.toJvmName(call.getClassName())), - call.getFieldName(), - new Type(call.getSignature()) - ); - } -} -- cgit v1.2.3 From ab6de199201f3cb292b986b2803d7d30b1485a47 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 9 Feb 2015 23:31:24 -0500 Subject: work around bad tokens generated by procyon for now --- src/cuchaz/enigma/TranslatingTypeLoader.java | 5 ++++- src/cuchaz/enigma/analysis/SourceIndex.java | 9 +++++---- .../enigma/analysis/SourceIndexBehaviorVisitor.java | 21 ++------------------- .../enigma/analysis/SourceIndexClassVisitor.java | 4 ++-- src/cuchaz/enigma/bytecode/InnerClassWriter.java | 6 +++--- 5 files changed, 16 insertions(+), 29 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index cfa03a1..8bec17f 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -104,6 +104,7 @@ public class TranslatingTypeLoader implements ITypeLoader { } private byte[] loadType(String deobfClassName) { + ClassEntry deobfClassEntry = new ClassEntry(deobfClassName); ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry); @@ -176,7 +177,9 @@ public class TranslatingTypeLoader implements ITypeLoader { } } - public CtClass transformClass(CtClass c) throws IOException, NotFoundException, CannotCompileException { + public CtClass transformClass(CtClass c) + throws IOException, NotFoundException, CannotCompileException { + // we moved a lot of classes out of the default package into the none package // make sure all the class references are consistent ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java index b43ab61..e31b803 100644 --- a/src/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/cuchaz/enigma/analysis/SourceIndex.java @@ -82,10 +82,11 @@ public class SourceIndex { // DEBUG // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) ); - // for tokens representing inner classes, make sure we only get the simple name - int pos = name.lastIndexOf('$'); - if (pos >= 0) { - token.end -= pos + 1; + // if the token has a $ in it, something's wrong. Ignore this token + if (name.lastIndexOf('$') >= 0) { + // DEBUG + System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name)); + return null; } return token; diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java index b4094d9..a9a055b 100644 --- a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java +++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java @@ -17,12 +17,10 @@ import com.strobel.assembler.metadata.ParameterDefinition; import com.strobel.assembler.metadata.TypeReference; import com.strobel.decompiler.languages.TextLocation; import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; import com.strobel.decompiler.languages.java.ast.IdentifierExpression; import com.strobel.decompiler.languages.java.ast.InvocationExpression; import com.strobel.decompiler.languages.java.ast.Keys; import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; -import com.strobel.decompiler.languages.java.ast.MethodDeclaration; import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; import com.strobel.decompiler.languages.java.ast.SimpleType; @@ -33,6 +31,7 @@ import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.Signature; @@ -46,16 +45,6 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { m_behaviorEntry = behaviorEntry; } - @Override - public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { - return recurse(node, index); - } - @Override public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); @@ -122,14 +111,8 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { @Override public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); MethodDefinition methodDef = (MethodDefinition)def.getMethod(); - BehaviorEntry behaviorEntry; - if (methodDef.isConstructor()) { - behaviorEntry = new ConstructorEntry(classEntry, new Signature(methodDef.getSignature())); - } else { - behaviorEntry = new MethodEntry(classEntry, methodDef.getName(), new Signature(methodDef.getSignature())); - } + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(methodDef); ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName()); index.addDeclaration(node.getNameToken(), argumentEntry); diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java index d6692f6..f4f4956 100644 --- a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -94,7 +94,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName(), new Type(def.getSignature())); + FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName(), new Type(def.getErasedSignature())); assert (node.getVariables().size() == 1); VariableInitializer variable = node.getVariables().firstOrNullObject(); index.addDeclaration(variable.getNameToken(), fieldEntry); @@ -107,7 +107,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { // treat enum declarations as field declarations FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName(), new Type(def.getSignature())); + FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName(), new Type(def.getErasedSignature())); index.addDeclaration(node.getNameToken(), fieldEntry); return recurse(node, index); diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java index 817500b..5350b86 100644 --- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -13,7 +13,6 @@ package cuchaz.enigma.bytecode; import java.util.Collection; import javassist.CtClass; -import javassist.bytecode.AccessFlag; import javassist.bytecode.ConstPool; import javassist.bytecode.Descriptor; import javassist.bytecode.EnclosingMethodAttribute; @@ -77,18 +76,19 @@ public class InnerClassWriter { int innerClassIndex = constPool.addClassInfo(obfClassEntry.getName()); int outerClassIndex = 0; int innerClassSimpleNameIndex = 0; + int accessFlags = 0; if (!m_jarIndex.isAnonymousClass(obfInnerClassName)) { outerClassIndex = constPool.addClassInfo(obfClassEntry.getOuterClassName()); innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getInnerClassName()); } - attr.append(innerClassIndex, outerClassIndex, innerClassSimpleNameIndex, c.getClassFile().getAccessFlags() & ~AccessFlag.SUPER); + attr.append(innerClassIndex, outerClassIndex, innerClassSimpleNameIndex, accessFlags); /* DEBUG System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", obfClassEntry, - attr.outerClass(attr.tableLength() - 1), attr.innerClass(attr.tableLength() - 1), + attr.outerClass(attr.tableLength() - 1), attr.innerName(attr.tableLength() - 1), Constants.NonePackage + "/" + obfInnerClassName, obfClassEntry.getName() -- cgit v1.2.3 From 2b3c5c52865b40adfa93910d41738242f17338d4 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 10 Feb 2015 22:10:16 -0500 Subject: add BRIDGE flag to bridge methods --- src/cuchaz/enigma/TranslatingTypeLoader.java | 2 + src/cuchaz/enigma/analysis/BridgeMarker.java | 36 ++++++++++++++++++ src/cuchaz/enigma/analysis/JarIndex.java | 55 ++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 src/cuchaz/enigma/analysis/BridgeMarker.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index 8bec17f..19e3d2a 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -29,6 +29,7 @@ import com.strobel.assembler.metadata.Buffer; import com.strobel.assembler.metadata.ClasspathTypeLoader; import com.strobel.assembler.metadata.ITypeLoader; +import cuchaz.enigma.analysis.BridgeMarker; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.bytecode.ClassRenamer; import cuchaz.enigma.bytecode.ClassTranslator; @@ -198,6 +199,7 @@ public class TranslatingTypeLoader implements ITypeLoader { assertClassName(c, obfClassEntry); // do all kinds of deobfuscating transformations on the class + new BridgeMarker(m_jarIndex.getBridgedMethods()).markBridges(c); new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c); new ClassTranslator(m_deobfuscatingTranslator).translate(c); diff --git a/src/cuchaz/enigma/analysis/BridgeMarker.java b/src/cuchaz/enigma/analysis/BridgeMarker.java new file mode 100644 index 0000000..e80f87d --- /dev/null +++ b/src/cuchaz/enigma/analysis/BridgeMarker.java @@ -0,0 +1,36 @@ +package cuchaz.enigma.analysis; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.bytecode.AccessFlag; + +import com.google.common.collect.BiMap; + +import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.MethodEntry; + +public class BridgeMarker { + + private BiMap m_bridgedMethods; + + public BridgeMarker(BiMap bridgedMethods) { + m_bridgedMethods = bridgedMethods; + } + + public void markBridges(CtClass c) { + + for (CtMethod method : c.getDeclaredMethods()) { + MethodEntry methodEntry = EntryFactory.getMethodEntry(method); + + // is this a bridge method? + MethodEntry bridgedMethodEntry = m_bridgedMethods.get(methodEntry); + if (bridgedMethodEntry != null) { + + // it's a bridge method! add the bridge flag + int flags = method.getMethodInfo().getAccessFlags(); + flags |= AccessFlag.BRIDGE; + method.getMethodInfo().setAccessFlags(flags); + } + } + } +} diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 24d110e..797deb8 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -23,6 +23,8 @@ import javassist.CtBehavior; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; +import javassist.CtMethod; +import javassist.NotFoundException; import javassist.bytecode.AccessFlag; import javassist.bytecode.Descriptor; import javassist.bytecode.FieldInfo; @@ -32,6 +34,8 @@ import javassist.expr.FieldAccess; import javassist.expr.MethodCall; import javassist.expr.NewExpr; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -62,6 +66,7 @@ public class JarIndex { private Multimap m_innerClasses; private Map m_outerClasses; private Map m_anonymousClasses; + private BiMap m_bridgedMethods; public JarIndex() { m_obfClassEntries = Sets.newHashSet(); @@ -74,6 +79,7 @@ public class JarIndex { m_innerClasses = HashMultimap.create(); m_outerClasses = Maps.newHashMap(); m_anonymousClasses = Maps.newHashMap(); + m_bridgedMethods = HashBiMap.create(); } public void indexJar(JarFile jar, boolean buildInnerClasses) { @@ -171,6 +177,12 @@ public class JarIndex { // index implementation m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry); + + // look for bridge and bridged methods + CtMethod bridgedMethod = getBridgedMethod((CtMethod)behavior); + if (bridgedMethod != null) { + m_bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod)); + } } // looks like we don't care about constructors here } @@ -241,6 +253,45 @@ public class JarIndex { } } + private CtMethod getBridgedMethod(CtMethod method) { + + // bridge methods just call another method, cast it to the return type, and return the result + // let's see if we can detect this scenario + + // skip non-synthetic methods + if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) { + return null; + } + + // get all the called methods + final List methodCalls = Lists.newArrayList(); + try { + method.instrument(new ExprEditor() { + @Override + public void edit(MethodCall call) { + methodCalls.add(call); + } + }); + } catch (CannotCompileException ex) { + // this is stupid... we're not even compiling anything + throw new Error(ex); + } + + // is there just one? + if (methodCalls.size() != 1) { + return null; + } + MethodCall call = methodCalls.get(0); + + try { + // we have a bridge method! + return call.getMethod(); + } catch (NotFoundException ex) { + // can't find the type? not a bridge method + return null; + } + } + private String findOuterClass(CtClass c) { // inner classes: @@ -706,4 +757,8 @@ public class JarIndex { throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); } } + + public BiMap getBridgedMethods() { + return m_bridgedMethods; + } } -- cgit v1.2.3 From c6a194dcf933dd7a4e2bf6b92bcb417957aba765 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 10 Feb 2015 22:23:12 -0500 Subject: ignore harmless exceptions I can't fix fyi, it's really hard to test this fix because the exception is not generally reproducable --- src/cuchaz/enigma/ExceptionIgnorer.java | 24 ++++++++++++++++++++++++ src/cuchaz/enigma/gui/Gui.java | 9 ++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/cuchaz/enigma/ExceptionIgnorer.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ExceptionIgnorer.java b/src/cuchaz/enigma/ExceptionIgnorer.java new file mode 100644 index 0000000..37c67da --- /dev/null +++ b/src/cuchaz/enigma/ExceptionIgnorer.java @@ -0,0 +1,24 @@ +package cuchaz.enigma; + +public class ExceptionIgnorer { + + public static boolean shouldIgnore(Throwable t) { + + // is this that pesky concurrent access bug in the highlight painter system? + // (ancient ui code is ancient) + if (t instanceof ArrayIndexOutOfBoundsException) { + StackTraceElement[] stackTrace = t.getStackTrace(); + if (stackTrace.length > 1) { + + // does this stack frame match javax.swing.text.DefaultHighlighter.paint() ? + StackTraceElement frame = stackTrace[1]; + if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().equals("paint")) { + return true; + } + } + } + + return false; + } + +} diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java index 187ef5b..00cff59 100644 --- a/src/cuchaz/enigma/gui/Gui.java +++ b/src/cuchaz/enigma/gui/Gui.java @@ -70,6 +70,7 @@ import jsyntaxpane.DefaultSyntaxKit; import com.google.common.collect.Lists; import cuchaz.enigma.Constants; +import cuchaz.enigma.ExceptionIgnorer; import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; import cuchaz.enigma.analysis.ClassImplementationsTreeNode; import cuchaz.enigma.analysis.ClassInheritanceTreeNode; @@ -147,9 +148,11 @@ public class Gui { CrashDialog.init(m_frame); Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override - public void uncaughtException(Thread thread, Throwable ex) { - ex.printStackTrace(System.err); - CrashDialog.show(ex); + public void uncaughtException(Thread thread, Throwable t) { + t.printStackTrace(System.err); + if (!ExceptionIgnorer.shouldIgnore(t)) { + CrashDialog.show(t); + } } }); } -- cgit v1.2.3 From ef3c296d17d8213dfadd66212d66d9e92c089402 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 10 Feb 2015 22:32:00 -0500 Subject: fix issue with removing field mappings --- src/cuchaz/enigma/mapping/ClassMapping.java | 2 ++ src/cuchaz/enigma/mapping/MappingsRenamer.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java index 7133265..885400b 100644 --- a/src/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -221,6 +221,7 @@ public class ClassMapping implements Serializable, Comparable { public void setFieldName(String obfName, Type obfType, String deobfName) { + assert(deobfName != null); FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType)); if (fieldMapping == null) { fieldMapping = new FieldMapping(obfName, obfType, deobfName); @@ -316,6 +317,7 @@ public class ClassMapping implements Serializable, Comparable { //// ARGUMENTS //////// public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { + assert(argumentName != null); MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); if (methodMapping == null) { methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java index 095e5e9..ea343c4 100644 --- a/src/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java @@ -87,7 +87,7 @@ public class MappingsRenamer { public void removeFieldMapping(FieldEntry obf) { ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getType(), null); + classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType())); } public void markFieldAsDeobfuscated(FieldEntry obf) { -- cgit v1.2.3 From 6044c91079ae416ecaff2412f8ca8653f39e6f83 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 10 Feb 2015 22:51:55 -0500 Subject: black list Object methods from deobfuscation --- src/cuchaz/enigma/Deobfuscator.java | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index b54a94a..9cd6e41 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -397,6 +397,38 @@ public class Deobfuscator { } public boolean isObfuscatedIdentifier(Entry obfEntry) { + + if (obfEntry instanceof MethodEntry) { + + // HACKHACK: Object methods are not obfuscated identifiers + MethodEntry obfMethodEntry = (MethodEntry)obfEntry; + String name = obfMethodEntry.getName(); + String sig = obfMethodEntry.getSignature().toString(); + if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { + return false; + } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { + return false; + } else if (name.equals("finalize") && sig.equals("()V")) { + return false; + } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) { + return false; + } else if (name.equals("hashCode") && sig.equals("()I")) { + return false; + } else if (name.equals("notify") && sig.equals("()V")) { + return false; + } else if (name.equals("notifyAll") && sig.equals("()V")) { + return false; + } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) { + return false; + } else if (name.equals("wait") && sig.equals("()V")) { + return false; + } else if (name.equals("wait") && sig.equals("(J)V")) { + return false; + } else if (name.equals("wait") && sig.equals("(JI)V")) { + return false; + } + } + return m_jarIndex.containsObfEntry(obfEntry); } -- cgit v1.2.3 From 1bddb51a8370f96af2dfd61e75d72b155b71923e Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 10 Feb 2015 23:26:33 -0500 Subject: repackage as v0.7b --- src/cuchaz/enigma/Constants.java | 2 +- src/cuchaz/enigma/TranslatingTypeLoader.java | 2 +- src/cuchaz/enigma/analysis/BridgeMarker.java | 11 ++++------- src/cuchaz/enigma/analysis/JarIndex.java | 10 ++++------ 4 files changed, 10 insertions(+), 15 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java index a1ba2e9..3bf3999 100644 --- a/src/cuchaz/enigma/Constants.java +++ b/src/cuchaz/enigma/Constants.java @@ -12,7 +12,7 @@ package cuchaz.enigma; public class Constants { public static final String Name = "Enigma"; - public static final String Version = "0.6 beta"; + public static final String Version = "0.7 beta"; public static final String Url = "http://www.cuchazinteractive.com/enigma"; public static final int MiB = 1024 * 1024; // 1 mebibyte public static final int KiB = 1024; // 1 kebibyte diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index 19e3d2a..12cde4b 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -199,7 +199,7 @@ public class TranslatingTypeLoader implements ITypeLoader { assertClassName(c, obfClassEntry); // do all kinds of deobfuscating transformations on the class - new BridgeMarker(m_jarIndex.getBridgedMethods()).markBridges(c); + new BridgeMarker(m_jarIndex).markBridges(c); new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c); new ClassTranslator(m_deobfuscatingTranslator).translate(c); diff --git a/src/cuchaz/enigma/analysis/BridgeMarker.java b/src/cuchaz/enigma/analysis/BridgeMarker.java index e80f87d..28e3517 100644 --- a/src/cuchaz/enigma/analysis/BridgeMarker.java +++ b/src/cuchaz/enigma/analysis/BridgeMarker.java @@ -3,18 +3,15 @@ package cuchaz.enigma.analysis; import javassist.CtClass; import javassist.CtMethod; import javassist.bytecode.AccessFlag; - -import com.google.common.collect.BiMap; - import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.MethodEntry; public class BridgeMarker { - private BiMap m_bridgedMethods; + private JarIndex m_jarIndex; - public BridgeMarker(BiMap bridgedMethods) { - m_bridgedMethods = bridgedMethods; + public BridgeMarker(JarIndex jarIndex) { + m_jarIndex = jarIndex; } public void markBridges(CtClass c) { @@ -23,7 +20,7 @@ public class BridgeMarker { MethodEntry methodEntry = EntryFactory.getMethodEntry(method); // is this a bridge method? - MethodEntry bridgedMethodEntry = m_bridgedMethods.get(methodEntry); + MethodEntry bridgedMethodEntry = m_jarIndex.getBridgedMethod(methodEntry); if (bridgedMethodEntry != null) { // it's a bridge method! add the bridge flag diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 797deb8..1c74f15 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -34,8 +34,6 @@ import javassist.expr.FieldAccess; import javassist.expr.MethodCall; import javassist.expr.NewExpr; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -66,7 +64,7 @@ public class JarIndex { private Multimap m_innerClasses; private Map m_outerClasses; private Map m_anonymousClasses; - private BiMap m_bridgedMethods; + private Map m_bridgedMethods; public JarIndex() { m_obfClassEntries = Sets.newHashSet(); @@ -79,7 +77,7 @@ public class JarIndex { m_innerClasses = HashMultimap.create(); m_outerClasses = Maps.newHashMap(); m_anonymousClasses = Maps.newHashMap(); - m_bridgedMethods = HashBiMap.create(); + m_bridgedMethods = Maps.newHashMap(); } public void indexJar(JarFile jar, boolean buildInnerClasses) { @@ -758,7 +756,7 @@ public class JarIndex { } } - public BiMap getBridgedMethods() { - return m_bridgedMethods; + public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) { + return m_bridgedMethods.get(bridgeMethodEntry); } } -- cgit v1.2.3 From ddaa0f6bdeeec392764ff36d822d39baf154d8c6 Mon Sep 17 00:00:00 2001 From: jeff Date: Fri, 20 Feb 2015 18:19:57 -0500 Subject: better error messages for procyon type resolution --- src/cuchaz/enigma/Deobfuscator.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 9cd6e41..c1954fc 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -31,6 +31,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; 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; @@ -225,9 +226,15 @@ public class Deobfuscator { getTranslator(TranslationDirection.Obfuscating), getTranslator(TranslationDirection.Deobfuscating) )); + + // see if procyon can find the type + TypeReference type = new MetadataSystem(m_settings.getTypeLoader()).lookupType(lookupClassName); + if (type == null) { + throw new Error("Unable to find type: " + lookupClassName + " (obf name: " + obfClassName + ")"); + } + TypeDefinition resolvedType = type.resolve(); // decompile it! - TypeDefinition resolvedType = new MetadataSystem(m_settings.getTypeLoader()).lookupType(lookupClassName).resolve(); DecompilerContext context = new DecompilerContext(); context.setCurrentType(resolvedType); context.setSettings(m_settings); -- cgit v1.2.3 From 2107e493239333b3c62802c97209775a1e3f543f Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 21 Feb 2015 18:14:24 -0500 Subject: make types serializable --- src/cuchaz/enigma/mapping/Signature.java | 5 ++++- src/cuchaz/enigma/mapping/Type.java | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/mapping/Signature.java b/src/cuchaz/enigma/mapping/Signature.java index ff7f807..273a77b 100644 --- a/src/cuchaz/enigma/mapping/Signature.java +++ b/src/cuchaz/enigma/mapping/Signature.java @@ -1,12 +1,15 @@ package cuchaz.enigma.mapping; +import java.io.Serializable; import java.util.List; import com.beust.jcommander.internal.Lists; import cuchaz.enigma.Util; -public class Signature { +public class Signature implements Serializable { + + private static final long serialVersionUID = -5843719505729497539L; private List m_argumentTypes; private Type m_returnType; diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java index 9f5d52f..d8c073e 100644 --- a/src/cuchaz/enigma/mapping/Type.java +++ b/src/cuchaz/enigma/mapping/Type.java @@ -1,11 +1,14 @@ package cuchaz.enigma.mapping; +import java.io.Serializable; import java.util.Map; import com.google.common.collect.Maps; -public class Type { +public class Type implements Serializable { + private static final long serialVersionUID = 7862257669347104063L; + public enum Primitive { Byte('B'), Character('C'), -- cgit v1.2.3 From 2dc7428e37bdd7a119f53d02ce157675509b0d63 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 23 Feb 2015 23:29:22 -0500 Subject: lots of work in better handling of inner classes also working on recognizing unobfuscated and deobfuscated jars (needed for M3L) --- src/cuchaz/enigma/CommandMain.java | 43 ++++--- src/cuchaz/enigma/Deobfuscator.java | 4 +- src/cuchaz/enigma/MainFormatConverter.java | 2 +- src/cuchaz/enigma/TranslatingTypeLoader.java | 6 +- src/cuchaz/enigma/analysis/JarIndex.java | 67 +++++----- src/cuchaz/enigma/bytecode/InnerClassWriter.java | 43 ++++--- src/cuchaz/enigma/convert/ClassMatcher.java | 6 +- src/cuchaz/enigma/mapping/ClassMapping.java | 41 +++--- src/cuchaz/enigma/mapping/EntryFactory.java | 35 +++--- src/cuchaz/enigma/mapping/Mappings.java | 14 +-- src/cuchaz/enigma/mapping/MappingsRenamer.java | 2 +- src/cuchaz/enigma/mapping/MappingsWriter.java | 4 +- src/cuchaz/enigma/mapping/Translator.java | 154 +++++++++++++++++------ 13 files changed, 261 insertions(+), 160 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/CommandMain.java b/src/cuchaz/enigma/CommandMain.java index 1ec2ad2..0253a92 100644 --- a/src/cuchaz/enigma/CommandMain.java +++ b/src/cuchaz/enigma/CommandMain.java @@ -51,7 +51,7 @@ public class CommandMain { try { // process the command - String command = getArg(args, 0, "command"); + String command = getArg(args, 0, "command", true); if (command.equalsIgnoreCase("deobfuscate")) { deobfuscate(args); } else if(command.equalsIgnoreCase("decompile")) { @@ -70,46 +70,55 @@ public class CommandMain { System.out.println("Usage:"); System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain "); System.out.println("\twhere is one of:"); - System.out.println("\t\tdeobfuscate "); - System.out.println("\t\tdecompile "); + System.out.println("\t\tdeobfuscate []"); + System.out.println("\t\tdecompile []"); } private static void decompile(String[] args) throws Exception { - File fileMappings = getReadableFile(getArg(args, 1, "mappings file")); - File fileJarIn = getReadableFile(getArg(args, 2, "in jar")); - File fileJarOut = getWritableFolder(getArg(args, 3, "out folder")); + File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); + File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)); + File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false)); Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener()); } private static void deobfuscate(String[] args) throws Exception { - File fileMappings = getReadableFile(getArg(args, 1, "mappings file")); - File fileJarIn = getReadableFile(getArg(args, 2, "in jar")); - File fileJarOut = getWritableFile(getArg(args, 3, "out jar")); + File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); + File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); + File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false)); Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener()); } private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar) throws Exception { - System.out.println("Reading mappings..."); - Mappings mappings = new MappingsReader().read(new FileReader(fileMappings)); System.out.println("Reading jar..."); Deobfuscator deobfuscator = new Deobfuscator(jar); - deobfuscator.setMappings(mappings); + if (fileMappings != null) { + System.out.println("Reading mappings..."); + Mappings mappings = new MappingsReader().read(new FileReader(fileMappings)); + deobfuscator.setMappings(mappings); + } return deobfuscator; } - private static String getArg(String[] args, int i, String name) { + private static String getArg(String[] args, int i, String name, boolean required) { if (i >= args.length) { - throw new IllegalArgumentException(name + " is required"); + if (required) { + throw new IllegalArgumentException(name + " is required"); + } else { + return null; + } } return args[i]; } private static File getWritableFile(String path) { + if (path == null) { + return null; + } File file = new File(path).getAbsoluteFile(); File dir = file.getParentFile(); if (dir == null || !dir.exists()) { @@ -119,6 +128,9 @@ public class CommandMain { } private static File getWritableFolder(String path) { + if (path == null) { + return null; + } File dir = new File(path).getAbsoluteFile(); if (!dir.exists()) { throw new IllegalArgumentException("Cannot write to folder: " + dir); @@ -127,6 +139,9 @@ public class CommandMain { } private static File getReadableFile(String path) { + if (path == null) { + return null; + } File file = new File(path).getAbsoluteFile(); if (!file.exists()) { throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index c1954fc..b7440a7 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -163,9 +163,9 @@ public class Deobfuscator { } // check inner classes - for (ClassMapping innerClassMapping : classMapping.innerClasses()) { + for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) { if (!checkClassMapping(relatedMethodChecker, innerClassMapping)) { - System.err.println("WARNING: unable to find inner class " + innerClassMapping + ". dropping mapping."); + System.err.println("WARNING: unable to find inner class " + EntryFactory.getObfClassEntry(m_jarIndex, classMapping) + ". dropping mapping."); classMapping.removeInnerClassMapping(innerClassMapping); } } diff --git a/src/cuchaz/enigma/MainFormatConverter.java b/src/cuchaz/enigma/MainFormatConverter.java index d4bb2db..5db0e53 100644 --- a/src/cuchaz/enigma/MainFormatConverter.java +++ b/src/cuchaz/enigma/MainFormatConverter.java @@ -111,7 +111,7 @@ public class MainFormatConverter { } private static Object getFieldKey(ClassMapping classMapping, FieldMapping fieldMapping) { - return new ClassEntry(classMapping.getObfName()).getSimpleName() + "." + fieldMapping.getObfName(); + return classMapping.getObfSimpleName() + "." + fieldMapping.getObfName(); } private static String getFieldKey(FieldEntry obfFieldEntry) { diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index 12cde4b..26d5e7a 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -110,10 +110,10 @@ public class TranslatingTypeLoader implements ITypeLoader { ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry); // is this an inner class referenced directly? - String obfOuterClassName = m_jarIndex.getOuterClass(obfClassEntry.getSimpleName()); - if (obfOuterClassName != null) { + ClassEntry obfOuterClassEntry = m_jarIndex.getOuterClass(obfClassEntry); + if (obfOuterClassEntry != null) { // this class doesn't really exist. Reference it by outer$inner instead - System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassName)); + System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassEntry)); return null; } diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 1c74f15..6e7c69d 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -61,9 +61,9 @@ public class JarIndex { private Multimap m_methodImplementations; private Multimap> m_behaviorReferences; private Multimap> m_fieldReferences; - private Multimap m_innerClasses; - private Map m_outerClasses; - private Map m_anonymousClasses; + private Multimap m_innerClassesByOuter; + private Map m_outerClassesByInner; + private Map m_anonymousClasses; private Map m_bridgedMethods; public JarIndex() { @@ -74,8 +74,8 @@ public class JarIndex { m_methodImplementations = HashMultimap.create(); m_behaviorReferences = HashMultimap.create(); m_fieldReferences = HashMultimap.create(); - m_innerClasses = HashMultimap.create(); - m_outerClasses = Maps.newHashMap(); + m_innerClassesByOuter = HashMultimap.create(); + m_outerClassesByInner = Maps.newHashMap(); m_anonymousClasses = Maps.newHashMap(); m_bridgedMethods = Maps.newHashMap(); } @@ -129,33 +129,40 @@ public class JarIndex { } if (buildInnerClasses) { + // step 5: index inner classes and anonymous classes for (CtClass c : JarClassIterator.classes(jar)) { ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); - String outerClassName = findOuterClass(c); - if (outerClassName != null) { - String innerClassName = c.getSimpleName(); - m_innerClasses.put(outerClassName, innerClassName); - boolean innerWasAdded = m_outerClasses.put(innerClassName, outerClassName) == null; + ClassEntry innerClassEntry = EntryFactory.getClassEntry(c); + ClassEntry outerClassEntry = findOuterClass(c); + if (outerClassEntry != null) { + m_innerClassesByOuter.put(outerClassEntry, innerClassEntry); + boolean innerWasAdded = m_outerClassesByInner.put(innerClassEntry, outerClassEntry) == null; assert (innerWasAdded); - BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassName); + BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry); if (enclosingBehavior != null) { - m_anonymousClasses.put(innerClassName, enclosingBehavior); + m_anonymousClasses.put(innerClassEntry, enclosingBehavior); // DEBUG - // System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName ); + //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); } else { // DEBUG - // System.out.println( "INNER: " + outerClassName + "$" + innerClassName ); + //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); } } } // step 6: update other indices with inner class info Map renames = Maps.newHashMap(); - for (Map.Entry entry : m_outerClasses.entrySet()) { - renames.put(Constants.NonePackage + "/" + entry.getKey(), entry.getValue() + "$" + entry.getKey()); + for (Map.Entry mapEntry : m_innerClassesByOuter.entries()) { + ClassEntry outerClassEntry = mapEntry.getKey(); + ClassEntry innerClassEntry = mapEntry.getValue(); + outerClassEntry = EntryFactory.getChainedOuterClassName(this, outerClassEntry); + String newName = outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName(); + // DEBUG + //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName); + renames.put(innerClassEntry.getName(), newName); } EntryRenamer.renameClassesInSet(renames, m_obfClassEntries); m_translationIndex.renameClasses(renames); @@ -290,7 +297,7 @@ public class JarIndex { } } - private String findOuterClass(CtClass c) { + private ClassEntry findOuterClass(CtClass c) { // inner classes: // have constructors that can (illegally) set synthetic fields @@ -341,19 +348,19 @@ public class JarIndex { // do we have an answer yet? if (callerClasses.isEmpty()) { if (illegallySetClasses.size() == 1) { - return illegallySetClasses.iterator().next().getName(); + return illegallySetClasses.iterator().next(); } else { System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry)); } } else { if (callerClasses.size() == 1) { - return callerClasses.iterator().next().getName(); + return callerClasses.iterator().next(); } else { // multiple callers, do the illegally set classes narrow it down? Set intersection = Sets.newHashSet(callerClasses); intersection.retainAll(illegallySetClasses); if (intersection.size() == 1) { - return intersection.iterator().next().getName(); + return intersection.iterator().next(); } else { System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses)); } @@ -448,7 +455,7 @@ public class JarIndex { return true; } - private BehaviorEntry isAnonymousClass(CtClass c, String outerClassName) { + private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); @@ -669,23 +676,19 @@ public class JarIndex { return behaviorEntries; } - public Collection getInnerClasses(String obfOuterClassName) { - return m_innerClasses.get(obfOuterClassName); + public Collection getInnerClasses(ClassEntry obfOuterClassEntry) { + return m_innerClassesByOuter.get(obfOuterClassEntry); } - public String getOuterClass(String obfInnerClassName) { - // make sure we use the right name - if (new ClassEntry(obfInnerClassName).getPackageName() != null) { - throw new IllegalArgumentException("Don't reference obfuscated inner classes using packages: " + obfInnerClassName); - } - return m_outerClasses.get(obfInnerClassName); + public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) { + return m_outerClassesByInner.get(obfInnerClassEntry); } - public boolean isAnonymousClass(String obfInnerClassName) { - return m_anonymousClasses.containsKey(obfInnerClassName); + public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) { + return m_anonymousClasses.containsKey(obfInnerClassEntry); } - public BehaviorEntry getAnonymousClassCaller(String obfInnerClassName) { + public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) { return m_anonymousClasses.get(obfInnerClassName); } diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java index 5350b86..e82f56c 100644 --- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -14,13 +14,12 @@ import java.util.Collection; import javassist.CtClass; import javassist.bytecode.ConstPool; -import javassist.bytecode.Descriptor; import javassist.bytecode.EnclosingMethodAttribute; import javassist.bytecode.InnerClassesAttribute; -import cuchaz.enigma.Constants; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.EntryFactory; public class InnerClassWriter { @@ -32,18 +31,23 @@ public class InnerClassWriter { public void write(CtClass c) { - // is this an inner or outer class? - String obfInnerClassName = new ClassEntry(Descriptor.toJvmName(c.getName())).getSimpleName(); - String obfOuterClassName = m_jarIndex.getOuterClass(obfInnerClassName); - if (obfOuterClassName == null) { - // this is an outer class - obfOuterClassName = Descriptor.toJvmName(c.getName()); + // first, assume this is an inner class + ClassEntry obfInnerClassEntry = EntryFactory.getClassEntry(c); + ClassEntry obfOuterClassEntry = m_jarIndex.getOuterClass(obfInnerClassEntry); + + // see if we're right + if (obfOuterClassEntry == null) { + + // nope, it's an outer class + obfInnerClassEntry = null; + obfOuterClassEntry = EntryFactory.getClassEntry(c); } else { - // this is an inner class, rename it to outer$inner - ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName); + + // yeah, it's an inner class, rename it to outer$inner + ClassEntry obfClassEntry = new ClassEntry(obfOuterClassEntry.getName() + "$" + obfInnerClassEntry.getSimpleName()); c.setName(obfClassEntry.getName()); - BehaviorEntry caller = m_jarIndex.getAnonymousClassCaller(obfInnerClassName); + BehaviorEntry caller = m_jarIndex.getAnonymousClassCaller(obfInnerClassEntry); if (caller != null) { // write the enclosing method attribute if (caller.getName().equals("")) { @@ -55,18 +59,19 @@ public class InnerClassWriter { } // write the inner classes if needed - Collection obfInnerClassNames = m_jarIndex.getInnerClasses(obfOuterClassName); - if (obfInnerClassNames != null && !obfInnerClassNames.isEmpty()) { - writeInnerClasses(c, obfOuterClassName, obfInnerClassNames); + Collection obfInnerClassEntries = m_jarIndex.getInnerClasses(obfOuterClassEntry); + if (obfInnerClassEntries != null && !obfInnerClassEntries.isEmpty()) { + writeInnerClasses(c, obfOuterClassEntry, obfInnerClassEntries); } } - private void writeInnerClasses(CtClass c, String obfOuterClassName, Collection obfInnerClassNames) { + private void writeInnerClasses(CtClass c, ClassEntry obfOuterClassEntry, Collection obfInnerClassEntries) { InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); c.getClassFile().addAttribute(attr); - for (String obfInnerClassName : obfInnerClassNames) { + for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) { + // get the new inner class name - ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName); + ClassEntry obfClassEntry = new ClassEntry(obfOuterClassEntry.getName() + "$" + obfInnerClassEntry.getSimpleName()); // here's what the JVM spec says about the InnerClasses attribute // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags ); @@ -77,7 +82,7 @@ public class InnerClassWriter { int outerClassIndex = 0; int innerClassSimpleNameIndex = 0; int accessFlags = 0; - if (!m_jarIndex.isAnonymousClass(obfInnerClassName)) { + if (!m_jarIndex.isAnonymousClass(obfInnerClassEntry)) { outerClassIndex = constPool.addClassInfo(obfClassEntry.getOuterClassName()); innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getInnerClassName()); } @@ -96,7 +101,7 @@ public class InnerClassWriter { */ // make sure the outer class references only the new inner class names - c.replaceClassName(Constants.NonePackage + "/" + obfInnerClassName, obfClassEntry.getName()); + c.replaceClassName(obfInnerClassEntry.getName(), obfClassEntry.getName()); } } } diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java index d70b8eb..224d004 100644 --- a/src/cuchaz/enigma/convert/ClassMatcher.java +++ b/src/cuchaz/enigma/convert/ClassMatcher.java @@ -222,7 +222,7 @@ public class ClassMatcher { // check the method matches System.out.println("Checking methods..."); for (ClassMapping classMapping : mappings.classes()) { - ClassEntry classEntry = new ClassEntry(classMapping.getObfName()); + ClassEntry classEntry = new ClassEntry(classMapping.getObfFullName()); for (MethodMapping methodMapping : classMapping.methods()) { // skip constructors @@ -240,13 +240,13 @@ public class ClassMatcher { // show the available methods System.err.println("\tAvailable dest methods:"); - CtClass c = destLoader.loadClass(classMapping.getObfName()); + CtClass c = destLoader.loadClass(classMapping.getObfFullName()); for (CtBehavior behavior : c.getDeclaredBehaviors()) { System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); } System.err.println("\tAvailable source methods:"); - c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfName())); + c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfFullName())); for (CtBehavior behavior : c.getDeclaredBehaviors()) { System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); } diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java index 885400b..3610e33 100644 --- a/src/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -20,7 +20,8 @@ public class ClassMapping implements Serializable, Comparable { private static final long serialVersionUID = -5148491146902340107L; - private String m_obfName; + private String m_obfFullName; + private String m_obfSimpleName; private String m_deobfName; private Map m_innerClassesByObf; private Map m_innerClassesByDeobf; @@ -34,7 +35,8 @@ public class ClassMapping implements Serializable, Comparable { } public ClassMapping(String obfName, String deobfName) { - m_obfName = obfName; + m_obfFullName = obfName; + m_obfSimpleName = new ClassEntry(obfName).getSimpleName(); m_deobfName = NameValidator.validateClassName(deobfName, false); m_innerClassesByObf = Maps.newHashMap(); m_innerClassesByDeobf = Maps.newHashMap(); @@ -44,8 +46,12 @@ public class ClassMapping implements Serializable, Comparable { m_methodsByDeobf = Maps.newHashMap(); } - public String getObfName() { - return m_obfName; + public String getObfFullName() { + return m_obfFullName; + } + + public String getObfSimpleName() { + return m_obfSimpleName; } public String getDeobfName() { @@ -64,8 +70,7 @@ public class ClassMapping implements Serializable, Comparable { } public void addInnerClassMapping(ClassMapping classMapping) { - assert (isSimpleClassName(classMapping.getObfName())); - boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfName(), classMapping) == null; + boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfSimpleName(), classMapping) == null; assert (obfWasAdded); if (classMapping.getDeobfName() != null) { assert (isSimpleClassName(classMapping.getDeobfName())); @@ -75,7 +80,7 @@ public class ClassMapping implements Serializable, Comparable { } public void removeInnerClassMapping(ClassMapping classMapping) { - boolean obfWasRemoved = m_innerClassesByObf.remove(classMapping.getObfName()) != null; + boolean obfWasRemoved = m_innerClassesByObf.remove(classMapping.getObfSimpleName()) != null; assert (obfWasRemoved); if (classMapping.getDeobfName() != null) { boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; @@ -112,11 +117,11 @@ public class ClassMapping implements Serializable, Comparable { return classMapping; } - public String getObfInnerClassName(String deobfName) { + public String getObfInnerClassSimpleName(String deobfName) { assert (isSimpleClassName(deobfName)); ClassMapping classMapping = m_innerClassesByDeobf.get(deobfName); if (classMapping != null) { - return classMapping.getObfName(); + return classMapping.getObfSimpleName(); } return null; } @@ -163,7 +168,7 @@ public class ClassMapping implements Serializable, Comparable { public void addFieldMapping(FieldMapping fieldMapping) { String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); if (m_fieldsByObf.containsKey(obfKey)) { - throw new Error("Already have mapping for " + m_obfName + "." + obfKey); + throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey); } String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); if (m_fieldsByDeobf.containsKey(deobfKey)) { @@ -257,7 +262,7 @@ public class ClassMapping implements Serializable, Comparable { public void addMethodMapping(MethodMapping methodMapping) { String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); if (m_methodsByObf.containsKey(obfKey)) { - throw new Error("Already have mapping for " + m_obfName + "." + obfKey); + throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey); } boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null; assert (wasAdded); @@ -339,7 +344,7 @@ public class ClassMapping implements Serializable, Comparable { @Override public String toString() { StringBuilder buf = new StringBuilder(); - buf.append(m_obfName); + buf.append(m_obfFullName); buf.append(" <-> "); buf.append(m_deobfName); buf.append("\n"); @@ -359,7 +364,7 @@ public class ClassMapping implements Serializable, Comparable { buf.append("Inner Classes:\n"); for (ClassMapping classMapping : m_innerClassesByObf.values()) { buf.append("\t"); - buf.append(classMapping.getObfName()); + buf.append(classMapping.getObfSimpleName()); buf.append(" <-> "); buf.append(classMapping.getDeobfName()); buf.append("\n"); @@ -370,10 +375,10 @@ public class ClassMapping implements Serializable, Comparable { @Override public int compareTo(ClassMapping other) { // sort by a, b, c, ... aa, ab, etc - if (m_obfName.length() != other.m_obfName.length()) { - return m_obfName.length() - other.m_obfName.length(); + if (m_obfFullName.length() != other.m_obfFullName.length()) { + return m_obfFullName.length() - other.m_obfFullName.length(); } - return m_obfName.compareTo(other.m_obfName); + return m_obfFullName.compareTo(other.m_obfFullName); } public boolean renameObfClass(String oldObfClassName, String newObfClassName) { @@ -399,9 +404,9 @@ public class ClassMapping implements Serializable, Comparable { } } - if (m_obfName.equals(oldObfClassName)) { + if (m_obfFullName.equals(oldObfClassName)) { // rename this class - m_obfName = newObfClassName; + m_obfFullName = newObfClassName; return true; } return false; diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java index dceea29..bbdfa73 100644 --- a/src/cuchaz/enigma/mapping/EntryFactory.java +++ b/src/cuchaz/enigma/mapping/EntryFactory.java @@ -25,25 +25,19 @@ public class EntryFactory { } public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { - return new ClassEntry(getChainedOuterClassName(jarIndex, classMapping.getObfName())); + return getChainedOuterClassName(jarIndex, new ClassEntry(classMapping.getObfFullName())); } - private static String getChainedOuterClassName(JarIndex jarIndex, String obfClassName) { + public static ClassEntry getChainedOuterClassName(JarIndex jarIndex, ClassEntry obfClassEntry) { // lookup the chain of outer classes - List obfOuterClassNames = Lists.newArrayList(); - String checkName = obfClassName; + List obfClassChain = Lists.newArrayList(obfClassEntry); + ClassEntry checkClassEntry = obfClassEntry; while (true) { - - // if this class name has a package, then it can't be an inner class - if (!new ClassEntry(checkName).isInDefaultPackage()) { - break; - } - - String obfOuterClassName = jarIndex.getOuterClass(checkName); - if (obfOuterClassName != null) { - obfOuterClassNames.add(obfOuterClassName); - checkName = obfOuterClassName; + ClassEntry obfOuterClassEntry = jarIndex.getOuterClass(checkClassEntry); + if (obfOuterClassEntry != null) { + obfClassChain.add(obfOuterClassEntry); + checkClassEntry = obfOuterClassEntry; } else { break; } @@ -51,12 +45,15 @@ public class EntryFactory { // build the chained class name StringBuilder buf = new StringBuilder(); - for (int i=obfOuterClassNames.size()-1; i>=0; i--) { - buf.append(obfOuterClassNames.get(i)); - buf.append("$"); + for (int i=obfClassChain.size()-1; i>=0; i--) { + if (buf.length() == 0) { + buf.append(obfClassChain.get(i).getName()); + } else { + buf.append("$"); + buf.append(obfClassChain.get(i).getSimpleName()); + } } - buf.append(obfClassName); - return buf.toString(); + return new ClassEntry(buf.toString()); } public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java index 675fdf1..a85bcbf 100644 --- a/src/cuchaz/enigma/mapping/Mappings.java +++ b/src/cuchaz/enigma/mapping/Mappings.java @@ -37,7 +37,7 @@ public class Mappings implements Serializable { this(); for (ClassMapping classMapping : classes) { - m_classesByObf.put(classMapping.getObfName(), classMapping); + m_classesByObf.put(classMapping.getObfFullName(), classMapping); if (classMapping.getDeobfName() != null) { m_classesByDeobf.put(classMapping.getDeobfName(), classMapping); } @@ -50,10 +50,10 @@ public class Mappings implements Serializable { } public void addClassMapping(ClassMapping classMapping) { - if (m_classesByObf.containsKey(classMapping.getObfName())) { - throw new Error("Already have mapping for " + classMapping.getObfName()); + if (m_classesByObf.containsKey(classMapping.getObfFullName())) { + throw new Error("Already have mapping for " + classMapping.getObfFullName()); } - boolean obfWasAdded = m_classesByObf.put(classMapping.getObfName(), classMapping) == null; + boolean obfWasAdded = m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null; assert (obfWasAdded); if (classMapping.getDeobfName() != null) { if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) { @@ -65,7 +65,7 @@ public class Mappings implements Serializable { } public void removeClassMapping(ClassMapping classMapping) { - boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfName()) != null; + boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfFullName()) != null; assert (obfWasRemoved); if (classMapping.getDeobfName() != null) { boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null; @@ -103,7 +103,7 @@ public class Mappings implements Serializable { if (classMapping.getDeobfName() != null) { classes.put(classMapping.getDeobfName(), classMapping); } else { - classes.put(classMapping.getObfName(), classMapping); + classes.put(classMapping.getObfFullName(), classMapping); } } @@ -144,7 +144,7 @@ public class Mappings implements Serializable { for (ClassMapping classMapping : classes()) { // add the class name - classNames.add(classMapping.getObfName()); + classNames.add(classMapping.getObfFullName()); // add classes from method signatures for (MethodMapping methodMapping : classMapping.methods()) { diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java index ea343c4..16f700d 100644 --- a/src/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java @@ -213,7 +213,7 @@ public class MappingsRenamer { ClassMapping classMapping = m_mappings.m_classesByObf.get(obfClassName); if (classMapping == null) { classMapping = new ClassMapping(obfClassName); - boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfName(), classMapping) == null; + boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null; assert (obfWasAdded); } return classMapping; diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java index c7c2cc0..8b62db8 100644 --- a/src/cuchaz/enigma/mapping/MappingsWriter.java +++ b/src/cuchaz/enigma/mapping/MappingsWriter.java @@ -31,9 +31,9 @@ public class MappingsWriter { private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { if (classMapping.getDeobfName() == null) { - out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfName()); + out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfFullName()); } else { - out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfName(), classMapping.getDeobfName()); + out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName()); } for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java index 759dddf..d985032 100644 --- a/src/cuchaz/enigma/mapping/Translator.java +++ b/src/cuchaz/enigma/mapping/Translator.java @@ -10,8 +10,10 @@ ******************************************************************************/ package cuchaz.enigma.mapping; +import java.util.List; import java.util.Map; +import com.beust.jcommander.internal.Lists; import com.google.common.collect.Maps; import cuchaz.enigma.analysis.TranslationIndex; @@ -50,54 +52,106 @@ public class Translator { } } + public String translate(T entry) { + if (entry instanceof ClassEntry) { + return translate((ClassEntry)entry); + } else if (entry instanceof FieldEntry) { + return translate((FieldEntry)entry); + } else if (entry instanceof MethodEntry) { + return translate((MethodEntry)entry); + } else if (entry instanceof ConstructorEntry) { + return translate((ConstructorEntry)entry); + } else if (entry instanceof ArgumentEntry) { + return translate((ArgumentEntry)entry); + } else { + throw new Error("Unknown entry type: " + entry.getClass().getName()); + } + } + public String translateClass(String className) { return translate(new ClassEntry(className)); } public String translate(ClassEntry in) { - ClassMapping classMapping = m_classes.get(in.getOuterClassName()); - if (classMapping != null) { - if (in.isInnerClass()) { - // translate the inner class - String translatedInnerClassName = m_direction.choose( - classMapping.getDeobfInnerClassName(in.getInnerClassName()), - classMapping.getObfInnerClassName(in.getInnerClassName()) + + if (in.isInnerClass()) { + + // translate everything in the class chain, or return null + List mappingsChain = getClassMappingChain(in); + StringBuilder buf = new StringBuilder(); + for (ClassMapping classMapping : mappingsChain) { + if (classMapping == null) { + return null; + } + boolean isFirstClass = buf.length() == 0; + String name = m_direction.choose( + classMapping.getDeobfName(), + isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() ); - if (translatedInnerClassName != null) { - // try to translate the outer name - String translatedOuterClassName = m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName()); - if (translatedOuterClassName != null) { - return translatedOuterClassName + "$" + translatedInnerClassName; - } else { - return in.getOuterClassName() + "$" + translatedInnerClassName; - } + if (name == null) { + return null; + } + if (!isFirstClass) { + buf.append("$"); } - } else { - // just return outer - return m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName()); + buf.append(name); } + return buf.toString(); + + } else { + + // normal classes are easier + ClassMapping classMapping = m_classes.get(in.getName()); + if (classMapping == null) { + return null; + } + return m_direction.choose( + classMapping.getDeobfName(), + classMapping.getObfFullName() + ); } - return null; } public ClassEntry translateEntry(ClassEntry in) { - // can we translate the inner class? - String name = translate(in); - if (name != null) { - return new ClassEntry(name); - } - if (in.isInnerClass()) { - // guess not. just translate the outer class name then - String outerClassName = translate(in.getOuterClassEntry()); - if (outerClassName != null) { - return new ClassEntry(outerClassName + "$" + in.getInnerClassName()); + // translate as much of the class chain as we can + List mappingsChain = getClassMappingChain(in); + String[] obfClassNames = in.getName().split("\\$"); + StringBuilder buf = new StringBuilder(); + for (int i=0; i mappingChain = getClassMappingChain(in); + return mappingChain.get(mappingChain.size() - 1); + } + + private List getClassMappingChain(ClassEntry in) { + + // get a list of all the classes in the hierarchy + String[] parts = in.getName().split("\\$"); + List mappingsChain = Lists.newArrayList(); + + // get mappings for the outer class + ClassMapping outerClassMapping = m_classes.get(parts[0]); + mappingsChain.add(outerClassMapping); + + for (int i=1; i obfClassChain = Lists.newArrayList(); + ClassEntry checkClassEntry = obfClassEntry; + while (checkClassEntry != null) { + obfClassChain.add(checkClassEntry); + checkClassEntry = m_index.getOuterClass(checkClassEntry); + } - // see if we're right - if (obfOuterClassEntry == null) { - - // nope, it's an outer class - obfInnerClassEntry = null; - obfOuterClassEntry = EntryFactory.getClassEntry(c); - } else { + // change order: outer to inner + Collections.reverse(obfClassChain); + + // does this class have any inner classes? + Collection obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry); + + boolean isInnerClass = obfClassChain.size() > 1; + if (isInnerClass) { - // yeah, it's an inner class, rename it to outer$inner - ClassEntry obfClassEntry = new ClassEntry(obfOuterClassEntry.getName() + "$" + obfInnerClassEntry.getSimpleName()); - c.setName(obfClassEntry.getName()); + // it's an inner class, rename it to the fully qualified name + StringBuilder buf = new StringBuilder(); + for (ClassEntry obfChainClassEntry : obfClassChain) { + if (buf.length() == 0) { + buf.append(obfChainClassEntry.getName()); + } else { + buf.append("$"); + buf.append(obfChainClassEntry.getSimpleName()); + } + } + c.setName(buf.toString()); - BehaviorEntry caller = m_jarIndex.getAnonymousClassCaller(obfInnerClassEntry); + BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry); if (caller != null) { + // write the enclosing method attribute if (caller.getName().equals("")) { c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName())); @@ -58,50 +78,89 @@ public class InnerClassWriter { } } - // write the inner classes if needed - Collection obfInnerClassEntries = m_jarIndex.getInnerClasses(obfOuterClassEntry); - if (obfInnerClassEntries != null && !obfInnerClassEntries.isEmpty()) { - writeInnerClasses(c, obfOuterClassEntry, obfInnerClassEntries); - } - } - - private void writeInnerClasses(CtClass c, ClassEntry obfOuterClassEntry, Collection obfInnerClassEntries) { - InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); - c.getClassFile().addAttribute(attr); - for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) { + if (isInnerClass || !obfInnerClassEntries.isEmpty()) { - // get the new inner class name - ClassEntry obfClassEntry = new ClassEntry(obfOuterClassEntry.getName() + "$" + obfInnerClassEntry.getSimpleName()); + // create an inner class attribute + InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); + c.getClassFile().addAttribute(attr); - // here's what the JVM spec says about the InnerClasses attribute - // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags ); - - // update the attribute with this inner class - ConstPool constPool = c.getClassFile().getConstPool(); - int innerClassIndex = constPool.addClassInfo(obfClassEntry.getName()); - int outerClassIndex = 0; - int innerClassSimpleNameIndex = 0; - int accessFlags = 0; - if (!m_jarIndex.isAnonymousClass(obfInnerClassEntry)) { - outerClassIndex = constPool.addClassInfo(obfClassEntry.getOuterClassName()); - innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getInnerClassName()); + // write the ancestry, but not the outermost class + for (int i=1; i ATTR: %s,%s,%s (replace %s with %s)", - obfClassEntry, - attr.innerClass(attr.tableLength() - 1), - attr.outerClass(attr.tableLength() - 1), - attr.innerName(attr.tableLength() - 1), - Constants.NonePackage + "/" + obfInnerClassName, - obfClassEntry.getName() - )); - */ - - // make sure the outer class references only the new inner class names - c.replaceClassName(obfInnerClassEntry.getName(), obfClassEntry.getName()); + // write the inner classes + for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) { + + // extend the class chain + List extendedObfClassChain = Lists.newArrayList(obfClassChain); + extendedObfClassChain.add(obfInnerClassEntry); + + String fullyQualifiedInnerClassName = writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry); + + // make sure we only reference the fully qualified inner class name + c.replaceClassName(obfInnerClassEntry.getName(), fullyQualifiedInnerClassName); + } + } + } + + private String writeInnerClass(InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) { + + // get the new inner class name + String obfInnerClassName = getFullyQualifiedName(obfClassChain, obfClassEntry); + String obfParentClassName = getFullyQualifiedParentName(obfClassChain, obfClassEntry); + + // here's what the JVM spec says about the InnerClasses attribute + // append(inner, parent, 0 if anonymous else simple name, flags); + + // update the attribute with this inner class + ConstPool constPool = attr.getConstPool(); + int innerClassIndex = constPool.addClassInfo(obfInnerClassName); + int parentClassIndex = constPool.addClassInfo(obfParentClassName); + int innerClassSimpleNameIndex = 0; + int accessFlags = 0; + if (!m_index.isAnonymousClass(obfClassEntry)) { + innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getSimpleName()); + } + + attr.append(innerClassIndex, parentClassIndex, innerClassSimpleNameIndex, accessFlags); + + /* DEBUG + System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", + obfClassEntry, + attr.innerClass(attr.tableLength() - 1), + attr.outerClass(attr.tableLength() - 1), + attr.innerName(attr.tableLength() - 1), + Constants.NonePackage + "/" + obfInnerClassName, + obfClassEntry.getName() + )); + */ + + return obfInnerClassName; + } + + private String getFullyQualifiedParentName(List classChain, ClassEntry classEntry) { + assert(classChain.size() > 1); + assert(classChain.contains(classEntry)); + StringBuilder buf = new StringBuilder(); + for (int i=0; classChain.get(i) != classEntry; i++) { + ClassEntry chainEntry = classChain.get(i); + if (buf.length() == 0) { + buf.append(chainEntry.getName()); + } else { + buf.append("$"); + buf.append(chainEntry.getSimpleName()); + } + } + return buf.toString(); + } + + private String getFullyQualifiedName(List classChain, ClassEntry classEntry) { + boolean isInner = classChain.size() > 1; + if (isInner) { + return getFullyQualifiedParentName(classChain, classEntry) + "$" + classEntry.getSimpleName(); + } else { + return classEntry.getName(); } } } diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java index cf41001..69f171a 100644 --- a/src/cuchaz/enigma/mapping/ClassEntry.java +++ b/src/cuchaz/enigma/mapping/ClassEntry.java @@ -114,6 +114,9 @@ public class ClassEntry implements Entry, Serializable { } public String getSimpleName() { + if (isInnerClass()) { + return getInnerClassName(); + } int pos = m_name.lastIndexOf('/'); if (pos > 0) { return m_name.substring(pos + 1); -- cgit v1.2.3 From 9809078524bd3bd40fbf7aa411f6e0dca02fd009 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 25 Feb 2015 22:42:34 -0500 Subject: fixed lots of issues with inner class reconstruction, particularly for inner class trees also fixed lots of issues with reading jars that aren't Minecraft. =P --- src/cuchaz/enigma/Deobfuscator.java | 33 ++++--- src/cuchaz/enigma/TranslatingTypeLoader.java | 113 +++++++++++++++-------- src/cuchaz/enigma/analysis/JarIndex.java | 29 +++++- src/cuchaz/enigma/bytecode/InnerClassWriter.java | 84 +++++------------ src/cuchaz/enigma/mapping/ClassEntry.java | 22 ++++- src/cuchaz/enigma/mapping/EntryFactory.java | 34 +------ 6 files changed, 156 insertions(+), 159 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index b7440a7..0b7808d 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -204,33 +204,36 @@ public class Deobfuscator { } } - public CompilationUnit getSourceTree(String obfClassName) { - // is this class deobfuscated? + public CompilationUnit getSourceTree(String className) { + + // 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 the deobfuscated class, so we need to load it by the deobfuscated name - String lookupClassName = obfClassName; - ClassMapping classMapping = m_mappings.getClassByObf(obfClassName); - if (classMapping != null && classMapping.getDeobfName() != null) { - lookupClassName = classMapping.getDeobfName(); - } + // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one - // is this class even in the jar? - if (!m_jarIndex.containsObfClass(new ClassEntry(obfClassName))) { - return null; + // first, assume class name is deobf + String deobfClassName = className; + + // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name + ClassMapping classMapping = m_mappings.getClassByObf(className); + if (classMapping != null && classMapping.getDeobfName() != null) { + deobfClassName = classMapping.getDeobfName(); } // set the type loader - m_settings.setTypeLoader(new TranslatingTypeLoader( + TranslatingTypeLoader loader = new TranslatingTypeLoader( m_jar, m_jarIndex, getTranslator(TranslationDirection.Obfuscating), getTranslator(TranslationDirection.Deobfuscating) - )); + ); + m_settings.setTypeLoader(loader); // see if procyon can find the type - TypeReference type = new MetadataSystem(m_settings.getTypeLoader()).lookupType(lookupClassName); + TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName); if (type == null) { - throw new Error("Unable to find type: " + lookupClassName + " (obf name: " + obfClassName + ")"); + throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s", + className, deobfClassName, loader.getClassNamesToTry(deobfClassName) + )); } TypeDefinition resolvedType = type.resolve(); diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index 26d5e7a..94ad6eb 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -13,6 +13,7 @@ package cuchaz.enigma; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -24,6 +25,7 @@ import javassist.CtClass; import javassist.NotFoundException; import javassist.bytecode.Descriptor; +import com.beust.jcommander.internal.Lists; import com.google.common.collect.Maps; import com.strobel.assembler.metadata.Buffer; import com.strobel.assembler.metadata.ClasspathTypeLoader; @@ -65,19 +67,20 @@ public class TranslatingTypeLoader implements ITypeLoader { } @Override - public boolean tryLoadType(String deobfClassName, Buffer out) { + public boolean tryLoadType(String className, Buffer out) { + // check the cache byte[] data; - if (m_cache.containsKey(deobfClassName)) { - data = m_cache.get(deobfClassName); + if (m_cache.containsKey(className)) { + data = m_cache.get(className); } else { - data = loadType(deobfClassName); - m_cache.put(deobfClassName, data); + data = loadType(className); + m_cache.put(className, data); } if (data == null) { // chain to default type loader - return m_defaultTypeLoader.tryLoadType(deobfClassName, out); + return m_defaultTypeLoader.tryLoadType(className, out); } // send the class to the decompiler @@ -88,6 +91,7 @@ public class TranslatingTypeLoader implements ITypeLoader { } public CtClass loadClass(String deobfClassName) { + byte[] data = loadType(deobfClassName); if (data == null) { return null; @@ -104,40 +108,35 @@ public class TranslatingTypeLoader implements ITypeLoader { } } - private byte[] loadType(String deobfClassName) { - - ClassEntry deobfClassEntry = new ClassEntry(deobfClassName); - ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry); - - // is this an inner class referenced directly? - ClassEntry obfOuterClassEntry = m_jarIndex.getOuterClass(obfClassEntry); - if (obfOuterClassEntry != null) { - // this class doesn't really exist. Reference it by outer$inner instead - System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassEntry)); - return null; - } - - /* DEBUG - if( !Arrays.asList( "java", "org", "io" ).contains( deobfClassName.split( "/" )[0] ) ) { - System.out.println( String.format( "Looking for %s (%s)", deobfClassEntry.getName(), obfClassEntry.getName() ) ); + private byte[] loadType(String className) { + + // NOTE: don't know if class name is obf or deobf + ClassEntry classEntry = new ClassEntry(className); + ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(classEntry); + + // is this an inner class referenced directly? (ie trying to load b instead of a$b) + if (!obfClassEntry.isInnerClass()) { + List classChain = m_jarIndex.getObfClassChain(obfClassEntry); + if (classChain.size() > 1) { + System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s", + className, obfClassEntry.buildClassEntry(classChain) + )); + return null; + } } - */ - // get the jar entry - String classFileName; - if (obfClassEntry.isInnerClass()) { - // use just the inner class name for inner classes - classFileName = obfClassEntry.getInnerClassName(); - } else if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) { - // use the outer class simple name for classes in the none package - classFileName = obfClassEntry.getSimpleName(); - } else { - // otherwise, just use the class name (ie for classes in packages) - classFileName = obfClassEntry.getName(); + // is this a class we should even know about? + if (!m_jarIndex.containsObfClass(obfClassEntry)) { + return null; } - JarEntry entry = m_jar.getJarEntry(classFileName + ".class"); - if (entry == null) { + // DEBUG + //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName())); + + // find the class in the jar + String classInJarName = findClassInJar(obfClassEntry); + if (classInJarName == null) { + // couldn't find it return null; } @@ -145,7 +144,7 @@ public class TranslatingTypeLoader implements ITypeLoader { // read the class file into a buffer ByteArrayOutputStream data = new ByteArrayOutputStream(); byte[] buf = new byte[1024 * 1024]; // 1 KiB - InputStream in = m_jar.getInputStream(entry); + InputStream in = m_jar.getInputStream(m_jar.getJarEntry(classInJarName + ".class")); while (true) { int bytesRead = in.read(buf); if (bytesRead <= 0) { @@ -158,15 +157,15 @@ public class TranslatingTypeLoader implements ITypeLoader { buf = data.toByteArray(); // load the javassist handle to the raw class - String javaClassFileName = Descriptor.toJavaName(classFileName); ClassPool classPool = new ClassPool(); - classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, buf)); - CtClass c = classPool.get(javaClassFileName); + String classInJarJavaName = Descriptor.toJavaName(classInJarName); + classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf)); + CtClass c = classPool.get(classInJarJavaName); c = transformClass(c); // sanity checking - assertClassName(c, deobfClassEntry); + assertClassName(c, classEntry); // DEBUG //Util.writeClass( c ); @@ -178,6 +177,38 @@ public class TranslatingTypeLoader implements ITypeLoader { } } + private String findClassInJar(ClassEntry obfClassEntry) { + + // try to find the class in the jar + for (String className : getClassNamesToTry(obfClassEntry)) { + JarEntry jarEntry = m_jar.getJarEntry(className + ".class"); + if (jarEntry != null) { + return className; + } + } + + // didn't find it ;_; + return null; + } + + public List getClassNamesToTry(String className) { + return getClassNamesToTry(m_obfuscatingTranslator.translateEntry(new ClassEntry(className))); + } + + public List getClassNamesToTry(ClassEntry obfClassEntry) { + List classNamesToTry = Lists.newArrayList(); + classNamesToTry.add(obfClassEntry.getName()); + if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) { + // taking off the none package, if any + classNamesToTry.add(obfClassEntry.getSimpleName()); + } + if (obfClassEntry.isInnerClass()) { + // try just the inner class name + classNamesToTry.add(obfClassEntry.getInnerClassName()); + } + return classNamesToTry; + } + public CtClass transformClass(CtClass c) throws IOException, NotFoundException, CannotCompileException { diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 1afcb76..e0a8bf5 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -12,6 +12,7 @@ package cuchaz.enigma.analysis; import java.lang.reflect.Modifier; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -156,11 +157,8 @@ public class JarIndex { // step 6: update other indices with inner class info Map renames = Maps.newHashMap(); - for (Map.Entry mapEntry : m_innerClassesByOuter.entries()) { - ClassEntry outerClassEntry = mapEntry.getKey(); - ClassEntry innerClassEntry = mapEntry.getValue(); - outerClassEntry = EntryFactory.getChainedOuterClassName(this, outerClassEntry); - String newName = outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName(); + for (ClassEntry innerClassEntry : m_innerClassesByOuter.values()) { + String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName(); if (!innerClassEntry.getName().equals(newName)) { // DEBUG //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName); @@ -780,4 +778,25 @@ public class JarIndex { public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) { return m_bridgedMethods.get(bridgeMethodEntry); } + + public List getObfClassChain(ClassEntry obfClassEntry) { + + // build class chain in inner-to-outer order + List obfClassChain = Lists.newArrayList(obfClassEntry); + ClassEntry checkClassEntry = obfClassEntry; + while (true) { + ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry); + if (obfOuterClassEntry != null) { + obfClassChain.add(obfOuterClassEntry); + checkClassEntry = obfOuterClassEntry; + } else { + break; + } + } + + // switch to outer-to-inner order + Collections.reverse(obfClassChain); + + return obfClassChain; + } } diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java index 3bebd17..dd21a78 100644 --- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -11,7 +11,6 @@ package cuchaz.enigma.bytecode; import java.util.Collection; -import java.util.Collections; import java.util.List; import javassist.CtClass; @@ -36,35 +35,14 @@ public class InnerClassWriter { public void write(CtClass c) { - // build the class chain (inner to outer) ClassEntry obfClassEntry = EntryFactory.getClassEntry(c); - List obfClassChain = Lists.newArrayList(); - ClassEntry checkClassEntry = obfClassEntry; - while (checkClassEntry != null) { - obfClassChain.add(checkClassEntry); - checkClassEntry = m_index.getOuterClass(checkClassEntry); - } - - // change order: outer to inner - Collections.reverse(obfClassChain); - - // does this class have any inner classes? - Collection obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry); + List obfClassChain = m_index.getObfClassChain(obfClassEntry); boolean isInnerClass = obfClassChain.size() > 1; if (isInnerClass) { // it's an inner class, rename it to the fully qualified name - StringBuilder buf = new StringBuilder(); - for (ClassEntry obfChainClassEntry : obfClassChain) { - if (buf.length() == 0) { - buf.append(obfChainClassEntry.getName()); - } else { - buf.append("$"); - buf.append(obfChainClassEntry.getSimpleName()); - } - } - c.setName(buf.toString()); + c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName()); BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry); if (caller != null) { @@ -78,6 +56,9 @@ public class InnerClassWriter { } } + // does this class have any inner classes? + Collection obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry); + if (isInnerClass || !obfInnerClassEntries.isEmpty()) { // create an inner class attribute @@ -86,7 +67,11 @@ public class InnerClassWriter { // write the ancestry, but not the outermost class for (int i=1; i extendedObfClassChain = Lists.newArrayList(obfClassChain); extendedObfClassChain.add(obfInnerClassEntry); - String fullyQualifiedInnerClassName = writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry); + writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry); - // make sure we only reference the fully qualified inner class name - c.replaceClassName(obfInnerClassEntry.getName(), fullyQualifiedInnerClassName); + // update references to use the fully qualified inner class name + c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName()); } } } - private String writeInnerClass(InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) { + private void writeInnerClass(InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) { // get the new inner class name - String obfInnerClassName = getFullyQualifiedName(obfClassChain, obfClassEntry); - String obfParentClassName = getFullyQualifiedParentName(obfClassChain, obfClassEntry); + ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); + ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry(); // here's what the JVM spec says about the InnerClasses attribute // append(inner, parent, 0 if anonymous else simple name, flags); // update the attribute with this inner class ConstPool constPool = attr.getConstPool(); - int innerClassIndex = constPool.addClassInfo(obfInnerClassName); - int parentClassIndex = constPool.addClassInfo(obfParentClassName); - int innerClassSimpleNameIndex = 0; + int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName()); + int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName()); + int innerClassNameIndex = 0; int accessFlags = 0; if (!m_index.isAnonymousClass(obfClassEntry)) { - innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getSimpleName()); + innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnerClassName()); } - attr.append(innerClassIndex, parentClassIndex, innerClassSimpleNameIndex, accessFlags); + attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags); /* DEBUG System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", @@ -135,32 +120,5 @@ public class InnerClassWriter { obfClassEntry.getName() )); */ - - return obfInnerClassName; - } - - private String getFullyQualifiedParentName(List classChain, ClassEntry classEntry) { - assert(classChain.size() > 1); - assert(classChain.contains(classEntry)); - StringBuilder buf = new StringBuilder(); - for (int i=0; classChain.get(i) != classEntry; i++) { - ClassEntry chainEntry = classChain.get(i); - if (buf.length() == 0) { - buf.append(chainEntry.getName()); - } else { - buf.append("$"); - buf.append(chainEntry.getSimpleName()); - } - } - return buf.toString(); - } - - private String getFullyQualifiedName(List classChain, ClassEntry classEntry) { - boolean isInner = classChain.size() > 1; - if (isInner) { - return getFullyQualifiedParentName(classChain, classEntry) + "$" + classEntry.getSimpleName(); - } else { - return classEntry.getName(); - } } } diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java index 69f171a..69e66bc 100644 --- a/src/cuchaz/enigma/mapping/ClassEntry.java +++ b/src/cuchaz/enigma/mapping/ClassEntry.java @@ -11,6 +11,7 @@ package cuchaz.enigma.mapping; import java.io.Serializable; +import java.util.List; public class ClassEntry implements Entry, Serializable { @@ -114,13 +115,28 @@ public class ClassEntry implements Entry, Serializable { } public String getSimpleName() { - if (isInnerClass()) { - return getInnerClassName(); - } int pos = m_name.lastIndexOf('/'); if (pos > 0) { return m_name.substring(pos + 1); } return m_name; } + + public ClassEntry buildClassEntry(List classChain) { + assert(classChain.contains(this)); + StringBuilder buf = new StringBuilder(); + for (ClassEntry chainEntry : classChain) { + if (buf.length() == 0) { + buf.append(chainEntry.getName()); + } else { + buf.append("$"); + buf.append(chainEntry.isInnerClass() ? chainEntry.getInnerClassName() : chainEntry.getSimpleName()); + } + + if (chainEntry == this) { + break; + } + } + return new ClassEntry(buf.toString()); + } } diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java index bbdfa73..f4d62c8 100644 --- a/src/cuchaz/enigma/mapping/EntryFactory.java +++ b/src/cuchaz/enigma/mapping/EntryFactory.java @@ -1,7 +1,5 @@ package cuchaz.enigma.mapping; -import java.util.List; - import javassist.CtBehavior; import javassist.CtClass; import javassist.CtConstructor; @@ -13,7 +11,6 @@ import javassist.expr.FieldAccess; import javassist.expr.MethodCall; import javassist.expr.NewExpr; -import com.beust.jcommander.internal.Lists; import com.strobel.assembler.metadata.MethodDefinition; import cuchaz.enigma.analysis.JarIndex; @@ -25,35 +22,8 @@ public class EntryFactory { } public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { - return getChainedOuterClassName(jarIndex, new ClassEntry(classMapping.getObfFullName())); - } - - public static ClassEntry getChainedOuterClassName(JarIndex jarIndex, ClassEntry obfClassEntry) { - - // lookup the chain of outer classes - List obfClassChain = Lists.newArrayList(obfClassEntry); - ClassEntry checkClassEntry = obfClassEntry; - while (true) { - ClassEntry obfOuterClassEntry = jarIndex.getOuterClass(checkClassEntry); - if (obfOuterClassEntry != null) { - obfClassChain.add(obfOuterClassEntry); - checkClassEntry = obfOuterClassEntry; - } else { - break; - } - } - - // build the chained class name - StringBuilder buf = new StringBuilder(); - for (int i=obfClassChain.size()-1; i>=0; i--) { - if (buf.length() == 0) { - buf.append(obfClassChain.get(i).getName()); - } else { - buf.append("$"); - buf.append(obfClassChain.get(i).getSimpleName()); - } - } - return new ClassEntry(buf.toString()); + ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName()); + return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry)); } public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { -- cgit v1.2.3 From 09e41e6dcce1698f9bd626c4f745a63af5077fe9 Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 28 Feb 2015 11:17:39 -0500 Subject: update version string to 0.8 beta --- src/cuchaz/enigma/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java index 3bf3999..db14778 100644 --- a/src/cuchaz/enigma/Constants.java +++ b/src/cuchaz/enigma/Constants.java @@ -12,7 +12,7 @@ package cuchaz.enigma; public class Constants { public static final String Name = "Enigma"; - public static final String Version = "0.7 beta"; + public static final String Version = "0.8 beta"; public static final String Url = "http://www.cuchazinteractive.com/enigma"; public static final int MiB = 1024 * 1024; // 1 mebibyte public static final int KiB = 1024; // 1 kebibyte -- cgit v1.2.3 From e0ff85569297808a14ab677e65a3ace8f2f56a14 Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 28 Feb 2015 12:09:48 -0500 Subject: ignore more harmless exceptions from the buggy highlight painter system --- src/cuchaz/enigma/ExceptionIgnorer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ExceptionIgnorer.java b/src/cuchaz/enigma/ExceptionIgnorer.java index 37c67da..1bb5824 100644 --- a/src/cuchaz/enigma/ExceptionIgnorer.java +++ b/src/cuchaz/enigma/ExceptionIgnorer.java @@ -10,9 +10,9 @@ public class ExceptionIgnorer { StackTraceElement[] stackTrace = t.getStackTrace(); if (stackTrace.length > 1) { - // does this stack frame match javax.swing.text.DefaultHighlighter.paint() ? + // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ? StackTraceElement frame = stackTrace[1]; - if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().equals("paint")) { + if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) { return true; } } -- cgit v1.2.3 From 741e3472f76d959645ee0e025547d69a03e5b6f2 Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 28 Feb 2015 18:00:25 -0500 Subject: fix up conversion tool to handle Minecraft 1.8.3 --- src/cuchaz/enigma/convert/ClassForest.java | 50 ++++ src/cuchaz/enigma/convert/ClassIdentifier.java | 41 +++ src/cuchaz/enigma/convert/ClassIdentity.java | 125 +++++---- src/cuchaz/enigma/convert/ClassMatch.java | 72 +++++ src/cuchaz/enigma/convert/ClassMatcher.java | 356 +++++++++++-------------- src/cuchaz/enigma/convert/ClassMatching.java | 200 +++++++------- src/cuchaz/enigma/convert/ClassNamer.java | 10 +- src/cuchaz/enigma/mapping/Translator.java | 1 + 8 files changed, 486 insertions(+), 369 deletions(-) create mode 100644 src/cuchaz/enigma/convert/ClassForest.java create mode 100644 src/cuchaz/enigma/convert/ClassIdentifier.java create mode 100644 src/cuchaz/enigma/convert/ClassMatch.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java new file mode 100644 index 0000000..e113eeb --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassForest.java @@ -0,0 +1,50 @@ +package cuchaz.enigma.convert; + +import java.util.Collection; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class ClassForest { + + private ClassIdentifier m_identifier; + private Multimap m_forest; + + public ClassForest(ClassIdentifier identifier) { + m_identifier = identifier; + m_forest = HashMultimap.create(); + } + + public ClassIdentifier getIdentifier() { + return m_identifier; + } + + public void addAll(Iterable entries) { + for (ClassEntry entry : entries) { + add(entry); + } + } + + private void add(ClassEntry entry) { + m_forest.put(m_identifier.identify(entry), entry); + } + + public Collection identities() { + return m_forest.keySet(); + } + + public Collection classes() { + return m_forest.values(); + } + + public Collection getClasses(ClassIdentity identity) { + return m_forest.get(identity); + } + + public boolean containsIdentity(ClassIdentity identity) { + return m_forest.containsKey(identity); + } +} diff --git a/src/cuchaz/enigma/convert/ClassIdentifier.java b/src/cuchaz/enigma/convert/ClassIdentifier.java new file mode 100644 index 0000000..bdbf11b --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassIdentifier.java @@ -0,0 +1,41 @@ +package cuchaz.enigma.convert; + +import java.util.Map; +import java.util.jar.JarFile; + +import javassist.CtClass; + +import com.beust.jcommander.internal.Maps; + +import cuchaz.enigma.TranslatingTypeLoader; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.ClassEntry; + + +public class ClassIdentifier { + + private JarIndex m_index; + private SidedClassNamer m_namer; + private boolean m_useReferences; + private TranslatingTypeLoader m_loader; + private Map m_cache; + + public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) { + m_index = index; + m_namer = namer; + m_useReferences = useReferences; + m_loader = new TranslatingTypeLoader(jar, index); + m_cache = Maps.newHashMap(); + } + + public ClassIdentity identify(ClassEntry classEntry) { + ClassIdentity identity = m_cache.get(classEntry); + if (identity == null) { + CtClass c = m_loader.loadClass(classEntry.getName()); + identity = new ClassIdentity(c, m_namer, m_index, m_useReferences); + m_cache.put(classEntry, identity); + } + return identity; + } +} diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index b514012..3736a53 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -52,9 +52,10 @@ import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassNameReplacer; import cuchaz.enigma.mapping.Entry; -import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.Type; public class ClassIdentity { @@ -68,7 +69,45 @@ public class ClassIdentity { private Multiset m_implements; private Multiset m_implementations; private Multiset m_references; - + + private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() { + + private Map m_classNames = Maps.newHashMap(); + + @Override + public String replace(String className) { + + // classes not in the none package can be passed through + ClassEntry classEntry = new ClassEntry(className); + if (!classEntry.getPackageName().equals(Constants.NonePackage)) { + return className; + } + + // is this class ourself? + if (className.equals(m_classEntry.getName())) { + return "CSelf"; + } + + // try the namer + if (m_namer != null) { + String newName = m_namer.getName(className); + if (newName != null) { + return newName; + } + } + + // otherwise, use local naming + if (!m_classNames.containsKey(className)) { + m_classNames.put(className, getNewClassName()); + } + return m_classNames.get(className); + } + + private String getNewClassName() { + return String.format("C%03d", m_classNames.size()); + } + }; + public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { m_namer = namer; @@ -77,7 +116,7 @@ public class ClassIdentity { m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); m_fields = HashMultiset.create(); for (CtField field : c.getDeclaredFields()) { - m_fields.add(scrubSignature(field.getSignature())); + m_fields.add(scrubType(field.getSignature())); } m_methods = HashMultiset.create(); for (CtMethod method : c.getDeclaredMethods()) { @@ -93,11 +132,11 @@ public class ClassIdentity { } m_extends = ""; if (c.getClassFile().getSuperclass() != null) { - m_extends = scrubClassName(c.getClassFile().getSuperclass()); + m_extends = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass())); } m_implements = HashMultiset.create(); for (String interfaceName : c.getClassFile().getInterfaces()) { - m_implements.add(scrubClassName(interfaceName)); + m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName))); } // stuff from the jar index @@ -132,9 +171,14 @@ public class ClassIdentity { private void addReference(EntryReference reference) { if (reference.context.getSignature() != null) { - m_references.add(String.format("%s_%s", scrubClassName(reference.context.getClassName()), scrubSignature(reference.context.getSignature()))); + m_references.add(String.format("%s_%s", + scrubClassName(reference.context.getClassName()), + scrubSignature(reference.context.getSignature()) + )); } else { - m_references.add(String.format("%s_", scrubClassName(reference.context.getClassName()))); + m_references.add(String.format("%s_", + scrubClassName(reference.context.getClassName()) + )); } } @@ -194,52 +238,27 @@ public class ClassIdentity { } private String scrubClassName(String className) { - return scrubSignature("L" + Descriptor.toJvmName(className) + ";"); + return m_classNameReplacer.replace(className); + } + + private String scrubType(String typeName) { + return scrubType(new Type(typeName)).toString(); + } + + private Type scrubType(Type type) { + if (type.hasClass()) { + return new Type(type, m_classNameReplacer); + } else { + return type; + } } private String scrubSignature(String signature) { - return scrubSignature(new Signature(signature)); + return scrubSignature(new Signature(signature)).toString(); } - private String scrubSignature(Signature signature) { - - return new Signature(signature, new ClassNameReplacer() { - - private Map m_classNames = Maps.newHashMap(); - - @Override - public String replace(String className) { - - // classes not in the none package can be passed through - ClassEntry classEntry = new ClassEntry(className); - if (!classEntry.getPackageName().equals(Constants.NonePackage)) { - return className; - } - - // is this class ourself? - if (className.equals(m_classEntry.getName())) { - return "CSelf"; - } - - // try the namer - if (m_namer != null) { - String newName = m_namer.getName(className); - if (newName != null) { - return newName; - } - } - - // otherwise, use local naming - if (!m_classNames.containsKey(className)) { - m_classNames.put(className, getNewClassName()); - } - return m_classNames.get(className); - } - - private String getNewClassName() { - return String.format("C%03d", m_classNames.size()); - } - }).toString(); + private Signature scrubSignature(Signature signature) { + return new Signature(signature, m_classNameReplacer); } private boolean isClassMatchedUniquely(String className) { @@ -284,7 +303,7 @@ public class ClassIdentity { behavior.instrument(new ExprEditor() { @Override public void edit(MethodCall call) { - updateHashWithString(digest, scrubClassName(call.getClassName())); + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); updateHashWithString(digest, scrubSignature(call.getSignature())); if (isClassMatchedUniquely(call.getClassName())) { updateHashWithString(digest, call.getMethodName()); @@ -293,8 +312,8 @@ public class ClassIdentity { @Override public void edit(FieldAccess access) { - updateHashWithString(digest, scrubClassName(access.getClassName())); - updateHashWithString(digest, scrubSignature(access.getSignature())); + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName()))); + updateHashWithString(digest, scrubType(access.getSignature())); if (isClassMatchedUniquely(access.getClassName())) { updateHashWithString(digest, access.getFieldName()); } @@ -302,13 +321,13 @@ public class ClassIdentity { @Override public void edit(ConstructorCall call) { - updateHashWithString(digest, scrubClassName(call.getClassName())); + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); updateHashWithString(digest, scrubSignature(call.getSignature())); } @Override public void edit(NewExpr expr) { - updateHashWithString(digest, scrubClassName(expr.getClassName())); + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName()))); } }); diff --git a/src/cuchaz/enigma/convert/ClassMatch.java b/src/cuchaz/enigma/convert/ClassMatch.java new file mode 100644 index 0000000..9cecf70 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassMatch.java @@ -0,0 +1,72 @@ +package cuchaz.enigma.convert; + +import java.util.Collection; +import java.util.Set; + +import com.google.common.collect.Sets; + +import cuchaz.enigma.Util; +import cuchaz.enigma.mapping.ClassEntry; + + +public class ClassMatch { + + public Set sourceClasses; + public Set destClasses; + + public ClassMatch(Collection sourceClasses, Collection destClasses) { + this.sourceClasses = Sets.newHashSet(sourceClasses); + this.destClasses = Sets.newHashSet(destClasses); + } + + public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) { + this.sourceClasses = Sets.newHashSet(sourceClass); + this.destClasses = Sets.newHashSet(destClass); + } + + public boolean isMatched() { + return sourceClasses.size() > 0 && destClasses.size() > 0; + } + + public boolean isAmbiguous() { + return sourceClasses.size() > 1 || destClasses.size() > 1; + } + + public ClassEntry getUniqueSource() { + if (sourceClasses.size() != 1) { + throw new IllegalStateException("Match has ambiguous source!"); + } + return sourceClasses.iterator().next(); + } + + public ClassEntry getUniqueDest() { + if (destClasses.size() != 1) { + throw new IllegalStateException("Match has ambiguous source!"); + } + return destClasses.iterator().next(); + } + + public Set intersectSourceClasses(Set classes) { + Set intersection = Sets.newHashSet(sourceClasses); + intersection.retainAll(classes); + return intersection; + } + + @Override + public int hashCode() { + return Util.combineHashesOrdered(sourceClasses, destClasses); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ClassMatch) { + return equals((ClassMatch)other); + } + return false; + } + + public boolean equals(ClassMatch other) { + return this.sourceClasses.equals(other.sourceClasses) + && this.destClasses.equals(other.destClasses); + } +} diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java index 224d004..f43f5b2 100644 --- a/src/cuchaz/enigma/convert/ClassMatcher.java +++ b/src/cuchaz/enigma/convert/ClassMatcher.java @@ -26,9 +26,6 @@ import java.util.Map; import java.util.Set; import java.util.jar.JarFile; -import javassist.CtBehavior; -import javassist.CtClass; - import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -37,12 +34,10 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; -import cuchaz.enigma.TranslatingTypeLoader; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; -import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsReader; @@ -52,18 +47,23 @@ import cuchaz.enigma.mapping.MethodMapping; public class ClassMatcher { - public static void main(String[] args) throws IOException, MappingParseException { - // TEMP - JarFile sourceJar = new JarFile(new File("input/1.8-pre3.jar")); - JarFile destJar = new JarFile(new File("input/1.8.jar")); - File inMappingsFile = new File("../Enigma Mappings/1.8-pre3.mappings"); - File outMappingsFile = new File("../Enigma Mappings/1.8.mappings"); + public static void main(String[] args) + throws IOException, MappingParseException { + + // setup files + File home = new File(System.getProperty("user.home")); + JarFile sourceJar = new JarFile(new File(home, ".minecraft/versions/1.8/1.8.jar")); + JarFile destJar = new JarFile(new File(home, ".minecraft/versions/1.8.3/1.8.3.jar")); + File inMappingsFile = new File("../Enigma Mappings/1.8.mappings"); + File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings"); // define a matching to use when the automated system cannot find a match Map fallbackMatching = Maps.newHashMap(); + /* fallbackMatching.put("none/ayb", "none/ayf"); fallbackMatching.put("none/ayd", "none/ayd"); fallbackMatching.put("none/bgk", "unknown/bgk"); + */ // do the conversion Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); @@ -77,6 +77,7 @@ public class ClassMatcher { } private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map fallbackMatching) { + // index jars System.out.println("Indexing source jar..."); JarIndex sourceIndex = new JarIndex(); @@ -84,48 +85,88 @@ public class ClassMatcher { System.out.println("Indexing dest jar..."); JarIndex destIndex = new JarIndex(); destIndex.indexJar(destJar, false); - TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader(sourceJar, sourceIndex); - TranslatingTypeLoader destLoader = new TranslatingTypeLoader(destJar, destIndex); // compute the matching - ClassMatching matching = computeMatching(sourceIndex, sourceLoader, destIndex, destLoader); - Map>> matchingIndex = matching.getIndex(); + ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex); // get all the obf class names used in the mappings - Set usedClassNames = mappings.getAllObfClassNames(); - Set allClassNames = Sets.newHashSet(); - for (ClassEntry classEntry : sourceIndex.getObfClassEntries()) { - allClassNames.add(classEntry.getName()); + Set usedClasses = Sets.newHashSet(); + for (String className : mappings.getAllObfClassNames()) { + usedClasses.add(new ClassEntry(className)); } - usedClassNames.retainAll(allClassNames); - System.out.println("Used " + usedClassNames.size() + " classes in the mappings"); + System.out.println("Mappings reference " + usedClasses.size() + " classes"); - // probabilistically match the non-uniquely-matched source classes - for (Map.Entry> entry : matchingIndex.values()) { - ClassIdentity sourceClass = entry.getKey(); - List destClasses = entry.getValue(); - - // skip classes that are uniquely matched - if (destClasses.size() == 1) { + // see what the used classes map to + BiMap uniqueUsedMatches = HashBiMap.create(); + Map ambiguousUsedMatches = Maps.newHashMap(); + Set unmatchedUsedClasses = Sets.newHashSet(); + for (ClassMatch match : matching.matches()) { + Set matchUsedClasses = match.intersectSourceClasses(usedClasses); + if (matchUsedClasses.isEmpty()) { continue; } - - // skip classes that aren't used in the mappings - if (!usedClassNames.contains(sourceClass.getClassEntry().getName())) { - continue; + + // classify the match + if (!match.isMatched()) { + // unmatched + unmatchedUsedClasses.addAll(matchUsedClasses); + } else { + if (match.isAmbiguous()) { + // ambiguously matched + for (ClassEntry matchUsedClass : matchUsedClasses) { + ambiguousUsedMatches.put(matchUsedClass, match); + } + } else { + // uniquely matched + uniqueUsedMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } } + } + + // get unmatched dest classes + Set unmatchedDestClasses = Sets.newHashSet(); + for (ClassMatch match : matching.matches()) { + if (!match.isMatched()) { + unmatchedDestClasses.addAll(match.destClasses); + } + } + + // warn about the ambiguous used matches + if (ambiguousUsedMatches.size() > 0) { + System.out.println(String.format("%d source classes have ambiguous mappings", ambiguousUsedMatches.size())); + List ambiguousMatchesList = Lists.newArrayList(Sets.newHashSet(ambiguousUsedMatches.values())); + Collections.sort(ambiguousMatchesList, new Comparator() { + @Override + public int compare(ClassMatch a, ClassMatch b) { + String aName = a.sourceClasses.iterator().next().getName(); + String bName = b.sourceClasses.iterator().next().getName(); + return aName.compareTo(bName); + } + }); + for (ClassMatch match : ambiguousMatchesList) { + System.out.println("Ambiguous matching:"); + System.out.println("\tSource: " + getClassNames(match.sourceClasses)); + System.out.println("\tDest: " + getClassNames(match.destClasses)); + } + } + + // warn about unmatched used classes + for (ClassEntry unmatchedUsedClass : unmatchedUsedClasses) { + System.out.println("No exact match for source class " + unmatchedUsedClass.getClassEntry()); - System.out.println("No exact match for source class " + sourceClass.getClassEntry()); - - // find the closest classes - Multimap scoredMatches = ArrayListMultimap.create(); - for (ClassIdentity c : destClasses) { - scoredMatches.put(sourceClass.getMatchScore(c), c); + // rank all the unmatched dest classes against the used class + ClassIdentity sourceIdentity = matching.getSourceIdentifier().identify(unmatchedUsedClass); + Multimap scoredDestClasses = ArrayListMultimap.create(); + for (ClassEntry unmatchedDestClass : unmatchedDestClasses) { + ClassIdentity destIdentity = matching.getDestIdentifier().identify(unmatchedDestClass); + scoredDestClasses.put(sourceIdentity.getMatchScore(destIdentity), unmatchedDestClass); } - List scores = new ArrayList(scoredMatches.keySet()); + + List scores = new ArrayList(scoredDestClasses.keySet()); Collections.sort(scores, Collections.reverseOrder()); - printScoredMatches(sourceClass.getMaxMatchScore(), scores, scoredMatches); + printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses); + /* TODO: re-enable auto-pick logic // does the best match have a non-zero score and the same name? int bestScore = scores.get(0); Collection bestMatches = scoredMatches.get(bestScore); @@ -138,85 +179,45 @@ public class ClassMatcher { destClasses.add(bestMatch); } } + */ } - // group the matching into unique and non-unique matches - BiMap matchedClassNames = HashBiMap.create(); - Set unmatchedSourceClassNames = Sets.newHashSet(); - for (String className : usedClassNames) { - // is there a match for this class? - Map.Entry> entry = matchingIndex.get(className); - ClassIdentity sourceClass = entry.getKey(); - List matches = entry.getValue(); - - if (matches.size() == 1) { - // unique match! We're good to go! - matchedClassNames.put(sourceClass.getClassEntry().getName(), matches.get(0).getClassEntry().getName()); - } else { - // no match, check the fallback matching - String fallbackMatch = fallbackMatching.get(className); - if (fallbackMatch != null) { - matchedClassNames.put(sourceClass.getClassEntry().getName(), fallbackMatch); - } else { - unmatchedSourceClassNames.add(className); - } - } - } - - // report unmatched classes - if (!unmatchedSourceClassNames.isEmpty()) { - System.err.println("ERROR: there were unmatched classes!"); - for (String className : unmatchedSourceClassNames) { - System.err.println("\t" + className); - } - return; - } - - // get the class name changes from the matched class names - Map classChanges = Maps.newHashMap(); - for (Map.Entry entry : matchedClassNames.entrySet()) { - if (!entry.getKey().equals(entry.getValue())) { - classChanges.put(entry.getKey(), entry.getValue()); - System.out.println(String.format("Class change: %s -> %s", entry.getKey(), entry.getValue())); - /* DEBUG - System.out.println(String.format("\n%s\n%s", - new ClassIdentity(sourceLoader.loadClass(entry.getKey()), null, sourceIndex, false, false), - new ClassIdentity( destLoader.loadClass(entry.getValue()), null, destIndex, false, false) - )); - */ - } + // bail if there were unmatched classes + if (!unmatchedUsedClasses.isEmpty()) { + throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!"); } // sort the changes so classes are renamed in the correct order // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b - LinkedHashMap orderedClassChanges = Maps.newLinkedHashMap(); - int numChangesLeft = classChanges.size(); - while (!classChanges.isEmpty()) { - Iterator> iter = classChanges.entrySet().iterator(); + BiMap unsortedChanges = HashBiMap.create(uniqueUsedMatches); + LinkedHashMap sortedChanges = Maps.newLinkedHashMap(); + int numChangesLeft = unsortedChanges.size(); + while (!unsortedChanges.isEmpty()) { + Iterator> iter = unsortedChanges.entrySet().iterator(); while (iter.hasNext()) { - Map.Entry entry = iter.next(); - if (classChanges.get(entry.getValue()) == null) { - orderedClassChanges.put(entry.getKey(), entry.getValue()); + Map.Entry change = iter.next(); + if (unsortedChanges.containsKey(change.getValue())) { + sortedChanges.put(change.getKey(), change.getValue()); iter.remove(); } } // did we remove any changes? - if (numChangesLeft - classChanges.size() > 0) { + if (numChangesLeft - unsortedChanges.size() > 0) { // keep going - numChangesLeft = classChanges.size(); + numChangesLeft = unsortedChanges.size(); } else { // can't sort anymore. There must be a loop break; } } - if (classChanges.size() > 0) { - throw new Error(String.format("Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size())); + if (!unsortedChanges.isEmpty()) { + throw new Error(String.format("Unable to sort %d/%d class changes!", unsortedChanges.size(), uniqueUsedMatches.size())); } // convert the mappings in the correct class order - for (Map.Entry entry : orderedClassChanges.entrySet()) { - mappings.renameObfClass(entry.getKey(), entry.getValue()); + for (Map.Entry entry : sortedChanges.entrySet()) { + mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); } // check the method matches @@ -238,6 +239,7 @@ public class ClassMatcher { if (!destIndex.containsObfBehavior(methodEntry)) { System.err.println("WARNING: method doesn't match: " + methodEntry); + /* TODO: show methods if needed // show the available methods System.err.println("\tAvailable dest methods:"); CtClass c = destLoader.loadClass(classMapping.getObfFullName()); @@ -250,6 +252,7 @@ public class ClassMatcher { for (CtBehavior behavior : c.getDeclaredBehaviors()) { System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); } + */ } } } @@ -257,125 +260,72 @@ public class ClassMatcher { System.out.println("Done!"); } - public static ClassMatching computeMatching(JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader) { + public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) { - System.out.println("Matching classes..."); + System.out.println("Iteratively matching classes..."); - ClassMatching matching = null; + ClassMatching lastMatching = null; + int round = 0; + SidedClassNamer sourceNamer = null; + SidedClassNamer destNamer = null; for (boolean useReferences : Arrays.asList(false, true)) { - int numMatches = 0; - do { - SidedClassNamer sourceNamer = null; - SidedClassNamer destNamer = null; - if (matching != null) { - // build a class namer - ClassNamer namer = new ClassNamer(matching.getUniqueMatches()); - sourceNamer = namer.getSourceNamer(); - destNamer = namer.getDestNamer(); - - // note the number of matches - numMatches = matching.getUniqueMatches().size(); - } + + int numUniqueMatchesLastTime = 0; + if (lastMatching != null) { + numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); + } + + while (true) { + + System.out.println("Round " + (++round) + " ..."); + + // init the matching with identity settings + ClassMatching matching = new ClassMatching( + new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), + new ClassIdentifier(destJar, destIndex, destNamer, useReferences) + ); - // get the entries left to match - Set sourceClassEntries = Sets.newHashSet(); - Set destClassEntries = Sets.newHashSet(); - if (matching == null) { - sourceClassEntries.addAll(sourceIndex.getObfClassEntries()); - destClassEntries.addAll(destIndex.getObfClassEntries()); - matching = new ClassMatching(); + if (lastMatching == null) { + // search all classes + matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); } else { - for (Map.Entry,List> entry : matching.getAmbiguousMatches().entrySet()) { - for (ClassIdentity c : entry.getKey()) { - sourceClassEntries.add(c.getClassEntry()); - matching.removeSource(c); - } - for (ClassIdentity c : entry.getValue()) { - destClassEntries.add(c.getClassEntry()); - matching.removeDest(c); - } - } - for (ClassIdentity c : matching.getUnmatchedSourceClasses()) { - sourceClassEntries.add(c.getClassEntry()); - matching.removeSource(c); - } - for (ClassIdentity c : matching.getUnmatchedDestClasses()) { - destClassEntries.add(c.getClassEntry()); - matching.removeDest(c); + // we already know about these matches + matching.addKnownMatches(lastMatching.uniqueMatches()); + + // search unmatched and ambiguously-matched classes + matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); + for (ClassMatch match : lastMatching.ambiguousMatches()) { + matching.match(match.sourceClasses, match.destClasses); } } + System.out.println(matching); + BiMap uniqueMatches = matching.uniqueMatches(); - // compute a matching for the classes - for (ClassEntry classEntry : sourceClassEntries) { - CtClass c = sourceLoader.loadClass(classEntry.getName()); - ClassIdentity sourceClass = new ClassIdentity(c, sourceNamer, sourceIndex, useReferences); - matching.addSource(sourceClass); - } - for (ClassEntry classEntry : destClassEntries) { - CtClass c = destLoader.loadClass(classEntry.getName()); - ClassIdentity destClass = new ClassIdentity(c, destNamer, destIndex, useReferences); - matching.matchDestClass(destClass); + // did we match anything new this time? + if (uniqueMatches.size() > numUniqueMatchesLastTime) { + numUniqueMatchesLastTime = uniqueMatches.size(); + lastMatching = matching; + } else { + break; } - // TEMP - System.out.println(matching); - } while (matching.getUniqueMatches().size() - numMatches > 0); - } - - // check the class matches - System.out.println("Checking class matches..."); - ClassNamer namer = new ClassNamer(matching.getUniqueMatches()); - SidedClassNamer sourceNamer = namer.getSourceNamer(); - SidedClassNamer destNamer = namer.getDestNamer(); - for (Map.Entry entry : matching.getUniqueMatches().entrySet()) { - - // check source - ClassIdentity sourceClass = entry.getKey(); - CtClass sourceC = sourceLoader.loadClass(sourceClass.getClassEntry().getName()); - assert (sourceC != null) : "Unable to load source class " + sourceClass.getClassEntry(); - assert (sourceClass.matches(sourceC)) : "Source " + sourceClass + " doesn't match " + new ClassIdentity(sourceC, sourceNamer, sourceIndex, false); - - // check dest - ClassIdentity destClass = entry.getValue(); - CtClass destC = destLoader.loadClass(destClass.getClassEntry().getName()); - assert (destC != null) : "Unable to load dest class " + destClass.getClassEntry(); - assert (destClass.matches(destC)) : "Dest " + destClass + " doesn't match " + new ClassIdentity(destC, destNamer, destIndex, false); - } - - // warn about the ambiguous matchings - List,List>> ambiguousMatches = new ArrayList,List>>(matching.getAmbiguousMatches().entrySet()); - Collections.sort(ambiguousMatches, new Comparator,List>>() { - @Override - public int compare(Map.Entry,List> a, Map.Entry,List> b) { - String aName = a.getKey().get(0).getClassEntry().getName(); - String bName = b.getKey().get(0).getClassEntry().getName(); - return aName.compareTo(bName); + // update the namers + ClassNamer namer = new ClassNamer(uniqueMatches); + sourceNamer = namer.getSourceNamer(); + destNamer = namer.getDestNamer(); } - }); - for (Map.Entry,List> entry : ambiguousMatches) { - System.out.println("Ambiguous matching:"); - System.out.println("\tSource: " + getClassNames(entry.getKey())); - System.out.println("\tDest: " + getClassNames(entry.getValue())); } - /* DEBUG - Map.Entry,List> entry = ambiguousMatches.get( 7 ); - for (ClassIdentity c : entry.getKey()) { - System.out.println(c); - } - for(ClassIdentity c : entry.getKey()) { - System.out.println(decompile(sourceLoader, c.getClassEntry())); - } - */ - - return matching; + return lastMatching; } - private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { + private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { int numScoredMatchesShown = 0; for (int score : scores) { - for (ClassIdentity scoredMatch : scoredMatches.get(score)) { - System.out.println(String.format("\tScore: %3d %3.0f%% %s", score, 100.0 * score / maxScore, scoredMatch.getClassEntry().getName())); + for (ClassEntry classEntry : scoredMatches.get(score)) { + System.out.println(String.format("\tScore: %3d %3.0f%% %s", + score, 100.0 * score / maxScore, classEntry.getName() + )); if (numScoredMatchesShown++ > 10) { return; } @@ -383,10 +333,10 @@ public class ClassMatcher { } } - private static List getClassNames(Collection classes) { + private static List getClassNames(Collection classes) { List out = Lists.newArrayList(); - for (ClassIdentity c : classes) { - out.add(c.getClassEntry().getName()); + for (ClassEntry c : classes) { + out.add(c.getName()); } Collections.sort(out); return out; diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java index 53b6f7f..b94fd8b 100644 --- a/src/cuchaz/enigma/convert/ClassMatching.java +++ b/src/cuchaz/enigma/convert/ClassMatching.java @@ -10,164 +10,146 @@ ******************************************************************************/ package cuchaz.enigma.convert; -import java.util.AbstractMap; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; public class ClassMatching { - private Multimap m_sourceClasses; - private Multimap m_matchedDestClasses; - private List m_unmatchedDestClasses; - - public ClassMatching() { - m_sourceClasses = ArrayListMultimap.create(); - m_matchedDestClasses = ArrayListMultimap.create(); - m_unmatchedDestClasses = Lists.newArrayList(); - } + private ClassForest m_sourceClasses; + private ClassForest m_destClasses; + private BiMap m_knownMatches; - public void addSource(ClassIdentity c) { - m_sourceClasses.put(c, c); + public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) { + m_sourceClasses = new ClassForest(sourceIdentifier); + m_destClasses = new ClassForest(destIdentifier); + m_knownMatches = HashBiMap.create(); } - public void matchDestClass(ClassIdentity destClass) { - Collection matchedSourceClasses = m_sourceClasses.get(destClass); - if (matchedSourceClasses.isEmpty()) { - // no match - m_unmatchedDestClasses.add(destClass); - } else { - // found a match - m_matchedDestClasses.put(destClass, destClass); - - // DEBUG - ClassIdentity sourceClass = matchedSourceClasses.iterator().next(); - assert (sourceClass.hashCode() == destClass.hashCode()); - assert (sourceClass.equals(destClass)); - } + public void addKnownMatches(BiMap knownMatches) { + m_knownMatches.putAll(knownMatches); } - public void removeSource(ClassIdentity sourceClass) { - m_sourceClasses.remove(sourceClass, sourceClass); + public void match(Iterable sourceClasses, Iterable destClasses) { + m_sourceClasses.addAll(sourceClasses); + m_destClasses.addAll(destClasses); } - public void removeDest(ClassIdentity destClass) { - m_matchedDestClasses.remove(destClass, destClass); - m_unmatchedDestClasses.remove(destClass); + public Collection matches() { + List matches = Lists.newArrayList(); + for (Entry entry : m_knownMatches.entrySet()) { + matches.add(new ClassMatch( + entry.getKey(), + entry.getValue() + )); + } + for (ClassIdentity identity : m_sourceClasses.identities()) { + matches.add(new ClassMatch( + m_sourceClasses.getClasses(identity), + m_destClasses.getClasses(identity) + )); + } + for (ClassIdentity identity : m_destClasses.identities()) { + if (!m_sourceClasses.containsIdentity(identity)) { + matches.add(new ClassMatch( + new ArrayList(), + m_destClasses.getClasses(identity) + )); + } + } + return matches; } - public List getSourceClasses() { - return new ArrayList(m_sourceClasses.values()); + public Collection sourceClasses() { + Set classes = Sets.newHashSet(); + for (ClassMatch match : matches()) { + classes.addAll(match.sourceClasses); + } + return classes; } - public List getDestClasses() { - List classes = Lists.newArrayList(); - classes.addAll(m_matchedDestClasses.values()); - classes.addAll(m_unmatchedDestClasses); + public Collection destClasses() { + Set classes = Sets.newHashSet(); + for (ClassMatch match : matches()) { + classes.addAll(match.destClasses); + } return classes; } - public BiMap getUniqueMatches() { - BiMap uniqueMatches = HashBiMap.create(); - for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { - Collection matchedSourceClasses = m_sourceClasses.get(sourceClass); - Collection matchedDestClasses = m_matchedDestClasses.get(sourceClass); - if (matchedSourceClasses.size() == 1 && matchedDestClasses.size() == 1) { - ClassIdentity matchedSourceClass = matchedSourceClasses.iterator().next(); - ClassIdentity matchedDestClass = matchedDestClasses.iterator().next(); - uniqueMatches.put(matchedSourceClass, matchedDestClass); + public BiMap uniqueMatches() { + BiMap uniqueMatches = HashBiMap.create(); + for (ClassMatch match : matches()) { + if (match.isMatched() && !match.isAmbiguous()) { + uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); } } return uniqueMatches; } - public BiMap,List> getAmbiguousMatches() { - BiMap,List> ambiguousMatches = HashBiMap.create(); - for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { - Collection matchedSourceClasses = m_sourceClasses.get(sourceClass); - Collection matchedDestClasses = m_matchedDestClasses.get(sourceClass); - if (matchedSourceClasses.size() > 1 && matchedDestClasses.size() > 1) { - ambiguousMatches.put( - new ArrayList(matchedSourceClasses), - new ArrayList(matchedDestClasses) - ); + public Collection ambiguousMatches() { + List ambiguousMatches = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (match.isMatched() && match.isAmbiguous()) { + ambiguousMatches.add(match); } } return ambiguousMatches; } - public int getNumAmbiguousSourceMatches() { - int num = 0; - for (Map.Entry,List> entry : getAmbiguousMatches().entrySet()) { - num += entry.getKey().size(); - } - return num; - } - - public int getNumAmbiguousDestMatches() { - int num = 0; - for (Map.Entry,List> entry : getAmbiguousMatches().entrySet()) { - num += entry.getValue().size(); + public Collection unmatchedSourceClasses() { + List classes = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (!match.isMatched() && !match.sourceClasses.isEmpty()) { + classes.addAll(match.sourceClasses); + } } - return num; + return classes; } - public List getUnmatchedSourceClasses() { - List classes = Lists.newArrayList(); - for (ClassIdentity sourceClass : getSourceClasses()) { - if (m_matchedDestClasses.get(sourceClass).isEmpty()) { - classes.add(sourceClass); + public Collection unmatchedDestClasses() { + List classes = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (!match.isMatched() && !match.destClasses.isEmpty()) { + classes.addAll(match.destClasses); } } return classes; } - public List getUnmatchedDestClasses() { - return new ArrayList(m_unmatchedDestClasses); + public ClassIdentifier getSourceIdentifier() { + return m_sourceClasses.getIdentifier(); } - public Map>> getIndex() { - Map>> conversion = Maps.newHashMap(); - for (Map.Entry entry : getUniqueMatches().entrySet()) { - conversion.put( - entry.getKey().getClassEntry().getName(), - new AbstractMap.SimpleEntry>(entry.getKey(), Arrays.asList(entry.getValue())) - ); - } - for (Map.Entry,List> entry : getAmbiguousMatches().entrySet()) { - for (ClassIdentity sourceClass : entry.getKey()) { - conversion.put( - sourceClass.getClassEntry().getName(), - new AbstractMap.SimpleEntry>(sourceClass, entry.getValue()) - ); - } - } - for (ClassIdentity sourceClass : getUnmatchedSourceClasses()) { - conversion.put( - sourceClass.getClassEntry().getName(), - new AbstractMap.SimpleEntry>(sourceClass, getUnmatchedDestClasses()) - ); - } - return conversion; + public ClassIdentifier getDestIdentifier() { + return m_destClasses.getIdentifier(); } @Override public String toString() { + + // count the ambiguous classes + int numAmbiguousSource = 0; + int numAmbiguousDest = 0; + for (ClassMatch match : ambiguousMatches()) { + numAmbiguousSource += match.sourceClasses.size(); + numAmbiguousDest += match.destClasses.size(); + } + StringBuilder buf = new StringBuilder(); - buf.append(String.format("%12s%8s%8s\n", "", "Source", "Dest")); - buf.append(String.format("%12s%8d%8d\n", "Classes", getSourceClasses().size(), getDestClasses().size())); - buf.append(String.format("%12s%8d%8d\n", "Unique", getUniqueMatches().size(), getUniqueMatches().size())); - buf.append(String.format("%12s%8d%8d\n", "Ambiguous", getNumAmbiguousSourceMatches(), getNumAmbiguousDestMatches())); - buf.append(String.format("%12s%8d%8d\n", "Unmatched", getUnmatchedSourceClasses().size(), getUnmatchedDestClasses().size())); + buf.append(String.format("%20s%8s%8s\n", "", "Source", "Dest")); + buf.append(String.format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size())); + buf.append(String.format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size())); + buf.append(String.format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest)); + buf.append(String.format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size())); return buf.toString(); } } diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java index 1b6e81c..6423a18 100644 --- a/src/cuchaz/enigma/convert/ClassNamer.java +++ b/src/cuchaz/enigma/convert/ClassNamer.java @@ -15,6 +15,8 @@ import java.util.Map; import com.google.common.collect.BiMap; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.ClassEntry; + public class ClassNamer { public interface SidedClassNamer { @@ -24,15 +26,15 @@ public class ClassNamer { private Map m_sourceNames; private Map m_destNames; - public ClassNamer(BiMap mappings) { + public ClassNamer(BiMap mappings) { // convert the identity mappings to name maps m_sourceNames = Maps.newHashMap(); m_destNames = Maps.newHashMap(); int i = 0; - for (Map.Entry entry : mappings.entrySet()) { + for (Map.Entry entry : mappings.entrySet()) { String name = String.format("M%04d", i++); - m_sourceNames.put(entry.getKey().getClassEntry().getName(), name); - m_destNames.put(entry.getValue().getClassEntry().getName(), name); + m_sourceNames.put(entry.getKey().getName(), name); + m_destNames.put(entry.getValue().getName(), name); } } diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java index d985032..d3b6e77 100644 --- a/src/cuchaz/enigma/mapping/Translator.java +++ b/src/cuchaz/enigma/mapping/Translator.java @@ -27,6 +27,7 @@ public class Translator { public Translator() { m_direction = null; m_classes = Maps.newHashMap(); + m_index = new TranslationIndex(); } public Translator(TranslationDirection direction, Map classes, TranslationIndex index) { -- cgit v1.2.3 From 6a4bc21a63513bf5dd1ddcf72e19e6d503697342 Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 28 Feb 2015 19:45:52 -0500 Subject: switch to better-maintained version of JSyntaxPane --- src/cuchaz/enigma/gui/Gui.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java index 00cff59..a214243 100644 --- a/src/cuchaz/enigma/gui/Gui.java +++ b/src/cuchaz/enigma/gui/Gui.java @@ -65,8 +65,6 @@ import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; -import jsyntaxpane.DefaultSyntaxKit; - import com.google.common.collect.Lists; import cuchaz.enigma.Constants; @@ -90,6 +88,7 @@ import cuchaz.enigma.mapping.IllegalNameException; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.Signature; +import de.sciss.syntaxpane.DefaultSyntaxKit; public class Gui { @@ -262,7 +261,7 @@ public class Gui { // turn off token highlighting (it's wrong most of the time anyway...) DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit(); - kit.toggleComponent(m_editor, "jsyntaxpane.components.TokenMarker"); + kit.toggleComponent(m_editor, "de.sciss.syntaxpane.components.TokenMarker"); // init editor popup menu JPopupMenu popupMenu = new JPopupMenu(); -- cgit v1.2.3 From 88671184e20b3ad3791125cf96c83ca048cb2861 Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 28 Feb 2015 23:36:47 -0500 Subject: refactor converter a bit for upcoming convert gui --- src/cuchaz/enigma/ConvertMain.java | 70 +++++ src/cuchaz/enigma/convert/ClassForest.java | 4 - src/cuchaz/enigma/convert/ClassMatcher.java | 356 ----------------------- src/cuchaz/enigma/convert/ClassMatching.java | 8 - src/cuchaz/enigma/convert/MappingsConverter.java | 180 ++++++++++++ src/cuchaz/enigma/convert/Matches.java | 89 ++++++ src/cuchaz/enigma/convert/MatchesReader.java | 45 +++ src/cuchaz/enigma/convert/MatchesWriter.java | 41 +++ src/cuchaz/enigma/gui/MatchingGui.java | 140 +++++++++ 9 files changed, 565 insertions(+), 368 deletions(-) create mode 100644 src/cuchaz/enigma/ConvertMain.java delete mode 100644 src/cuchaz/enigma/convert/ClassMatcher.java create mode 100644 src/cuchaz/enigma/convert/MappingsConverter.java create mode 100644 src/cuchaz/enigma/convert/Matches.java create mode 100644 src/cuchaz/enigma/convert/MatchesReader.java create mode 100644 src/cuchaz/enigma/convert/MatchesWriter.java create mode 100644 src/cuchaz/enigma/gui/MatchingGui.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java new file mode 100644 index 0000000..7ba4761 --- /dev/null +++ b/src/cuchaz/enigma/ConvertMain.java @@ -0,0 +1,70 @@ +package cuchaz.enigma; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.jar.JarFile; + +import cuchaz.enigma.convert.MappingsConverter; +import cuchaz.enigma.convert.Matches; +import cuchaz.enigma.convert.MatchesReader; +import cuchaz.enigma.convert.MatchesWriter; +import cuchaz.enigma.gui.MatchingGui; +import cuchaz.enigma.mapping.MappingParseException; +import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsReader; + + +public class ConvertMain { + + public static void main(String[] args) + throws IOException, MappingParseException { + + // init files + File home = new File(System.getProperty("user.home")); + JarFile sourceJar = new JarFile(new File(home, ".minecraft/versions/1.8/1.8.jar")); + JarFile destJar = new JarFile(new File(home, ".minecraft/versions/1.8.3/1.8.3.jar")); + File inMappingsFile = new File("../Enigma Mappings/1.8.mappings"); + File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings"); + Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); + File matchingFile = new File(inMappingsFile.getName() + ".matching"); + + //computeMatches(matchingFile, sourceJar, destJar, mappings); + editMatches(matchingFile, sourceJar, destJar, mappings); + //convertMappings(outMappingsFile, mappings, matchingFile); + + /* TODO + // write out the converted mappings + FileWriter writer = new FileWriter(outMappingsFile); + new MappingsWriter().write(writer, mappings); + writer.close(); + System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); + */ + } + + private static void computeMatches(File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + throws IOException { + Matches matches = MappingsConverter.computeMatches(sourceJar, destJar, mappings); + MatchesWriter.write(matches, matchingFile); + System.out.println("Wrote:\n\t" + matchingFile.getAbsolutePath()); + } + + private static void editMatches(File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + throws IOException { + System.out.println("Reading matches..."); + Matches matches = MatchesReader.read(matchingFile); + System.out.println("Indexing source jar..."); + Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); + sourceDeobfuscator.setMappings(mappings); + System.out.println("Indexing dest jar..."); + Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + System.out.println("Starting GUI..."); + new MatchingGui(matches, sourceDeobfuscator, destDeobfuscator); + } + + private static void convertMappings(File outMappingsFile, Mappings mappings, File matchingFile) + throws IOException { + Matches matches = MatchesReader.read(matchingFile); + MappingsConverter.convertMappings(mappings, matches.getUniqueMatches()); + } +} diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java index e113eeb..3673139 100644 --- a/src/cuchaz/enigma/convert/ClassForest.java +++ b/src/cuchaz/enigma/convert/ClassForest.java @@ -18,10 +18,6 @@ public class ClassForest { m_forest = HashMultimap.create(); } - public ClassIdentifier getIdentifier() { - return m_identifier; - } - public void addAll(Iterable entries) { for (ClassEntry entry : entries) { add(entry); diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java deleted file mode 100644 index f43f5b2..0000000 --- a/src/cuchaz/enigma/convert/ClassMatcher.java +++ /dev/null @@ -1,356 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ -package cuchaz.enigma.convert; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.jar.JarFile; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; - -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ClassMapping; -import cuchaz.enigma.mapping.MappingParseException; -import cuchaz.enigma.mapping.Mappings; -import cuchaz.enigma.mapping.MappingsReader; -import cuchaz.enigma.mapping.MappingsWriter; -import cuchaz.enigma.mapping.MethodEntry; -import cuchaz.enigma.mapping.MethodMapping; - -public class ClassMatcher { - - public static void main(String[] args) - throws IOException, MappingParseException { - - // setup files - File home = new File(System.getProperty("user.home")); - JarFile sourceJar = new JarFile(new File(home, ".minecraft/versions/1.8/1.8.jar")); - JarFile destJar = new JarFile(new File(home, ".minecraft/versions/1.8.3/1.8.3.jar")); - File inMappingsFile = new File("../Enigma Mappings/1.8.mappings"); - File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings"); - - // define a matching to use when the automated system cannot find a match - Map fallbackMatching = Maps.newHashMap(); - /* - fallbackMatching.put("none/ayb", "none/ayf"); - fallbackMatching.put("none/ayd", "none/ayd"); - fallbackMatching.put("none/bgk", "unknown/bgk"); - */ - - // do the conversion - Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); - convertMappings(sourceJar, destJar, mappings, fallbackMatching); - - // write out the converted mappings - FileWriter writer = new FileWriter(outMappingsFile); - new MappingsWriter().write(writer, mappings); - writer.close(); - System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); - } - - private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map fallbackMatching) { - - // index jars - System.out.println("Indexing source jar..."); - JarIndex sourceIndex = new JarIndex(); - sourceIndex.indexJar(sourceJar, false); - System.out.println("Indexing dest jar..."); - JarIndex destIndex = new JarIndex(); - destIndex.indexJar(destJar, false); - - // compute the matching - ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex); - - // get all the obf class names used in the mappings - Set usedClasses = Sets.newHashSet(); - for (String className : mappings.getAllObfClassNames()) { - usedClasses.add(new ClassEntry(className)); - } - System.out.println("Mappings reference " + usedClasses.size() + " classes"); - - // see what the used classes map to - BiMap uniqueUsedMatches = HashBiMap.create(); - Map ambiguousUsedMatches = Maps.newHashMap(); - Set unmatchedUsedClasses = Sets.newHashSet(); - for (ClassMatch match : matching.matches()) { - Set matchUsedClasses = match.intersectSourceClasses(usedClasses); - if (matchUsedClasses.isEmpty()) { - continue; - } - - // classify the match - if (!match.isMatched()) { - // unmatched - unmatchedUsedClasses.addAll(matchUsedClasses); - } else { - if (match.isAmbiguous()) { - // ambiguously matched - for (ClassEntry matchUsedClass : matchUsedClasses) { - ambiguousUsedMatches.put(matchUsedClass, match); - } - } else { - // uniquely matched - uniqueUsedMatches.put(match.getUniqueSource(), match.getUniqueDest()); - } - } - } - - // get unmatched dest classes - Set unmatchedDestClasses = Sets.newHashSet(); - for (ClassMatch match : matching.matches()) { - if (!match.isMatched()) { - unmatchedDestClasses.addAll(match.destClasses); - } - } - - // warn about the ambiguous used matches - if (ambiguousUsedMatches.size() > 0) { - System.out.println(String.format("%d source classes have ambiguous mappings", ambiguousUsedMatches.size())); - List ambiguousMatchesList = Lists.newArrayList(Sets.newHashSet(ambiguousUsedMatches.values())); - Collections.sort(ambiguousMatchesList, new Comparator() { - @Override - public int compare(ClassMatch a, ClassMatch b) { - String aName = a.sourceClasses.iterator().next().getName(); - String bName = b.sourceClasses.iterator().next().getName(); - return aName.compareTo(bName); - } - }); - for (ClassMatch match : ambiguousMatchesList) { - System.out.println("Ambiguous matching:"); - System.out.println("\tSource: " + getClassNames(match.sourceClasses)); - System.out.println("\tDest: " + getClassNames(match.destClasses)); - } - } - - // warn about unmatched used classes - for (ClassEntry unmatchedUsedClass : unmatchedUsedClasses) { - System.out.println("No exact match for source class " + unmatchedUsedClass.getClassEntry()); - - // rank all the unmatched dest classes against the used class - ClassIdentity sourceIdentity = matching.getSourceIdentifier().identify(unmatchedUsedClass); - Multimap scoredDestClasses = ArrayListMultimap.create(); - for (ClassEntry unmatchedDestClass : unmatchedDestClasses) { - ClassIdentity destIdentity = matching.getDestIdentifier().identify(unmatchedDestClass); - scoredDestClasses.put(sourceIdentity.getMatchScore(destIdentity), unmatchedDestClass); - } - - List scores = new ArrayList(scoredDestClasses.keySet()); - Collections.sort(scores, Collections.reverseOrder()); - printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses); - - /* TODO: re-enable auto-pick logic - // does the best match have a non-zero score and the same name? - int bestScore = scores.get(0); - Collection bestMatches = scoredMatches.get(bestScore); - if (bestScore > 0 && bestMatches.size() == 1) { - ClassIdentity bestMatch = bestMatches.iterator().next(); - if (bestMatch.getClassEntry().equals(sourceClass.getClassEntry())) { - // use it - System.out.println("\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName()); - destClasses.clear(); - destClasses.add(bestMatch); - } - } - */ - } - - // bail if there were unmatched classes - if (!unmatchedUsedClasses.isEmpty()) { - throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!"); - } - - // sort the changes so classes are renamed in the correct order - // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b - BiMap unsortedChanges = HashBiMap.create(uniqueUsedMatches); - LinkedHashMap sortedChanges = Maps.newLinkedHashMap(); - int numChangesLeft = unsortedChanges.size(); - while (!unsortedChanges.isEmpty()) { - Iterator> iter = unsortedChanges.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry change = iter.next(); - if (unsortedChanges.containsKey(change.getValue())) { - sortedChanges.put(change.getKey(), change.getValue()); - iter.remove(); - } - } - - // did we remove any changes? - if (numChangesLeft - unsortedChanges.size() > 0) { - // keep going - numChangesLeft = unsortedChanges.size(); - } else { - // can't sort anymore. There must be a loop - break; - } - } - if (!unsortedChanges.isEmpty()) { - throw new Error(String.format("Unable to sort %d/%d class changes!", unsortedChanges.size(), uniqueUsedMatches.size())); - } - - // convert the mappings in the correct class order - for (Map.Entry entry : sortedChanges.entrySet()) { - mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); - } - - // check the method matches - System.out.println("Checking methods..."); - for (ClassMapping classMapping : mappings.classes()) { - ClassEntry classEntry = new ClassEntry(classMapping.getObfFullName()); - for (MethodMapping methodMapping : classMapping.methods()) { - - // skip constructors - if (methodMapping.getObfName().equals("")) { - continue; - } - - MethodEntry methodEntry = new MethodEntry( - classEntry, - methodMapping.getObfName(), - methodMapping.getObfSignature() - ); - if (!destIndex.containsObfBehavior(methodEntry)) { - System.err.println("WARNING: method doesn't match: " + methodEntry); - - /* TODO: show methods if needed - // show the available methods - System.err.println("\tAvailable dest methods:"); - CtClass c = destLoader.loadClass(classMapping.getObfFullName()); - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); - } - - System.err.println("\tAvailable source methods:"); - c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfFullName())); - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); - } - */ - } - } - } - - System.out.println("Done!"); - } - - public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) { - - System.out.println("Iteratively matching classes..."); - - ClassMatching lastMatching = null; - int round = 0; - SidedClassNamer sourceNamer = null; - SidedClassNamer destNamer = null; - for (boolean useReferences : Arrays.asList(false, true)) { - - int numUniqueMatchesLastTime = 0; - if (lastMatching != null) { - numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); - } - - while (true) { - - System.out.println("Round " + (++round) + " ..."); - - // init the matching with identity settings - ClassMatching matching = new ClassMatching( - new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), - new ClassIdentifier(destJar, destIndex, destNamer, useReferences) - ); - - if (lastMatching == null) { - // search all classes - matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); - } else { - // we already know about these matches - matching.addKnownMatches(lastMatching.uniqueMatches()); - - // search unmatched and ambiguously-matched classes - matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); - for (ClassMatch match : lastMatching.ambiguousMatches()) { - matching.match(match.sourceClasses, match.destClasses); - } - } - System.out.println(matching); - BiMap uniqueMatches = matching.uniqueMatches(); - - // did we match anything new this time? - if (uniqueMatches.size() > numUniqueMatchesLastTime) { - numUniqueMatchesLastTime = uniqueMatches.size(); - lastMatching = matching; - } else { - break; - } - - // update the namers - ClassNamer namer = new ClassNamer(uniqueMatches); - sourceNamer = namer.getSourceNamer(); - destNamer = namer.getDestNamer(); - } - } - - return lastMatching; - } - - private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { - int numScoredMatchesShown = 0; - for (int score : scores) { - for (ClassEntry classEntry : scoredMatches.get(score)) { - System.out.println(String.format("\tScore: %3d %3.0f%% %s", - score, 100.0 * score / maxScore, classEntry.getName() - )); - if (numScoredMatchesShown++ > 10) { - return; - } - } - } - } - - private static List getClassNames(Collection classes) { - List out = Lists.newArrayList(); - for (ClassEntry c : classes) { - out.add(c.getName()); - } - Collections.sort(out); - return out; - } - - /* DEBUG - private static String decompile(TranslatingTypeLoader loader, ClassEntry classEntry) { - PlainTextOutput output = new PlainTextOutput(); - DecompilerSettings settings = DecompilerSettings.javaDefaults(); - settings.setForceExplicitImports(true); - settings.setShowSyntheticMembers(true); - settings.setTypeLoader(loader); - Decompiler.decompile(classEntry.getName(), output, settings); - return output.toString(); - } - */ -} diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java index b94fd8b..9f93130 100644 --- a/src/cuchaz/enigma/convert/ClassMatching.java +++ b/src/cuchaz/enigma/convert/ClassMatching.java @@ -125,14 +125,6 @@ public class ClassMatching { return classes; } - public ClassIdentifier getSourceIdentifier() { - return m_sourceClasses.getIdentifier(); - } - - public ClassIdentifier getDestIdentifier() { - return m_destClasses.getIdentifier(); - } - @Override public String toString() { diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java new file mode 100644 index 0000000..d0f9382 --- /dev/null +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.convert; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.jar.JarFile; + +import com.google.common.collect.BiMap; +import com.google.common.collect.Maps; + +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Mappings; + +public class MappingsConverter { + + public static Matches computeMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { + + // index jars + System.out.println("Indexing source jar..."); + JarIndex sourceIndex = new JarIndex(); + sourceIndex.indexJar(sourceJar, false); + System.out.println("Indexing dest jar..."); + JarIndex destIndex = new JarIndex(); + destIndex.indexJar(destJar, false); + + // compute the matching + ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex); + return new Matches(matching.matches()); + } + + public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) { + + System.out.println("Iteratively matching classes"); + + ClassMatching lastMatching = null; + int round = 0; + SidedClassNamer sourceNamer = null; + SidedClassNamer destNamer = null; + for (boolean useReferences : Arrays.asList(false, true)) { + + int numUniqueMatchesLastTime = 0; + if (lastMatching != null) { + numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); + } + + while (true) { + + System.out.println("Round " + (++round) + "..."); + + // init the matching with identity settings + ClassMatching matching = new ClassMatching( + new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), + new ClassIdentifier(destJar, destIndex, destNamer, useReferences) + ); + + if (lastMatching == null) { + // search all classes + matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); + } else { + // we already know about these matches + matching.addKnownMatches(lastMatching.uniqueMatches()); + + // search unmatched and ambiguously-matched classes + matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); + for (ClassMatch match : lastMatching.ambiguousMatches()) { + matching.match(match.sourceClasses, match.destClasses); + } + } + System.out.println(matching); + BiMap uniqueMatches = matching.uniqueMatches(); + + // did we match anything new this time? + if (uniqueMatches.size() > numUniqueMatchesLastTime) { + numUniqueMatchesLastTime = uniqueMatches.size(); + lastMatching = matching; + } else { + break; + } + + // update the namers + ClassNamer namer = new ClassNamer(uniqueMatches); + sourceNamer = namer.getSourceNamer(); + destNamer = namer.getDestNamer(); + } + } + + return lastMatching; + } + + public static void convertMappings(Mappings mappings, BiMap changes) { + + // sort the changes so classes are renamed in the correct order + // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b + LinkedHashMap sortedChanges = Maps.newLinkedHashMap(); + int numChangesLeft = changes.size(); + while (!changes.isEmpty()) { + Iterator> iter = changes.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry change = iter.next(); + if (changes.containsKey(change.getValue())) { + sortedChanges.put(change.getKey(), change.getValue()); + iter.remove(); + } + } + + // did we remove any changes? + if (numChangesLeft - changes.size() > 0) { + // keep going + numChangesLeft = changes.size(); + } else { + // can't sort anymore. There must be a loop + break; + } + } + if (!changes.isEmpty()) { + throw new Error("Unable to sort class changes! There must be a cycle."); + } + + // convert the mappings in the correct class order + for (Map.Entry entry : sortedChanges.entrySet()) { + mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); + } + } + + /* TODO: after we get a mapping, check to see that the other entries match + public static void checkMethods() { + + // check the method matches + System.out.println("Checking methods..."); + for (ClassMapping classMapping : mappings.classes()) { + ClassEntry classEntry = new ClassEntry(classMapping.getObfFullName()); + for (MethodMapping methodMapping : classMapping.methods()) { + + // skip constructors + if (methodMapping.getObfName().equals("")) { + continue; + } + + MethodEntry methodEntry = new MethodEntry( + classEntry, + methodMapping.getObfName(), + methodMapping.getObfSignature() + ); + if (!destIndex.containsObfBehavior(methodEntry)) { + System.err.println("WARNING: method doesn't match: " + methodEntry); + + // TODO: show methods if needed + // show the available methods + System.err.println("\tAvailable dest methods:"); + CtClass c = destLoader.loadClass(classMapping.getObfFullName()); + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); + } + + System.err.println("\tAvailable source methods:"); + c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfFullName())); + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); + } + } + } + } + + System.out.println("Done!"); + } + */ +} diff --git a/src/cuchaz/enigma/convert/Matches.java b/src/cuchaz/enigma/convert/Matches.java new file mode 100644 index 0000000..75ecc2a --- /dev/null +++ b/src/cuchaz/enigma/convert/Matches.java @@ -0,0 +1,89 @@ +package cuchaz.enigma.convert; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class Matches implements Iterable { + + Collection m_matches; + BiMap m_uniqueMatches; + Map m_ambiguousMatchesBySource; + Map m_ambiguousMatchesByDest; + Set m_unmatchedSourceClasses; + Set m_unmatchedDestClasses; + + public Matches() { + this(new ArrayList()); + } + + public Matches(Collection matches) { + m_matches = matches; + m_uniqueMatches = HashBiMap.create(); + m_ambiguousMatchesBySource = Maps.newHashMap(); + m_ambiguousMatchesByDest = Maps.newHashMap(); + m_unmatchedSourceClasses = Sets.newHashSet(); + m_unmatchedDestClasses = Sets.newHashSet(); + + for (ClassMatch match : matches) { + indexMatch(match); + } + } + + public void add(ClassMatch match) { + m_matches.add(match); + indexMatch(match); + } + + public int size() { + return m_matches.size(); + } + + @Override + public Iterator iterator() { + return m_matches.iterator(); + } + + private void indexMatch(ClassMatch match) { + if (!match.isMatched()) { + // unmatched + m_unmatchedSourceClasses.addAll(match.sourceClasses); + m_unmatchedDestClasses.addAll(match.destClasses); + } else { + if (match.isAmbiguous()) { + // ambiguously matched + for (ClassEntry entry : match.sourceClasses) { + m_ambiguousMatchesBySource.put(entry, match); + } + for (ClassEntry entry : match.destClasses) { + m_ambiguousMatchesByDest.put(entry, match); + } + } else { + // uniquely matched + m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } + } + } + + public BiMap getUniqueMatches() { + return m_uniqueMatches; + } + + public Set getUnmatchedSourceClasses() { + return m_unmatchedSourceClasses; + } + + public Set getUnmatchedDestClasses() { + return m_unmatchedDestClasses; + } +} diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java new file mode 100644 index 0000000..808f8d0 --- /dev/null +++ b/src/cuchaz/enigma/convert/MatchesReader.java @@ -0,0 +1,45 @@ +package cuchaz.enigma.convert; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +import com.beust.jcommander.internal.Lists; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class MatchesReader { + + public static Matches read(File file) + throws IOException { + try (BufferedReader in = new BufferedReader(new FileReader(file))) { + Matches matches = new Matches(); + String line = null; + while ((line = in.readLine()) != null) { + matches.add(readMatch(line)); + } + return matches; + } + } + + private static ClassMatch readMatch(String line) + throws IOException { + String[] sides = line.split(":", 2); + return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); + } + + private static Collection readClasses(String in) { + List entries = Lists.newArrayList(); + for (String className : in.split(",")) { + className = className.trim(); + if (className.length() > 0) { + entries.add(new ClassEntry(className)); + } + } + return entries; + } +} diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java new file mode 100644 index 0000000..49ffb6d --- /dev/null +++ b/src/cuchaz/enigma/convert/MatchesWriter.java @@ -0,0 +1,41 @@ +package cuchaz.enigma.convert; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class MatchesWriter { + + public static void write(Matches matches, File file) + throws IOException { + try (FileWriter out = new FileWriter(file)) { + for (ClassMatch match : matches) { + writeMatch(out, match); + } + } + } + + private static void writeMatch(FileWriter out, ClassMatch match) + throws IOException { + writeClasses(out, match.sourceClasses); + out.write(":"); + writeClasses(out, match.destClasses); + out.write("\n"); + } + + private static void writeClasses(FileWriter out, Iterable classes) + throws IOException { + boolean isFirst = true; + for (ClassEntry entry : classes) { + if (isFirst) { + isFirst = false; + } else { + out.write(","); + } + out.write(entry.toString()); + } + } +} diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java new file mode 100644 index 0000000..53c767a --- /dev/null +++ b/src/cuchaz/enigma/gui/MatchingGui.java @@ -0,0 +1,140 @@ +package cuchaz.enigma.gui; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.convert.Matches; + + +public class MatchingGui { + + public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + // TODO Auto-generated constructor stub + } + + + /* TODO: see if we can use any of this here + public static doTheThings() { + + // get all the obf class names used in the mappings + Set usedClasses = Sets.newHashSet(); + for (String className : mappings.getAllObfClassNames()) { + usedClasses.add(new ClassEntry(className)); + } + System.out.println(String.format("Mappings reference %d/%d classes", + usedClasses.size(), sourceIndex.getObfClassEntries().size() + )); + + // get the used matches + Collection matches = matching.matches(); + Matches usedMatches = new Matches(); + for (ClassMatch match : matching.matches()) { + if (!match.intersectSourceClasses(usedClasses).isEmpty()) { + usedMatches.add(match); + } + } + System.out.println(String.format("Mappings reference %d/%d match groups", + usedMatches.size(), matches.size() + )); + + // see what the used classes map to + BiMap uniqueUsedMatches = HashBiMap.create(); + Map ambiguousUsedMatches = Maps.newHashMap(); + Set unmatchedUsedClasses = Sets.newHashSet(); + for (ClassMatch match : matching.matches()) { + Set matchUsedClasses = match.intersectSourceClasses(usedClasses); + if (matchUsedClasses.isEmpty()) { + continue; + } + + usedMatches.add(match); + + // classify the match + if (!match.isMatched()) { + // unmatched + unmatchedUsedClasses.addAll(matchUsedClasses); + } else { + if (match.isAmbiguous()) { + // ambiguously matched + for (ClassEntry matchUsedClass : matchUsedClasses) { + ambiguousUsedMatches.put(matchUsedClass, match); + } + } else { + // uniquely matched + uniqueUsedMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } + } + } + + // get unmatched dest classes + Set unmatchedDestClasses = Sets.newHashSet(); + for (ClassMatch match : matching.matches()) { + if (!match.isMatched()) { + unmatchedDestClasses.addAll(match.destClasses); + } + } + + // warn about the ambiguous used matches + if (ambiguousUsedMatches.size() > 0) { + System.out.println(String.format("%d source classes have ambiguous mappings", ambiguousUsedMatches.size())); + List ambiguousMatchesList = Lists.newArrayList(Sets.newHashSet(ambiguousUsedMatches.values())); + Collections.sort(ambiguousMatchesList, new Comparator() { + @Override + public int compare(ClassMatch a, ClassMatch b) { + String aName = a.sourceClasses.iterator().next().getName(); + String bName = b.sourceClasses.iterator().next().getName(); + return aName.compareTo(bName); + } + }); + for (ClassMatch match : ambiguousMatchesList) { + System.out.println("Ambiguous matching:"); + System.out.println("\tSource: " + getClassNames(match.sourceClasses)); + System.out.println("\tDest: " + getClassNames(match.destClasses)); + } + } + + // warn about unmatched used classes + for (ClassEntry unmatchedUsedClass : unmatchedUsedClasses) { + System.out.println("No exact match for source class " + unmatchedUsedClass.getClassEntry()); + + // rank all the unmatched dest classes against the used class + ClassIdentity sourceIdentity = matching.getSourceIdentifier().identify(unmatchedUsedClass); + Multimap scoredDestClasses = ArrayListMultimap.create(); + for (ClassEntry unmatchedDestClass : unmatchedDestClasses) { + ClassIdentity destIdentity = matching.getDestIdentifier().identify(unmatchedDestClass); + scoredDestClasses.put(sourceIdentity.getMatchScore(destIdentity), unmatchedDestClass); + } + + List scores = new ArrayList(scoredDestClasses.keySet()); + Collections.sort(scores, Collections.reverseOrder()); + printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses); + } + + // bail if there were unmatched classes + if (!unmatchedUsedClasses.isEmpty()) { + throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!"); + } + } + + private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { + int numScoredMatchesShown = 0; + for (int score : scores) { + for (ClassEntry classEntry : scoredMatches.get(score)) { + System.out.println(String.format("\tScore: %3d %3.0f%% %s", + score, 100.0 * score / maxScore, classEntry.getName() + )); + if (numScoredMatchesShown++ > 10) { + return; + } + } + } + } + + private static List getClassNames(Collection classes) { + List out = Lists.newArrayList(); + for (ClassEntry c : classes) { + out.add(c.getName()); + } + Collections.sort(out); + return out; + } + */ +} -- cgit v1.2.3 From 54d17da93c6708e54c296d63783a60f1c024797b Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 2 Mar 2015 01:01:51 -0500 Subject: finished most of the matching gui --- src/cuchaz/enigma/ConvertMain.java | 1 + src/cuchaz/enigma/Deobfuscator.java | 4 + src/cuchaz/enigma/convert/MappingsConverter.java | 61 ++++ src/cuchaz/enigma/convert/Matches.java | 26 ++ src/cuchaz/enigma/gui/ClassSelector.java | 19 +- src/cuchaz/enigma/gui/ClassSelectorClassNode.java | 3 + src/cuchaz/enigma/gui/DecoratedClassEntry.java | 20 ++ src/cuchaz/enigma/gui/MatchingGui.java | 413 +++++++++++++++++----- 8 files changed, 446 insertions(+), 101 deletions(-) create mode 100644 src/cuchaz/enigma/gui/DecoratedClassEntry.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index 7ba4761..4fc58e8 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -58,6 +58,7 @@ public class ConvertMain { sourceDeobfuscator.setMappings(mappings); System.out.println("Indexing dest jar..."); Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + destDeobfuscator.setMappings(MappingsConverter.newMappings(matches, mappings, sourceDeobfuscator, destDeobfuscator)); System.out.println("Starting GUI..."); new MatchingGui(matches, sourceDeobfuscator, destDeobfuscator); } diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 0b7808d..e5d0e3d 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -99,6 +99,10 @@ public class Deobfuscator { setMappings(new Mappings()); } + public JarFile getJar() { + return m_jar; + } + public String getJarName() { return m_jar.getName(); } diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index d0f9382..aa067d4 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -11,17 +11,25 @@ package cuchaz.enigma.convert; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.jar.JarFile; +import com.beust.jcommander.internal.Lists; import com.google.common.collect.BiMap; +import com.google.common.collect.HashMultimap; import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassMapping; import cuchaz.enigma.mapping.Mappings; public class MappingsConverter { @@ -100,6 +108,59 @@ public class MappingsConverter { return lastMatching; } + public static Mappings newMappings(Matches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + + // sort the unique matches by size of inner class chain + Multimap> matchesByDestChainSize = HashMultimap.create(); + for (Entry match : matches.getUniqueMatches().entrySet()) { + int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size(); + matchesByDestChainSize.put(chainSize, match); + } + + // build the mappings (in order of small-to-large inner chains) + Mappings newMappings = new Mappings(); + List chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); + Collections.sort(chainSizes); + for (int chainSize : chainSizes) { + for (Entry match : matchesByDestChainSize.get(chainSize)) { + + // get class info + ClassEntry sourceClassEntry = match.getKey(); + ClassEntry deobfClassEntry = sourceDeobfuscator.deobfuscateEntry(sourceClassEntry); + ClassEntry destClassEntry = match.getValue(); + List destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(destClassEntry); + + // find out where to make the dest class mapping + if (destClassChain.size() == 1) { + // not an inner class, add directly to mappings + newMappings.addClassMapping(new ClassMapping(destClassEntry.getName(), deobfClassEntry.getName())); + } else { + // inner class, find the outer class mapping + ClassMapping destMapping = null; + for (int i=0; i changes) { // sort the changes so classes are renamed in the correct order diff --git a/src/cuchaz/enigma/convert/Matches.java b/src/cuchaz/enigma/convert/Matches.java index 75ecc2a..5faa923 100644 --- a/src/cuchaz/enigma/convert/Matches.java +++ b/src/cuchaz/enigma/convert/Matches.java @@ -17,6 +17,8 @@ import cuchaz.enigma.mapping.ClassEntry; public class Matches implements Iterable { Collection m_matches; + Map m_matchesBySource; + Map m_matchesByDest; BiMap m_uniqueMatches; Map m_ambiguousMatchesBySource; Map m_ambiguousMatchesByDest; @@ -29,6 +31,8 @@ public class Matches implements Iterable { public Matches(Collection matches) { m_matches = matches; + m_matchesBySource = Maps.newHashMap(); + m_matchesByDest = Maps.newHashMap(); m_uniqueMatches = HashBiMap.create(); m_ambiguousMatchesBySource = Maps.newHashMap(); m_ambiguousMatchesByDest = Maps.newHashMap(); @@ -73,6 +77,12 @@ public class Matches implements Iterable { m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); } } + for (ClassEntry entry : match.sourceClasses) { + m_matchesBySource.put(entry, match); + } + for (ClassEntry entry : match.destClasses) { + m_matchesByDest.put(entry, match); + } } public BiMap getUniqueMatches() { @@ -86,4 +96,20 @@ public class Matches implements Iterable { public Set getUnmatchedDestClasses() { return m_unmatchedDestClasses; } + + public Set getAmbiguouslyMatchedSourceClasses() { + return m_ambiguousMatchesBySource.keySet(); + } + + public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) { + return m_ambiguousMatchesBySource.get(sourceClass); + } + + public ClassMatch getMatchBySource(ClassEntry sourceClass) { + return m_matchesBySource.get(sourceClass); + } + + public ClassMatch getMatchByDest(ClassEntry destClass) { + return m_matchesByDest.get(destClass); + } } diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java index 654bfbe..e5f550b 100644 --- a/src/cuchaz/enigma/gui/ClassSelector.java +++ b/src/cuchaz/enigma/gui/ClassSelector.java @@ -41,21 +41,32 @@ public class ClassSelector extends JTree { public static Comparator ObfuscatedClassEntryComparator; public static Comparator DeobfuscatedClassEntryComparator; + private static String getClassEntryDisplayName(ClassEntry entry) { + if (entry instanceof DecoratedClassEntry) { + return ((DecoratedClassEntry)entry).getDecoration() + entry.getName(); + } + return entry.getName(); + } + static { ObfuscatedClassEntryComparator = new Comparator() { @Override public int compare(ClassEntry a, ClassEntry b) { - if (a.getName().length() != b.getName().length()) { - return a.getName().length() - b.getName().length(); + String aname = getClassEntryDisplayName(a); + String bname = getClassEntryDisplayName(b); + if (aname.length() != bname.length()) { + return aname.length() - bname.length(); } - return a.getName().compareTo(b.getName()); + return aname.compareTo(bname); } }; DeobfuscatedClassEntryComparator = new Comparator() { @Override public int compare(ClassEntry a, ClassEntry b) { - return a.getName().compareTo(b.getName()); + String aname = getClassEntryDisplayName(a); + String bname = getClassEntryDisplayName(b); + return aname.compareTo(bname); } }; } diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java index 66e931b..b3d7b89 100644 --- a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java +++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java @@ -30,6 +30,9 @@ public class ClassSelectorClassNode extends DefaultMutableTreeNode { @Override public String toString() { + if (m_classEntry instanceof DecoratedClassEntry) { + return ((DecoratedClassEntry)m_classEntry).getDecoration() + m_classEntry.getSimpleName(); + } return m_classEntry.getSimpleName(); } } diff --git a/src/cuchaz/enigma/gui/DecoratedClassEntry.java b/src/cuchaz/enigma/gui/DecoratedClassEntry.java new file mode 100644 index 0000000..dd8b4fa --- /dev/null +++ b/src/cuchaz/enigma/gui/DecoratedClassEntry.java @@ -0,0 +1,20 @@ +package cuchaz.enigma.gui; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class DecoratedClassEntry extends ClassEntry { + + private static final long serialVersionUID = -8798725308554217105L; + + private String m_decoration; + + public DecoratedClassEntry(ClassEntry other, String decoration) { + super(other); + m_decoration = decoration; + } + + public String getDecoration() { + return m_decoration; + } +} diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java index 53c767a..e9dff16 100644 --- a/src/cuchaz/enigma/gui/MatchingGui.java +++ b/src/cuchaz/enigma/gui/MatchingGui.java @@ -1,133 +1,352 @@ package cuchaz.enigma.gui; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.WindowConstants; + +import com.beust.jcommander.internal.Lists; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; + +import cuchaz.enigma.Constants; import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.convert.ClassIdentifier; +import cuchaz.enigma.convert.ClassIdentity; +import cuchaz.enigma.convert.ClassMatch; +import cuchaz.enigma.convert.ClassNamer; import cuchaz.enigma.convert.Matches; +import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; +import cuchaz.enigma.mapping.ClassEntry; +import de.sciss.syntaxpane.DefaultSyntaxKit; public class MatchingGui { - - public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - // TODO Auto-generated constructor stub + + private static enum SourceType { + Matched { + + @Override + public Collection getSourceClasses(Matches matches) { + return matches.getUniqueMatches().keySet(); + } + }, + Unmatched { + + @Override + public Collection getSourceClasses(Matches matches) { + return matches.getUnmatchedSourceClasses(); + } + }, + Ambiguous { + + @Override + public Collection getSourceClasses(Matches matches) { + return matches.getAmbiguouslyMatchedSourceClasses(); + } + }; + + public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { + JRadioButton button = new JRadioButton(name(), this == getDefault()); + button.setActionCommand(name()); + button.addActionListener(listener); + group.add(button); + return button; + } + + public abstract Collection getSourceClasses(Matches matches); + + public static SourceType getDefault() { + return values()[0]; + } } + // controls + private JFrame m_frame; + private ClassSelector m_sourceClasses; + private ClassSelector m_destClasses; + private JEditorPane m_sourceReader; + private JEditorPane m_destReader; + private JLabel m_sourceClassLabel; + private JLabel m_destClassLabel; + private JButton m_matchButton; + + private Matches m_matches; + private Deobfuscator m_sourceDeobfuscator; + private Deobfuscator m_destDeobfuscator; + private ClassEntry m_sourceClass; + private ClassEntry m_destClass; - /* TODO: see if we can use any of this here - public static doTheThings() { + public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - // get all the obf class names used in the mappings - Set usedClasses = Sets.newHashSet(); - for (String className : mappings.getAllObfClassNames()) { - usedClasses.add(new ClassEntry(className)); - } - System.out.println(String.format("Mappings reference %d/%d classes", - usedClasses.size(), sourceIndex.getObfClassEntries().size() - )); - - // get the used matches - Collection matches = matching.matches(); - Matches usedMatches = new Matches(); - for (ClassMatch match : matching.matches()) { - if (!match.intersectSourceClasses(usedClasses).isEmpty()) { - usedMatches.add(match); + m_matches = matches; + m_sourceDeobfuscator = sourceDeobfuscator; + m_destDeobfuscator = destDeobfuscator; + + // init frame + m_frame = new JFrame(Constants.Name); + final Container pane = m_frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + // init source side + JPanel sourcePanel = new JPanel(); + sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS)); + sourcePanel.setPreferredSize(new Dimension(200, 0)); + pane.add(sourcePanel, BorderLayout.WEST); + sourcePanel.add(new JLabel("Source Classes")); + + // init source type radios + JPanel sourceTypePanel = new JPanel(); + sourcePanel.add(sourceTypePanel); + sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); + ActionListener sourceTypeListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + setSourceType(SourceType.valueOf(event.getActionCommand())); } + }; + ButtonGroup sourceTypeButtons = new ButtonGroup(); + for (SourceType sourceType : SourceType.values()) { + sourceTypePanel.add(sourceType.newRadio(sourceTypeListener, sourceTypeButtons)); } - System.out.println(String.format("Mappings reference %d/%d match groups", - usedMatches.size(), matches.size() - )); - - // see what the used classes map to - BiMap uniqueUsedMatches = HashBiMap.create(); - Map ambiguousUsedMatches = Maps.newHashMap(); - Set unmatchedUsedClasses = Sets.newHashSet(); - for (ClassMatch match : matching.matches()) { - Set matchUsedClasses = match.intersectSourceClasses(usedClasses); - if (matchUsedClasses.isEmpty()) { - continue; + + m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); + m_sourceClasses.setListener(new ClassSelectionListener() { + @Override + public void onSelectClass(ClassEntry classEntry) { + setSourceClass(classEntry); } - - usedMatches.add(match); + }); + JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); + sourcePanel.add(sourceScroller); + + // init dest side + JPanel destPanel = new JPanel(); + destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS)); + destPanel.setPreferredSize(new Dimension(200, 0)); + pane.add(destPanel, BorderLayout.WEST); + destPanel.add(new JLabel("Destination Classes")); + + m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); + m_destClasses.setListener(new ClassSelectionListener() { + @Override + public void onSelectClass(ClassEntry classEntry) { + setDestClass(classEntry); + } + }); + JScrollPane destScroller = new JScrollPane(m_destClasses); + destPanel.add(destScroller); + + // init source panels + DefaultSyntaxKit.initKit(); + m_sourceReader = new JEditorPane(); + m_sourceReader.setEditable(false); + m_sourceReader.setContentType("text/java"); + m_destReader = new JEditorPane(); + m_destReader.setEditable(false); + m_destReader.setContentType("text/java"); + + // init all the splits + JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader)); + splitLeft.setResizeWeight(0); // let the right side take all the slack + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel); + splitRight.setResizeWeight(1); // let the left side take all the slack + JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight); + splitCenter.setResizeWeight(0.5); // resize 50:50 + pane.add(splitCenter, BorderLayout.CENTER); + splitCenter.resetToPreferredSizes(); + + // init bottom panel + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new FlowLayout()); + + m_sourceClassLabel = new JLabel(); + m_sourceClassLabel.setPreferredSize(new Dimension(300, 0)); + m_destClassLabel = new JLabel(); + m_destClassLabel.setPreferredSize(new Dimension(300, 0)); + + m_matchButton = new JButton(); + m_matchButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + onMatchClick(); + } + }); + m_matchButton.setPreferredSize(new Dimension(140, 24)); + + bottomPanel.add(m_sourceClassLabel); + bottomPanel.add(m_matchButton); + bottomPanel.add(m_destClassLabel); + pane.add(bottomPanel, BorderLayout.SOUTH); + + // show the frame + pane.doLayout(); + m_frame.setSize(1024, 576); + m_frame.setMinimumSize(new Dimension(640, 480)); + m_frame.setVisible(true); + m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + // init state + setSourceType(SourceType.getDefault()); + updateMatchButton(); + } - // classify the match - if (!match.isMatched()) { - // unmatched - unmatchedUsedClasses.addAll(matchUsedClasses); + protected void setSourceType(SourceType val) { + // show the source classes + m_sourceClasses.setClasses(deobfuscateClasses(val.getSourceClasses(m_matches), m_sourceDeobfuscator)); + } + + private Collection deobfuscateClasses(Collection in, Deobfuscator deobfuscator) { + List out = Lists.newArrayList(); + for (ClassEntry entry : in) { + out.add(deobfuscator.deobfuscateEntry(entry)); + } + return out; + } + + protected void setSourceClass(ClassEntry classEntry) { + + // update the current source class + m_sourceClass = classEntry; + m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : ""); + + if (m_sourceClass != null) { + + // show the dest class(es) + ClassMatch match = m_matches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); + assert(match != null); + if (match.destClasses.isEmpty()) { + m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator)); } else { - if (match.isAmbiguous()) { - // ambiguously matched - for (ClassEntry matchUsedClass : matchUsedClasses) { - ambiguousUsedMatches.put(matchUsedClass, match); - } - } else { - // uniquely matched - uniqueUsedMatches.put(match.getUniqueSource(), match.getUniqueDest()); - } + m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator)); } + m_destClasses.expandRow(0); } - // get unmatched dest classes - Set unmatchedDestClasses = Sets.newHashSet(); - for (ClassMatch match : matching.matches()) { - if (!match.isMatched()) { - unmatchedDestClasses.addAll(match.destClasses); - } + setDestClass(null); + readSource(m_sourceClass, m_sourceDeobfuscator, m_sourceReader); + + updateMatchButton(); + } + + private Collection getLikelyMatches(ClassEntry sourceClass) { + + ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); + + // set up identifiers + ClassNamer namer = new ClassNamer(m_matches.getUniqueMatches()); + ClassIdentifier sourceIdentifier = new ClassIdentifier( + m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), + namer.getSourceNamer(), true + ); + ClassIdentifier destIdentifier = new ClassIdentifier( + m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(), + namer.getDestNamer(), true + ); + + // rank all the unmatched dest classes against the source class + ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); + Multimap scoredDestClasses = ArrayListMultimap.create(); + for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) { + ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); + float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) + /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); + scoredDestClasses.put(score, unmatchedDestClass); } - // warn about the ambiguous used matches - if (ambiguousUsedMatches.size() > 0) { - System.out.println(String.format("%d source classes have ambiguous mappings", ambiguousUsedMatches.size())); - List ambiguousMatchesList = Lists.newArrayList(Sets.newHashSet(ambiguousUsedMatches.values())); - Collections.sort(ambiguousMatchesList, new Comparator() { - @Override - public int compare(ClassMatch a, ClassMatch b) { - String aName = a.sourceClasses.iterator().next().getName(); - String bName = b.sourceClasses.iterator().next().getName(); - return aName.compareTo(bName); + // sort by scores + List scores = new ArrayList(scoredDestClasses.keySet()); + Collections.sort(scores, Collections.reverseOrder()); + + // collect the scored classes in order + List scoredClasses = Lists.newArrayList(); + for (float score : scores) { + for (ClassEntry classEntry : scoredDestClasses.get(score)) { + scoredClasses.add(new DecoratedClassEntry(classEntry, String.format("%.0f%% ", score))); + if (scoredClasses.size() > 10) { + return scoredClasses; } - }); - for (ClassMatch match : ambiguousMatchesList) { - System.out.println("Ambiguous matching:"); - System.out.println("\tSource: " + getClassNames(match.sourceClasses)); - System.out.println("\tDest: " + getClassNames(match.destClasses)); } } + return scoredClasses; + } + + protected void setDestClass(ClassEntry classEntry) { - // warn about unmatched used classes - for (ClassEntry unmatchedUsedClass : unmatchedUsedClasses) { - System.out.println("No exact match for source class " + unmatchedUsedClass.getClassEntry()); - - // rank all the unmatched dest classes against the used class - ClassIdentity sourceIdentity = matching.getSourceIdentifier().identify(unmatchedUsedClass); - Multimap scoredDestClasses = ArrayListMultimap.create(); - for (ClassEntry unmatchedDestClass : unmatchedDestClasses) { - ClassIdentity destIdentity = matching.getDestIdentifier().identify(unmatchedDestClass); - scoredDestClasses.put(sourceIdentity.getMatchScore(destIdentity), unmatchedDestClass); - } - - List scores = new ArrayList(scoredDestClasses.keySet()); - Collections.sort(scores, Collections.reverseOrder()); - printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses); - } + // update the current source class + m_destClass = classEntry; + m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : ""); + + readSource(m_destClass, m_destDeobfuscator, m_destReader); + + updateMatchButton(); + } + + protected void readSource(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) { - // bail if there were unmatched classes - if (!unmatchedUsedClasses.isEmpty()) { - throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!"); + if (classEntry == null) { + reader.setText(null); + return; } + + reader.setText("(decompiling...)"); + + // run decompiler in a separate thread to keep ui responsive + new Thread() { + @Override + public void run() { + + // get the outermost class + ClassEntry obfClassEntry = deobfuscator.obfuscateEntry(classEntry); + List classChain = deobfuscator.getJarIndex().getObfClassChain(obfClassEntry); + ClassEntry obfOutermostClassEntry = classChain.get(0); + + // decompile it + reader.setText(deobfuscator.getSource(deobfuscator.getSourceTree(obfOutermostClassEntry.getName()))); + } + }.start(); } - private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { - int numScoredMatchesShown = 0; - for (int score : scores) { - for (ClassEntry classEntry : scoredMatches.get(score)) { - System.out.println(String.format("\tScore: %3d %3.0f%% %s", - score, 100.0 * score / maxScore, classEntry.getName() - )); - if (numScoredMatchesShown++ > 10) { - return; - } + private void updateMatchButton() { + + boolean twoSelected = m_sourceClass != null && m_destClass != null; + boolean isMatched = twoSelected && m_matches.getUniqueMatches().containsKey(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); + + m_matchButton.setEnabled(twoSelected); + if (twoSelected) { + if (isMatched) { + m_matchButton.setText("Unmatch"); + } else { + m_matchButton.setText("Match"); } + } else { + m_matchButton.setText(""); } } + protected void onMatchClick() { + // TODO + } + + /* private static List getClassNames(Collection classes) { List out = Lists.newArrayList(); for (ClassEntry c : classes) { -- cgit v1.2.3 From e8d6cb9e1ab61357ac26eb93a0a67917ded8a7b5 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 2 Mar 2015 01:41:28 -0500 Subject: added simple renamer for local variable table --- src/cuchaz/enigma/TranslatingTypeLoader.java | 2 ++ .../enigma/bytecode/LocalVariableRenamer.java | 32 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/cuchaz/enigma/bytecode/LocalVariableRenamer.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index 94ad6eb..7b57cfa 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -36,6 +36,7 @@ import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.bytecode.ClassRenamer; import cuchaz.enigma.bytecode.ClassTranslator; import cuchaz.enigma.bytecode.InnerClassWriter; +import cuchaz.enigma.bytecode.LocalVariableRenamer; import cuchaz.enigma.bytecode.MethodParameterWriter; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Translator; @@ -232,6 +233,7 @@ public class TranslatingTypeLoader implements ITypeLoader { // do all kinds of deobfuscating transformations on the class new BridgeMarker(m_jarIndex).markBridges(c); new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c); + new LocalVariableRenamer().rename(c); new ClassTranslator(m_deobfuscatingTranslator).translate(c); return c; diff --git a/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java new file mode 100644 index 0000000..53f207c --- /dev/null +++ b/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java @@ -0,0 +1,32 @@ +package cuchaz.enigma.bytecode; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.bytecode.ByteArray; +import javassist.bytecode.ConstPool; +import javassist.bytecode.LocalVariableAttribute; + + +public class LocalVariableRenamer { + + public void rename(CtClass c) { + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + + // if there's a local variable table, just rename everything to v1, v2, v3, ... for now + LocalVariableAttribute table = (LocalVariableAttribute)behavior.getMethodInfo().getCodeAttribute().getAttribute(LocalVariableAttribute.tag); + if (table == null) { + continue; + } + + ConstPool constants = c.getClassFile().getConstPool(); + for (int i=0; i 0 && destClasses.size() > 0; } diff --git a/src/cuchaz/enigma/convert/Matches.java b/src/cuchaz/enigma/convert/Matches.java index 5faa923..0b00b29 100644 --- a/src/cuchaz/enigma/convert/Matches.java +++ b/src/cuchaz/enigma/convert/Matches.java @@ -48,6 +48,22 @@ public class Matches implements Iterable { m_matches.add(match); indexMatch(match); } + + public void remove(ClassMatch match) { + for (ClassEntry sourceClass : match.sourceClasses) { + m_matchesBySource.remove(sourceClass); + m_uniqueMatches.remove(sourceClass); + m_ambiguousMatchesBySource.remove(sourceClass); + m_unmatchedSourceClasses.remove(sourceClass); + } + for (ClassEntry destClass : match.sourceClasses) { + m_matchesByDest.remove(destClass); + m_uniqueMatches.inverse().remove(destClass); + m_ambiguousMatchesByDest.remove(destClass); + m_unmatchedDestClasses.remove(destClass); + } + m_matches.remove(match); + } public int size() { return m_matches.size(); @@ -112,4 +128,26 @@ public class Matches implements Iterable { public ClassMatch getMatchByDest(ClassEntry destClass) { return m_matchesByDest.get(destClass); } + + public void removeSource(ClassEntry sourceClass) { + ClassMatch match = m_matchesBySource.get(sourceClass); + if (match != null) { + remove(match); + match.sourceClasses.remove(sourceClass); + if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { + add(match); + } + } + } + + public void removeDest(ClassEntry destClass) { + ClassMatch match = m_matchesByDest.get(destClass); + if (match != null) { + remove(match); + match.destClasses.remove(destClass); + if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { + add(match); + } + } + } } diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java index e9dff16..e2d517e 100644 --- a/src/cuchaz/enigma/gui/MatchingGui.java +++ b/src/cuchaz/enigma/gui/MatchingGui.java @@ -7,6 +7,7 @@ import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -25,6 +26,7 @@ import javax.swing.WindowConstants; import com.beust.jcommander.internal.Lists; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.BiMap; import com.google.common.collect.Multimap; import cuchaz.enigma.Constants; @@ -33,6 +35,7 @@ import cuchaz.enigma.convert.ClassIdentifier; import cuchaz.enigma.convert.ClassIdentity; import cuchaz.enigma.convert.ClassMatch; import cuchaz.enigma.convert.ClassNamer; +import cuchaz.enigma.convert.MappingsConverter; import cuchaz.enigma.convert.Matches; import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; import cuchaz.enigma.mapping.ClassEntry; @@ -79,6 +82,10 @@ public class MatchingGui { } } + public static interface SaveListener { + public void save(Matches matches); + } + // controls private JFrame m_frame; private ClassSelector m_sourceClasses; @@ -94,6 +101,8 @@ public class MatchingGui { private Deobfuscator m_destDeobfuscator; private ClassEntry m_sourceClass; private ClassEntry m_destClass; + private SourceType m_sourceType; + private SaveListener m_saveListener; public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { @@ -179,17 +188,13 @@ public class MatchingGui { bottomPanel.setLayout(new FlowLayout()); m_sourceClassLabel = new JLabel(); - m_sourceClassLabel.setPreferredSize(new Dimension(300, 0)); + m_sourceClassLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT); + m_sourceClassLabel.setPreferredSize(new Dimension(300, 24)); m_destClassLabel = new JLabel(); - m_destClassLabel.setPreferredSize(new Dimension(300, 0)); + m_destClassLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT); + m_destClassLabel.setPreferredSize(new Dimension(300, 24)); m_matchButton = new JButton(); - m_matchButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - onMatchClick(); - } - }); m_matchButton.setPreferredSize(new Dimension(140, 24)); bottomPanel.add(m_sourceClassLabel); @@ -205,13 +210,29 @@ public class MatchingGui { m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); // init state + updateMappings(); setSourceType(SourceType.getDefault()); updateMatchButton(); + m_saveListener = null; + } + + public void setSaveListener(SaveListener val) { + m_saveListener = val; + } + + private void updateMappings() { + m_destDeobfuscator.setMappings(MappingsConverter.newMappings( + m_matches, + m_sourceDeobfuscator.getMappings(), + m_sourceDeobfuscator, + m_destDeobfuscator + )); } protected void setSourceType(SourceType val) { // show the source classes - m_sourceClasses.setClasses(deobfuscateClasses(val.getSourceClasses(m_matches), m_sourceDeobfuscator)); + m_sourceType = val; + m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator)); } private Collection deobfuscateClasses(Collection in, Deobfuscator deobfuscator) { @@ -234,11 +255,24 @@ public class MatchingGui { ClassMatch match = m_matches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); assert(match != null); if (match.destClasses.isEmpty()) { - m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator)); + + m_destClasses.setClasses(null); + + // run in a separate thread to keep ui responsive + new Thread() { + @Override + public void run() { + m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator)); + m_destClasses.expandRow(0); + } + }.start(); + } else { + m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator)); + m_destClasses.expandRow(0); + } - m_destClasses.expandRow(0); } setDestClass(null); @@ -309,7 +343,7 @@ public class MatchingGui { reader.setText("(decompiling...)"); - // run decompiler in a separate thread to keep ui responsive + // run in a separate thread to keep ui responsive new Thread() { @Override public void run() { @@ -327,33 +361,98 @@ public class MatchingGui { private void updateMatchButton() { + ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); + ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); + + BiMap uniqueMatches = m_matches.getUniqueMatches(); boolean twoSelected = m_sourceClass != null && m_destClass != null; - boolean isMatched = twoSelected && m_matches.getUniqueMatches().containsKey(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); + boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); + boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest); - m_matchButton.setEnabled(twoSelected); + deactivateButton(m_matchButton); if (twoSelected) { if (isMatched) { - m_matchButton.setText("Unmatch"); - } else { - m_matchButton.setText("Match"); + activateButton(m_matchButton, "Unmatch", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + onUnmatchClick(); + } + }); + } else if (canMatch) { + activateButton(m_matchButton, "Match", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + onMatchClick(); + } + }); } - } else { - m_matchButton.setText(""); } } - protected void onMatchClick() { - // TODO + private void deactivateButton(JButton button) { + button.setEnabled(false); + button.setText(""); + for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + button.removeActionListener(listener); + } } - /* - private static List getClassNames(Collection classes) { - List out = Lists.newArrayList(); - for (ClassEntry c : classes) { - out.add(c.getName()); + private void activateButton(JButton button, String text, ActionListener newListener) { + button.setText(text); + button.setEnabled(true); + for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + button.removeActionListener(listener); + } + button.addActionListener(newListener); + } + + private void onMatchClick() { + // precondition: source and dest classes are set correctly + + ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); + ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); + + // remove the classes from their match + m_matches.removeSource(obfSource); + m_matches.removeDest(obfDest); + + // add them as matched classes + m_matches.add(new ClassMatch(obfSource, obfDest)); + + // TEMP + System.out.println("Match: " + obfSource + " <-> " + obfDest); + + //save(); + updateMappings(); + setDestClass(null); + m_destClasses.setClasses(null); + updateMatchButton(); + setSourceType(m_sourceType); + } + + private void onUnmatchClick() { + // precondition: source and dest classes are set to a unique match + + ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); + + // remove the source to break the match, then add the source back as unmatched + m_matches.removeSource(obfSource); + m_matches.add(new ClassMatch(obfSource, null)); + + // TEMP + System.out.println("Unmatch: " + obfSource + " <-> " + m_destDeobfuscator.obfuscateEntry(m_destClass)); + + //save(); + updateMappings(); + setDestClass(null); + m_destClasses.setClasses(null); + updateMatchButton(); + setSourceType(m_sourceType); + } + + private void save() { + if (m_saveListener != null) { + m_saveListener.save(m_matches); } - Collections.sort(out); - return out; } - */ } -- cgit v1.2.3 From d1a041362a164e4469a4b725608c631bd0961c2e Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 7 Mar 2015 20:54:44 -0500 Subject: ui improvements --- src/cuchaz/enigma/convert/ClassForest.java | 8 +- src/cuchaz/enigma/convert/ClassIdentifier.java | 6 +- src/cuchaz/enigma/convert/ClassMatching.java | 12 +- src/cuchaz/enigma/convert/MappingsConverter.java | 10 +- src/cuchaz/enigma/convert/Matches.java | 2 +- src/cuchaz/enigma/gui/ClassSelectorClassNode.java | 12 ++ .../enigma/gui/ClassSelectorPackageNode.java | 12 ++ src/cuchaz/enigma/gui/MatchingGui.java | 141 +++++++++++++++------ 8 files changed, 155 insertions(+), 48 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java index 3673139..a5ea056 100644 --- a/src/cuchaz/enigma/convert/ClassForest.java +++ b/src/cuchaz/enigma/convert/ClassForest.java @@ -24,8 +24,12 @@ public class ClassForest { } } - private void add(ClassEntry entry) { - m_forest.put(m_identifier.identify(entry), entry); + public void add(ClassEntry entry) { + try { + m_forest.put(m_identifier.identify(entry), entry); + } catch (ClassNotFoundException ex) { + throw new Error("Unable to find class " + entry.getName()); + } } public Collection identities() { diff --git a/src/cuchaz/enigma/convert/ClassIdentifier.java b/src/cuchaz/enigma/convert/ClassIdentifier.java index bdbf11b..da799cd 100644 --- a/src/cuchaz/enigma/convert/ClassIdentifier.java +++ b/src/cuchaz/enigma/convert/ClassIdentifier.java @@ -29,10 +29,14 @@ public class ClassIdentifier { m_cache = Maps.newHashMap(); } - public ClassIdentity identify(ClassEntry classEntry) { + public ClassIdentity identify(ClassEntry classEntry) + throws ClassNotFoundException { ClassIdentity identity = m_cache.get(classEntry); if (identity == null) { CtClass c = m_loader.loadClass(classEntry.getName()); + if (c == null) { + throw new ClassNotFoundException(classEntry.getName()); + } identity = new ClassIdentity(c, m_namer, m_index, m_useReferences); m_cache.put(classEntry, identity); } diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java index 9f93130..d8973ac 100644 --- a/src/cuchaz/enigma/convert/ClassMatching.java +++ b/src/cuchaz/enigma/convert/ClassMatching.java @@ -40,8 +40,16 @@ public class ClassMatching { } public void match(Iterable sourceClasses, Iterable destClasses) { - m_sourceClasses.addAll(sourceClasses); - m_destClasses.addAll(destClasses); + for (ClassEntry sourceClass : sourceClasses) { + if (!m_knownMatches.containsKey(sourceClass)) { + m_sourceClasses.add(sourceClass); + } + } + for (ClassEntry destClass : destClasses) { + if (!m_knownMatches.containsValue(destClass)) { + m_destClasses.add(destClass); + } + } } public Collection matches() { diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index aa067d4..f38723f 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -45,11 +45,11 @@ public class MappingsConverter { destIndex.indexJar(destJar, false); // compute the matching - ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex); + ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); return new Matches(matching.matches()); } - public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) { + public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap knownMatches) { System.out.println("Iteratively matching classes"); @@ -74,11 +74,15 @@ public class MappingsConverter { new ClassIdentifier(destJar, destIndex, destNamer, useReferences) ); + if (knownMatches != null) { + matching.addKnownMatches(knownMatches); + } + if (lastMatching == null) { // search all classes matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); } else { - // we already know about these matches + // we already know about these matches from last time matching.addKnownMatches(lastMatching.uniqueMatches()); // search unmatched and ambiguously-matched classes diff --git a/src/cuchaz/enigma/convert/Matches.java b/src/cuchaz/enigma/convert/Matches.java index 0b00b29..19bb155 100644 --- a/src/cuchaz/enigma/convert/Matches.java +++ b/src/cuchaz/enigma/convert/Matches.java @@ -56,7 +56,7 @@ public class Matches implements Iterable { m_ambiguousMatchesBySource.remove(sourceClass); m_unmatchedSourceClasses.remove(sourceClass); } - for (ClassEntry destClass : match.sourceClasses) { + for (ClassEntry destClass : match.destClasses) { m_matchesByDest.remove(destClass); m_uniqueMatches.inverse().remove(destClass); m_ambiguousMatchesByDest.remove(destClass); diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java index b3d7b89..f054188 100644 --- a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java +++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java @@ -35,4 +35,16 @@ public class ClassSelectorClassNode extends DefaultMutableTreeNode { } return m_classEntry.getSimpleName(); } + + @Override + public boolean equals(Object other) { + if (other instanceof ClassSelectorClassNode) { + return equals((ClassSelectorClassNode)other); + } + return false; + } + + public boolean equals(ClassSelectorClassNode other) { + return m_classEntry.equals(other.m_classEntry); + } } diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java index 451d380..5685abb 100644 --- a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java +++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java @@ -30,4 +30,16 @@ public class ClassSelectorPackageNode extends DefaultMutableTreeNode { public String toString() { return m_packageName; } + + @Override + public boolean equals(Object other) { + if (other instanceof ClassSelectorPackageNode) { + return equals((ClassSelectorPackageNode)other); + } + return false; + } + + public boolean equals(ClassSelectorPackageNode other) { + return m_packageName.equals(other.m_packageName); + } } diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java index e2d517e..04dbd7a 100644 --- a/src/cuchaz/enigma/gui/MatchingGui.java +++ b/src/cuchaz/enigma/gui/MatchingGui.java @@ -11,10 +11,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JLabel; @@ -22,9 +24,12 @@ import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSplitPane; +import javax.swing.SwingConstants; import javax.swing.WindowConstants; +import javax.swing.tree.TreePath; import com.beust.jcommander.internal.Lists; +import com.beust.jcommander.internal.Maps; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.Multimap; @@ -34,6 +39,7 @@ import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.convert.ClassIdentifier; import cuchaz.enigma.convert.ClassIdentity; import cuchaz.enigma.convert.ClassMatch; +import cuchaz.enigma.convert.ClassMatching; import cuchaz.enigma.convert.ClassNamer; import cuchaz.enigma.convert.MappingsConverter; import cuchaz.enigma.convert.Matches; @@ -95,6 +101,8 @@ public class MatchingGui { private JLabel m_sourceClassLabel; private JLabel m_destClassLabel; private JButton m_matchButton; + private Map m_sourceTypeButtons; + private JCheckBox m_advanceCheck; private Matches m_matches; private Deobfuscator m_sourceDeobfuscator; @@ -133,8 +141,11 @@ public class MatchingGui { } }; ButtonGroup sourceTypeButtons = new ButtonGroup(); + m_sourceTypeButtons = Maps.newHashMap(); for (SourceType sourceType : SourceType.values()) { - sourceTypePanel.add(sourceType.newRadio(sourceTypeListener, sourceTypeButtons)); + JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); + m_sourceTypeButtons.put(sourceType, button); + sourceTypePanel.add(button); } m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); @@ -164,6 +175,15 @@ public class MatchingGui { JScrollPane destScroller = new JScrollPane(m_destClasses); destPanel.add(destScroller); + JButton autoMatchButton = new JButton("AutoMatch"); + autoMatchButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + autoMatch(); + } + }); + destPanel.add(autoMatchButton); + // init source panels DefaultSyntaxKit.initKit(); m_sourceReader = new JEditorPane(); @@ -188,18 +208,21 @@ public class MatchingGui { bottomPanel.setLayout(new FlowLayout()); m_sourceClassLabel = new JLabel(); - m_sourceClassLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT); + m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT); m_sourceClassLabel.setPreferredSize(new Dimension(300, 24)); m_destClassLabel = new JLabel(); - m_destClassLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT); + m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT); m_destClassLabel.setPreferredSize(new Dimension(300, 24)); m_matchButton = new JButton(); m_matchButton.setPreferredSize(new Dimension(140, 24)); + m_advanceCheck = new JCheckBox("Advance to next likely match"); + bottomPanel.add(m_sourceClassLabel); bottomPanel.add(m_matchButton); bottomPanel.add(m_destClassLabel); + bottomPanel.add(m_advanceCheck); pane.add(bottomPanel, BorderLayout.SOUTH); // show the frame @@ -210,7 +233,7 @@ public class MatchingGui { m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); // init state - updateMappings(); + updateDestMappings(); setSourceType(SourceType.getDefault()); updateMatchButton(); m_saveListener = null; @@ -220,7 +243,7 @@ public class MatchingGui { m_saveListener = val; } - private void updateMappings() { + private void updateDestMappings() { m_destDeobfuscator.setMappings(MappingsConverter.newMappings( m_matches, m_sourceDeobfuscator.getMappings(), @@ -230,9 +253,18 @@ public class MatchingGui { } protected void setSourceType(SourceType val) { + // show the source classes m_sourceType = val; m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator)); + + // update counts + for (SourceType sourceType : SourceType.values()) { + m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", + sourceType.name(), + sourceType.getSourceClasses(m_matches).size() + )); + } } private Collection deobfuscateClasses(Collection in, Deobfuscator deobfuscator) { @@ -296,31 +328,37 @@ public class MatchingGui { namer.getDestNamer(), true ); - // rank all the unmatched dest classes against the source class - ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); - Multimap scoredDestClasses = ArrayListMultimap.create(); - for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) { - ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); - float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) - /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); - scoredDestClasses.put(score, unmatchedDestClass); - } + try { + + // rank all the unmatched dest classes against the source class + ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); + Multimap scoredDestClasses = ArrayListMultimap.create(); + for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) { + ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); + float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) + /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); + scoredDestClasses.put(score, unmatchedDestClass); + } - // sort by scores - List scores = new ArrayList(scoredDestClasses.keySet()); - Collections.sort(scores, Collections.reverseOrder()); - - // collect the scored classes in order - List scoredClasses = Lists.newArrayList(); - for (float score : scores) { - for (ClassEntry classEntry : scoredDestClasses.get(score)) { - scoredClasses.add(new DecoratedClassEntry(classEntry, String.format("%.0f%% ", score))); - if (scoredClasses.size() > 10) { - return scoredClasses; + // sort by scores + List scores = new ArrayList(scoredDestClasses.keySet()); + Collections.sort(scores, Collections.reverseOrder()); + + // collect the scored classes in order + List scoredClasses = Lists.newArrayList(); + for (float score : scores) { + for (ClassEntry classEntry : scoredDestClasses.get(score)) { + scoredClasses.add(new DecoratedClassEntry(classEntry, String.format("%2.0f%% ", score))); + if (scoredClasses.size() > 10) { + return scoredClasses; + } } } + return scoredClasses; + + } catch (ClassNotFoundException ex) { + throw new Error("Unable to find class " + ex.getMessage()); } - return scoredClasses; } protected void setDestClass(ClassEntry classEntry) { @@ -419,15 +457,18 @@ public class MatchingGui { // add them as matched classes m_matches.add(new ClassMatch(obfSource, obfDest)); - // TEMP - System.out.println("Match: " + obfSource + " <-> " + obfDest); + // remember where we were in the source tree + TreePath path = m_sourceClasses.getSelectionPath(); - //save(); - updateMappings(); - setDestClass(null); - m_destClasses.setClasses(null); - updateMatchButton(); - setSourceType(m_sourceType); + save(); + updateMatches(); + + // put the tree back to where it was + m_sourceClasses.expandPath(path); + + if (m_advanceCheck.isSelected()) { + + } } private void onUnmatchClick() { @@ -439,11 +480,12 @@ public class MatchingGui { m_matches.removeSource(obfSource); m_matches.add(new ClassMatch(obfSource, null)); - // TEMP - System.out.println("Unmatch: " + obfSource + " <-> " + m_destDeobfuscator.obfuscateEntry(m_destClass)); - - //save(); - updateMappings(); + save(); + updateMatches(); + } + + private void updateMatches() { + updateDestMappings(); setDestClass(null); m_destClasses.setClasses(null); updateMatchButton(); @@ -455,4 +497,25 @@ public class MatchingGui { m_saveListener.save(m_matches); } } + + private void autoMatch() { + + System.out.println("Automatching..."); + + // compute a new matching + ClassMatching matching = MappingsConverter.computeMatching( + m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), + m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(), + m_matches.getUniqueMatches() + ); + Matches newMatches = new Matches(matching.matches()); + System.out.println(String.format("Automatch found %d new matches", + newMatches.getUniqueMatches().size() - m_matches.getUniqueMatches().size() + )); + + // update the current matches + m_matches = newMatches; + save(); + updateMatches(); + } } -- cgit v1.2.3 From 42b2f3e6cf1e5c5c25055e1c05b083d099542b7a Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 8 Mar 2015 11:17:52 -0400 Subject: Closing branch: default --- src/cuchaz/enigma/gui/MatchingGui.java | 42 +++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 8 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java index 04dbd7a..f1da25a 100644 --- a/src/cuchaz/enigma/gui/MatchingGui.java +++ b/src/cuchaz/enigma/gui/MatchingGui.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; import java.util.List; import java.util.Map; @@ -26,6 +27,7 @@ import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.SwingConstants; import javax.swing.WindowConstants; +import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import com.beust.jcommander.internal.Lists; @@ -276,7 +278,7 @@ public class MatchingGui { } protected void setSourceClass(ClassEntry classEntry) { - + // update the current source class m_sourceClass = classEntry; m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : ""); @@ -457,17 +459,11 @@ public class MatchingGui { // add them as matched classes m_matches.add(new ClassMatch(obfSource, obfDest)); - // remember where we were in the source tree - TreePath path = m_sourceClasses.getSelectionPath(); - save(); updateMatches(); - // put the tree back to where it was - m_sourceClasses.expandPath(path); - if (m_advanceCheck.isSelected()) { - + advance(); } } @@ -489,7 +485,33 @@ public class MatchingGui { setDestClass(null); m_destClasses.setClasses(null); updateMatchButton(); + + // remember where we were in the source tree + String packageName = null; + if (!m_sourceClasses.isSelectionEmpty()) { + packageName = m_sourceClasses.getSelectionPath().getParentPath().getLastPathComponent().toString(); + } + setSourceType(m_sourceType); + + if (packageName != null) { + // find the corresponding path in the new tree + TreePath path = null; + DefaultMutableTreeNode root = (DefaultMutableTreeNode)m_sourceClasses.getModel().getRoot(); + Enumeration children = root.children(); + while (children.hasMoreElements()) { + Object child = children.nextElement(); + if (child.toString().equals(packageName)) { + path = new TreePath(new Object[] {root, child}); + break; + } + } + + if (path != null) { + // put the tree back to where it was + m_sourceClasses.expandPath(path); + } + } } private void save() { @@ -518,4 +540,8 @@ public class MatchingGui { save(); updateMatches(); } + + private void advance() { + // TODO: find a likely match + } } -- cgit v1.2.3 From 61eb14f65e73a9b3d0ea6eca6b04da804a4ff61b Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 8 Mar 2015 13:49:44 -0400 Subject: lots of small tweaks and improvements --- src/cuchaz/enigma/analysis/SourceIndex.java | 4 + src/cuchaz/enigma/convert/ClassIdentity.java | 19 +- src/cuchaz/enigma/gui/ClassSelector.java | 142 +++++++++++++-- src/cuchaz/enigma/gui/ClassSelectorClassNode.java | 4 +- src/cuchaz/enigma/gui/DecoratedClassEntry.java | 20 --- src/cuchaz/enigma/gui/Gui.java | 50 +----- src/cuchaz/enigma/gui/GuiTricks.java | 58 ++++++ src/cuchaz/enigma/gui/MatchingGui.java | 208 +++++++++++++++------- src/cuchaz/enigma/gui/ScoredClassEntry.java | 20 +++ 9 files changed, 378 insertions(+), 147 deletions(-) delete mode 100644 src/cuchaz/enigma/gui/DecoratedClassEntry.java create mode 100644 src/cuchaz/enigma/gui/ScoredClassEntry.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java index e31b803..b3fb751 100644 --- a/src/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/cuchaz/enigma/analysis/SourceIndex.java @@ -146,6 +146,10 @@ public class SourceIndex { return m_declarationToToken.values(); } + public Iterable declarations() { + return m_declarationToToken.keySet(); + } + public Token getDeclarationToken(Entry deobfEntry) { return m_declarationToToken.get(deobfEntry); } diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index 3736a53..d07e0a4 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -69,6 +69,7 @@ public class ClassIdentity { private Multiset m_implements; private Multiset m_implementations; private Multiset m_references; + private String m_outer; private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() { @@ -167,6 +168,8 @@ public class ClassIdentity { } } } + + m_outer = EntryFactory.getClassEntry(c).getOuterClassName(); } private void addReference(EntryReference reference) { @@ -234,6 +237,9 @@ public class ClassIdentity { buf.append(reference); buf.append("\n"); } + buf.append("\touter "); + buf.append(m_outer); + buf.append("\n"); return buf.toString(); } @@ -402,13 +408,15 @@ public class ClassIdentity { } public int getMatchScore(ClassIdentity other) { - return getNumMatches(m_fields, other.m_fields) + return 2*getNumMatches(m_extends, other.m_extends) + + 2*getNumMatches(m_outer, other.m_outer) + + getNumMatches(m_fields, other.m_fields) + getNumMatches(m_methods, other.m_methods) + getNumMatches(m_constructors, other.m_constructors); } public int getMaxMatchScore() { - return m_fields.size() + m_methods.size() + m_constructors.size(); + return 2 + 2 + m_fields.size() + m_methods.size() + m_constructors.size(); } public boolean matches(CtClass c) { @@ -427,4 +435,11 @@ public class ClassIdentity { } return numMatches; } + + private int getNumMatches(String a, String b) { + if (a.equals(b)) { + return 1; + } + return 0; + } } diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java index e5f550b..2a63675 100644 --- a/src/cuchaz/enigma/gui/ClassSelector.java +++ b/src/cuchaz/enigma/gui/ClassSelector.java @@ -15,6 +15,7 @@ import java.awt.event.MouseEvent; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Enumeration; import java.util.List; import java.util.Map; @@ -41,19 +42,12 @@ public class ClassSelector extends JTree { public static Comparator ObfuscatedClassEntryComparator; public static Comparator DeobfuscatedClassEntryComparator; - private static String getClassEntryDisplayName(ClassEntry entry) { - if (entry instanceof DecoratedClassEntry) { - return ((DecoratedClassEntry)entry).getDecoration() + entry.getName(); - } - return entry.getName(); - } - static { ObfuscatedClassEntryComparator = new Comparator() { @Override public int compare(ClassEntry a, ClassEntry b) { - String aname = getClassEntryDisplayName(a); - String bname = getClassEntryDisplayName(b); + String aname = a.getName(); + String bname = a.getName(); if (aname.length() != bname.length()) { return aname.length() - bname.length(); } @@ -64,9 +58,13 @@ public class ClassSelector extends JTree { DeobfuscatedClassEntryComparator = new Comparator() { @Override public int compare(ClassEntry a, ClassEntry b) { - String aname = getClassEntryDisplayName(a); - String bname = getClassEntryDisplayName(b); - return aname.compareTo(bname); + if (a instanceof ScoredClassEntry && b instanceof ScoredClassEntry) { + return Float.compare( + ((ScoredClassEntry)b).getScore(), + ((ScoredClassEntry)a).getScore() + ); + } + return a.getName().compareTo(b.getName()); } }; } @@ -172,4 +170,124 @@ public class ClassSelector extends JTree { // finally, update the tree control setModel(new DefaultTreeModel(root)); } + + public ClassEntry getSelectedClass() { + if (!isSelectionEmpty()) { + Object selectedNode = getSelectionPath().getLastPathComponent(); + if (selectedNode instanceof ClassSelectorClassNode) { + ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode; + return classNode.getClassEntry(); + } + } + return null; + } + + public String getSelectedPackage() { + if (!isSelectionEmpty()) { + Object selectedNode = getSelectionPath().getLastPathComponent(); + if (selectedNode instanceof ClassSelectorPackageNode) { + ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)selectedNode; + return packageNode.getPackageName(); + } else if (selectedNode instanceof ClassSelectorClassNode) { + ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode; + return classNode.getClassEntry().getPackageName(); + } + } + return null; + } + + public Iterable packageNodes() { + List nodes = Lists.newArrayList(); + DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot(); + Enumeration children = root.children(); + while (children.hasMoreElements()) { + ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)children.nextElement(); + nodes.add(packageNode); + } + return nodes; + } + + public Iterable classNodes(ClassSelectorPackageNode packageNode) { + List nodes = Lists.newArrayList(); + Enumeration children = packageNode.children(); + while (children.hasMoreElements()) { + ClassSelectorClassNode classNode = (ClassSelectorClassNode)children.nextElement(); + nodes.add(classNode); + } + return nodes; + } + + public void expandPackage(String packageName) { + if (packageName == null) { + return; + } + for (ClassSelectorPackageNode packageNode : packageNodes()) { + if (packageNode.getPackageName().equals(packageName)) { + expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode})); + return; + } + } + } + + public void expandAll() { + for (ClassSelectorPackageNode packageNode : packageNodes()) { + expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode})); + } + } + + public ClassEntry getFirstClass() { + for (ClassSelectorPackageNode packageNode : packageNodes()) { + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + return classNode.getClassEntry(); + } + } + return null; + } + + public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { + for (ClassSelectorPackageNode packageNode : packageNodes()) { + if (packageNode.getPackageName().equals(entry.getPackageName())) { + return packageNode; + } + } + return null; + } + + public ClassEntry getNextClass(ClassEntry entry) { + boolean foundIt = false; + for (ClassSelectorPackageNode packageNode : packageNodes()) { + if (!foundIt) { + // skip to the package with our target in it + if (packageNode.getPackageName().equals(entry.getPackageName())) { + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + if (!foundIt) { + if (classNode.getClassEntry().equals(entry)) { + foundIt = true; + } + } else { + // return the next class + return classNode.getClassEntry(); + } + } + } + } else { + // return the next class + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + return classNode.getClassEntry(); + } + } + } + return null; + } + + public void setSelectionClass(ClassEntry classEntry) { + expandPackage(classEntry.getPackageName()); + for (ClassSelectorPackageNode packageNode : packageNodes()) { + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + if (classNode.getClassEntry().equals(classEntry)) { + setSelectionPath(new TreePath(new Object[] {getModel().getRoot(), packageNode, classNode})); + } + } + } + } } diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java index f054188..6c1c9a0 100644 --- a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java +++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java @@ -30,8 +30,8 @@ public class ClassSelectorClassNode extends DefaultMutableTreeNode { @Override public String toString() { - if (m_classEntry instanceof DecoratedClassEntry) { - return ((DecoratedClassEntry)m_classEntry).getDecoration() + m_classEntry.getSimpleName(); + if (m_classEntry instanceof ScoredClassEntry) { + return String.format("%d%% %s", (int)((ScoredClassEntry)m_classEntry).getScore(), m_classEntry.getSimpleName()); } return m_classEntry.getSimpleName(); } diff --git a/src/cuchaz/enigma/gui/DecoratedClassEntry.java b/src/cuchaz/enigma/gui/DecoratedClassEntry.java deleted file mode 100644 index dd8b4fa..0000000 --- a/src/cuchaz/enigma/gui/DecoratedClassEntry.java +++ /dev/null @@ -1,20 +0,0 @@ -package cuchaz.enigma.gui; - -import cuchaz.enigma.mapping.ClassEntry; - - -public class DecoratedClassEntry extends ClassEntry { - - private static final long serialVersionUID = -8798725308554217105L; - - private String m_decoration; - - public DecoratedClassEntry(ClassEntry other, String decoration) { - super(other); - m_decoration = decoration; - } - - public String getDecoration() { - return m_decoration; - } -} diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java index a214243..38a6ee9 100644 --- a/src/cuchaz/enigma/gui/Gui.java +++ b/src/cuchaz/enigma/gui/Gui.java @@ -16,7 +16,6 @@ import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; -import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; @@ -54,8 +53,6 @@ import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; -import javax.swing.SwingUtilities; -import javax.swing.Timer; import javax.swing.WindowConstants; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; @@ -740,52 +737,7 @@ public class Gui { if (token == null) { throw new IllegalArgumentException("Token cannot be null!"); } - - // set the caret position to the token - m_editor.setCaretPosition(token.start); - m_editor.grabFocus(); - - try { - // make sure the token is visible in the scroll window - Rectangle start = m_editor.modelToView(token.start); - Rectangle end = m_editor.modelToView(token.end); - final Rectangle show = start.union(end); - show.grow(start.width * 10, start.height * 6); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - m_editor.scrollRectToVisible(show); - } - }); - } catch (BadLocationException ex) { - throw new Error(ex); - } - - // highlight the token momentarily - final Timer timer = new Timer(200, new ActionListener() { - private int m_counter = 0; - private Object m_highlight = null; - - @Override - public void actionPerformed(ActionEvent event) { - if (m_counter % 2 == 0) { - try { - m_highlight = m_editor.getHighlighter().addHighlight(token.start, token.end, m_selectionHighlightPainter); - } catch (BadLocationException ex) { - // don't care - } - } else if (m_highlight != null) { - m_editor.getHighlighter().removeHighlight(m_highlight); - } - - if (m_counter++ > 6) { - Timer timer = (Timer)event.getSource(); - timer.stop(); - } - } - }); - timer.start(); - + GuiTricks.navigateToToken(m_editor, token, m_selectionHighlightPainter); redraw(); } diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java index df9e221..5a3a01d 100644 --- a/src/cuchaz/enigma/gui/GuiTricks.java +++ b/src/cuchaz/enigma/gui/GuiTricks.java @@ -11,11 +11,21 @@ package cuchaz.enigma.gui; import java.awt.Font; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import javax.swing.JComponent; +import javax.swing.JEditorPane; import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import javax.swing.Timer; import javax.swing.ToolTipManager; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter.HighlightPainter; + +import cuchaz.enigma.analysis.Token; public class GuiTricks { @@ -33,4 +43,52 @@ public class GuiTricks { manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); manager.setInitialDelay(oldDelay); } + + public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { + + // set the caret position to the token + editor.setCaretPosition(token.start); + editor.grabFocus(); + + try { + // make sure the token is visible in the scroll window + Rectangle start = editor.modelToView(token.start); + Rectangle end = editor.modelToView(token.end); + final Rectangle show = start.union(end); + show.grow(start.width * 10, start.height * 6); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + editor.scrollRectToVisible(show); + } + }); + } catch (BadLocationException ex) { + throw new Error(ex); + } + + // highlight the token momentarily + final Timer timer = new Timer(200, new ActionListener() { + private int m_counter = 0; + private Object m_highlight = null; + + @Override + public void actionPerformed(ActionEvent event) { + if (m_counter % 2 == 0) { + try { + m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); + } catch (BadLocationException ex) { + // don't care + } + } else if (m_highlight != null) { + editor.getHighlighter().removeHighlight(m_highlight); + } + + if (m_counter++ > 6) { + Timer timer = (Timer)event.getSource(); + timer.stop(); + } + } + }); + timer.start(); + } } diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java index f1da25a..1e618d0 100644 --- a/src/cuchaz/enigma/gui/MatchingGui.java +++ b/src/cuchaz/enigma/gui/MatchingGui.java @@ -10,7 +10,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Enumeration; import java.util.List; import java.util.Map; @@ -27,17 +26,18 @@ import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.SwingConstants; import javax.swing.WindowConstants; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreePath; import com.beust.jcommander.internal.Lists; import com.beust.jcommander.internal.Maps; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.Multimap; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; import cuchaz.enigma.Constants; import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.analysis.SourceIndex; +import cuchaz.enigma.analysis.Token; import cuchaz.enigma.convert.ClassIdentifier; import cuchaz.enigma.convert.ClassIdentity; import cuchaz.enigma.convert.ClassMatch; @@ -47,6 +47,7 @@ import cuchaz.enigma.convert.MappingsConverter; import cuchaz.enigma.convert.Matches; import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; import de.sciss.syntaxpane.DefaultSyntaxKit; @@ -105,6 +106,7 @@ public class MatchingGui { private JButton m_matchButton; private Map m_sourceTypeButtons; private JCheckBox m_advanceCheck; + private SelectionHighlightPainter m_selectionHighlightPainter; private Matches m_matches; private Deobfuscator m_sourceDeobfuscator; @@ -188,12 +190,8 @@ public class MatchingGui { // init source panels DefaultSyntaxKit.initKit(); - m_sourceReader = new JEditorPane(); - m_sourceReader.setEditable(false); - m_sourceReader.setContentType("text/java"); - m_destReader = new JEditorPane(); - m_destReader.setEditable(false); - m_destReader.setContentType("text/java"); + m_sourceReader = makeReader(); + m_destReader = makeReader(); // init all the splits JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader)); @@ -220,6 +218,14 @@ public class MatchingGui { m_matchButton.setPreferredSize(new Dimension(140, 24)); m_advanceCheck = new JCheckBox("Advance to next likely match"); + m_advanceCheck.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + if (m_advanceCheck.isSelected()) { + advance(); + } + } + }); bottomPanel.add(m_sourceClassLabel); bottomPanel.add(m_matchButton); @@ -234,6 +240,8 @@ public class MatchingGui { m_frame.setVisible(true); m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + m_selectionHighlightPainter = new SelectionHighlightPainter(); + // init state updateDestMappings(); setSourceType(SourceType.getDefault()); @@ -241,6 +249,19 @@ public class MatchingGui { m_saveListener = null; } + private JEditorPane makeReader() { + + JEditorPane reader = new JEditorPane(); + reader.setEditable(false); + reader.setContentType("text/java"); + + // turn off token highlighting (it's wrong most of the time anyway...) + DefaultSyntaxKit kit = (DefaultSyntaxKit)reader.getEditorKit(); + kit.toggleComponent(reader, "de.sciss.syntaxpane.components.TokenMarker"); + + return reader; + } + public void setSaveListener(SaveListener val) { m_saveListener = val; } @@ -272,12 +293,24 @@ public class MatchingGui { private Collection deobfuscateClasses(Collection in, Deobfuscator deobfuscator) { List out = Lists.newArrayList(); for (ClassEntry entry : in) { - out.add(deobfuscator.deobfuscateEntry(entry)); + + ClassEntry deobf = deobfuscator.deobfuscateEntry(entry); + + // make sure we preserve any scores + if (entry instanceof ScoredClassEntry) { + deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry)entry).getScore()); + } + + out.add(deobf); } return out; } protected void setSourceClass(ClassEntry classEntry) { + setSourceClass(classEntry, null); + } + + protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) { // update the current source class m_sourceClass = classEntry; @@ -297,20 +330,27 @@ public class MatchingGui { @Override public void run() { m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator)); - m_destClasses.expandRow(0); + m_destClasses.expandAll(); + + if (onGetDestClasses != null) { + onGetDestClasses.run(); + } } }.start(); } else { m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator)); - m_destClasses.expandRow(0); + m_destClasses.expandAll(); + if (onGetDestClasses != null) { + onGetDestClasses.run(); + } } } setDestClass(null); - readSource(m_sourceClass, m_sourceDeobfuscator, m_sourceReader); + decompileClass(m_sourceClass, m_sourceDeobfuscator, m_sourceReader); updateMatchButton(); } @@ -334,29 +374,14 @@ public class MatchingGui { // rank all the unmatched dest classes against the source class ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); - Multimap scoredDestClasses = ArrayListMultimap.create(); + List scoredDestClasses = Lists.newArrayList(); for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) { ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); - scoredDestClasses.put(score, unmatchedDestClass); - } - - // sort by scores - List scores = new ArrayList(scoredDestClasses.keySet()); - Collections.sort(scores, Collections.reverseOrder()); - - // collect the scored classes in order - List scoredClasses = Lists.newArrayList(); - for (float score : scores) { - for (ClassEntry classEntry : scoredDestClasses.get(score)) { - scoredClasses.add(new DecoratedClassEntry(classEntry, String.format("%2.0f%% ", score))); - if (scoredClasses.size() > 10) { - return scoredClasses; - } - } + scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score)); } - return scoredClasses; + return scoredDestClasses; } catch (ClassNotFoundException ex) { throw new Error("Unable to find class " + ex.getMessage()); @@ -369,12 +394,12 @@ public class MatchingGui { m_destClass = classEntry; m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : ""); - readSource(m_destClass, m_destDeobfuscator, m_destReader); + decompileClass(m_destClass, m_destDeobfuscator, m_destReader); updateMatchButton(); } - protected void readSource(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) { + protected void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) { if (classEntry == null) { reader.setText(null); @@ -389,12 +414,37 @@ public class MatchingGui { public void run() { // get the outermost class - ClassEntry obfClassEntry = deobfuscator.obfuscateEntry(classEntry); - List classChain = deobfuscator.getJarIndex().getObfClassChain(obfClassEntry); - ClassEntry obfOutermostClassEntry = classChain.get(0); + ClassEntry outermostClassEntry = classEntry; + while (outermostClassEntry.isInnerClass()) { + outermostClassEntry = outermostClassEntry.getOuterClassEntry(); + } // decompile it - reader.setText(deobfuscator.getSource(deobfuscator.getSourceTree(obfOutermostClassEntry.getName()))); + CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); + String source = deobfuscator.getSource(sourceTree); + reader.setText(source); + SourceIndex sourceIndex = deobfuscator.getSourceIndex(sourceTree, source); + + // navigate to the class declaration + Token token = sourceIndex.getDeclarationToken(classEntry); + if (token == null) { + // couldn't find the class declaration token, might be an anonymous class + // look for any declaration in that class instead + for (Entry entry : sourceIndex.declarations()) { + if (entry.getClassEntry().equals(classEntry)) { + token = sourceIndex.getDeclarationToken(entry); + break; + } + } + } + + if (token != null) { + GuiTricks.navigateToToken(reader, token, m_selectionHighlightPainter); + } else { + // couldn't find anything =( + System.out.println("Unable to find declaration in source for " + classEntry); + } + } }.start(); } @@ -459,11 +509,16 @@ public class MatchingGui { // add them as matched classes m_matches.add(new ClassMatch(obfSource, obfDest)); + ClassEntry nextClass = null; + if (m_advanceCheck.isSelected()) { + nextClass = m_sourceClasses.getNextClass(m_sourceClass); + } + save(); updateMatches(); - if (m_advanceCheck.isSelected()) { - advance(); + if (nextClass != null) { + advance(nextClass); } } @@ -487,31 +542,11 @@ public class MatchingGui { updateMatchButton(); // remember where we were in the source tree - String packageName = null; - if (!m_sourceClasses.isSelectionEmpty()) { - packageName = m_sourceClasses.getSelectionPath().getParentPath().getLastPathComponent().toString(); - } + String packageName = m_sourceClasses.getSelectedPackage(); setSourceType(m_sourceType); - if (packageName != null) { - // find the corresponding path in the new tree - TreePath path = null; - DefaultMutableTreeNode root = (DefaultMutableTreeNode)m_sourceClasses.getModel().getRoot(); - Enumeration children = root.children(); - while (children.hasMoreElements()) { - Object child = children.nextElement(); - if (child.toString().equals(packageName)) { - path = new TreePath(new Object[] {root, child}); - break; - } - } - - if (path != null) { - // put the tree back to where it was - m_sourceClasses.expandPath(path); - } - } + m_sourceClasses.expandPackage(packageName); } private void save() { @@ -542,6 +577,55 @@ public class MatchingGui { } private void advance() { - // TODO: find a likely match + advance(null); + } + + private void advance(ClassEntry sourceClass) { + + // make sure we have a source class + if (sourceClass == null) { + sourceClass = m_sourceClasses.getSelectedClass(); + if (sourceClass != null) { + sourceClass = m_sourceClasses.getNextClass(sourceClass); + } else { + sourceClass = m_sourceClasses.getFirstClass(); + } + } + + // set the source class + setSourceClass(sourceClass, new Runnable() { + @Override + public void run() { + + // then, pick the best dest class + ClassEntry firstClass = null; + ScoredClassEntry bestDestClass = null; + for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) { + for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) { + if (firstClass == null) { + firstClass = classNode.getClassEntry(); + } + if (classNode.getClassEntry() instanceof ScoredClassEntry) { + ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry(); + if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) { + bestDestClass = scoredClass; + } + } + } + } + + // pick the entry to show + ClassEntry destClass = null; + if (bestDestClass != null) { + destClass = bestDestClass; + } else if (firstClass != null) { + destClass = firstClass; + } + + setDestClass(destClass); + m_destClasses.setSelectionClass(destClass); + } + }); + m_sourceClasses.setSelectionClass(sourceClass); } } diff --git a/src/cuchaz/enigma/gui/ScoredClassEntry.java b/src/cuchaz/enigma/gui/ScoredClassEntry.java new file mode 100644 index 0000000..dd7ba61 --- /dev/null +++ b/src/cuchaz/enigma/gui/ScoredClassEntry.java @@ -0,0 +1,20 @@ +package cuchaz.enigma.gui; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class ScoredClassEntry extends ClassEntry { + + private static final long serialVersionUID = -8798725308554217105L; + + private float m_score; + + public ScoredClassEntry(ClassEntry other, float score) { + super(other); + m_score = score; + } + + public float getScore() { + return m_score; + } +} -- cgit v1.2.3 From 4ceb8d490058e48df666bf7227ce020e60928be5 Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 8 Mar 2015 20:48:30 -0400 Subject: more tweaks, improvements, and bug fixes --- src/cuchaz/enigma/ConvertMain.java | 22 +++++-- src/cuchaz/enigma/Deobfuscator.java | 25 ++++++-- src/cuchaz/enigma/convert/ClassIdentity.java | 25 +++++++- src/cuchaz/enigma/convert/MappingsConverter.java | 60 +++++++++++++++--- src/cuchaz/enigma/gui/MatchingGui.java | 79 ++++++++++++++---------- src/cuchaz/enigma/mapping/ArgumentMapping.java | 5 ++ src/cuchaz/enigma/mapping/ClassMapping.java | 11 ++++ src/cuchaz/enigma/mapping/FieldMapping.java | 26 ++++++++ src/cuchaz/enigma/mapping/MethodMapping.java | 16 ++++- src/cuchaz/enigma/mapping/Signature.java | 5 ++ src/cuchaz/enigma/mapping/Type.java | 13 ++++ 11 files changed, 233 insertions(+), 54 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index cad49f5..975cdcc 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -2,6 +2,7 @@ package cuchaz.enigma; import java.io.File; import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.util.jar.JarFile; @@ -14,6 +15,7 @@ import cuchaz.enigma.gui.MatchingGui.SaveListener; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsReader; +import cuchaz.enigma.mapping.MappingsWriter; public class ConvertMain { @@ -32,7 +34,7 @@ public class ConvertMain { //computeMatches(matchingFile, sourceJar, destJar, mappings); editMatches(matchingFile, sourceJar, destJar, mappings); - //convertMappings(outMappingsFile, mappings, matchingFile); + //convertMappings(outMappingsFile, sourceJar, destJar, mappings, matchingFile); /* TODO // write out the converted mappings @@ -56,7 +58,7 @@ public class ConvertMain { Matches matches = MatchesReader.read(matchingFile); System.out.println("Indexing source jar..."); Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); - sourceDeobfuscator.setMappings(mappings); + sourceDeobfuscator.setMappings(mappings, false); System.out.println("Indexing dest jar..."); Deobfuscator destDeobfuscator = new Deobfuscator(destJar); System.out.println("Starting GUI..."); @@ -72,9 +74,21 @@ public class ConvertMain { }); } - private static void convertMappings(File outMappingsFile, Mappings mappings, File matchingFile) + private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File matchingFile) throws IOException { + System.out.println("Reading matches..."); Matches matches = MatchesReader.read(matchingFile); - MappingsConverter.convertMappings(mappings, matches.getUniqueMatches()); + System.out.println("Indexing source jar..."); + Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); + sourceDeobfuscator.setMappings(mappings); + System.out.println("Indexing dest jar..."); + Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + + Mappings newMappings = MappingsConverter.newMappings(matches, mappings, sourceDeobfuscator, destDeobfuscator); + + try (FileWriter out = new FileWriter(outMappingsFile)) { + new MappingsWriter().write(out, newMappings); + } + System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath()); } } diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index e5d0e3d..9b0d3db 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -116,6 +116,10 @@ public class Deobfuscator { } public void setMappings(Mappings val) { + setMappings(val, true); + } + + public void setMappings(Mappings val, boolean warnAboutDrops) { if (val == null) { val = new Mappings(); } @@ -123,7 +127,10 @@ public class Deobfuscator { // drop mappings that don't match the jar RelatedMethodChecker relatedMethodChecker = new RelatedMethodChecker(m_jarIndex); for (ClassMapping classMapping : Lists.newArrayList(val.classes())) { - if (!checkClassMapping(relatedMethodChecker, classMapping)) { + if (!checkClassMapping(relatedMethodChecker, classMapping, warnAboutDrops)) { + if (warnAboutDrops) { + System.err.println("WARNING: unable to find class " + classMapping.getObfFullName() + ". dropping mapping"); + } val.removeClassMapping(classMapping); } } @@ -138,7 +145,7 @@ public class Deobfuscator { m_translatorCache.clear(); } - private boolean checkClassMapping(RelatedMethodChecker relatedMethodChecker, ClassMapping classMapping) { + private boolean checkClassMapping(RelatedMethodChecker relatedMethodChecker, ClassMapping classMapping, boolean warnAboutDrops) { // check the class ClassEntry classEntry = EntryFactory.getObfClassEntry(m_jarIndex, classMapping); @@ -150,7 +157,9 @@ public class Deobfuscator { for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { FieldEntry fieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName(), fieldMapping.getObfType()); if (!m_jarIndex.containsObfField(fieldEntry)) { - System.err.println("WARNING: unable to find field " + fieldEntry + ". dropping mapping."); + if (warnAboutDrops) { + System.err.println("WARNING: unable to find field " + fieldEntry + ". dropping mapping."); + } classMapping.removeFieldMapping(fieldMapping); } } @@ -159,7 +168,9 @@ public class Deobfuscator { for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); if (!m_jarIndex.containsObfBehavior(obfBehaviorEntry)) { - System.err.println("WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping."); + if (warnAboutDrops) { + System.err.println("WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping."); + } classMapping.removeMethodMapping(methodMapping); } @@ -168,8 +179,10 @@ public class Deobfuscator { // check inner classes for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) { - if (!checkClassMapping(relatedMethodChecker, innerClassMapping)) { - System.err.println("WARNING: unable to find inner class " + EntryFactory.getObfClassEntry(m_jarIndex, classMapping) + ". dropping mapping."); + if (!checkClassMapping(relatedMethodChecker, innerClassMapping, warnAboutDrops)) { + if (warnAboutDrops) { + System.err.println("WARNING: unable to find inner class " + EntryFactory.getObfClassEntry(m_jarIndex, classMapping) + ". dropping mapping."); + } classMapping.removeInnerClassMapping(innerClassMapping); } } diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index d07e0a4..35667b0 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -16,6 +16,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Enumeration; import java.util.List; import java.util.Map; +import java.util.Set; import javassist.CannotCompileException; import javassist.CtBehavior; @@ -38,6 +39,7 @@ import com.google.common.collect.HashMultiset; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multiset; +import com.google.common.collect.Sets; import cuchaz.enigma.Constants; import cuchaz.enigma.Util; @@ -67,6 +69,7 @@ public class ClassIdentity { private String m_staticInitializer; private String m_extends; private Multiset m_implements; + private Set m_stringLiterals; private Multiset m_implementations; private Multiset m_references; private String m_outer; @@ -140,6 +143,14 @@ public class ClassIdentity { m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName))); } + m_stringLiterals = Sets.newHashSet(); + ConstPool constants = c.getClassFile().getConstPool(); + for (int i=1; i a, Set b) { + int numMatches = 0; + for (String val : a) { + if (b.contains(val)) { + numMatches++; + } + } + return numMatches; + } + private int getNumMatches(Multiset a, Multiset b) { int numMatches = 0; for (String val : a) { diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index f38723f..5883878 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -30,7 +30,10 @@ import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; +import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MethodMapping; public class MappingsConverter { @@ -129,15 +132,20 @@ public class MappingsConverter { for (Entry match : matchesByDestChainSize.get(chainSize)) { // get class info - ClassEntry sourceClassEntry = match.getKey(); - ClassEntry deobfClassEntry = sourceDeobfuscator.deobfuscateEntry(sourceClassEntry); - ClassEntry destClassEntry = match.getValue(); - List destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(destClassEntry); + ClassEntry obfSourceClassEntry = match.getKey(); + ClassEntry obfDestClassEntry = match.getValue(); + List destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry); + + ClassMapping sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry); + if (sourceMapping == null) { + // if this class was never deobfuscated, don't try to match it + continue; + } // find out where to make the dest class mapping if (destClassChain.size() == 1) { // not an inner class, add directly to mappings - newMappings.addClassMapping(new ClassMapping(destClassEntry.getName(), deobfClassEntry.getName())); + newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false)); } else { // inner class, find the outer class mapping ClassMapping destMapping = null; @@ -157,14 +165,52 @@ public class MappingsConverter { } } } - String deobfName = deobfClassEntry.isInnerClass() ? deobfClassEntry.getInnerClassName() : deobfClassEntry.getSimpleName(); - destMapping.addInnerClassMapping(new ClassMapping(destClassEntry.getName(), deobfName)); + destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true)); } } } return newMappings; } + private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping mapping, final Matches matches, boolean useSimpleName) { + + ClassNameReplacer replacer = new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className)); + if (newClassEntry != null) { + return newClassEntry.getName(); + } + return null; + } + }; + + ClassMapping newMapping; + String deobfName = mapping.getDeobfName(); + if (deobfName != null) { + if (useSimpleName) { + deobfName = new ClassEntry(deobfName).getSimpleName(); + } + newMapping = new ClassMapping(newObfClass.getName(), deobfName); + } else { + newMapping = new ClassMapping(newObfClass.getName()); + } + + // copy fields + for (FieldMapping fieldMapping : mapping.fields()) { + // TODO: map field obf names too... + newMapping.addFieldMapping(new FieldMapping(fieldMapping, replacer)); + } + + // copy methods + for (MethodMapping methodMapping : mapping.methods()) { + // TODO: map method obf names too... + newMapping.addMethodMapping(new MethodMapping(methodMapping, replacer)); + } + + return newMapping; + } + public static void convertMappings(Mappings mappings, BiMap changes) { // sort the changes so classes are renamed in the correct order diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java index 1e618d0..85842c1 100644 --- a/src/cuchaz/enigma/gui/MatchingGui.java +++ b/src/cuchaz/enigma/gui/MatchingGui.java @@ -6,10 +6,8 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -29,9 +27,7 @@ import javax.swing.WindowConstants; import com.beust.jcommander.internal.Lists; import com.beust.jcommander.internal.Maps; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; -import com.google.common.collect.Multimap; import com.strobel.decompiler.languages.java.ast.CompilationUnit; import cuchaz.enigma.Constants; @@ -272,7 +268,7 @@ public class MatchingGui { m_sourceDeobfuscator.getMappings(), m_sourceDeobfuscator, m_destDeobfuscator - )); + ), false); } protected void setSourceType(SourceType val) { @@ -307,7 +303,18 @@ public class MatchingGui { } protected void setSourceClass(ClassEntry classEntry) { - setSourceClass(classEntry, null); + + Runnable onGetDestClasses = null; + if (m_advanceCheck.isSelected()) { + onGetDestClasses = new Runnable() { + @Override + public void run() { + pickBestDestClass(); + } + }; + } + + setSourceClass(classEntry, onGetDestClasses); } protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) { @@ -596,36 +603,40 @@ public class MatchingGui { setSourceClass(sourceClass, new Runnable() { @Override public void run() { - - // then, pick the best dest class - ClassEntry firstClass = null; - ScoredClassEntry bestDestClass = null; - for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) { - for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) { - if (firstClass == null) { - firstClass = classNode.getClassEntry(); - } - if (classNode.getClassEntry() instanceof ScoredClassEntry) { - ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry(); - if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) { - bestDestClass = scoredClass; - } - } - } - } - - // pick the entry to show - ClassEntry destClass = null; - if (bestDestClass != null) { - destClass = bestDestClass; - } else if (firstClass != null) { - destClass = firstClass; - } - - setDestClass(destClass); - m_destClasses.setSelectionClass(destClass); + pickBestDestClass(); } }); m_sourceClasses.setSelectionClass(sourceClass); } + + private void pickBestDestClass() { + + // then, pick the best dest class + ClassEntry firstClass = null; + ScoredClassEntry bestDestClass = null; + for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) { + for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) { + if (firstClass == null) { + firstClass = classNode.getClassEntry(); + } + if (classNode.getClassEntry() instanceof ScoredClassEntry) { + ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry(); + if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) { + bestDestClass = scoredClass; + } + } + } + } + + // pick the entry to show + ClassEntry destClass = null; + if (bestDestClass != null) { + destClass = bestDestClass; + } else if (firstClass != null) { + destClass = firstClass; + } + + setDestClass(destClass); + m_destClasses.setSelectionClass(destClass); + } } diff --git a/src/cuchaz/enigma/mapping/ArgumentMapping.java b/src/cuchaz/enigma/mapping/ArgumentMapping.java index f4d8e77..9f366a0 100644 --- a/src/cuchaz/enigma/mapping/ArgumentMapping.java +++ b/src/cuchaz/enigma/mapping/ArgumentMapping.java @@ -25,6 +25,11 @@ public class ArgumentMapping implements Serializable, Comparable { } } + // rename field types + for (FieldMapping fieldMapping : new ArrayList(m_fieldsByObf.values())) { + String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) { + boolean wasRemoved = m_fieldsByObf.remove(oldFieldKey) != null; + assert (wasRemoved); + boolean wasAdded = m_fieldsByObf.put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null; + assert (wasAdded); + } + } + // rename method signatures for (MethodMapping methodMapping : new ArrayList(m_methodsByObf.values())) { String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java index 14b20dd..55b0a19 100644 --- a/src/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/cuchaz/enigma/mapping/FieldMapping.java @@ -26,6 +26,12 @@ public class FieldMapping implements Serializable, Comparable { m_obfType = obfType; } + public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) { + m_obfName = other.m_obfName; + m_deobfName = other.m_deobfName; + m_obfType = new Type(other.m_obfType, obfClassNameReplacer); + } + public String getObfName() { return m_obfName; } @@ -46,4 +52,24 @@ public class FieldMapping implements Serializable, Comparable { public int compareTo(FieldMapping other) { return (m_obfName + m_obfType).compareTo(other.m_obfName + other.m_obfType); } + + public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { + + // rename obf classes in the type + Type newType = new Type(m_obfType, new ClassNameReplacer() { + @Override + public String replace(String className) { + if (className.equals(oldObfClassName)) { + return newObfClassName; + } + return null; + } + }); + + if (!newType.equals(m_obfType)) { + m_obfType = newType; + return true; + } + return false; + } } diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java index 1704428..bf8a94f 100644 --- a/src/cuchaz/enigma/mapping/MethodMapping.java +++ b/src/cuchaz/enigma/mapping/MethodMapping.java @@ -12,7 +12,9 @@ package cuchaz.enigma.mapping; import java.io.Serializable; import java.util.Map; -import java.util.TreeMap; +import java.util.Map.Entry; + +import com.google.common.collect.Maps; public class MethodMapping implements Serializable, Comparable { @@ -37,9 +39,19 @@ public class MethodMapping implements Serializable, Comparable { m_obfName = obfName; m_deobfName = NameValidator.validateMethodName(deobfName); m_obfSignature = obfSignature; - m_arguments = new TreeMap(); + m_arguments = Maps.newTreeMap(); } + public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) { + m_obfName = other.m_obfName; + m_deobfName = other.m_deobfName; + m_obfSignature = new Signature(other.m_obfSignature, obfClassNameReplacer); + m_arguments = Maps.newTreeMap(); + for (Entry entry : other.m_arguments.entrySet()) { + m_arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue())); + } + } + public String getObfName() { return m_obfName; } diff --git a/src/cuchaz/enigma/mapping/Signature.java b/src/cuchaz/enigma/mapping/Signature.java index 273a77b..ea83e40 100644 --- a/src/cuchaz/enigma/mapping/Signature.java +++ b/src/cuchaz/enigma/mapping/Signature.java @@ -39,6 +39,11 @@ public class Signature implements Serializable { } } + public Signature(Signature other) { + m_argumentTypes = Lists.newArrayList(other.m_argumentTypes); + m_returnType = new Type(other.m_returnType); + } + public Signature(Signature other, ClassNameReplacer replacer) { m_argumentTypes = Lists.newArrayList(other.m_argumentTypes); for (int i=0; i mapping : checker.getDroppedClassMappings().entrySet()) { + System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); + } + for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { + System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); + } + for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { + System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); + } + for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { + System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); } } // check for related method inconsistencies - if (relatedMethodChecker.hasProblems()) { - throw new Error("Related methods are inconsistent! Need to fix the mappings manually.\n" + relatedMethodChecker.getReport()); + if (checker.getRelatedMethodChecker().hasProblems()) { + throw new Error("Related methods are inconsistent! Need to fix the mappings manually.\n" + checker.getRelatedMethodChecker().getReport()); } m_mappings = val; @@ -145,51 +150,6 @@ public class Deobfuscator { m_translatorCache.clear(); } - private boolean checkClassMapping(RelatedMethodChecker relatedMethodChecker, ClassMapping classMapping, boolean warnAboutDrops) { - - // check the class - ClassEntry classEntry = EntryFactory.getObfClassEntry(m_jarIndex, classMapping); - if (!m_jarIndex.getObfClassEntries().contains(classEntry)) { - return false; - } - - // check the fields - for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { - FieldEntry fieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName(), fieldMapping.getObfType()); - if (!m_jarIndex.containsObfField(fieldEntry)) { - if (warnAboutDrops) { - System.err.println("WARNING: unable to find field " + fieldEntry + ". dropping mapping."); - } - classMapping.removeFieldMapping(fieldMapping); - } - } - - // check methods - for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { - BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); - if (!m_jarIndex.containsObfBehavior(obfBehaviorEntry)) { - if (warnAboutDrops) { - System.err.println("WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping."); - } - classMapping.removeMethodMapping(methodMapping); - } - - relatedMethodChecker.checkMethod(classEntry, methodMapping); - } - - // check inner classes - for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) { - if (!checkClassMapping(relatedMethodChecker, innerClassMapping, warnAboutDrops)) { - if (warnAboutDrops) { - System.err.println("WARNING: unable to find inner class " + EntryFactory.getObfClassEntry(m_jarIndex, classMapping) + ". dropping mapping."); - } - classMapping.removeInnerClassMapping(innerClassMapping); - } - } - - return true; - } - public Translator getTranslator(TranslationDirection direction) { Translator translator = m_translatorCache.get(direction); if (translator == null) { diff --git a/src/cuchaz/enigma/gui/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java new file mode 100644 index 0000000..ff7cda9 --- /dev/null +++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java @@ -0,0 +1,661 @@ +package cuchaz.enigma.gui; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.SwingConstants; +import javax.swing.WindowConstants; + +import com.beust.jcommander.internal.Lists; +import com.beust.jcommander.internal.Maps; +import com.google.common.collect.BiMap; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.analysis.SourceIndex; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.convert.ClassIdentifier; +import cuchaz.enigma.convert.ClassIdentity; +import cuchaz.enigma.convert.ClassMatch; +import cuchaz.enigma.convert.ClassMatching; +import cuchaz.enigma.convert.ClassNamer; +import cuchaz.enigma.convert.MappingsConverter; +import cuchaz.enigma.convert.Matches; +import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsChecker; +import de.sciss.syntaxpane.DefaultSyntaxKit; + + +public class ClassMatchingGui { + + private static enum SourceType { + Matched { + + @Override + public Collection getSourceClasses(Matches matches) { + return matches.getUniqueMatches().keySet(); + } + }, + Unmatched { + + @Override + public Collection getSourceClasses(Matches matches) { + return matches.getUnmatchedSourceClasses(); + } + }, + Ambiguous { + + @Override + public Collection getSourceClasses(Matches matches) { + return matches.getAmbiguouslyMatchedSourceClasses(); + } + }; + + public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { + JRadioButton button = new JRadioButton(name(), this == getDefault()); + button.setActionCommand(name()); + button.addActionListener(listener); + group.add(button); + return button; + } + + public abstract Collection getSourceClasses(Matches matches); + + public static SourceType getDefault() { + return values()[0]; + } + } + + public static interface SaveListener { + public void save(Matches matches); + } + + // controls + private JFrame m_frame; + private ClassSelector m_sourceClasses; + private ClassSelector m_destClasses; + private JEditorPane m_sourceReader; + private JEditorPane m_destReader; + private JLabel m_sourceClassLabel; + private JLabel m_destClassLabel; + private JButton m_matchButton; + private Map m_sourceTypeButtons; + private JCheckBox m_advanceCheck; + private SelectionHighlightPainter m_selectionHighlightPainter; + + private Matches m_matches; + private Deobfuscator m_sourceDeobfuscator; + private Deobfuscator m_destDeobfuscator; + private ClassEntry m_sourceClass; + private ClassEntry m_destClass; + private SourceType m_sourceType; + private SaveListener m_saveListener; + + public ClassMatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + + m_matches = matches; + m_sourceDeobfuscator = sourceDeobfuscator; + m_destDeobfuscator = destDeobfuscator; + + // init frame + m_frame = new JFrame(Constants.Name); + final Container pane = m_frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + // init source side + JPanel sourcePanel = new JPanel(); + sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS)); + sourcePanel.setPreferredSize(new Dimension(200, 0)); + pane.add(sourcePanel, BorderLayout.WEST); + sourcePanel.add(new JLabel("Source Classes")); + + // init source type radios + JPanel sourceTypePanel = new JPanel(); + sourcePanel.add(sourceTypePanel); + sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); + ActionListener sourceTypeListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + setSourceType(SourceType.valueOf(event.getActionCommand())); + } + }; + ButtonGroup sourceTypeButtons = new ButtonGroup(); + m_sourceTypeButtons = Maps.newHashMap(); + for (SourceType sourceType : SourceType.values()) { + JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); + m_sourceTypeButtons.put(sourceType, button); + sourceTypePanel.add(button); + } + + m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); + m_sourceClasses.setListener(new ClassSelectionListener() { + @Override + public void onSelectClass(ClassEntry classEntry) { + setSourceClass(classEntry); + } + }); + JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); + sourcePanel.add(sourceScroller); + + // init dest side + JPanel destPanel = new JPanel(); + destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS)); + destPanel.setPreferredSize(new Dimension(200, 0)); + pane.add(destPanel, BorderLayout.WEST); + destPanel.add(new JLabel("Destination Classes")); + + m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); + m_destClasses.setListener(new ClassSelectionListener() { + @Override + public void onSelectClass(ClassEntry classEntry) { + setDestClass(classEntry); + } + }); + JScrollPane destScroller = new JScrollPane(m_destClasses); + destPanel.add(destScroller); + + JButton autoMatchButton = new JButton("AutoMatch"); + autoMatchButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + autoMatch(); + } + }); + destPanel.add(autoMatchButton); + + // init source panels + DefaultSyntaxKit.initKit(); + m_sourceReader = makeReader(); + m_destReader = makeReader(); + + // init all the splits + JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader)); + splitLeft.setResizeWeight(0); // let the right side take all the slack + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel); + splitRight.setResizeWeight(1); // let the left side take all the slack + JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight); + splitCenter.setResizeWeight(0.5); // resize 50:50 + pane.add(splitCenter, BorderLayout.CENTER); + splitCenter.resetToPreferredSizes(); + + // init bottom panel + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new FlowLayout()); + + m_sourceClassLabel = new JLabel(); + m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT); + m_sourceClassLabel.setPreferredSize(new Dimension(300, 24)); + m_destClassLabel = new JLabel(); + m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT); + m_destClassLabel.setPreferredSize(new Dimension(300, 24)); + + m_matchButton = new JButton(); + m_matchButton.setPreferredSize(new Dimension(140, 24)); + + m_advanceCheck = new JCheckBox("Advance to next likely match"); + m_advanceCheck.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + if (m_advanceCheck.isSelected()) { + advance(); + } + } + }); + + bottomPanel.add(m_sourceClassLabel); + bottomPanel.add(m_matchButton); + bottomPanel.add(m_destClassLabel); + bottomPanel.add(m_advanceCheck); + pane.add(bottomPanel, BorderLayout.SOUTH); + + // show the frame + pane.doLayout(); + m_frame.setSize(1024, 576); + m_frame.setMinimumSize(new Dimension(640, 480)); + m_frame.setVisible(true); + m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + m_selectionHighlightPainter = new SelectionHighlightPainter(); + + // init state + updateDestMappings(); + setSourceType(SourceType.getDefault()); + updateMatchButton(); + m_saveListener = null; + } + + private JEditorPane makeReader() { + + JEditorPane reader = new JEditorPane(); + reader.setEditable(false); + reader.setContentType("text/java"); + + // turn off token highlighting (it's wrong most of the time anyway...) + DefaultSyntaxKit kit = (DefaultSyntaxKit)reader.getEditorKit(); + kit.toggleComponent(reader, "de.sciss.syntaxpane.components.TokenMarker"); + + return reader; + } + + public void setSaveListener(SaveListener val) { + m_saveListener = val; + } + + private void updateDestMappings() { + + Mappings newMappings = MappingsConverter.newMappings( + m_matches, + m_sourceDeobfuscator.getMappings(), + m_sourceDeobfuscator, + m_destDeobfuscator + ); + + // look for dropped mappings + MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex()); + checker.dropBrokenMappings(newMappings); + + // count them + int numDroppedFields = checker.getDroppedFieldMappings().size(); + int numDroppedMethods = checker.getDroppedMethodMappings().size(); + System.out.println(String.format( + "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods", + numDroppedFields + numDroppedMethods, + numDroppedFields, + numDroppedMethods + )); + + m_destDeobfuscator.setMappings(newMappings); + } + + protected void setSourceType(SourceType val) { + + // show the source classes + m_sourceType = val; + m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator)); + + // update counts + for (SourceType sourceType : SourceType.values()) { + m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", + sourceType.name(), + sourceType.getSourceClasses(m_matches).size() + )); + } + } + + private Collection deobfuscateClasses(Collection in, Deobfuscator deobfuscator) { + List out = Lists.newArrayList(); + for (ClassEntry entry : in) { + + ClassEntry deobf = deobfuscator.deobfuscateEntry(entry); + + // make sure we preserve any scores + if (entry instanceof ScoredClassEntry) { + deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry)entry).getScore()); + } + + out.add(deobf); + } + return out; + } + + protected void setSourceClass(ClassEntry classEntry) { + + Runnable onGetDestClasses = null; + if (m_advanceCheck.isSelected()) { + onGetDestClasses = new Runnable() { + @Override + public void run() { + pickBestDestClass(); + } + }; + } + + setSourceClass(classEntry, onGetDestClasses); + } + + protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) { + + // update the current source class + m_sourceClass = classEntry; + m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : ""); + + if (m_sourceClass != null) { + + // show the dest class(es) + ClassMatch match = m_matches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); + assert(match != null); + if (match.destClasses.isEmpty()) { + + m_destClasses.setClasses(null); + + // run in a separate thread to keep ui responsive + new Thread() { + @Override + public void run() { + m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator)); + m_destClasses.expandAll(); + + if (onGetDestClasses != null) { + onGetDestClasses.run(); + } + } + }.start(); + + } else { + + m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator)); + m_destClasses.expandAll(); + + if (onGetDestClasses != null) { + onGetDestClasses.run(); + } + } + } + + setDestClass(null); + decompileClass(m_sourceClass, m_sourceDeobfuscator, m_sourceReader); + + updateMatchButton(); + } + + private Collection getLikelyMatches(ClassEntry sourceClass) { + + ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); + + // set up identifiers + ClassNamer namer = new ClassNamer(m_matches.getUniqueMatches()); + ClassIdentifier sourceIdentifier = new ClassIdentifier( + m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), + namer.getSourceNamer(), true + ); + ClassIdentifier destIdentifier = new ClassIdentifier( + m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(), + namer.getDestNamer(), true + ); + + try { + + // rank all the unmatched dest classes against the source class + ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); + List scoredDestClasses = Lists.newArrayList(); + for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) { + ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); + float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) + /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); + scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score)); + } + return scoredDestClasses; + + } catch (ClassNotFoundException ex) { + throw new Error("Unable to find class " + ex.getMessage()); + } + } + + protected void setDestClass(ClassEntry classEntry) { + + // update the current source class + m_destClass = classEntry; + m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : ""); + + decompileClass(m_destClass, m_destDeobfuscator, m_destReader); + + updateMatchButton(); + } + + protected void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) { + + if (classEntry == null) { + reader.setText(null); + return; + } + + reader.setText("(decompiling...)"); + + // run in a separate thread to keep ui responsive + new Thread() { + @Override + public void run() { + + // get the outermost class + ClassEntry outermostClassEntry = classEntry; + while (outermostClassEntry.isInnerClass()) { + outermostClassEntry = outermostClassEntry.getOuterClassEntry(); + } + + // decompile it + CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); + String source = deobfuscator.getSource(sourceTree); + reader.setText(source); + SourceIndex sourceIndex = deobfuscator.getSourceIndex(sourceTree, source); + + // navigate to the class declaration + Token token = sourceIndex.getDeclarationToken(classEntry); + if (token == null) { + // couldn't find the class declaration token, might be an anonymous class + // look for any declaration in that class instead + for (Entry entry : sourceIndex.declarations()) { + if (entry.getClassEntry().equals(classEntry)) { + token = sourceIndex.getDeclarationToken(entry); + break; + } + } + } + + if (token != null) { + GuiTricks.navigateToToken(reader, token, m_selectionHighlightPainter); + } else { + // couldn't find anything =( + System.out.println("Unable to find declaration in source for " + classEntry); + } + + } + }.start(); + } + + private void updateMatchButton() { + + ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); + ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); + + BiMap uniqueMatches = m_matches.getUniqueMatches(); + boolean twoSelected = m_sourceClass != null && m_destClass != null; + boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); + boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest); + + deactivateButton(m_matchButton); + if (twoSelected) { + if (isMatched) { + activateButton(m_matchButton, "Unmatch", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + onUnmatchClick(); + } + }); + } else if (canMatch) { + activateButton(m_matchButton, "Match", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + onMatchClick(); + } + }); + } + } + } + + private void deactivateButton(JButton button) { + button.setEnabled(false); + button.setText(""); + for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + button.removeActionListener(listener); + } + } + + private void activateButton(JButton button, String text, ActionListener newListener) { + button.setText(text); + button.setEnabled(true); + for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + button.removeActionListener(listener); + } + button.addActionListener(newListener); + } + + private void onMatchClick() { + // precondition: source and dest classes are set correctly + + ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); + ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); + + // remove the classes from their match + m_matches.removeSource(obfSource); + m_matches.removeDest(obfDest); + + // add them as matched classes + m_matches.add(new ClassMatch(obfSource, obfDest)); + + ClassEntry nextClass = null; + if (m_advanceCheck.isSelected()) { + nextClass = m_sourceClasses.getNextClass(m_sourceClass); + } + + save(); + updateMatches(); + + if (nextClass != null) { + advance(nextClass); + } + } + + private void onUnmatchClick() { + // precondition: source and dest classes are set to a unique match + + ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); + + // remove the source to break the match, then add the source back as unmatched + m_matches.removeSource(obfSource); + m_matches.add(new ClassMatch(obfSource, null)); + + save(); + updateMatches(); + } + + private void updateMatches() { + updateDestMappings(); + setDestClass(null); + m_destClasses.setClasses(null); + updateMatchButton(); + + // remember where we were in the source tree + String packageName = m_sourceClasses.getSelectedPackage(); + + setSourceType(m_sourceType); + + m_sourceClasses.expandPackage(packageName); + } + + private void save() { + if (m_saveListener != null) { + m_saveListener.save(m_matches); + } + } + + private void autoMatch() { + + System.out.println("Automatching..."); + + // compute a new matching + ClassMatching matching = MappingsConverter.computeMatching( + m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), + m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(), + m_matches.getUniqueMatches() + ); + Matches newMatches = new Matches(matching.matches()); + System.out.println(String.format("Automatch found %d new matches", + newMatches.getUniqueMatches().size() - m_matches.getUniqueMatches().size() + )); + + // update the current matches + m_matches = newMatches; + save(); + updateMatches(); + } + + private void advance() { + advance(null); + } + + private void advance(ClassEntry sourceClass) { + + // make sure we have a source class + if (sourceClass == null) { + sourceClass = m_sourceClasses.getSelectedClass(); + if (sourceClass != null) { + sourceClass = m_sourceClasses.getNextClass(sourceClass); + } else { + sourceClass = m_sourceClasses.getFirstClass(); + } + } + + // set the source class + setSourceClass(sourceClass, new Runnable() { + @Override + public void run() { + pickBestDestClass(); + } + }); + m_sourceClasses.setSelectionClass(sourceClass); + } + + private void pickBestDestClass() { + + // then, pick the best dest class + ClassEntry firstClass = null; + ScoredClassEntry bestDestClass = null; + for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) { + for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) { + if (firstClass == null) { + firstClass = classNode.getClassEntry(); + } + if (classNode.getClassEntry() instanceof ScoredClassEntry) { + ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry(); + if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) { + bestDestClass = scoredClass; + } + } + } + } + + // pick the entry to show + ClassEntry destClass = null; + if (bestDestClass != null) { + destClass = bestDestClass; + } else if (firstClass != null) { + destClass = firstClass; + } + + setDestClass(destClass); + m_destClasses.setSelectionClass(destClass); + } +} diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java deleted file mode 100644 index 85842c1..0000000 --- a/src/cuchaz/enigma/gui/MatchingGui.java +++ /dev/null @@ -1,642 +0,0 @@ -package cuchaz.enigma.gui; - -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JEditorPane; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.SwingConstants; -import javax.swing.WindowConstants; - -import com.beust.jcommander.internal.Lists; -import com.beust.jcommander.internal.Maps; -import com.google.common.collect.BiMap; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; - -import cuchaz.enigma.Constants; -import cuchaz.enigma.Deobfuscator; -import cuchaz.enigma.analysis.SourceIndex; -import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.convert.ClassIdentifier; -import cuchaz.enigma.convert.ClassIdentity; -import cuchaz.enigma.convert.ClassMatch; -import cuchaz.enigma.convert.ClassMatching; -import cuchaz.enigma.convert.ClassNamer; -import cuchaz.enigma.convert.MappingsConverter; -import cuchaz.enigma.convert.Matches; -import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.Entry; -import de.sciss.syntaxpane.DefaultSyntaxKit; - - -public class MatchingGui { - - private static enum SourceType { - Matched { - - @Override - public Collection getSourceClasses(Matches matches) { - return matches.getUniqueMatches().keySet(); - } - }, - Unmatched { - - @Override - public Collection getSourceClasses(Matches matches) { - return matches.getUnmatchedSourceClasses(); - } - }, - Ambiguous { - - @Override - public Collection getSourceClasses(Matches matches) { - return matches.getAmbiguouslyMatchedSourceClasses(); - } - }; - - public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { - JRadioButton button = new JRadioButton(name(), this == getDefault()); - button.setActionCommand(name()); - button.addActionListener(listener); - group.add(button); - return button; - } - - public abstract Collection getSourceClasses(Matches matches); - - public static SourceType getDefault() { - return values()[0]; - } - } - - public static interface SaveListener { - public void save(Matches matches); - } - - // controls - private JFrame m_frame; - private ClassSelector m_sourceClasses; - private ClassSelector m_destClasses; - private JEditorPane m_sourceReader; - private JEditorPane m_destReader; - private JLabel m_sourceClassLabel; - private JLabel m_destClassLabel; - private JButton m_matchButton; - private Map m_sourceTypeButtons; - private JCheckBox m_advanceCheck; - private SelectionHighlightPainter m_selectionHighlightPainter; - - private Matches m_matches; - private Deobfuscator m_sourceDeobfuscator; - private Deobfuscator m_destDeobfuscator; - private ClassEntry m_sourceClass; - private ClassEntry m_destClass; - private SourceType m_sourceType; - private SaveListener m_saveListener; - - public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - - m_matches = matches; - m_sourceDeobfuscator = sourceDeobfuscator; - m_destDeobfuscator = destDeobfuscator; - - // init frame - m_frame = new JFrame(Constants.Name); - final Container pane = m_frame.getContentPane(); - pane.setLayout(new BorderLayout()); - - // init source side - JPanel sourcePanel = new JPanel(); - sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS)); - sourcePanel.setPreferredSize(new Dimension(200, 0)); - pane.add(sourcePanel, BorderLayout.WEST); - sourcePanel.add(new JLabel("Source Classes")); - - // init source type radios - JPanel sourceTypePanel = new JPanel(); - sourcePanel.add(sourceTypePanel); - sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); - ActionListener sourceTypeListener = new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - setSourceType(SourceType.valueOf(event.getActionCommand())); - } - }; - ButtonGroup sourceTypeButtons = new ButtonGroup(); - m_sourceTypeButtons = Maps.newHashMap(); - for (SourceType sourceType : SourceType.values()) { - JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); - m_sourceTypeButtons.put(sourceType, button); - sourceTypePanel.add(button); - } - - m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); - m_sourceClasses.setListener(new ClassSelectionListener() { - @Override - public void onSelectClass(ClassEntry classEntry) { - setSourceClass(classEntry); - } - }); - JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); - sourcePanel.add(sourceScroller); - - // init dest side - JPanel destPanel = new JPanel(); - destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS)); - destPanel.setPreferredSize(new Dimension(200, 0)); - pane.add(destPanel, BorderLayout.WEST); - destPanel.add(new JLabel("Destination Classes")); - - m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); - m_destClasses.setListener(new ClassSelectionListener() { - @Override - public void onSelectClass(ClassEntry classEntry) { - setDestClass(classEntry); - } - }); - JScrollPane destScroller = new JScrollPane(m_destClasses); - destPanel.add(destScroller); - - JButton autoMatchButton = new JButton("AutoMatch"); - autoMatchButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - autoMatch(); - } - }); - destPanel.add(autoMatchButton); - - // init source panels - DefaultSyntaxKit.initKit(); - m_sourceReader = makeReader(); - m_destReader = makeReader(); - - // init all the splits - JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader)); - splitLeft.setResizeWeight(0); // let the right side take all the slack - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel); - splitRight.setResizeWeight(1); // let the left side take all the slack - JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight); - splitCenter.setResizeWeight(0.5); // resize 50:50 - pane.add(splitCenter, BorderLayout.CENTER); - splitCenter.resetToPreferredSizes(); - - // init bottom panel - JPanel bottomPanel = new JPanel(); - bottomPanel.setLayout(new FlowLayout()); - - m_sourceClassLabel = new JLabel(); - m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT); - m_sourceClassLabel.setPreferredSize(new Dimension(300, 24)); - m_destClassLabel = new JLabel(); - m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT); - m_destClassLabel.setPreferredSize(new Dimension(300, 24)); - - m_matchButton = new JButton(); - m_matchButton.setPreferredSize(new Dimension(140, 24)); - - m_advanceCheck = new JCheckBox("Advance to next likely match"); - m_advanceCheck.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - if (m_advanceCheck.isSelected()) { - advance(); - } - } - }); - - bottomPanel.add(m_sourceClassLabel); - bottomPanel.add(m_matchButton); - bottomPanel.add(m_destClassLabel); - bottomPanel.add(m_advanceCheck); - pane.add(bottomPanel, BorderLayout.SOUTH); - - // show the frame - pane.doLayout(); - m_frame.setSize(1024, 576); - m_frame.setMinimumSize(new Dimension(640, 480)); - m_frame.setVisible(true); - m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - - m_selectionHighlightPainter = new SelectionHighlightPainter(); - - // init state - updateDestMappings(); - setSourceType(SourceType.getDefault()); - updateMatchButton(); - m_saveListener = null; - } - - private JEditorPane makeReader() { - - JEditorPane reader = new JEditorPane(); - reader.setEditable(false); - reader.setContentType("text/java"); - - // turn off token highlighting (it's wrong most of the time anyway...) - DefaultSyntaxKit kit = (DefaultSyntaxKit)reader.getEditorKit(); - kit.toggleComponent(reader, "de.sciss.syntaxpane.components.TokenMarker"); - - return reader; - } - - public void setSaveListener(SaveListener val) { - m_saveListener = val; - } - - private void updateDestMappings() { - m_destDeobfuscator.setMappings(MappingsConverter.newMappings( - m_matches, - m_sourceDeobfuscator.getMappings(), - m_sourceDeobfuscator, - m_destDeobfuscator - ), false); - } - - protected void setSourceType(SourceType val) { - - // show the source classes - m_sourceType = val; - m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator)); - - // update counts - for (SourceType sourceType : SourceType.values()) { - m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", - sourceType.name(), - sourceType.getSourceClasses(m_matches).size() - )); - } - } - - private Collection deobfuscateClasses(Collection in, Deobfuscator deobfuscator) { - List out = Lists.newArrayList(); - for (ClassEntry entry : in) { - - ClassEntry deobf = deobfuscator.deobfuscateEntry(entry); - - // make sure we preserve any scores - if (entry instanceof ScoredClassEntry) { - deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry)entry).getScore()); - } - - out.add(deobf); - } - return out; - } - - protected void setSourceClass(ClassEntry classEntry) { - - Runnable onGetDestClasses = null; - if (m_advanceCheck.isSelected()) { - onGetDestClasses = new Runnable() { - @Override - public void run() { - pickBestDestClass(); - } - }; - } - - setSourceClass(classEntry, onGetDestClasses); - } - - protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) { - - // update the current source class - m_sourceClass = classEntry; - m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : ""); - - if (m_sourceClass != null) { - - // show the dest class(es) - ClassMatch match = m_matches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); - assert(match != null); - if (match.destClasses.isEmpty()) { - - m_destClasses.setClasses(null); - - // run in a separate thread to keep ui responsive - new Thread() { - @Override - public void run() { - m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator)); - m_destClasses.expandAll(); - - if (onGetDestClasses != null) { - onGetDestClasses.run(); - } - } - }.start(); - - } else { - - m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator)); - m_destClasses.expandAll(); - - if (onGetDestClasses != null) { - onGetDestClasses.run(); - } - } - } - - setDestClass(null); - decompileClass(m_sourceClass, m_sourceDeobfuscator, m_sourceReader); - - updateMatchButton(); - } - - private Collection getLikelyMatches(ClassEntry sourceClass) { - - ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); - - // set up identifiers - ClassNamer namer = new ClassNamer(m_matches.getUniqueMatches()); - ClassIdentifier sourceIdentifier = new ClassIdentifier( - m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), - namer.getSourceNamer(), true - ); - ClassIdentifier destIdentifier = new ClassIdentifier( - m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(), - namer.getDestNamer(), true - ); - - try { - - // rank all the unmatched dest classes against the source class - ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); - List scoredDestClasses = Lists.newArrayList(); - for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) { - ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); - float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) - /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); - scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score)); - } - return scoredDestClasses; - - } catch (ClassNotFoundException ex) { - throw new Error("Unable to find class " + ex.getMessage()); - } - } - - protected void setDestClass(ClassEntry classEntry) { - - // update the current source class - m_destClass = classEntry; - m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : ""); - - decompileClass(m_destClass, m_destDeobfuscator, m_destReader); - - updateMatchButton(); - } - - protected void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) { - - if (classEntry == null) { - reader.setText(null); - return; - } - - reader.setText("(decompiling...)"); - - // run in a separate thread to keep ui responsive - new Thread() { - @Override - public void run() { - - // get the outermost class - ClassEntry outermostClassEntry = classEntry; - while (outermostClassEntry.isInnerClass()) { - outermostClassEntry = outermostClassEntry.getOuterClassEntry(); - } - - // decompile it - CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); - String source = deobfuscator.getSource(sourceTree); - reader.setText(source); - SourceIndex sourceIndex = deobfuscator.getSourceIndex(sourceTree, source); - - // navigate to the class declaration - Token token = sourceIndex.getDeclarationToken(classEntry); - if (token == null) { - // couldn't find the class declaration token, might be an anonymous class - // look for any declaration in that class instead - for (Entry entry : sourceIndex.declarations()) { - if (entry.getClassEntry().equals(classEntry)) { - token = sourceIndex.getDeclarationToken(entry); - break; - } - } - } - - if (token != null) { - GuiTricks.navigateToToken(reader, token, m_selectionHighlightPainter); - } else { - // couldn't find anything =( - System.out.println("Unable to find declaration in source for " + classEntry); - } - - } - }.start(); - } - - private void updateMatchButton() { - - ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); - ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); - - BiMap uniqueMatches = m_matches.getUniqueMatches(); - boolean twoSelected = m_sourceClass != null && m_destClass != null; - boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); - boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest); - - deactivateButton(m_matchButton); - if (twoSelected) { - if (isMatched) { - activateButton(m_matchButton, "Unmatch", new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - onUnmatchClick(); - } - }); - } else if (canMatch) { - activateButton(m_matchButton, "Match", new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - onMatchClick(); - } - }); - } - } - } - - private void deactivateButton(JButton button) { - button.setEnabled(false); - button.setText(""); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { - button.removeActionListener(listener); - } - } - - private void activateButton(JButton button, String text, ActionListener newListener) { - button.setText(text); - button.setEnabled(true); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { - button.removeActionListener(listener); - } - button.addActionListener(newListener); - } - - private void onMatchClick() { - // precondition: source and dest classes are set correctly - - ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); - ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); - - // remove the classes from their match - m_matches.removeSource(obfSource); - m_matches.removeDest(obfDest); - - // add them as matched classes - m_matches.add(new ClassMatch(obfSource, obfDest)); - - ClassEntry nextClass = null; - if (m_advanceCheck.isSelected()) { - nextClass = m_sourceClasses.getNextClass(m_sourceClass); - } - - save(); - updateMatches(); - - if (nextClass != null) { - advance(nextClass); - } - } - - private void onUnmatchClick() { - // precondition: source and dest classes are set to a unique match - - ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); - - // remove the source to break the match, then add the source back as unmatched - m_matches.removeSource(obfSource); - m_matches.add(new ClassMatch(obfSource, null)); - - save(); - updateMatches(); - } - - private void updateMatches() { - updateDestMappings(); - setDestClass(null); - m_destClasses.setClasses(null); - updateMatchButton(); - - // remember where we were in the source tree - String packageName = m_sourceClasses.getSelectedPackage(); - - setSourceType(m_sourceType); - - m_sourceClasses.expandPackage(packageName); - } - - private void save() { - if (m_saveListener != null) { - m_saveListener.save(m_matches); - } - } - - private void autoMatch() { - - System.out.println("Automatching..."); - - // compute a new matching - ClassMatching matching = MappingsConverter.computeMatching( - m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), - m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(), - m_matches.getUniqueMatches() - ); - Matches newMatches = new Matches(matching.matches()); - System.out.println(String.format("Automatch found %d new matches", - newMatches.getUniqueMatches().size() - m_matches.getUniqueMatches().size() - )); - - // update the current matches - m_matches = newMatches; - save(); - updateMatches(); - } - - private void advance() { - advance(null); - } - - private void advance(ClassEntry sourceClass) { - - // make sure we have a source class - if (sourceClass == null) { - sourceClass = m_sourceClasses.getSelectedClass(); - if (sourceClass != null) { - sourceClass = m_sourceClasses.getNextClass(sourceClass); - } else { - sourceClass = m_sourceClasses.getFirstClass(); - } - } - - // set the source class - setSourceClass(sourceClass, new Runnable() { - @Override - public void run() { - pickBestDestClass(); - } - }); - m_sourceClasses.setSelectionClass(sourceClass); - } - - private void pickBestDestClass() { - - // then, pick the best dest class - ClassEntry firstClass = null; - ScoredClassEntry bestDestClass = null; - for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) { - for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) { - if (firstClass == null) { - firstClass = classNode.getClassEntry(); - } - if (classNode.getClassEntry() instanceof ScoredClassEntry) { - ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry(); - if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) { - bestDestClass = scoredClass; - } - } - } - } - - // pick the entry to show - ClassEntry destClass = null; - if (bestDestClass != null) { - destClass = bestDestClass; - } else if (firstClass != null) { - destClass = firstClass; - } - - setDestClass(destClass); - m_destClasses.setSelectionClass(destClass); - } -} diff --git a/src/cuchaz/enigma/mapping/MappingsChecker.java b/src/cuchaz/enigma/mapping/MappingsChecker.java new file mode 100644 index 0000000..c5ff7a7 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsChecker.java @@ -0,0 +1,97 @@ +package cuchaz.enigma.mapping; + +import java.util.Map; + +import com.beust.jcommander.internal.Maps; +import com.google.common.collect.Lists; + +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.analysis.RelatedMethodChecker; + + +public class MappingsChecker { + + private JarIndex m_index; + private RelatedMethodChecker m_relatedMethodChecker; + private Map m_droppedClassMappings; + private Map m_droppedInnerClassMappings; + private Map m_droppedFieldMappings; + private Map m_droppedMethodMappings; + + public MappingsChecker(JarIndex index) { + m_index = index; + m_relatedMethodChecker = new RelatedMethodChecker(m_index); + m_droppedClassMappings = Maps.newHashMap(); + m_droppedInnerClassMappings = Maps.newHashMap(); + m_droppedFieldMappings = Maps.newHashMap(); + m_droppedMethodMappings = Maps.newHashMap(); + } + + public RelatedMethodChecker getRelatedMethodChecker() { + return m_relatedMethodChecker; + } + + public Map getDroppedClassMappings() { + return m_droppedClassMappings; + } + + public Map getDroppedInnerClassMappings() { + return m_droppedInnerClassMappings; + } + + public Map getDroppedFieldMappings() { + return m_droppedFieldMappings; + } + + public Map getDroppedMethodMappings() { + return m_droppedMethodMappings; + } + + public void dropBrokenMappings(Mappings mappings) { + for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) { + if (!checkClassMapping(classMapping)) { + mappings.removeClassMapping(classMapping); + m_droppedClassMappings.put(EntryFactory.getObfClassEntry(m_index, classMapping), classMapping); + } + } + } + + private boolean checkClassMapping(ClassMapping classMapping) { + + // check the class + ClassEntry classEntry = EntryFactory.getObfClassEntry(m_index, classMapping); + if (!m_index.getObfClassEntries().contains(classEntry)) { + return false; + } + + // check the fields + for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { + FieldEntry obfFieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName(), fieldMapping.getObfType()); + if (!m_index.containsObfField(obfFieldEntry)) { + classMapping.removeFieldMapping(fieldMapping); + m_droppedFieldMappings.put(obfFieldEntry, fieldMapping); + } + } + + // check methods + for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { + BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); + if (!m_index.containsObfBehavior(obfBehaviorEntry)) { + classMapping.removeMethodMapping(methodMapping); + m_droppedMethodMappings.put(obfBehaviorEntry, methodMapping); + } + + m_relatedMethodChecker.checkMethod(classEntry, methodMapping); + } + + // check inner classes + for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) { + if (!checkClassMapping(innerClassMapping)) { + classMapping.removeInnerClassMapping(innerClassMapping); + m_droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(m_index, innerClassMapping), innerClassMapping); + } + } + + return true; + } +} -- cgit v1.2.3 From d6b2a223a7973941e5e4fb45c8ceec4885891496 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 9 Mar 2015 12:53:11 -0400 Subject: starting on field matching gui --- src/cuchaz/enigma/ConvertMain.java | 136 +++++++++++++++---- src/cuchaz/enigma/convert/ClassMatches.java | 153 +++++++++++++++++++++ src/cuchaz/enigma/convert/FieldMatches.java | 35 +++++ src/cuchaz/enigma/convert/MappingsConverter.java | 8 +- src/cuchaz/enigma/convert/Matches.java | 153 --------------------- src/cuchaz/enigma/convert/MatchesReader.java | 8 +- src/cuchaz/enigma/convert/MatchesWriter.java | 6 +- src/cuchaz/enigma/gui/ClassMatchingGui.java | 145 ++++++-------------- src/cuchaz/enigma/gui/CodeReader.java | 164 +++++++++++++++++++++++ src/cuchaz/enigma/gui/FieldMatchingGui.java | 131 ++++++++++++++++++ src/cuchaz/enigma/gui/Gui.java | 2 +- src/cuchaz/enigma/gui/GuiTricks.java | 58 -------- 12 files changed, 645 insertions(+), 354 deletions(-) create mode 100644 src/cuchaz/enigma/convert/ClassMatches.java create mode 100644 src/cuchaz/enigma/convert/FieldMatches.java delete mode 100644 src/cuchaz/enigma/convert/Matches.java create mode 100644 src/cuchaz/enigma/gui/CodeReader.java create mode 100644 src/cuchaz/enigma/gui/FieldMatchingGui.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index 2afd9ca..624eb40 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -6,14 +6,16 @@ import java.io.FileWriter; import java.io.IOException; import java.util.jar.JarFile; +import cuchaz.enigma.convert.ClassMatches; +import cuchaz.enigma.convert.FieldMatches; import cuchaz.enigma.convert.MappingsConverter; -import cuchaz.enigma.convert.Matches; import cuchaz.enigma.convert.MatchesReader; import cuchaz.enigma.convert.MatchesWriter; import cuchaz.enigma.gui.ClassMatchingGui; -import cuchaz.enigma.gui.ClassMatchingGui.SaveListener; +import cuchaz.enigma.gui.FieldMatchingGui; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsChecker; import cuchaz.enigma.mapping.MappingsReader; import cuchaz.enigma.mapping.MappingsWriter; @@ -30,11 +32,13 @@ public class ConvertMain { File inMappingsFile = new File("../Enigma Mappings/1.8.mappings"); File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings"); Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); - File matchingFile = new File(inMappingsFile.getName() + ".matching"); + File classMatchingFile = new File(inMappingsFile.getName() + ".class.matching"); + File fieldMatchingFile = new File(inMappingsFile.getName() + ".field.matching"); - //computeMatches(matchingFile, sourceJar, destJar, mappings); - editMatches(matchingFile, sourceJar, destJar, mappings); - //convertMappings(outMappingsFile, sourceJar, destJar, mappings, matchingFile); + //computeMatches(classMatchingFile, sourceJar, destJar, mappings); + //editClasssMatches(classMatchingFile, sourceJar, destJar, mappings); + //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchingFile); + editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchingFile, fieldMatchingFile); /* TODO // write out the converted mappings @@ -45,28 +49,25 @@ public class ConvertMain { */ } - private static void computeMatches(File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + private static void computeMatches(File classMatchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) throws IOException { - Matches matches = MappingsConverter.computeMatches(sourceJar, destJar, mappings); - MatchesWriter.write(matches, matchingFile); - System.out.println("Wrote:\n\t" + matchingFile.getAbsolutePath()); + ClassMatches classMatches = MappingsConverter.computeMatches(sourceJar, destJar, mappings); + MatchesWriter.writeClasses(classMatches, classMatchingFile); + System.out.println("Wrote:\n\t" + classMatchingFile.getAbsolutePath()); } - private static void editMatches(final File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + private static void editClasssMatches(final File classMatchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) throws IOException { System.out.println("Reading matches..."); - Matches matches = MatchesReader.read(matchingFile); - System.out.println("Indexing source jar..."); - Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); - sourceDeobfuscator.setMappings(mappings); - System.out.println("Indexing dest jar..."); - Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + ClassMatches classMatches = MatchesReader.readClasses(classMatchingFile); + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(mappings); System.out.println("Starting GUI..."); - new ClassMatchingGui(matches, sourceDeobfuscator, destDeobfuscator).setSaveListener(new SaveListener() { + new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new ClassMatchingGui.SaveListener() { @Override - public void save(Matches matches) { + public void save(ClassMatches matches) { try { - MatchesWriter.write(matches, matchingFile); + MatchesWriter.writeClasses(matches, classMatchingFile); } catch (IOException ex) { throw new Error(ex); } @@ -74,21 +75,100 @@ public class ConvertMain { }); } - private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File matchingFile) + private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchingFile) throws IOException { System.out.println("Reading matches..."); - Matches matches = MatchesReader.read(matchingFile); - System.out.println("Indexing source jar..."); - Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); - sourceDeobfuscator.setMappings(mappings); - System.out.println("Indexing dest jar..."); - Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + ClassMatches classMatches = MatchesReader.readClasses(classMatchingFile); + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(mappings); - Mappings newMappings = MappingsConverter.newMappings(matches, mappings, sourceDeobfuscator, destDeobfuscator); + Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.source); try (FileWriter out = new FileWriter(outMappingsFile)) { new MappingsWriter().write(out, newMappings); } System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath()); } + + private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchingFile, final File fieldMatchingFile) + throws IOException, MappingParseException { + + System.out.println("Reading matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchingFile); + FieldMatches fieldMatches; + if (fieldMatchingFile.exists() /* TEMP */ && false) { + // TODO + //fieldMatches = MatchesReader.readFields(fieldMatchingFile); + } else { + fieldMatches = new FieldMatches(); + } + + // prep deobfuscators + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(sourceMappings); + Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile)); + MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); + checker.dropBrokenMappings(destMappings); + deobfuscators.dest.setMappings(destMappings); + + new FieldMatchingGui(classMatches, fieldMatches, checker.getDroppedFieldMappings(), deobfuscators.source, deobfuscators.dest).setSaveListener(new FieldMatchingGui.SaveListener() { + @Override + public void save(FieldMatches matches) { + /* TODO + try { + MatchesWriter.writeFields(matches, fieldMatchingFile); + } catch (IOException ex) { + throw new Error(ex); + } + */ + } + }); + } + + private static class Deobfuscators { + + public Deobfuscator source; + public Deobfuscator dest; + + public Deobfuscators(JarFile sourceJar, JarFile destJar) { + System.out.println("Indexing source jar..."); + IndexerThread sourceIndexer = new IndexerThread(sourceJar); + sourceIndexer.start(); + System.out.println("Indexing dest jar..."); + IndexerThread destIndexer = new IndexerThread(destJar); + destIndexer.start(); + sourceIndexer.joinOrBail(); + destIndexer.joinOrBail(); + source = sourceIndexer.deobfuscator; + dest = destIndexer.deobfuscator; + } + } + + private static class IndexerThread extends Thread { + + private JarFile m_jarFile; + public Deobfuscator deobfuscator; + + public IndexerThread(JarFile jarFile) { + m_jarFile = jarFile; + deobfuscator = null; + } + + public void joinOrBail() { + try { + join(); + } catch (InterruptedException ex) { + throw new Error(ex); + } + } + + @Override + public void run() { + try { + deobfuscator = new Deobfuscator(m_jarFile); + } catch (IOException ex) { + throw new Error(ex); + } + } + } } diff --git a/src/cuchaz/enigma/convert/ClassMatches.java b/src/cuchaz/enigma/convert/ClassMatches.java new file mode 100644 index 0000000..f8b2afd --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassMatches.java @@ -0,0 +1,153 @@ +package cuchaz.enigma.convert; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class ClassMatches implements Iterable { + + Collection m_matches; + Map m_matchesBySource; + Map m_matchesByDest; + BiMap m_uniqueMatches; + Map m_ambiguousMatchesBySource; + Map m_ambiguousMatchesByDest; + Set m_unmatchedSourceClasses; + Set m_unmatchedDestClasses; + + public ClassMatches() { + this(new ArrayList()); + } + + public ClassMatches(Collection matches) { + m_matches = matches; + m_matchesBySource = Maps.newHashMap(); + m_matchesByDest = Maps.newHashMap(); + m_uniqueMatches = HashBiMap.create(); + m_ambiguousMatchesBySource = Maps.newHashMap(); + m_ambiguousMatchesByDest = Maps.newHashMap(); + m_unmatchedSourceClasses = Sets.newHashSet(); + m_unmatchedDestClasses = Sets.newHashSet(); + + for (ClassMatch match : matches) { + indexMatch(match); + } + } + + public void add(ClassMatch match) { + m_matches.add(match); + indexMatch(match); + } + + public void remove(ClassMatch match) { + for (ClassEntry sourceClass : match.sourceClasses) { + m_matchesBySource.remove(sourceClass); + m_uniqueMatches.remove(sourceClass); + m_ambiguousMatchesBySource.remove(sourceClass); + m_unmatchedSourceClasses.remove(sourceClass); + } + for (ClassEntry destClass : match.destClasses) { + m_matchesByDest.remove(destClass); + m_uniqueMatches.inverse().remove(destClass); + m_ambiguousMatchesByDest.remove(destClass); + m_unmatchedDestClasses.remove(destClass); + } + m_matches.remove(match); + } + + public int size() { + return m_matches.size(); + } + + @Override + public Iterator iterator() { + return m_matches.iterator(); + } + + private void indexMatch(ClassMatch match) { + if (!match.isMatched()) { + // unmatched + m_unmatchedSourceClasses.addAll(match.sourceClasses); + m_unmatchedDestClasses.addAll(match.destClasses); + } else { + if (match.isAmbiguous()) { + // ambiguously matched + for (ClassEntry entry : match.sourceClasses) { + m_ambiguousMatchesBySource.put(entry, match); + } + for (ClassEntry entry : match.destClasses) { + m_ambiguousMatchesByDest.put(entry, match); + } + } else { + // uniquely matched + m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } + } + for (ClassEntry entry : match.sourceClasses) { + m_matchesBySource.put(entry, match); + } + for (ClassEntry entry : match.destClasses) { + m_matchesByDest.put(entry, match); + } + } + + public BiMap getUniqueMatches() { + return m_uniqueMatches; + } + + public Set getUnmatchedSourceClasses() { + return m_unmatchedSourceClasses; + } + + public Set getUnmatchedDestClasses() { + return m_unmatchedDestClasses; + } + + public Set getAmbiguouslyMatchedSourceClasses() { + return m_ambiguousMatchesBySource.keySet(); + } + + public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) { + return m_ambiguousMatchesBySource.get(sourceClass); + } + + public ClassMatch getMatchBySource(ClassEntry sourceClass) { + return m_matchesBySource.get(sourceClass); + } + + public ClassMatch getMatchByDest(ClassEntry destClass) { + return m_matchesByDest.get(destClass); + } + + public void removeSource(ClassEntry sourceClass) { + ClassMatch match = m_matchesBySource.get(sourceClass); + if (match != null) { + remove(match); + match.sourceClasses.remove(sourceClass); + if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { + add(match); + } + } + } + + public void removeDest(ClassEntry destClass) { + ClassMatch match = m_matchesByDest.get(destClass); + if (match != null) { + remove(match); + match.destClasses.remove(destClass); + if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { + add(match); + } + } + } +} diff --git a/src/cuchaz/enigma/convert/FieldMatches.java b/src/cuchaz/enigma/convert/FieldMatches.java new file mode 100644 index 0000000..f78a8f5 --- /dev/null +++ b/src/cuchaz/enigma/convert/FieldMatches.java @@ -0,0 +1,35 @@ +package cuchaz.enigma.convert; + +import java.util.Collection; +import java.util.Set; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.FieldEntry; + + +public class FieldMatches { + + private BiMap m_matches; + private Set m_unmatchedSourceFields; + + public FieldMatches() { + m_matches = HashBiMap.create(); + m_unmatchedSourceFields = Sets.newHashSet(); + } + + public void addUnmatchedSourceFields(Set fieldEntries) { + m_unmatchedSourceFields.addAll(fieldEntries); + } + + public Collection getSourceClassesWithUnmatchedFields() { + Set classEntries = Sets.newHashSet(); + for (FieldEntry fieldEntry : m_unmatchedSourceFields) { + classEntries.add(fieldEntry.getClassEntry()); + } + return classEntries; + } +} diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 5883878..667ee9d 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -37,7 +37,7 @@ import cuchaz.enigma.mapping.MethodMapping; public class MappingsConverter { - public static Matches computeMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { + public static ClassMatches computeMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { // index jars System.out.println("Indexing source jar..."); @@ -49,7 +49,7 @@ public class MappingsConverter { // compute the matching ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); - return new Matches(matching.matches()); + return new ClassMatches(matching.matches()); } public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap knownMatches) { @@ -115,7 +115,7 @@ public class MappingsConverter { return lastMatching; } - public static Mappings newMappings(Matches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { // sort the unique matches by size of inner class chain Multimap> matchesByDestChainSize = HashMultimap.create(); @@ -172,7 +172,7 @@ public class MappingsConverter { return newMappings; } - private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping mapping, final Matches matches, boolean useSimpleName) { + private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping mapping, final ClassMatches matches, boolean useSimpleName) { ClassNameReplacer replacer = new ClassNameReplacer() { @Override diff --git a/src/cuchaz/enigma/convert/Matches.java b/src/cuchaz/enigma/convert/Matches.java deleted file mode 100644 index 19bb155..0000000 --- a/src/cuchaz/enigma/convert/Matches.java +++ /dev/null @@ -1,153 +0,0 @@ -package cuchaz.enigma.convert; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import cuchaz.enigma.mapping.ClassEntry; - - -public class Matches implements Iterable { - - Collection m_matches; - Map m_matchesBySource; - Map m_matchesByDest; - BiMap m_uniqueMatches; - Map m_ambiguousMatchesBySource; - Map m_ambiguousMatchesByDest; - Set m_unmatchedSourceClasses; - Set m_unmatchedDestClasses; - - public Matches() { - this(new ArrayList()); - } - - public Matches(Collection matches) { - m_matches = matches; - m_matchesBySource = Maps.newHashMap(); - m_matchesByDest = Maps.newHashMap(); - m_uniqueMatches = HashBiMap.create(); - m_ambiguousMatchesBySource = Maps.newHashMap(); - m_ambiguousMatchesByDest = Maps.newHashMap(); - m_unmatchedSourceClasses = Sets.newHashSet(); - m_unmatchedDestClasses = Sets.newHashSet(); - - for (ClassMatch match : matches) { - indexMatch(match); - } - } - - public void add(ClassMatch match) { - m_matches.add(match); - indexMatch(match); - } - - public void remove(ClassMatch match) { - for (ClassEntry sourceClass : match.sourceClasses) { - m_matchesBySource.remove(sourceClass); - m_uniqueMatches.remove(sourceClass); - m_ambiguousMatchesBySource.remove(sourceClass); - m_unmatchedSourceClasses.remove(sourceClass); - } - for (ClassEntry destClass : match.destClasses) { - m_matchesByDest.remove(destClass); - m_uniqueMatches.inverse().remove(destClass); - m_ambiguousMatchesByDest.remove(destClass); - m_unmatchedDestClasses.remove(destClass); - } - m_matches.remove(match); - } - - public int size() { - return m_matches.size(); - } - - @Override - public Iterator iterator() { - return m_matches.iterator(); - } - - private void indexMatch(ClassMatch match) { - if (!match.isMatched()) { - // unmatched - m_unmatchedSourceClasses.addAll(match.sourceClasses); - m_unmatchedDestClasses.addAll(match.destClasses); - } else { - if (match.isAmbiguous()) { - // ambiguously matched - for (ClassEntry entry : match.sourceClasses) { - m_ambiguousMatchesBySource.put(entry, match); - } - for (ClassEntry entry : match.destClasses) { - m_ambiguousMatchesByDest.put(entry, match); - } - } else { - // uniquely matched - m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); - } - } - for (ClassEntry entry : match.sourceClasses) { - m_matchesBySource.put(entry, match); - } - for (ClassEntry entry : match.destClasses) { - m_matchesByDest.put(entry, match); - } - } - - public BiMap getUniqueMatches() { - return m_uniqueMatches; - } - - public Set getUnmatchedSourceClasses() { - return m_unmatchedSourceClasses; - } - - public Set getUnmatchedDestClasses() { - return m_unmatchedDestClasses; - } - - public Set getAmbiguouslyMatchedSourceClasses() { - return m_ambiguousMatchesBySource.keySet(); - } - - public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) { - return m_ambiguousMatchesBySource.get(sourceClass); - } - - public ClassMatch getMatchBySource(ClassEntry sourceClass) { - return m_matchesBySource.get(sourceClass); - } - - public ClassMatch getMatchByDest(ClassEntry destClass) { - return m_matchesByDest.get(destClass); - } - - public void removeSource(ClassEntry sourceClass) { - ClassMatch match = m_matchesBySource.get(sourceClass); - if (match != null) { - remove(match); - match.sourceClasses.remove(sourceClass); - if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { - add(match); - } - } - } - - public void removeDest(ClassEntry destClass) { - ClassMatch match = m_matchesByDest.get(destClass); - if (match != null) { - remove(match); - match.destClasses.remove(destClass); - if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { - add(match); - } - } - } -} diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java index 808f8d0..b43535c 100644 --- a/src/cuchaz/enigma/convert/MatchesReader.java +++ b/src/cuchaz/enigma/convert/MatchesReader.java @@ -14,19 +14,19 @@ import cuchaz.enigma.mapping.ClassEntry; public class MatchesReader { - public static Matches read(File file) + public static ClassMatches readClasses(File file) throws IOException { try (BufferedReader in = new BufferedReader(new FileReader(file))) { - Matches matches = new Matches(); + ClassMatches matches = new ClassMatches(); String line = null; while ((line = in.readLine()) != null) { - matches.add(readMatch(line)); + matches.add(readClassMatch(line)); } return matches; } } - private static ClassMatch readMatch(String line) + private static ClassMatch readClassMatch(String line) throws IOException { String[] sides = line.split(":", 2); return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java index 49ffb6d..6658e2a 100644 --- a/src/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/cuchaz/enigma/convert/MatchesWriter.java @@ -9,16 +9,16 @@ import cuchaz.enigma.mapping.ClassEntry; public class MatchesWriter { - public static void write(Matches matches, File file) + public static void writeClasses(ClassMatches matches, File file) throws IOException { try (FileWriter out = new FileWriter(file)) { for (ClassMatch match : matches) { - writeMatch(out, match); + writeClassMatch(out, match); } } } - private static void writeMatch(FileWriter out, ClassMatch match) + private static void writeClassMatch(FileWriter out, ClassMatch match) throws IOException { writeClasses(out, match.sourceClasses); out.write(":"); diff --git a/src/cuchaz/enigma/gui/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java index ff7cda9..b674451 100644 --- a/src/cuchaz/enigma/gui/ClassMatchingGui.java +++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java @@ -15,7 +15,6 @@ import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; -import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; @@ -28,22 +27,18 @@ import javax.swing.WindowConstants; import com.beust.jcommander.internal.Lists; import com.beust.jcommander.internal.Maps; import com.google.common.collect.BiMap; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; import cuchaz.enigma.Constants; import cuchaz.enigma.Deobfuscator; -import cuchaz.enigma.analysis.SourceIndex; -import cuchaz.enigma.analysis.Token; import cuchaz.enigma.convert.ClassIdentifier; import cuchaz.enigma.convert.ClassIdentity; import cuchaz.enigma.convert.ClassMatch; +import cuchaz.enigma.convert.ClassMatches; import cuchaz.enigma.convert.ClassMatching; import cuchaz.enigma.convert.ClassNamer; import cuchaz.enigma.convert.MappingsConverter; -import cuchaz.enigma.convert.Matches; import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; import de.sciss.syntaxpane.DefaultSyntaxKit; @@ -55,21 +50,21 @@ public class ClassMatchingGui { Matched { @Override - public Collection getSourceClasses(Matches matches) { + public Collection getSourceClasses(ClassMatches matches) { return matches.getUniqueMatches().keySet(); } }, Unmatched { @Override - public Collection getSourceClasses(Matches matches) { + public Collection getSourceClasses(ClassMatches matches) { return matches.getUnmatchedSourceClasses(); } }, Ambiguous { @Override - public Collection getSourceClasses(Matches matches) { + public Collection getSourceClasses(ClassMatches matches) { return matches.getAmbiguouslyMatchedSourceClasses(); } }; @@ -82,7 +77,7 @@ public class ClassMatchingGui { return button; } - public abstract Collection getSourceClasses(Matches matches); + public abstract Collection getSourceClasses(ClassMatches matches); public static SourceType getDefault() { return values()[0]; @@ -90,23 +85,22 @@ public class ClassMatchingGui { } public static interface SaveListener { - public void save(Matches matches); + public void save(ClassMatches matches); } // controls private JFrame m_frame; private ClassSelector m_sourceClasses; private ClassSelector m_destClasses; - private JEditorPane m_sourceReader; - private JEditorPane m_destReader; + private CodeReader m_sourceReader; + private CodeReader m_destReader; private JLabel m_sourceClassLabel; private JLabel m_destClassLabel; private JButton m_matchButton; private Map m_sourceTypeButtons; private JCheckBox m_advanceCheck; - private SelectionHighlightPainter m_selectionHighlightPainter; - private Matches m_matches; + private ClassMatches m_classMatches; private Deobfuscator m_sourceDeobfuscator; private Deobfuscator m_destDeobfuscator; private ClassEntry m_sourceClass; @@ -114,14 +108,14 @@ public class ClassMatchingGui { private SourceType m_sourceType; private SaveListener m_saveListener; - public ClassMatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - m_matches = matches; + m_classMatches = matches; m_sourceDeobfuscator = sourceDeobfuscator; m_destDeobfuscator = destDeobfuscator; // init frame - m_frame = new JFrame(Constants.Name); + m_frame = new JFrame(Constants.Name + " - Class Matcher"); final Container pane = m_frame.getContentPane(); pane.setLayout(new BorderLayout()); @@ -188,8 +182,8 @@ public class ClassMatchingGui { // init source panels DefaultSyntaxKit.initKit(); - m_sourceReader = makeReader(); - m_destReader = makeReader(); + m_sourceReader = new CodeReader(); + m_destReader = new CodeReader(); // init all the splits JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader)); @@ -238,8 +232,6 @@ public class ClassMatchingGui { m_frame.setVisible(true); m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - m_selectionHighlightPainter = new SelectionHighlightPainter(); - // init state updateDestMappings(); setSourceType(SourceType.getDefault()); @@ -247,19 +239,6 @@ public class ClassMatchingGui { m_saveListener = null; } - private JEditorPane makeReader() { - - JEditorPane reader = new JEditorPane(); - reader.setEditable(false); - reader.setContentType("text/java"); - - // turn off token highlighting (it's wrong most of the time anyway...) - DefaultSyntaxKit kit = (DefaultSyntaxKit)reader.getEditorKit(); - kit.toggleComponent(reader, "de.sciss.syntaxpane.components.TokenMarker"); - - return reader; - } - public void setSaveListener(SaveListener val) { m_saveListener = val; } @@ -267,7 +246,7 @@ public class ClassMatchingGui { private void updateDestMappings() { Mappings newMappings = MappingsConverter.newMappings( - m_matches, + m_classMatches, m_sourceDeobfuscator.getMappings(), m_sourceDeobfuscator, m_destDeobfuscator @@ -294,13 +273,13 @@ public class ClassMatchingGui { // show the source classes m_sourceType = val; - m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator)); + m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator)); // update counts for (SourceType sourceType : SourceType.values()) { m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", sourceType.name(), - sourceType.getSourceClasses(m_matches).size() + sourceType.getSourceClasses(m_classMatches).size() )); } } @@ -345,7 +324,7 @@ public class ClassMatchingGui { if (m_sourceClass != null) { // show the dest class(es) - ClassMatch match = m_matches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); + ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); assert(match != null); if (match.destClasses.isEmpty()) { @@ -376,7 +355,12 @@ public class ClassMatchingGui { } setDestClass(null); - decompileClass(m_sourceClass, m_sourceDeobfuscator, m_sourceReader); + m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, new Runnable() { + @Override + public void run() { + m_sourceReader.navigateToClassDeclaration(m_sourceClass); + } + }); updateMatchButton(); } @@ -386,7 +370,7 @@ public class ClassMatchingGui { ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); // set up identifiers - ClassNamer namer = new ClassNamer(m_matches.getUniqueMatches()); + ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches()); ClassIdentifier sourceIdentifier = new ClassIdentifier( m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), namer.getSourceNamer(), true @@ -401,7 +385,7 @@ public class ClassMatchingGui { // rank all the unmatched dest classes against the source class ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); List scoredDestClasses = Lists.newArrayList(); - for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) { + for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) { ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); @@ -420,59 +404,14 @@ public class ClassMatchingGui { m_destClass = classEntry; m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : ""); - decompileClass(m_destClass, m_destDeobfuscator, m_destReader); - - updateMatchButton(); - } - - protected void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) { - - if (classEntry == null) { - reader.setText(null); - return; - } - - reader.setText("(decompiling...)"); - - // run in a separate thread to keep ui responsive - new Thread() { + m_destReader.decompileClass(m_destClass, m_destDeobfuscator, new Runnable() { @Override public void run() { - - // get the outermost class - ClassEntry outermostClassEntry = classEntry; - while (outermostClassEntry.isInnerClass()) { - outermostClassEntry = outermostClassEntry.getOuterClassEntry(); - } - - // decompile it - CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); - String source = deobfuscator.getSource(sourceTree); - reader.setText(source); - SourceIndex sourceIndex = deobfuscator.getSourceIndex(sourceTree, source); - - // navigate to the class declaration - Token token = sourceIndex.getDeclarationToken(classEntry); - if (token == null) { - // couldn't find the class declaration token, might be an anonymous class - // look for any declaration in that class instead - for (Entry entry : sourceIndex.declarations()) { - if (entry.getClassEntry().equals(classEntry)) { - token = sourceIndex.getDeclarationToken(entry); - break; - } - } - } - - if (token != null) { - GuiTricks.navigateToToken(reader, token, m_selectionHighlightPainter); - } else { - // couldn't find anything =( - System.out.println("Unable to find declaration in source for " + classEntry); - } - + m_destReader.navigateToClassDeclaration(m_destClass); } - }.start(); + }); + + updateMatchButton(); } private void updateMatchButton() { @@ -480,7 +419,7 @@ public class ClassMatchingGui { ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); - BiMap uniqueMatches = m_matches.getUniqueMatches(); + BiMap uniqueMatches = m_classMatches.getUniqueMatches(); boolean twoSelected = m_sourceClass != null && m_destClass != null; boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest); @@ -529,11 +468,11 @@ public class ClassMatchingGui { ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); // remove the classes from their match - m_matches.removeSource(obfSource); - m_matches.removeDest(obfDest); + m_classMatches.removeSource(obfSource); + m_classMatches.removeDest(obfDest); // add them as matched classes - m_matches.add(new ClassMatch(obfSource, obfDest)); + m_classMatches.add(new ClassMatch(obfSource, obfDest)); ClassEntry nextClass = null; if (m_advanceCheck.isSelected()) { @@ -554,8 +493,8 @@ public class ClassMatchingGui { ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); // remove the source to break the match, then add the source back as unmatched - m_matches.removeSource(obfSource); - m_matches.add(new ClassMatch(obfSource, null)); + m_classMatches.removeSource(obfSource); + m_classMatches.add(new ClassMatch(obfSource, null)); save(); updateMatches(); @@ -577,7 +516,7 @@ public class ClassMatchingGui { private void save() { if (m_saveListener != null) { - m_saveListener.save(m_matches); + m_saveListener.save(m_classMatches); } } @@ -589,15 +528,15 @@ public class ClassMatchingGui { ClassMatching matching = MappingsConverter.computeMatching( m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(), - m_matches.getUniqueMatches() + m_classMatches.getUniqueMatches() ); - Matches newMatches = new Matches(matching.matches()); + ClassMatches newMatches = new ClassMatches(matching.matches()); System.out.println(String.format("Automatch found %d new matches", - newMatches.getUniqueMatches().size() - m_matches.getUniqueMatches().size() + newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size() )); // update the current matches - m_matches = newMatches; + m_classMatches = newMatches; save(); updateMatches(); } diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java new file mode 100644 index 0000000..05feb59 --- /dev/null +++ b/src/cuchaz/enigma/gui/CodeReader.java @@ -0,0 +1,164 @@ +package cuchaz.enigma.gui; + +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JEditorPane; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter.HighlightPainter; + +import com.strobel.decompiler.languages.java.ast.CompilationUnit; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.analysis.SourceIndex; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import de.sciss.syntaxpane.DefaultSyntaxKit; + + +public class CodeReader extends JEditorPane { + + private static final long serialVersionUID = 3673180950485748810L; + + private static final Object m_lock = new Object(); + + private SelectionHighlightPainter m_highlightPainter; + private SourceIndex m_sourceIndex; + + 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"); + + m_highlightPainter = new SelectionHighlightPainter(); + m_sourceIndex = null; + } + + public void setCode(String code) { + // sadly, the java lexer is not thread safe, so we have to serialize all these calls + synchronized (m_lock) { + setText(code); + } + } + + public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) { + decompileClass(classEntry, deobfuscator, null); + } + + public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Runnable callback) { + + if (classEntry == null) { + setCode(null); + return; + } + + setCode("(decompiling...)"); + + // run decompilation in a separate thread to keep ui responsive + new Thread() { + @Override + public void run() { + + // get the outermost class + ClassEntry outermostClassEntry = classEntry; + while (outermostClassEntry.isInnerClass()) { + outermostClassEntry = outermostClassEntry.getOuterClassEntry(); + } + + // decompile it + CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); + String source = deobfuscator.getSource(sourceTree); + setCode(source); + m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source); + + if (callback != null) { + callback.run(); + } + } + }.start(); + } + + public void navigateToClassDeclaration(ClassEntry classEntry) { + + // navigate to the class declaration + Token token = m_sourceIndex.getDeclarationToken(classEntry); + if (token == null) { + // couldn't find the class declaration token, might be an anonymous class + // look for any declaration in that class instead + for (Entry entry : m_sourceIndex.declarations()) { + if (entry.getClassEntry().equals(classEntry)) { + token = m_sourceIndex.getDeclarationToken(entry); + break; + } + } + } + + if (token != null) { + navigateToToken(token); + } else { + // couldn't find anything =( + System.out.println("Unable to find declaration in source for " + classEntry); + } + } + + public void navigateToToken(final Token token) { + navigateToToken(this, token, m_highlightPainter); + } + + // 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); + editor.grabFocus(); + + try { + // make sure the token is visible in the scroll window + Rectangle start = editor.modelToView(token.start); + Rectangle end = editor.modelToView(token.end); + final Rectangle show = start.union(end); + show.grow(start.width * 10, start.height * 6); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + editor.scrollRectToVisible(show); + } + }); + } catch (BadLocationException ex) { + throw new Error(ex); + } + + // highlight the token momentarily + final Timer timer = new Timer(200, new ActionListener() { + private int m_counter = 0; + private Object m_highlight = null; + + @Override + public void actionPerformed(ActionEvent event) { + if (m_counter % 2 == 0) { + try { + m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); + } catch (BadLocationException ex) { + // don't care + } + } else if (m_highlight != null) { + editor.getHighlighter().removeHighlight(m_highlight); + } + + if (m_counter++ > 6) { + Timer timer = (Timer)event.getSource(); + timer.stop(); + } + } + }); + timer.start(); + } +} diff --git a/src/cuchaz/enigma/gui/FieldMatchingGui.java b/src/cuchaz/enigma/gui/FieldMatchingGui.java new file mode 100644 index 0000000..de9ba14 --- /dev/null +++ b/src/cuchaz/enigma/gui/FieldMatchingGui.java @@ -0,0 +1,131 @@ +package cuchaz.enigma.gui; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.util.Map; + +import javax.swing.BoxLayout; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.WindowConstants; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.convert.ClassMatches; +import cuchaz.enigma.convert.FieldMatches; +import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.FieldMapping; +import de.sciss.syntaxpane.DefaultSyntaxKit; + + +public class FieldMatchingGui { + + public static interface SaveListener { + public void save(FieldMatches matches); + } + + // controls + private JFrame m_frame; + private ClassSelector m_sourceClasses; + private CodeReader m_sourceReader; + private CodeReader m_destReader; + + private ClassMatches m_classMatches; + private FieldMatches m_fieldMatches; + private Map m_droppedFieldMappings; + private Deobfuscator m_sourceDeobfuscator; + private Deobfuscator m_destDeobfuscator; + private SaveListener m_saveListener; + + public FieldMatchingGui(ClassMatches classMatches, FieldMatches fieldMatches, Map droppedFieldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + + m_classMatches = classMatches; + m_fieldMatches = fieldMatches; + m_droppedFieldMappings = droppedFieldMappings; + m_sourceDeobfuscator = sourceDeobfuscator; + m_destDeobfuscator = destDeobfuscator; + + // init frame + m_frame = new JFrame(Constants.Name + " - Field Matcher"); + final Container pane = m_frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + // init classes side + JPanel classesPanel = new JPanel(); + classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS)); + classesPanel.setPreferredSize(new Dimension(200, 0)); + pane.add(classesPanel, BorderLayout.WEST); + classesPanel.add(new JLabel("Classes")); + + m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); + m_sourceClasses.setListener(new ClassSelectionListener() { + @Override + public void onSelectClass(ClassEntry classEntry) { + setSourceClass(classEntry); + } + }); + JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); + classesPanel.add(sourceScroller); + + // init fields side + JPanel fieldsPanel = new JPanel(); + fieldsPanel.setLayout(new BoxLayout(fieldsPanel, BoxLayout.PAGE_AXIS)); + fieldsPanel.setPreferredSize(new Dimension(200, 0)); + pane.add(fieldsPanel, BorderLayout.WEST); + fieldsPanel.add(new JLabel("Destination Fields")); + + // init readers + DefaultSyntaxKit.initKit(); + m_sourceReader = new CodeReader(); + m_destReader = new CodeReader(); + + // init all the splits + JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, new JScrollPane(m_sourceReader)); + splitLeft.setResizeWeight(0); // let the right side take all the slack + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), fieldsPanel); + splitRight.setResizeWeight(1); // let the left side take all the slack + JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight); + splitCenter.setResizeWeight(0.5); // resize 50:50 + pane.add(splitCenter, BorderLayout.CENTER); + splitCenter.resetToPreferredSizes(); + + // init bottom panel + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new FlowLayout()); + + // show the frame + pane.doLayout(); + m_frame.setSize(1024, 576); + m_frame.setMinimumSize(new Dimension(640, 480)); + m_frame.setVisible(true); + m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + // init state + m_saveListener = null; + m_fieldMatches.addUnmatchedSourceFields(droppedFieldMappings.keySet()); + m_sourceClasses.setClasses(m_fieldMatches.getSourceClassesWithUnmatchedFields()); + } + + public void setSaveListener(SaveListener val) { + m_saveListener = val; + } + + protected void setSourceClass(ClassEntry obfSourceClass) { + + // get the matched dest class + final ClassEntry obfDestClass = m_classMatches.getUniqueMatches().get(obfSourceClass); + if (obfDestClass == null) { + throw new Error("No matching dest class for source class: " + obfSourceClass); + } + + m_sourceReader.decompileClass(obfSourceClass, m_sourceDeobfuscator); + m_destReader.decompileClass(obfDestClass, m_destDeobfuscator); + } +} diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java index 38a6ee9..ea05d25 100644 --- a/src/cuchaz/enigma/gui/Gui.java +++ b/src/cuchaz/enigma/gui/Gui.java @@ -737,7 +737,7 @@ public class Gui { if (token == null) { throw new IllegalArgumentException("Token cannot be null!"); } - GuiTricks.navigateToToken(m_editor, token, m_selectionHighlightPainter); + CodeReader.navigateToToken(m_editor, token, m_selectionHighlightPainter); redraw(); } diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java index 5a3a01d..df9e221 100644 --- a/src/cuchaz/enigma/gui/GuiTricks.java +++ b/src/cuchaz/enigma/gui/GuiTricks.java @@ -11,21 +11,11 @@ package cuchaz.enigma.gui; import java.awt.Font; -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import javax.swing.JComponent; -import javax.swing.JEditorPane; import javax.swing.JLabel; -import javax.swing.SwingUtilities; -import javax.swing.Timer; import javax.swing.ToolTipManager; -import javax.swing.text.BadLocationException; -import javax.swing.text.Highlighter.HighlightPainter; - -import cuchaz.enigma.analysis.Token; public class GuiTricks { @@ -43,52 +33,4 @@ public class GuiTricks { manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); manager.setInitialDelay(oldDelay); } - - public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { - - // set the caret position to the token - editor.setCaretPosition(token.start); - editor.grabFocus(); - - try { - // make sure the token is visible in the scroll window - Rectangle start = editor.modelToView(token.start); - Rectangle end = editor.modelToView(token.end); - final Rectangle show = start.union(end); - show.grow(start.width * 10, start.height * 6); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - editor.scrollRectToVisible(show); - } - }); - } catch (BadLocationException ex) { - throw new Error(ex); - } - - // highlight the token momentarily - final Timer timer = new Timer(200, new ActionListener() { - private int m_counter = 0; - private Object m_highlight = null; - - @Override - public void actionPerformed(ActionEvent event) { - if (m_counter % 2 == 0) { - try { - m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); - } catch (BadLocationException ex) { - // don't care - } - } else if (m_highlight != null) { - editor.getHighlighter().removeHighlight(m_highlight); - } - - if (m_counter++ > 6) { - Timer timer = (Timer)event.getSource(); - timer.stop(); - } - } - }); - timer.start(); - } } -- cgit v1.2.3 From 1ad33bfe0a96b1b4a1f3c02cf2c054e8a101dfd8 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 9 Mar 2015 20:08:15 -0400 Subject: field matcher is starting to be useful --- src/cuchaz/enigma/ConvertMain.java | 135 +++++++++++++---- src/cuchaz/enigma/analysis/JarIndex.java | 28 +++- src/cuchaz/enigma/convert/FieldMatches.java | 69 +++++++-- src/cuchaz/enigma/convert/MatchesReader.java | 40 +++++ src/cuchaz/enigma/convert/MatchesWriter.java | 38 +++++ src/cuchaz/enigma/gui/ClassMatchingGui.java | 3 - src/cuchaz/enigma/gui/CodeReader.java | 56 ++++++- src/cuchaz/enigma/gui/FieldMatchingGui.java | 212 +++++++++++++++++++++++---- src/cuchaz/enigma/mapping/EntryFactory.java | 16 ++ 9 files changed, 525 insertions(+), 72 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index 624eb40..a5a00e8 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -4,8 +4,12 @@ import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.util.Set; import java.util.jar.JarFile; +import com.google.common.collect.BiMap; +import com.google.common.collect.Sets; + import cuchaz.enigma.convert.ClassMatches; import cuchaz.enigma.convert.FieldMatches; import cuchaz.enigma.convert.MappingsConverter; @@ -13,11 +17,18 @@ import cuchaz.enigma.convert.MatchesReader; import cuchaz.enigma.convert.MatchesWriter; import cuchaz.enigma.gui.ClassMatchingGui; import cuchaz.enigma.gui.FieldMatchingGui; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassMapping; +import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; import cuchaz.enigma.mapping.MappingsReader; import cuchaz.enigma.mapping.MappingsWriter; +import cuchaz.enigma.mapping.Type; public class ConvertMain { @@ -32,13 +43,14 @@ public class ConvertMain { File inMappingsFile = new File("../Enigma Mappings/1.8.mappings"); File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings"); Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); - File classMatchingFile = new File(inMappingsFile.getName() + ".class.matching"); - File fieldMatchingFile = new File(inMappingsFile.getName() + ".field.matching"); + File classMatchesFile = new File(inMappingsFile.getName() + ".class.matches"); + File fieldMatchesFile = new File(inMappingsFile.getName() + ".field.matches"); - //computeMatches(classMatchingFile, sourceJar, destJar, mappings); + //computeClassMatches(classMatchingFile, sourceJar, destJar, mappings); //editClasssMatches(classMatchingFile, sourceJar, destJar, mappings); //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchingFile); - editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchingFile, fieldMatchingFile); + //computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile); + editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile); /* TODO // write out the converted mappings @@ -49,17 +61,17 @@ public class ConvertMain { */ } - private static void computeMatches(File classMatchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) throws IOException { ClassMatches classMatches = MappingsConverter.computeMatches(sourceJar, destJar, mappings); - MatchesWriter.writeClasses(classMatches, classMatchingFile); - System.out.println("Wrote:\n\t" + classMatchingFile.getAbsolutePath()); + MatchesWriter.writeClasses(classMatches, classMatchesFile); + System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath()); } - private static void editClasssMatches(final File classMatchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) throws IOException { - System.out.println("Reading matches..."); - ClassMatches classMatches = MatchesReader.readClasses(classMatchingFile); + System.out.println("Reading class matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); deobfuscators.source.setMappings(mappings); System.out.println("Starting GUI..."); @@ -67,7 +79,7 @@ public class ConvertMain { @Override public void save(ClassMatches matches) { try { - MatchesWriter.writeClasses(matches, classMatchingFile); + MatchesWriter.writeClasses(matches, classMatchesFile); } catch (IOException ex) { throw new Error(ex); } @@ -75,10 +87,10 @@ public class ConvertMain { }); } - private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchingFile) + private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile) throws IOException { - System.out.println("Reading matches..."); - ClassMatches classMatches = MatchesReader.readClasses(classMatchingFile); + System.out.println("Reading class matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); deobfuscators.source.setMappings(mappings); @@ -90,19 +102,90 @@ public class ConvertMain { System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath()); } - private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchingFile, final File fieldMatchingFile) + private static void computeFieldMatches(File fieldMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile) throws IOException, MappingParseException { - System.out.println("Reading matches..."); - ClassMatches classMatches = MatchesReader.readClasses(classMatchingFile); - FieldMatches fieldMatches; - if (fieldMatchingFile.exists() /* TEMP */ && false) { - // TODO - //fieldMatches = MatchesReader.readFields(fieldMatchingFile); - } else { - fieldMatches = new FieldMatches(); + System.out.println("Reading class matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + System.out.println("Reading mappings..."); + Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile)); + System.out.println("Indexing dest jar..."); + Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + + System.out.println("Writing field matches..."); + + // get the matched and unmatched field mappings + FieldMatches fieldMatches = new FieldMatches(); + + // unmatched source fields are easy + MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); + checker.dropBrokenMappings(destMappings); + for (FieldEntry destObfField : checker.getDroppedFieldMappings().keySet()) { + FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); + fieldMatches.addUnmatchedSourceField(srcObfField); + } + + // get matched fields (anything that's left after the checks/drops is matched( + for (ClassMapping classMapping : destMappings.classes()) { + collectMatchedFields(fieldMatches, classMapping, classMatches); + } + + // get unmatched dest fields + Set unmatchedDestFields = Sets.newHashSet(); + for (FieldEntry destFieldEntry : destDeobfuscator.getJarIndex().getObfFieldEntries()) { + if (!fieldMatches.isDestMatched(destFieldEntry)) { + unmatchedDestFields.add(destFieldEntry); + } + } + fieldMatches.addUnmatchedDestFields(unmatchedDestFields); + + MatchesWriter.writeFields(fieldMatches, fieldMatchesFile); + System.out.println("Wrote:\n\t" + fieldMatchesFile.getAbsolutePath()); + } + + private static void collectMatchedFields(FieldMatches fieldMatches, ClassMapping destClassMapping, ClassMatches classMatches) { + + // get the fields for this class + for (FieldMapping destFieldMapping : destClassMapping.fields()) { + FieldEntry destObfField = EntryFactory.getObfFieldEntry(destClassMapping, destFieldMapping); + FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); + fieldMatches.addMatch(srcObfField, destObfField); } + // recurse + for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { + collectMatchedFields(fieldMatches, destInnerClassMapping, classMatches); + } + } + + private static FieldEntry translate(FieldEntry in, BiMap map) { + return new FieldEntry( + map.get(in.getClassEntry()), + in.getName(), + translate(in.getType(), map) + ); + } + + private static Type translate(Type type, final BiMap map) { + return new Type(type, new ClassNameReplacer() { + @Override + public String replace(String inClassName) { + ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); + if (outClassEntry == null) { + return null; + } + return outClassEntry.getName(); + } + }); + } + + private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile) + throws IOException, MappingParseException { + + System.out.println("Reading matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + FieldMatches fieldMatches = MatchesReader.readFields(fieldMatchesFile); + // prep deobfuscators Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); deobfuscators.source.setMappings(sourceMappings); @@ -111,16 +194,14 @@ public class ConvertMain { checker.dropBrokenMappings(destMappings); deobfuscators.dest.setMappings(destMappings); - new FieldMatchingGui(classMatches, fieldMatches, checker.getDroppedFieldMappings(), deobfuscators.source, deobfuscators.dest).setSaveListener(new FieldMatchingGui.SaveListener() { + new FieldMatchingGui(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new FieldMatchingGui.SaveListener() { @Override public void save(FieldMatches matches) { - /* TODO try { - MatchesWriter.writeFields(matches, fieldMatchingFile); + MatchesWriter.writeFields(matches, fieldMatchesFile); } catch (IOException ex) { throw new Error(ex); } - */ } }); } diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index e0a8bf5..7ebbd97 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -60,6 +60,8 @@ public class JarIndex { private TranslationIndex m_translationIndex; private Multimap m_interfaces; private Map m_access; + private Multimap m_fields; + private Multimap m_behaviors; private Multimap m_methodImplementations; private Multimap> m_behaviorReferences; private Multimap> m_fieldReferences; @@ -73,6 +75,8 @@ public class JarIndex { m_translationIndex = new TranslationIndex(); m_interfaces = HashMultimap.create(); m_access = Maps.newHashMap(); + m_fields = HashMultimap.create(); + m_behaviors = HashMultimap.create(); m_methodImplementations = HashMultimap.create(); m_behaviorReferences = HashMultimap.create(); m_fieldReferences = HashMultimap.create(); @@ -97,10 +101,14 @@ public class JarIndex { for (CtClass c : JarClassIterator.classes(jar)) { ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); for (CtField field : c.getDeclaredFields()) { - m_access.put(EntryFactory.getFieldEntry(field), Access.get(field)); + FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); + m_access.put(fieldEntry, Access.get(field)); + m_fields.put(fieldEntry.getClassEntry(), fieldEntry); } for (CtBehavior behavior : c.getDeclaredBehaviors()) { - m_access.put(EntryFactory.getBehaviorEntry(behavior), Access.get(behavior)); + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + m_access.put(behaviorEntry, Access.get(behavior)); + m_behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry); } } @@ -520,6 +528,22 @@ public class JarIndex { return m_obfClassEntries; } + public Collection getObfFieldEntries() { + return m_fields.values(); + } + + public Collection getObfFieldEntries(ClassEntry classEntry) { + return m_fields.get(classEntry); + } + + public Collection getObfBehaviorEntries() { + return m_behaviors.values(); + } + + public Collection getObfBehaviorEntries(ClassEntry classEntry) { + return m_behaviors.get(classEntry); + } + public TranslationIndex getTranslationIndex() { return m_translationIndex; } diff --git a/src/cuchaz/enigma/convert/FieldMatches.java b/src/cuchaz/enigma/convert/FieldMatches.java index f78a8f5..6335974 100644 --- a/src/cuchaz/enigma/convert/FieldMatches.java +++ b/src/cuchaz/enigma/convert/FieldMatches.java @@ -5,7 +5,8 @@ import java.util.Set; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.google.common.collect.Sets; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.FieldEntry; @@ -14,22 +15,70 @@ import cuchaz.enigma.mapping.FieldEntry; public class FieldMatches { private BiMap m_matches; - private Set m_unmatchedSourceFields; + private Multimap m_unmatchedSourceFields; + private Multimap m_unmatchedDestFields; public FieldMatches() { m_matches = HashBiMap.create(); - m_unmatchedSourceFields = Sets.newHashSet(); + m_unmatchedSourceFields = HashMultimap.create(); + m_unmatchedDestFields = HashMultimap.create(); } - public void addUnmatchedSourceFields(Set fieldEntries) { - m_unmatchedSourceFields.addAll(fieldEntries); + public void addMatch(FieldEntry srcField, FieldEntry destField) { + m_matches.put(srcField, destField); } - public Collection getSourceClassesWithUnmatchedFields() { - Set classEntries = Sets.newHashSet(); - for (FieldEntry fieldEntry : m_unmatchedSourceFields) { - classEntries.add(fieldEntry.getClassEntry()); + public void addUnmatchedSourceField(FieldEntry fieldEntry) { + m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); + } + + public void addUnmatchedSourceFields(Iterable fieldEntries) { + for (FieldEntry fieldEntry : fieldEntries) { + addUnmatchedSourceField(fieldEntry); + } + } + + public void addUnmatchedDestField(FieldEntry fieldEntry) { + m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); + } + + public void addUnmatchedDestFields(Iterable fieldEntries) { + for (FieldEntry fieldEntry : fieldEntries) { + addUnmatchedDestField(fieldEntry); } - return classEntries; + } + + public Set getSourceClassesWithUnmatchedFields() { + return m_unmatchedSourceFields.keySet(); + } + + public Collection getUnmatchedSourceFields() { + return m_unmatchedSourceFields.values(); + } + + public Collection getUnmatchedSourceFields(ClassEntry sourceClass) { + return m_unmatchedSourceFields.get(sourceClass); + } + + public Collection getUnmatchedDestFields() { + return m_unmatchedDestFields.values(); + } + + public Collection getUnmatchedDestFields(ClassEntry sourceClass) { + return m_unmatchedDestFields.get(sourceClass); + } + + public BiMap matches() { + return m_matches; + } + + public boolean isDestMatched(FieldEntry destFieldEntry) { + return m_matches.containsValue(destFieldEntry); + } + + public void makeMatch(FieldEntry sourceField, FieldEntry destField) { + m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + m_unmatchedDestFields.remove(destField.getClassEntry(), destField); + m_matches.put(sourceField, destField); } } diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java index b43535c..1dd042d 100644 --- a/src/cuchaz/enigma/convert/MatchesReader.java +++ b/src/cuchaz/enigma/convert/MatchesReader.java @@ -10,6 +10,8 @@ import java.util.List; import com.beust.jcommander.internal.Lists; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.Type; public class MatchesReader { @@ -42,4 +44,42 @@ public class MatchesReader { } return entries; } + + public static FieldMatches readFields(File file) + throws IOException { + try (BufferedReader in = new BufferedReader(new FileReader(file))) { + FieldMatches matches = new FieldMatches(); + String line = null; + while ((line = in.readLine()) != null) { + readFieldMatch(matches, line); + } + return matches; + } + } + + private static void readFieldMatch(FieldMatches matches, String line) { + String[] parts = line.split(":", 2); + FieldEntry source = readField(parts[0]); + FieldEntry dest = readField(parts[1]); + if (source != null && dest != null) { + matches.addMatch(source, dest); + } else if (source != null) { + matches.addUnmatchedSourceField(source); + } else if (dest != null) { + matches.addUnmatchedDestField(dest); + } + } + + private static FieldEntry readField(String in) { + if (in.length() <= 0) { + return null; + } + String[] parts = in.split(" "); + assert(parts.length == 3); + return new FieldEntry( + new ClassEntry(parts[0]), + parts[1], + new Type(parts[2]) + ); + } } diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java index 6658e2a..6e371bc 100644 --- a/src/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/cuchaz/enigma/convert/MatchesWriter.java @@ -3,8 +3,10 @@ package cuchaz.enigma.convert; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.Map; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.FieldEntry; public class MatchesWriter { @@ -38,4 +40,40 @@ public class MatchesWriter { out.write(entry.toString()); } } + + public static void writeFields(FieldMatches fieldMatches, File file) + throws IOException { + try (FileWriter out = new FileWriter(file)) { + for (Map.Entry match : fieldMatches.matches().entrySet()) { + writeFieldMatch(out, match.getKey(), match.getValue()); + } + for (FieldEntry fieldEntry : fieldMatches.getUnmatchedSourceFields()) { + writeFieldMatch(out, fieldEntry, null); + } + for (FieldEntry fieldEntry : fieldMatches.getUnmatchedDestFields()) { + writeFieldMatch(out, null, fieldEntry); + } + } + } + + private static void writeFieldMatch(FileWriter out, FieldEntry source, FieldEntry dest) + throws IOException { + if (source != null) { + writeField(out, source); + } + out.write(":"); + if (dest != null) { + writeField(out, dest); + } + out.write("\n"); + } + + private static void writeField(FileWriter out, FieldEntry fieldEntry) + throws IOException { + out.write(fieldEntry.getClassName()); + out.write(" "); + out.write(fieldEntry.getName()); + out.write(" "); + out.write(fieldEntry.getType().toString()); + } } diff --git a/src/cuchaz/enigma/gui/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java index b674451..6943c3e 100644 --- a/src/cuchaz/enigma/gui/ClassMatchingGui.java +++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java @@ -201,13 +201,10 @@ public class ClassMatchingGui { m_sourceClassLabel = new JLabel(); m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT); - m_sourceClassLabel.setPreferredSize(new Dimension(300, 24)); m_destClassLabel = new JLabel(); m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT); - m_destClassLabel.setPreferredSize(new Dimension(300, 24)); m_matchButton = new JButton(); - m_matchButton.setPreferredSize(new Dimension(140, 24)); m_advanceCheck = new JCheckBox("Advance to next likely match"); m_advanceCheck.addActionListener(new ActionListener() { diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java index 05feb59..aa7e2db 100644 --- a/src/cuchaz/enigma/gui/CodeReader.java +++ b/src/cuchaz/enigma/gui/CodeReader.java @@ -7,12 +7,15 @@ import java.awt.event.ActionListener; import javax.swing.JEditorPane; import javax.swing.SwingUtilities; import javax.swing.Timer; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; import javax.swing.text.BadLocationException; import javax.swing.text.Highlighter.HighlightPainter; 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.mapping.ClassEntry; @@ -26,8 +29,13 @@ public class CodeReader extends JEditorPane { private static final Object m_lock = new Object(); - private SelectionHighlightPainter m_highlightPainter; + public static interface SelectionListener { + void onSelect(EntryReference reference); + } + + private SelectionHighlightPainter m_selectionHighlightPainter; private SourceIndex m_sourceIndex; + private SelectionListener m_selectionListener; public CodeReader() { @@ -38,8 +46,28 @@ public class CodeReader extends JEditorPane { DefaultSyntaxKit kit = (DefaultSyntaxKit)getEditorKit(); kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker"); - m_highlightPainter = new SelectionHighlightPainter(); + // hook events + addCaretListener(new CaretListener() { + @Override + public void caretUpdate(CaretEvent event) { + if (m_selectionListener != null && m_sourceIndex != null) { + Token token = m_sourceIndex.getReferenceToken(event.getDot()); + if (token != null) { + m_selectionListener.onSelect(m_sourceIndex.getDeobfReference(token)); + } else { + m_selectionListener.onSelect(null); + } + } + } + }); + + m_selectionHighlightPainter = new SelectionHighlightPainter(); m_sourceIndex = null; + m_selectionListener = null; + } + + public void setSelectionListener(SelectionListener val) { + m_selectionListener = val; } public void setCode(String code) { @@ -49,6 +77,10 @@ public class CodeReader extends JEditorPane { } } + public SourceIndex getSourceIndex() { + return m_sourceIndex; + } + public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) { decompileClass(classEntry, deobfuscator, null); } @@ -110,7 +142,7 @@ public class CodeReader extends JEditorPane { } public void navigateToToken(final Token token) { - navigateToToken(this, token, m_highlightPainter); + navigateToToken(this, token, m_selectionHighlightPainter); } // HACKHACK: someday we can update the main GUI to use this code reader @@ -161,4 +193,22 @@ public class CodeReader extends JEditorPane { }); timer.start(); } + + public void setHighlightedTokens(Iterable tokens, HighlightPainter painter) { + for (Token token : tokens) { + setHighlightedToken(token, painter); + } + } + + public void setHighlightedToken(Token token, HighlightPainter painter) { + try { + getHighlighter().addHighlight(token.start, token.end, painter); + } catch (BadLocationException ex) { + throw new IllegalArgumentException(ex); + } + } + + public void clearHighlights() { + getHighlighter().removeAllHighlights(); + } } diff --git a/src/cuchaz/enigma/gui/FieldMatchingGui.java b/src/cuchaz/enigma/gui/FieldMatchingGui.java index de9ba14..ef374c8 100644 --- a/src/cuchaz/enigma/gui/FieldMatchingGui.java +++ b/src/cuchaz/enigma/gui/FieldMatchingGui.java @@ -4,24 +4,34 @@ import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; -import java.util.Map; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Set; import javax.swing.BoxLayout; +import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.WindowConstants; +import javax.swing.text.Highlighter.HighlightPainter; + +import com.google.common.collect.Sets; import cuchaz.enigma.Constants; import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.SourceIndex; +import cuchaz.enigma.analysis.Token; import cuchaz.enigma.convert.ClassMatches; import cuchaz.enigma.convert.FieldMatches; import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.FieldMapping; import de.sciss.syntaxpane.DefaultSyntaxKit; @@ -36,19 +46,28 @@ public class FieldMatchingGui { private ClassSelector m_sourceClasses; private CodeReader m_sourceReader; private CodeReader m_destReader; + private JButton m_matchButton; + private JLabel m_sourceLabel; + private JLabel m_destLabel; + private HighlightPainter m_unmatchedHighlightPainter; + private HighlightPainter m_matchedHighlightPainter; private ClassMatches m_classMatches; private FieldMatches m_fieldMatches; - private Map m_droppedFieldMappings; private Deobfuscator m_sourceDeobfuscator; private Deobfuscator m_destDeobfuscator; private SaveListener m_saveListener; + private ClassEntry m_obfSourceClass; + private ClassEntry m_obfDestClass; + private FieldEntry m_obfSourceField; + private FieldEntry m_obfDestField; + private Set m_obfUnmatchedSourceFields; + private Set m_obfUnmatchedDestFields; - public FieldMatchingGui(ClassMatches classMatches, FieldMatches fieldMatches, Map droppedFieldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + public FieldMatchingGui(ClassMatches classMatches, FieldMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { m_classMatches = classMatches; m_fieldMatches = fieldMatches; - m_droppedFieldMappings = droppedFieldMappings; m_sourceDeobfuscator = sourceDeobfuscator; m_destDeobfuscator = destDeobfuscator; @@ -74,31 +93,57 @@ public class FieldMatchingGui { JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); classesPanel.add(sourceScroller); - // init fields side - JPanel fieldsPanel = new JPanel(); - fieldsPanel.setLayout(new BoxLayout(fieldsPanel, BoxLayout.PAGE_AXIS)); - fieldsPanel.setPreferredSize(new Dimension(200, 0)); - pane.add(fieldsPanel, BorderLayout.WEST); - fieldsPanel.add(new JLabel("Destination Fields")); - // init readers DefaultSyntaxKit.initKit(); m_sourceReader = new CodeReader(); + m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() { + @Override + public void onSelect(EntryReference reference) { + if (reference != null) { + onSelectSource(reference.entry); + } else { + onSelectSource(null); + } + } + }); m_destReader = new CodeReader(); + m_destReader.setSelectionListener(new CodeReader.SelectionListener() { + @Override + public void onSelect(EntryReference reference) { + if (reference != null) { + onSelectDest(reference.entry); + } else { + onSelectDest(null); + } + } + }); // init all the splits - JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, new JScrollPane(m_sourceReader)); + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader)); + splitRight.setResizeWeight(0.5); // resize 50:50 + JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight); splitLeft.setResizeWeight(0); // let the right side take all the slack - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), fieldsPanel); - splitRight.setResizeWeight(1); // let the left side take all the slack - JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight); - splitCenter.setResizeWeight(0.5); // resize 50:50 - pane.add(splitCenter, BorderLayout.CENTER); - splitCenter.resetToPreferredSizes(); + pane.add(splitLeft, BorderLayout.CENTER); + splitLeft.resetToPreferredSizes(); // init bottom panel JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new FlowLayout()); + pane.add(bottomPanel, BorderLayout.SOUTH); + + m_matchButton = new JButton("Match"); + m_matchButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + match(); + } + }); + + m_sourceLabel = new JLabel(); + bottomPanel.add(m_sourceLabel); + bottomPanel.add(m_matchButton); + m_destLabel = new JLabel(); + bottomPanel.add(m_destLabel); // show the frame pane.doLayout(); @@ -106,26 +151,139 @@ public class FieldMatchingGui { m_frame.setMinimumSize(new Dimension(640, 480)); m_frame.setVisible(true); m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); + m_matchedHighlightPainter = new DeobfuscatedHighlightPainter(); // init state m_saveListener = null; - m_fieldMatches.addUnmatchedSourceFields(droppedFieldMappings.keySet()); - m_sourceClasses.setClasses(m_fieldMatches.getSourceClassesWithUnmatchedFields()); + m_obfSourceClass = null; + m_obfDestClass = null; + m_obfSourceField = null; + m_obfDestField = null; + m_obfUnmatchedSourceFields = null; + m_obfUnmatchedDestFields = null; + updateSourceClasses(); + updateMatchButton(); } public void setSaveListener(SaveListener val) { m_saveListener = val; } + private void updateSourceClasses() { + m_sourceClasses.setClasses(m_fieldMatches.getSourceClassesWithUnmatchedFields()); + m_sourceClasses.expandAll(); + } + protected void setSourceClass(ClassEntry obfSourceClass) { - // get the matched dest class - final ClassEntry obfDestClass = m_classMatches.getUniqueMatches().get(obfSourceClass); - if (obfDestClass == null) { - throw new Error("No matching dest class for source class: " + obfSourceClass); + m_obfSourceClass = obfSourceClass; + m_obfDestClass = m_classMatches.getUniqueMatches().get(obfSourceClass); + if (m_obfDestClass == null) { + throw new Error("No matching dest class for source class: " + m_obfSourceClass); } + + updateUnmatchedFields(); + m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, new Runnable() { + @Override + public void run() { + updateSourceHighlights(); + } + }); + m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, new Runnable() { + @Override + public void run() { + updateDestHighlights(); + } + }); + } + + private void updateUnmatchedFields() { + m_obfUnmatchedSourceFields = Sets.newHashSet(m_fieldMatches.getUnmatchedSourceFields(m_obfSourceClass)); + m_obfUnmatchedDestFields = Sets.newHashSet(); + for (FieldEntry destFieldEntry : m_destDeobfuscator.getJarIndex().getObfFieldEntries(m_obfDestClass)) { + if (!m_fieldMatches.isDestMatched(destFieldEntry)) { + m_obfUnmatchedDestFields.add(destFieldEntry); + } + } + } + + protected void updateSourceHighlights() { + highlightFields(m_sourceReader, m_sourceDeobfuscator, m_obfUnmatchedSourceFields, m_fieldMatches.matches().keySet()); + } + + protected void updateDestHighlights() { + highlightFields(m_destReader, m_destDeobfuscator, m_obfUnmatchedDestFields, m_fieldMatches.matches().values()); + } + + private void highlightFields(CodeReader reader, Deobfuscator deobfuscator, Collection obfFieldEntries, Collection obfMatchedFieldEntries) { + reader.clearHighlights(); + SourceIndex index = reader.getSourceIndex(); + for (FieldEntry obfFieldEntry : obfFieldEntries) { + FieldEntry deobfFieldEntry = deobfuscator.deobfuscateEntry(obfFieldEntry); + Token token = index.getDeclarationToken(deobfFieldEntry); + if (token == null) { + System.err.println("WARNING: Can't find declaration token for " + deobfFieldEntry); + } else { + reader.setHighlightedToken( + token, + obfMatchedFieldEntries.contains(obfFieldEntry) ? m_matchedHighlightPainter : m_unmatchedHighlightPainter + ); + } + } + } + + protected void onSelectSource(Entry entry) { + m_sourceLabel.setText(""); + m_obfSourceField = null; + if (entry != null && entry instanceof FieldEntry) { + FieldEntry fieldEntry = (FieldEntry)entry; + FieldEntry obfFieldEntry = m_sourceDeobfuscator.obfuscateEntry(fieldEntry); + if (m_obfUnmatchedSourceFields.contains(obfFieldEntry)) { + m_obfSourceField = obfFieldEntry; + m_sourceLabel.setText(fieldEntry.getName() + " " + fieldEntry.getType().toString()); + } + } + updateMatchButton(); + } - m_sourceReader.decompileClass(obfSourceClass, m_sourceDeobfuscator); - m_destReader.decompileClass(obfDestClass, m_destDeobfuscator); + protected void onSelectDest(Entry entry) { + m_destLabel.setText(""); + m_obfDestField = null; + if (entry != null && entry instanceof FieldEntry) { + FieldEntry fieldEntry = (FieldEntry)entry; + FieldEntry obfFieldEntry = m_destDeobfuscator.obfuscateEntry(fieldEntry); + if (m_obfUnmatchedDestFields.contains(obfFieldEntry)) { + m_obfDestField = obfFieldEntry; + m_destLabel.setText(fieldEntry.getName() + " " + fieldEntry.getType().toString()); + } + } + updateMatchButton(); + } + + private void updateMatchButton() { + m_matchButton.setEnabled(m_obfSourceField != null && m_obfDestField != null); + } + + protected void match() { + + // update the field matches + m_fieldMatches.makeMatch(m_obfSourceField, m_obfDestField); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateUnmatchedFields(); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + private void save() { + if (m_saveListener != null) { + m_saveListener.save(m_fieldMatches); + } } } diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java index f4d62c8..333bb09 100644 --- a/src/cuchaz/enigma/mapping/EntryFactory.java +++ b/src/cuchaz/enigma/mapping/EntryFactory.java @@ -26,6 +26,10 @@ public class EntryFactory { return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry)); } + private static ClassEntry getObfClassEntry(ClassMapping classMapping) { + return new ClassEntry(classMapping.getObfFullName()); + } + public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { return new ClassEntry(classMapping.getDeobfName()); } @@ -50,6 +54,14 @@ public class EntryFactory { ); } + public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { + return new FieldEntry( + getObfClassEntry(classMapping), + fieldMapping.getObfName(), + fieldMapping.getObfType() + ); + } + public static MethodEntry getMethodEntry(CtMethod method) { return new MethodEntry( getClassEntry(method.getDeclaringClass()), @@ -148,4 +160,8 @@ public class EntryFactory { public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) { return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature()); } + + public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) { + return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping); + } } -- cgit v1.2.3 From 430df87ba5d855ca29bc53a5765a2862d2209098 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 10 Mar 2015 00:55:03 -0400 Subject: tweaks and improvements to field matching gui --- src/cuchaz/enigma/ConvertMain.java | 73 +---- src/cuchaz/enigma/Deobfuscator.java | 12 +- src/cuchaz/enigma/analysis/SourceIndex.java | 8 +- .../analysis/SourceIndexBehaviorVisitor.java | 10 +- src/cuchaz/enigma/convert/FieldMatches.java | 81 +++++- src/cuchaz/enigma/convert/MappingsConverter.java | 130 ++++++--- src/cuchaz/enigma/convert/MatchesReader.java | 22 +- src/cuchaz/enigma/convert/MatchesWriter.java | 10 + src/cuchaz/enigma/gui/ClassMatchingGui.java | 24 +- src/cuchaz/enigma/gui/CodeReader.java | 8 +- src/cuchaz/enigma/gui/FieldMatchingGui.java | 317 ++++++++++++++++----- src/cuchaz/enigma/gui/GuiTricks.java | 20 ++ 12 files changed, 495 insertions(+), 220 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index a5a00e8..a45fb35 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -4,12 +4,8 @@ import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.util.Set; import java.util.jar.JarFile; -import com.google.common.collect.BiMap; -import com.google.common.collect.Sets; - import cuchaz.enigma.convert.ClassMatches; import cuchaz.enigma.convert.FieldMatches; import cuchaz.enigma.convert.MappingsConverter; @@ -17,18 +13,11 @@ import cuchaz.enigma.convert.MatchesReader; import cuchaz.enigma.convert.MatchesWriter; import cuchaz.enigma.gui.ClassMatchingGui; import cuchaz.enigma.gui.FieldMatchingGui; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ClassMapping; -import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.EntryFactory; -import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; import cuchaz.enigma.mapping.MappingsReader; import cuchaz.enigma.mapping.MappingsWriter; -import cuchaz.enigma.mapping.Type; public class ConvertMain { @@ -63,7 +52,7 @@ public class ConvertMain { private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) throws IOException { - ClassMatches classMatches = MappingsConverter.computeMatches(sourceJar, destJar, mappings); + ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings); MatchesWriter.writeClasses(classMatches, classMatchesFile); System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath()); } @@ -115,70 +104,12 @@ public class ConvertMain { System.out.println("Writing field matches..."); // get the matched and unmatched field mappings - FieldMatches fieldMatches = new FieldMatches(); - - // unmatched source fields are easy - MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); - checker.dropBrokenMappings(destMappings); - for (FieldEntry destObfField : checker.getDroppedFieldMappings().keySet()) { - FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); - fieldMatches.addUnmatchedSourceField(srcObfField); - } - - // get matched fields (anything that's left after the checks/drops is matched( - for (ClassMapping classMapping : destMappings.classes()) { - collectMatchedFields(fieldMatches, classMapping, classMatches); - } - - // get unmatched dest fields - Set unmatchedDestFields = Sets.newHashSet(); - for (FieldEntry destFieldEntry : destDeobfuscator.getJarIndex().getObfFieldEntries()) { - if (!fieldMatches.isDestMatched(destFieldEntry)) { - unmatchedDestFields.add(destFieldEntry); - } - } - fieldMatches.addUnmatchedDestFields(unmatchedDestFields); + FieldMatches fieldMatches = MappingsConverter.computeFieldMatches(destDeobfuscator, destMappings, classMatches); MatchesWriter.writeFields(fieldMatches, fieldMatchesFile); System.out.println("Wrote:\n\t" + fieldMatchesFile.getAbsolutePath()); } - private static void collectMatchedFields(FieldMatches fieldMatches, ClassMapping destClassMapping, ClassMatches classMatches) { - - // get the fields for this class - for (FieldMapping destFieldMapping : destClassMapping.fields()) { - FieldEntry destObfField = EntryFactory.getObfFieldEntry(destClassMapping, destFieldMapping); - FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); - fieldMatches.addMatch(srcObfField, destObfField); - } - - // recurse - for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { - collectMatchedFields(fieldMatches, destInnerClassMapping, classMatches); - } - } - - private static FieldEntry translate(FieldEntry in, BiMap map) { - return new FieldEntry( - map.get(in.getClassEntry()), - in.getName(), - translate(in.getType(), map) - ); - } - - private static Type translate(Type type, final BiMap map) { - return new Type(type, new ClassNameReplacer() { - @Override - public String replace(String inClassName) { - ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); - if (outClassEntry == null) { - return null; - } - return outClassEntry.getName(); - } - }); - } - private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile) throws IOException, MappingParseException { diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 35cfd0b..f5012bd 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -225,8 +225,18 @@ public class Deobfuscator { } public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) { + return getSourceIndex(sourceTree, source, null); + } + + public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) { + // build the source index - SourceIndex index = new SourceIndex(source); + SourceIndex index; + if (ignoreBadTokens != null) { + index = new SourceIndex(source, ignoreBadTokens); + } else { + index = new SourceIndex(source); + } sourceTree.acceptVisitor(new SourceIndexVisitor(), index); // DEBUG diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java index b3fb751..8f751ef 100644 --- a/src/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/cuchaz/enigma/analysis/SourceIndex.java @@ -32,9 +32,15 @@ public class SourceIndex { private Multimap,Token> m_referenceToTokens; private Map m_declarationToToken; private List m_lineOffsets; + private boolean m_ignoreBadTokens; public SourceIndex(String source) { + this(source, true); + } + + public SourceIndex(String source, boolean ignoreBadTokens) { m_source = source; + m_ignoreBadTokens = ignoreBadTokens; m_tokenToReference = Maps.newTreeMap(); m_referenceToTokens = HashMultimap.create(); m_declarationToToken = Maps.newHashMap(); @@ -83,7 +89,7 @@ public class SourceIndex { // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) ); // if the token has a $ in it, something's wrong. Ignore this token - if (name.lastIndexOf('$') >= 0) { + if (name.lastIndexOf('$') >= 0 && m_ignoreBadTokens) { // DEBUG System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name)); return null; diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java index a9a055b..eb120b6 100644 --- a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java +++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java @@ -111,10 +111,12 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { @Override public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); - MethodDefinition methodDef = (MethodDefinition)def.getMethod(); - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(methodDef); - ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName()); - index.addDeclaration(node.getNameToken(), argumentEntry); + if (def.getMethod() instanceof MethodDefinition) { + MethodDefinition methodDef = (MethodDefinition)def.getMethod(); + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(methodDef); + ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName()); + index.addDeclaration(node.getNameToken(), argumentEntry); + } return recurse(node, index); } diff --git a/src/cuchaz/enigma/convert/FieldMatches.java b/src/cuchaz/enigma/convert/FieldMatches.java index 6335974..2973356 100644 --- a/src/cuchaz/enigma/convert/FieldMatches.java +++ b/src/cuchaz/enigma/convert/FieldMatches.java @@ -7,6 +7,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.FieldEntry; @@ -15,21 +16,29 @@ import cuchaz.enigma.mapping.FieldEntry; public class FieldMatches { private BiMap m_matches; + private Multimap m_matchedSourceFields; private Multimap m_unmatchedSourceFields; private Multimap m_unmatchedDestFields; + private Multimap m_unmatchableSourceFields; public FieldMatches() { m_matches = HashBiMap.create(); + m_matchedSourceFields = HashMultimap.create(); m_unmatchedSourceFields = HashMultimap.create(); m_unmatchedDestFields = HashMultimap.create(); + m_unmatchableSourceFields = HashMultimap.create(); } public void addMatch(FieldEntry srcField, FieldEntry destField) { - m_matches.put(srcField, destField); + boolean wasAdded = m_matches.put(srcField, destField) == null; + assert (wasAdded); + wasAdded = m_matchedSourceFields.put(srcField.getClassEntry(), srcField); + assert (wasAdded); } public void addUnmatchedSourceField(FieldEntry fieldEntry) { - m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); + boolean wasAdded = m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); + assert (wasAdded); } public void addUnmatchedSourceFields(Iterable fieldEntries) { @@ -39,7 +48,8 @@ public class FieldMatches { } public void addUnmatchedDestField(FieldEntry fieldEntry) { - m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); + boolean wasAdded = m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); + assert (wasAdded); } public void addUnmatchedDestFields(Iterable fieldEntries) { @@ -48,9 +58,21 @@ public class FieldMatches { } } + public void addUnmatchableSourceField(FieldEntry sourceField) { + boolean wasAdded = m_unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField); + assert (wasAdded); + } + public Set getSourceClassesWithUnmatchedFields() { return m_unmatchedSourceFields.keySet(); } + + public Collection getSourceClassesWithoutUnmatchedFields() { + Set out = Sets.newHashSet(); + out.addAll(m_matchedSourceFields.keySet()); + out.removeAll(m_unmatchedSourceFields.keySet()); + return out; + } public Collection getUnmatchedSourceFields() { return m_unmatchedSourceFields.values(); @@ -64,21 +86,60 @@ public class FieldMatches { return m_unmatchedDestFields.values(); } - public Collection getUnmatchedDestFields(ClassEntry sourceClass) { - return m_unmatchedDestFields.get(sourceClass); + public Collection getUnmatchedDestFields(ClassEntry destClass) { + return m_unmatchedDestFields.get(destClass); + } + + public Collection getUnmatchableSourceFields() { + return m_unmatchableSourceFields.values(); + } + + public boolean hasSource(FieldEntry fieldEntry) { + return m_matches.containsKey(fieldEntry) || m_unmatchedSourceFields.containsValue(fieldEntry); + } + + public boolean hasDest(FieldEntry fieldEntry) { + return m_matches.containsValue(fieldEntry) || m_unmatchedDestFields.containsValue(fieldEntry); } public BiMap matches() { return m_matches; } + + public boolean isMatchedSourceField(FieldEntry sourceField) { + return m_matches.containsKey(sourceField); + } - public boolean isDestMatched(FieldEntry destFieldEntry) { - return m_matches.containsValue(destFieldEntry); + public boolean isMatchedDestField(FieldEntry destField) { + return m_matches.containsValue(destField); } public void makeMatch(FieldEntry sourceField, FieldEntry destField) { - m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); - m_unmatchedDestFields.remove(destField.getClassEntry(), destField); - m_matches.put(sourceField, destField); + boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + wasRemoved = m_unmatchedDestFields.remove(destField.getClassEntry(), destField); + assert (wasRemoved); + addMatch(sourceField, destField); + } + + public boolean isMatched(FieldEntry sourceField, FieldEntry destField) { + FieldEntry match = m_matches.get(sourceField); + return match != null && match.equals(destField); + } + + public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) { + boolean wasRemoved = m_matches.remove(sourceField) != null; + assert (wasRemoved); + wasRemoved = m_matchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + addUnmatchedSourceField(sourceField); + addUnmatchedDestField(destField); + } + + public void makeSourceUnmatchable(FieldEntry sourceField) { + assert(!isMatchedSourceField(sourceField)); + boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + addUnmatchableSourceField(sourceField); } } diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 667ee9d..9ab1baa 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -17,6 +17,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.jar.JarFile; import com.beust.jcommander.internal.Lists; @@ -24,6 +25,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.JarIndex; @@ -31,13 +33,17 @@ import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsChecker; import cuchaz.enigma.mapping.MethodMapping; +import cuchaz.enigma.mapping.Type; public class MappingsConverter { - public static ClassMatches computeMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { + public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { // index jars System.out.println("Indexing source jar..."); @@ -245,47 +251,101 @@ public class MappingsConverter { mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); } } - - /* TODO: after we get a mapping, check to see that the other entries match - public static void checkMethods() { + + public static FieldMatches computeFieldMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + + FieldMatches fieldMatches = new FieldMatches(); + + // unmatched source fields are easy + MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); + checker.dropBrokenMappings(destMappings); + for (FieldEntry destObfField : checker.getDroppedFieldMappings().keySet()) { + FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); + fieldMatches.addUnmatchedSourceField(srcObfField); + } - // check the method matches - System.out.println("Checking methods..."); - for (ClassMapping classMapping : mappings.classes()) { - ClassEntry classEntry = new ClassEntry(classMapping.getObfFullName()); - for (MethodMapping methodMapping : classMapping.methods()) { + // get matched fields (anything that's left after the checks/drops is matched( + for (ClassMapping classMapping : destMappings.classes()) { + collectMatchedFields(fieldMatches, classMapping, classMatches); + } + + // get unmatched dest fields + for (FieldEntry destFieldEntry : destDeobfuscator.getJarIndex().getObfFieldEntries()) { + if (!fieldMatches.isMatchedDestField(destFieldEntry)) { + fieldMatches.addUnmatchedDestField(destFieldEntry); + } + } + + System.out.println("Automatching " + fieldMatches.getUnmatchedSourceFields().size() + " unmatched source fields..."); + + // go through the unmatched source fields and try to pick out the easy matches + for (ClassEntry obfSourceClass : Lists.newArrayList(fieldMatches.getSourceClassesWithUnmatchedFields())) { + for (FieldEntry obfSourceField : Lists.newArrayList(fieldMatches.getUnmatchedSourceFields(obfSourceClass))) { - // skip constructors - if (methodMapping.getObfName().equals("")) { - continue; - } + // get the possible dest matches + ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - MethodEntry methodEntry = new MethodEntry( - classEntry, - methodMapping.getObfName(), - methodMapping.getObfSignature() - ); - if (!destIndex.containsObfBehavior(methodEntry)) { - System.err.println("WARNING: method doesn't match: " + methodEntry); - - // TODO: show methods if needed - // show the available methods - System.err.println("\tAvailable dest methods:"); - CtClass c = destLoader.loadClass(classMapping.getObfFullName()); - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); - } - - System.err.println("\tAvailable source methods:"); - c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfFullName())); - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); + // filter by type + Set obfDestFields = Sets.newHashSet(); + for (FieldEntry obfDestField : fieldMatches.getUnmatchedDestFields(obfDestClass)) { + Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); + if (translatedDestType.equals(obfSourceField.getType())) { + obfDestFields.add(obfDestField); } } + + if (obfDestFields.size() == 1) { + // make the easy match + FieldEntry obfDestField = obfDestFields.iterator().next(); + fieldMatches.makeMatch(obfSourceField, obfDestField); + } else if (obfDestFields.isEmpty()) { + // no match is possible =( + fieldMatches.makeSourceUnmatchable(obfSourceField); + } } } - System.out.println("Done!"); + System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source fields", + fieldMatches.getUnmatchedSourceFields().size(), + fieldMatches.getUnmatchableSourceFields().size() + )); + + return fieldMatches; + } + + private static void collectMatchedFields(FieldMatches fieldMatches, ClassMapping destClassMapping, ClassMatches classMatches) { + + // get the fields for this class + for (FieldMapping destFieldMapping : destClassMapping.fields()) { + FieldEntry destObfField = EntryFactory.getObfFieldEntry(destClassMapping, destFieldMapping); + FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); + fieldMatches.addMatch(srcObfField, destObfField); + } + + // recurse + for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { + collectMatchedFields(fieldMatches, destInnerClassMapping, classMatches); + } + } + + private static FieldEntry translate(FieldEntry in, BiMap map) { + return new FieldEntry( + map.get(in.getClassEntry()), + in.getName(), + translate(in.getType(), map) + ); + } + + private static Type translate(Type type, final BiMap map) { + return new Type(type, new ClassNameReplacer() { + @Override + public String replace(String inClassName) { + ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); + if (outClassEntry == null) { + return null; + } + return outClassEntry.getName(); + } + }); } - */ } diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java index 1dd042d..921ab1d 100644 --- a/src/cuchaz/enigma/convert/MatchesReader.java +++ b/src/cuchaz/enigma/convert/MatchesReader.java @@ -58,15 +58,19 @@ public class MatchesReader { } private static void readFieldMatch(FieldMatches matches, String line) { - String[] parts = line.split(":", 2); - FieldEntry source = readField(parts[0]); - FieldEntry dest = readField(parts[1]); - if (source != null && dest != null) { - matches.addMatch(source, dest); - } else if (source != null) { - matches.addUnmatchedSourceField(source); - } else if (dest != null) { - matches.addUnmatchedDestField(dest); + if (line.startsWith("!")) { + matches.addUnmatchableSourceField(readField(line.substring(1))); + } else { + String[] parts = line.split(":", 2); + FieldEntry source = readField(parts[0]); + FieldEntry dest = readField(parts[1]); + if (source != null && dest != null) { + matches.addMatch(source, dest); + } else if (source != null) { + matches.addUnmatchedSourceField(source); + } else if (dest != null) { + matches.addUnmatchedDestField(dest); + } } } diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java index 6e371bc..2118dd0 100644 --- a/src/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/cuchaz/enigma/convert/MatchesWriter.java @@ -53,6 +53,9 @@ public class MatchesWriter { for (FieldEntry fieldEntry : fieldMatches.getUnmatchedDestFields()) { writeFieldMatch(out, null, fieldEntry); } + for (FieldEntry fieldEntry : fieldMatches.getUnmatchableSourceFields()) { + writeUnmatchableField(out, fieldEntry); + } } } @@ -68,6 +71,13 @@ public class MatchesWriter { out.write("\n"); } + private static void writeUnmatchableField(FileWriter out, FieldEntry fieldEntry) + throws IOException { + out.write("!"); + writeField(out, fieldEntry); + out.write("\n"); + } + private static void writeField(FileWriter out, FieldEntry fieldEntry) throws IOException { out.write(fieldEntry.getClassName()); diff --git a/src/cuchaz/enigma/gui/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java index 6943c3e..9e210ec 100644 --- a/src/cuchaz/enigma/gui/ClassMatchingGui.java +++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java @@ -6,7 +6,6 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -421,17 +420,17 @@ public class ClassMatchingGui { boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest); - deactivateButton(m_matchButton); + GuiTricks.deactivateButton(m_matchButton); if (twoSelected) { if (isMatched) { - activateButton(m_matchButton, "Unmatch", new ActionListener() { + GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() { @Override public void actionPerformed(ActionEvent event) { onUnmatchClick(); } }); } else if (canMatch) { - activateButton(m_matchButton, "Match", new ActionListener() { + GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() { @Override public void actionPerformed(ActionEvent event) { onMatchClick(); @@ -440,23 +439,6 @@ public class ClassMatchingGui { } } } - - private void deactivateButton(JButton button) { - button.setEnabled(false); - button.setText(""); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { - button.removeActionListener(listener); - } - } - - private void activateButton(JButton button, String text, ActionListener newListener) { - button.setText(text); - button.setEnabled(true); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { - button.removeActionListener(listener); - } - button.addActionListener(newListener); - } private void onMatchClick() { // precondition: source and dest classes are set correctly diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java index aa7e2db..743ef2e 100644 --- a/src/cuchaz/enigma/gui/CodeReader.java +++ b/src/cuchaz/enigma/gui/CodeReader.java @@ -85,7 +85,11 @@ public class CodeReader extends JEditorPane { decompileClass(classEntry, deobfuscator, null); } - public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Runnable callback) { + 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); @@ -109,7 +113,7 @@ public class CodeReader extends JEditorPane { CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); String source = deobfuscator.getSource(sourceTree); setCode(source); - m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source); + m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); if (callback != null) { callback.run(); diff --git a/src/cuchaz/enigma/gui/FieldMatchingGui.java b/src/cuchaz/enigma/gui/FieldMatchingGui.java index ef374c8..3f4a378 100644 --- a/src/cuchaz/enigma/gui/FieldMatchingGui.java +++ b/src/cuchaz/enigma/gui/FieldMatchingGui.java @@ -6,20 +6,26 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; import java.util.Collection; -import java.util.Set; +import java.util.List; +import java.util.Map; import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.WindowConstants; import javax.swing.text.Highlighter.HighlightPainter; -import com.google.common.collect.Sets; +import com.beust.jcommander.internal.Lists; +import com.beust.jcommander.internal.Maps; import cuchaz.enigma.Constants; import cuchaz.enigma.Deobfuscator; @@ -37,16 +43,49 @@ import de.sciss.syntaxpane.DefaultSyntaxKit; public class FieldMatchingGui { + private static enum SourceType { + Matched { + + @Override + public Collection getObfSourceClasses(FieldMatches matches) { + return matches.getSourceClassesWithoutUnmatchedFields(); + } + }, + Unmatched { + + @Override + public Collection getObfSourceClasses(FieldMatches matches) { + return matches.getSourceClassesWithUnmatchedFields(); + } + }; + + public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { + JRadioButton button = new JRadioButton(name(), this == getDefault()); + button.setActionCommand(name()); + button.addActionListener(listener); + group.add(button); + return button; + } + + public abstract Collection getObfSourceClasses(FieldMatches matches); + + public static SourceType getDefault() { + return values()[0]; + } + } + public static interface SaveListener { public void save(FieldMatches matches); } // controls private JFrame m_frame; + private Map m_sourceTypeButtons; private ClassSelector m_sourceClasses; private CodeReader m_sourceReader; private CodeReader m_destReader; private JButton m_matchButton; + private JButton m_unmatchableButton; private JLabel m_sourceLabel; private JLabel m_destLabel; private HighlightPainter m_unmatchedHighlightPainter; @@ -57,12 +96,11 @@ public class FieldMatchingGui { private Deobfuscator m_sourceDeobfuscator; private Deobfuscator m_destDeobfuscator; private SaveListener m_saveListener; + private SourceType m_sourceType; private ClassEntry m_obfSourceClass; private ClassEntry m_obfDestClass; private FieldEntry m_obfSourceField; private FieldEntry m_obfDestField; - private Set m_obfUnmatchedSourceFields; - private Set m_obfUnmatchedDestFields; public FieldMatchingGui(ClassMatches classMatches, FieldMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { @@ -83,6 +121,24 @@ public class FieldMatchingGui { pane.add(classesPanel, BorderLayout.WEST); classesPanel.add(new JLabel("Classes")); + // init source type radios + JPanel sourceTypePanel = new JPanel(); + classesPanel.add(sourceTypePanel); + sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); + ActionListener sourceTypeListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + setSourceType(SourceType.valueOf(event.getActionCommand())); + } + }; + ButtonGroup sourceTypeButtons = new ButtonGroup(); + m_sourceTypeButtons = Maps.newHashMap(); + for (SourceType sourceType : SourceType.values()) { + JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); + m_sourceTypeButtons.put(sourceType, button); + sourceTypePanel.add(button); + } + m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); m_sourceClasses.setListener(new ClassSelectionListener() { @Override @@ -117,6 +173,20 @@ public class FieldMatchingGui { } } }); + + // add key bindings + KeyAdapter keyListener = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_M: + m_matchButton.doClick(); + break; + } + } + }; + m_sourceReader.addKeyListener(keyListener); + m_destReader.addKeyListener(keyListener); // init all the splits JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader)); @@ -131,17 +201,13 @@ public class FieldMatchingGui { bottomPanel.setLayout(new FlowLayout()); pane.add(bottomPanel, BorderLayout.SOUTH); - m_matchButton = new JButton("Match"); - m_matchButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - match(); - } - }); + m_matchButton = new JButton(); + m_unmatchableButton = new JButton(); m_sourceLabel = new JLabel(); bottomPanel.add(m_sourceLabel); bottomPanel.add(m_matchButton); + bottomPanel.add(m_unmatchableButton); m_destLabel = new JLabel(); bottomPanel.add(m_destLabel); @@ -161,10 +227,13 @@ public class FieldMatchingGui { m_obfDestClass = null; m_obfSourceField = null; m_obfDestField = null; - m_obfUnmatchedSourceFields = null; - m_obfUnmatchedDestFields = null; + setSourceType(SourceType.getDefault()); + updateButtons(); + } + + protected void setSourceType(SourceType val) { + m_sourceType = val; updateSourceClasses(); - updateMatchButton(); } public void setSaveListener(SaveListener val) { @@ -172,26 +241,41 @@ public class FieldMatchingGui { } private void updateSourceClasses() { - m_sourceClasses.setClasses(m_fieldMatches.getSourceClassesWithUnmatchedFields()); - m_sourceClasses.expandAll(); + + String selectedPackage = m_sourceClasses.getSelectedPackage(); + + List deobfClassEntries = Lists.newArrayList(); + for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_fieldMatches)) { + deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry)); + } + m_sourceClasses.setClasses(deobfClassEntries); + + if (selectedPackage != null) { + m_sourceClasses.expandPackage(selectedPackage); + } + + for (SourceType sourceType : SourceType.values()) { + m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", + sourceType.name(), sourceType.getObfSourceClasses(m_fieldMatches).size() + )); + } } - protected void setSourceClass(ClassEntry obfSourceClass) { + protected void setSourceClass(ClassEntry sourceClass) { - m_obfSourceClass = obfSourceClass; - m_obfDestClass = m_classMatches.getUniqueMatches().get(obfSourceClass); + m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); + m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass); if (m_obfDestClass == null) { throw new Error("No matching dest class for source class: " + m_obfSourceClass); } - updateUnmatchedFields(); - m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, new Runnable() { + m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() { @Override public void run() { updateSourceHighlights(); } }); - m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, new Runnable() { + m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() { @Override public void run() { updateDestHighlights(); @@ -199,71 +283,145 @@ public class FieldMatchingGui { }); } - private void updateUnmatchedFields() { - m_obfUnmatchedSourceFields = Sets.newHashSet(m_fieldMatches.getUnmatchedSourceFields(m_obfSourceClass)); - m_obfUnmatchedDestFields = Sets.newHashSet(); - for (FieldEntry destFieldEntry : m_destDeobfuscator.getJarIndex().getObfFieldEntries(m_obfDestClass)) { - if (!m_fieldMatches.isDestMatched(destFieldEntry)) { - m_obfUnmatchedDestFields.add(destFieldEntry); - } - } - } - protected void updateSourceHighlights() { - highlightFields(m_sourceReader, m_sourceDeobfuscator, m_obfUnmatchedSourceFields, m_fieldMatches.matches().keySet()); + highlightFields(m_sourceReader, m_sourceDeobfuscator, m_fieldMatches.matches().keySet(), m_fieldMatches.getUnmatchedSourceFields()); } protected void updateDestHighlights() { - highlightFields(m_destReader, m_destDeobfuscator, m_obfUnmatchedDestFields, m_fieldMatches.matches().values()); + highlightFields(m_destReader, m_destDeobfuscator, m_fieldMatches.matches().values(), m_fieldMatches.getUnmatchedDestFields()); } - private void highlightFields(CodeReader reader, Deobfuscator deobfuscator, Collection obfFieldEntries, Collection obfMatchedFieldEntries) { + private void highlightFields(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedFields, Collection obfUnmatchedFields) { reader.clearHighlights(); SourceIndex index = reader.getSourceIndex(); - for (FieldEntry obfFieldEntry : obfFieldEntries) { + + // matched fields + for (FieldEntry obfFieldEntry : obfMatchedFields) { FieldEntry deobfFieldEntry = deobfuscator.deobfuscateEntry(obfFieldEntry); Token token = index.getDeclarationToken(deobfFieldEntry); - if (token == null) { - System.err.println("WARNING: Can't find declaration token for " + deobfFieldEntry); - } else { - reader.setHighlightedToken( - token, - obfMatchedFieldEntries.contains(obfFieldEntry) ? m_matchedHighlightPainter : m_unmatchedHighlightPainter - ); + if (token != null) { + reader.setHighlightedToken(token, m_matchedHighlightPainter); + } + } + + // unmatched fields + for (FieldEntry obfFieldEntry : obfUnmatchedFields) { + FieldEntry deobfFieldEntry = deobfuscator.deobfuscateEntry(obfFieldEntry); + Token token = index.getDeclarationToken(deobfFieldEntry); + if (token != null) { + reader.setHighlightedToken(token, m_unmatchedHighlightPainter); } } } - protected void onSelectSource(Entry entry) { - m_sourceLabel.setText(""); - m_obfSourceField = null; - if (entry != null && entry instanceof FieldEntry) { - FieldEntry fieldEntry = (FieldEntry)entry; - FieldEntry obfFieldEntry = m_sourceDeobfuscator.obfuscateEntry(fieldEntry); - if (m_obfUnmatchedSourceFields.contains(obfFieldEntry)) { - m_obfSourceField = obfFieldEntry; - m_sourceLabel.setText(fieldEntry.getName() + " " + fieldEntry.getType().toString()); + private boolean isSelectionMatched() { + return m_obfSourceField != null && m_obfDestField != null + && m_fieldMatches.isMatched(m_obfSourceField, m_obfDestField); + } + + protected void onSelectSource(Entry source) { + + // start with no selection + if (isSelectionMatched()) { + setDest(null); + } + setSource(null); + + // then look for a valid source selection + if (source != null && source instanceof FieldEntry) { + FieldEntry sourceField = (FieldEntry)source; + FieldEntry obfSourceField = m_sourceDeobfuscator.obfuscateEntry(sourceField); + if (m_fieldMatches.hasSource(obfSourceField)) { + setSource(obfSourceField); + + // look for a matched dest too + FieldEntry obfDestField = m_fieldMatches.matches().get(obfSourceField); + if (obfDestField != null) { + setDest(obfDestField); + } } } - updateMatchButton(); + + updateButtons(); } - protected void onSelectDest(Entry entry) { - m_destLabel.setText(""); - m_obfDestField = null; - if (entry != null && entry instanceof FieldEntry) { - FieldEntry fieldEntry = (FieldEntry)entry; - FieldEntry obfFieldEntry = m_destDeobfuscator.obfuscateEntry(fieldEntry); - if (m_obfUnmatchedDestFields.contains(obfFieldEntry)) { - m_obfDestField = obfFieldEntry; - m_destLabel.setText(fieldEntry.getName() + " " + fieldEntry.getType().toString()); + protected void onSelectDest(Entry dest) { + + // start with no selection + if (isSelectionMatched()) { + setSource(null); + } + setDest(null); + + // then look for a valid dest selection + if (dest != null && dest instanceof FieldEntry) { + FieldEntry destField = (FieldEntry)dest; + FieldEntry obfDestField = m_destDeobfuscator.obfuscateEntry(destField); + if (m_fieldMatches.hasDest(obfDestField)) { + setDest(obfDestField); + + // look for a matched source too + FieldEntry obfSourceField = m_fieldMatches.matches().inverse().get(obfDestField); + if (obfSourceField != null) { + setSource(obfSourceField); + } } } - updateMatchButton(); + + updateButtons(); } + + private void setSource(FieldEntry obfField) { + if (obfField == null) { + m_obfSourceField = obfField; + m_sourceLabel.setText(""); + } else { + m_obfSourceField = obfField; + FieldEntry deobfField = m_sourceDeobfuscator.deobfuscateEntry(obfField); + m_sourceLabel.setText(deobfField.getName() + " " + deobfField.getType().toString()); + } + } + + private void setDest(FieldEntry obfField) { + if (obfField == null) { + m_obfDestField = obfField; + m_destLabel.setText(""); + } else { + m_obfDestField = obfField; + FieldEntry deobfField = m_destDeobfuscator.deobfuscateEntry(obfField); + m_destLabel.setText(deobfField.getName() + " " + deobfField.getType().toString()); + } + } + + private void updateButtons() { - private void updateMatchButton() { - m_matchButton.setEnabled(m_obfSourceField != null && m_obfDestField != null); + GuiTricks.deactivateButton(m_matchButton); + GuiTricks.deactivateButton(m_unmatchableButton); + + if (m_obfSourceField != null && m_obfDestField != null) { + if (m_fieldMatches.isMatched(m_obfSourceField, m_obfDestField)) { + GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + unmatch(); + } + }); + } else if (!m_fieldMatches.isMatchedSourceField(m_obfSourceField) && !m_fieldMatches.isMatchedDestField(m_obfDestField)) { + GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + match(); + } + }); + } + } else if (m_obfSourceField != null) { + GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + unmatchable(); + } + }); + } } protected void match() { @@ -275,7 +433,34 @@ public class FieldMatchingGui { // update the ui onSelectSource(null); onSelectDest(null); - updateUnmatchedFields(); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatch() { + + // update the field matches + m_fieldMatches.unmakeMatch(m_obfSourceField, m_obfDestField); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatchable() { + + // update the field matches + m_fieldMatches.makeSourceUnmatchable(m_obfSourceField); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); updateSourceHighlights(); updateDestHighlights(); updateSourceClasses(); diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java index df9e221..7e539a1 100644 --- a/src/cuchaz/enigma/gui/GuiTricks.java +++ b/src/cuchaz/enigma/gui/GuiTricks.java @@ -11,8 +11,11 @@ package cuchaz.enigma.gui; import java.awt.Font; +import java.awt.event.ActionListener; import java.awt.event.MouseEvent; +import java.util.Arrays; +import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.ToolTipManager; @@ -33,4 +36,21 @@ public class GuiTricks { manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); manager.setInitialDelay(oldDelay); } + + public static void deactivateButton(JButton button) { + button.setEnabled(false); + button.setText(""); + for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + button.removeActionListener(listener); + } + } + + public static void activateButton(JButton button, String text, ActionListener newListener) { + button.setText(text); + button.setEnabled(true); + for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + button.removeActionListener(listener); + } + button.addActionListener(newListener); + } } -- cgit v1.2.3 From 0a7b3da29f1ff53351c940eebd1c40c1bfc91e29 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 10 Mar 2015 00:56:40 -0400 Subject: nothing of consequence --- src/cuchaz/enigma/ConvertMain.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index a45fb35..e012f19 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -35,11 +35,11 @@ public class ConvertMain { File classMatchesFile = new File(inMappingsFile.getName() + ".class.matches"); File fieldMatchesFile = new File(inMappingsFile.getName() + ".field.matches"); - //computeClassMatches(classMatchingFile, sourceJar, destJar, mappings); - //editClasssMatches(classMatchingFile, sourceJar, destJar, mappings); - //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchingFile); + //computeClassMatches(classMatchesFile, sourceJar, destJar, mappings); + editClasssMatches(classMatchesFile, sourceJar, destJar, mappings); + //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile); //computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile); - editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile); + //editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile); /* TODO // write out the converted mappings -- cgit v1.2.3 From e33b4003a5c423894e7aef575faff359dd1d33b1 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 11 Mar 2015 11:03:16 -0400 Subject: generalized field matching added method matching --- src/cuchaz/enigma/ConvertMain.java | 82 +++- src/cuchaz/enigma/convert/MappingsConverter.java | 196 ++++++--- src/cuchaz/enigma/convert/MatchesReader.java | 46 ++- src/cuchaz/enigma/convert/MatchesWriter.java | 50 ++- src/cuchaz/enigma/convert/MemberMatches.java | 145 +++++++ src/cuchaz/enigma/gui/FieldMatchingGui.java | 474 ---------------------- src/cuchaz/enigma/gui/MemberMatchingGui.java | 489 +++++++++++++++++++++++ src/cuchaz/enigma/mapping/ClassMapping.java | 4 + src/cuchaz/enigma/mapping/EntryFactory.java | 12 + src/cuchaz/enigma/mapping/FieldMapping.java | 7 +- src/cuchaz/enigma/mapping/MemberMapping.java | 6 + src/cuchaz/enigma/mapping/MethodMapping.java | 11 +- 12 files changed, 957 insertions(+), 565 deletions(-) create mode 100644 src/cuchaz/enigma/convert/MemberMatches.java delete mode 100644 src/cuchaz/enigma/gui/FieldMatchingGui.java create mode 100644 src/cuchaz/enigma/gui/MemberMatchingGui.java create mode 100644 src/cuchaz/enigma/mapping/MemberMapping.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index e012f19..c3a2ad5 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -7,12 +7,14 @@ import java.io.IOException; import java.util.jar.JarFile; import cuchaz.enigma.convert.ClassMatches; -import cuchaz.enigma.convert.FieldMatches; import cuchaz.enigma.convert.MappingsConverter; import cuchaz.enigma.convert.MatchesReader; import cuchaz.enigma.convert.MatchesWriter; +import cuchaz.enigma.convert.MemberMatches; import cuchaz.enigma.gui.ClassMatchingGui; -import cuchaz.enigma.gui.FieldMatchingGui; +import cuchaz.enigma.gui.MemberMatchingGui; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; @@ -34,13 +36,21 @@ public class ConvertMain { Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); File classMatchesFile = new File(inMappingsFile.getName() + ".class.matches"); File fieldMatchesFile = new File(inMappingsFile.getName() + ".field.matches"); + File methodMatchesFile = new File(inMappingsFile.getName() + ".method.matches"); + // match classes //computeClassMatches(classMatchesFile, sourceJar, destJar, mappings); - editClasssMatches(classMatchesFile, sourceJar, destJar, mappings); + //editClasssMatches(classMatchesFile, sourceJar, destJar, mappings); //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile); + + // match fields //computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile); //editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile); + // match methods/constructors + //computeMethodMatches(methodMatchesFile, destJar, outMappingsFile, classMatchesFile); + editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile); + /* TODO // write out the converted mappings FileWriter writer = new FileWriter(outMappingsFile); @@ -91,7 +101,7 @@ public class ConvertMain { System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath()); } - private static void computeFieldMatches(File fieldMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile) + private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile) throws IOException, MappingParseException { System.out.println("Reading class matches..."); @@ -101,13 +111,13 @@ public class ConvertMain { System.out.println("Indexing dest jar..."); Deobfuscator destDeobfuscator = new Deobfuscator(destJar); - System.out.println("Writing field matches..."); + System.out.println("Writing matches..."); - // get the matched and unmatched field mappings - FieldMatches fieldMatches = MappingsConverter.computeFieldMatches(destDeobfuscator, destMappings, classMatches); + // get the matched and unmatched mappings + MemberMatches fieldMatches = MappingsConverter.computeFieldMatches(destDeobfuscator, destMappings, classMatches); - MatchesWriter.writeFields(fieldMatches, fieldMatchesFile); - System.out.println("Wrote:\n\t" + fieldMatchesFile.getAbsolutePath()); + MatchesWriter.writeMembers(fieldMatches, memberMatchesFile); + System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath()); } private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile) @@ -115,7 +125,7 @@ public class ConvertMain { System.out.println("Reading matches..."); ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); - FieldMatches fieldMatches = MatchesReader.readFields(fieldMatchesFile); + MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); // prep deobfuscators Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); @@ -125,18 +135,64 @@ public class ConvertMain { checker.dropBrokenMappings(destMappings); deobfuscators.dest.setMappings(destMappings); - new FieldMatchingGui(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new FieldMatchingGui.SaveListener() { + new MemberMatchingGui(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener() { @Override - public void save(FieldMatches matches) { + public void save(MemberMatches matches) { try { - MatchesWriter.writeFields(matches, fieldMatchesFile); + MatchesWriter.writeMembers(matches, fieldMatchesFile); } catch (IOException ex) { throw new Error(ex); } } }); } + + private static void computeMethodMatches(File methodMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile) + throws IOException, MappingParseException { + + System.out.println("Reading class matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + System.out.println("Reading mappings..."); + Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile)); + System.out.println("Indexing dest jar..."); + Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + + System.out.println("Writing method matches..."); + + // get the matched and unmatched mappings + MemberMatches methodMatches = MappingsConverter.computeBehaviorMatches(destDeobfuscator, destMappings, classMatches); + + MatchesWriter.writeMembers(methodMatches, methodMatchesFile); + System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath()); + } + private static void editMethodMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File methodMatchesFile) + throws IOException, MappingParseException { + + System.out.println("Reading matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + MemberMatches methodMatches = MatchesReader.readMembers(methodMatchesFile); + + // prep deobfuscators + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(sourceMappings); + Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile)); + MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); + checker.dropBrokenMappings(destMappings); + deobfuscators.dest.setMappings(destMappings); + + new MemberMatchingGui(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener() { + @Override + public void save(MemberMatches matches) { + try { + MatchesWriter.writeMembers(matches, methodMatchesFile); + } catch (IOException ex) { + throw new Error(ex); + } + } + }); + } + private static class Deobfuscators { public Deobfuscator source; diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 9ab1baa..2987ea0 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -11,12 +11,12 @@ package cuchaz.enigma.convert; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.jar.JarFile; @@ -30,15 +30,20 @@ import com.google.common.collect.Sets; import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; +import cuchaz.enigma.mapping.MemberMapping; +import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.MethodMapping; +import cuchaz.enigma.mapping.Signature; import cuchaz.enigma.mapping.Type; public class MappingsConverter { @@ -124,8 +129,8 @@ public class MappingsConverter { public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { // sort the unique matches by size of inner class chain - Multimap> matchesByDestChainSize = HashMultimap.create(); - for (Entry match : matches.getUniqueMatches().entrySet()) { + Multimap> matchesByDestChainSize = HashMultimap.create(); + for (java.util.Map.Entry match : matches.getUniqueMatches().entrySet()) { int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size(); matchesByDestChainSize.put(chainSize, match); } @@ -135,7 +140,7 @@ public class MappingsConverter { List chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); Collections.sort(chainSizes); for (int chainSize : chainSizes) { - for (Entry match : matchesByDestChainSize.get(chainSize)) { + for (java.util.Map.Entry match : matchesByDestChainSize.get(chainSize)) { // get class info ClassEntry obfSourceClassEntry = match.getKey(); @@ -251,89 +256,172 @@ public class MappingsConverter { mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); } } + + public static interface Doer { + Collection getDroppedEntries(MappingsChecker checker); + Collection getObfEntries(JarIndex jarIndex); + Collection> getMappings(ClassMapping destClassMapping); + Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); + } + + public static MemberMatches computeFieldMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + return computeMemberMatches(destDeobfuscator, destMappings, classMatches, new Doer() { + + @Override + public Collection getDroppedEntries(MappingsChecker checker) { + return checker.getDroppedFieldMappings().keySet(); + } + + @Override + public Collection getObfEntries(JarIndex jarIndex) { + return jarIndex.getObfFieldEntries(); + } + + @Override + public Collection> getMappings(ClassMapping destClassMapping) { + return (Collection>)destClassMapping.fields(); + } + + @Override + public Set filterEntries(Collection obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) { + Set out = Sets.newHashSet(); + for (FieldEntry obfDestField : obfDestFields) { + Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); + if (translatedDestType.equals(obfSourceField.getType())) { + out.add(obfDestField); + } + } + return out; + } + }); + } + + public static MemberMatches computeBehaviorMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + return computeMemberMatches(destDeobfuscator, destMappings, classMatches, new Doer() { + + @Override + public Collection getDroppedEntries(MappingsChecker checker) { + return checker.getDroppedMethodMappings().keySet(); + } + + @Override + public Collection getObfEntries(JarIndex jarIndex) { + return jarIndex.getObfBehaviorEntries(); + } - public static FieldMatches computeFieldMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + @Override + public Collection> getMappings(ClassMapping destClassMapping) { + return (Collection>)destClassMapping.methods(); + } + + @Override + public Set filterEntries(Collection obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) { + Set out = Sets.newHashSet(); + for (BehaviorEntry obfDestField : obfDestFields) { + Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); + if (translatedDestSignature == null && obfSourceField.getSignature() == null) { + out.add(obfDestField); + } else if (translatedDestSignature == null || obfSourceField.getSignature() == null) { + // skip it + } else if (translatedDestSignature.equals(obfSourceField.getSignature())) { + out.add(obfDestField); + } + } + return out; + } + }); + } + + public static MemberMatches computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer doer) { - FieldMatches fieldMatches = new FieldMatches(); + MemberMatches memberMatches = new MemberMatches(); // unmatched source fields are easy MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); checker.dropBrokenMappings(destMappings); - for (FieldEntry destObfField : checker.getDroppedFieldMappings().keySet()) { - FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); - fieldMatches.addUnmatchedSourceField(srcObfField); + for (T destObfEntry : doer.getDroppedEntries(checker)) { + T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); + memberMatches.addUnmatchedSourceEntry(srcObfEntry); } // get matched fields (anything that's left after the checks/drops is matched( for (ClassMapping classMapping : destMappings.classes()) { - collectMatchedFields(fieldMatches, classMapping, classMatches); + collectMatchedFields(memberMatches, classMapping, classMatches, doer); } // get unmatched dest fields - for (FieldEntry destFieldEntry : destDeobfuscator.getJarIndex().getObfFieldEntries()) { - if (!fieldMatches.isMatchedDestField(destFieldEntry)) { - fieldMatches.addUnmatchedDestField(destFieldEntry); + for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { + if (!memberMatches.isMatchedDestEntry(destEntry)) { + memberMatches.addUnmatchedDestEntry(destEntry); } } - System.out.println("Automatching " + fieldMatches.getUnmatchedSourceFields().size() + " unmatched source fields..."); + System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); // go through the unmatched source fields and try to pick out the easy matches - for (ClassEntry obfSourceClass : Lists.newArrayList(fieldMatches.getSourceClassesWithUnmatchedFields())) { - for (FieldEntry obfSourceField : Lists.newArrayList(fieldMatches.getUnmatchedSourceFields(obfSourceClass))) { + for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { + for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { // get the possible dest matches ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - // filter by type - Set obfDestFields = Sets.newHashSet(); - for (FieldEntry obfDestField : fieldMatches.getUnmatchedDestFields(obfDestClass)) { - Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); - if (translatedDestType.equals(obfSourceField.getType())) { - obfDestFields.add(obfDestField); - } - } + // filter by type/signature + Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); - if (obfDestFields.size() == 1) { + if (obfDestEntries.size() == 1) { // make the easy match - FieldEntry obfDestField = obfDestFields.iterator().next(); - fieldMatches.makeMatch(obfSourceField, obfDestField); - } else if (obfDestFields.isEmpty()) { + memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); + } else if (obfDestEntries.isEmpty()) { // no match is possible =( - fieldMatches.makeSourceUnmatchable(obfSourceField); + memberMatches.makeSourceUnmatchable(obfSourceEntry); } } } - System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source fields", - fieldMatches.getUnmatchedSourceFields().size(), - fieldMatches.getUnmatchableSourceFields().size() + System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", + memberMatches.getUnmatchedSourceEntries().size(), + memberMatches.getUnmatchableSourceEntries().size() )); - return fieldMatches; + return memberMatches; } - private static void collectMatchedFields(FieldMatches fieldMatches, ClassMapping destClassMapping, ClassMatches classMatches) { + private static void collectMatchedFields(MemberMatches memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer doer) { // get the fields for this class - for (FieldMapping destFieldMapping : destClassMapping.fields()) { - FieldEntry destObfField = EntryFactory.getObfFieldEntry(destClassMapping, destFieldMapping); - FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); - fieldMatches.addMatch(srcObfField, destObfField); + for (MemberMapping destEntryMapping : doer.getMappings(destClassMapping)) { + T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry()); + T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); + memberMatches.addMatch(srcObfField, destObfField); } // recurse for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { - collectMatchedFields(fieldMatches, destInnerClassMapping, classMatches); + collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer); } } - private static FieldEntry translate(FieldEntry in, BiMap map) { - return new FieldEntry( - map.get(in.getClassEntry()), - in.getName(), - translate(in.getType(), map) - ); + @SuppressWarnings("unchecked") + private static T translate(T in, BiMap map) { + if (in instanceof FieldEntry) { + return (T)new FieldEntry( + map.get(in.getClassEntry()), + in.getName(), + translate(((FieldEntry)in).getType(), map) + ); + } else if (in instanceof MethodEntry) { + return (T)new MethodEntry( + map.get(in.getClassEntry()), + in.getName(), + translate(((MethodEntry)in).getSignature(), map) + ); + } else if (in instanceof ConstructorEntry) { + return (T)new ConstructorEntry( + map.get(in.getClassEntry()), + translate(((ConstructorEntry)in).getSignature(), map) + ); + } + throw new Error("Unhandled entry type: " + in.getClass()); } private static Type translate(Type type, final BiMap map) { @@ -348,4 +436,20 @@ public class MappingsConverter { } }); } + + private static Signature translate(Signature signature, final BiMap map) { + if (signature == null) { + return null; + } + return new Signature(signature, new ClassNameReplacer() { + @Override + public String replace(String inClassName) { + ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); + if (outClassEntry == null) { + return null; + } + return outClassEntry.getName(); + } + }); + } } diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java index 921ab1d..dac2f05 100644 --- a/src/cuchaz/enigma/convert/MatchesReader.java +++ b/src/cuchaz/enigma/convert/MatchesReader.java @@ -10,6 +10,8 @@ import java.util.List; import com.beust.jcommander.internal.Lists; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.Type; @@ -45,45 +47,57 @@ public class MatchesReader { return entries; } - public static FieldMatches readFields(File file) + public static MemberMatches readMembers(File file) throws IOException { try (BufferedReader in = new BufferedReader(new FileReader(file))) { - FieldMatches matches = new FieldMatches(); + MemberMatches matches = new MemberMatches(); String line = null; while ((line = in.readLine()) != null) { - readFieldMatch(matches, line); + readMemberMatch(matches, line); } return matches; } } - private static void readFieldMatch(FieldMatches matches, String line) { + private static void readMemberMatch(MemberMatches matches, String line) { if (line.startsWith("!")) { - matches.addUnmatchableSourceField(readField(line.substring(1))); + T source = readEntry(line.substring(1)); + matches.addUnmatchableSourceEntry(source); } else { String[] parts = line.split(":", 2); - FieldEntry source = readField(parts[0]); - FieldEntry dest = readField(parts[1]); + T source = readEntry(parts[0]); + T dest = readEntry(parts[1]); if (source != null && dest != null) { matches.addMatch(source, dest); } else if (source != null) { - matches.addUnmatchedSourceField(source); + matches.addUnmatchedSourceEntry(source); } else if (dest != null) { - matches.addUnmatchedDestField(dest); + matches.addUnmatchedDestEntry(dest); } } } - private static FieldEntry readField(String in) { + @SuppressWarnings("unchecked") + private static T readEntry(String in) { if (in.length() <= 0) { return null; } String[] parts = in.split(" "); - assert(parts.length == 3); - return new FieldEntry( - new ClassEntry(parts[0]), - parts[1], - new Type(parts[2]) - ); + if (parts.length == 3 && parts[2].indexOf('(') < 0) { + return (T)new FieldEntry( + new ClassEntry(parts[0]), + parts[1], + new Type(parts[2]) + ); + } else { + assert(parts.length == 2 || parts.length == 3); + if (parts.length == 2) { + return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1]); + } else if (parts.length == 3) { + return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]); + } else { + throw new Error("Malformed behavior entry: " + in); + } + } } } diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java index 2118dd0..9e9ead0 100644 --- a/src/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/cuchaz/enigma/convert/MatchesWriter.java @@ -5,7 +5,9 @@ import java.io.FileWriter; import java.io.IOException; import java.util.Map; +import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; @@ -41,43 +43,52 @@ public class MatchesWriter { } } - public static void writeFields(FieldMatches fieldMatches, File file) + public static void writeMembers(MemberMatches matches, File file) throws IOException { try (FileWriter out = new FileWriter(file)) { - for (Map.Entry match : fieldMatches.matches().entrySet()) { - writeFieldMatch(out, match.getKey(), match.getValue()); + for (Map.Entry match : matches.matches().entrySet()) { + writeMemberMatch(out, match.getKey(), match.getValue()); } - for (FieldEntry fieldEntry : fieldMatches.getUnmatchedSourceFields()) { - writeFieldMatch(out, fieldEntry, null); + for (T entry : matches.getUnmatchedSourceEntries()) { + writeMemberMatch(out, entry, null); } - for (FieldEntry fieldEntry : fieldMatches.getUnmatchedDestFields()) { - writeFieldMatch(out, null, fieldEntry); + for (T entry : matches.getUnmatchedDestEntries()) { + writeMemberMatch(out, null, entry); } - for (FieldEntry fieldEntry : fieldMatches.getUnmatchableSourceFields()) { - writeUnmatchableField(out, fieldEntry); + for (T entry : matches.getUnmatchableSourceEntries()) { + writeUnmatchableEntry(out, entry); } } } - private static void writeFieldMatch(FileWriter out, FieldEntry source, FieldEntry dest) + private static void writeMemberMatch(FileWriter out, T source, T dest) throws IOException { if (source != null) { - writeField(out, source); + writeEntry(out, source); } out.write(":"); if (dest != null) { - writeField(out, dest); + writeEntry(out, dest); } out.write("\n"); } - private static void writeUnmatchableField(FileWriter out, FieldEntry fieldEntry) + private static void writeUnmatchableEntry(FileWriter out, T entry) throws IOException { out.write("!"); - writeField(out, fieldEntry); + writeEntry(out, entry); out.write("\n"); } + private static void writeEntry(FileWriter out, T entry) + throws IOException { + if (entry instanceof FieldEntry) { + writeField(out, (FieldEntry)entry); + } else if (entry instanceof BehaviorEntry) { + writeBehavior(out, (BehaviorEntry)entry); + } + } + private static void writeField(FileWriter out, FieldEntry fieldEntry) throws IOException { out.write(fieldEntry.getClassName()); @@ -86,4 +97,15 @@ public class MatchesWriter { out.write(" "); out.write(fieldEntry.getType().toString()); } + + private static void writeBehavior(FileWriter out, BehaviorEntry behaviorEntry) + throws IOException { + out.write(behaviorEntry.getClassName()); + out.write(" "); + out.write(behaviorEntry.getName()); + out.write(" "); + if (behaviorEntry.getSignature() != null) { + out.write(behaviorEntry.getSignature().toString()); + } + } } diff --git a/src/cuchaz/enigma/convert/MemberMatches.java b/src/cuchaz/enigma/convert/MemberMatches.java new file mode 100644 index 0000000..1078ab7 --- /dev/null +++ b/src/cuchaz/enigma/convert/MemberMatches.java @@ -0,0 +1,145 @@ +package cuchaz.enigma.convert; + +import java.util.Collection; +import java.util.Set; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; + + +public class MemberMatches { + + private BiMap m_matches; + private Multimap m_matchedSourceEntries; + private Multimap m_unmatchedSourceEntries; + private Multimap m_unmatchedDestEntries; + private Multimap m_unmatchableSourceEntries; + + public MemberMatches() { + m_matches = HashBiMap.create(); + m_matchedSourceEntries = HashMultimap.create(); + m_unmatchedSourceEntries = HashMultimap.create(); + m_unmatchedDestEntries = HashMultimap.create(); + m_unmatchableSourceEntries = HashMultimap.create(); + } + + public void addMatch(T srcEntry, T destEntry) { + boolean wasAdded = m_matches.put(srcEntry, destEntry) == null; + assert (wasAdded); + wasAdded = m_matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntry(T sourceEntry) { + boolean wasAdded = m_unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntries(Iterable sourceEntries) { + for (T sourceEntry : sourceEntries) { + addUnmatchedSourceEntry(sourceEntry); + } + } + + public void addUnmatchedDestEntry(T destEntry) { + boolean wasAdded = m_unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry); + assert (wasAdded); + } + + public void addUnmatchedDestEntries(Iterable destEntriesntries) { + for (T entry : destEntriesntries) { + addUnmatchedDestEntry(entry); + } + } + + public void addUnmatchableSourceEntry(T sourceEntry) { + boolean wasAdded = m_unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public Set getSourceClassesWithUnmatchedEntries() { + return m_unmatchedSourceEntries.keySet(); + } + + public Collection getSourceClassesWithoutUnmatchedEntries() { + Set out = Sets.newHashSet(); + out.addAll(m_matchedSourceEntries.keySet()); + out.removeAll(m_unmatchedSourceEntries.keySet()); + return out; + } + + public Collection getUnmatchedSourceEntries() { + return m_unmatchedSourceEntries.values(); + } + + public Collection getUnmatchedSourceEntries(ClassEntry sourceClass) { + return m_unmatchedSourceEntries.get(sourceClass); + } + + public Collection getUnmatchedDestEntries() { + return m_unmatchedDestEntries.values(); + } + + public Collection getUnmatchedDestEntries(ClassEntry destClass) { + return m_unmatchedDestEntries.get(destClass); + } + + public Collection getUnmatchableSourceEntries() { + return m_unmatchableSourceEntries.values(); + } + + public boolean hasSource(T sourceEntry) { + return m_matches.containsKey(sourceEntry) || m_unmatchedSourceEntries.containsValue(sourceEntry); + } + + public boolean hasDest(T destEntry) { + return m_matches.containsValue(destEntry) || m_unmatchedDestEntries.containsValue(destEntry); + } + + public BiMap matches() { + return m_matches; + } + + public boolean isMatchedSourceEntry(T sourceEntry) { + return m_matches.containsKey(sourceEntry); + } + + public boolean isMatchedDestEntry(T destEntry) { + return m_matches.containsValue(destEntry); + } + + public void makeMatch(T sourceEntry, T destEntry) { + boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + wasRemoved = m_unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry); + assert (wasRemoved); + addMatch(sourceEntry, destEntry); + } + + public boolean isMatched(T sourceEntry, T destEntry) { + T match = m_matches.get(sourceEntry); + return match != null && match.equals(destEntry); + } + + public void unmakeMatch(T sourceEntry, T destEntry) { + boolean wasRemoved = m_matches.remove(sourceEntry) != null; + assert (wasRemoved); + wasRemoved = m_matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchedSourceEntry(sourceEntry); + addUnmatchedDestEntry(destEntry); + } + + public void makeSourceUnmatchable(T sourceEntry) { + assert(!isMatchedSourceEntry(sourceEntry)); + boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchableSourceEntry(sourceEntry); + } +} diff --git a/src/cuchaz/enigma/gui/FieldMatchingGui.java b/src/cuchaz/enigma/gui/FieldMatchingGui.java deleted file mode 100644 index 3f4a378..0000000 --- a/src/cuchaz/enigma/gui/FieldMatchingGui.java +++ /dev/null @@ -1,474 +0,0 @@ -package cuchaz.enigma.gui; - -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.WindowConstants; -import javax.swing.text.Highlighter.HighlightPainter; - -import com.beust.jcommander.internal.Lists; -import com.beust.jcommander.internal.Maps; - -import cuchaz.enigma.Constants; -import cuchaz.enigma.Deobfuscator; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.SourceIndex; -import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.convert.ClassMatches; -import cuchaz.enigma.convert.FieldMatches; -import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.Entry; -import cuchaz.enigma.mapping.FieldEntry; -import de.sciss.syntaxpane.DefaultSyntaxKit; - - -public class FieldMatchingGui { - - private static enum SourceType { - Matched { - - @Override - public Collection getObfSourceClasses(FieldMatches matches) { - return matches.getSourceClassesWithoutUnmatchedFields(); - } - }, - Unmatched { - - @Override - public Collection getObfSourceClasses(FieldMatches matches) { - return matches.getSourceClassesWithUnmatchedFields(); - } - }; - - public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { - JRadioButton button = new JRadioButton(name(), this == getDefault()); - button.setActionCommand(name()); - button.addActionListener(listener); - group.add(button); - return button; - } - - public abstract Collection getObfSourceClasses(FieldMatches matches); - - public static SourceType getDefault() { - return values()[0]; - } - } - - public static interface SaveListener { - public void save(FieldMatches matches); - } - - // controls - private JFrame m_frame; - private Map m_sourceTypeButtons; - private ClassSelector m_sourceClasses; - private CodeReader m_sourceReader; - private CodeReader m_destReader; - private JButton m_matchButton; - private JButton m_unmatchableButton; - private JLabel m_sourceLabel; - private JLabel m_destLabel; - private HighlightPainter m_unmatchedHighlightPainter; - private HighlightPainter m_matchedHighlightPainter; - - private ClassMatches m_classMatches; - private FieldMatches m_fieldMatches; - private Deobfuscator m_sourceDeobfuscator; - private Deobfuscator m_destDeobfuscator; - private SaveListener m_saveListener; - private SourceType m_sourceType; - private ClassEntry m_obfSourceClass; - private ClassEntry m_obfDestClass; - private FieldEntry m_obfSourceField; - private FieldEntry m_obfDestField; - - public FieldMatchingGui(ClassMatches classMatches, FieldMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - - m_classMatches = classMatches; - m_fieldMatches = fieldMatches; - m_sourceDeobfuscator = sourceDeobfuscator; - m_destDeobfuscator = destDeobfuscator; - - // init frame - m_frame = new JFrame(Constants.Name + " - Field Matcher"); - final Container pane = m_frame.getContentPane(); - pane.setLayout(new BorderLayout()); - - // init classes side - JPanel classesPanel = new JPanel(); - classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS)); - classesPanel.setPreferredSize(new Dimension(200, 0)); - pane.add(classesPanel, BorderLayout.WEST); - classesPanel.add(new JLabel("Classes")); - - // init source type radios - JPanel sourceTypePanel = new JPanel(); - classesPanel.add(sourceTypePanel); - sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); - ActionListener sourceTypeListener = new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - setSourceType(SourceType.valueOf(event.getActionCommand())); - } - }; - ButtonGroup sourceTypeButtons = new ButtonGroup(); - m_sourceTypeButtons = Maps.newHashMap(); - for (SourceType sourceType : SourceType.values()) { - JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); - m_sourceTypeButtons.put(sourceType, button); - sourceTypePanel.add(button); - } - - m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); - m_sourceClasses.setListener(new ClassSelectionListener() { - @Override - public void onSelectClass(ClassEntry classEntry) { - setSourceClass(classEntry); - } - }); - JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); - classesPanel.add(sourceScroller); - - // init readers - DefaultSyntaxKit.initKit(); - m_sourceReader = new CodeReader(); - m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() { - @Override - public void onSelect(EntryReference reference) { - if (reference != null) { - onSelectSource(reference.entry); - } else { - onSelectSource(null); - } - } - }); - m_destReader = new CodeReader(); - m_destReader.setSelectionListener(new CodeReader.SelectionListener() { - @Override - public void onSelect(EntryReference reference) { - if (reference != null) { - onSelectDest(reference.entry); - } else { - onSelectDest(null); - } - } - }); - - // add key bindings - KeyAdapter keyListener = new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.VK_M: - m_matchButton.doClick(); - break; - } - } - }; - m_sourceReader.addKeyListener(keyListener); - m_destReader.addKeyListener(keyListener); - - // init all the splits - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader)); - splitRight.setResizeWeight(0.5); // resize 50:50 - JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight); - splitLeft.setResizeWeight(0); // let the right side take all the slack - pane.add(splitLeft, BorderLayout.CENTER); - splitLeft.resetToPreferredSizes(); - - // init bottom panel - JPanel bottomPanel = new JPanel(); - bottomPanel.setLayout(new FlowLayout()); - pane.add(bottomPanel, BorderLayout.SOUTH); - - m_matchButton = new JButton(); - m_unmatchableButton = new JButton(); - - m_sourceLabel = new JLabel(); - bottomPanel.add(m_sourceLabel); - bottomPanel.add(m_matchButton); - bottomPanel.add(m_unmatchableButton); - m_destLabel = new JLabel(); - bottomPanel.add(m_destLabel); - - // show the frame - pane.doLayout(); - m_frame.setSize(1024, 576); - m_frame.setMinimumSize(new Dimension(640, 480)); - m_frame.setVisible(true); - m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - - m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); - m_matchedHighlightPainter = new DeobfuscatedHighlightPainter(); - - // init state - m_saveListener = null; - m_obfSourceClass = null; - m_obfDestClass = null; - m_obfSourceField = null; - m_obfDestField = null; - setSourceType(SourceType.getDefault()); - updateButtons(); - } - - protected void setSourceType(SourceType val) { - m_sourceType = val; - updateSourceClasses(); - } - - public void setSaveListener(SaveListener val) { - m_saveListener = val; - } - - private void updateSourceClasses() { - - String selectedPackage = m_sourceClasses.getSelectedPackage(); - - List deobfClassEntries = Lists.newArrayList(); - for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_fieldMatches)) { - deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry)); - } - m_sourceClasses.setClasses(deobfClassEntries); - - if (selectedPackage != null) { - m_sourceClasses.expandPackage(selectedPackage); - } - - for (SourceType sourceType : SourceType.values()) { - m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", - sourceType.name(), sourceType.getObfSourceClasses(m_fieldMatches).size() - )); - } - } - - protected void setSourceClass(ClassEntry sourceClass) { - - m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); - m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass); - if (m_obfDestClass == null) { - throw new Error("No matching dest class for source class: " + m_obfSourceClass); - } - - m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() { - @Override - public void run() { - updateSourceHighlights(); - } - }); - m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() { - @Override - public void run() { - updateDestHighlights(); - } - }); - } - - protected void updateSourceHighlights() { - highlightFields(m_sourceReader, m_sourceDeobfuscator, m_fieldMatches.matches().keySet(), m_fieldMatches.getUnmatchedSourceFields()); - } - - protected void updateDestHighlights() { - highlightFields(m_destReader, m_destDeobfuscator, m_fieldMatches.matches().values(), m_fieldMatches.getUnmatchedDestFields()); - } - - private void highlightFields(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedFields, Collection obfUnmatchedFields) { - reader.clearHighlights(); - SourceIndex index = reader.getSourceIndex(); - - // matched fields - for (FieldEntry obfFieldEntry : obfMatchedFields) { - FieldEntry deobfFieldEntry = deobfuscator.deobfuscateEntry(obfFieldEntry); - Token token = index.getDeclarationToken(deobfFieldEntry); - if (token != null) { - reader.setHighlightedToken(token, m_matchedHighlightPainter); - } - } - - // unmatched fields - for (FieldEntry obfFieldEntry : obfUnmatchedFields) { - FieldEntry deobfFieldEntry = deobfuscator.deobfuscateEntry(obfFieldEntry); - Token token = index.getDeclarationToken(deobfFieldEntry); - if (token != null) { - reader.setHighlightedToken(token, m_unmatchedHighlightPainter); - } - } - } - - private boolean isSelectionMatched() { - return m_obfSourceField != null && m_obfDestField != null - && m_fieldMatches.isMatched(m_obfSourceField, m_obfDestField); - } - - protected void onSelectSource(Entry source) { - - // start with no selection - if (isSelectionMatched()) { - setDest(null); - } - setSource(null); - - // then look for a valid source selection - if (source != null && source instanceof FieldEntry) { - FieldEntry sourceField = (FieldEntry)source; - FieldEntry obfSourceField = m_sourceDeobfuscator.obfuscateEntry(sourceField); - if (m_fieldMatches.hasSource(obfSourceField)) { - setSource(obfSourceField); - - // look for a matched dest too - FieldEntry obfDestField = m_fieldMatches.matches().get(obfSourceField); - if (obfDestField != null) { - setDest(obfDestField); - } - } - } - - updateButtons(); - } - - protected void onSelectDest(Entry dest) { - - // start with no selection - if (isSelectionMatched()) { - setSource(null); - } - setDest(null); - - // then look for a valid dest selection - if (dest != null && dest instanceof FieldEntry) { - FieldEntry destField = (FieldEntry)dest; - FieldEntry obfDestField = m_destDeobfuscator.obfuscateEntry(destField); - if (m_fieldMatches.hasDest(obfDestField)) { - setDest(obfDestField); - - // look for a matched source too - FieldEntry obfSourceField = m_fieldMatches.matches().inverse().get(obfDestField); - if (obfSourceField != null) { - setSource(obfSourceField); - } - } - } - - updateButtons(); - } - - private void setSource(FieldEntry obfField) { - if (obfField == null) { - m_obfSourceField = obfField; - m_sourceLabel.setText(""); - } else { - m_obfSourceField = obfField; - FieldEntry deobfField = m_sourceDeobfuscator.deobfuscateEntry(obfField); - m_sourceLabel.setText(deobfField.getName() + " " + deobfField.getType().toString()); - } - } - - private void setDest(FieldEntry obfField) { - if (obfField == null) { - m_obfDestField = obfField; - m_destLabel.setText(""); - } else { - m_obfDestField = obfField; - FieldEntry deobfField = m_destDeobfuscator.deobfuscateEntry(obfField); - m_destLabel.setText(deobfField.getName() + " " + deobfField.getType().toString()); - } - } - - private void updateButtons() { - - GuiTricks.deactivateButton(m_matchButton); - GuiTricks.deactivateButton(m_unmatchableButton); - - if (m_obfSourceField != null && m_obfDestField != null) { - if (m_fieldMatches.isMatched(m_obfSourceField, m_obfDestField)) { - GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - unmatch(); - } - }); - } else if (!m_fieldMatches.isMatchedSourceField(m_obfSourceField) && !m_fieldMatches.isMatchedDestField(m_obfDestField)) { - GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - match(); - } - }); - } - } else if (m_obfSourceField != null) { - GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - unmatchable(); - } - }); - } - } - - protected void match() { - - // update the field matches - m_fieldMatches.makeMatch(m_obfSourceField, m_obfDestField); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - protected void unmatch() { - - // update the field matches - m_fieldMatches.unmakeMatch(m_obfSourceField, m_obfDestField); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - protected void unmatchable() { - - // update the field matches - m_fieldMatches.makeSourceUnmatchable(m_obfSourceField); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - private void save() { - if (m_saveListener != null) { - m_saveListener.save(m_fieldMatches); - } - } -} diff --git a/src/cuchaz/enigma/gui/MemberMatchingGui.java b/src/cuchaz/enigma/gui/MemberMatchingGui.java new file mode 100644 index 0000000..52545b3 --- /dev/null +++ b/src/cuchaz/enigma/gui/MemberMatchingGui.java @@ -0,0 +1,489 @@ +package cuchaz.enigma.gui; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.WindowConstants; +import javax.swing.text.Highlighter.HighlightPainter; + +import com.beust.jcommander.internal.Lists; +import com.beust.jcommander.internal.Maps; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.SourceIndex; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.convert.ClassMatches; +import cuchaz.enigma.convert.MemberMatches; +import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import de.sciss.syntaxpane.DefaultSyntaxKit; + + +public class MemberMatchingGui { + + private static enum SourceType { + Matched { + + @Override + public Collection getObfSourceClasses(MemberMatches matches) { + return matches.getSourceClassesWithoutUnmatchedEntries(); + } + }, + Unmatched { + + @Override + public Collection getObfSourceClasses(MemberMatches matches) { + return matches.getSourceClassesWithUnmatchedEntries(); + } + }; + + public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { + JRadioButton button = new JRadioButton(name(), this == getDefault()); + button.setActionCommand(name()); + button.addActionListener(listener); + group.add(button); + return button; + } + + public abstract Collection getObfSourceClasses(MemberMatches matches); + + public static SourceType getDefault() { + return values()[0]; + } + } + + public static interface SaveListener { + public void save(MemberMatches matches); + } + + // controls + private JFrame m_frame; + private Map m_sourceTypeButtons; + private ClassSelector m_sourceClasses; + private CodeReader m_sourceReader; + private CodeReader m_destReader; + private JButton m_matchButton; + private JButton m_unmatchableButton; + private JLabel m_sourceLabel; + private JLabel m_destLabel; + private HighlightPainter m_unmatchedHighlightPainter; + private HighlightPainter m_matchedHighlightPainter; + + private ClassMatches m_classMatches; + private MemberMatches m_memberMatches; + private Deobfuscator m_sourceDeobfuscator; + private Deobfuscator m_destDeobfuscator; + private SaveListener m_saveListener; + private SourceType m_sourceType; + private ClassEntry m_obfSourceClass; + private ClassEntry m_obfDestClass; + private T m_obfSourceEntry; + private T m_obfDestEntry; + + public MemberMatchingGui(ClassMatches classMatches, MemberMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + + m_classMatches = classMatches; + m_memberMatches = fieldMatches; + m_sourceDeobfuscator = sourceDeobfuscator; + m_destDeobfuscator = destDeobfuscator; + + // init frame + m_frame = new JFrame(Constants.Name + " - Member Matcher"); + final Container pane = m_frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + // init classes side + JPanel classesPanel = new JPanel(); + classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS)); + classesPanel.setPreferredSize(new Dimension(200, 0)); + pane.add(classesPanel, BorderLayout.WEST); + classesPanel.add(new JLabel("Classes")); + + // init source type radios + JPanel sourceTypePanel = new JPanel(); + classesPanel.add(sourceTypePanel); + sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); + ActionListener sourceTypeListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + setSourceType(SourceType.valueOf(event.getActionCommand())); + } + }; + ButtonGroup sourceTypeButtons = new ButtonGroup(); + m_sourceTypeButtons = Maps.newHashMap(); + for (SourceType sourceType : SourceType.values()) { + JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); + m_sourceTypeButtons.put(sourceType, button); + sourceTypePanel.add(button); + } + + m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); + m_sourceClasses.setListener(new ClassSelectionListener() { + @Override + public void onSelectClass(ClassEntry classEntry) { + setSourceClass(classEntry); + } + }); + JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); + classesPanel.add(sourceScroller); + + // init readers + DefaultSyntaxKit.initKit(); + m_sourceReader = new CodeReader(); + m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() { + @Override + public void onSelect(EntryReference reference) { + if (reference != null) { + onSelectSource(reference.entry); + } else { + onSelectSource(null); + } + } + }); + m_destReader = new CodeReader(); + m_destReader.setSelectionListener(new CodeReader.SelectionListener() { + @Override + public void onSelect(EntryReference reference) { + if (reference != null) { + onSelectDest(reference.entry); + } else { + onSelectDest(null); + } + } + }); + + // add key bindings + KeyAdapter keyListener = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_M: + m_matchButton.doClick(); + break; + } + } + }; + m_sourceReader.addKeyListener(keyListener); + m_destReader.addKeyListener(keyListener); + + // init all the splits + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader)); + splitRight.setResizeWeight(0.5); // resize 50:50 + JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight); + splitLeft.setResizeWeight(0); // let the right side take all the slack + pane.add(splitLeft, BorderLayout.CENTER); + splitLeft.resetToPreferredSizes(); + + // init bottom panel + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new FlowLayout()); + pane.add(bottomPanel, BorderLayout.SOUTH); + + m_matchButton = new JButton(); + m_unmatchableButton = new JButton(); + + m_sourceLabel = new JLabel(); + bottomPanel.add(m_sourceLabel); + bottomPanel.add(m_matchButton); + bottomPanel.add(m_unmatchableButton); + m_destLabel = new JLabel(); + bottomPanel.add(m_destLabel); + + // show the frame + pane.doLayout(); + m_frame.setSize(1024, 576); + m_frame.setMinimumSize(new Dimension(640, 480)); + m_frame.setVisible(true); + m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); + m_matchedHighlightPainter = new DeobfuscatedHighlightPainter(); + + // init state + m_saveListener = null; + m_obfSourceClass = null; + m_obfDestClass = null; + m_obfSourceEntry = null; + m_obfDestEntry = null; + setSourceType(SourceType.getDefault()); + updateButtons(); + } + + protected void setSourceType(SourceType val) { + m_sourceType = val; + updateSourceClasses(); + } + + public void setSaveListener(SaveListener val) { + m_saveListener = val; + } + + private void updateSourceClasses() { + + String selectedPackage = m_sourceClasses.getSelectedPackage(); + + List deobfClassEntries = Lists.newArrayList(); + for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) { + deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry)); + } + m_sourceClasses.setClasses(deobfClassEntries); + + if (selectedPackage != null) { + m_sourceClasses.expandPackage(selectedPackage); + } + + for (SourceType sourceType : SourceType.values()) { + m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", + sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size() + )); + } + } + + protected void setSourceClass(ClassEntry sourceClass) { + + m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); + m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass); + if (m_obfDestClass == null) { + throw new Error("No matching dest class for source class: " + m_obfSourceClass); + } + + m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() { + @Override + public void run() { + updateSourceHighlights(); + } + }); + m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() { + @Override + public void run() { + updateDestHighlights(); + } + }); + } + + protected void updateSourceHighlights() { + highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries()); + } + + protected void updateDestHighlights() { + highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries()); + } + + private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedEntries, Collection obfUnmatchedEntries) { + reader.clearHighlights(); + SourceIndex index = reader.getSourceIndex(); + + // matched fields + for (T obfT : obfMatchedEntries) { + T deobfT = deobfuscator.deobfuscateEntry(obfT); + Token token = index.getDeclarationToken(deobfT); + if (token != null) { + reader.setHighlightedToken(token, m_matchedHighlightPainter); + } + } + + // unmatched fields + for (T obfT : obfUnmatchedEntries) { + T deobfT = deobfuscator.deobfuscateEntry(obfT); + Token token = index.getDeclarationToken(deobfT); + if (token != null) { + reader.setHighlightedToken(token, m_unmatchedHighlightPainter); + } + } + } + + private boolean isSelectionMatched() { + return m_obfSourceEntry != null && m_obfDestEntry != null + && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry); + } + + protected void onSelectSource(Entry source) { + + // start with no selection + if (isSelectionMatched()) { + setDest(null); + } + setSource(null); + + // then look for a valid source selection + if (source != null) { + + // this looks really scary, but it's actually ok + // Deobfuscator.obfuscateEntry can handle all implementations of Entry + // and MemberMatches.hasSource() will only pass entries that actually match T + @SuppressWarnings("unchecked") + T sourceEntry = (T)source; + + T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry); + if (m_memberMatches.hasSource(obfSourceEntry)) { + setSource(obfSourceEntry); + + // look for a matched dest too + T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry); + if (obfDestEntry != null) { + setDest(obfDestEntry); + } + } + } + + updateButtons(); + } + + protected void onSelectDest(Entry dest) { + + // start with no selection + if (isSelectionMatched()) { + setSource(null); + } + setDest(null); + + // then look for a valid dest selection + if (dest != null) { + + // this looks really scary, but it's actually ok + // Deobfuscator.obfuscateEntry can handle all implementations of Entry + // and MemberMatches.hasSource() will only pass entries that actually match T + @SuppressWarnings("unchecked") + T destEntry = (T)dest; + + T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry); + if (m_memberMatches.hasDest(obfDestEntry)) { + setDest(obfDestEntry); + + // look for a matched source too + T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry); + if (obfSourceEntry != null) { + setSource(obfSourceEntry); + } + } + } + + updateButtons(); + } + + private void setSource(T obfEntry) { + if (obfEntry == null) { + m_obfSourceEntry = obfEntry; + m_sourceLabel.setText(""); + } else { + m_obfSourceEntry = obfEntry; + m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator)); + } + } + + private void setDest(T obfEntry) { + if (obfEntry == null) { + m_obfDestEntry = obfEntry; + m_destLabel.setText(""); + } else { + m_obfDestEntry = obfEntry; + m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator)); + } + } + + private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) { + // deobfuscate, then take off the class name + T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry); + return deobfEntry.toString().substring(deobfEntry.getClassName().length() + 1); + } + + private void updateButtons() { + + GuiTricks.deactivateButton(m_matchButton); + GuiTricks.deactivateButton(m_unmatchableButton); + + if (m_obfSourceEntry != null && m_obfDestEntry != null) { + if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) { + GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + unmatch(); + } + }); + } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) { + GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + match(); + } + }); + } + } else if (m_obfSourceEntry != null) { + GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + unmatchable(); + } + }); + } + } + + protected void match() { + + // update the field matches + m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatch() { + + // update the field matches + m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatchable() { + + // update the field matches + m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + private void save() { + if (m_saveListener != null) { + m_saveListener.save(m_memberMatches); + } + } +} diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java index 43605e5..07fed32 100644 --- a/src/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -434,4 +434,8 @@ public class ClassMapping implements Serializable, Comparable { public static boolean isSimpleClassName(String name) { return name.indexOf('/') < 0 && name.indexOf('$') < 0; } + + public ClassEntry getObfEntry() { + return new ClassEntry(m_obfFullName); + } } diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java index 333bb09..7bc6183 100644 --- a/src/cuchaz/enigma/mapping/EntryFactory.java +++ b/src/cuchaz/enigma/mapping/EntryFactory.java @@ -138,6 +138,10 @@ public class EntryFactory { public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) { return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature)); } + + public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) { + return getBehaviorEntry(new ClassEntry(className), behaviorName); + } public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) { if (behaviorName.equals("")) { @@ -149,6 +153,14 @@ public class EntryFactory { } } + public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) { + if(behaviorName.equals("")) { + return new ConstructorEntry(classEntry); + } else { + throw new IllegalArgumentException("Only class initializers don't have signatures"); + } + } + public static BehaviorEntry getBehaviorEntry(MethodDefinition def) { if (def.isConstructor() || def.isTypeInitializer()) { return getConstructorEntry(def); diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java index 55b0a19..1289351 100644 --- a/src/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/cuchaz/enigma/mapping/FieldMapping.java @@ -12,7 +12,7 @@ package cuchaz.enigma.mapping; import java.io.Serializable; -public class FieldMapping implements Serializable, Comparable { +public class FieldMapping implements Serializable, Comparable, MemberMapping { private static final long serialVersionUID = 8610742471440861315L; @@ -72,4 +72,9 @@ public class FieldMapping implements Serializable, Comparable { } return false; } + + @Override + public FieldEntry getObfEntry(ClassEntry classEntry) { + return new FieldEntry(classEntry, m_obfName, new Type(m_obfType)); + } } diff --git a/src/cuchaz/enigma/mapping/MemberMapping.java b/src/cuchaz/enigma/mapping/MemberMapping.java new file mode 100644 index 0000000..d62a7c1 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MemberMapping.java @@ -0,0 +1,6 @@ +package cuchaz.enigma.mapping; + + +public interface MemberMapping { + T getObfEntry(ClassEntry classEntry); +} diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java index bf8a94f..bf6dacc 100644 --- a/src/cuchaz/enigma/mapping/MethodMapping.java +++ b/src/cuchaz/enigma/mapping/MethodMapping.java @@ -16,7 +16,7 @@ import java.util.Map.Entry; import com.google.common.collect.Maps; -public class MethodMapping implements Serializable, Comparable { +public class MethodMapping implements Serializable, Comparable, MemberMapping { private static final long serialVersionUID = -4409570216084263978L; @@ -170,4 +170,13 @@ public class MethodMapping implements Serializable, Comparable { } return false; } + + @Override + public BehaviorEntry getObfEntry(ClassEntry classEntry) { + if (isConstructor()) { + return new ConstructorEntry(classEntry, m_obfSignature); + } else { + return new MethodEntry(classEntry, m_obfName, m_obfSignature); + } + } } -- cgit v1.2.3 From dc2120999c137aa4763ea2358b8df83f4098d280 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 11 Mar 2015 11:44:24 -0400 Subject: working on writing mappings based on all the matches --- src/cuchaz/enigma/ConvertMain.java | 50 ++++++++++++++++++------ src/cuchaz/enigma/convert/MappingsConverter.java | 50 +++++++++++++++++++++--- src/cuchaz/enigma/mapping/ClassMapping.java | 18 +++++++++ src/cuchaz/enigma/mapping/FieldMapping.java | 4 ++ src/cuchaz/enigma/mapping/MethodMapping.java | 4 ++ 5 files changed, 109 insertions(+), 17 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index c3a2ad5..15658d9 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -49,15 +49,10 @@ public class ConvertMain { // match methods/constructors //computeMethodMatches(methodMatchesFile, destJar, outMappingsFile, classMatchesFile); - editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile); + //editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile); - /* TODO - // write out the converted mappings - FileWriter writer = new FileWriter(outMappingsFile); - new MappingsWriter().write(writer, mappings); - writer.close(); - System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); - */ + // write final converted mappings + writeFinalMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); } private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) @@ -93,7 +88,7 @@ public class ConvertMain { Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); deobfuscators.source.setMappings(mappings); - Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.source); + Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); try (FileWriter out = new FileWriter(outMappingsFile)) { new MappingsWriter().write(out, newMappings); @@ -114,7 +109,12 @@ public class ConvertMain { System.out.println("Writing matches..."); // get the matched and unmatched mappings - MemberMatches fieldMatches = MappingsConverter.computeFieldMatches(destDeobfuscator, destMappings, classMatches); + MemberMatches fieldMatches = MappingsConverter.computeMemberMatches( + destDeobfuscator, + destMappings, + classMatches, + MappingsConverter.getFieldDoer() + ); MatchesWriter.writeMembers(fieldMatches, memberMatchesFile); System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath()); @@ -160,7 +160,12 @@ public class ConvertMain { System.out.println("Writing method matches..."); // get the matched and unmatched mappings - MemberMatches methodMatches = MappingsConverter.computeBehaviorMatches(destDeobfuscator, destMappings, classMatches); + MemberMatches methodMatches = MappingsConverter.computeMemberMatches( + destDeobfuscator, + destMappings, + classMatches, + MappingsConverter.getMethodDoer() + ); MatchesWriter.writeMembers(methodMatches, methodMatchesFile); System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath()); @@ -192,6 +197,29 @@ public class ConvertMain { } }); } + + private static void writeFinalMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile) + throws IOException { + + System.out.println("Reading matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); + MemberMatches methodMatches = MatchesReader.readMembers(methodMatchesFile); + + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(mappings); + + // apply matches + Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); + MappingsConverter.applyMemberMatches(newMappings, fieldMatches, MappingsConverter.getFieldDoer()); + MappingsConverter.applyMemberMatches(newMappings, methodMatches, MappingsConverter.getMethodDoer()); + + // write out the converted mappings + try (FileWriter out = new FileWriter(outMappingsFile)) { + new MappingsWriter().write(out, newMappings); + } + System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); + } private static class Deobfuscators { diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 2987ea0..44bc8b8 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -262,10 +262,11 @@ public class MappingsConverter { Collection getObfEntries(JarIndex jarIndex); Collection> getMappings(ClassMapping destClassMapping); Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); + void setMemberObfName(ClassMapping classMapping, MemberMapping memberMapping, String newObfName); } - public static MemberMatches computeFieldMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { - return computeMemberMatches(destDeobfuscator, destMappings, classMatches, new Doer() { + public static Doer getFieldDoer() { + return new Doer() { @Override public Collection getDroppedEntries(MappingsChecker checker) { @@ -293,11 +294,17 @@ public class MappingsConverter { } return out; } - }); + + @Override + public void setMemberObfName(ClassMapping classMapping, MemberMapping memberMapping, String newObfName) { + FieldMapping fieldMapping = (FieldMapping)memberMapping; + classMapping.setFieldObfName(fieldMapping.getObfName(), fieldMapping.getObfType(), newObfName); + } + }; } - public static MemberMatches computeBehaviorMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { - return computeMemberMatches(destDeobfuscator, destMappings, classMatches, new Doer() { + public static Doer getMethodDoer() { + return new Doer() { @Override public Collection getDroppedEntries(MappingsChecker checker) { @@ -329,7 +336,13 @@ public class MappingsConverter { } return out; } - }); + + @Override + public void setMemberObfName(ClassMapping classMapping, MemberMapping memberMapping, String newObfName) { + MethodMapping methodMapping = (MethodMapping)memberMapping; + classMapping.setMethodObfName(methodMapping.getObfName(), methodMapping.getObfSignature(), newObfName); + } + }; } public static MemberMatches computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer doer) { @@ -452,4 +465,29 @@ public class MappingsConverter { } }); } + + public static void applyMemberMatches(Mappings mappings, MemberMatches memberMatches, Doer doer) { + for (ClassMapping classMapping : mappings.classes()) { + applyMemberMatches(classMapping, memberMatches, doer); + } + } + + private static void applyMemberMatches(ClassMapping classMapping, MemberMatches memberMatches, Doer doer) { + ClassEntry classEntry = classMapping.getObfEntry(); + + // apply to this class + // TODO: need to sort renames so they happen in the right order!! + for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { + T obfSourceEntry = memberMapping.getObfEntry(classEntry); + T obfDestEntry = memberMatches.matches().get(obfSourceEntry); + if (obfDestEntry != null) { + doer.setMemberObfName(classMapping, memberMapping, obfDestEntry.getName()); + } + } + + // recurse + for (ClassMapping innerClassMapping : classMapping.innerClasses()) { + applyMemberMatches(innerClassMapping, memberMatches, doer); + } + } } diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java index 07fed32..ac70df0 100644 --- a/src/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -243,6 +243,15 @@ public class ClassMapping implements Serializable, Comparable { } } + public void setFieldObfName(String oldObfName, Type obfType, String newObfName) { + assert(newObfName != null); + FieldMapping fieldMapping = m_fieldsByObf.remove(getFieldKey(oldObfName, obfType)); + assert(fieldMapping != null); + fieldMapping.setObfName(newObfName); + boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(newObfName, obfType), fieldMapping) == null; + assert(obfWasAdded); + } + //// METHODS //////// @@ -319,6 +328,15 @@ public class ClassMapping implements Serializable, Comparable { } } + public void setMethodObfName(String oldObfName, Signature obfSignature, String newObfName) { + assert(newObfName != null); + MethodMapping methodMapping = m_methodsByObf.remove(getMethodKey(oldObfName, obfSignature)); + assert(methodMapping != null); + methodMapping.setObfName(newObfName); + boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, obfSignature), methodMapping) == null; + assert(obfWasAdded); + } + //// ARGUMENTS //////// public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java index 1289351..3aa9e69 100644 --- a/src/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/cuchaz/enigma/mapping/FieldMapping.java @@ -36,6 +36,10 @@ public class FieldMapping implements Serializable, Comparable, Mem return m_obfName; } + public void setObfName(String val) { + m_obfName = NameValidator.validateFieldName(val); + } + public String getDeobfName() { return m_deobfName; } diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java index bf6dacc..a67e352 100644 --- a/src/cuchaz/enigma/mapping/MethodMapping.java +++ b/src/cuchaz/enigma/mapping/MethodMapping.java @@ -56,6 +56,10 @@ public class MethodMapping implements Serializable, Comparable, M return m_obfName; } + public void setObfName(String val) { + m_obfName = NameValidator.validateMethodName(val); + } + public String getDeobfName() { return m_deobfName; } -- cgit v1.2.3 From 65f551cd25739f1ccfa15d819c6a23060ebf2629 Mon Sep 17 00:00:00 2001 From: jeff Date: Fri, 13 Mar 2015 16:46:02 -0400 Subject: complete mappings converion code. Still need to debug though --- src/cuchaz/enigma/ConvertMain.java | 29 ++++++++++++--- src/cuchaz/enigma/convert/MappingsConverter.java | 45 ++++++++++++++++++++++-- src/cuchaz/enigma/gui/MemberMatchingGui.java | 4 +-- 3 files changed, 69 insertions(+), 9 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index 15658d9..c5c92bc 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -46,13 +46,12 @@ public class ConvertMain { // match fields //computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile); //editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile); + //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile); // match methods/constructors //computeMethodMatches(methodMatchesFile, destJar, outMappingsFile, classMatchesFile); //editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile); - - // write final converted mappings - writeFinalMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); + convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); } private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) @@ -146,6 +145,28 @@ public class ConvertMain { } }); } + + private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile) + throws IOException { + + System.out.println("Reading matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); + + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(mappings); + + // apply matches + Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); + MappingsConverter.applyMemberMatches(newMappings, fieldMatches, MappingsConverter.getFieldDoer()); + + // write out the converted mappings + try (FileWriter out = new FileWriter(outMappingsFile)) { + new MappingsWriter().write(out, newMappings); + } + System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); + } + private static void computeMethodMatches(File methodMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile) throws IOException, MappingParseException { @@ -198,7 +219,7 @@ public class ConvertMain { }); } - private static void writeFinalMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile) + private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile) throws IOException { System.out.println("Reading matches..."); diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 44bc8b8..59f3b5b 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -263,6 +263,7 @@ public class MappingsConverter { Collection> getMappings(ClassMapping destClassMapping); Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); void setMemberObfName(ClassMapping classMapping, MemberMapping memberMapping, String newObfName); + boolean hasObfMember(ClassMapping classMapping, T obfEntry); } public static Doer getFieldDoer() { @@ -300,6 +301,11 @@ public class MappingsConverter { FieldMapping fieldMapping = (FieldMapping)memberMapping; classMapping.setFieldObfName(fieldMapping.getObfName(), fieldMapping.getObfType(), newObfName); } + + @Override + public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { + return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; + } }; } @@ -342,6 +348,11 @@ public class MappingsConverter { MethodMapping methodMapping = (MethodMapping)memberMapping; classMapping.setMethodObfName(methodMapping.getObfName(), methodMapping.getObfSignature(), newObfName); } + + @Override + public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { + return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; + } }; } @@ -475,13 +486,41 @@ public class MappingsConverter { private static void applyMemberMatches(ClassMapping classMapping, MemberMatches memberMatches, Doer doer) { ClassEntry classEntry = classMapping.getObfEntry(); - // apply to this class - // TODO: need to sort renames so they happen in the right order!! + // make a map of all the renames we need to make + Map renames = Maps.newHashMap(); for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { T obfSourceEntry = memberMapping.getObfEntry(classEntry); T obfDestEntry = memberMatches.matches().get(obfSourceEntry); if (obfDestEntry != null) { - doer.setMemberObfName(classMapping, memberMapping, obfDestEntry.getName()); + if (obfDestEntry.getName().equals(obfSourceEntry.getName())) { + // same name, don't need to change anything + continue; + } + renames.put(obfSourceEntry, obfDestEntry); + } + } + + // apply to this class (should never need more than n passes) + int numRenames = renames.size(); + for (int i=0; i memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { + T obfSourceEntry = memberMapping.getObfEntry(classEntry); + T obfDestEntry = renames.get(obfSourceEntry); + if (obfDestEntry != null) { + // make sure this rename won't cause a collision + if (!doer.hasObfMember(classMapping, obfDestEntry)) { + doer.setMemberObfName(classMapping, memberMapping, obfDestEntry.getName()); + renames.remove(obfSourceEntry); + } + } + } + } + if (!renames.isEmpty()) { + System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d/%d renames left.", + classMapping.getObfFullName(), renames.size(), numRenames + )); + for (Map.Entry entry : renames.entrySet()) { + System.err.println(String.format("\t%s -> %s", entry.getKey(), entry.getValue())); } } diff --git a/src/cuchaz/enigma/gui/MemberMatchingGui.java b/src/cuchaz/enigma/gui/MemberMatchingGui.java index 52545b3..181fb54 100644 --- a/src/cuchaz/enigma/gui/MemberMatchingGui.java +++ b/src/cuchaz/enigma/gui/MemberMatchingGui.java @@ -403,9 +403,9 @@ public class MemberMatchingGui { } private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) { - // deobfuscate, then take off the class name + // show obfuscated and deobfuscated names, but no types/signatures T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry); - return deobfEntry.toString().substring(deobfEntry.getClassName().length() + 1); + return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName()); } private void updateButtons() { -- cgit v1.2.3 From 294e97d7e4cda6cadb62918fd822e7d0d11f16a9 Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 15 Mar 2015 00:44:35 -0400 Subject: fix bugs in the mappings converter --- src/cuchaz/enigma/ConvertMain.java | 27 ++++- src/cuchaz/enigma/convert/MappingsConverter.java | 125 ++++++++++++++--------- src/cuchaz/enigma/convert/MemberMatches.java | 4 + src/cuchaz/enigma/mapping/ClassMapping.java | 10 +- src/cuchaz/enigma/mapping/FieldMapping.java | 5 + src/cuchaz/enigma/mapping/MemberMapping.java | 1 + src/cuchaz/enigma/mapping/MethodMapping.java | 5 + 7 files changed, 121 insertions(+), 56 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index c5c92bc..79caae4 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -14,12 +14,16 @@ import cuchaz.enigma.convert.MemberMatches; import cuchaz.enigma.gui.ClassMatchingGui; import cuchaz.enigma.gui.MemberMatchingGui; import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassMapping; import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; import cuchaz.enigma.mapping.MappingsReader; import cuchaz.enigma.mapping.MappingsWriter; +import cuchaz.enigma.mapping.MethodMapping; public class ConvertMain { @@ -158,7 +162,7 @@ public class ConvertMain { // apply matches Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); - MappingsConverter.applyMemberMatches(newMappings, fieldMatches, MappingsConverter.getFieldDoer()); + MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer()); // write out the converted mappings try (FileWriter out = new FileWriter(outMappingsFile)) { @@ -232,8 +236,25 @@ public class ConvertMain { // apply matches Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); - MappingsConverter.applyMemberMatches(newMappings, fieldMatches, MappingsConverter.getFieldDoer()); - MappingsConverter.applyMemberMatches(newMappings, methodMatches, MappingsConverter.getMethodDoer()); + MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer()); + MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer()); + + // check the final mappings + MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); + checker.dropBrokenMappings(newMappings); + + for (java.util.Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) { + System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); + } + for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { + System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); + } + for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { + System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); + } + for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { + System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); + } // write out the converted mappings try (FileWriter out = new FileWriter(outMappingsFile)) { diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 59f3b5b..ddd3a53 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -183,7 +183,7 @@ public class MappingsConverter { return newMappings; } - private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping mapping, final ClassMatches matches, boolean useSimpleName) { + private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) { ClassNameReplacer replacer = new ClassNameReplacer() { @Override @@ -196,30 +196,28 @@ public class MappingsConverter { } }; - ClassMapping newMapping; - String deobfName = mapping.getDeobfName(); + ClassMapping newClassMapping; + String deobfName = oldClassMapping.getDeobfName(); if (deobfName != null) { if (useSimpleName) { deobfName = new ClassEntry(deobfName).getSimpleName(); } - newMapping = new ClassMapping(newObfClass.getName(), deobfName); + newClassMapping = new ClassMapping(newObfClass.getName(), deobfName); } else { - newMapping = new ClassMapping(newObfClass.getName()); + newClassMapping = new ClassMapping(newObfClass.getName()); } // copy fields - for (FieldMapping fieldMapping : mapping.fields()) { - // TODO: map field obf names too... - newMapping.addFieldMapping(new FieldMapping(fieldMapping, replacer)); + for (FieldMapping fieldMapping : oldClassMapping.fields()) { + newClassMapping.addFieldMapping(new FieldMapping(fieldMapping, replacer)); } // copy methods - for (MethodMapping methodMapping : mapping.methods()) { - // TODO: map method obf names too... - newMapping.addMethodMapping(new MethodMapping(methodMapping, replacer)); + for (MethodMapping methodMapping : oldClassMapping.methods()) { + newClassMapping.addMethodMapping(new MethodMapping(methodMapping, replacer)); } - return newMapping; + return newClassMapping; } public static void convertMappings(Mappings mappings, BiMap changes) { @@ -262,8 +260,9 @@ public class MappingsConverter { Collection getObfEntries(JarIndex jarIndex); Collection> getMappings(ClassMapping destClassMapping); Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); - void setMemberObfName(ClassMapping classMapping, MemberMapping memberMapping, String newObfName); + void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, T newEntry); boolean hasObfMember(ClassMapping classMapping, T obfEntry); + void removeMemberByObf(ClassMapping classMapping, T obfEntry); } public static Doer getFieldDoer() { @@ -297,15 +296,20 @@ public class MappingsConverter { } @Override - public void setMemberObfName(ClassMapping classMapping, MemberMapping memberMapping, String newObfName) { + public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, FieldEntry newField) { FieldMapping fieldMapping = (FieldMapping)memberMapping; - classMapping.setFieldObfName(fieldMapping.getObfName(), fieldMapping.getObfType(), newObfName); + classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType()); } @Override public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; } + + @Override + public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) { + classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType())); + } }; } @@ -344,15 +348,20 @@ public class MappingsConverter { } @Override - public void setMemberObfName(ClassMapping classMapping, MemberMapping memberMapping, String newObfName) { + public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, BehaviorEntry newBehavior) { MethodMapping methodMapping = (MethodMapping)memberMapping; - classMapping.setMethodObfName(methodMapping.getObfName(), methodMapping.getObfSignature(), newObfName); + classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature()); } @Override public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; } + + @Override + public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) { + classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature())); + } }; } @@ -477,56 +486,74 @@ public class MappingsConverter { }); } - public static void applyMemberMatches(Mappings mappings, MemberMatches memberMatches, Doer doer) { + public static void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { for (ClassMapping classMapping : mappings.classes()) { - applyMemberMatches(classMapping, memberMatches, doer); + applyMemberMatches(classMapping, classMatches, memberMatches, doer); } } - private static void applyMemberMatches(ClassMapping classMapping, MemberMatches memberMatches, Doer doer) { - ClassEntry classEntry = classMapping.getObfEntry(); + private static void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { + + // get the classes + ClassEntry obfDestClass = classMapping.getObfEntry(); // make a map of all the renames we need to make Map renames = Maps.newHashMap(); for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { - T obfSourceEntry = memberMapping.getObfEntry(classEntry); - T obfDestEntry = memberMatches.matches().get(obfSourceEntry); - if (obfDestEntry != null) { - if (obfDestEntry.getName().equals(obfSourceEntry.getName())) { - // same name, don't need to change anything - continue; - } - renames.put(obfSourceEntry, obfDestEntry); + T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); + T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches); + + // but drop the unmatchable things + if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) { + doer.removeMemberByObf(classMapping, obfOldDestEntry); + continue; + } + + T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry); + if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) { + renames.put(obfOldDestEntry, obfNewDestEntry); } } - // apply to this class (should never need more than n passes) - int numRenames = renames.size(); - for (int i=0; i memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { - T obfSourceEntry = memberMapping.getObfEntry(classEntry); - T obfDestEntry = renames.get(obfSourceEntry); - if (obfDestEntry != null) { - // make sure this rename won't cause a collision - if (!doer.hasObfMember(classMapping, obfDestEntry)) { - doer.setMemberObfName(classMapping, memberMapping, obfDestEntry.getName()); - renames.remove(obfSourceEntry); + if (!renames.isEmpty()) { + + // apply to this class (should never need more than n passes) + int numRenamesAppliedThisRound; + do { + numRenamesAppliedThisRound = 0; + + for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { + T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); + T obfNewDestEntry = renames.get(obfOldDestEntry); + if (obfNewDestEntry != null) { + // make sure this rename won't cause a collision + // otherwise, save it for the next round and try again next time + if (!doer.hasObfMember(classMapping, obfNewDestEntry)) { + doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry); + renames.remove(obfOldDestEntry); + numRenamesAppliedThisRound++; + } } } - } - } - if (!renames.isEmpty()) { - System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d/%d renames left.", - classMapping.getObfFullName(), renames.size(), numRenames - )); - for (Map.Entry entry : renames.entrySet()) { - System.err.println(String.format("\t%s -> %s", entry.getKey(), entry.getValue())); + } while(numRenamesAppliedThisRound > 0); + + if (!renames.isEmpty()) { + System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.", + classMapping.getObfFullName(), renames.size() + )); + for (Map.Entry entry : renames.entrySet()) { + System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName())); + } } } // recurse for (ClassMapping innerClassMapping : classMapping.innerClasses()) { - applyMemberMatches(innerClassMapping, memberMatches, doer); + applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer); } } + + private static T getSourceEntryFromDestMapping(MemberMapping destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) { + return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse()); + } } diff --git a/src/cuchaz/enigma/convert/MemberMatches.java b/src/cuchaz/enigma/convert/MemberMatches.java index 1078ab7..1c162ea 100644 --- a/src/cuchaz/enigma/convert/MemberMatches.java +++ b/src/cuchaz/enigma/convert/MemberMatches.java @@ -113,6 +113,10 @@ public class MemberMatches { public boolean isMatchedDestEntry(T destEntry) { return m_matches.containsValue(destEntry); } + + public boolean isUnmatchableSourceEntry(T sourceEntry) { + return m_unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry); + } public void makeMatch(T sourceEntry, T destEntry) { boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java index ac70df0..38cd3d6 100644 --- a/src/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -243,12 +243,13 @@ public class ClassMapping implements Serializable, Comparable { } } - public void setFieldObfName(String oldObfName, Type obfType, String newObfName) { + public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) { assert(newObfName != null); FieldMapping fieldMapping = m_fieldsByObf.remove(getFieldKey(oldObfName, obfType)); assert(fieldMapping != null); fieldMapping.setObfName(newObfName); - boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(newObfName, obfType), fieldMapping) == null; + fieldMapping.setObfType(newObfType); + boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null; assert(obfWasAdded); } @@ -328,12 +329,13 @@ public class ClassMapping implements Serializable, Comparable { } } - public void setMethodObfName(String oldObfName, Signature obfSignature, String newObfName) { + public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) { assert(newObfName != null); MethodMapping methodMapping = m_methodsByObf.remove(getMethodKey(oldObfName, obfSignature)); assert(methodMapping != null); methodMapping.setObfName(newObfName); - boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, obfSignature), methodMapping) == null; + methodMapping.setObfSignature(newObfSignature); + boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null; assert(obfWasAdded); } diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java index 3aa9e69..463d901 100644 --- a/src/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/cuchaz/enigma/mapping/FieldMapping.java @@ -32,6 +32,7 @@ public class FieldMapping implements Serializable, Comparable, Mem m_obfType = new Type(other.m_obfType, obfClassNameReplacer); } + @Override public String getObfName() { return m_obfName; } @@ -52,6 +53,10 @@ public class FieldMapping implements Serializable, Comparable, Mem return m_obfType; } + public void setObfType(Type val) { + m_obfType = val; + } + @Override public int compareTo(FieldMapping other) { return (m_obfName + m_obfType).compareTo(other.m_obfName + other.m_obfType); diff --git a/src/cuchaz/enigma/mapping/MemberMapping.java b/src/cuchaz/enigma/mapping/MemberMapping.java index d62a7c1..d8e2ee3 100644 --- a/src/cuchaz/enigma/mapping/MemberMapping.java +++ b/src/cuchaz/enigma/mapping/MemberMapping.java @@ -3,4 +3,5 @@ package cuchaz.enigma.mapping; public interface MemberMapping { T getObfEntry(ClassEntry classEntry); + String getObfName(); } diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java index a67e352..bef232e 100644 --- a/src/cuchaz/enigma/mapping/MethodMapping.java +++ b/src/cuchaz/enigma/mapping/MethodMapping.java @@ -52,6 +52,7 @@ public class MethodMapping implements Serializable, Comparable, M } } + @Override public String getObfName() { return m_obfName; } @@ -72,6 +73,10 @@ public class MethodMapping implements Serializable, Comparable, M return m_obfSignature; } + public void setObfSignature(Signature val) { + m_obfSignature = val; + } + public Iterable arguments() { return m_arguments.values(); } -- cgit v1.2.3 From 2ff03cb72dccafd387776f5b0f91be1e8b056afb Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 15 Mar 2015 09:53:21 -0400 Subject: repackage for 0.9 beta --- src/cuchaz/enigma/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java index db14778..8910a96 100644 --- a/src/cuchaz/enigma/Constants.java +++ b/src/cuchaz/enigma/Constants.java @@ -12,7 +12,7 @@ package cuchaz.enigma; public class Constants { public static final String Name = "Enigma"; - public static final String Version = "0.8 beta"; + public static final String Version = "0.9 beta"; public static final String Url = "http://www.cuchazinteractive.com/enigma"; public static final int MiB = 1024 * 1024; // 1 mebibyte public static final int KiB = 1024; // 1 kebibyte -- cgit v1.2.3 From c133e05b786ff5357931842581571c046f958c74 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 16 Mar 2015 12:29:17 -0400 Subject: fix a zillion issues with inner classes --- src/cuchaz/enigma/Deobfuscator.java | 8 +- src/cuchaz/enigma/TranslatingTypeLoader.java | 2 +- src/cuchaz/enigma/analysis/EntryReference.java | 2 +- src/cuchaz/enigma/analysis/JarIndex.java | 2 +- src/cuchaz/enigma/bytecode/ClassRenamer.java | 2 +- src/cuchaz/enigma/bytecode/ClassTranslator.java | 17 +-- src/cuchaz/enigma/bytecode/InnerClassWriter.java | 4 +- src/cuchaz/enigma/convert/ClassIdentity.java | 2 +- src/cuchaz/enigma/convert/MappingsConverter.java | 2 +- src/cuchaz/enigma/gui/CodeReader.java | 2 +- src/cuchaz/enigma/gui/GuiController.java | 2 +- src/cuchaz/enigma/mapping/ClassEntry.java | 31 ++++-- src/cuchaz/enigma/mapping/ClassMapping.java | 79 +++++++------- src/cuchaz/enigma/mapping/Mappings.java | 28 +++++ src/cuchaz/enigma/mapping/MappingsRenamer.java | 128 +++++++++++------------ src/cuchaz/enigma/mapping/Translator.java | 73 ++++--------- 16 files changed, 198 insertions(+), 186 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index f5012bd..5a23ce5 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -438,7 +438,13 @@ public class Deobfuscator { public boolean hasDeobfuscatedName(Entry obfEntry) { Translator translator = getTranslator(TranslationDirection.Deobfuscating); if (obfEntry instanceof ClassEntry) { - return translator.translate((ClassEntry)obfEntry) != null; + ClassEntry obfClass = (ClassEntry)obfEntry; + ClassEntry translated = translator.translateEntry(obfClass); + if (obfClass.isInnerClass()) { + return !obfClass.getInnermostClassName().equals(translated.getInnermostClassName()); + } else { + return !obfClass.equals(translated); + } } else if (obfEntry instanceof FieldEntry) { return translator.translate((FieldEntry)obfEntry) != null; } else if (obfEntry instanceof MethodEntry) { diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index 7b57cfa..ecd7d64 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -205,7 +205,7 @@ public class TranslatingTypeLoader implements ITypeLoader { } if (obfClassEntry.isInnerClass()) { // try just the inner class name - classNamesToTry.add(obfClassEntry.getInnerClassName()); + classNamesToTry.add(obfClassEntry.getInnermostClassName()); } return classNamesToTry; } diff --git a/src/cuchaz/enigma/analysis/EntryReference.java b/src/cuchaz/enigma/analysis/EntryReference.java index bb611df..d0a5c6a 100644 --- a/src/cuchaz/enigma/analysis/EntryReference.java +++ b/src/cuchaz/enigma/analysis/EntryReference.java @@ -74,7 +74,7 @@ public class EntryReference { ClassEntry classEntry = (ClassEntry)getNameableEntry(); if (classEntry.isInnerClass()) { // make sure we only rename the inner class name - return classEntry.getInnerClassName(); + return classEntry.getInnermostClassName(); } } diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 7ebbd97..a4a3abb 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -312,7 +312,7 @@ public class JarIndex { // does this class already have an outer class? if (classEntry.isInnerClass()) { - return classEntry.getOuterClassEntry(); + return classEntry.getOutermostClassEntry(); } InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); if (innerClassesAttribute != null) { diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java index a5fea92..e9cdea3 100644 --- a/src/cuchaz/enigma/bytecode/ClassRenamer.java +++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java @@ -43,7 +43,7 @@ public class ClassRenamer { for (int i = 0; i < attr.tableLength(); i++) { ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i))); if (attr.innerNameIndex(i) != 0) { - attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnerClassName())); + attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnermostClassName())); } /* DEBUG diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java index 4167731..94ab2c4 100644 --- a/src/cuchaz/enigma/bytecode/ClassTranslator.java +++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java @@ -26,7 +26,6 @@ import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.Signature; import cuchaz.enigma.mapping.Translator; import cuchaz.enigma.mapping.Type; @@ -101,26 +100,30 @@ public class ClassTranslator { } // translate the type - Type translatedType = m_translator.translateType(new Type(field.getFieldInfo().getDescriptor())); + Type translatedType = m_translator.translateType(entry.getType()); field.getFieldInfo().setDescriptor(translatedType.toString()); } // translate all the methods and constructors for (CtBehavior behavior : c.getDeclaredBehaviors()) { + + BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior); + if (behavior instanceof CtMethod) { CtMethod method = (CtMethod)behavior; // translate the name - MethodEntry entry = EntryFactory.getMethodEntry(method); String translatedName = m_translator.translate(entry); if (translatedName != null) { method.setName(translatedName); } } - // translate the type - Signature translatedSignature = m_translator.translateSignature(new Signature(behavior.getMethodInfo().getDescriptor())); - behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); + if (entry.getSignature() != null) { + // translate the type + Signature translatedSignature = m_translator.translateSignature(entry.getSignature()); + behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); + } } // translate all the class names referenced in the code @@ -137,7 +140,7 @@ public class ClassTranslator { // translate the source file attribute too ClassEntry deobfClassEntry = map.get(classEntry); if (deobfClassEntry != null) { - String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOuterClassName()) + ".java"; + String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassName()) + ".java"; c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); } } diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java index dd21a78..976028d 100644 --- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -93,7 +93,7 @@ public class InnerClassWriter { // get the new inner class name ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); - ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry(); + ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOutermostClassEntry(); // here's what the JVM spec says about the InnerClasses attribute // append(inner, parent, 0 if anonymous else simple name, flags); @@ -105,7 +105,7 @@ public class InnerClassWriter { int innerClassNameIndex = 0; int accessFlags = 0; if (!m_index.isAnonymousClass(obfClassEntry)) { - innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnerClassName()); + innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName()); } attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags); diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index 35667b0..d76cd63 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -180,7 +180,7 @@ public class ClassIdentity { } } - m_outer = EntryFactory.getClassEntry(c).getOuterClassName(); + m_outer = EntryFactory.getClassEntry(c).getOutermostClassName(); } private void addReference(EntryReference reference) { diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index ddd3a53..2afa120 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -169,7 +169,7 @@ public class MappingsConverter { newMappings.addClassMapping(destMapping); } } else { - destMapping = destMapping.getInnerClassByObf(destChainClassEntry.getInnerClassName()); + destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName()); if (destMapping == null) { destMapping = new ClassMapping(destChainClassEntry.getName()); destMapping.addInnerClassMapping(destMapping); diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java index 743ef2e..fb8e082 100644 --- a/src/cuchaz/enigma/gui/CodeReader.java +++ b/src/cuchaz/enigma/gui/CodeReader.java @@ -106,7 +106,7 @@ public class CodeReader extends JEditorPane { // get the outermost class ClassEntry outermostClassEntry = classEntry; while (outermostClassEntry.isInnerClass()) { - outermostClassEntry = outermostClassEntry.getOuterClassEntry(); + outermostClassEntry = outermostClassEntry.getOutermostClassEntry(); } // decompile it diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java index 9fa633e..552ee47 100644 --- a/src/cuchaz/enigma/gui/GuiController.java +++ b/src/cuchaz/enigma/gui/GuiController.java @@ -257,7 +257,7 @@ public class GuiController { // get the reference target class EntryReference obfReference = m_deobfuscator.obfuscateReference(deobfReference); - ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOuterClassEntry(); + ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry(); if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); } diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java index 69e66bc..5f3b5e2 100644 --- a/src/cuchaz/enigma/mapping/ClassEntry.java +++ b/src/cuchaz/enigma/mapping/ClassEntry.java @@ -13,6 +13,8 @@ package cuchaz.enigma.mapping; import java.io.Serializable; import java.util.List; +import com.beust.jcommander.internal.Lists; + public class ClassEntry implements Entry, Serializable { private static final long serialVersionUID = 4235460580973955811L; @@ -29,7 +31,7 @@ public class ClassEntry implements Entry, Serializable { m_name = className; - if (isInnerClass() && getInnerClassName().indexOf('/') >= 0) { + if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) { throw new IllegalArgumentException("Inner class must not have a package: " + className); } } @@ -84,22 +86,39 @@ public class ClassEntry implements Entry, Serializable { return m_name.lastIndexOf('$') >= 0; } - public String getOuterClassName() { + public List getClassChainNames() { + return Lists.newArrayList(m_name.split("\\$")); + } + + public List getClassChain() { + List entries = Lists.newArrayList(); + StringBuilder buf = new StringBuilder(); + for (String name : getClassChainNames()) { + if (buf.length() > 0) { + buf.append("$"); + } + buf.append(name); + entries.add(new ClassEntry(buf.toString())); + } + return entries; + } + + public String getOutermostClassName() { if (isInnerClass()) { return m_name.substring(0, m_name.lastIndexOf('$')); } return m_name; } - public String getInnerClassName() { + public String getInnermostClassName() { if (!isInnerClass()) { throw new Error("This is not an inner class!"); } return m_name.substring(m_name.lastIndexOf('$') + 1); } - public ClassEntry getOuterClassEntry() { - return new ClassEntry(getOuterClassName()); + public ClassEntry getOutermostClassEntry() { + return new ClassEntry(getOutermostClassName()); } public boolean isInDefaultPackage() { @@ -130,7 +149,7 @@ public class ClassEntry implements Entry, Serializable { buf.append(chainEntry.getName()); } else { buf.append("$"); - buf.append(chainEntry.isInnerClass() ? chainEntry.getInnerClassName() : chainEntry.getSimpleName()); + buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName()); } if (chainEntry == this) { diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java index 38cd3d6..6e7fd17 100644 --- a/src/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -23,22 +23,23 @@ public class ClassMapping implements Serializable, Comparable { private String m_obfFullName; private String m_obfSimpleName; private String m_deobfName; - private Map m_innerClassesByObf; + private Map m_innerClassesByObfSimple; private Map m_innerClassesByDeobf; private Map m_fieldsByObf; private Map m_fieldsByDeobf; private Map m_methodsByObf; private Map m_methodsByDeobf; - public ClassMapping(String obfName) { - this(obfName, null); + public ClassMapping(String obfFullName) { + this(obfFullName, null); } - public ClassMapping(String obfName, String deobfName) { - m_obfFullName = obfName; - m_obfSimpleName = new ClassEntry(obfName).getSimpleName(); + public ClassMapping(String obfFullName, String deobfName) { + m_obfFullName = obfFullName; + ClassEntry classEntry = new ClassEntry(obfFullName); + m_obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName(); m_deobfName = NameValidator.validateClassName(deobfName, false); - m_innerClassesByObf = Maps.newHashMap(); + m_innerClassesByObfSimple = Maps.newHashMap(); m_innerClassesByDeobf = Maps.newHashMap(); m_fieldsByObf = Maps.newHashMap(); m_fieldsByDeobf = Maps.newHashMap(); @@ -65,12 +66,12 @@ public class ClassMapping implements Serializable, Comparable { //// INNER CLASSES //////// public Iterable innerClasses() { - assert (m_innerClassesByObf.size() >= m_innerClassesByDeobf.size()); - return m_innerClassesByObf.values(); + assert (m_innerClassesByObfSimple.size() >= m_innerClassesByDeobf.size()); + return m_innerClassesByObfSimple.values(); } public void addInnerClassMapping(ClassMapping classMapping) { - boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfSimpleName(), classMapping) == null; + boolean obfWasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null; assert (obfWasAdded); if (classMapping.getDeobfName() != null) { assert (isSimpleClassName(classMapping.getDeobfName())); @@ -80,7 +81,7 @@ public class ClassMapping implements Serializable, Comparable { } public void removeInnerClassMapping(ClassMapping classMapping) { - boolean obfWasRemoved = m_innerClassesByObf.remove(classMapping.getObfSimpleName()) != null; + boolean obfWasRemoved = m_innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null; assert (obfWasRemoved); if (classMapping.getDeobfName() != null) { boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; @@ -88,20 +89,19 @@ public class ClassMapping implements Serializable, Comparable { } } - public ClassMapping getOrCreateInnerClass(String obfName) { - assert (isSimpleClassName(obfName)); - ClassMapping classMapping = m_innerClassesByObf.get(obfName); + public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) { + ClassMapping classMapping = m_innerClassesByObfSimple.get(obfInnerClass.getInnermostClassName()); if (classMapping == null) { - classMapping = new ClassMapping(obfName); - boolean wasAdded = m_innerClassesByObf.put(obfName, classMapping) == null; + classMapping = new ClassMapping(obfInnerClass.getName()); + boolean wasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null; assert (wasAdded); } return classMapping; } - public ClassMapping getInnerClassByObf(String obfName) { - assert (isSimpleClassName(obfName)); - return m_innerClassesByObf.get(obfName); + public ClassMapping getInnerClassByObfSimple(String obfSimpleName) { + assert (isSimpleClassName(obfSimpleName)); + return m_innerClassesByObfSimple.get(obfSimpleName); } public ClassMapping getInnerClassByDeobf(String deobfName) { @@ -109,35 +109,25 @@ public class ClassMapping implements Serializable, Comparable { return m_innerClassesByDeobf.get(deobfName); } - public ClassMapping getInnerClassByDeobfThenObf(String name) { + public ClassMapping getInnerClassByDeobfThenObfSimple(String name) { ClassMapping classMapping = getInnerClassByDeobf(name); if (classMapping == null) { - classMapping = getInnerClassByObf(name); + classMapping = getInnerClassByObfSimple(name); } return classMapping; } - public String getObfInnerClassSimpleName(String deobfName) { - assert (isSimpleClassName(deobfName)); - ClassMapping classMapping = m_innerClassesByDeobf.get(deobfName); - if (classMapping != null) { - return classMapping.getObfSimpleName(); - } - return null; - } - - public String getDeobfInnerClassName(String obfName) { - assert (isSimpleClassName(obfName)); - ClassMapping classMapping = m_innerClassesByObf.get(obfName); + public String getDeobfInnerClassName(String obfSimpleName) { + assert (isSimpleClassName(obfSimpleName)); + ClassMapping classMapping = m_innerClassesByObfSimple.get(obfSimpleName); if (classMapping != null) { return classMapping.getDeobfName(); } return null; } - public void setInnerClassName(String obfName, String deobfName) { - assert (isSimpleClassName(obfName)); - ClassMapping classMapping = getOrCreateInnerClass(obfName); + public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) { + ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass); if (classMapping.getDeobfName() != null) { boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; assert (wasRemoved); @@ -150,6 +140,15 @@ public class ClassMapping implements Serializable, Comparable { } } + public boolean hasInnerClassByObfSimple(String obfSimpleName) { + return m_innerClassesByObfSimple.containsKey(obfSimpleName); + } + + public boolean hasInnerClassByDeobf(String deobfName) { + return m_innerClassesByDeobf.containsKey(deobfName); + } + + //// FIELDS //////// public Iterable fields() { @@ -382,7 +381,7 @@ public class ClassMapping implements Serializable, Comparable { buf.append("\n"); } buf.append("Inner Classes:\n"); - for (ClassMapping classMapping : m_innerClassesByObf.values()) { + for (ClassMapping classMapping : m_innerClassesByObfSimple.values()) { buf.append("\t"); buf.append(classMapping.getObfSimpleName()); buf.append(" <-> "); @@ -404,11 +403,11 @@ public class ClassMapping implements Serializable, Comparable { public boolean renameObfClass(String oldObfClassName, String newObfClassName) { // rename inner classes - for (ClassMapping innerClassMapping : new ArrayList(m_innerClassesByObf.values())) { + for (ClassMapping innerClassMapping : new ArrayList(m_innerClassesByObfSimple.values())) { if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) { - boolean wasRemoved = m_innerClassesByObf.remove(oldObfClassName) != null; + boolean wasRemoved = m_innerClassesByObfSimple.remove(oldObfClassName) != null; assert (wasRemoved); - boolean wasAdded = m_innerClassesByObf.put(newObfClassName, innerClassMapping) == null; + boolean wasAdded = m_innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null; assert (wasAdded); } } diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java index a85bcbf..659d23a 100644 --- a/src/cuchaz/enigma/mapping/Mappings.java +++ b/src/cuchaz/enigma/mapping/Mappings.java @@ -13,9 +13,11 @@ package cuchaz.enigma.mapping; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; +import com.beust.jcommander.internal.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -89,6 +91,18 @@ public class Mappings implements Serializable { return m_classesByDeobf.get(deobfName); } + public void setClassDeobfName(ClassMapping classMapping, String deobfName) { + if (classMapping.getDeobfName() != null) { + boolean wasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (wasRemoved); + } + classMapping.setDeobfName(deobfName); + if (deobfName != null) { + boolean wasAdded = m_classesByDeobf.put(deobfName, classMapping) == null; + assert (wasAdded); + } + } + public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { switch (direction) { case Deobfuscating: @@ -185,4 +199,18 @@ public class Mappings implements Serializable { } return false; } + + public List getClassMappingChain(ClassEntry obfClass) { + List mappingChain = Lists.newArrayList(); + ClassMapping classMapping = null; + for (ClassEntry obfClassEntry : obfClass.getClassChain()) { + if (mappingChain.isEmpty()) { + classMapping = m_classesByObf.get(obfClassEntry.getName()); + } else if (classMapping != null) { + classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName()); + } + mappingChain.add(classMapping); + } + return mappingChain; + } } diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java index 16f700d..d7766dc 100644 --- a/src/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java @@ -13,10 +13,10 @@ package cuchaz.enigma.mapping; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; +import java.util.List; import java.util.Set; import java.util.zip.GZIPOutputStream; -import cuchaz.enigma.Constants; import cuchaz.enigma.analysis.JarIndex; public class MappingsRenamer { @@ -30,48 +30,43 @@ public class MappingsRenamer { } public void setClassName(ClassEntry obf, String deobfName) { - deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass()); - ClassEntry targetEntry = new ClassEntry(deobfName); - if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(targetEntry)) { - throw new IllegalNameException(deobfName, "There is already a class with that name"); - } - ClassMapping classMapping = getOrCreateClassMapping(obf); + deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass()); - if (obf.isInnerClass()) { - classMapping.setInnerClassName(obf.getInnerClassName(), deobfName); + List mappingChain = getOrCreateClassMappingChain(obf); + if (mappingChain.size() == 1) { + + if (deobfName != null) { + // make sure we don't rename to an existing obf or deobf class + if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(new ClassEntry(deobfName))) { + throw new IllegalNameException(deobfName, "There is already a class with that name"); + } + } + + ClassMapping classMapping = mappingChain.get(0); + m_mappings.setClassDeobfName(classMapping, deobfName); + } else { - if (classMapping.getDeobfName() != null) { - boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null; - assert (wasRemoved); + + ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2); + + if (deobfName != null) { + // make sure we don't rename to an existing obf or deobf inner class + if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) { + throw new IllegalNameException(deobfName, "There is already a class with that name"); + } } - classMapping.setDeobfName(deobfName); - boolean wasAdded = m_mappings.m_classesByDeobf.put(deobfName, classMapping) == null; - assert (wasAdded); + + outerClassMapping.setInnerClassName(obf, deobfName); } } public void removeClassMapping(ClassEntry obf) { - ClassMapping classMapping = getClassMapping(obf); - if (obf.isInnerClass()) { - classMapping.setInnerClassName(obf.getName(), null); - } else { - boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null; - assert (wasRemoved); - classMapping.setDeobfName(null); - } + setClassName(obf, null); } public void markClassAsDeobfuscated(ClassEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf); - if (obf.isInnerClass()) { - String innerClassName = Constants.NonePackage + "/" + obf.getInnerClassName(); - classMapping.setInnerClassName(innerClassName, innerClassName); - } else { - classMapping.setDeobfName(obf.getName()); - boolean wasAdded = m_mappings.m_classesByDeobf.put(obf.getName(), classMapping) == null; - assert (wasAdded); - } + setClassName(obf, obf.isInnerClass() ? obf.getInnermostClassName() : obf.getSimpleName()); } public void setFieldName(FieldEntry obf, String deobfName) { @@ -81,17 +76,17 @@ public class MappingsRenamer { throw new IllegalNameException(deobfName, "There is already a field with that name"); } - ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); classMapping.setFieldName(obf.getName(), obf.getType(), deobfName); } public void removeFieldMapping(FieldEntry obf) { - ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType())); } public void markFieldAsDeobfuscated(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName()); } @@ -121,7 +116,7 @@ public class MappingsRenamer { throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); } - ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); } @@ -132,7 +127,7 @@ public class MappingsRenamer { } public void removeMethodMapping(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); classMapping.setMethodName(obf.getName(), obf.getSignature(), null); } @@ -143,7 +138,7 @@ public class MappingsRenamer { } public void markMethodAsDeobfuscated(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); } @@ -154,17 +149,17 @@ public class MappingsRenamer { throw new IllegalNameException(deobfName, "There is already an argument with that name"); } - ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); } public void removeArgumentMapping(ArgumentEntry obf) { - ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); } public void markArgumentAsDeobfuscated(ArgumentEntry obf) { - ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); } @@ -204,34 +199,31 @@ public class MappingsRenamer { gzipout.finish(); } - private ClassMapping getClassMapping(ClassEntry obfClassEntry) { - return m_mappings.m_classesByObf.get(obfClassEntry.getOuterClassName()); - } - private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) { - String obfClassName = obfClassEntry.getOuterClassName(); - ClassMapping classMapping = m_mappings.m_classesByObf.get(obfClassName); - if (classMapping == null) { - classMapping = new ClassMapping(obfClassName); - boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null; - assert (obfWasAdded); - } - return classMapping; - } - - private ClassMapping getClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) { - ClassMapping classMapping = getClassMapping(obfClassEntry); - if (obfClassEntry.isInDefaultPackage()) { - classMapping = classMapping.getInnerClassByObf(obfClassEntry.getInnerClassName()); - } - return classMapping; - } - - private ClassMapping getOrCreateClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) { - ClassMapping classMapping = getOrCreateClassMapping(obfClassEntry); - if (obfClassEntry.isInnerClass()) { - classMapping = classMapping.getOrCreateInnerClass(obfClassEntry.getInnerClassName()); + List mappingChain = getOrCreateClassMappingChain(obfClassEntry); + return mappingChain.get(mappingChain.size() - 1); + } + + private List getOrCreateClassMappingChain(ClassEntry obfClassEntry) { + List classChain = obfClassEntry.getClassChain(); + List mappingChain = m_mappings.getClassMappingChain(obfClassEntry); + for (int i=0; i m_classes; private TranslationIndex m_index; + private ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() { + @Override + public String replace(String className) { + return translateEntry(new ClassEntry(className)).getName(); + } + }; + public Translator() { m_direction = null; m_classes = Maps.newHashMap(); @@ -69,48 +76,16 @@ public class Translator { } } - public String translateClass(String className) { - return translate(new ClassEntry(className)); - } - public String translate(ClassEntry in) { - - if (in.isInnerClass()) { - - // translate everything in the class chain, or return null - List mappingsChain = getClassMappingChain(in); - StringBuilder buf = new StringBuilder(); - for (ClassMapping classMapping : mappingsChain) { - if (classMapping == null) { - return null; - } - boolean isFirstClass = buf.length() == 0; - String name = m_direction.choose( - classMapping.getDeobfName(), - isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() - ); - if (name == null) { - return null; - } - if (!isFirstClass) { - buf.append("$"); - } - buf.append(name); - } - return buf.toString(); - - } else { - - // normal classes are easier - ClassMapping classMapping = m_classes.get(in.getName()); - if (classMapping == null) { - return null; - } - return m_direction.choose( - classMapping.getDeobfName(), - classMapping.getObfFullName() - ); + ClassEntry translated = translateEntry(in); + if (translated.equals(in)) { + return null; } + return translated.getName(); + } + + public String translateClass(String className) { + return translate(new ClassEntry(className)); } public ClassEntry translateEntry(ClassEntry in) { @@ -264,21 +239,11 @@ public class Translator { } public Type translateType(Type type) { - return new Type(type, new ClassNameReplacer() { - @Override - public String replace(String className) { - return translateClass(className); - } - }); + return new Type(type, m_classNameReplacer); } public Signature translateSignature(Signature signature) { - return new Signature(signature, new ClassNameReplacer() { - @Override - public String replace(String className) { - return translateClass(className); - } - }); + return new Signature(signature, m_classNameReplacer); } private ClassMapping findClassMapping(ClassEntry in) { @@ -302,8 +267,8 @@ public class Translator { ClassMapping innerClassMapping = null; if (outerClassMapping != null) { innerClassMapping = m_direction.choose( - outerClassMapping.getInnerClassByObf(parts[i]), - outerClassMapping.getInnerClassByDeobfThenObf(parts[i]) + outerClassMapping.getInnerClassByObfSimple(parts[i]), + outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i]) ); } mappingsChain.add(innerClassMapping); -- cgit v1.2.3 From 563c5e08e3d61bfd39402a94e78bbaaf75623b04 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 16 Mar 2015 12:52:09 -0400 Subject: fix more inner class issues --- src/cuchaz/enigma/analysis/JarIndex.java | 2 +- src/cuchaz/enigma/bytecode/InnerClassWriter.java | 2 +- src/cuchaz/enigma/convert/ClassIdentity.java | 2 +- src/cuchaz/enigma/gui/CodeReader.java | 8 +------- src/cuchaz/enigma/mapping/ClassEntry.java | 21 ++++++++++++++++----- 5 files changed, 20 insertions(+), 15 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index a4a3abb..7ebbd97 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -312,7 +312,7 @@ public class JarIndex { // does this class already have an outer class? if (classEntry.isInnerClass()) { - return classEntry.getOutermostClassEntry(); + return classEntry.getOuterClassEntry(); } InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); if (innerClassesAttribute != null) { diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java index 976028d..bb64315 100644 --- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -93,7 +93,7 @@ public class InnerClassWriter { // get the new inner class name ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); - ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOutermostClassEntry(); + ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry(); // here's what the JVM spec says about the InnerClasses attribute // append(inner, parent, 0 if anonymous else simple name, flags); diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index d76cd63..35667b0 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -180,7 +180,7 @@ public class ClassIdentity { } } - m_outer = EntryFactory.getClassEntry(c).getOutermostClassName(); + m_outer = EntryFactory.getClassEntry(c).getOuterClassName(); } private void addReference(EntryReference reference) { diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java index fb8e082..d8fb394 100644 --- a/src/cuchaz/enigma/gui/CodeReader.java +++ b/src/cuchaz/enigma/gui/CodeReader.java @@ -103,14 +103,8 @@ public class CodeReader extends JEditorPane { @Override public void run() { - // get the outermost class - ClassEntry outermostClassEntry = classEntry; - while (outermostClassEntry.isInnerClass()) { - outermostClassEntry = outermostClassEntry.getOutermostClassEntry(); - } - // decompile it - CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); + CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName()); String source = deobfuscator.getSource(sourceTree); setCode(source); m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java index 5f3b5e2..e6400b8 100644 --- a/src/cuchaz/enigma/mapping/ClassEntry.java +++ b/src/cuchaz/enigma/mapping/ClassEntry.java @@ -105,20 +105,31 @@ public class ClassEntry implements Entry, Serializable { public String getOutermostClassName() { if (isInnerClass()) { - return m_name.substring(0, m_name.lastIndexOf('$')); + return m_name.substring(0, m_name.indexOf('$')); } return m_name; } - public String getInnermostClassName() { + public ClassEntry getOutermostClassEntry() { + return new ClassEntry(getOutermostClassName()); + } + + public String getOuterClassName() { if (!isInnerClass()) { throw new Error("This is not an inner class!"); } - return m_name.substring(m_name.lastIndexOf('$') + 1); + return m_name.substring(0, m_name.lastIndexOf('$')); } - public ClassEntry getOutermostClassEntry() { - return new ClassEntry(getOutermostClassName()); + public ClassEntry getOuterClassEntry() { + return new ClassEntry(getOuterClassName()); + } + + public String getInnermostClassName() { + if (!isInnerClass()) { + throw new Error("This is not an inner class!"); + } + return m_name.substring(m_name.lastIndexOf('$') + 1); } public boolean isInDefaultPackage() { -- cgit v1.2.3 From 5e3743a0aca3529eacf9be400c8b8d7547f66e7f Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 16 Mar 2015 19:22:22 -0400 Subject: started adding minimal support for generics fixed mark-as-deobfuscated issue --- src/cuchaz/enigma/Deobfuscator.java | 9 +- src/cuchaz/enigma/analysis/JarIndex.java | 31 ++-- .../enigma/analysis/SourceIndexClassVisitor.java | 7 +- src/cuchaz/enigma/bytecode/ClassRenamer.java | 164 ++++++++++++++------- src/cuchaz/enigma/bytecode/ClassTranslator.java | 42 ++---- src/cuchaz/enigma/mapping/EntryFactory.java | 17 ++- src/cuchaz/enigma/mapping/MappingsChecker.java | 2 +- src/cuchaz/enigma/mapping/MappingsRenamer.java | 10 +- src/cuchaz/enigma/mapping/ParameterizedType.java | 54 +++++++ src/cuchaz/enigma/mapping/Signature.java | 10 -- src/cuchaz/enigma/mapping/Type.java | 78 ++++++++-- 11 files changed, 294 insertions(+), 130 deletions(-) create mode 100644 src/cuchaz/enigma/mapping/ParameterizedType.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 5a23ce5..b63f163 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -439,12 +439,9 @@ public class Deobfuscator { Translator translator = getTranslator(TranslationDirection.Deobfuscating); if (obfEntry instanceof ClassEntry) { ClassEntry obfClass = (ClassEntry)obfEntry; - ClassEntry translated = translator.translateEntry(obfClass); - if (obfClass.isInnerClass()) { - return !obfClass.getInnermostClassName().equals(translated.getInnermostClassName()); - } else { - return !obfClass.equals(translated); - } + List mappingChain = m_mappings.getClassMappingChain(obfClass); + ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1); + return classMapping != null && classMapping.getDeobfName() != null; } else if (obfEntry instanceof FieldEntry) { return translator.translate((FieldEntry)obfEntry) != null; } else if (obfEntry instanceof MethodEntry) { diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 7ebbd97..e255468 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -28,6 +28,7 @@ import javassist.CtMethod; import javassist.NotFoundException; import javassist.bytecode.AccessFlag; import javassist.bytecode.Descriptor; +import javassist.bytecode.EnclosingMethodAttribute; import javassist.bytecode.FieldInfo; import javassist.bytecode.InnerClassesAttribute; import javassist.expr.ConstructorCall; @@ -314,15 +315,6 @@ public class JarIndex { if (classEntry.isInnerClass()) { return classEntry.getOuterClassEntry(); } - InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (innerClassesAttribute != null) { - for (int i=0; i 0) { + return EntryFactory.getBehaviorEntry( + Descriptor.toJvmName(enclosingMethodAttribute.className()), + enclosingMethodAttribute.methodName(), + enclosingMethodAttribute.methodDescriptor() + ); + } else { + // an attribute but no method? assume not anonymous + return null; + } + } + + // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous + InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (innerClassesAttribute != null) { + return null; + } + ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); // anonymous classes: diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java index f4f4956..f4202b5 100644 --- a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -30,7 +30,6 @@ import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.Type; public class SourceIndexClassVisitor extends SourceIndexVisitor { @@ -93,8 +92,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { @Override public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName(), new Type(def.getErasedSignature())); + FieldEntry fieldEntry = EntryFactory.getFieldEntry(def); assert (node.getVariables().size() == 1); VariableInitializer variable = node.getVariables().firstOrNullObject(); index.addDeclaration(variable.getNameToken(), fieldEntry); @@ -106,8 +104,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { // treat enum declarations as field declarations FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName(), new Type(def.getErasedSignature())); + FieldEntry fieldEntry = EntryFactory.getFieldEntry(def); index.addDeclaration(node.getNameToken(), fieldEntry); return recurse(node, index); diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java index e9cdea3..8bc084d 100644 --- a/src/cuchaz/enigma/bytecode/ClassRenamer.java +++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java @@ -23,60 +23,100 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.ParameterizedType; +import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.Type; public class ClassRenamer { - public static void renameClasses(CtClass c, Map map) { - - // build the map used by javassist - ClassMap nameMap = new ClassMap(); - for (Map.Entry entry : map.entrySet()) { - nameMap.put(entry.getKey().getName(), entry.getValue().getName()); - } - - c.replaceClassName(nameMap); - - // replace simple names in the InnerClasses attribute too - ConstPool constants = c.getClassFile().getConstPool(); - InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i))); - if (attr.innerNameIndex(i) != 0) { - attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnermostClassName())); + public static void renameClasses(CtClass c, final Translator translator) { + renameClasses(c, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = translator.translateEntry(new ClassEntry(className)); + if (entry != null) { + return entry.getName(); } - - /* DEBUG - System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); - */ + return null; } - } + }); } - public static Set getAllClassEntries(final CtClass c) { + public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) { + renameClasses(c, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = new ClassEntry(className); + if (entry.isInDefaultPackage()) { + return newPackageName + "/" + entry.getName(); + } + return null; + } + }); + } + + public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) { + renameClasses(c, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = new ClassEntry(className); + if (entry.getPackageName().equals(oldPackageName)) { + return entry.getSimpleName(); + } + return null; + } + }); + } + + public static void renameClasses(CtClass c, ClassNameReplacer replacer) { + Map map = Maps.newHashMap(); + for (ParameterizedType type : ClassRenamer.getAllClassTypes(c)) { + ParameterizedType renamedType = new ParameterizedType(type, replacer); + if (!type.equals(renamedType)) { + map.put(type, renamedType); + } + } + renameTypes(c, map); + } + + public static Set getAllClassTypes(final CtClass c) { - // get the classes that javassist knows about - final Set entries = Sets.newHashSet(); + // TODO: might have to scan SignatureAttributes directly because javassist is buggy + + // get the class types that javassist knows about + final Set types = Sets.newHashSet(); ClassMap map = new ClassMap() { @Override public Object get(Object obj) { if (obj instanceof String) { String str = (String)obj; - // javassist throws a lot of weird things at this map - // I either have to implement my on class scanner, or just try to filter out the weirdness - // I'm opting to filter out the weirdness for now + // sometimes javasist gives us dot-separated classes... whadda hell? + str = str.replace('.', '/'); - // skip anything with generic arguments - if (str.indexOf('<') >= 0 || str.indexOf('>') >= 0 || str.indexOf(';') >= 0) { + // skip weird types + boolean hasNestedParams = str.indexOf('<') >= 0 && str.indexOf('<', str.indexOf('<')+1) >= 0; + boolean hasWeirdChars = str.indexOf('*') >= 0 || str.indexOf('-') >= 0 || str.indexOf('+') >= 0; + if (hasNestedParams || hasWeirdChars) { + // TEMP + System.out.println("Skipped translating: " + str); return null; } - // convert path/to/class.inner to path/to/class$inner - str = str.replace('.', '$'); + ParameterizedType type = new ParameterizedType(new Type("L" + str + ";")); + assert(type.isClass()); + // TEMP + try { + type.getClassEntry(); + } catch (Throwable t) { + // bad type + // TEMP + System.out.println("Skipped translating: " + str); + return null; + } - // remember everything else - entries.add(new ClassEntry(str)); + types.add(type); } return null; } @@ -85,26 +125,46 @@ public class ClassRenamer { }; c.replaceClassName(map); - return entries; + return types; } - - public static void moveAllClassesOutOfDefaultPackage(CtClass c, String newPackageName) { - Map map = Maps.newHashMap(); - for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) { - if (classEntry.isInDefaultPackage()) { - map.put(classEntry, new ClassEntry(newPackageName + "/" + classEntry.getName())); - } + + public static void renameTypes(CtClass c, Map map) { + + // convert the type map to a javassist class map + ClassMap nameMap = new ClassMap(); + for (Map.Entry entry : map.entrySet()) { + String source = entry.getKey().toString(); + String dest = entry.getValue().toString(); + + // don't forget to chop off the L ... ; + // javassist doesn't want it there + source = source.substring(1, source.length() - 1); + dest = dest.substring(1, dest.length() - 1); + + nameMap.put(source, dest); } - ClassRenamer.renameClasses(c, map); - } - - public static void moveAllClassesIntoDefaultPackage(CtClass c, String oldPackageName) { - Map map = Maps.newHashMap(); - for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) { - if (classEntry.getPackageName().equals(oldPackageName)) { - map.put(classEntry, new ClassEntry(classEntry.getSimpleName())); + + // replace!! + c.replaceClassName(nameMap); + + // replace simple names in the InnerClasses attribute too + ConstPool constants = c.getClassFile().getConstPool(); + InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i = 0; i < attr.tableLength(); i++) { + + // get the inner class full name (which has already been translated) + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i))); + + if (attr.innerNameIndex(i) != 0) { + // update the inner name + attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnermostClassName())); + } + + /* DEBUG + System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); + */ } } - ClassRenamer.renameClasses(c, map); } } diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java index 94ab2c4..7952577 100644 --- a/src/cuchaz/enigma/bytecode/ClassTranslator.java +++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java @@ -10,8 +10,6 @@ ******************************************************************************/ package cuchaz.enigma.bytecode; -import java.util.Map; - import javassist.CtBehavior; import javassist.CtClass; import javassist.CtField; @@ -19,9 +17,6 @@ import javassist.CtMethod; import javassist.bytecode.ConstPool; import javassist.bytecode.Descriptor; import javassist.bytecode.SourceFileAttribute; - -import com.google.common.collect.Maps; - import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.EntryFactory; @@ -50,20 +45,15 @@ public class ClassTranslator { case ConstPool.CONST_Fieldref: { - // translate the name - FieldEntry entry = new FieldEntry( - new ClassEntry(Descriptor.toJvmName(constants.getFieldrefClassName(i))), + // translate the name and type + FieldEntry entry = EntryFactory.getFieldEntry( + Descriptor.toJvmName(constants.getFieldrefClassName(i)), constants.getFieldrefName(i), - new Type(constants.getFieldrefType(i)) + constants.getFieldrefType(i) ); FieldEntry translatedEntry = m_translator.translateEntry(entry); - - // translate the type - Type type = new Type(constants.getFieldrefType(i)); - Type translatedType = m_translator.translateType(type); - - if (!entry.equals(translatedEntry) || !type.equals(translatedType)) { - editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedType.toString()); + if (!entry.equals(translatedEntry)) { + editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString()); } } break; @@ -71,15 +61,14 @@ public class ClassTranslator { case ConstPool.CONST_Methodref: case ConstPool.CONST_InterfaceMethodref: { - // translate the name and type + // translate the name and type (ie signature) BehaviorEntry entry = EntryFactory.getBehaviorEntry( Descriptor.toJvmName(editor.getMemberrefClassname(i)), editor.getMemberrefName(i), editor.getMemberrefType(i) ); BehaviorEntry translatedEntry = m_translator.translateEntry(entry); - - if (!entry.getName().equals(translatedEntry.getName()) || !entry.getSignature().equals(translatedEntry.getSignature())) { + if (!entry.equals(translatedEntry)) { editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString()); } } @@ -120,25 +109,18 @@ public class ClassTranslator { } if (entry.getSignature() != null) { - // translate the type + // translate the signature Signature translatedSignature = m_translator.translateSignature(entry.getSignature()); behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); } } // translate all the class names referenced in the code - // the above code only changed method/field/reference names and types, but not the class names themselves - Map map = Maps.newHashMap(); - for (ClassEntry obfClassEntry : ClassRenamer.getAllClassEntries(c)) { - ClassEntry deobfClassEntry = m_translator.translateEntry(obfClassEntry); - if (!obfClassEntry.equals(deobfClassEntry)) { - map.put(obfClassEntry, deobfClassEntry); - } - } - ClassRenamer.renameClasses(c, map); + // the above code only changed method/field/reference names and types, but not the rest of the class references + ClassRenamer.renameClasses(c, m_translator); // translate the source file attribute too - ClassEntry deobfClassEntry = map.get(classEntry); + ClassEntry deobfClassEntry = m_translator.translateEntry(classEntry); if (deobfClassEntry != null) { String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassName()) + ".java"; c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java index 7bc6183..4898e6d 100644 --- a/src/cuchaz/enigma/mapping/EntryFactory.java +++ b/src/cuchaz/enigma/mapping/EntryFactory.java @@ -11,6 +11,7 @@ import javassist.expr.FieldAccess; import javassist.expr.MethodCall; import javassist.expr.NewExpr; +import com.strobel.assembler.metadata.FieldDefinition; import com.strobel.assembler.metadata.MethodDefinition; import cuchaz.enigma.analysis.JarIndex; @@ -54,6 +55,18 @@ public class EntryFactory { ); } + public static FieldEntry getFieldEntry(FieldDefinition def) { + return new FieldEntry( + new ClassEntry(def.getDeclaringType().getInternalName()), + def.getName(), + new Type(def.getErasedSignature()) + ); + } + + public static FieldEntry getFieldEntry(String className, String name, String type) { + return new FieldEntry(new ClassEntry(className), name, new Type(type)); + } + public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { return new FieldEntry( getObfClassEntry(classMapping), @@ -82,7 +95,7 @@ public class EntryFactory { return new MethodEntry( new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), - new Signature(def.getSignature()) + new Signature(def.getErasedSignature()) ); } @@ -121,7 +134,7 @@ public class EntryFactory { } else { return new ConstructorEntry( new ClassEntry(def.getDeclaringType().getInternalName()), - new Signature(def.getSignature()) + new Signature(def.getErasedSignature()) ); } } diff --git a/src/cuchaz/enigma/mapping/MappingsChecker.java b/src/cuchaz/enigma/mapping/MappingsChecker.java index c5ff7a7..57ea90c 100644 --- a/src/cuchaz/enigma/mapping/MappingsChecker.java +++ b/src/cuchaz/enigma/mapping/MappingsChecker.java @@ -66,7 +66,7 @@ public class MappingsChecker { // check the fields for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { - FieldEntry obfFieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName(), fieldMapping.getObfType()); + FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping); if (!m_index.containsObfField(obfFieldEntry)) { classMapping.removeFieldMapping(fieldMapping); m_droppedFieldMappings.put(obfFieldEntry, fieldMapping); diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java index d7766dc..ad6c878 100644 --- a/src/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java @@ -66,7 +66,15 @@ public class MappingsRenamer { } public void markClassAsDeobfuscated(ClassEntry obf) { - setClassName(obf, obf.isInnerClass() ? obf.getInnermostClassName() : obf.getSimpleName()); + String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName(); + List mappingChain = getOrCreateClassMappingChain(obf); + if (mappingChain.size() == 1) { + ClassMapping classMapping = mappingChain.get(0); + m_mappings.setClassDeobfName(classMapping, deobfName); + } else { + ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2); + outerClassMapping.setInnerClassName(obf, deobfName); + } } public void setFieldName(FieldEntry obf, String deobfName) { diff --git a/src/cuchaz/enigma/mapping/ParameterizedType.java b/src/cuchaz/enigma/mapping/ParameterizedType.java new file mode 100644 index 0000000..af24ef4 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ParameterizedType.java @@ -0,0 +1,54 @@ +package cuchaz.enigma.mapping; + +import cuchaz.enigma.Util; + + + +public class ParameterizedType extends Type { + + private static final long serialVersionUID = 1758975507937309011L; + + public ParameterizedType(Type other) { + super(other); + for (int i=0; i;"); + return buf.toString(); + } else { + return m_name; + } + } + + @Override + public boolean equals(Object other) { + if (other instanceof ParameterizedType) { + return equals((ParameterizedType)other); + } + return false; + } + + public boolean equals(ParameterizedType other) { + return m_name.equals(other.m_name) && m_parameters.equals(other.m_parameters); + } + + public int hashCode() { + return Util.combineHashesOrdered(m_name.hashCode(), m_parameters.hashCode()); + } + +} diff --git a/src/cuchaz/enigma/mapping/Signature.java b/src/cuchaz/enigma/mapping/Signature.java index ea83e40..f4850ac 100644 --- a/src/cuchaz/enigma/mapping/Signature.java +++ b/src/cuchaz/enigma/mapping/Signature.java @@ -79,16 +79,6 @@ public class Signature implements Serializable { return types; } - public Iterable classes() { - List out = Lists.newArrayList(); - for (Type type : types()) { - if (type.isClass()) { - out.add(type.getClassEntry()); - } - } - return out; - } - @Override public boolean equals(Object other) { if (other instanceof Signature) { diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java index 72118b0..d530083 100644 --- a/src/cuchaz/enigma/mapping/Type.java +++ b/src/cuchaz/enigma/mapping/Type.java @@ -1,8 +1,10 @@ package cuchaz.enigma.mapping; import java.io.Serializable; +import java.util.List; import java.util.Map; +import com.beust.jcommander.internal.Lists; import com.google.common.collect.Maps; public class Type implements Serializable { @@ -84,33 +86,66 @@ public class Type implements Serializable { throw new IllegalArgumentException("don't know how to parse: " + in); } - private String m_name; + protected String m_name; + protected List m_parameters; public Type(String name) { - m_name = name; + m_name = null; + m_parameters = Lists.newArrayList(); + + int start = name.indexOf('<'); + int stop = name.lastIndexOf('>'); + if (start > 0 && stop > start) { + + // deal with generic parameters + m_name = name.substring(0, start) + name.substring(stop + 1); + + String parameters = name.substring(start + 1, stop); + int i=0; + while (i parameters() { + return m_parameters; + } + @Override public boolean equals(Object other) { if (other instanceof Type) { @@ -214,7 +264,7 @@ public class Type implements Serializable { private static String readClass(String in) { // read all the characters in the buffer until we hit a ';' - // remember to treat parameters correctly + // include the parameters too StringBuilder buf = new StringBuilder(); int depth = 0; for (int i=0; i { + + private static final long serialVersionUID = 317915213205066168L; + + private ClassNameReplacer m_replacer; + + public ReplacerClassMap(ClassNameReplacer replacer) { + m_replacer = replacer; + } + + @Override + public String get(Object obj) { + if (obj instanceof String) { + return get((String)obj); + } else if (obj instanceof ObjectType) { + return get((ObjectType)obj); + } + return null; + } + + public String get(String typeName) { + + // javassist doesn't give us the class framing, add it + typeName = "L" + typeName + ";"; + + String out = getFramed(typeName); + if (out == null) { + return null; + } + + // javassist doesn't want the class framing, so remove it + out = out.substring(1, out.length() - 1); + + return out; + } + + public String getFramed(String typeName) { + ParameterizedType type = new ParameterizedType(new Type(typeName)); + ParameterizedType renamedType = new ParameterizedType(type, m_replacer); + if (!type.equals(renamedType)) { + return renamedType.toString(); + } + return null; + } + + public String get(ObjectType type) { + + // we can deal with the ones that start with a class + String signature = type.encode(); + if (signature.startsWith("L") || signature.startsWith("[")) { + + // TEMP: skip special characters for now + if (signature.indexOf('*') >= 0 || signature.indexOf('+') >= 0 || signature.indexOf('-') >= 0) { + System.out.println("Skipping translating: " + signature); + return null; + } + + // replace inner class / with $ + int pos = signature.indexOf("$"); + if (pos >= 0) { + signature = signature.substring(0, pos + 1) + signature.substring(pos, signature.length()).replace('/', '$'); + } + + return getFramed(signature); + } else if (signature.startsWith("T")) { + // don't need to care about template names + return null; + } else { + // TEMP + System.out.println("Skipping translating: " + signature); + return null; + } + } + } + public static void renameClasses(CtClass c, final Translator translator) { renameClasses(c, new ClassNameReplacer() { @Override @@ -69,86 +180,42 @@ public class ClassRenamer { }); } + @SuppressWarnings("unchecked") public static void renameClasses(CtClass c, ClassNameReplacer replacer) { - Map map = Maps.newHashMap(); - for (ParameterizedType type : ClassRenamer.getAllClassTypes(c)) { - ParameterizedType renamedType = new ParameterizedType(type, replacer); - if (!type.equals(renamedType)) { - map.put(type, renamedType); - } - } - renameTypes(c, map); - } - - public static Set getAllClassTypes(final CtClass c) { - // TODO: might have to scan SignatureAttributes directly because javassist is buggy + // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =( - // get the class types that javassist knows about - final Set types = Sets.newHashSet(); - ClassMap map = new ClassMap() { - @Override - public Object get(Object obj) { - if (obj instanceof String) { - String str = (String)obj; - - // sometimes javasist gives us dot-separated classes... whadda hell? - str = str.replace('.', '/'); - - // skip weird types - boolean hasNestedParams = str.indexOf('<') >= 0 && str.indexOf('<', str.indexOf('<')+1) >= 0; - boolean hasWeirdChars = str.indexOf('*') >= 0 || str.indexOf('-') >= 0 || str.indexOf('+') >= 0; - if (hasNestedParams || hasWeirdChars) { - // TEMP - System.out.println("Skipped translating: " + str); - return null; - } - - ParameterizedType type = new ParameterizedType(new Type("L" + str + ";")); - assert(type.isClass()); - // TEMP - try { - type.getClassEntry(); - } catch (Throwable t) { - // bad type - // TEMP - System.out.println("Skipped translating: " + str); - return null; - } - - types.add(type); - } - return null; - } - - private static final long serialVersionUID = -202160293602070641L; - }; - c.replaceClassName(map); + ReplacerClassMap map = new ReplacerClassMap(replacer); + ClassFile classFile = c.getClassFile(); - return types; - } - - public static void renameTypes(CtClass c, Map map) { + // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo) + ConstPool constPool = c.getClassFile().getConstPool(); + constPool.renameClass(map); - // convert the type map to a javassist class map - ClassMap nameMap = new ClassMap(); - for (Map.Entry entry : map.entrySet()) { - String source = entry.getKey().toString(); - String dest = entry.getValue().toString(); - - // don't forget to chop off the L ... ; - // javassist doesn't want it there - source = source.substring(1, source.length() - 1); - dest = dest.substring(1, dest.length() - 1); - - nameMap.put(source, dest); + // rename class attributes + renameAttributes(classFile.getAttributes(), map, SignatureType.Class); + + // rename methods + for (MethodInfo methodInfo : (List)classFile.getMethods()) { + methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map)); + renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method); + } + + // rename fields + for (FieldInfo fieldInfo : (List)classFile.getFields()) { + fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map)); + renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field); + } + + // rename the class name itself last + // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass() + // we only want to replace exactly this class name + String newName = replacer.replace(Descriptor.toJvmName(c.getName())); + if (newName != null) { + c.setName(Descriptor.toJavaName(newName)); } - // replace!! - c.replaceClassName(nameMap); - // replace simple names in the InnerClasses attribute too - ConstPool constants = c.getClassFile().getConstPool(); InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); if (attr != null) { for (int i = 0; i < attr.tableLength(); i++) { @@ -158,7 +225,7 @@ public class ClassRenamer { if (attr.innerNameIndex(i) != 0) { // update the inner name - attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnermostClassName())); + attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName())); } /* DEBUG @@ -167,4 +234,88 @@ public class ClassRenamer { } } } + + @SuppressWarnings("unchecked") + private static void renameAttributes(List attributes, ReplacerClassMap map, SignatureType type) { + try { + + // make the rename class method accessible + Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class); + renameClassMethod.setAccessible(true); + + for (AttributeInfo attribute : attributes) { + if (attribute instanceof SignatureAttribute) { + // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell + SignatureAttribute signatureAttribute = (SignatureAttribute)attribute; + type.rename(signatureAttribute, map); + } else if (attribute instanceof CodeAttribute) { + // code attributes have signature attributes too (indirectly) + CodeAttribute codeAttribute = (CodeAttribute)attribute; + renameAttributes(codeAttribute.getAttributes(), map, type); + } else if (attribute instanceof LocalVariableTypeAttribute) { + // lvt attributes have signature attributes too + LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute)attribute; + renameLocalVariableTypeAttribute(localVariableAttribute, map); + } else { + renameClassMethod.invoke(attribute, map); + } + } + + } catch(NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw new Error("Unable to call javassist methods by reflection!", ex); + } + } + + private static void renameClassSignatureAttribute(SignatureAttribute attribute, ReplacerClassMap map) { + try { + ClassSignature classSignature = SignatureAttribute.toClassSignature(attribute.getSignature()); + // TODO: do class signatures + } catch (BadBytecode ex) { + throw new Error("Unable to parse class signature: " + attribute.getSignature(), ex); + } + } + + private static void renameFieldSignatureAttribute(SignatureAttribute attribute, ReplacerClassMap map) { + try { + ObjectType fieldSignature = SignatureAttribute.toFieldSignature(attribute.getSignature()); + String newSignature = map.get(fieldSignature); + if (newSignature != null) { + attribute.setSignature(newSignature); + } + } catch (BadBytecode ex) { + throw new Error("Unable to parse field signature: " + attribute.getSignature(), ex); + } + } + + private static void renameMethodSignatureAttribute(SignatureAttribute attribute, ReplacerClassMap map) { + try { + MethodSignature methodSignature = SignatureAttribute.toMethodSignature(attribute.getSignature()); + // TODO: do method signatures + } catch (BadBytecode ex) { + throw new Error("Unable to parse method signature: " + attribute.getSignature(), ex); + } + } + + private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) { + // adapted from LocalVariableAttribute.renameClass() + ConstPool cp = attribute.getConstPool(); + int n = attribute.tableLength(); + byte[] info = attribute.get(); + for (int i = 0; i < n; ++i) { + int pos = i * 10 + 2; + int index = ByteArray.readU16bit(info, pos + 6); + if (index != 0) { + String desc = cp.getUtf8Info(index); + try { + ObjectType fieldSignature = SignatureAttribute.toFieldSignature(desc); + String newDesc = map.get(fieldSignature); + if (newDesc != null) { + ByteArray.write16bit(cp.addUtf8Info(newDesc), info, pos + 6); + } + } catch (BadBytecode ex) { + throw new Error("Unable to parse field signature: " + desc, ex); + } + } + } + } } -- cgit v1.2.3 From ecfda21f3db9e62e3acf074e9842e92ace4cb3ab Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 17 Mar 2015 20:01:55 -0400 Subject: parsing generic signatures is tricky. don't write custom code to do it. switching to library instead --- src/cuchaz/enigma/bytecode/ClassRenamer.java | 9 ++-- src/cuchaz/enigma/mapping/ParameterizedType.java | 54 --------------------- src/cuchaz/enigma/mapping/Type.java | 61 +++--------------------- 3 files changed, 12 insertions(+), 112 deletions(-) delete mode 100644 src/cuchaz/enigma/mapping/ParameterizedType.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java index d88daa5..d7acd30 100644 --- a/src/cuchaz/enigma/bytecode/ClassRenamer.java +++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java @@ -34,7 +34,6 @@ import javassist.bytecode.SignatureAttribute.MethodSignature; import javassist.bytecode.SignatureAttribute.ObjectType; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.ParameterizedType; import cuchaz.enigma.mapping.Translator; import cuchaz.enigma.mapping.Type; @@ -103,8 +102,8 @@ public class ClassRenamer { } public String getFramed(String typeName) { - ParameterizedType type = new ParameterizedType(new Type(typeName)); - ParameterizedType renamedType = new ParameterizedType(type, m_replacer); + Type type = new Type(typeName); + Type renamedType = new Type(type, m_replacer); if (!type.equals(renamedType)) { return renamedType.toString(); } @@ -115,6 +114,7 @@ public class ClassRenamer { // we can deal with the ones that start with a class String signature = type.encode(); + /* if (signature.startsWith("L") || signature.startsWith("[")) { // TEMP: skip special characters for now @@ -134,10 +134,11 @@ public class ClassRenamer { // don't need to care about template names return null; } else { + */ // TEMP System.out.println("Skipping translating: " + signature); return null; - } + //} } } diff --git a/src/cuchaz/enigma/mapping/ParameterizedType.java b/src/cuchaz/enigma/mapping/ParameterizedType.java deleted file mode 100644 index af24ef4..0000000 --- a/src/cuchaz/enigma/mapping/ParameterizedType.java +++ /dev/null @@ -1,54 +0,0 @@ -package cuchaz.enigma.mapping; - -import cuchaz.enigma.Util; - - - -public class ParameterizedType extends Type { - - private static final long serialVersionUID = 1758975507937309011L; - - public ParameterizedType(Type other) { - super(other); - for (int i=0; i;"); - return buf.toString(); - } else { - return m_name; - } - } - - @Override - public boolean equals(Object other) { - if (other instanceof ParameterizedType) { - return equals((ParameterizedType)other); - } - return false; - } - - public boolean equals(ParameterizedType other) { - return m_name.equals(other.m_name) && m_parameters.equals(other.m_parameters); - } - - public int hashCode() { - return Util.combineHashesOrdered(m_name.hashCode(), m_parameters.hashCode()); - } - -} diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java index d530083..3f441e2 100644 --- a/src/cuchaz/enigma/mapping/Type.java +++ b/src/cuchaz/enigma/mapping/Type.java @@ -1,10 +1,8 @@ package cuchaz.enigma.mapping; import java.io.Serializable; -import java.util.List; import java.util.Map; -import com.beust.jcommander.internal.Lists; import com.google.common.collect.Maps; public class Type implements Serializable { @@ -85,43 +83,22 @@ public class Type implements Serializable { throw new IllegalArgumentException("don't know how to parse: " + in); } - + protected String m_name; - protected List m_parameters; public Type(String name) { - m_name = null; - m_parameters = Lists.newArrayList(); - int start = name.indexOf('<'); - int stop = name.lastIndexOf('>'); - if (start > 0 && stop > start) { - - // deal with generic parameters - m_name = name.substring(0, start) + name.substring(stop + 1); - - String parameters = name.substring(start + 1, stop); - int i=0; - while (i= 0 || name.indexOf('>') >= 0) { + throw new IllegalArgumentException("don't use with generic types or templates: " + name); } + + m_name = name; } public Type(Type other) { m_name = other.m_name; - m_parameters = Lists.newArrayList(); - for (Type parameter : other.m_parameters) { - m_parameters.add(new Type(parameter)); - } } public Type(ClassEntry classEntry) { @@ -141,11 +118,6 @@ public class Type implements Serializable { m_name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";"; } } - - m_parameters = Lists.newArrayList(); - for (Type parameter : other.m_parameters) { - m_parameters.add(new Type(parameter, replacer)); - } } @Override @@ -191,17 +163,6 @@ public class Type implements Serializable { } } - public boolean isTemplate() { - return m_name.charAt(0) == 'T' && m_name.charAt(m_name.length() - 1) == ';'; - } - - public String getTemplate() { - if (!isTemplate()) { - throw new IllegalStateException("not an template"); - } - return m_name.substring(1, m_name.length() - 1); - } - public boolean isArray() { return m_name.charAt(0) == '['; } @@ -232,14 +193,6 @@ public class Type implements Serializable { return isClass() || (isArray() && getArrayType().hasClass()); } - public boolean hasParameters() { - return !m_parameters.isEmpty(); - } - - public Iterable parameters() { - return m_parameters; - } - @Override public boolean equals(Object other) { if (other instanceof Type) { -- cgit v1.2.3 From 0c15b6485cfeff42a438e8387d209055ad7d7704 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 18 Mar 2015 00:06:33 -0400 Subject: added full rename support for classes buried in generic signatures oi, what a pain in the ass... --- src/cuchaz/enigma/bytecode/ClassRenamer.java | 378 ++++++++++++++++++++------- 1 file changed, 280 insertions(+), 98 deletions(-) (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java index d7acd30..8d25e72 100644 --- a/src/cuchaz/enigma/bytecode/ClassRenamer.java +++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java @@ -12,6 +12,7 @@ package cuchaz.enigma.bytecode; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,13 +30,19 @@ import javassist.bytecode.InnerClassesAttribute; import javassist.bytecode.LocalVariableTypeAttribute; import javassist.bytecode.MethodInfo; import javassist.bytecode.SignatureAttribute; +import javassist.bytecode.SignatureAttribute.ArrayType; +import javassist.bytecode.SignatureAttribute.BaseType; import javassist.bytecode.SignatureAttribute.ClassSignature; +import javassist.bytecode.SignatureAttribute.ClassType; import javassist.bytecode.SignatureAttribute.MethodSignature; +import javassist.bytecode.SignatureAttribute.NestedClassType; import javassist.bytecode.SignatureAttribute.ObjectType; +import javassist.bytecode.SignatureAttribute.Type; +import javassist.bytecode.SignatureAttribute.TypeArgument; +import javassist.bytecode.SignatureAttribute.TypeVariable; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassNameReplacer; import cuchaz.enigma.mapping.Translator; -import cuchaz.enigma.mapping.Type; public class ClassRenamer { @@ -43,26 +50,26 @@ public class ClassRenamer { Class { @Override - public void rename(SignatureAttribute attribute, ReplacerClassMap map) { - renameClassSignatureAttribute(attribute, map); + public String rename(String signature, ReplacerClassMap map) { + return renameClassSignature(signature, map); } }, Field { @Override - public void rename(SignatureAttribute attribute, ReplacerClassMap map) { - renameFieldSignatureAttribute(attribute, map); + public String rename(String signature, ReplacerClassMap map) { + return renameFieldSignature(signature, map); } }, Method { @Override - public void rename(SignatureAttribute attribute, ReplacerClassMap map) { - renameMethodSignatureAttribute(attribute, map); + public String rename(String signature, ReplacerClassMap map) { + return renameMethodSignature(signature, map); } }; - public abstract void rename(SignatureAttribute attribute, ReplacerClassMap map); + public abstract String rename(String signature, ReplacerClassMap map); } private static class ReplacerClassMap extends HashMap { @@ -79,66 +86,12 @@ public class ClassRenamer { public String get(Object obj) { if (obj instanceof String) { return get((String)obj); - } else if (obj instanceof ObjectType) { - return get((ObjectType)obj); } return null; } - public String get(String typeName) { - - // javassist doesn't give us the class framing, add it - typeName = "L" + typeName + ";"; - - String out = getFramed(typeName); - if (out == null) { - return null; - } - - // javassist doesn't want the class framing, so remove it - out = out.substring(1, out.length() - 1); - - return out; - } - - public String getFramed(String typeName) { - Type type = new Type(typeName); - Type renamedType = new Type(type, m_replacer); - if (!type.equals(renamedType)) { - return renamedType.toString(); - } - return null; - } - - public String get(ObjectType type) { - - // we can deal with the ones that start with a class - String signature = type.encode(); - /* - if (signature.startsWith("L") || signature.startsWith("[")) { - - // TEMP: skip special characters for now - if (signature.indexOf('*') >= 0 || signature.indexOf('+') >= 0 || signature.indexOf('-') >= 0) { - System.out.println("Skipping translating: " + signature); - return null; - } - - // replace inner class / with $ - int pos = signature.indexOf("$"); - if (pos >= 0) { - signature = signature.substring(0, pos + 1) + signature.substring(pos, signature.length()).replace('/', '$'); - } - - return getFramed(signature); - } else if (signature.startsWith("T")) { - // don't need to care about template names - return null; - } else { - */ - // TEMP - System.out.println("Skipping translating: " + signature); - return null; - //} + public String get(String className) { + return m_replacer.replace(className); } } @@ -248,7 +201,10 @@ public class ClassRenamer { if (attribute instanceof SignatureAttribute) { // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell SignatureAttribute signatureAttribute = (SignatureAttribute)attribute; - type.rename(signatureAttribute, map); + String newSignature = type.rename(signatureAttribute.getSignature(), map); + if (newSignature != null) { + signatureAttribute.setSignature(newSignature); + } } else if (attribute instanceof CodeAttribute) { // code attributes have signature attributes too (indirectly) CodeAttribute codeAttribute = (CodeAttribute)attribute; @@ -267,56 +223,282 @@ public class ClassRenamer { } } - private static void renameClassSignatureAttribute(SignatureAttribute attribute, ReplacerClassMap map) { + private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) { + + // adapted from LocalVariableAttribute.renameClass() + ConstPool cp = attribute.getConstPool(); + int n = attribute.tableLength(); + byte[] info = attribute.get(); + for (int i = 0; i < n; ++i) { + int pos = i * 10 + 2; + int index = ByteArray.readU16bit(info, pos + 6); + if (index != 0) { + String signature = cp.getUtf8Info(index); + String newSignature = renameLocalVariableSignature(signature, map); + if (newSignature != null) { + ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6); + } + } + } + } + + private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) { + + // for some reason, signatures with . in them don't count as field signatures + // looks like anonymous classes delimit with . in stead of $ + // convert the . to $, but keep track of how many we replace + // we need to put them back after we translate + int start = signature.lastIndexOf('$') + 1; + int numConverted = 0; + StringBuilder buf = new StringBuilder(signature); + for (int i=buf.length()-1; i>=start; i--) { + char c = buf.charAt(i); + if (c == '.') { + buf.setCharAt(i, '$'); + numConverted++; + } + } + signature = buf.toString(); + + // translate + String newSignature = renameFieldSignature(signature, map); + if (newSignature != null) { + + // put the delimiters back + buf = new StringBuilder(newSignature); + for (int i=buf.length()-1; i>=0 && numConverted > 0; i--) { + char c = buf.charAt(i); + if (c == '$') { + buf.setCharAt(i, '.'); + numConverted--; + } + } + assert(numConverted == 0); + newSignature = buf.toString(); + + return newSignature; + } + + return null; + } + + private static String renameClassSignature(String signature, ReplacerClassMap map) { try { - ClassSignature classSignature = SignatureAttribute.toClassSignature(attribute.getSignature()); - // TODO: do class signatures + return getSignature(renameType(SignatureAttribute.toClassSignature(signature), map)); } catch (BadBytecode ex) { - throw new Error("Unable to parse class signature: " + attribute.getSignature(), ex); + throw new Error("Can't parse field signature: " + signature); } } - private static void renameFieldSignatureAttribute(SignatureAttribute attribute, ReplacerClassMap map) { + private static String renameFieldSignature(String signature, ReplacerClassMap map) { try { - ObjectType fieldSignature = SignatureAttribute.toFieldSignature(attribute.getSignature()); - String newSignature = map.get(fieldSignature); - if (newSignature != null) { - attribute.setSignature(newSignature); - } + return getSignature(renameType(SignatureAttribute.toFieldSignature(signature), map)); } catch (BadBytecode ex) { - throw new Error("Unable to parse field signature: " + attribute.getSignature(), ex); + throw new Error("Can't parse class signature: " + signature); } } - private static void renameMethodSignatureAttribute(SignatureAttribute attribute, ReplacerClassMap map) { + private static String renameMethodSignature(String signature, ReplacerClassMap map) { try { - MethodSignature methodSignature = SignatureAttribute.toMethodSignature(attribute.getSignature()); - // TODO: do method signatures + return getSignature(renameType(SignatureAttribute.toMethodSignature(signature), map)); } catch (BadBytecode ex) { - throw new Error("Unable to parse method signature: " + attribute.getSignature(), ex); + throw new Error("Can't parse method signature: " + signature); } } - private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) { - // adapted from LocalVariableAttribute.renameClass() - ConstPool cp = attribute.getConstPool(); - int n = attribute.tableLength(); - byte[] info = attribute.get(); - for (int i = 0; i < n; ++i) { - int pos = i * 10 + 2; - int index = ByteArray.readU16bit(info, pos + 6); - if (index != 0) { - String desc = cp.getUtf8Info(index); - try { - ObjectType fieldSignature = SignatureAttribute.toFieldSignature(desc); - String newDesc = map.get(fieldSignature); - if (newDesc != null) { - ByteArray.write16bit(cp.addUtf8Info(newDesc), info, pos + 6); - } - } catch (BadBytecode ex) { - throw new Error("Unable to parse field signature: " + desc, ex); + private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) { + + // NOTE: don't have to translate type parameters + + // translate superclass + ClassType superclassType = type.getSuperClass(); + if (superclassType != ClassType.OBJECT) { + ClassType newSuperclassType = renameType(superclassType, map); + if (newSuperclassType != null) { + superclassType = newSuperclassType; + } + } + + // translate interfaces + ClassType[] interfaceTypes = type.getInterfaces(); + if (interfaceTypes != null) { + interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); + for (int i=0; i