From ba7a354efae7d49833c887cf147ac940c975a1fa Mon Sep 17 00:00:00 2001 From: Gegy Date: Wed, 30 Jan 2019 21:05:32 +0200 Subject: Remap sources (#106) * Source remapping beginnings * Fix navigation to remapped classes * Translate identifier info reference * Remap local variables with default names in source * Caching translator * Fix lack of highlighting for first opened class * Fix unicode variable names * Unicode checker shouldn't be checking just alphanumeric * Fix package tree being built from obf names * Don't index `this` as method call for method::reference * Apply proposed names * Fix source export issues * Replace unicode var names at bytecode level uniquely * Drop imports from editor source * Class selector fixes * Delta keep track of base mappings to enable lookup of old names * Optimize source remapping by remapping source with a StringBuffer instead of copying * Bump version --- src/main/java/cuchaz/enigma/gui/ClassSelector.java | 182 +++++++----- src/main/java/cuchaz/enigma/gui/CodeReader.java | 94 +------ .../cuchaz/enigma/gui/DecompiledClassSource.java | 129 +++++++++ src/main/java/cuchaz/enigma/gui/Gui.java | 102 ++++--- src/main/java/cuchaz/enigma/gui/GuiController.java | 305 +++++++++------------ .../java/cuchaz/enigma/gui/SourceRemapper.java | 64 +++++ .../java/cuchaz/enigma/gui/elements/MenuBar.java | 8 +- .../enigma/gui/highlight/BoxHighlightPainter.java | 4 +- .../enigma/gui/highlight/TokenHighlightType.java | 7 + .../enigma/gui/node/ClassSelectorClassNode.java | 8 +- 10 files changed, 503 insertions(+), 400 deletions(-) create mode 100644 src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java create mode 100644 src/main/java/cuchaz/enigma/gui/SourceRemapper.java create mode 100644 src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java (limited to 'src/main/java/cuchaz/enigma/gui') diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java index c3b7288..39d0333 100644 --- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java @@ -17,9 +17,11 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import cuchaz.enigma.gui.node.ClassSelectorClassNode; import cuchaz.enigma.gui.node.ClassSelectorPackageNode; -import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import javax.annotation.Nullable; import javax.swing.*; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; @@ -27,21 +29,26 @@ import javax.swing.tree.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.*; -import java.util.List; public class ClassSelector extends JTree { public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName); + + private final GuiController controller; + private DefaultMutableTreeNode rootNodes; private ClassSelectionListener selectionListener; private RenameSelectionListener renameSelectionListener; private Comparator comparator; + private final Map displayedObfToDeobf = new HashMap<>(); + public ClassSelector(Gui gui, Comparator comparator, boolean isRenamable) { this.comparator = comparator; + this.controller = gui.getController(); // configure the tree control - setEditable(gui != null); + setEditable(true); setRootVisible(false); setShowsRootHandles(false); setModel(null); @@ -55,66 +62,64 @@ public class ClassSelector extends JTree { TreePath path = getSelectionPath(); if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); - selectionListener.onSelectClass(node.getClassEntry()); + selectionListener.onSelectClass(node.getObfEntry()); } } } }); - if (gui != null) { - final JTree tree = this; + final JTree tree = this; - final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, + final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, (DefaultTreeCellRenderer) tree.getCellRenderer()) { - @Override - public boolean isCellEditable(EventObject event) { - return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); - } - }; - this.setCellEditor(editor); - editor.addCellEditorListener(new CellEditorListener() { - @Override - public void editingStopped(ChangeEvent e) { - String data = editor.getCellEditorValue().toString(); - TreePath path = getSelectionPath(); - - Object realPath = path.getLastPathComponent(); - if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; - TreeNode parentNode = node.getParent(); - if (parentNode == null) - return; - boolean allowEdit = true; - for (int i = 0; i < parentNode.getChildCount(); i++) { - TreeNode childNode = parentNode.getChildAt(i); - if (childNode != null && childNode.toString().equals(data) && childNode != node) { - allowEdit = false; - break; - } + @Override + public boolean isCellEditable(EventObject event) { + return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); + } + }; + this.setCellEditor(editor); + editor.addCellEditorListener(new CellEditorListener() { + @Override + public void editingStopped(ChangeEvent e) { + String data = editor.getCellEditorValue().toString(); + TreePath path = getSelectionPath(); + + Object realPath = path.getLastPathComponent(); + if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; + TreeNode parentNode = node.getParent(); + if (parentNode == null) + return; + boolean allowEdit = true; + for (int i = 0; i < parentNode.getChildCount(); i++) { + TreeNode childNode = parentNode.getChildAt(i); + if (childNode != null && childNode.toString().equals(data) && childNode != node) { + allowEdit = false; + break; } - if (allowEdit && renameSelectionListener != null) { - Object prevData = node.getUserObject(); - Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data; - try { - renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); - node.setUserObject(objectData); // Make sure that it's modified - } catch (IllegalNameException ex) { - JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, - JOptionPane.ERROR_MESSAGE, null, new String[] { "Ok" }, "OK"); - editor.cancelCellEditing(); - } - } else - editor.cancelCellEditing(); } - + if (allowEdit && renameSelectionListener != null) { + Object prevData = node.getUserObject(); + Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data; + try { + renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); + node.setUserObject(objectData); // Make sure that it's modified + } catch (IllegalNameException ex) { + JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, + JOptionPane.ERROR_MESSAGE, null, new String[]{"Ok"}, "OK"); + editor.cancelCellEditing(); + } + } else + editor.cancelCellEditing(); } - @Override - public void editingCanceled(ChangeEvent e) { - // NOP - } - }); - } + } + + @Override + public void editingCanceled(ChangeEvent e) { + // NOP + } + }); // init defaults this.selectionListener = null; this.renameSelectionListener = null; @@ -142,16 +147,21 @@ public class ClassSelector extends JTree { } public void setClasses(Collection classEntries) { + displayedObfToDeobf.clear(); + List state = getExpansionState(this); if (classEntries == null) { setModel(null); return; } + Translator translator = controller.getDeobfuscator().getMapper().getDeobfuscator(); + // build the package names Map packages = Maps.newHashMap(); - for (ClassEntry classEntry : classEntries) { - packages.put(classEntry.getPackageName(), null); + for (ClassEntry obfClass : classEntries) { + ClassEntry deobfClass = translator.translate(obfClass); + packages.put(deobfClass.getPackageName(), null); } // sort the packages @@ -191,20 +201,24 @@ public class ClassSelector extends JTree { // put the classes into packages Multimap packagedClassEntries = ArrayListMultimap.create(); - for (ClassEntry classEntry : classEntries) { - packagedClassEntries.put(classEntry.getPackageName(), classEntry); + for (ClassEntry obfClass : classEntries) { + ClassEntry deobfClass = translator.translate(obfClass); + packagedClassEntries.put(deobfClass.getPackageName(), obfClass); } // build the class nodes for (String packageName : packagedClassEntries.keySet()) { // sort the class entries List classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); - classEntriesInPackage.sort(this.comparator); + classEntriesInPackage.sort((o1, o2) -> comparator.compare(translator.translate(o1), translator.translate(o2))); // create the nodes in order - for (ClassEntry classEntry : classEntriesInPackage) { + for (ClassEntry obfClass : classEntriesInPackage) { + ClassEntry deobfClass = translator.translate(obfClass); ClassSelectorPackageNode node = packages.get(packageName); - node.add(new ClassSelectorClassNode(classEntry)); + ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfClass, deobfClass); + displayedObfToDeobf.put(obfClass, deobfClass); + node.add(classNode); } } @@ -324,7 +338,7 @@ public class ClassSelector extends JTree { } for (ClassSelectorPackageNode packageNode : packageNodes()) { if (packageNode.getPackageName().equals(packageName)) { - expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode })); + expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); return; } } @@ -332,14 +346,13 @@ public class ClassSelector extends JTree { public void expandAll() { for (ClassSelectorPackageNode packageNode : packageNodes()) { - expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode })); + expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); } } public ClassEntry getFirstClass() { ClassSelectorPackageNode packageNode = packageNodes().get(0); - if (packageNode != null) - { + if (packageNode != null) { ClassSelectorClassNode classNode = classNodes(packageNode).get(0); if (classNode != null) { return classNode.getClassEntry(); @@ -350,7 +363,7 @@ public class ClassSelector extends JTree { public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { String packageName = entry.getPackageName(); - if (packageName == null){ + if (packageName == null) { packageName = "(none)"; } for (ClassSelectorPackageNode packageNode : packageNodes()) { @@ -361,6 +374,11 @@ public class ClassSelector extends JTree { return null; } + @Nullable + public ClassEntry getDisplayedDeobf(ClassEntry obfEntry) { + return displayedObfToDeobf.get(obfEntry); + } + public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) { ClassSelectorPackageNode packageNode = getPackageNode(entry); @@ -402,7 +420,7 @@ public class ClassSelector extends JTree { for (ClassSelectorPackageNode packageNode : packageNodes()) { for (ClassSelectorClassNode classNode : classNodes(packageNode)) { if (classNode.getClassEntry().equals(classEntry)) { - setSelectionPath(new TreePath(new Object[] { getModel().getRoot(), packageNode, classNode })); + setSelectionPath(new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode})); } } } @@ -418,6 +436,9 @@ public class ClassSelector extends JTree { DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) { model.removeNodeFromParent(childNode); + if (childNode instanceof ClassSelectorClassNode) { + displayedObfToDeobf.remove(((ClassSelectorClassNode) childNode).getObfEntry()); + } break; } } @@ -428,13 +449,25 @@ public class ClassSelector extends JTree { ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); } - public void moveClassTree(ClassEntry oldClassEntry, ClassEntry newClassEntry, ClassSelector otherSelector) { - if (otherSelector == null) - removeNode(getPackageNode(oldClassEntry), oldClassEntry); - insertNode(getOrCreate(newClassEntry), newClassEntry); + public void moveClassIn(ClassEntry classEntry) { + removeEntry(classEntry); + insertNode(classEntry); } - public ClassSelectorPackageNode getOrCreate(ClassEntry entry) { + public void moveClassOut(ClassEntry classEntry) { + removeEntry(classEntry); + } + + private void removeEntry(ClassEntry classEntry) { + ClassEntry previousDeobf = displayedObfToDeobf.get(classEntry); + if (previousDeobf != null) { + ClassSelectorPackageNode packageNode = getPackageNode(previousDeobf); + removeNode(packageNode, previousDeobf); + removeNodeIfEmpty(packageNode); + } + } + + public ClassSelectorPackageNode getOrCreatePackage(ClassEntry entry) { DefaultTreeModel model = (DefaultTreeModel) getModel(); ClassSelectorPackageNode newPackageNode = getPackageNode(entry); if (newPackageNode == null) { @@ -444,10 +477,15 @@ public class ClassSelector extends JTree { return newPackageNode; } - public void insertNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { + public void insertNode(ClassEntry obfEntry) { + ClassEntry deobfEntry = controller.getDeobfuscator().deobfuscate(obfEntry); + ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry); + DefaultTreeModel model = (DefaultTreeModel) getModel(); - ClassSelectorClassNode classNode = new ClassSelectorClassNode(entry); + ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfEntry, deobfEntry); model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); + + displayedObfToDeobf.put(obfEntry, deobfEntry); } public void reload() { diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java index 0810043..e119640 100644 --- a/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java @@ -11,58 +11,27 @@ package cuchaz.enigma.gui; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import cuchaz.enigma.Deobfuscator; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import de.sciss.syntaxpane.DefaultSyntaxKit; import javax.swing.*; import javax.swing.text.BadLocationException; +import javax.swing.text.Document; import javax.swing.text.Highlighter.HighlightPainter; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class CodeReader extends JEditorPane { - private static final long serialVersionUID = 3673180950485748810L; - private static final Object lock = new Object(); - private SourceIndex sourceIndex; - private SelectionListener selectionListener; - - public CodeReader() { - - setEditable(false); - setContentType("text/java"); - - // turn off token highlighting (it's wrong most of the time anyway...) - DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit(); - kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker"); - - // hook events - addCaretListener(event -> - { - if (selectionListener != null && sourceIndex != null) { - Token token = sourceIndex.getReferenceToken(event.getDot()); - if (token != null) { - selectionListener.onSelect(sourceIndex.getDeobfReference(token)); - } else { - selectionListener.onSelect(null); - } - } - }); - } - // HACKHACK: someday we can update the main GUI to use this code reader public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { // set the caret position to the token - editor.setCaretPosition(token.start); + Document document = editor.getDocument(); + int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength()); + + editor.setCaretPosition(clampedPosition); editor.grabFocus(); try { @@ -101,57 +70,4 @@ public class CodeReader extends JEditorPane { }); timer.start(); } - - public void setSelectionListener(SelectionListener val) { - selectionListener = val; - } - - public void setCode(String code) { - // sadly, the java lexer is not thread safe, so we have to serialize all these calls - synchronized (lock) { - setText(code); - } - } - - public SourceIndex getSourceIndex() { - return sourceIndex; - } - - public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) { - decompileClass(classEntry, deobfuscator, null); - } - - public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) { - decompileClass(classEntry, deobfuscator, null, callback); - } - - public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) { - - if (classEntry == null) { - setCode(null); - return; - } - - setCode("(decompiling...)"); - - // run decompilation in a separate thread to keep ui responsive - new Thread(() -> - { - - // decompile it - - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getName()); - String source = deobfuscator.getSource(sourceTree); - setCode(source); - sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); - - if (callback != null) { - callback.run(); - } - }).start(); - } - - public interface SelectionListener { - void onSelect(EntryReference, Entry> reference); - } } diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java new file mode 100644 index 0000000..03f76c9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java @@ -0,0 +1,129 @@ +package cuchaz.enigma.gui; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.SourceIndex; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.api.EnigmaPlugin; +import cuchaz.enigma.gui.highlight.TokenHighlightType; +import cuchaz.enigma.translation.LocalNameGenerator; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry; + +import javax.annotation.Nullable; +import java.util.*; + +public class DecompiledClassSource { + private final ClassEntry classEntry; + private final Deobfuscator deobfuscator; + + private final SourceIndex obfuscatedIndex; + private SourceIndex remappedIndex; + + private final Map> highlightedTokens = new EnumMap<>(TokenHighlightType.class); + + public DecompiledClassSource(ClassEntry classEntry, Deobfuscator deobfuscator, SourceIndex index) { + this.classEntry = classEntry; + this.deobfuscator = deobfuscator; + this.obfuscatedIndex = index; + this.remappedIndex = index; + } + + public void remapSource(Translator translator) { + highlightedTokens.clear(); + + SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); + + SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(token, movedToken, translator)); + remappedIndex = obfuscatedIndex.remapTo(remapResult); + } + + private String remapToken(Token token, Token movedToken, Translator translator) { + EntryReference, Entry> reference = obfuscatedIndex.getReference(token); + + if (deobfuscator.isRenamable(reference)) { + Entry entry = reference.getNameableEntry(); + Entry translatedEntry = translator.translate(entry); + + if (isDeobfuscated(entry, translatedEntry)) { + highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED); + return translatedEntry.getSourceRemapName(); + } else { + String proposedName = proposeName(entry); + if (proposedName != null) { + highlightToken(movedToken, TokenHighlightType.PROPOSED); + return proposedName; + } + + highlightToken(movedToken, TokenHighlightType.OBFUSCATED); + + String defaultName = generateDefaultName(translatedEntry); + if (defaultName != null) { + return defaultName; + } + } + } + + return null; + } + + @Nullable + private String proposeName(Entry entry) { + if (entry instanceof FieldEntry) { + for (EnigmaPlugin plugin : deobfuscator.getPlugins()) { + String owner = entry.getContainingClass().getFullName(); + String proposal = plugin.proposeFieldName(owner, entry.getName(), ((FieldEntry) entry).getDesc().toString()); + if (proposal != null) { + return proposal; + } + } + } + return null; + } + + @Nullable + private String generateDefaultName(Entry entry) { + if (entry instanceof LocalVariableDefEntry) { + LocalVariableDefEntry localVariable = (LocalVariableDefEntry) entry; + + int index = localVariable.getIndex(); + if (localVariable.isArgument()) { + List arguments = localVariable.getParent().getDesc().getArgumentDescs(); + return LocalNameGenerator.generateArgumentName(index, localVariable.getDesc(), arguments); + } else { + return LocalNameGenerator.generateLocalVariableName(index, localVariable.getDesc()); + } + } + + return null; + } + + private boolean isDeobfuscated(Entry entry, Entry translatedEntry) { + return !entry.getName().equals(translatedEntry.getName()); + } + + public ClassEntry getEntry() { + return classEntry; + } + + public SourceIndex getIndex() { + return remappedIndex; + } + + public Map> getHighlightedTokens() { + return highlightedTokens; + } + + private void highlightToken(Token token, TokenHighlightType highlightType) { + highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); + } + + @Override + public String toString() { + return remappedIndex.getSource(); + } +} diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index d119735..a6e20a2 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java @@ -24,7 +24,7 @@ import cuchaz.enigma.gui.filechooser.FileChooserAny; import cuchaz.enigma.gui.filechooser.FileChooserFolder; import cuchaz.enigma.gui.highlight.BoxHighlightPainter; import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; -import cuchaz.enigma.gui.node.ClassSelectorPackageNode; +import cuchaz.enigma.gui.highlight.TokenHighlightType; import cuchaz.enigma.gui.panels.PanelDeobf; import cuchaz.enigma.gui.panels.PanelEditor; import cuchaz.enigma.gui.panels.PanelIdentifier; @@ -44,10 +44,9 @@ import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.*; -import java.io.IOException; import java.nio.file.Path; -import java.util.*; import java.util.List; +import java.util.*; import java.util.function.Function; public class Gui { @@ -71,7 +70,7 @@ public class Gui { private JPanel classesPanel; private JSplitPane splitClasses; private PanelIdentifier infoPanel; - public Map boxHighlightPainters; + public Map boxHighlightPainters; private SelectionHighlightPainter selectionHighlightPainter; private JTree inheritanceTree; private JTree implementationsTree; @@ -320,7 +319,7 @@ public class Gui { this.frame.setTitle(Constants.NAME + " - " + jarName); this.classesPanel.removeAll(); this.classesPanel.add(splitClasses); - setSource(null); + setEditorText(null); // update menu this.menuBar.closeJarMenu.setEnabled(true); @@ -342,7 +341,7 @@ public class Gui { this.frame.setTitle(Constants.NAME); setObfClasses(null); setDeobfClasses(null); - setSource(null); + setEditorText(null); this.classesPanel.removeAll(); // update menu @@ -373,11 +372,16 @@ public class Gui { this.menuBar.saveMappingsMenu.setEnabled(path != null); } - public void setSource(String source) { + public void setEditorText(String source) { this.editor.getHighlighter().removeAllHighlights(); this.editor.setText(source); } + public void setSource(DecompiledClassSource source) { + editor.setText(source.toString()); + setHighlightedTokens(source.getHighlightedTokens()); + } + public void showToken(final Token token) { if (token == null) { throw new IllegalArgumentException("Token cannot be null!"); @@ -401,15 +405,15 @@ public class Gui { showToken(sortedTokens.get(0)); } - public void setHighlightedTokens(Map> tokens) { + public void setHighlightedTokens(Map> tokens) { // remove any old highlighters this.editor.getHighlighter().removeAllHighlights(); if (boxHighlightPainters != null) { - for (String s : tokens.keySet()) { - BoxHighlightPainter painter = boxHighlightPainters.get(s); + for (TokenHighlightType type : tokens.keySet()) { + BoxHighlightPainter painter = boxHighlightPainters.get(type); if (painter != null) { - setHighlightedTokens(tokens.get(s), painter); + setHighlightedTokens(tokens.get(type), painter); } } } @@ -435,17 +439,19 @@ public class Gui { this.reference = reference; + EntryReference, Entry> translatedReference = controller.getDeobfuscator().deobfuscate(reference); + infoPanel.removeAll(); - if (reference.entry instanceof ClassEntry) { - showClassEntry((ClassEntry) this.reference.entry); - } else if (this.reference.entry instanceof FieldEntry) { - showFieldEntry((FieldEntry) this.reference.entry); - } else if (this.reference.entry instanceof MethodEntry) { - showMethodEntry((MethodEntry) this.reference.entry); - } else if (this.reference.entry instanceof LocalVariableEntry) { - showLocalVariableEntry((LocalVariableEntry) this.reference.entry); + if (translatedReference.entry instanceof ClassEntry) { + showClassEntry((ClassEntry) translatedReference.entry); + } else if (translatedReference.entry instanceof FieldEntry) { + showFieldEntry((FieldEntry) translatedReference.entry); + } else if (translatedReference.entry instanceof MethodEntry) { + showMethodEntry((MethodEntry) translatedReference.entry); + } else if (translatedReference.entry instanceof LocalVariableEntry) { + showLocalVariableEntry((LocalVariableEntry) translatedReference.entry); } else { - throw new Error("Unknown entry desc: " + this.reference.entry.getClass().getName()); + throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName()); } redraw(); @@ -519,7 +525,7 @@ public class Gui { Token token = this.controller.getToken(pos); boolean isToken = token != null; - reference = this.controller.getDeobfReference(token); + reference = this.controller.getReference(token); Entry referenceEntry = reference != null ? reference.entry : null; boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry; @@ -527,7 +533,7 @@ public class Gui { boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); boolean isInJar = isToken && this.controller.entryIsInJar(referenceEntry); - boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); + boolean isRenameable = isToken && this.controller.getDeobfuscator().isRenamable(reference); if (isToken) { showReference(reference); @@ -544,7 +550,7 @@ public class Gui { this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); - if (isToken && this.controller.entryHasDeobfuscatedName(referenceEntry)) { + if (isToken && this.controller.getDeobfuscator().isRemapped(referenceEntry)) { this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); } else { this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); @@ -576,7 +582,10 @@ public class Gui { // init the text box final JTextField text = new JTextField(); - text.setText(reference.getNameableName()); + + EntryReference, Entry> translatedReference = controller.getDeobfuscator().deobfuscate(reference); + text.setText(translatedReference.getNameableName()); + text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); text.addKeyListener(new KeyAdapter() { @Override @@ -603,7 +612,7 @@ public class Gui { int offset = text.getText().lastIndexOf('/') + 1; // If it's a class and isn't in the default package, assume that it's deobfuscated. - if (reference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0) + if (translatedReference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0) text.select(offset, text.getText().length()); else text.selectAll(); @@ -719,7 +728,7 @@ public class Gui { } public void toggleMapping() { - if (this.controller.entryHasDeobfuscatedName(reference.entry)) { + if (this.controller.getDeobfuscator().isRemapped(reference.entry)) { this.controller.removeMapping(reference); } else { this.controller.markAsDeobfuscated(reference); @@ -743,7 +752,7 @@ public class Gui { callback.apply(response); } - public void saveMapping() throws IOException { + public void saveMapping() { if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); } @@ -757,13 +766,8 @@ public class Gui { // ask to save before closing showDiscardDiag((response) -> { if (response == JOptionPane.YES_OPTION) { - try { - this.saveMapping(); - this.frame.dispose(); - - } catch (IOException ex) { - throw new Error(ex); - } + this.saveMapping(); + this.frame.dispose(); } else if (response == JOptionPane.NO_OPTION) this.frame.dispose(); @@ -796,47 +800,39 @@ public class Gui { this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); } - public void moveClassTree(EntryReference, Entry> deobfReference, String newName) { - String oldEntry = deobfReference.entry.getContainingClass().getPackageName(); + public void moveClassTree(EntryReference, Entry> obfReference, String newName) { + String oldEntry = obfReference.entry.getContainingClass().getPackageName(); String newEntry = new ClassEntry(newName).getPackageName(); - moveClassTree(deobfReference, newName, oldEntry == null, - newEntry == null); + moveClassTree(obfReference, oldEntry == null, newEntry == null); } // TODO: getExpansionState will *not* actually update itself based on name changes! - public void moveClassTree(EntryReference, Entry> deobfReference, String newName, boolean isOldOb, boolean isNewOb) { - ClassEntry oldEntry = deobfReference.entry.getContainingClass(); - ClassEntry newEntry = new ClassEntry(newName); + public void moveClassTree(EntryReference, Entry> obfReference, boolean isOldOb, boolean isNewOb) { + ClassEntry classEntry = obfReference.entry.getContainingClass(); // Ob -> deob List stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses); List stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses); if (isOldOb && !isNewOb) { - this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, obfPanel.obfClasses); - ClassSelectorPackageNode packageNode = this.obfPanel.obfClasses.getPackageNode(oldEntry); - this.obfPanel.obfClasses.removeNode(packageNode, oldEntry); - this.obfPanel.obfClasses.removeNodeIfEmpty(packageNode); + this.deobfPanel.deobfClasses.moveClassIn(classEntry); + this.obfPanel.obfClasses.moveClassOut(classEntry); this.deobfPanel.deobfClasses.reload(); this.obfPanel.obfClasses.reload(); } // Deob -> ob else if (isNewOb && !isOldOb) { - this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, deobfPanel.deobfClasses); - ClassSelectorPackageNode packageNode = this.deobfPanel.deobfClasses.getPackageNode(oldEntry); - this.deobfPanel.deobfClasses.removeNode(packageNode, oldEntry); - this.deobfPanel.deobfClasses.removeNodeIfEmpty(packageNode); + this.obfPanel.obfClasses.moveClassIn(classEntry); + this.deobfPanel.deobfClasses.moveClassOut(classEntry); this.deobfPanel.deobfClasses.reload(); this.obfPanel.obfClasses.reload(); } // Local move else if (isOldOb) { - this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, null); - this.obfPanel.obfClasses.removeNodeIfEmpty(this.obfPanel.obfClasses.getPackageNode(oldEntry)); + this.obfPanel.obfClasses.moveClassIn(classEntry); this.obfPanel.obfClasses.reload(); } else { - this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, null); - this.deobfPanel.deobfClasses.removeNodeIfEmpty(this.deobfPanel.deobfClasses.getPackageNode(oldEntry)); + this.deobfPanel.deobfClasses.moveClassIn(classEntry); this.deobfPanel.deobfClasses.reload(); } diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index fd9e7f0..03e1768 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -11,13 +11,13 @@ package cuchaz.enigma.gui; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Queues; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.strobel.decompiler.languages.java.ast.CompilationUnit; import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.SourceProvider; import cuchaz.enigma.analysis.*; -import cuchaz.enigma.api.EnigmaPlugin; import cuchaz.enigma.config.Config; import cuchaz.enigma.gui.dialog.ProgressDialog; import cuchaz.enigma.throwables.MappingParseException; @@ -36,16 +36,20 @@ import java.awt.event.ItemEvent; import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.*; +import java.util.Collection; +import java.util.Deque; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.jar.JarFile; import java.util.stream.Collectors; public class GuiController { + private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("decompiler-thread").build()); private Deobfuscator deobfuscator; private Gui gui; - private SourceIndex index; - private ClassEntry currentObfClass; + private DecompiledClassSource currentSource; private Deque, Entry>> referenceStack; private Path loadedMappingPath; @@ -54,8 +58,7 @@ public class GuiController { public GuiController(Gui gui) { this.gui = gui; this.deobfuscator = null; - this.index = null; - this.currentObfClass = null; + this.currentSource = null; this.referenceStack = Queues.newArrayDeque(); } @@ -93,7 +96,7 @@ public class GuiController { public void saveMappings(MappingFormat format, Path path) { EntryRemapper mapper = deobfuscator.getMapper(); - MappingDelta delta = mapper.takeMappingDelta(); + MappingDelta delta = mapper.takeMappingDelta(); boolean saveAll = !path.equals(loadedMappingPath); ProgressDialog.runInThread(this.gui.getFrame(), progress -> { @@ -116,189 +119,167 @@ public class GuiController { } public void exportSource(final File dirOut) { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); + ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut.toPath(), progress)); } public void exportJar(final File fileOut) { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress)); + ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeTransformedJar(fileOut, progress)); } public Token getToken(int pos) { - if (this.index == null) { + if (this.currentSource == null) { return null; } - return this.index.getReferenceToken(pos); + return this.currentSource.getIndex().getReferenceToken(pos); } @Nullable - public EntryReference, Entry> getDeobfReference(Token token) { - if (this.index == null) { + public EntryReference, Entry> getReference(Token token) { + if (this.currentSource == null) { return null; } - return this.index.getDeobfReference(token); + return this.currentSource.getIndex().getReference(token); } public ReadableToken getReadableToken(Token token) { - if (this.index == null) { + if (this.currentSource == null) { return null; } + SourceIndex index = this.currentSource.getIndex(); return new ReadableToken( - this.index.getLineNumber(token.start), - this.index.getColumnNumber(token.start), - this.index.getColumnNumber(token.end) + index.getLineNumber(token.start), + index.getColumnNumber(token.start), + index.getColumnNumber(token.end) ); } - public boolean entryHasDeobfuscatedName(Entry deobfEntry) { - EntryResolver resolver = this.deobfuscator.getMapper().getDeobfResolver(); - Entry resolvedEntry = resolver.resolveFirstEntry(deobfEntry, ResolutionStrategy.RESOLVE_ROOT); - return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.getMapper().obfuscate(resolvedEntry)); + public boolean entryIsInJar(Entry entry) { + if (entry == null) return false; + return this.deobfuscator.isRenamable(entry); } - public boolean entryIsInJar(Entry deobfEntry) { - if (deobfEntry == null) return false; - return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.getMapper().obfuscate(deobfEntry)); - } - - public boolean referenceIsRenameable(EntryReference, Entry> deobfReference) { - if (deobfReference == null) return false; - return this.deobfuscator.isRenameable(this.deobfuscator.getMapper().obfuscate(deobfReference)); - } - - public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry); + public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, obfClassEntry); - return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); + ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, entry); + return ClassInheritanceTreeNode.findNode(rootNode, entry); } - public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry); + public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) { Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, obfClassEntry); + return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, entry); } - public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { - MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry); + public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) { Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, obfMethodEntry); - return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); + MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, entry); + return MethodInheritanceTreeNode.findNode(rootNode, entry); } - public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { - MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry); + public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) { Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - List rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, obfMethodEntry); + List rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, entry); if (rootNodes.isEmpty()) { return null; } if (rootNodes.size() > 1) { - System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one."); + System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one."); } - return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry); + return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry); } - public ClassReferenceTreeNode getClassReferences(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry); + public ClassReferenceTreeNode getClassReferences(ClassEntry entry) { Translator deobfuscator = this.deobfuscator.getMapper().getDeobfuscator(); - ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, obfClassEntry); + ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } - public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { - FieldEntry obfFieldEntry = this.deobfuscator.getMapper().obfuscate(deobfFieldEntry); + public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) { Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, obfFieldEntry); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } - public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry, boolean recursive) { - MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry); + public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) { Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); - MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, obfMethodEntry); + MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry); rootNode.load(this.deobfuscator.getJarIndex(), true, recursive); return rootNode; } - public void rename(EntryReference, Entry> deobfReference, String newName, boolean refreshClassTree) { - EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); - this.deobfuscator.rename(obfReference.getNameableEntry(), newName); - - if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) - this.gui.moveClassTree(deobfReference, newName); - refreshCurrentClass(obfReference); + public void rename(EntryReference, Entry> reference, String newName, boolean refreshClassTree) { + this.deobfuscator.rename(reference.getNameableEntry(), newName); + if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) + this.gui.moveClassTree(reference, newName); + refreshCurrentClass(reference); } - public void removeMapping(EntryReference, Entry> deobfReference) { - EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); - this.deobfuscator.removeMapping(obfReference.getNameableEntry()); - if (deobfReference.entry instanceof ClassEntry) - this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true); - refreshCurrentClass(obfReference); + public void removeMapping(EntryReference, Entry> reference) { + this.deobfuscator.removeMapping(reference.getNameableEntry()); + if (reference.entry instanceof ClassEntry) + this.gui.moveClassTree(reference, false, true); + refreshCurrentClass(reference); } - public void markAsDeobfuscated(EntryReference, Entry> deobfReference) { - EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); - this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); - if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) - this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false); - refreshCurrentClass(obfReference); + public void markAsDeobfuscated(EntryReference, Entry> reference) { + this.deobfuscator.markAsDeobfuscated(reference.getNameableEntry()); + if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) + this.gui.moveClassTree(reference, true, false); + refreshCurrentClass(reference); } - public void openDeclaration(Entry deobfEntry) { - if (deobfEntry == null) { + public void openDeclaration(Entry entry) { + if (entry == null) { throw new IllegalArgumentException("Entry cannot be null!"); } - openReference(new EntryReference<>(deobfEntry, deobfEntry.getName())); + openReference(new EntryReference<>(entry, entry.getName())); } - public void openReference(EntryReference, Entry> deobfReference) { - if (deobfReference == null) { + public void openReference(EntryReference, Entry> reference) { + if (reference == null) { throw new IllegalArgumentException("Reference cannot be null!"); } // get the reference target class - EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); - ClassEntry obfClassEntry = obfReference.getLocationClassEntry(); - if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { - throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); + ClassEntry classEntry = reference.getLocationClassEntry(); + if (!this.deobfuscator.isRenamable(classEntry)) { + throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!"); } - if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) { + + if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) { // deobfuscate the class, then navigate to the reference - this.currentObfClass = obfClassEntry; - deobfuscate(this.currentObfClass, obfReference); + loadClass(classEntry, () -> showReference(reference)); } else { - showReference(obfReference); + showReference(reference); } } - private void showReference(EntryReference, Entry> obfReference) { + private void showReference(EntryReference, Entry> reference) { EntryRemapper mapper = this.deobfuscator.getMapper(); - Collection tokens = mapper.getObfResolver().resolveReference(obfReference, ResolutionStrategy.RESOLVE_ROOT) + SourceIndex index = this.currentSource.getIndex(); + Collection tokens = mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_ROOT) .stream() - .map(mapper::deobfuscate) - .flatMap(reference -> index.getReferenceTokens(reference).stream()) + .flatMap(r -> index.getReferenceTokens(r).stream()) .collect(Collectors.toList()); if (tokens.isEmpty()) { // DEBUG - System.err.println(String.format("WARNING: no tokens found for %s in %s", tokens, this.currentObfClass)); + System.err.println(String.format("WARNING: no tokens found for %s in %s", tokens, this.currentSource.getEntry())); } else { this.gui.showTokens(tokens); } } - public void savePreviousReference(EntryReference, Entry> deobfReference) { - this.referenceStack.push(this.deobfuscator.getMapper().obfuscate(deobfReference)); + public void savePreviousReference(EntryReference, Entry> reference) { + this.referenceStack.push(reference); } public void openPreviousReference() { if (hasPreviousLocation()) { - openReference(this.deobfuscator.getMapper().deobfuscate(this.referenceStack.pop())); + openReference(this.referenceStack.pop()); } } @@ -318,97 +299,65 @@ public class GuiController { refreshCurrentClass(null); } - private void refreshCurrentClass(EntryReference, Entry> obfReference) { - if (this.currentObfClass != null) { - deobfuscate(this.currentObfClass, obfReference); + private void refreshCurrentClass(EntryReference, Entry> reference) { + if (currentSource != null) { + loadClass(currentSource.getEntry(), () -> { + if (reference != null) { + showReference(reference); + } + }); } } - private void deobfuscate(final ClassEntry classEntry, final EntryReference, Entry> obfReference) { + private void loadClass(ClassEntry classEntry, Runnable callback) { + ClassEntry targetClass = classEntry.getOutermostClass(); - this.gui.setSource("(deobfuscating...)"); + boolean requiresDecompile = currentSource == null || !currentSource.getEntry().equals(targetClass); + if (requiresDecompile) { + gui.setEditorText("(decompiling...)"); + } - // run the deobfuscator in a separate thread so we don't block the GUI event queue - new Thread(() -> - { - // decompile,deobfuscate the bytecode - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClass().getFullName()); - if (sourceTree == null) { - // decompilation of this class is not supported - gui.setSource("Unable to find class: " + classEntry); - return; - } - String source = deobfuscator.getSource(sourceTree); - index = deobfuscator.getSourceIndex(sourceTree, source); - - String sourceString = index.getSource(); - - // set the highlighted tokens - List obfuscatedTokens = Lists.newArrayList(); - List proposedTokens = Lists.newArrayList(); - List deobfuscatedTokens = Lists.newArrayList(); - List otherTokens = Lists.newArrayList(); - - int offset = 0; - Map tokenRemap = new HashMap<>(); - boolean remapped = false; - - for (Token inToken : index.referenceTokens()) { - Token token = inToken.move(offset); - - EntryReference, Entry> reference = index.getDeobfReference(inToken); - if (referenceIsRenameable(reference)) { - boolean added = false; - - if (!entryHasDeobfuscatedName(reference.getNameableEntry())) { - Entry obfEntry = deobfuscator.getMapper().obfuscate(reference.getNameableEntry()); - if (obfEntry instanceof FieldEntry) { - for (EnigmaPlugin plugin : deobfuscator.getPlugins()) { - String owner = obfEntry.getContainingClass().getFullName(); - String proposal = plugin.proposeFieldName(owner, obfEntry.getName(), ((FieldEntry) obfEntry).getDesc().toString()); - if (proposal != null) { - proposedTokens.add(token); - offset += token.getRenameOffset(proposal); - sourceString = token.rename(sourceString, proposal); - added = true; - remapped = true; - break; - } - } - } - } - - if (!added) { - if (entryHasDeobfuscatedName(reference.getNameableEntry())) { - deobfuscatedTokens.add(token); - } else { - obfuscatedTokens.add(token); - } - } - } else { - otherTokens.add(token); + DECOMPILER_SERVICE.submit(() -> { + try { + if (requiresDecompile) { + decompileSource(targetClass, deobfuscator.getObfSourceProvider()); } - tokenRemap.put(inToken, token); + remapSource(deobfuscator.getMapper().getDeobfuscator()); + callback.run(); + } catch (Throwable t) { + System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName()); + t.printStackTrace(System.err); } + }); + } - if (remapped) { - index.remap(sourceString, tokenRemap); - } + private void decompileSource(ClassEntry targetClass, SourceProvider sourceProvider) { + CompilationUnit sourceTree = sourceProvider.getSources(targetClass.getFullName()); + if (sourceTree == null) { + gui.setEditorText("Unable to find class: " + targetClass); + return; + } - gui.setSource(sourceString); - if (obfReference != null) { - showReference(obfReference); - } + DropImportAstTransform.INSTANCE.run(sourceTree); + + String sourceString = sourceProvider.writeSourceToString(sourceTree); + + SourceIndex index = SourceIndex.buildIndex(sourceString, sourceTree, true); + index.resolveReferences(deobfuscator.getMapper().getObfResolver()); + + currentSource = new DecompiledClassSource(targetClass, deobfuscator, index); + } + + private void remapSource(Translator translator) { + if (currentSource == null) { + return; + } + + currentSource.remapSource(translator); - gui.setEditorTheme(Config.getInstance().lookAndFeel); - gui.setHighlightedTokens(ImmutableMap.of( - "obfuscated", obfuscatedTokens, - "proposed", proposedTokens, - "deobfuscated", deobfuscatedTokens, - "other", otherTokens - )); - }).start(); + gui.setEditorTheme(Config.getInstance().lookAndFeel); + gui.setSource(currentSource); } public Deobfuscator getDeobfuscator() { diff --git a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java new file mode 100644 index 0000000..f38f44e --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java @@ -0,0 +1,64 @@ +package cuchaz.enigma.gui; + +import cuchaz.enigma.analysis.Token; + +import java.util.HashMap; +import java.util.Map; + +public class SourceRemapper { + private final String source; + private final Iterable tokens; + + public SourceRemapper(String source, Iterable tokens) { + this.source = source; + this.tokens = tokens; + } + + public Result remap(Remapper remapper) { + StringBuffer remappedSource = new StringBuffer(source); + Map remappedTokens = new HashMap<>(); + + int accumulatedOffset = 0; + for (Token token : tokens) { + Token movedToken = token.move(accumulatedOffset); + + String remappedName = remapper.remap(token, movedToken); + if (remappedName != null) { + accumulatedOffset += movedToken.getRenameOffset(remappedName); + movedToken.rename(remappedSource, remappedName); + } + + if (!token.equals(movedToken)) { + remappedTokens.put(token, movedToken); + } + } + + return new Result(remappedSource.toString(), remappedTokens); + } + + public static class Result { + private final String remappedSource; + private final Map remappedTokens; + + Result(String remappedSource, Map remappedTokens) { + this.remappedSource = remappedSource; + this.remappedTokens = remappedTokens; + } + + public String getSource() { + return remappedSource; + } + + public Token getRemappedToken(Token token) { + return remappedTokens.getOrDefault(token, token); + } + + public boolean isEmpty() { + return remappedTokens.isEmpty(); + } + } + + public interface Remapper { + String remap(Token token, Token movedToken); + } +} diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index f4f0277..dfbfa65 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -152,12 +152,8 @@ public class MenuBar extends JMenuBar { if (this.gui.getController().isDirty()) { this.gui.showDiscardDiag((response -> { if (response == JOptionPane.YES_OPTION) { - try { - gui.saveMapping(); - this.gui.getController().closeMappings(); - } catch (IOException e) { - throw new Error(e); - } + gui.saveMapping(); + this.gui.getController().closeMappings(); } else if (response == JOptionPane.NO_OPTION) this.gui.getController().closeMappings(); return null; diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java index 10366ce..cef6494 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java @@ -34,7 +34,9 @@ public class BoxHighlightPainter implements Highlighter.HighlightPainter { public static Rectangle getBounds(JTextComponent text, int start, int end) { try { // determine the bounds of the text - Rectangle bounds = text.modelToView(start).union(text.modelToView(end)); + Rectangle startRect = text.modelToView(start); + Rectangle endRect = text.modelToView(end); + Rectangle bounds = startRect.union(endRect); // adjust the box so it looks nice bounds.x -= 2; diff --git a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java new file mode 100644 index 0000000..ae23f32 --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.gui.highlight; + +public enum TokenHighlightType { + OBFUSCATED, + DEOBFUSCATED, + PROPOSED +} diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java index bf6b178..922f8f2 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java @@ -17,13 +17,19 @@ import javax.swing.tree.DefaultMutableTreeNode; public class ClassSelectorClassNode extends DefaultMutableTreeNode { + private final ClassEntry obfEntry; private ClassEntry classEntry; - public ClassSelectorClassNode(ClassEntry classEntry) { + public ClassSelectorClassNode(ClassEntry obfEntry, ClassEntry classEntry) { + this.obfEntry = obfEntry; this.classEntry = classEntry; this.setUserObject(classEntry); } + public ClassEntry getObfEntry() { + return obfEntry; + } + public ClassEntry getClassEntry() { return this.classEntry; } -- cgit v1.2.3