From 5540c815de36e316d0749ce2163f12c61895b327 Mon Sep 17 00:00:00 2001
From: asiekierka
Date: Wed, 17 Aug 2016 18:35:12 +0200
Subject: Revert "Removed unused methods"
This reverts commit 1742190f784d0d62e7cc869eebafdfe1927e448f.
---
src/main/java/cuchaz/enigma/gui/BrowserCaret.java | 2 +-
.../java/cuchaz/enigma/gui/ClassMatchingGui.java | 546 +++++++++++++++++++++
src/main/java/cuchaz/enigma/gui/ClassSelector.java | 120 +++++
src/main/java/cuchaz/enigma/gui/CodeReader.java | 223 +++++++++
src/main/java/cuchaz/enigma/gui/Gui.java | 2 +-
src/main/java/cuchaz/enigma/gui/GuiTricks.java | 56 +++
.../java/cuchaz/enigma/gui/MemberMatchingGui.java | 490 ++++++++++++++++++
.../java/cuchaz/enigma/gui/ScoredClassEntry.java | 30 ++
.../enigma/gui/node/ClassSelectorPackageNode.java | 4 +
9 files changed, 1471 insertions(+), 2 deletions(-)
create mode 100644 src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
create mode 100644 src/main/java/cuchaz/enigma/gui/CodeReader.java
create mode 100644 src/main/java/cuchaz/enigma/gui/GuiTricks.java
create mode 100644 src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
create mode 100644 src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
(limited to 'src/main/java/cuchaz/enigma/gui')
diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
index f58d012..f9701f2 100644
--- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
+++ b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
@@ -30,6 +30,6 @@ public class BrowserCaret extends DefaultCaret {
@Override
public Highlighter.HighlightPainter getSelectionPainter() {
- return selectionPainter;
+ return this.selectionPainter;
}
}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
new file mode 100644
index 0000000..ec63900
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,546 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.gui;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.*;
+
+import cuchaz.enigma.Constants;
+import cuchaz.enigma.Deobfuscator;
+import cuchaz.enigma.convert.*;
+import cuchaz.enigma.gui.node.ClassSelectorClassNode;
+import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.Mappings;
+import cuchaz.enigma.mapping.MappingsChecker;
+import cuchaz.enigma.throwables.MappingConflict;
+import de.sciss.syntaxpane.DefaultSyntaxKit;
+
+
+public class ClassMatchingGui {
+
+ private enum SourceType {
+ Matched {
+ @Override
+ public Collection getSourceClasses(ClassMatches matches) {
+ return matches.getUniqueMatches().keySet();
+ }
+ },
+ Unmatched {
+ @Override
+ public Collection getSourceClasses(ClassMatches matches) {
+ return matches.getUnmatchedSourceClasses();
+ }
+ },
+ Ambiguous {
+ @Override
+ public Collection getSourceClasses(ClassMatches 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(ClassMatches matches);
+
+ public static SourceType getDefault() {
+ return values()[0];
+ }
+ }
+
+ public interface SaveListener {
+ void save(ClassMatches matches);
+ }
+
+ // controls
+ private JFrame m_frame;
+ private ClassSelector m_sourceClasses;
+ private ClassSelector m_destClasses;
+ 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 JCheckBox m_top10Matches;
+
+ private ClassMatches m_classMatches;
+ 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(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
+
+ m_classMatches = matches;
+ m_sourceDeobfuscator = sourceDeobfuscator;
+ m_destDeobfuscator = destDeobfuscator;
+
+ // init frame
+ m_frame = new JFrame(Constants.NAME + " - Class Matcher");
+ 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 = 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.DEOBF_CLASS_COMPARATOR);
+ m_sourceClasses.setListener(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_top10Matches = new JCheckBox("Show only top 10 matches");
+ destPanel.add(m_top10Matches);
+ m_top10Matches.addActionListener(event -> toggleTop10Matches());
+
+ m_destClasses = new ClassSelector(ClassSelector.DEOBF_CLASS_COMPARATOR);
+ m_destClasses.setListener(this::setDestClass);
+ JScrollPane destScroller = new JScrollPane(m_destClasses);
+ destPanel.add(destScroller);
+
+ JButton autoMatchButton = new JButton("AutoMatch");
+ autoMatchButton.addActionListener(event -> autoMatch());
+ destPanel.add(autoMatchButton);
+
+ // init source panels
+ DefaultSyntaxKit.initKit();
+ 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));
+ 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_destClassLabel = new JLabel();
+ m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
+
+ m_matchButton = new JButton();
+
+ m_advanceCheck = new JCheckBox("Advance to next likely match");
+ m_advanceCheck.addActionListener(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);
+
+ // init state
+ updateDestMappings();
+ setSourceType(SourceType.getDefault());
+ updateMatchButton();
+ m_saveListener = null;
+ }
+
+ public void setSaveListener(SaveListener val) {
+ m_saveListener = val;
+ }
+
+ private void updateDestMappings() {
+ try {
+ Mappings newMappings = MappingsConverter.newMappings(
+ m_classMatches,
+ 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);
+ } catch (MappingConflict ex) {
+ System.out.println(ex.getMessage());
+ ex.printStackTrace();
+ return;
+ }
+ }
+
+ protected void setSourceType(SourceType val) {
+
+ // show the source classes
+ m_sourceType = val;
+ 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_classMatches).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 = this::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_classMatches.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);
+ m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, () -> m_sourceReader.navigateToClassDeclaration(m_sourceClass));
+
+ updateMatchButton();
+ }
+
+ private Collection getLikelyMatches(ClassEntry sourceClass) {
+
+ ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
+
+ // set up identifiers
+ ClassNamer namer = new ClassNamer(m_classMatches.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_classMatches.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));
+ }
+
+ if (m_top10Matches.isSelected() && scoredDestClasses.size() > 10) {
+ Collections.sort(scoredDestClasses, (a, b) -> {
+ ScoredClassEntry sa = (ScoredClassEntry) a;
+ ScoredClassEntry sb = (ScoredClassEntry) b;
+ return -Float.compare(sa.getScore(), sb.getScore());
+ });
+ scoredDestClasses = scoredDestClasses.subList(0, 10);
+ }
+
+ 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() : "");
+
+ m_destReader.decompileClass(m_destClass, m_destDeobfuscator, () -> m_destReader.navigateToClassDeclaration(m_destClass));
+
+ updateMatchButton();
+ }
+
+ private void updateMatchButton() {
+
+ ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
+ ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
+
+ 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);
+
+ GuiTricks.deactivateButton(m_matchButton);
+ if (twoSelected) {
+ if (isMatched) {
+ GuiTricks.activateButton(m_matchButton, "Unmatch", event -> onUnmatchClick());
+ } else if (canMatch) {
+ GuiTricks.activateButton(m_matchButton, "Match", event -> onMatchClick());
+ }
+ }
+ }
+
+ 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_classMatches.removeSource(obfSource);
+ m_classMatches.removeDest(obfDest);
+
+ // add them as matched classes
+ m_classMatches.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_classMatches.removeSource(obfSource);
+ m_classMatches.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_classMatches);
+ }
+ }
+
+ 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_classMatches.getUniqueMatches()
+ );
+ ClassMatches newMatches = new ClassMatches(matching.matches());
+ System.out.println(String.format("Automatch found %d new matches",
+ newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
+ ));
+
+ // update the current matches
+ m_classMatches = 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, this::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);
+ }
+
+ private void toggleTop10Matches() {
+ if (m_sourceClass != null) {
+ m_destClasses.clearSelection();
+ m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
+ m_destClasses.expandAll();
+ }
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index 27b4d36..3df9042 100644
--- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -138,6 +138,31 @@ public class ClassSelector extends JTree {
restoreExpanstionState(this, 0, state);
}
+ 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 boolean isDescendant(TreePath path1, TreePath path2) {
int count1 = path1.getPathCount();
int count2 = path2.getPathCount();
@@ -175,4 +200,99 @@ public class ClassSelector extends JTree {
tree.expandRow(token);
}
}
+
+ 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/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java
new file mode 100644
index 0000000..601e5b9
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java
@@ -0,0 +1,223 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.gui;
+
+import com.strobel.decompiler.languages.java.ast.CompilationUnit;
+
+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.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Highlighter.HighlightPainter;
+
+import cuchaz.enigma.Deobfuscator;
+import cuchaz.enigma.analysis.EntryReference;
+import cuchaz.enigma.analysis.SourceIndex;
+import cuchaz.enigma.analysis.Token;
+import cuchaz.enigma.gui.highlight.SelectionHighlightPainter;
+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();
+
+ public interface SelectionListener {
+ void onSelect(EntryReference reference);
+ }
+
+ private SelectionHighlightPainter m_selectionHighlightPainter;
+ private SourceIndex m_sourceIndex;
+ private SelectionListener m_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(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) {
+ // sadly, the java lexer is not thread safe, so we have to serialize all these calls
+ synchronized (m_lock) {
+ setText(code);
+ }
+ }
+
+ public SourceIndex getSourceIndex() {
+ return m_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() {
+ @Override
+ public void run() {
+
+ // decompile it
+ CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
+ String source = deobfuscator.getSource(sourceTree);
+ setCode(source);
+ m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
+
+ 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_selectionHighlightPainter);
+ }
+
+ // 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();
+ }
+
+ 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/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
index 80cb3ed..fd59a81 100644
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -370,7 +370,7 @@ public class Gui {
if (token == null) {
throw new IllegalArgumentException("Token cannot be null!");
}
- Utils.navigateToToken(this.editor, token, m_selectionHighlightPainter);
+ CodeReader.navigateToToken(this.editor, token, m_selectionHighlightPainter);
redraw();
}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 0000000..da2ec74
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.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;
+
+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);
+ }
+
+ 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);
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
new file mode 100644
index 0000000..60c6d8e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -0,0 +1,490 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.gui;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+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.*;
+import javax.swing.text.Highlighter.HighlightPainter;
+
+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.gui.highlight.DeobfuscatedHighlightPainter;
+import cuchaz.enigma.gui.highlight.ObfuscatedHighlightPainter;
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.Entry;
+import de.sciss.syntaxpane.DefaultSyntaxKit;
+
+
+public class MemberMatchingGui {
+
+ private 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 interface SaveListener {
+ 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.DEOBF_CLASS_COMPARATOR);
+ 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) {
+ // show obfuscated and deobfuscated names, but no types/signatures
+ T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
+ return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
+ }
+
+ 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/main/java/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
new file mode 100644
index 0000000..d1e2de0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.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;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
index 805b3a8..dfdc765 100644
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
+++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
@@ -20,6 +20,10 @@ public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
this.packageName = packageName;
}
+ public String getPackageName() {
+ return packageName;
+ }
+
@Override
public String toString() {
return this.packageName;
--
cgit v1.2.3