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