From 6e464ea251cab63c776ece0b2a356f1498ffa294 Mon Sep 17 00:00:00 2001 From: Thog Date: Wed, 8 Mar 2017 08:17:04 +0100 Subject: Follow Fabric guidelines --- src/main/java/cuchaz/enigma/gui/BrowserCaret.java | 17 +- .../java/cuchaz/enigma/gui/ClassMatchingGui.java | 990 ++++++------ src/main/java/cuchaz/enigma/gui/ClassSelector.java | 968 ++++++------ src/main/java/cuchaz/enigma/gui/CodeReader.java | 362 +++-- src/main/java/cuchaz/enigma/gui/Gui.java | 1594 ++++++++++---------- src/main/java/cuchaz/enigma/gui/GuiController.java | 640 ++++---- src/main/java/cuchaz/enigma/gui/GuiTricks.java | 41 +- .../java/cuchaz/enigma/gui/MemberMatchingGui.java | 815 +++++----- .../java/cuchaz/enigma/gui/ScoredClassEntry.java | 42 +- .../cuchaz/enigma/gui/TokenListCellRenderer.java | 35 +- .../java/cuchaz/enigma/gui/dialog/AboutDialog.java | 93 +- .../java/cuchaz/enigma/gui/dialog/CrashDialog.java | 118 +- .../cuchaz/enigma/gui/dialog/ProgressDialog.java | 157 +- .../java/cuchaz/enigma/gui/elements/MenuBar.java | 400 +++-- .../cuchaz/enigma/gui/elements/PopupMenuBar.java | 139 +- .../enigma/gui/filechooser/FileChooserAny.java | 11 +- .../enigma/gui/filechooser/FileChooserFile.java | 6 +- .../enigma/gui/filechooser/FileChooserFolder.java | 10 +- .../enigma/gui/highlight/BoxHighlightPainter.java | 87 +- .../highlight/DeobfuscatedHighlightPainter.java | 9 +- .../gui/highlight/ObfuscatedHighlightPainter.java | 9 +- .../gui/highlight/OtherHighlightPainter.java | 9 +- .../gui/highlight/SelectionHighlightPainter.java | 22 +- .../enigma/gui/node/ClassSelectorClassNode.java | 81 +- .../enigma/gui/node/ClassSelectorPackageNode.java | 81 +- .../java/cuchaz/enigma/gui/panels/PanelDeobf.java | 31 +- .../java/cuchaz/enigma/gui/panels/PanelEditor.java | 108 +- .../cuchaz/enigma/gui/panels/PanelIdentifier.java | 40 +- .../java/cuchaz/enigma/gui/panels/PanelObf.java | 57 +- 29 files changed, 3432 insertions(+), 3540 deletions(-) (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 bcdff51..af105db 100644 --- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java +++ b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java @@ -8,20 +8,21 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import javax.swing.text.DefaultCaret; public class BrowserCaret extends DefaultCaret { - @Override - public boolean isSelectionVisible() { - return true; - } + @Override + public boolean isSelectionVisible() { + return true; + } - @Override - public boolean isVisible() { - return true; - } + @Override + public boolean isVisible() { + return true; + } } diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java index dcbe1c5..05501f4 100644 --- a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java +++ b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.google.common.collect.BiMap; @@ -31,508 +32,505 @@ import java.util.Collection; import java.util.List; import java.util.Map; - 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 frame; - private ClassSelector sourceClasses; - private ClassSelector destClasses; - private CodeReader sourceReader; - private CodeReader destReader; - private JLabel sourceClassLabel; - private JLabel destClassLabel; - private JButton matchButton; - private Map sourceTypeButtons; - private JCheckBox advanceCheck; - private JCheckBox top10Matches; - - private ClassMatches classMatches; - private Deobfuscator sourceDeobfuscator; - private Deobfuscator destDeobfuscator; - private ClassEntry sourceClass; - private ClassEntry destClass; - private SourceType sourceType; - private SaveListener saveListener; - - public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - - classMatches = matches; - this.sourceDeobfuscator = sourceDeobfuscator; - this.destDeobfuscator = destDeobfuscator; - - // init frame - frame = new JFrame(Constants.NAME + " - Class Matcher"); - final Container pane = 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(); - this.sourceTypeButtons = Maps.newHashMap(); - for (SourceType sourceType : SourceType.values()) { - JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); - this.sourceTypeButtons.put(sourceType, button); - sourceTypePanel.add(button); - } - - sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); - sourceClasses.setSelectionListener(this::setSourceClass); - JScrollPane sourceScroller = new JScrollPane(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")); - - top10Matches = new JCheckBox("Show only top 10 matches"); - destPanel.add(top10Matches); - top10Matches.addActionListener(event -> toggleTop10Matches()); - - destClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); - destClasses.setSelectionListener(this::setDestClass); - JScrollPane destScroller = new JScrollPane(destClasses); - destPanel.add(destScroller); - - JButton autoMatchButton = new JButton("AutoMatch"); - autoMatchButton.addActionListener(event -> autoMatch()); - destPanel.add(autoMatchButton); - - // init source panels - DefaultSyntaxKit.initKit(); - sourceReader = new CodeReader(); - destReader = new CodeReader(); - - // init all the splits - JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane( - sourceReader)); - splitLeft.setResizeWeight(0); // let the right side take all the slack - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(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()); - - sourceClassLabel = new JLabel(); - sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT); - destClassLabel = new JLabel(); - destClassLabel.setHorizontalAlignment(SwingConstants.LEFT); - - matchButton = new JButton(); - - advanceCheck = new JCheckBox("Advance to next likely match"); - advanceCheck.addActionListener(event -> { - if (advanceCheck.isSelected()) { - advance(); - } - }); - - bottomPanel.add(sourceClassLabel); - bottomPanel.add(matchButton); - bottomPanel.add(destClassLabel); - bottomPanel.add(advanceCheck); - pane.add(bottomPanel, BorderLayout.SOUTH); - - // show the frame - pane.doLayout(); - frame.setSize(1024, 576); - frame.setMinimumSize(new Dimension(640, 480)); - frame.setVisible(true); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - - // init state - updateDestMappings(); - setSourceType(SourceType.getDefault()); - updateMatchButton(); - saveListener = null; - } - - public void setSaveListener(SaveListener val) { - saveListener = val; - } - - private void updateDestMappings() { - try { - Mappings newMappings = MappingsConverter.newMappings(classMatches, - sourceDeobfuscator.getMappings(), sourceDeobfuscator, destDeobfuscator - ); - - // look for dropped mappings - MappingsChecker checker = new MappingsChecker(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 - )); - - destDeobfuscator.setMappings(newMappings); - } catch (MappingConflict ex) { - System.out.println(ex.getMessage()); - ex.printStackTrace(); - return; - } - } - - protected void setSourceType(SourceType val) { - - // show the source classes - sourceType = val; - sourceClasses.setClasses(deobfuscateClasses(sourceType.getSourceClasses(classMatches), sourceDeobfuscator)); - - // update counts - for (SourceType sourceType : SourceType.values()) { - sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", - sourceType.name(), - sourceType.getSourceClasses(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 (advanceCheck.isSelected()) { - onGetDestClasses = this::pickBestDestClass; - } - - setSourceClass(classEntry, onGetDestClasses); - } - - protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) { - - // update the current source class - sourceClass = classEntry; - sourceClassLabel.setText(sourceClass != null ? sourceClass.getName() : ""); - - if (sourceClass != null) { - - // show the dest class(es) - ClassMatch match = classMatches.getMatchBySource(sourceDeobfuscator.obfuscateEntry(sourceClass)); - assert (match != null); - if (match.destClasses.isEmpty()) { - - destClasses.setClasses(null); - - // run in a separate thread to keep ui responsive - new Thread(() -> - { - destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator)); - destClasses.expandAll(); - - if (onGetDestClasses != null) { - onGetDestClasses.run(); - } - }).start(); - - } else { - - destClasses.setClasses(deobfuscateClasses(match.destClasses, destDeobfuscator)); - destClasses.expandAll(); - - if (onGetDestClasses != null) { - onGetDestClasses.run(); - } - } - } - - setDestClass(null); - sourceReader.decompileClass( - sourceClass, sourceDeobfuscator, () -> sourceReader.navigateToClassDeclaration(sourceClass)); - - updateMatchButton(); - } - - private Collection getLikelyMatches(ClassEntry sourceClass) { - - ClassEntry obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass); - - // set up identifiers - ClassNamer namer = new ClassNamer(classMatches.getUniqueMatches()); - ClassIdentifier sourceIdentifier = new ClassIdentifier( - sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(), - namer.getSourceNamer(), true - ); - ClassIdentifier destIdentifier = new ClassIdentifier( - destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(), - namer.getDestNamer(), true - ); + // controls + private JFrame frame; + private ClassSelector sourceClasses; + private ClassSelector destClasses; + private CodeReader sourceReader; + private CodeReader destReader; + private JLabel sourceClassLabel; + private JLabel destClassLabel; + private JButton matchButton; + private Map sourceTypeButtons; + private JCheckBox advanceCheck; + private JCheckBox top10Matches; + private ClassMatches classMatches; + private Deobfuscator sourceDeobfuscator; + private Deobfuscator destDeobfuscator; + private ClassEntry sourceClass; + private ClassEntry destClass; + private SourceType sourceType; + private SaveListener saveListener; + public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + + classMatches = matches; + this.sourceDeobfuscator = sourceDeobfuscator; + this.destDeobfuscator = destDeobfuscator; + + // init frame + frame = new JFrame(Constants.NAME + " - Class Matcher"); + final Container pane = 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(); + this.sourceTypeButtons = Maps.newHashMap(); + for (SourceType sourceType : SourceType.values()) { + JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); + this.sourceTypeButtons.put(sourceType, button); + sourceTypePanel.add(button); + } + + sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); + sourceClasses.setSelectionListener(this::setSourceClass); + JScrollPane sourceScroller = new JScrollPane(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")); + + top10Matches = new JCheckBox("Show only top 10 matches"); + destPanel.add(top10Matches); + top10Matches.addActionListener(event -> toggleTop10Matches()); + + destClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); + destClasses.setSelectionListener(this::setDestClass); + JScrollPane destScroller = new JScrollPane(destClasses); + destPanel.add(destScroller); + + JButton autoMatchButton = new JButton("AutoMatch"); + autoMatchButton.addActionListener(event -> autoMatch()); + destPanel.add(autoMatchButton); + + // init source panels + DefaultSyntaxKit.initKit(); + sourceReader = new CodeReader(); + destReader = new CodeReader(); + + // init all the splits + JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane( + sourceReader)); + splitLeft.setResizeWeight(0); // let the right side take all the slack + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(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()); + + sourceClassLabel = new JLabel(); + sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT); + destClassLabel = new JLabel(); + destClassLabel.setHorizontalAlignment(SwingConstants.LEFT); + + matchButton = new JButton(); + + advanceCheck = new JCheckBox("Advance to next likely match"); + advanceCheck.addActionListener(event -> { + if (advanceCheck.isSelected()) { + advance(); + } + }); + + bottomPanel.add(sourceClassLabel); + bottomPanel.add(matchButton); + bottomPanel.add(destClassLabel); + bottomPanel.add(advanceCheck); + pane.add(bottomPanel, BorderLayout.SOUTH); + + // show the frame + pane.doLayout(); + frame.setSize(1024, 576); + frame.setMinimumSize(new Dimension(640, 480)); + frame.setVisible(true); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + // init state + updateDestMappings(); + setSourceType(SourceType.getDefault()); + updateMatchButton(); + saveListener = null; + } + + public void setSaveListener(SaveListener val) { + saveListener = val; + } + + private void updateDestMappings() { + try { + Mappings newMappings = MappingsConverter.newMappings(classMatches, + sourceDeobfuscator.getMappings(), sourceDeobfuscator, destDeobfuscator + ); + + // look for dropped mappings + MappingsChecker checker = new MappingsChecker(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 + )); + + destDeobfuscator.setMappings(newMappings); + } catch (MappingConflict ex) { + System.out.println(ex.getMessage()); + ex.printStackTrace(); + return; + } + } + + protected void setSourceType(SourceType val) { + + // show the source classes + sourceType = val; + sourceClasses.setClasses(deobfuscateClasses(sourceType.getSourceClasses(classMatches), sourceDeobfuscator)); + + // update counts + for (SourceType sourceType : SourceType.values()) { + sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", + sourceType.name(), + sourceType.getSourceClasses(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 (advanceCheck.isSelected()) { + onGetDestClasses = this::pickBestDestClass; + } + + setSourceClass(classEntry, onGetDestClasses); + } + + protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) { + + // update the current source class + sourceClass = classEntry; + sourceClassLabel.setText(sourceClass != null ? sourceClass.getName() : ""); + + if (sourceClass != null) { + + // show the dest class(es) + ClassMatch match = classMatches.getMatchBySource(sourceDeobfuscator.obfuscateEntry(sourceClass)); + assert (match != null); + if (match.destClasses.isEmpty()) { + + destClasses.setClasses(null); + + // run in a separate thread to keep ui responsive + new Thread(() -> + { + destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator)); + destClasses.expandAll(); + + if (onGetDestClasses != null) { + onGetDestClasses.run(); + } + }).start(); + + } else { + + destClasses.setClasses(deobfuscateClasses(match.destClasses, destDeobfuscator)); + destClasses.expandAll(); + + if (onGetDestClasses != null) { + onGetDestClasses.run(); + } + } + } + + setDestClass(null); + sourceReader.decompileClass( + sourceClass, sourceDeobfuscator, () -> sourceReader.navigateToClassDeclaration(sourceClass)); + + updateMatchButton(); + } + + private Collection getLikelyMatches(ClassEntry sourceClass) { + + ClassEntry obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass); + + // set up identifiers + ClassNamer namer = new ClassNamer(classMatches.getUniqueMatches()); + ClassIdentifier sourceIdentifier = new ClassIdentifier( + sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(), + namer.getSourceNamer(), true + ); + ClassIdentifier destIdentifier = new ClassIdentifier( + destDeobfuscator.getJar(), 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 : 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)); - } + try { + + // rank all the unmatched dest classes against the source class + ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); + List scoredDestClasses = Lists.newArrayList(); + for (ClassEntry unmatchedDestClass : 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 (top10Matches.isSelected() && scoredDestClasses.size() > 10) { - scoredDestClasses.sort((a, b) -> - { - ScoredClassEntry sa = (ScoredClassEntry) a; - ScoredClassEntry sb = (ScoredClassEntry) b; - return -Float.compare(sa.getScore(), sb.getScore()); - }); - scoredDestClasses = scoredDestClasses.subList(0, 10); - } + if (top10Matches.isSelected() && scoredDestClasses.size() > 10) { + scoredDestClasses.sort((a, b) -> + { + ScoredClassEntry sa = (ScoredClassEntry) a; + ScoredClassEntry sb = (ScoredClassEntry) b; + return -Float.compare(sa.getScore(), sb.getScore()); + }); + scoredDestClasses = scoredDestClasses.subList(0, 10); + } - return scoredDestClasses; + return scoredDestClasses; - } catch (ClassNotFoundException ex) { - throw new Error("Unable to find class " + ex.getMessage()); - } - } + } catch (ClassNotFoundException ex) { + throw new Error("Unable to find class " + ex.getMessage()); + } + } - protected void setDestClass(ClassEntry classEntry) { + protected void setDestClass(ClassEntry classEntry) { - // update the current source class - destClass = classEntry; - destClassLabel.setText(destClass != null ? destClass.getName() : ""); - - destReader.decompileClass(destClass, destDeobfuscator, () -> destReader.navigateToClassDeclaration(destClass)); - - updateMatchButton(); - } - - private void updateMatchButton() { - - ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); - ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); - - BiMap uniqueMatches = classMatches.getUniqueMatches(); - boolean twoSelected = sourceClass != null && destClass != null; - boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); - boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest); - - GuiTricks.deactivateButton(matchButton); - if (twoSelected) { - if (isMatched) { - GuiTricks.activateButton(matchButton, "Unmatch", event -> onUnmatchClick()); - } else if (canMatch) { - GuiTricks.activateButton(matchButton, "Match", event -> onMatchClick()); - } - } - } - - private void onMatchClick() { - // precondition: source and dest classes are set correctly - - ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); - ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); - - // remove the classes from their match - classMatches.removeSource(obfSource); - classMatches.removeDest(obfDest); - - // add them as matched classes - classMatches.add(new ClassMatch(obfSource, obfDest)); - - ClassEntry nextClass = null; - if (advanceCheck.isSelected()) { - nextClass = sourceClasses.getNextClass(sourceClass); - } - - save(); - updateMatches(); - - if (nextClass != null) { - advance(nextClass); - } - } - - private void onUnmatchClick() { - // precondition: source and dest classes are set to a unique match - - ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); - - // remove the source to break the match, then add the source back as unmatched - classMatches.removeSource(obfSource); - classMatches.add(new ClassMatch(obfSource, null)); - - save(); - updateMatches(); - } - - private void updateMatches() { - updateDestMappings(); - setDestClass(null); - destClasses.setClasses(null); - updateMatchButton(); - - // remember where we were in the source tree - String packageName = sourceClasses.getSelectedPackage(); - - setSourceType(sourceType); - - sourceClasses.expandPackage(packageName); - } - - private void save() { - if (saveListener != null) { - saveListener.save(classMatches); - } - } - - private void autoMatch() { - - System.out.println("Automatching..."); - - // compute a new matching - ClassMatching matching = MappingsConverter.computeMatching( - sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(), - destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(), - classMatches.getUniqueMatches() - ); - ClassMatches newMatches = new ClassMatches(matching.matches()); - System.out.println(String.format("Automatch found %d new matches", - newMatches.getUniqueMatches().size() - classMatches.getUniqueMatches().size() - )); - - // update the current matches - 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 = sourceClasses.getSelectedClass(); - if (sourceClass != null) { - sourceClass = sourceClasses.getNextClass(sourceClass); - } else { - sourceClass = sourceClasses.getFirstClass(); - } - } - - // set the source class - setSourceClass(sourceClass, this::pickBestDestClass); - sourceClasses.setSelectionClass(sourceClass); - } - - private void pickBestDestClass() { - - // then, pick the best dest class - ClassEntry firstClass = null; - ScoredClassEntry bestDestClass = null; - for (ClassSelectorPackageNode packageNode : destClasses.packageNodes()) { - for (ClassSelectorClassNode classNode : 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); - destClasses.setSelectionClass(destClass); - } - - private void toggleTop10Matches() { - if (sourceClass != null) { - destClasses.clearSelection(); - destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator)); - destClasses.expandAll(); - } - } + // update the current source class + destClass = classEntry; + destClassLabel.setText(destClass != null ? destClass.getName() : ""); + + destReader.decompileClass(destClass, destDeobfuscator, () -> destReader.navigateToClassDeclaration(destClass)); + + updateMatchButton(); + } + + private void updateMatchButton() { + + ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); + ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); + + BiMap uniqueMatches = classMatches.getUniqueMatches(); + boolean twoSelected = sourceClass != null && destClass != null; + boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); + boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest); + + GuiTricks.deactivateButton(matchButton); + if (twoSelected) { + if (isMatched) { + GuiTricks.activateButton(matchButton, "Unmatch", event -> onUnmatchClick()); + } else if (canMatch) { + GuiTricks.activateButton(matchButton, "Match", event -> onMatchClick()); + } + } + } + + private void onMatchClick() { + // precondition: source and dest classes are set correctly + + ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); + ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); + + // remove the classes from their match + classMatches.removeSource(obfSource); + classMatches.removeDest(obfDest); + + // add them as matched classes + classMatches.add(new ClassMatch(obfSource, obfDest)); + + ClassEntry nextClass = null; + if (advanceCheck.isSelected()) { + nextClass = sourceClasses.getNextClass(sourceClass); + } + + save(); + updateMatches(); + + if (nextClass != null) { + advance(nextClass); + } + } + + private void onUnmatchClick() { + // precondition: source and dest classes are set to a unique match + + ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); + + // remove the source to break the match, then add the source back as unmatched + classMatches.removeSource(obfSource); + classMatches.add(new ClassMatch(obfSource, null)); + + save(); + updateMatches(); + } + + private void updateMatches() { + updateDestMappings(); + setDestClass(null); + destClasses.setClasses(null); + updateMatchButton(); + + // remember where we were in the source tree + String packageName = sourceClasses.getSelectedPackage(); + + setSourceType(sourceType); + + sourceClasses.expandPackage(packageName); + } + + private void save() { + if (saveListener != null) { + saveListener.save(classMatches); + } + } + + private void autoMatch() { + + System.out.println("Automatching..."); + + // compute a new matching + ClassMatching matching = MappingsConverter.computeMatching( + sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(), + destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(), + classMatches.getUniqueMatches() + ); + ClassMatches newMatches = new ClassMatches(matching.matches()); + System.out.println(String.format("Automatch found %d new matches", + newMatches.getUniqueMatches().size() - classMatches.getUniqueMatches().size() + )); + + // update the current matches + 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 = sourceClasses.getSelectedClass(); + if (sourceClass != null) { + sourceClass = sourceClasses.getNextClass(sourceClass); + } else { + sourceClass = sourceClasses.getFirstClass(); + } + } + + // set the source class + setSourceClass(sourceClass, this::pickBestDestClass); + sourceClasses.setSelectionClass(sourceClass); + } + + private void pickBestDestClass() { + + // then, pick the best dest class + ClassEntry firstClass = null; + ScoredClassEntry bestDestClass = null; + for (ClassSelectorPackageNode packageNode : destClasses.packageNodes()) { + for (ClassSelectorClassNode classNode : 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); + destClasses.setSelectionClass(destClass); + } + + private void toggleTop10Matches() { + if (sourceClass != null) { + destClasses.clearSelection(); + destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator)); + destClasses.expandAll(); + } + } + + 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 static SourceType getDefault() { + return values()[0]; + } + + 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 interface SaveListener { + void save(ClassMatches matches); + } } diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java index 8ece0a0..f7d7703 100644 --- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.google.common.collect.ArrayListMultimap; @@ -29,505 +30,470 @@ import java.util.*; public class ClassSelector extends JTree { - public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getName); - private DefaultMutableTreeNode rootNodes; - - public interface ClassSelectionListener { - void onSelectClass(ClassEntry classEntry); - } - - public interface RenameSelectionListener { - void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node); - } - - private ClassSelectionListener selectionListener; - private RenameSelectionListener renameSelectionListener; - private Comparator comparator; - - public ClassSelector(Gui gui, Comparator comparator, boolean isRenamable) { - this.comparator = comparator; - - // configure the tree control - setEditable(gui != null); - setRootVisible(false); - setShowsRootHandles(false); - setModel(null); - - // hook events - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (selectionListener != null && event.getClickCount() == 2) { - // get the selected node - TreePath path = getSelectionPath(); - if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { - ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); - selectionListener.onSelectClass(node.getClassEntry()); - } - } - } - }); - - if (gui != null) - { - final JTree tree = this; - - final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, - (DefaultTreeCellRenderer) tree.getCellRenderer()) - { - @Override public boolean isCellEditable(EventObject event) - { - return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); - } - }; - this.setCellEditor(editor); - editor.addCellEditorListener(new CellEditorListener() - { - @Override public void editingStopped(ChangeEvent e) - { - String data = editor.getCellEditorValue().toString(); - TreePath path = getSelectionPath(); - - Object realPath = path.getLastPathComponent(); - if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) - { - DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; - TreeNode parentNode = node.getParent(); - if (parentNode == null) - return; - boolean allowEdit = true; - for (int i = 0; i < parentNode.getChildCount(); i++) - { - TreeNode childNode = parentNode.getChildAt(i); - if (childNode != null && childNode.toString().equals(data) && childNode != node) - { - allowEdit = false; - break; - } - } - if (allowEdit && renameSelectionListener != null) - { - Object prevData = node.getUserObject(); - Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry)prevData).getPackageName() + "/" + data) : data; - try - { - renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); - node.setUserObject(objectData); // Make sure that it's modified - } catch (IllegalNameException ex) - { - JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, - JOptionPane.ERROR_MESSAGE, null, new String[] {"Ok"}, "OK"); - editor.cancelCellEditing(); - } - } - else - editor.cancelCellEditing(); - } - - } - - @Override public void editingCanceled(ChangeEvent e) - { - // NOP - } - }); - } - // init defaults - this.selectionListener = null; - this.renameSelectionListener = null; - } - - public boolean isDuplicate(Object[] nodes, String data) - { - int count = 0; - - for (Object node : nodes) - { - if (node.toString().equals(data)) - { - count++; - if (count == 2) - return true; - } - } - return false; - } - - public void setSelectionListener(ClassSelectionListener val) { - this.selectionListener = val; - } - - public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) - { - this.renameSelectionListener = renameSelectionListener; - } - - public void setClasses(Collection classEntries) { - String state = getExpansionState(this, 0); - if (classEntries == null) { - setModel(null); - return; - } - - // build the package names - Map packages = Maps.newHashMap(); - for (ClassEntry classEntry : classEntries) { - packages.put(classEntry.getPackageName(), null); - } - - // sort the packages - List sortedPackageNames = Lists.newArrayList(packages.keySet()); - sortedPackageNames.sort((a, b) -> - { - // I can never keep this rule straight when writing these damn things... - // a < b => -1, a == b => 0, a > b => +1 - - if (b == null || a == null) - { - return 0; - } - - String[] aparts = a.split("/"); - String[] bparts = b.split("/"); - for (int i = 0; true; i++) - { - if (i >= aparts.length) - { - return -1; - } - else if (i >= bparts.length) - { - return 1; - } - - int result = aparts[i].compareTo(bparts[i]); - if (result != 0) - { - return result; - } - } - }); - - // create the rootNodes node and the package nodes - rootNodes = new DefaultMutableTreeNode(); - for (String packageName : sortedPackageNames) { - ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); - packages.put(packageName, node); - rootNodes.add(node); - } - - // put the classes into packages - Multimap packagedClassEntries = ArrayListMultimap.create(); - for (ClassEntry classEntry : classEntries) { - packagedClassEntries.put(classEntry.getPackageName(), classEntry); - } - - // build the class nodes - for (String packageName : packagedClassEntries.keySet()) { - // sort the class entries - List classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); - classEntriesInPackage.sort(this.comparator); - - // create the nodes in order - for (ClassEntry classEntry : classEntriesInPackage) { - ClassSelectorPackageNode node = packages.get(packageName); - node.add(new ClassSelectorClassNode(classEntry)); - } - } - - // finally, update the tree control - setModel(new DefaultTreeModel(rootNodes)); - - 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(); - if (count1 <= count2) { - return false; - } - while (count1 != count2) { - path1 = path1.getParentPath(); - count1--; - } - return path1.equals(path2); - } - - public String getExpansionState(JTree tree, int row) { - TreePath rowPath = tree.getPathForRow(row); - StringBuilder buf = new StringBuilder(); - int rowCount = tree.getRowCount(); - for (int i = row; i < rowCount; i++) { - TreePath path = tree.getPathForRow(i); - if (i == row || isDescendant(path, rowPath)) { - if (tree.isExpanded(path)) { - buf.append(",").append(String.valueOf(i - row)); - } - } else { - break; - } - } - return buf.toString(); - } - - public void restoreExpanstionState(JTree tree, int row, String expansionState) { - StringTokenizer stok = new StringTokenizer(expansionState, ","); - while (stok.hasMoreTokens()) { - int token = row + Integer.parseInt(stok.nextToken()); - tree.expandRow(token); - } - } - - public List 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 List 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 ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) - { - ClassSelectorPackageNode packageNode = getPackageNode(entry); - - if (selector != null && packageNode == null && selector.getPackageNode(entry) != null) - return selector.getPackageNode(entry); - return packageNode; - } - - 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})); - } - } - } - } - - public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) - { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - - if (packageNode == null) - return; - - for (int i = 0; i < packageNode.getChildCount(); i++) - { - DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); - if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) - { - model.removeNodeFromParent(childNode); - break; - } - } - } - - public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) - { - if (packageNode != null && packageNode.getChildCount() == 0) - ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); - } - - public void moveClassTree(ClassEntry oldClassEntry, ClassEntry newClassEntry, ClassSelector otherSelector) - { - if (otherSelector == null) - removeNode(getPackageNode(oldClassEntry), oldClassEntry); - insertNode(getOrCreate(newClassEntry), newClassEntry); - } - - public ClassSelectorPackageNode getOrCreate(ClassEntry entry) - { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - ClassSelectorPackageNode newPackageNode = getPackageNode(entry); - if (newPackageNode == null) - { - newPackageNode = new ClassSelectorPackageNode(entry.getPackageName()); - model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode)); - } - return newPackageNode; - } - - public void insertNode(ClassSelectorPackageNode packageNode, ClassEntry entry) - { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - ClassSelectorClassNode classNode = new ClassSelectorClassNode(entry); - model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); - } - - public void reload() - { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - model.reload(sort(rootNodes)); - } - - private DefaultMutableTreeNode sort(DefaultMutableTreeNode node) { - - for(int i = 0; i < node.getChildCount() - 1; i++) { - DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); - if (child == null) - continue; - String nt = child.toString(); - - for(int j = i + 1; j <= node.getChildCount() - 1; j++) { - DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); - if (prevNode == null || prevNode.getUserObject() == null) - continue; - String np = prevNode.getUserObject().toString(); - - if(nt.compareToIgnoreCase(np) > 0) { - node.insert(child, j); - node.insert(prevNode, i); - } - } - if(child.getChildCount() > 0) { - sort(child); - } - } - - for(int i = 0; i < node.getChildCount() - 1; i++) { - DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); - for(int j = i + 1; j <= node.getChildCount() - 1; j++) { - DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); - - if(!prevNode.isLeaf() && child.isLeaf()) { - node.insert(child, j); - node.insert(prevNode, i); - } - } - } - - return node; - } - - private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) - { - List classNodes = classNodes(newPackageNode); - classNodes.add(classNode); - classNodes.sort(Comparator.comparing(ClassSelectorClassNode::toString)); - for (int i = 0; i < classNodes.size(); i++) - if (classNodes.get(i) == classNode) - return i; - - return 0; - } - - private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) - { - List packageNodes = packageNodes(); - if (!packageNodes.contains(newPackageNode)) - { - packageNodes.add(newPackageNode); - packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString)); - } - - for (int i = 0; i < packageNodes.size(); i++) - if (packageNodes.get(i) == newPackageNode) - return i; - - return 0; - } + public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getName); + private DefaultMutableTreeNode rootNodes; + private ClassSelectionListener selectionListener; + private RenameSelectionListener renameSelectionListener; + private Comparator comparator; + public ClassSelector(Gui gui, Comparator comparator, boolean isRenamable) { + this.comparator = comparator; + + // configure the tree control + setEditable(gui != null); + setRootVisible(false); + setShowsRootHandles(false); + setModel(null); + + // hook events + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (selectionListener != null && event.getClickCount() == 2) { + // get the selected node + TreePath path = getSelectionPath(); + if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { + ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); + selectionListener.onSelectClass(node.getClassEntry()); + } + } + } + }); + + if (gui != null) { + final JTree tree = this; + + final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, + (DefaultTreeCellRenderer) tree.getCellRenderer()) { + @Override + public boolean isCellEditable(EventObject event) { + return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); + } + }; + this.setCellEditor(editor); + editor.addCellEditorListener(new CellEditorListener() { + @Override + public void editingStopped(ChangeEvent e) { + String data = editor.getCellEditorValue().toString(); + TreePath path = getSelectionPath(); + + Object realPath = path.getLastPathComponent(); + if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; + TreeNode parentNode = node.getParent(); + if (parentNode == null) + return; + boolean allowEdit = true; + for (int i = 0; i < parentNode.getChildCount(); i++) { + TreeNode childNode = parentNode.getChildAt(i); + if (childNode != null && childNode.toString().equals(data) && childNode != node) { + allowEdit = false; + break; + } + } + if (allowEdit && renameSelectionListener != null) { + Object prevData = node.getUserObject(); + Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data; + try { + renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); + node.setUserObject(objectData); // Make sure that it's modified + } catch (IllegalNameException ex) { + JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, + JOptionPane.ERROR_MESSAGE, null, new String[] { "Ok" }, "OK"); + editor.cancelCellEditing(); + } + } else + editor.cancelCellEditing(); + } + + } + + @Override + public void editingCanceled(ChangeEvent e) { + // NOP + } + }); + } + // init defaults + this.selectionListener = null; + this.renameSelectionListener = null; + } + + public boolean isDuplicate(Object[] nodes, String data) { + int count = 0; + + for (Object node : nodes) { + if (node.toString().equals(data)) { + count++; + if (count == 2) + return true; + } + } + return false; + } + + public void setSelectionListener(ClassSelectionListener val) { + this.selectionListener = val; + } + + public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) { + this.renameSelectionListener = renameSelectionListener; + } + + public void setClasses(Collection classEntries) { + String state = getExpansionState(this, 0); + if (classEntries == null) { + setModel(null); + return; + } + + // build the package names + Map packages = Maps.newHashMap(); + for (ClassEntry classEntry : classEntries) { + packages.put(classEntry.getPackageName(), null); + } + + // sort the packages + List sortedPackageNames = Lists.newArrayList(packages.keySet()); + sortedPackageNames.sort((a, b) -> + { + // I can never keep this rule straight when writing these damn things... + // a < b => -1, a == b => 0, a > b => +1 + + if (b == null || a == null) { + return 0; + } + + String[] aparts = a.split("/"); + String[] bparts = b.split("/"); + for (int i = 0; true; i++) { + if (i >= aparts.length) { + return -1; + } else if (i >= bparts.length) { + return 1; + } + + int result = aparts[i].compareTo(bparts[i]); + if (result != 0) { + return result; + } + } + }); + + // create the rootNodes node and the package nodes + rootNodes = new DefaultMutableTreeNode(); + for (String packageName : sortedPackageNames) { + ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); + packages.put(packageName, node); + rootNodes.add(node); + } + + // put the classes into packages + Multimap packagedClassEntries = ArrayListMultimap.create(); + for (ClassEntry classEntry : classEntries) { + packagedClassEntries.put(classEntry.getPackageName(), classEntry); + } + + // build the class nodes + for (String packageName : packagedClassEntries.keySet()) { + // sort the class entries + List classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); + classEntriesInPackage.sort(this.comparator); + + // create the nodes in order + for (ClassEntry classEntry : classEntriesInPackage) { + ClassSelectorPackageNode node = packages.get(packageName); + node.add(new ClassSelectorClassNode(classEntry)); + } + } + + // finally, update the tree control + setModel(new DefaultTreeModel(rootNodes)); + + 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(); + if (count1 <= count2) { + return false; + } + while (count1 != count2) { + path1 = path1.getParentPath(); + count1--; + } + return path1.equals(path2); + } + + public String getExpansionState(JTree tree, int row) { + TreePath rowPath = tree.getPathForRow(row); + StringBuilder buf = new StringBuilder(); + int rowCount = tree.getRowCount(); + for (int i = row; i < rowCount; i++) { + TreePath path = tree.getPathForRow(i); + if (i == row || isDescendant(path, rowPath)) { + if (tree.isExpanded(path)) { + buf.append(",").append((i - row)); + } + } else { + break; + } + } + return buf.toString(); + } + + public void restoreExpanstionState(JTree tree, int row, String expansionState) { + StringTokenizer stok = new StringTokenizer(expansionState, ","); + while (stok.hasMoreTokens()) { + int token = row + Integer.parseInt(stok.nextToken()); + tree.expandRow(token); + } + } + + public List 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 List 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 ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) { + ClassSelectorPackageNode packageNode = getPackageNode(entry); + + if (selector != null && packageNode == null && selector.getPackageNode(entry) != null) + return selector.getPackageNode(entry); + return packageNode; + } + + 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 })); + } + } + } + } + + public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + + if (packageNode == null) + return; + + for (int i = 0; i < packageNode.getChildCount(); i++) { + DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); + if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) { + model.removeNodeFromParent(childNode); + break; + } + } + } + + public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) { + if (packageNode != null && packageNode.getChildCount() == 0) + ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); + } + + public void moveClassTree(ClassEntry oldClassEntry, ClassEntry newClassEntry, ClassSelector otherSelector) { + if (otherSelector == null) + removeNode(getPackageNode(oldClassEntry), oldClassEntry); + insertNode(getOrCreate(newClassEntry), newClassEntry); + } + + public ClassSelectorPackageNode getOrCreate(ClassEntry entry) { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + ClassSelectorPackageNode newPackageNode = getPackageNode(entry); + if (newPackageNode == null) { + newPackageNode = new ClassSelectorPackageNode(entry.getPackageName()); + model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode)); + } + return newPackageNode; + } + + public void insertNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + ClassSelectorClassNode classNode = new ClassSelectorClassNode(entry); + model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); + } + + public void reload() { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + model.reload(sort(rootNodes)); + } + + private DefaultMutableTreeNode sort(DefaultMutableTreeNode node) { + + for (int i = 0; i < node.getChildCount() - 1; i++) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); + if (child == null) + continue; + String nt = child.toString(); + + for (int j = i + 1; j <= node.getChildCount() - 1; j++) { + DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); + if (prevNode == null || prevNode.getUserObject() == null) + continue; + String np = prevNode.getUserObject().toString(); + + if (nt.compareToIgnoreCase(np) > 0) { + node.insert(child, j); + node.insert(prevNode, i); + } + } + if (child.getChildCount() > 0) { + sort(child); + } + } + + for (int i = 0; i < node.getChildCount() - 1; i++) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); + for (int j = i + 1; j <= node.getChildCount() - 1; j++) { + DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); + + if (!prevNode.isLeaf() && child.isLeaf()) { + node.insert(child, j); + node.insert(prevNode, i); + } + } + } + + return node; + } + + private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) { + List classNodes = classNodes(newPackageNode); + classNodes.add(classNode); + classNodes.sort(Comparator.comparing(ClassSelectorClassNode::toString)); + for (int i = 0; i < classNodes.size(); i++) + if (classNodes.get(i) == classNode) + return i; + + return 0; + } + + private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) { + List packageNodes = packageNodes(); + if (!packageNodes.contains(newPackageNode)) { + packageNodes.add(newPackageNode); + packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString)); + } + + for (int i = 0; i < packageNodes.size(); i++) + if (packageNodes.get(i) == newPackageNode) + return i; + + return 0; + } + + public interface ClassSelectionListener { + void onSelectClass(ClassEntry classEntry); + } + + public interface RenameSelectionListener { + void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node); + } } diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java index 8225d8f..2e235dc 100644 --- a/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java @@ -8,20 +8,10 @@ * 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.text.BadLocationException; -import javax.swing.text.Highlighter.HighlightPainter; - import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.SourceIndex; @@ -31,180 +21,184 @@ import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Entry; import de.sciss.syntaxpane.DefaultSyntaxKit; +import javax.swing.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter.HighlightPainter; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; public class CodeReader extends JEditorPane { - private static final long serialVersionUID = 3673180950485748810L; - - private static final Object lock = new Object(); - - public interface SelectionListener { - void onSelect(EntryReference reference); - } - - private SelectionHighlightPainter selectionHighlightPainter; - private SourceIndex sourceIndex; - private SelectionListener selectionListener; - - public CodeReader() { - - setEditable(false); - setContentType("text/java"); - - // turn off token highlighting (it's wrong most of the time anyway...) - DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit(); - kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker"); - - // hook events - addCaretListener(event -> - { - if (selectionListener != null && sourceIndex != null) { - Token token = sourceIndex.getReferenceToken(event.getDot()); - if (token != null) { - selectionListener.onSelect(sourceIndex.getDeobfReference(token)); - } else { - selectionListener.onSelect(null); - } - } - }); - - selectionHighlightPainter = new SelectionHighlightPainter(); - } - - public void setSelectionListener(SelectionListener val) { - selectionListener = val; - } - - public void setCode(String code) { - // sadly, the java lexer is not thread safe, so we have to serialize all these calls - synchronized (lock) { - setText(code); - } - } - - public SourceIndex getSourceIndex() { - return sourceIndex; - } - - public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) { - decompileClass(classEntry, deobfuscator, null); - } - - public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) { - decompileClass(classEntry, deobfuscator, null, callback); - } - - public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) { - - if (classEntry == null) { - setCode(null); - return; - } - - setCode("(decompiling...)"); - - // run decompilation in a separate thread to keep ui responsive - new Thread(() -> - { - - // decompile it - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName()); - String source = deobfuscator.getSource(sourceTree); - setCode(source); - sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); - - if (callback != null) { - callback.run(); - } - }).start(); - } - - public void navigateToClassDeclaration(ClassEntry classEntry) { - - // navigate to the class declaration - Token token = sourceIndex.getDeclarationToken(classEntry); - if (token == null) { - // couldn't find the class declaration token, might be an anonymous class - // look for any declaration in that class instead - for (Entry entry : sourceIndex.declarations()) { - if (entry.getClassEntry().equals(classEntry)) { - token = sourceIndex.getDeclarationToken(entry); - break; - } - } - } - - if (token != null) { - 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, 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(() -> editor.scrollRectToVisible(show)); - } catch (BadLocationException ex) { - throw new Error(ex); - } - - // highlight the token momentarily - final Timer timer = new Timer(200, new ActionListener() { - private int counter = 0; - private Object highlight = null; - - @Override - public void actionPerformed(ActionEvent event) { - if (counter % 2 == 0) { - try { - highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); - } catch (BadLocationException ex) { - // don't care - } - } else if (highlight != null) { - editor.getHighlighter().removeHighlight(highlight); - } - - if (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(); - } + private static final long serialVersionUID = 3673180950485748810L; + + private static final Object lock = new Object(); + private SelectionHighlightPainter selectionHighlightPainter; + private SourceIndex sourceIndex; + private SelectionListener selectionListener; + public CodeReader() { + + setEditable(false); + setContentType("text/java"); + + // turn off token highlighting (it's wrong most of the time anyway...) + DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit(); + kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker"); + + // hook events + addCaretListener(event -> + { + if (selectionListener != null && sourceIndex != null) { + Token token = sourceIndex.getReferenceToken(event.getDot()); + if (token != null) { + selectionListener.onSelect(sourceIndex.getDeobfReference(token)); + } else { + selectionListener.onSelect(null); + } + } + }); + + selectionHighlightPainter = new 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(() -> editor.scrollRectToVisible(show)); + } catch (BadLocationException ex) { + throw new Error(ex); + } + + // highlight the token momentarily + final Timer timer = new Timer(200, new ActionListener() { + private int counter = 0; + private Object highlight = null; + + @Override + public void actionPerformed(ActionEvent event) { + if (counter % 2 == 0) { + try { + highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); + } catch (BadLocationException ex) { + // don't care + } + } else if (highlight != null) { + editor.getHighlighter().removeHighlight(highlight); + } + + if (counter++ > 6) { + Timer timer = (Timer) event.getSource(); + timer.stop(); + } + } + }); + timer.start(); + } + + public void setSelectionListener(SelectionListener val) { + selectionListener = val; + } + + public void setCode(String code) { + // sadly, the java lexer is not thread safe, so we have to serialize all these calls + synchronized (lock) { + setText(code); + } + } + + public SourceIndex getSourceIndex() { + return sourceIndex; + } + + public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) { + decompileClass(classEntry, deobfuscator, null); + } + + public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) { + decompileClass(classEntry, deobfuscator, null, callback); + } + + public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) { + + if (classEntry == null) { + setCode(null); + return; + } + + setCode("(decompiling...)"); + + // run decompilation in a separate thread to keep ui responsive + new Thread(() -> + { + + // decompile it + CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName()); + String source = deobfuscator.getSource(sourceTree); + setCode(source); + sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); + + if (callback != null) { + callback.run(); + } + }).start(); + } + + public void navigateToClassDeclaration(ClassEntry classEntry) { + + // navigate to the class declaration + Token token = sourceIndex.getDeclarationToken(classEntry); + if (token == null) { + // couldn't find the class declaration token, might be an anonymous class + // look for any declaration in that class instead + for (Entry entry : sourceIndex.declarations()) { + if (entry.getClassEntry().equals(classEntry)) { + token = sourceIndex.getDeclarationToken(entry); + break; + } + } + } + + if (token != null) { + 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, selectionHighlightPainter); + } + + 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(); + } + + public interface SelectionListener { + void onSelect(EntryReference reference); + } } diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index 7cb494f..9f8d6fc 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.google.common.collect.Lists; @@ -54,811 +55,790 @@ import java.util.function.Function; public class Gui { - private GuiController controller; - - private final PanelObf obfPanel; - private final PanelDeobf deobfPanel; - - private final MenuBar menuBar; - public final PopupMenuBar popupMenu; - - private JFrame frame; - private PanelEditor editor; - private JPanel classesPanel; - private JSplitPane splitClasses; - private PanelIdentifier infoPanel; - private ObfuscatedHighlightPainter obfuscatedHighlightPainter; - private DeobfuscatedHighlightPainter deobfuscatedHighlightPainter; - private OtherHighlightPainter otherHighlightPainter; - private SelectionHighlightPainter selectionHighlightPainter; - private JTree inheritanceTree; - private JTree implementationsTree; - private JTree callsTree; - private JList tokens; - private JTabbedPane tabs; - - // state - public EntryReference reference; - - public JFileChooser jarFileChooser; - public JFileChooser enigmaMappingsFileChooser; - - public JFileChooser exportSourceFileChooser; - public JFileChooser exportJarFileChooser; - - public Gui() { - - // init frame - this.frame = new JFrame(Constants.NAME); - final Container pane = this.frame.getContentPane(); - pane.setLayout(new BorderLayout()); - - if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { - // install a global exception handler to the event thread - CrashDialog.init(this.frame); - Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { - t.printStackTrace(System.err); - if (!ExceptionIgnorer.shouldIgnore(t)) { - CrashDialog.show(t); - } - }); - } - - this.controller = new GuiController(this); - - // init file choosers - this.jarFileChooser = new FileChooserFile(); - - - this.enigmaMappingsFileChooser = new FileChooserAny(); - this.exportSourceFileChooser = new FileChooserFolder(); - this.exportJarFileChooser = new FileChooserFile(); - - this.obfPanel = new PanelObf(this); - this.deobfPanel = new PanelDeobf(this); - - // set up classes panel (don't add the splitter yet) - splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); - splitClasses.setResizeWeight(0.3); - this.classesPanel = new JPanel(); - this.classesPanel.setLayout(new BorderLayout()); - this.classesPanel.setPreferredSize(new Dimension(250, 0)); - - // init info panel - infoPanel = new PanelIdentifier(this); - infoPanel.clearReference(); - - // init editor - DefaultSyntaxKit.initKit(); - obfuscatedHighlightPainter = new ObfuscatedHighlightPainter(); - deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter(); - otherHighlightPainter = new OtherHighlightPainter(); - selectionHighlightPainter = new SelectionHighlightPainter(); - this.editor = new PanelEditor(this); - JScrollPane sourceScroller = new JScrollPane(this.editor); - this.editor.setContentType("text/java"); - DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); - kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); - - // init editor popup menu - this.popupMenu = new PopupMenuBar(this); - this.editor.setComponentPopupMenu(this.popupMenu); - - // init inheritance panel - inheritanceTree = new JTree(); - inheritanceTree.setModel(null); - inheritanceTree.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() == 2) { - // get the selected node - TreePath path = inheritanceTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - if (node instanceof ClassInheritanceTreeNode) { - ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; - navigateTo(new ClassEntry(classNode.getObfClassName())); - } else if (node instanceof MethodInheritanceTreeNode) { - MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; - if (methodNode.isImplemented()) { - navigateTo(methodNode.getMethodEntry()); - } - } - } - } - }); - JPanel inheritancePanel = new JPanel(); - inheritancePanel.setLayout(new BorderLayout()); - inheritancePanel.add(new JScrollPane(inheritanceTree)); - - // init implementations panel - implementationsTree = new JTree(); - implementationsTree.setModel(null); - implementationsTree.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() == 2) { - // get the selected node - TreePath path = implementationsTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - if (node instanceof ClassImplementationsTreeNode) { - ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; - navigateTo(classNode.getClassEntry()); - } else if (node instanceof MethodImplementationsTreeNode) { - MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; - navigateTo(methodNode.getMethodEntry()); - } - } - } - }); - JPanel implementationsPanel = new JPanel(); - implementationsPanel.setLayout(new BorderLayout()); - implementationsPanel.add(new JScrollPane(implementationsTree)); - - // init call panel - callsTree = new JTree(); - callsTree.setModel(null); - callsTree.addMouseListener(new MouseAdapter() { - @SuppressWarnings("unchecked") - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() == 2) { - // get the selected node - TreePath path = callsTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - if (node instanceof ReferenceTreeNode) { - ReferenceTreeNode referenceNode = ((ReferenceTreeNode) node); - if (referenceNode.getReference() != null) { - navigateTo(referenceNode.getReference()); - } else { - navigateTo(referenceNode.getEntry()); - } - } - } - } - }); - tokens = new JList<>(); - tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); - tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - tokens.setLayoutOrientation(JList.VERTICAL); - tokens.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() == 2) { - Token selected = tokens.getSelectedValue(); - if (selected != null) { - showToken(selected); - } - } - } - }); - tokens.setPreferredSize(new Dimension(0, 200)); - tokens.setMinimumSize(new Dimension(0, 200)); - JSplitPane callPanel = new JSplitPane( - JSplitPane.VERTICAL_SPLIT, - true, - new JScrollPane(callsTree), - new JScrollPane(tokens) - ); - callPanel.setResizeWeight(1); // let the top side take all the slack - callPanel.resetToPreferredSizes(); - - // layout controls - JPanel centerPanel = new JPanel(); - centerPanel.setLayout(new BorderLayout()); - centerPanel.add(infoPanel, BorderLayout.NORTH); - centerPanel.add(sourceScroller, BorderLayout.CENTER); - tabs = new JTabbedPane(); - tabs.setPreferredSize(new Dimension(250, 0)); - tabs.addTab("Inheritance", inheritancePanel); - tabs.addTab("Implementations", implementationsPanel); - tabs.addTab("Call Graph", callPanel); - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, tabs); - splitRight.setResizeWeight(1); // let the left side take all the slack - splitRight.resetToPreferredSizes(); - JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); - splitCenter.setResizeWeight(0); // let the right side take all the slack - pane.add(splitCenter, BorderLayout.CENTER); - - // init menus - this.menuBar = new MenuBar(this); - this.frame.setJMenuBar(this.menuBar); - - // init state - onCloseJar(); - - this.frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent event) { - close(); - } - }); - - // show the frame - pane.doLayout(); - this.frame.setSize(1024, 576); - this.frame.setMinimumSize(new Dimension(640, 480)); - this.frame.setVisible(true); - this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - } - - public JFrame getFrame() { - return this.frame; - } - - public GuiController getController() { - return this.controller; - } - - public void onStartOpenJar() { - this.classesPanel.removeAll(); - JPanel panel = new JPanel(); - panel.setLayout(new FlowLayout()); - panel.add(new JLabel("Loading...")); - this.classesPanel.add(panel); - redraw(); - } - - public void onFinishOpenJar(String jarName) { - // update gui - this.frame.setTitle(Constants.NAME + " - " + jarName); - this.classesPanel.removeAll(); - this.classesPanel.add(splitClasses); - setSource(null); - - // update menu - this.menuBar.closeJarMenu.setEnabled(true); - this.menuBar.openEnigmaMappingsMenu.setEnabled(true); - this.menuBar.saveMappingsMenu.setEnabled(false); - this.menuBar.saveMappingEnigmaFileMenu.setEnabled(true); - this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(true); - this.menuBar.saveMappingsSrgMenu.setEnabled(true); - this.menuBar.closeMappingsMenu.setEnabled(true); - this.menuBar.exportSourceMenu.setEnabled(true); - this.menuBar.exportJarMenu.setEnabled(true); - - redraw(); - } - - public void onCloseJar() { - // update gui - this.frame.setTitle(Constants.NAME); - setObfClasses(null); - setDeobfClasses(null); - setSource(null); - this.classesPanel.removeAll(); - - // update menu - this.menuBar.closeJarMenu.setEnabled(false); - this.menuBar.openEnigmaMappingsMenu.setEnabled(false); - this.menuBar.saveMappingsMenu.setEnabled(false); - this.menuBar.saveMappingEnigmaFileMenu.setEnabled(false); - this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(false); - this.menuBar.saveMappingsSrgMenu.setEnabled(false); - this.menuBar.closeMappingsMenu.setEnabled(false); - this.menuBar.exportSourceMenu.setEnabled(false); - this.menuBar.exportJarMenu.setEnabled(false); - - redraw(); - } - - public void setObfClasses(Collection obfClasses) { - this.obfPanel.obfClasses.setClasses(obfClasses); - } - - public void setDeobfClasses(Collection deobfClasses) { - this.deobfPanel.deobfClasses.setClasses(deobfClasses); - } - - public void setMappingsFile(File file) { - this.enigmaMappingsFileChooser.setSelectedFile(file); - this.menuBar.saveMappingsMenu.setEnabled(file != null); - } - - public void setSource(String source) { - this.editor.getHighlighter().removeAllHighlights(); - this.editor.setText(source); - } - - public void showToken(final Token token) { - if (token == null) { - throw new IllegalArgumentException("Token cannot be null!"); - } - CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); - redraw(); - } - - public void showTokens(Collection tokens) { - Vector sortedTokens = new Vector<>(tokens); - Collections.sort(sortedTokens); - if (sortedTokens.size() > 1) { - // sort the tokens and update the tokens panel - this.tokens.setListData(sortedTokens); - this.tokens.setSelectedIndex(0); - } else { - this.tokens.setListData(new Vector<>()); - } - - // show the first token - showToken(sortedTokens.get(0)); - } - - public void setHighlightedTokens(Iterable obfuscatedTokens, Iterable deobfuscatedTokens, Iterable otherTokens) { - - // remove any old highlighters - this.editor.getHighlighter().removeAllHighlights(); - - // color things based on the index - if (obfuscatedTokens != null) { - setHighlightedTokens(obfuscatedTokens, obfuscatedHighlightPainter); - } - if (deobfuscatedTokens != null) { - setHighlightedTokens(deobfuscatedTokens, deobfuscatedHighlightPainter); - } - if (otherTokens != null) { - setHighlightedTokens(otherTokens, otherHighlightPainter); - } - - redraw(); - } - - private void setHighlightedTokens(Iterable tokens, Highlighter.HighlightPainter painter) { - for (Token token : tokens) { - try { - this.editor.getHighlighter().addHighlight(token.start, token.end, painter); - } catch (BadLocationException ex) { - throw new IllegalArgumentException(ex); - } - } - } - - private void showReference(EntryReference reference) { - if (reference == null) { - infoPanel.clearReference(); - return; - } - - this.reference = reference; - - infoPanel.removeAll(); - if (reference.entry instanceof ClassEntry) { - showClassEntry((ClassEntry) this.reference.entry); - } else if (this.reference.entry instanceof FieldEntry) { - showFieldEntry((FieldEntry) this.reference.entry); - } else if (this.reference.entry instanceof MethodEntry) { - showMethodEntry((MethodEntry) this.reference.entry); - } else if (this.reference.entry instanceof ConstructorEntry) { - showConstructorEntry((ConstructorEntry) this.reference.entry); - } else if (this.reference.entry instanceof ArgumentEntry) { - showArgumentEntry((ArgumentEntry) this.reference.entry); - } else if (this.reference.entry instanceof LocalVariableEntry) { - showLocalVariableEntry((LocalVariableEntry) this.reference.entry); - } else { - throw new Error("Unknown entry type: " + this.reference.entry.getClass().getName()); - } - - redraw(); - } - - private void showLocalVariableEntry(LocalVariableEntry entry) { - addNameValue(infoPanel, "Variable", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); - addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); - addNameValue(infoPanel, "Type", entry.getType().toString()); - } - - private void showClassEntry(ClassEntry entry) { - addNameValue(infoPanel, "Class", entry.getName()); - addModifierComboBox(infoPanel, "Modifier", entry); - } - - private void showFieldEntry(FieldEntry entry) { - addNameValue(infoPanel, "Field", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Type", entry.getType().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); - } - - private void showMethodEntry(MethodEntry entry) { - addNameValue(infoPanel, "Method", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Signature", entry.getSignature().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); - - } - - private void showConstructorEntry(ConstructorEntry entry) { - addNameValue(infoPanel, "Constructor", entry.getClassEntry().getName()); - if (!entry.isStatic()) { - addNameValue(infoPanel, "Signature", entry.getSignature().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); - } - } - - private void showArgumentEntry(ArgumentEntry entry) { - addNameValue(infoPanel, "Argument", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); - addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); - } - - private void addNameValue(JPanel container, String name, String value) { - JPanel panel = new JPanel(); - panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); - container.add(panel); - - JLabel label = new JLabel(name + ":", JLabel.RIGHT); - label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); - panel.add(label); - - panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT))); - } - - private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) - { - if (!getController().entryIsInJar(entry)) - return null; - JPanel panel = new JPanel(); - panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); - container.add(panel); - JLabel label = new JLabel(name + ":", JLabel.RIGHT); - label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); - panel.add(label); - JComboBox combo = new JComboBox<>(Mappings.EntryModifier.values()); - ((JLabel)combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); - combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); - combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); - combo.addItemListener(getController()::modifierChange); - panel.add(combo); - return combo; - } - - public void onCaretMove(int pos) { - - Token token = this.controller.getToken(pos); - boolean isToken = token != null; - - reference = this.controller.getDeobfReference(token); - boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; - boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; - boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry; - boolean isConstructorEntry = isToken && reference.entry instanceof ConstructorEntry; - boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); - boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); - - if (isToken) { - showReference(reference); - } else { - infoPanel.clearReference(); - } - - this.popupMenu.renameMenu.setEnabled(isRenameable); - this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); - this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); - this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); - this.popupMenu.openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); - this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); - this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); - - if (isToken && this.controller.entryHasDeobfuscatedName(reference.entry)) { - this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); - } else { - this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); - } - } - - public void navigateTo(Entry entry) { - if (!this.controller.entryIsInJar(entry)) { - // entry is not in the jar. Ignore it - return; - } - if (reference != null) { - this.controller.savePreviousReference(reference); - } - this.controller.openDeclaration(entry); - } - - private void navigateTo(EntryReference reference) { - if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) { - return; - } - if (this.reference != null) { - this.controller.savePreviousReference(this.reference); - } - this.controller.openReference(reference); - } - - public void startRename() { - - // init the text box - final JTextField text = new JTextField(); - text.setText(reference.getNamableName()); - text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); - text.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.VK_ENTER: - finishRename(text, true); - break; - - case KeyEvent.VK_ESCAPE: - finishRename(text, false); - break; - default: - break; - } - } - }); - - // find the label with the name and replace it with the text box - JPanel panel = (JPanel) infoPanel.getComponent(0); - panel.remove(panel.getComponentCount() - 1); - panel.add(text); - text.grabFocus(); - - int offset = text.getText().lastIndexOf('/') + 1; - // If it's a class and isn't in the default package, assume that it's deobfuscated. - if (reference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0) - text.select(offset, text.getText().length()); - else - text.selectAll(); - - redraw(); - } - - private void finishRename(JTextField text, boolean saveName) { - String newName = text.getText(); - if (saveName && newName != null && newName.length() > 0) { - try { - this.controller.rename(reference, newName); - } catch (IllegalNameException ex) { - text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); - text.setToolTipText(ex.getReason()); - Utils.showToolTipNow(text); - } - return; - } - - // abort the rename - JPanel panel = (JPanel) infoPanel.getComponent(0); - panel.remove(panel.getComponentCount() - 1); - panel.add(Utils.unboldLabel(new JLabel(reference.getNamableName(), JLabel.LEFT))); - - this.editor.grabFocus(); - - redraw(); - } - - public void showInheritance() { - - if (reference == null) { - return; - } - - inheritanceTree.setModel(null); - - if (reference.entry instanceof ClassEntry) { - // get the class inheritance - ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) reference.entry); - - // show the tree at the root - TreePath path = getPathToRoot(classNode); - inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - inheritanceTree.expandPath(path); - inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); - } else if (reference.entry instanceof MethodEntry) { - // get the method inheritance - MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) reference.entry); - - // show the tree at the root - TreePath path = getPathToRoot(classNode); - inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - inheritanceTree.expandPath(path); - inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); - } - - tabs.setSelectedIndex(0); - redraw(); - } - - public void showImplementations() { - - if (reference == null) { - return; - } - - implementationsTree.setModel(null); - - DefaultMutableTreeNode node = null; - - // get the class implementations - if (reference.entry instanceof ClassEntry) - node = this.controller.getClassImplementations((ClassEntry) reference.entry); - else // get the method implementations - if (reference.entry instanceof MethodEntry) - node = this.controller.getMethodImplementations((MethodEntry) reference.entry); - - if (node != null) { - // show the tree at the root - TreePath path = getPathToRoot(node); - implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - implementationsTree.expandPath(path); - implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); - } - - tabs.setSelectedIndex(1); - redraw(); - } - - public void showCalls() { - if (reference == null) { - return; - } - - if (reference.entry instanceof ClassEntry) { - // look for calls to the default constructor - // TODO: get a list of all the constructors and find calls to all of them - BehaviorReferenceTreeNode node = this.controller.getMethodReferences(new ConstructorEntry((ClassEntry) reference.entry, new Signature("()V"))); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof FieldEntry) { - FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof MethodEntry) { - BehaviorReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof ConstructorEntry) { - BehaviorReferenceTreeNode node = this.controller.getMethodReferences((ConstructorEntry) reference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } - - tabs.setSelectedIndex(2); - redraw(); - } - - public void toggleMapping() { - if (this.controller.entryHasDeobfuscatedName(reference.entry)) { - this.controller.removeMapping(reference); - } else { - this.controller.markAsDeobfuscated(reference); - } - } - - private TreePath getPathToRoot(TreeNode node) { - List nodes = Lists.newArrayList(); - TreeNode n = node; - do { - nodes.add(n); - n = n.getParent(); - } while (n != null); - Collections.reverse(nodes); - return new TreePath(nodes.toArray()); - } - - public void showDiscardDiag(Function callback, String... options) - { - int response = JOptionPane.showOptionDialog(this.frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE, null, options, options[2]); - callback.apply(response); - } - - public void saveMapping() throws IOException - { - if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) - this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile()); - } - - public void close() { - if (!this.controller.isDirty()) { - // everything is saved, we can exit safely - this.frame.dispose(); - System.exit(0); - } else { - // ask to save before closing - showDiscardDiag((response) -> { - if (response == JOptionPane.YES_OPTION) - { - try { - this.saveMapping(); - this.frame.dispose(); - - } catch (IOException ex) { - throw new Error(ex); - } - } - else if (response == JOptionPane.NO_OPTION) - this.frame.dispose(); - - return null; - }, "Save and exit", "Discard changes", "Cancel"); - } - } - - public void redraw() { - this.frame.validate(); - this.frame.repaint(); - } - - public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException - { - // package rename - if (data instanceof String) - { - for (int i = 0; i < node.getChildCount(); i++) - { - data = Descriptor.toJvmName((String) data); - DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); - ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); - ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); - this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getName()), dataChild.getName(), false, i + 1 == node.getChildCount()); - childNode.setUserObject(dataChild); - } - node.setUserObject(data); - // Ob package will never be modified, just reload deob view - this.deobfPanel.deobfClasses.reload(); - } - // class rename - else if (data instanceof ClassEntry) - this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getName()), ((ClassEntry) data).getName(), false, true); - } - - public void moveClassTree(EntryReference deobfReference, String newName) - { - String oldEntry = deobfReference.entry.getClassEntry().getPackageName(); - String newEntry = new ClassEntry(Descriptor.toJvmName(newName)).getPackageName(); - moveClassTree(deobfReference, newName, oldEntry == null, - newEntry == null); - } - - public void moveClassTree(EntryReference deobfReference, String newName, boolean isOldOb, boolean isNewOb) - { - ClassEntry oldEntry = deobfReference.entry.getClassEntry(); - ClassEntry newEntry = new ClassEntry(Descriptor.toJvmName(newName)); - - // Ob -> deob - if (isOldOb && !isNewOb) - { - this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, obfPanel.obfClasses); - ClassSelectorPackageNode packageNode = this.obfPanel.obfClasses.getPackageNode(oldEntry); - this.obfPanel.obfClasses.removeNode(packageNode, oldEntry); - this.obfPanel.obfClasses.removeNodeIfEmpty(packageNode); - this.deobfPanel.deobfClasses.reload(); - this.obfPanel.obfClasses.reload(); - } - // Deob -> ob - else if (isNewOb && !isOldOb) - { - this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, deobfPanel.deobfClasses); - ClassSelectorPackageNode packageNode = this.deobfPanel.deobfClasses.getPackageNode(oldEntry); - this.deobfPanel.deobfClasses.removeNode(packageNode, oldEntry); - this.deobfPanel.deobfClasses.removeNodeIfEmpty(packageNode); - this.deobfPanel.deobfClasses.reload(); - this.obfPanel.obfClasses.reload(); - } - // Local move - else if (isOldOb) - { - this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, null); - this.obfPanel.obfClasses.removeNodeIfEmpty(this.obfPanel.obfClasses.getPackageNode(oldEntry)); - this.obfPanel.obfClasses.reload(); - } - else - { - this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, null); - this.deobfPanel.deobfClasses.removeNodeIfEmpty(this.deobfPanel.deobfClasses.getPackageNode(oldEntry)); - this.deobfPanel.deobfClasses.reload(); - } - } + public final PopupMenuBar popupMenu; + private final PanelObf obfPanel; + private final PanelDeobf deobfPanel; + + private final MenuBar menuBar; + // state + public EntryReference reference; + public JFileChooser jarFileChooser; + public JFileChooser enigmaMappingsFileChooser; + public JFileChooser exportSourceFileChooser; + public JFileChooser exportJarFileChooser; + private GuiController controller; + private JFrame frame; + private PanelEditor editor; + private JPanel classesPanel; + private JSplitPane splitClasses; + private PanelIdentifier infoPanel; + private ObfuscatedHighlightPainter obfuscatedHighlightPainter; + private DeobfuscatedHighlightPainter deobfuscatedHighlightPainter; + private OtherHighlightPainter otherHighlightPainter; + private SelectionHighlightPainter selectionHighlightPainter; + private JTree inheritanceTree; + private JTree implementationsTree; + private JTree callsTree; + private JList tokens; + private JTabbedPane tabs; + + public Gui() { + + // init frame + this.frame = new JFrame(Constants.NAME); + final Container pane = this.frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { + // install a global exception handler to the event thread + CrashDialog.init(this.frame); + Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { + t.printStackTrace(System.err); + if (!ExceptionIgnorer.shouldIgnore(t)) { + CrashDialog.show(t); + } + }); + } + + this.controller = new GuiController(this); + + // init file choosers + this.jarFileChooser = new FileChooserFile(); + + this.enigmaMappingsFileChooser = new FileChooserAny(); + this.exportSourceFileChooser = new FileChooserFolder(); + this.exportJarFileChooser = new FileChooserFile(); + + this.obfPanel = new PanelObf(this); + this.deobfPanel = new PanelDeobf(this); + + // set up classes panel (don't add the splitter yet) + splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); + splitClasses.setResizeWeight(0.3); + this.classesPanel = new JPanel(); + this.classesPanel.setLayout(new BorderLayout()); + this.classesPanel.setPreferredSize(new Dimension(250, 0)); + + // init info panel + infoPanel = new PanelIdentifier(this); + infoPanel.clearReference(); + + // init editor + DefaultSyntaxKit.initKit(); + obfuscatedHighlightPainter = new ObfuscatedHighlightPainter(); + deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter(); + otherHighlightPainter = new OtherHighlightPainter(); + selectionHighlightPainter = new SelectionHighlightPainter(); + this.editor = new PanelEditor(this); + JScrollPane sourceScroller = new JScrollPane(this.editor); + this.editor.setContentType("text/java"); + DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); + kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); + + // init editor popup menu + this.popupMenu = new PopupMenuBar(this); + this.editor.setComponentPopupMenu(this.popupMenu); + + // init inheritance panel + inheritanceTree = new JTree(); + inheritanceTree.setModel(null); + inheritanceTree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + // get the selected node + TreePath path = inheritanceTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ClassInheritanceTreeNode) { + ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; + navigateTo(new ClassEntry(classNode.getObfClassName())); + } else if (node instanceof MethodInheritanceTreeNode) { + MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; + if (methodNode.isImplemented()) { + navigateTo(methodNode.getMethodEntry()); + } + } + } + } + }); + JPanel inheritancePanel = new JPanel(); + inheritancePanel.setLayout(new BorderLayout()); + inheritancePanel.add(new JScrollPane(inheritanceTree)); + + // init implementations panel + implementationsTree = new JTree(); + implementationsTree.setModel(null); + implementationsTree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + // get the selected node + TreePath path = implementationsTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ClassImplementationsTreeNode) { + ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; + navigateTo(classNode.getClassEntry()); + } else if (node instanceof MethodImplementationsTreeNode) { + MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; + navigateTo(methodNode.getMethodEntry()); + } + } + } + }); + JPanel implementationsPanel = new JPanel(); + implementationsPanel.setLayout(new BorderLayout()); + implementationsPanel.add(new JScrollPane(implementationsTree)); + + // init call panel + callsTree = new JTree(); + callsTree.setModel(null); + callsTree.addMouseListener(new MouseAdapter() { + @SuppressWarnings("unchecked") + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + // get the selected node + TreePath path = callsTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ReferenceTreeNode) { + ReferenceTreeNode referenceNode = ((ReferenceTreeNode) node); + if (referenceNode.getReference() != null) { + navigateTo(referenceNode.getReference()); + } else { + navigateTo(referenceNode.getEntry()); + } + } + } + } + }); + tokens = new JList<>(); + tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); + tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + tokens.setLayoutOrientation(JList.VERTICAL); + tokens.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + Token selected = tokens.getSelectedValue(); + if (selected != null) { + showToken(selected); + } + } + } + }); + tokens.setPreferredSize(new Dimension(0, 200)); + tokens.setMinimumSize(new Dimension(0, 200)); + JSplitPane callPanel = new JSplitPane( + JSplitPane.VERTICAL_SPLIT, + true, + new JScrollPane(callsTree), + new JScrollPane(tokens) + ); + callPanel.setResizeWeight(1); // let the top side take all the slack + callPanel.resetToPreferredSizes(); + + // layout controls + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new BorderLayout()); + centerPanel.add(infoPanel, BorderLayout.NORTH); + centerPanel.add(sourceScroller, BorderLayout.CENTER); + tabs = new JTabbedPane(); + tabs.setPreferredSize(new Dimension(250, 0)); + tabs.addTab("Inheritance", inheritancePanel); + tabs.addTab("Implementations", implementationsPanel); + tabs.addTab("Call Graph", callPanel); + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, tabs); + splitRight.setResizeWeight(1); // let the left side take all the slack + splitRight.resetToPreferredSizes(); + JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); + splitCenter.setResizeWeight(0); // let the right side take all the slack + pane.add(splitCenter, BorderLayout.CENTER); + + // init menus + this.menuBar = new MenuBar(this); + this.frame.setJMenuBar(this.menuBar); + + // init state + onCloseJar(); + + this.frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + close(); + } + }); + + // show the frame + pane.doLayout(); + this.frame.setSize(1024, 576); + this.frame.setMinimumSize(new Dimension(640, 480)); + this.frame.setVisible(true); + this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } + + public JFrame getFrame() { + return this.frame; + } + + public GuiController getController() { + return this.controller; + } + + public void onStartOpenJar() { + this.classesPanel.removeAll(); + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout()); + panel.add(new JLabel("Loading...")); + this.classesPanel.add(panel); + redraw(); + } + + public void onFinishOpenJar(String jarName) { + // update gui + this.frame.setTitle(Constants.NAME + " - " + jarName); + this.classesPanel.removeAll(); + this.classesPanel.add(splitClasses); + setSource(null); + + // update menu + this.menuBar.closeJarMenu.setEnabled(true); + this.menuBar.openEnigmaMappingsMenu.setEnabled(true); + this.menuBar.saveMappingsMenu.setEnabled(false); + this.menuBar.saveMappingEnigmaFileMenu.setEnabled(true); + this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(true); + this.menuBar.saveMappingsSrgMenu.setEnabled(true); + this.menuBar.closeMappingsMenu.setEnabled(true); + this.menuBar.exportSourceMenu.setEnabled(true); + this.menuBar.exportJarMenu.setEnabled(true); + + redraw(); + } + + public void onCloseJar() { + // update gui + this.frame.setTitle(Constants.NAME); + setObfClasses(null); + setDeobfClasses(null); + setSource(null); + this.classesPanel.removeAll(); + + // update menu + this.menuBar.closeJarMenu.setEnabled(false); + this.menuBar.openEnigmaMappingsMenu.setEnabled(false); + this.menuBar.saveMappingsMenu.setEnabled(false); + this.menuBar.saveMappingEnigmaFileMenu.setEnabled(false); + this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(false); + this.menuBar.saveMappingsSrgMenu.setEnabled(false); + this.menuBar.closeMappingsMenu.setEnabled(false); + this.menuBar.exportSourceMenu.setEnabled(false); + this.menuBar.exportJarMenu.setEnabled(false); + + redraw(); + } + + public void setObfClasses(Collection obfClasses) { + this.obfPanel.obfClasses.setClasses(obfClasses); + } + + public void setDeobfClasses(Collection deobfClasses) { + this.deobfPanel.deobfClasses.setClasses(deobfClasses); + } + + public void setMappingsFile(File file) { + this.enigmaMappingsFileChooser.setSelectedFile(file); + this.menuBar.saveMappingsMenu.setEnabled(file != null); + } + + public void setSource(String source) { + this.editor.getHighlighter().removeAllHighlights(); + this.editor.setText(source); + } + + public void showToken(final Token token) { + if (token == null) { + throw new IllegalArgumentException("Token cannot be null!"); + } + CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); + redraw(); + } + + public void showTokens(Collection tokens) { + Vector sortedTokens = new Vector<>(tokens); + Collections.sort(sortedTokens); + if (sortedTokens.size() > 1) { + // sort the tokens and update the tokens panel + this.tokens.setListData(sortedTokens); + this.tokens.setSelectedIndex(0); + } else { + this.tokens.setListData(new Vector<>()); + } + + // show the first token + showToken(sortedTokens.get(0)); + } + + public void setHighlightedTokens(Iterable obfuscatedTokens, Iterable deobfuscatedTokens, Iterable otherTokens) { + + // remove any old highlighters + this.editor.getHighlighter().removeAllHighlights(); + + // color things based on the index + if (obfuscatedTokens != null) { + setHighlightedTokens(obfuscatedTokens, obfuscatedHighlightPainter); + } + if (deobfuscatedTokens != null) { + setHighlightedTokens(deobfuscatedTokens, deobfuscatedHighlightPainter); + } + if (otherTokens != null) { + setHighlightedTokens(otherTokens, otherHighlightPainter); + } + + redraw(); + } + + private void setHighlightedTokens(Iterable tokens, Highlighter.HighlightPainter painter) { + for (Token token : tokens) { + try { + this.editor.getHighlighter().addHighlight(token.start, token.end, painter); + } catch (BadLocationException ex) { + throw new IllegalArgumentException(ex); + } + } + } + + private void showReference(EntryReference reference) { + if (reference == null) { + infoPanel.clearReference(); + return; + } + + this.reference = reference; + + infoPanel.removeAll(); + if (reference.entry instanceof ClassEntry) { + showClassEntry((ClassEntry) this.reference.entry); + } else if (this.reference.entry instanceof FieldEntry) { + showFieldEntry((FieldEntry) this.reference.entry); + } else if (this.reference.entry instanceof MethodEntry) { + showMethodEntry((MethodEntry) this.reference.entry); + } else if (this.reference.entry instanceof ConstructorEntry) { + showConstructorEntry((ConstructorEntry) this.reference.entry); + } else if (this.reference.entry instanceof ArgumentEntry) { + showArgumentEntry((ArgumentEntry) this.reference.entry); + } else if (this.reference.entry instanceof LocalVariableEntry) { + showLocalVariableEntry((LocalVariableEntry) this.reference.entry); + } else { + throw new Error("Unknown entry type: " + this.reference.entry.getClass().getName()); + } + + redraw(); + } + + private void showLocalVariableEntry(LocalVariableEntry entry) { + addNameValue(infoPanel, "Variable", entry.getName()); + addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); + addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); + addNameValue(infoPanel, "Type", entry.getType().toString()); + } + + private void showClassEntry(ClassEntry entry) { + addNameValue(infoPanel, "Class", entry.getName()); + addModifierComboBox(infoPanel, "Modifier", entry); + } + + private void showFieldEntry(FieldEntry entry) { + addNameValue(infoPanel, "Field", entry.getName()); + addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(infoPanel, "Type", entry.getType().toString()); + addModifierComboBox(infoPanel, "Modifier", entry); + } + + private void showMethodEntry(MethodEntry entry) { + addNameValue(infoPanel, "Method", entry.getName()); + addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(infoPanel, "Signature", entry.getSignature().toString()); + addModifierComboBox(infoPanel, "Modifier", entry); + + } + + private void showConstructorEntry(ConstructorEntry entry) { + addNameValue(infoPanel, "Constructor", entry.getClassEntry().getName()); + if (!entry.isStatic()) { + addNameValue(infoPanel, "Signature", entry.getSignature().toString()); + addModifierComboBox(infoPanel, "Modifier", entry); + } + } + + private void showArgumentEntry(ArgumentEntry entry) { + addNameValue(infoPanel, "Argument", entry.getName()); + addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); + addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); + } + + private void addNameValue(JPanel container, String name, String value) { + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); + container.add(panel); + + JLabel label = new JLabel(name + ":", JLabel.RIGHT); + label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); + panel.add(label); + + panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT))); + } + + private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) { + if (!getController().entryIsInJar(entry)) + return null; + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); + container.add(panel); + JLabel label = new JLabel(name + ":", JLabel.RIGHT); + label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); + panel.add(label); + JComboBox combo = new JComboBox<>(Mappings.EntryModifier.values()); + ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); + combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); + combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); + combo.addItemListener(getController()::modifierChange); + panel.add(combo); + return combo; + } + + public void onCaretMove(int pos) { + + Token token = this.controller.getToken(pos); + boolean isToken = token != null; + + reference = this.controller.getDeobfReference(token); + boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; + boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; + boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry; + boolean isConstructorEntry = isToken && reference.entry instanceof ConstructorEntry; + boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); + boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); + + if (isToken) { + showReference(reference); + } else { + infoPanel.clearReference(); + } + + this.popupMenu.renameMenu.setEnabled(isRenameable); + this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); + this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); + this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); + this.popupMenu.openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); + this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); + this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); + + if (isToken && this.controller.entryHasDeobfuscatedName(reference.entry)) { + this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); + } else { + this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); + } + } + + public void navigateTo(Entry entry) { + if (!this.controller.entryIsInJar(entry)) { + // entry is not in the jar. Ignore it + return; + } + if (reference != null) { + this.controller.savePreviousReference(reference); + } + this.controller.openDeclaration(entry); + } + + private void navigateTo(EntryReference reference) { + if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) { + return; + } + if (this.reference != null) { + this.controller.savePreviousReference(this.reference); + } + this.controller.openReference(reference); + } + + public void startRename() { + + // init the text box + final JTextField text = new JTextField(); + text.setText(reference.getNamableName()); + text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); + text.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_ENTER: + finishRename(text, true); + break; + + case KeyEvent.VK_ESCAPE: + finishRename(text, false); + break; + default: + break; + } + } + }); + + // find the label with the name and replace it with the text box + JPanel panel = (JPanel) infoPanel.getComponent(0); + panel.remove(panel.getComponentCount() - 1); + panel.add(text); + text.grabFocus(); + + int offset = text.getText().lastIndexOf('/') + 1; + // If it's a class and isn't in the default package, assume that it's deobfuscated. + if (reference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0) + text.select(offset, text.getText().length()); + else + text.selectAll(); + + redraw(); + } + + private void finishRename(JTextField text, boolean saveName) { + String newName = text.getText(); + if (saveName && newName != null && !newName.isEmpty()) { + try { + this.controller.rename(reference, newName); + } catch (IllegalNameException ex) { + text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); + text.setToolTipText(ex.getReason()); + Utils.showToolTipNow(text); + } + return; + } + + // abort the rename + JPanel panel = (JPanel) infoPanel.getComponent(0); + panel.remove(panel.getComponentCount() - 1); + panel.add(Utils.unboldLabel(new JLabel(reference.getNamableName(), JLabel.LEFT))); + + this.editor.grabFocus(); + + redraw(); + } + + public void showInheritance() { + + if (reference == null) { + return; + } + + inheritanceTree.setModel(null); + + if (reference.entry instanceof ClassEntry) { + // get the class inheritance + ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) reference.entry); + + // show the tree at the root + TreePath path = getPathToRoot(classNode); + inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + inheritanceTree.expandPath(path); + inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); + } else if (reference.entry instanceof MethodEntry) { + // get the method inheritance + MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) reference.entry); + + // show the tree at the root + TreePath path = getPathToRoot(classNode); + inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + inheritanceTree.expandPath(path); + inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); + } + + tabs.setSelectedIndex(0); + redraw(); + } + + public void showImplementations() { + + if (reference == null) { + return; + } + + implementationsTree.setModel(null); + + DefaultMutableTreeNode node = null; + + // get the class implementations + if (reference.entry instanceof ClassEntry) + node = this.controller.getClassImplementations((ClassEntry) reference.entry); + else // get the method implementations + if (reference.entry instanceof MethodEntry) + node = this.controller.getMethodImplementations((MethodEntry) reference.entry); + + if (node != null) { + // show the tree at the root + TreePath path = getPathToRoot(node); + implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + implementationsTree.expandPath(path); + implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); + } + + tabs.setSelectedIndex(1); + redraw(); + } + + public void showCalls() { + if (reference == null) { + return; + } + + if (reference.entry instanceof ClassEntry) { + // look for calls to the default constructor + // TODO: get a list of all the constructors and find calls to all of them + BehaviorReferenceTreeNode node = this.controller.getMethodReferences(new ConstructorEntry((ClassEntry) reference.entry, new Signature("()V"))); + callsTree.setModel(new DefaultTreeModel(node)); + } else if (reference.entry instanceof FieldEntry) { + FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry); + callsTree.setModel(new DefaultTreeModel(node)); + } else if (reference.entry instanceof MethodEntry) { + BehaviorReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); + callsTree.setModel(new DefaultTreeModel(node)); + } else if (reference.entry instanceof ConstructorEntry) { + BehaviorReferenceTreeNode node = this.controller.getMethodReferences((ConstructorEntry) reference.entry); + callsTree.setModel(new DefaultTreeModel(node)); + } + + tabs.setSelectedIndex(2); + redraw(); + } + + public void toggleMapping() { + if (this.controller.entryHasDeobfuscatedName(reference.entry)) { + this.controller.removeMapping(reference); + } else { + this.controller.markAsDeobfuscated(reference); + } + } + + private TreePath getPathToRoot(TreeNode node) { + List nodes = Lists.newArrayList(); + TreeNode n = node; + do { + nodes.add(n); + n = n.getParent(); + } while (n != null); + Collections.reverse(nodes); + return new TreePath(nodes.toArray()); + } + + public void showDiscardDiag(Function callback, String... options) { + int response = JOptionPane.showOptionDialog(this.frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, options[2]); + callback.apply(response); + } + + public void saveMapping() throws IOException { + if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) + this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile()); + } + + public void close() { + if (!this.controller.isDirty()) { + // everything is saved, we can exit safely + this.frame.dispose(); + System.exit(0); + } else { + // ask to save before closing + showDiscardDiag((response) -> { + if (response == JOptionPane.YES_OPTION) { + try { + this.saveMapping(); + this.frame.dispose(); + + } catch (IOException ex) { + throw new Error(ex); + } + } else if (response == JOptionPane.NO_OPTION) + this.frame.dispose(); + + return null; + }, "Save and exit", "Discard changes", "Cancel"); + } + } + + public void redraw() { + this.frame.validate(); + this.frame.repaint(); + } + + public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException { + // package rename + if (data instanceof String) { + for (int i = 0; i < node.getChildCount(); i++) { + data = Descriptor.toJvmName((String) data); + DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); + ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); + ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); + this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getName()), dataChild.getName(), false, i + 1 == node.getChildCount()); + childNode.setUserObject(dataChild); + } + node.setUserObject(data); + // Ob package will never be modified, just reload deob view + this.deobfPanel.deobfClasses.reload(); + } + // class rename + else if (data instanceof ClassEntry) + this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getName()), ((ClassEntry) data).getName(), false, true); + } + + public void moveClassTree(EntryReference deobfReference, String newName) { + String oldEntry = deobfReference.entry.getClassEntry().getPackageName(); + String newEntry = new ClassEntry(Descriptor.toJvmName(newName)).getPackageName(); + moveClassTree(deobfReference, newName, oldEntry == null, + newEntry == null); + } + + public void moveClassTree(EntryReference deobfReference, String newName, boolean isOldOb, boolean isNewOb) { + ClassEntry oldEntry = deobfReference.entry.getClassEntry(); + ClassEntry newEntry = new ClassEntry(Descriptor.toJvmName(newName)); + + // Ob -> deob + if (isOldOb && !isNewOb) { + this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, obfPanel.obfClasses); + ClassSelectorPackageNode packageNode = this.obfPanel.obfClasses.getPackageNode(oldEntry); + this.obfPanel.obfClasses.removeNode(packageNode, oldEntry); + this.obfPanel.obfClasses.removeNodeIfEmpty(packageNode); + this.deobfPanel.deobfClasses.reload(); + this.obfPanel.obfClasses.reload(); + } + // Deob -> ob + else if (isNewOb && !isOldOb) { + this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, deobfPanel.deobfClasses); + ClassSelectorPackageNode packageNode = this.deobfPanel.deobfClasses.getPackageNode(oldEntry); + this.deobfPanel.deobfClasses.removeNode(packageNode, oldEntry); + this.deobfPanel.deobfClasses.removeNodeIfEmpty(packageNode); + this.deobfPanel.deobfClasses.reload(); + this.obfPanel.obfClasses.reload(); + } + // Local move + else if (isOldOb) { + this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, null); + this.obfPanel.obfClasses.removeNodeIfEmpty(this.obfPanel.obfClasses.getPackageNode(oldEntry)); + this.obfPanel.obfClasses.reload(); + } else { + this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, null); + this.deobfPanel.deobfClasses.removeNodeIfEmpty(this.deobfPanel.deobfClasses.getPackageNode(oldEntry)); + this.deobfPanel.deobfClasses.reload(); + } + } } diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index 68fd484..1b461da 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.google.common.collect.Lists; @@ -30,326 +31,321 @@ import java.util.jar.JarFile; public class GuiController { - private Deobfuscator deobfuscator; - private Gui gui; - private SourceIndex index; - private ClassEntry currentObfClass; - private boolean isDirty; - private Deque> referenceStack; - - public GuiController(Gui gui) { - this.gui = gui; - this.deobfuscator = null; - this.index = null; - this.currentObfClass = null; - this.isDirty = false; - this.referenceStack = Queues.newArrayDeque(); - } - - public boolean isDirty() { - return this.isDirty; - } - - public void openJar(final JarFile jar) { - this.gui.onStartOpenJar(); - this.deobfuscator = new Deobfuscator(jar); - this.gui.onFinishOpenJar(this.deobfuscator.getJarName()); - refreshClasses(); - } - - public void closeJar() { - this.deobfuscator = null; - this.gui.onCloseJar(); - } - - public void openEnigmaMappings(File file) throws IOException, MappingParseException { - this.deobfuscator.setMappings(new MappingsEnigmaReader().read(file)); - this.isDirty = false; - this.gui.setMappingsFile(file); - refreshClasses(); - refreshCurrentClass(); - } - - public void saveMappings(File file) throws IOException { - Mappings mappings = this.deobfuscator.getMappings(); - switch (mappings.getOriginMappingFormat()) - { - case SRG_FILE: - saveSRGMappings(file); - break; - default: - saveEnigmaMappings(file, Mappings.FormatType.ENIGMA_FILE != mappings.getOriginMappingFormat()); - break; - } - - } - - public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException { - this.deobfuscator.getMappings().saveEnigmaMappings(file, isDirectoryFormat); - this.isDirty = false; - } - - public void saveSRGMappings(File file) throws IOException { - this.deobfuscator.getMappings().saveSRGMappings(file); - this.isDirty = false; - } - - public void closeMappings() { - this.deobfuscator.setMappings(null); - this.gui.setMappingsFile(null); - refreshClasses(); - refreshCurrentClass(); - } - - public void rebuildMethodNames() { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.rebuildMethodNames(progress)); - } - - public void exportSource(final File dirOut) { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); - } - - public void exportJar(final File fileOut) { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress)); - } - - public Token getToken(int pos) { - if (this.index == null) { - return null; - } - return this.index.getReferenceToken(pos); - } - - public EntryReference getDeobfReference(Token token) { - if (this.index == null) { - return null; - } - return this.index.getDeobfReference(token); - } - - public ReadableToken getReadableToken(Token token) { - if (this.index == null) { - return null; - } - return new ReadableToken( - this.index.getLineNumber(token.start), - this.index.getColumnNumber(token.start), - this.index.getColumnNumber(token.end) - ); - } - - public boolean entryHasDeobfuscatedName(Entry deobfEntry) { - return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.obfuscateEntry(deobfEntry)); - } - - public boolean entryIsInJar(Entry deobfEntry) { - return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry)); - } - - public boolean referenceIsRenameable(EntryReference deobfReference) { - return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference), true); - } - - public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); - return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); - } - - public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); - } - - public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { - MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); - return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); - } - - public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { - MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - List rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); - if (rootNodes.isEmpty()) { - return null; - } - if (rootNodes.size() > 1) { - System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one."); - } - return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry); - } - - public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { - FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); - FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry); - rootNode.load(this.deobfuscator.getJarIndex(), true); - return rootNode; - } - - public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { - BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry); - BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry); - rootNode.load(this.deobfuscator.getJarIndex(), true); - return rootNode; - } - - public void rename(EntryReference deobfReference, String newName) { - rename(deobfReference, newName, true, true); - } - - public void rename(EntryReference deobfReference, String newName, boolean refreshClassTree, boolean clearTranslationCache) - { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - this.deobfuscator.rename(obfReference.getNameableEntry(), newName, clearTranslationCache); - this.isDirty = true; - - if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) - this.gui.moveClassTree(deobfReference, newName); - refreshCurrentClass(obfReference); - - } - - public void removeMapping(EntryReference deobfReference) { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - this.deobfuscator.removeMapping(obfReference.getNameableEntry()); - this.isDirty = true; - if (deobfReference.entry instanceof ClassEntry) - this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true); - refreshCurrentClass(obfReference); - } - - public void markAsDeobfuscated(EntryReference deobfReference) { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); - this.isDirty = true; - if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) - this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false); - refreshCurrentClass(obfReference); - } - - public void openDeclaration(Entry deobfEntry) { - if (deobfEntry == null) { - throw new IllegalArgumentException("Entry cannot be null!"); - } - openReference(new EntryReference<>(deobfEntry, deobfEntry.getName())); - } - - public void openReference(EntryReference deobfReference) { - if (deobfReference == null) { - throw new IllegalArgumentException("Reference cannot be null!"); - } - - // get the reference target class - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry(); - if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { - throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); - } - if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) { - // deobfuscate the class, then navigate to the reference - this.currentObfClass = obfClassEntry; - deobfuscate(this.currentObfClass, obfReference); - } else { - showReference(obfReference); - } - } - - private void showReference(EntryReference obfReference) { - EntryReference deobfReference = this.deobfuscator.deobfuscateReference(obfReference); - Collection tokens = this.index.getReferenceTokens(deobfReference); - if (tokens.isEmpty()) { - // DEBUG - System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass)); - } else { - this.gui.showTokens(tokens); - } - } - - public void savePreviousReference(EntryReference deobfReference) { - this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference)); - } - - public void openPreviousReference() { - if (hasPreviousLocation()) { - openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop())); - } - } - - public boolean hasPreviousLocation() { - return !this.referenceStack.isEmpty(); - } - - private void refreshClasses() { - List obfClasses = Lists.newArrayList(); - List deobfClasses = Lists.newArrayList(); - this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); - this.gui.setObfClasses(obfClasses); - this.gui.setDeobfClasses(deobfClasses); - } - - public void refreshCurrentClass() { - refreshCurrentClass(null); - } - - private void refreshCurrentClass(EntryReference obfReference) { - if (this.currentObfClass != null) { - deobfuscate(this.currentObfClass, obfReference); - } - } - - private void deobfuscate(final ClassEntry classEntry, final EntryReference obfReference) { - - this.gui.setSource("(deobfuscating...)"); - - // run the deobfuscator in a separate thread so we don't block the GUI event queue - new Thread(() -> - { - // decompile,deobfuscate the bytecode - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName()); - if (sourceTree == null) { - // decompilation of this class is not supported - gui.setSource("Unable to find class: " + classEntry); - return; - } - String source = deobfuscator.getSource(sourceTree); - index = deobfuscator.getSourceIndex(sourceTree, source); - gui.setSource(index.getSource()); - if (obfReference != null) { - showReference(obfReference); - } - - // set the highlighted tokens - List obfuscatedTokens = Lists.newArrayList(); - List deobfuscatedTokens = Lists.newArrayList(); - List otherTokens = Lists.newArrayList(); - for (Token token : index.referenceTokens()) { - EntryReference reference = index.getDeobfReference(token); - if (referenceIsRenameable(reference)) { - if (entryHasDeobfuscatedName(reference.getNameableEntry())) { - deobfuscatedTokens.add(token); - } else { - obfuscatedTokens.add(token); - } - } else { - otherTokens.add(token); - } - } - gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens); - }).start(); - } - - public Deobfuscator getDeobfuscator() - { - return deobfuscator; - } - - public void modifierChange(ItemEvent event) - { - if (event.getStateChange() == ItemEvent.SELECTED) - { - deobfuscator.changeModifier(gui.reference.entry, (Mappings.EntryModifier) event.getItem()); - this.isDirty = true; - refreshCurrentClass(); - } - } + private Deobfuscator deobfuscator; + private Gui gui; + private SourceIndex index; + private ClassEntry currentObfClass; + private boolean isDirty; + private Deque> referenceStack; + + public GuiController(Gui gui) { + this.gui = gui; + this.deobfuscator = null; + this.index = null; + this.currentObfClass = null; + this.isDirty = false; + this.referenceStack = Queues.newArrayDeque(); + } + + public boolean isDirty() { + return this.isDirty; + } + + public void openJar(final JarFile jar) { + this.gui.onStartOpenJar(); + this.deobfuscator = new Deobfuscator(jar); + this.gui.onFinishOpenJar(this.deobfuscator.getJarName()); + refreshClasses(); + } + + public void closeJar() { + this.deobfuscator = null; + this.gui.onCloseJar(); + } + + public void openEnigmaMappings(File file) throws IOException, MappingParseException { + this.deobfuscator.setMappings(new MappingsEnigmaReader().read(file)); + this.isDirty = false; + this.gui.setMappingsFile(file); + refreshClasses(); + refreshCurrentClass(); + } + + public void saveMappings(File file) throws IOException { + Mappings mappings = this.deobfuscator.getMappings(); + switch (mappings.getOriginMappingFormat()) { + case SRG_FILE: + saveSRGMappings(file); + break; + default: + saveEnigmaMappings(file, Mappings.FormatType.ENIGMA_FILE != mappings.getOriginMappingFormat()); + break; + } + + } + + public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException { + this.deobfuscator.getMappings().saveEnigmaMappings(file, isDirectoryFormat); + this.isDirty = false; + } + + public void saveSRGMappings(File file) throws IOException { + this.deobfuscator.getMappings().saveSRGMappings(file); + this.isDirty = false; + } + + public void closeMappings() { + this.deobfuscator.setMappings(null); + this.gui.setMappingsFile(null); + refreshClasses(); + refreshCurrentClass(); + } + + public void rebuildMethodNames() { + ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.rebuildMethodNames(progress)); + } + + public void exportSource(final File dirOut) { + ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); + } + + public void exportJar(final File fileOut) { + ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress)); + } + + public Token getToken(int pos) { + if (this.index == null) { + return null; + } + return this.index.getReferenceToken(pos); + } + + public EntryReference getDeobfReference(Token token) { + if (this.index == null) { + return null; + } + return this.index.getDeobfReference(token); + } + + public ReadableToken getReadableToken(Token token) { + if (this.index == null) { + return null; + } + return new ReadableToken( + this.index.getLineNumber(token.start), + this.index.getColumnNumber(token.start), + this.index.getColumnNumber(token.end) + ); + } + + public boolean entryHasDeobfuscatedName(Entry deobfEntry) { + return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.obfuscateEntry(deobfEntry)); + } + + public boolean entryIsInJar(Entry deobfEntry) { + return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry)); + } + + public boolean referenceIsRenameable(EntryReference deobfReference) { + return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference), true); + } + + public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { + ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); + ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); + return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); + } + + public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { + ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); + return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); + } + + public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { + MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); + MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); + return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); + } + + public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { + MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); + List rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); + if (rootNodes.isEmpty()) { + return null; + } + if (rootNodes.size() > 1) { + System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one."); + } + return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry); + } + + public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { + FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry); + rootNode.load(this.deobfuscator.getJarIndex(), true); + return rootNode; + } + + public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { + BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry); + BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry); + rootNode.load(this.deobfuscator.getJarIndex(), true); + return rootNode; + } + + public void rename(EntryReference deobfReference, String newName) { + rename(deobfReference, newName, true, true); + } + + public void rename(EntryReference deobfReference, String newName, boolean refreshClassTree, boolean clearTranslationCache) { + EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + this.deobfuscator.rename(obfReference.getNameableEntry(), newName, clearTranslationCache); + this.isDirty = true; + + if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) + this.gui.moveClassTree(deobfReference, newName); + refreshCurrentClass(obfReference); + + } + + public void removeMapping(EntryReference deobfReference) { + EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + this.deobfuscator.removeMapping(obfReference.getNameableEntry()); + this.isDirty = true; + if (deobfReference.entry instanceof ClassEntry) + this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true); + refreshCurrentClass(obfReference); + } + + public void markAsDeobfuscated(EntryReference deobfReference) { + EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); + this.isDirty = true; + if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) + this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false); + refreshCurrentClass(obfReference); + } + + public void openDeclaration(Entry deobfEntry) { + if (deobfEntry == null) { + throw new IllegalArgumentException("Entry cannot be null!"); + } + openReference(new EntryReference<>(deobfEntry, deobfEntry.getName())); + } + + public void openReference(EntryReference deobfReference) { + if (deobfReference == null) { + throw new IllegalArgumentException("Reference cannot be null!"); + } + + // get the reference target class + EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry(); + if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { + throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); + } + if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) { + // deobfuscate the class, then navigate to the reference + this.currentObfClass = obfClassEntry; + deobfuscate(this.currentObfClass, obfReference); + } else { + showReference(obfReference); + } + } + + private void showReference(EntryReference obfReference) { + EntryReference deobfReference = this.deobfuscator.deobfuscateReference(obfReference); + Collection tokens = this.index.getReferenceTokens(deobfReference); + if (tokens.isEmpty()) { + // DEBUG + System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass)); + } else { + this.gui.showTokens(tokens); + } + } + + public void savePreviousReference(EntryReference deobfReference) { + this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference)); + } + + public void openPreviousReference() { + if (hasPreviousLocation()) { + openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop())); + } + } + + public boolean hasPreviousLocation() { + return !this.referenceStack.isEmpty(); + } + + private void refreshClasses() { + List obfClasses = Lists.newArrayList(); + List deobfClasses = Lists.newArrayList(); + this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); + this.gui.setObfClasses(obfClasses); + this.gui.setDeobfClasses(deobfClasses); + } + + public void refreshCurrentClass() { + refreshCurrentClass(null); + } + + private void refreshCurrentClass(EntryReference obfReference) { + if (this.currentObfClass != null) { + deobfuscate(this.currentObfClass, obfReference); + } + } + + private void deobfuscate(final ClassEntry classEntry, final EntryReference obfReference) { + + this.gui.setSource("(deobfuscating...)"); + + // run the deobfuscator in a separate thread so we don't block the GUI event queue + new Thread(() -> + { + // decompile,deobfuscate the bytecode + CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName()); + if (sourceTree == null) { + // decompilation of this class is not supported + gui.setSource("Unable to find class: " + classEntry); + return; + } + String source = deobfuscator.getSource(sourceTree); + index = deobfuscator.getSourceIndex(sourceTree, source); + gui.setSource(index.getSource()); + if (obfReference != null) { + showReference(obfReference); + } + + // set the highlighted tokens + List obfuscatedTokens = Lists.newArrayList(); + List deobfuscatedTokens = Lists.newArrayList(); + List otherTokens = Lists.newArrayList(); + for (Token token : index.referenceTokens()) { + EntryReference reference = index.getDeobfReference(token); + if (referenceIsRenameable(reference)) { + if (entryHasDeobfuscatedName(reference.getNameableEntry())) { + deobfuscatedTokens.add(token); + } else { + obfuscatedTokens.add(token); + } + } else { + otherTokens.add(token); + } + } + gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens); + }).start(); + } + + public Deobfuscator getDeobfuscator() { + return deobfuscator; + } + + public void modifierChange(ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + deobfuscator.changeModifier(gui.reference.entry, (Mappings.EntryModifier) event.getItem()); + this.isDirty = true; + refreshCurrentClass(); + } + } } diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java index 85b65b0..8bf57d3 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiTricks.java +++ b/src/main/java/cuchaz/enigma/gui/GuiTricks.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import javax.swing.*; @@ -17,26 +18,26 @@ import java.util.Arrays; 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 JLabel unboldLabel(JLabel label) { + Font font = label.getFont(); + label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); + return label; + } - public static void deactivateButton(JButton button) { - button.setEnabled(false); - button.setText(""); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { - button.removeActionListener(listener); - } - } + 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); - } + 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 index 671f85f..4f5231f 100644 --- a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java +++ b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java @@ -8,25 +8,11 @@ * 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.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.SourceIndex; @@ -39,403 +25,410 @@ import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Entry; import de.sciss.syntaxpane.DefaultSyntaxKit; +import javax.swing.*; +import javax.swing.text.Highlighter.HighlightPainter; +import java.awt.*; +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; 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 frame; - private Map sourceTypeButtons; - private ClassSelector sourceClasses; - private CodeReader sourceReader; - private CodeReader destReader; - private JButton matchButton; - private JButton unmatchableButton; - private JLabel sourceLabel; - private JLabel destLabel; - private HighlightPainter unmatchedHighlightPainter; - private HighlightPainter matchedHighlightPainter; - private ClassMatches classMatches; - private MemberMatches memberMatches; - private Deobfuscator sourceDeobfuscator; - private Deobfuscator destDeobfuscator; - private SaveListener saveListener; - private SourceType sourceType; - private ClassEntry obfSourceClass; - private ClassEntry obfDestClass; - private T obfSourceEntry; - private T obfDestEntry; - - public MemberMatchingGui(ClassMatches classMatches, MemberMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - - this.classMatches = classMatches; - memberMatches = fieldMatches; - this.sourceDeobfuscator = sourceDeobfuscator; - this.destDeobfuscator = destDeobfuscator; - - // init frame - frame = new JFrame(Constants.NAME + " - Member Matcher"); - final Container pane = 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 = event -> setSourceType(SourceType.valueOf(event.getActionCommand())); - ButtonGroup sourceTypeButtons = new ButtonGroup(); - this.sourceTypeButtons = Maps.newHashMap(); - for (SourceType sourceType : SourceType.values()) { - JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); - this.sourceTypeButtons.put(sourceType, button); - sourceTypePanel.add(button); - } - - sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); - sourceClasses.setSelectionListener(this::setSourceClass); - JScrollPane sourceScroller = new JScrollPane(sourceClasses); - classesPanel.add(sourceScroller); - - // init readers - DefaultSyntaxKit.initKit(); - sourceReader = new CodeReader(); - sourceReader.setSelectionListener(reference -> - { - if (reference != null) { - onSelectSource(reference.entry); - } else { - onSelectSource(null); - } - }); - destReader = new CodeReader(); - destReader.setSelectionListener(reference -> - { - if (reference != null) { - onSelectDest(reference.entry); - } else { - onSelectDest(null); - } - }); - - // add key bindings - KeyAdapter keyListener = new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - if (event.getKeyCode() == KeyEvent.VK_M) - matchButton.doClick(); - } - }; - sourceReader.addKeyListener(keyListener); - destReader.addKeyListener(keyListener); - - // init all the splits - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(sourceReader), new JScrollPane( - 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); - - matchButton = new JButton(); - unmatchableButton = new JButton(); - - sourceLabel = new JLabel(); - bottomPanel.add(sourceLabel); - bottomPanel.add(matchButton); - bottomPanel.add(unmatchableButton); - destLabel = new JLabel(); - bottomPanel.add(destLabel); - - // show the frame - pane.doLayout(); - frame.setSize(1024, 576); - frame.setMinimumSize(new Dimension(640, 480)); - frame.setVisible(true); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - - unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); - matchedHighlightPainter = new DeobfuscatedHighlightPainter(); - - // init state - saveListener = null; - obfSourceClass = null; - obfDestClass = null; - obfSourceEntry = null; - obfDestEntry = null; - setSourceType(SourceType.getDefault()); - updateButtons(); - } - - protected void setSourceType(SourceType val) { - sourceType = val; - updateSourceClasses(); - } - - public void setSaveListener(SaveListener val) { - saveListener = val; - } - - private void updateSourceClasses() { - - String selectedPackage = sourceClasses.getSelectedPackage(); - - List deobfClassEntries = Lists.newArrayList(); - for (ClassEntry entry : sourceType.getObfSourceClasses(memberMatches)) { - deobfClassEntries.add(sourceDeobfuscator.deobfuscateEntry(entry)); - } - sourceClasses.setClasses(deobfClassEntries); - - if (selectedPackage != null) { - sourceClasses.expandPackage(selectedPackage); - } - - for (SourceType sourceType : SourceType.values()) { - sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", - sourceType.name(), sourceType.getObfSourceClasses(memberMatches).size() - )); - } - } - - protected void setSourceClass(ClassEntry sourceClass) { - - obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass); - obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - if (obfDestClass == null) { - throw new Error("No matching dest class for source class: " + obfSourceClass); - } - - sourceReader.decompileClass(obfSourceClass, sourceDeobfuscator, false, this::updateSourceHighlights); - destReader.decompileClass(obfDestClass, destDeobfuscator, false, this::updateDestHighlights); - } - - protected void updateSourceHighlights() { - highlightEntries(sourceReader, sourceDeobfuscator, memberMatches.matches().keySet(), memberMatches.getUnmatchedSourceEntries()); - } - - protected void updateDestHighlights() { - highlightEntries(destReader, destDeobfuscator, memberMatches.matches().values(), memberMatches.getUnmatchedDestEntries()); - } - - private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedEntries, Collection obfUnmatchedEntries) { - reader.clearHighlights(); - // matched fields - updateHighlighted(obfMatchedEntries, deobfuscator, reader, matchedHighlightPainter); - // unmatched fields - updateHighlighted(obfUnmatchedEntries, deobfuscator, reader, unmatchedHighlightPainter); - } - - private void updateHighlighted(Collection entries, Deobfuscator deobfuscator, CodeReader reader, HighlightPainter painter) - { - SourceIndex index = reader.getSourceIndex(); - for (T obfT : entries) { - T deobfT = deobfuscator.deobfuscateEntry(obfT); - Token token = index.getDeclarationToken(deobfT); - if (token != null) { - reader.setHighlightedToken(token, painter); - } - } - } - - private boolean isSelectionMatched() { - return obfSourceEntry != null && obfDestEntry != null - && memberMatches.isMatched(obfSourceEntry, 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 = sourceDeobfuscator.obfuscateEntry(sourceEntry); - if (memberMatches.hasSource(obfSourceEntry)) { - setSource(obfSourceEntry); - - // look for a matched dest too - T obfDestEntry = 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 = destDeobfuscator.obfuscateEntry(destEntry); - if (memberMatches.hasDest(obfDestEntry)) { - setDest(obfDestEntry); - - // look for a matched source too - T obfSourceEntry = memberMatches.matches().inverse().get(obfDestEntry); - if (obfSourceEntry != null) { - setSource(obfSourceEntry); - } - } - } - - updateButtons(); - } - - private void setSource(T obfEntry) { - if (obfEntry == null) { - obfSourceEntry = null; - sourceLabel.setText(""); - } else { - obfSourceEntry = obfEntry; - sourceLabel.setText(getEntryLabel(obfEntry, sourceDeobfuscator)); - } - } - - private void setDest(T obfEntry) { - if (obfEntry == null) { - obfDestEntry = null; - destLabel.setText(""); - } else { - obfDestEntry = obfEntry; - destLabel.setText(getEntryLabel(obfEntry, 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(matchButton); - GuiTricks.deactivateButton(unmatchableButton); - - if (obfSourceEntry != null && obfDestEntry != null) { - if (memberMatches.isMatched(obfSourceEntry, obfDestEntry)) - GuiTricks.activateButton(matchButton, "Unmatch", event -> unmatch()); - else if (!memberMatches.isMatchedSourceEntry(obfSourceEntry) && !memberMatches.isMatchedDestEntry( - obfDestEntry)) - GuiTricks.activateButton(matchButton, "Match", event -> match()); - } else if (obfSourceEntry != null) - GuiTricks.activateButton(unmatchableButton, "Set Unmatchable", event -> unmatchable()); - } - - protected void match() { - - // update the field matches - memberMatches.makeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - protected void unmatch() { - - // update the field matches - memberMatches.unmakeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - protected void unmatchable() { - - // update the field matches - memberMatches.makeSourceUnmatchable(obfSourceEntry, sourceDeobfuscator); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - private void save() { - if (saveListener != null) { - saveListener.save(memberMatches); - } - } + // controls + private JFrame frame; + private Map sourceTypeButtons; + private ClassSelector sourceClasses; + private CodeReader sourceReader; + private CodeReader destReader; + private JButton matchButton; + private JButton unmatchableButton; + private JLabel sourceLabel; + private JLabel destLabel; + private HighlightPainter unmatchedHighlightPainter; + private HighlightPainter matchedHighlightPainter; + private ClassMatches classMatches; + private MemberMatches memberMatches; + private Deobfuscator sourceDeobfuscator; + private Deobfuscator destDeobfuscator; + private SaveListener saveListener; + private SourceType sourceType; + private ClassEntry obfSourceClass; + private ClassEntry obfDestClass; + private T obfSourceEntry; + private T obfDestEntry; + public MemberMatchingGui(ClassMatches classMatches, MemberMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + + this.classMatches = classMatches; + memberMatches = fieldMatches; + this.sourceDeobfuscator = sourceDeobfuscator; + this.destDeobfuscator = destDeobfuscator; + + // init frame + frame = new JFrame(Constants.NAME + " - Member Matcher"); + final Container pane = 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 = event -> setSourceType(SourceType.valueOf(event.getActionCommand())); + ButtonGroup sourceTypeButtons = new ButtonGroup(); + this.sourceTypeButtons = Maps.newHashMap(); + for (SourceType sourceType : SourceType.values()) { + JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); + this.sourceTypeButtons.put(sourceType, button); + sourceTypePanel.add(button); + } + + sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); + sourceClasses.setSelectionListener(this::setSourceClass); + JScrollPane sourceScroller = new JScrollPane(sourceClasses); + classesPanel.add(sourceScroller); + + // init readers + DefaultSyntaxKit.initKit(); + sourceReader = new CodeReader(); + sourceReader.setSelectionListener(reference -> + { + if (reference != null) { + onSelectSource(reference.entry); + } else { + onSelectSource(null); + } + }); + destReader = new CodeReader(); + destReader.setSelectionListener(reference -> + { + if (reference != null) { + onSelectDest(reference.entry); + } else { + onSelectDest(null); + } + }); + + // add key bindings + KeyAdapter keyListener = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.VK_M) + matchButton.doClick(); + } + }; + sourceReader.addKeyListener(keyListener); + destReader.addKeyListener(keyListener); + + // init all the splits + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(sourceReader), new JScrollPane( + 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); + + matchButton = new JButton(); + unmatchableButton = new JButton(); + + sourceLabel = new JLabel(); + bottomPanel.add(sourceLabel); + bottomPanel.add(matchButton); + bottomPanel.add(unmatchableButton); + destLabel = new JLabel(); + bottomPanel.add(destLabel); + + // show the frame + pane.doLayout(); + frame.setSize(1024, 576); + frame.setMinimumSize(new Dimension(640, 480)); + frame.setVisible(true); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); + matchedHighlightPainter = new DeobfuscatedHighlightPainter(); + + // init state + saveListener = null; + obfSourceClass = null; + obfDestClass = null; + obfSourceEntry = null; + obfDestEntry = null; + setSourceType(SourceType.getDefault()); + updateButtons(); + } + + protected void setSourceType(SourceType val) { + sourceType = val; + updateSourceClasses(); + } + + public void setSaveListener(SaveListener val) { + saveListener = val; + } + + private void updateSourceClasses() { + + String selectedPackage = sourceClasses.getSelectedPackage(); + + List deobfClassEntries = Lists.newArrayList(); + for (ClassEntry entry : sourceType.getObfSourceClasses(memberMatches)) { + deobfClassEntries.add(sourceDeobfuscator.deobfuscateEntry(entry)); + } + sourceClasses.setClasses(deobfClassEntries); + + if (selectedPackage != null) { + sourceClasses.expandPackage(selectedPackage); + } + + for (SourceType sourceType : SourceType.values()) { + sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", + sourceType.name(), sourceType.getObfSourceClasses(memberMatches).size() + )); + } + } + + protected void setSourceClass(ClassEntry sourceClass) { + + obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass); + obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); + if (obfDestClass == null) { + throw new Error("No matching dest class for source class: " + obfSourceClass); + } + + sourceReader.decompileClass(obfSourceClass, sourceDeobfuscator, false, this::updateSourceHighlights); + destReader.decompileClass(obfDestClass, destDeobfuscator, false, this::updateDestHighlights); + } + + protected void updateSourceHighlights() { + highlightEntries(sourceReader, sourceDeobfuscator, memberMatches.matches().keySet(), memberMatches.getUnmatchedSourceEntries()); + } + + protected void updateDestHighlights() { + highlightEntries(destReader, destDeobfuscator, memberMatches.matches().values(), memberMatches.getUnmatchedDestEntries()); + } + + private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedEntries, Collection obfUnmatchedEntries) { + reader.clearHighlights(); + // matched fields + updateHighlighted(obfMatchedEntries, deobfuscator, reader, matchedHighlightPainter); + // unmatched fields + updateHighlighted(obfUnmatchedEntries, deobfuscator, reader, unmatchedHighlightPainter); + } + + private void updateHighlighted(Collection entries, Deobfuscator deobfuscator, CodeReader reader, HighlightPainter painter) { + SourceIndex index = reader.getSourceIndex(); + for (T obfT : entries) { + T deobfT = deobfuscator.deobfuscateEntry(obfT); + Token token = index.getDeclarationToken(deobfT); + if (token != null) { + reader.setHighlightedToken(token, painter); + } + } + } + + private boolean isSelectionMatched() { + return obfSourceEntry != null && obfDestEntry != null + && memberMatches.isMatched(obfSourceEntry, 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 = sourceDeobfuscator.obfuscateEntry(sourceEntry); + if (memberMatches.hasSource(obfSourceEntry)) { + setSource(obfSourceEntry); + + // look for a matched dest too + T obfDestEntry = 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 = destDeobfuscator.obfuscateEntry(destEntry); + if (memberMatches.hasDest(obfDestEntry)) { + setDest(obfDestEntry); + + // look for a matched source too + T obfSourceEntry = memberMatches.matches().inverse().get(obfDestEntry); + if (obfSourceEntry != null) { + setSource(obfSourceEntry); + } + } + } + + updateButtons(); + } + + private void setSource(T obfEntry) { + if (obfEntry == null) { + obfSourceEntry = null; + sourceLabel.setText(""); + } else { + obfSourceEntry = obfEntry; + sourceLabel.setText(getEntryLabel(obfEntry, sourceDeobfuscator)); + } + } + + private void setDest(T obfEntry) { + if (obfEntry == null) { + obfDestEntry = null; + destLabel.setText(""); + } else { + obfDestEntry = obfEntry; + destLabel.setText(getEntryLabel(obfEntry, 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(matchButton); + GuiTricks.deactivateButton(unmatchableButton); + + if (obfSourceEntry != null && obfDestEntry != null) { + if (memberMatches.isMatched(obfSourceEntry, obfDestEntry)) + GuiTricks.activateButton(matchButton, "Unmatch", event -> unmatch()); + else if (!memberMatches.isMatchedSourceEntry(obfSourceEntry) && !memberMatches.isMatchedDestEntry( + obfDestEntry)) + GuiTricks.activateButton(matchButton, "Match", event -> match()); + } else if (obfSourceEntry != null) + GuiTricks.activateButton(unmatchableButton, "Set Unmatchable", event -> unmatchable()); + } + + protected void match() { + + // update the field matches + memberMatches.makeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatch() { + + // update the field matches + memberMatches.unmakeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatchable() { + + // update the field matches + memberMatches.makeSourceUnmatchable(obfSourceEntry, sourceDeobfuscator); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + private void save() { + if (saveListener != null) { + saveListener.save(memberMatches); + } + } + + private enum SourceType { + Matched { + @Override + public Collection getObfSourceClasses(MemberMatches matches) { + return matches.getSourceClassesWithoutUnmatchedEntries(); + } + }, + Unmatched { + @Override + public Collection getObfSourceClasses(MemberMatches matches) { + return matches.getSourceClassesWithUnmatchedEntries(); + } + }; + + public static SourceType getDefault() { + return values()[0]; + } + + 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 interface SaveListener { + void save(MemberMatches matches); + } } diff --git a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java index bd9fe3d..1fd2fa8 100644 --- a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java +++ b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java @@ -8,37 +8,37 @@ * 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 static final long serialVersionUID = -8798725308554217105L; - private float score; + private float score; - public ScoredClassEntry(ClassEntry other, float score) { - super(other); - this.score = score; - } + public ScoredClassEntry(ClassEntry other, float score) { + super(other); + this.score = score; + } - public float getScore() { - return score; - } + public float getScore() { + return score; + } - @Override - public int hashCode() { - return Float.hashCode(score) + super.hashCode(); - } + @Override + public int hashCode() { + return Float.hashCode(score) + super.hashCode(); + } - @Override - public boolean equals(Object other) { - return super.equals(other) && other instanceof ScoredClassEntry && equals((ScoredClassEntry) other); - } + @Override + public boolean equals(Object other) { + return super.equals(other) && other instanceof ScoredClassEntry && equals((ScoredClassEntry) other); + } - public boolean equals(ScoredClassEntry other) { - return other != null && score == other.score; - } + public boolean equals(ScoredClassEntry other) { + return other != null && Float.compare(score, other.score) == 0; + } } diff --git a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java index 518055f..7375111 100644 --- a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java +++ b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java @@ -8,31 +8,28 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui; - -import java.awt.Component; -import javax.swing.DefaultListCellRenderer; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.ListCellRenderer; +package cuchaz.enigma.gui; import cuchaz.enigma.analysis.Token; +import javax.swing.*; +import java.awt.*; + public class TokenListCellRenderer implements ListCellRenderer { - private GuiController controller; - private DefaultListCellRenderer defaultRenderer; + private GuiController controller; + private DefaultListCellRenderer defaultRenderer; - public TokenListCellRenderer(GuiController controller) { - this.controller = controller; - this.defaultRenderer = new DefaultListCellRenderer(); - } + public TokenListCellRenderer(GuiController controller) { + this.controller = controller; + this.defaultRenderer = new DefaultListCellRenderer(); + } - @Override - public Component getListCellRendererComponent(JList list, Token token, int index, boolean isSelected, boolean hasFocus) { - JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); - label.setText(this.controller.getReadableToken(token).toString()); - return label; - } + @Override + public Component getListCellRendererComponent(JList list, Token token, int index, boolean isSelected, boolean hasFocus) { + JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); + label.setText(this.controller.getReadableToken(token).toString()); + return label; + } } diff --git a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java index f690b15..7b3234d 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java @@ -8,63 +8,60 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.dialog; - -import java.awt.Color; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.FlowLayout; -import java.io.IOException; -import javax.swing.*; +package cuchaz.enigma.gui.dialog; import cuchaz.enigma.Constants; import cuchaz.enigma.utils.Utils; +import javax.swing.*; +import java.awt.*; +import java.io.IOException; + public class AboutDialog { - public static void show(JFrame parent) { - // init frame - final JFrame frame = new JFrame(Constants.NAME + " - About"); - final Container pane = frame.getContentPane(); - pane.setLayout(new FlowLayout()); + public static void show(JFrame parent) { + // init frame + final JFrame frame = new JFrame(Constants.NAME + " - About"); + final Container pane = frame.getContentPane(); + pane.setLayout(new FlowLayout()); - // load the content - try { - String html = Utils.readResourceToString("/about.html"); - html = String.format(html, Constants.NAME, Constants.VERSION); - JLabel label = new JLabel(html); - label.setHorizontalAlignment(JLabel.CENTER); - pane.add(label); - } catch (IOException ex) { - throw new Error(ex); - } + // load the content + try { + String html = Utils.readResourceToString("/about.html"); + html = String.format(html, Constants.NAME, Constants.VERSION); + JLabel label = new JLabel(html); + label.setHorizontalAlignment(JLabel.CENTER); + pane.add(label); + } catch (IOException ex) { + throw new Error(ex); + } - // show the link - String html = "%s"; - html = String.format(html, Constants.URL, Constants.URL); - JButton link = new JButton(html); - link.addActionListener(event -> Utils.openUrl(Constants.URL)); - link.setBorderPainted(false); - link.setOpaque(false); - link.setBackground(Color.WHITE); - link.setCursor(new Cursor(Cursor.HAND_CURSOR)); - link.setFocusable(false); - JPanel linkPanel = new JPanel(); - linkPanel.add(link); - pane.add(linkPanel); + // show the link + String html = "%s"; + html = String.format(html, Constants.URL, Constants.URL); + JButton link = new JButton(html); + link.addActionListener(event -> Utils.openUrl(Constants.URL)); + link.setBorderPainted(false); + link.setOpaque(false); + link.setBackground(Color.WHITE); + link.setCursor(new Cursor(Cursor.HAND_CURSOR)); + link.setFocusable(false); + JPanel linkPanel = new JPanel(); + linkPanel.add(link); + pane.add(linkPanel); - // show ok button - JButton okButton = new JButton("Ok"); - pane.add(okButton); - okButton.addActionListener(arg0 -> frame.dispose()); + // show ok button + JButton okButton = new JButton("Ok"); + pane.add(okButton); + okButton.addActionListener(arg0 -> frame.dispose()); - // show the frame - pane.doLayout(); - frame.setSize(400, 220); - frame.setResizable(false); - frame.setLocationRelativeTo(parent); - frame.setVisible(true); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - } + // show the frame + pane.doLayout(); + frame.setSize(400, 220); + frame.setResizable(false); + frame.setLocationRelativeTo(parent); + frame.setVisible(true); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + } } diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java index 5c1d9ff..04dd5d7 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java @@ -8,80 +8,78 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.dialog; -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.FlowLayout; -import java.io.PrintWriter; -import java.io.StringWriter; - -import javax.swing.*; +package cuchaz.enigma.gui.dialog; import cuchaz.enigma.Constants; import cuchaz.enigma.utils.Utils; +import javax.swing.*; +import java.awt.*; +import java.io.PrintWriter; +import java.io.StringWriter; + public class CrashDialog { - private static CrashDialog instance = null; + private static CrashDialog instance = null; - private JFrame frame; - private JTextArea text; + private JFrame frame; + private JTextArea text; - private CrashDialog(JFrame parent) { - // init frame - frame = new JFrame(Constants.NAME + " - Crash Report"); - final Container pane = frame.getContentPane(); - pane.setLayout(new BorderLayout()); + private CrashDialog(JFrame parent) { + // init frame + frame = new JFrame(Constants.NAME + " - Crash Report"); + final Container pane = frame.getContentPane(); + pane.setLayout(new BorderLayout()); - JLabel label = new JLabel(Constants.NAME + " has crashed! =("); - label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - pane.add(label, BorderLayout.NORTH); + JLabel label = new JLabel(Constants.NAME + " has crashed! =("); + label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + pane.add(label, BorderLayout.NORTH); - // report panel - text = new JTextArea(); - text.setTabSize(2); - pane.add(new JScrollPane(text), BorderLayout.CENTER); + // report panel + text = new JTextArea(); + text.setTabSize(2); + pane.add(new JScrollPane(text), BorderLayout.CENTER); - // buttons panel - JPanel buttonsPanel = new JPanel(); - FlowLayout buttonsLayout = new FlowLayout(); - buttonsLayout.setAlignment(FlowLayout.RIGHT); - buttonsPanel.setLayout(buttonsLayout); - buttonsPanel.add(Utils.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work."))); - JButton ignoreButton = new JButton("Ignore"); - ignoreButton.addActionListener(event -> { - // close (hide) the dialog - frame.setVisible(false); - }); - buttonsPanel.add(ignoreButton); - JButton exitButton = new JButton("Exit"); - exitButton.addActionListener(event -> { - // exit enigma - System.exit(1); - }); - buttonsPanel.add(exitButton); - pane.add(buttonsPanel, BorderLayout.SOUTH); + // buttons panel + JPanel buttonsPanel = new JPanel(); + FlowLayout buttonsLayout = new FlowLayout(); + buttonsLayout.setAlignment(FlowLayout.RIGHT); + buttonsPanel.setLayout(buttonsLayout); + buttonsPanel.add(Utils.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work."))); + JButton ignoreButton = new JButton("Ignore"); + ignoreButton.addActionListener(event -> { + // close (hide) the dialog + frame.setVisible(false); + }); + buttonsPanel.add(ignoreButton); + JButton exitButton = new JButton("Exit"); + exitButton.addActionListener(event -> { + // exit enigma + System.exit(1); + }); + buttonsPanel.add(exitButton); + pane.add(buttonsPanel, BorderLayout.SOUTH); - // show the frame - frame.setSize(600, 400); - frame.setLocationRelativeTo(parent); - frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - } + // show the frame + frame.setSize(600, 400); + frame.setLocationRelativeTo(parent); + frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } - public static void init(JFrame parent) { - instance = new CrashDialog(parent); - } + public static void init(JFrame parent) { + instance = new CrashDialog(parent); + } - public static void show(Throwable ex) { - // get the error report - StringWriter buf = new StringWriter(); - ex.printStackTrace(new PrintWriter(buf)); - String report = buf.toString(); + public static void show(Throwable ex) { + // get the error report + StringWriter buf = new StringWriter(); + ex.printStackTrace(new PrintWriter(buf)); + String report = buf.toString(); - // show it! - instance.text.setText(report); - instance.frame.doLayout(); - instance.frame.setVisible(true); - } + // show it! + instance.text.setText(report); + instance.frame.doLayout(); + instance.frame.setVisible(true); + } } diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java index 8df22a7..5f04833 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java @@ -8,92 +8,89 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.dialog; - -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import javax.swing.*; +package cuchaz.enigma.gui.dialog; import cuchaz.enigma.Constants; import cuchaz.enigma.Deobfuscator.ProgressListener; import cuchaz.enigma.utils.Utils; +import javax.swing.*; +import java.awt.*; + public class ProgressDialog implements ProgressListener, AutoCloseable { - private JFrame frame; - private JLabel labelTitle; - private JLabel labelText; - private JProgressBar progress; - - public ProgressDialog(JFrame parent) { - - // init frame - this.frame = new JFrame(Constants.NAME + " - Operation in progress"); - final Container pane = this.frame.getContentPane(); - FlowLayout layout = new FlowLayout(); - layout.setAlignment(FlowLayout.LEFT); - pane.setLayout(layout); - - this.labelTitle = new JLabel(); - pane.add(this.labelTitle); - - // set up the progress bar - JPanel panel = new JPanel(); - pane.add(panel); - panel.setLayout(new BorderLayout()); - this.labelText = Utils.unboldLabel(new JLabel()); - this.progress = new JProgressBar(); - this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); - panel.add(this.labelText, BorderLayout.NORTH); - panel.add(this.progress, BorderLayout.CENTER); - panel.setPreferredSize(new Dimension(360, 50)); - - // show the frame - pane.doLayout(); - this.frame.setSize(400, 120); - this.frame.setResizable(false); - this.frame.setLocationRelativeTo(parent); - this.frame.setVisible(true); - this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - } - - public void close() { - this.frame.dispose(); - } - - @Override - public void init(int totalWork, String title) { - this.labelTitle.setText(title); - this.progress.setMinimum(0); - this.progress.setMaximum(totalWork); - this.progress.setValue(0); - } - - @Override - public void onProgress(int numDone, String message) { - this.labelText.setText(message); - this.progress.setValue(numDone); - - // update the frame - this.frame.validate(); - this.frame.repaint(); - } - - public interface ProgressRunnable { - void run(ProgressListener listener) throws Exception; - } - - public static void runInThread(final JFrame parent, final ProgressRunnable runnable) { - new Thread(() -> - { - try (ProgressDialog progress = new ProgressDialog(parent)) { - runnable.run(progress); - } catch (Exception ex) { - throw new Error(ex); - } - }).start(); - } + private JFrame frame; + private JLabel labelTitle; + private JLabel labelText; + private JProgressBar progress; + + public ProgressDialog(JFrame parent) { + + // init frame + this.frame = new JFrame(Constants.NAME + " - Operation in progress"); + final Container pane = this.frame.getContentPane(); + FlowLayout layout = new FlowLayout(); + layout.setAlignment(FlowLayout.LEFT); + pane.setLayout(layout); + + this.labelTitle = new JLabel(); + pane.add(this.labelTitle); + + // set up the progress bar + JPanel panel = new JPanel(); + pane.add(panel); + panel.setLayout(new BorderLayout()); + this.labelText = Utils.unboldLabel(new JLabel()); + this.progress = new JProgressBar(); + this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + panel.add(this.labelText, BorderLayout.NORTH); + panel.add(this.progress, BorderLayout.CENTER); + panel.setPreferredSize(new Dimension(360, 50)); + + // show the frame + pane.doLayout(); + this.frame.setSize(400, 120); + this.frame.setResizable(false); + this.frame.setLocationRelativeTo(parent); + this.frame.setVisible(true); + this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } + + public static void runInThread(final JFrame parent, final ProgressRunnable runnable) { + new Thread(() -> + { + try (ProgressDialog progress = new ProgressDialog(parent)) { + runnable.run(progress); + } catch (Exception ex) { + throw new Error(ex); + } + }).start(); + } + + public void close() { + this.frame.dispose(); + } + + @Override + public void init(int totalWork, String title) { + this.labelTitle.setText(title); + this.progress.setMinimum(0); + this.progress.setMaximum(totalWork); + this.progress.setValue(0); + } + + @Override + public void onProgress(int numDone, String message) { + this.labelText.setText(message); + this.progress.setValue(numDone); + + // update the frame + this.frame.validate(); + this.frame.repaint(); + } + + public interface ProgressRunnable { + void run(ProgressListener listener) throws Exception; + } } diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 0ccd537..cd11aca 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -1,219 +1,207 @@ package cuchaz.enigma.gui.elements; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.dialog.AboutDialog; +import cuchaz.enigma.throwables.MappingParseException; + +import javax.swing.*; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.io.IOException; import java.util.jar.JarFile; -import javax.swing.*; - -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.dialog.AboutDialog; -import cuchaz.enigma.throwables.MappingParseException; - public class MenuBar extends JMenuBar { - private final Gui gui; - - public final JMenuItem closeJarMenu; - - public final JMenuItem openEnigmaMappingsMenu; - - public final JMenuItem saveMappingsMenu; - public final JMenuItem saveMappingEnigmaFileMenu; - public final JMenuItem saveMappingEnigmaDirectoryMenu; - public final JMenuItem saveMappingsSrgMenu; - public final JMenuItem closeMappingsMenu; - - public final JMenuItem rebuildMethodNamesMenu; - - public final JMenuItem exportSourceMenu; - public final JMenuItem exportJarMenu; + public final JMenuItem closeJarMenu; + public final JMenuItem openEnigmaMappingsMenu; + public final JMenuItem saveMappingsMenu; + public final JMenuItem saveMappingEnigmaFileMenu; + public final JMenuItem saveMappingEnigmaDirectoryMenu; + public final JMenuItem saveMappingsSrgMenu; + public final JMenuItem closeMappingsMenu; + public final JMenuItem rebuildMethodNamesMenu; + public final JMenuItem exportSourceMenu; + public final JMenuItem exportJarMenu; + private final Gui gui; - public MenuBar(Gui gui) { - this.gui = gui; + public MenuBar(Gui gui) { + this.gui = gui; - { - JMenu menu = new JMenu("File"); - this.add(menu); - { - JMenuItem item = new JMenuItem("Open Jar..."); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.jarFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - // load the jar in a separate thread - new Thread(() -> - { - try { - gui.getController().openJar(new JarFile(gui.jarFileChooser.getSelectedFile())); - } catch (IOException ex) { - throw new Error(ex); - } - }).start(); - } - }); - } - { - JMenuItem item = new JMenuItem("Close Jar"); - menu.add(item); - item.addActionListener(event -> this.gui.getController().closeJar()); - this.closeJarMenu = item; - } - menu.addSeparator(); - JMenu openMenu = new JMenu("Open Mappings..."); - menu.add(openMenu); - { - JMenuItem item = new JMenuItem("Enigma"); - openMenu.add(item); - item.addActionListener(event -> { - if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().openEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); - } catch (IOException ex) { - throw new Error(ex); - } catch (MappingParseException ex) { - JOptionPane.showMessageDialog(this.gui.getFrame(), ex.getMessage()); - } - } - }); - this.openEnigmaMappingsMenu = item; - } - { - JMenuItem item = new JMenuItem("Save Mappings"); - menu.add(item); - item.addActionListener(event -> { - try { - this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); - } catch (IOException ex) { - throw new Error(ex); - } - }); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); - this.saveMappingsMenu = item; - } - JMenu saveMenu = new JMenu("Save Mappings As..."); - menu.add(saveMenu); - { - JMenuItem item = new JMenuItem("Enigma (single file)"); - saveMenu.add(item); - item.addActionListener(event -> { - // TODO: Use a specific file chooser for it - if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), false); - this.saveMappingsMenu.setEnabled(true); - } catch (IOException ex) { - throw new Error(ex); - } - } - }); - this.saveMappingEnigmaFileMenu = item; - } - { - JMenuItem item = new JMenuItem("Enigma (directory)"); - saveMenu.add(item); - item.addActionListener(event -> { - // TODO: Use a specific file chooser for it - if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), true); - this.saveMappingsMenu.setEnabled(true); - } catch (IOException ex) { - throw new Error(ex); - } - } - }); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); - this.saveMappingEnigmaDirectoryMenu = item; - } - { - JMenuItem item = new JMenuItem("SRG (single file)"); - saveMenu.add(item); - item.addActionListener(event -> { - // TODO: Use a specific file chooser for it - if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().saveSRGMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); - this.saveMappingsMenu.setEnabled(true); - } catch (IOException ex) { - throw new Error(ex); - } - } - }); - this.saveMappingsSrgMenu = item; - } - { - JMenuItem item = new JMenuItem("Close Mappings"); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.getController().isDirty()) - { - this.gui.showDiscardDiag((response -> { - if (response == JOptionPane.YES_OPTION) - { - try - { - gui.saveMapping(); - this.gui.getController().closeMappings(); - } catch (IOException e) - { - throw new Error(e); - } - } - else if (response == JOptionPane.NO_OPTION) - this.gui.getController().closeMappings(); - return null; - }), "Save and close", "Discard changes", "Cancel"); - } - else - this.gui.getController().closeMappings(); + { + JMenu menu = new JMenu("File"); + this.add(menu); + { + JMenuItem item = new JMenuItem("Open Jar..."); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.jarFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + // load the jar in a separate thread + new Thread(() -> + { + try { + gui.getController().openJar(new JarFile(gui.jarFileChooser.getSelectedFile())); + } catch (IOException ex) { + throw new Error(ex); + } + }).start(); + } + }); + } + { + JMenuItem item = new JMenuItem("Close Jar"); + menu.add(item); + item.addActionListener(event -> this.gui.getController().closeJar()); + this.closeJarMenu = item; + } + menu.addSeparator(); + JMenu openMenu = new JMenu("Open Mappings..."); + menu.add(openMenu); + { + JMenuItem item = new JMenuItem("Enigma"); + openMenu.add(item); + item.addActionListener(event -> { + if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + try { + this.gui.getController().openEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); + } catch (IOException ex) { + throw new Error(ex); + } catch (MappingParseException ex) { + JOptionPane.showMessageDialog(this.gui.getFrame(), ex.getMessage()); + } + } + }); + this.openEnigmaMappingsMenu = item; + } + { + JMenuItem item = new JMenuItem("Save Mappings"); + menu.add(item); + item.addActionListener(event -> { + try { + this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); + } catch (IOException ex) { + throw new Error(ex); + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); + this.saveMappingsMenu = item; + } + JMenu saveMenu = new JMenu("Save Mappings As..."); + menu.add(saveMenu); + { + JMenuItem item = new JMenuItem("Enigma (single file)"); + saveMenu.add(item); + item.addActionListener(event -> { + // TODO: Use a specific file chooser for it + if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + try { + this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), false); + this.saveMappingsMenu.setEnabled(true); + } catch (IOException ex) { + throw new Error(ex); + } + } + }); + this.saveMappingEnigmaFileMenu = item; + } + { + JMenuItem item = new JMenuItem("Enigma (directory)"); + saveMenu.add(item); + item.addActionListener(event -> { + // TODO: Use a specific file chooser for it + if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + try { + this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), true); + this.saveMappingsMenu.setEnabled(true); + } catch (IOException ex) { + throw new Error(ex); + } + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + this.saveMappingEnigmaDirectoryMenu = item; + } + { + JMenuItem item = new JMenuItem("SRG (single file)"); + saveMenu.add(item); + item.addActionListener(event -> { + // TODO: Use a specific file chooser for it + if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + try { + this.gui.getController().saveSRGMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); + this.saveMappingsMenu.setEnabled(true); + } catch (IOException ex) { + throw new Error(ex); + } + } + }); + this.saveMappingsSrgMenu = item; + } + { + JMenuItem item = new JMenuItem("Close Mappings"); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.getController().isDirty()) { + this.gui.showDiscardDiag((response -> { + if (response == JOptionPane.YES_OPTION) { + try { + gui.saveMapping(); + this.gui.getController().closeMappings(); + } catch (IOException e) { + throw new Error(e); + } + } else if (response == JOptionPane.NO_OPTION) + this.gui.getController().closeMappings(); + return null; + }), "Save and close", "Discard changes", "Cancel"); + } else + this.gui.getController().closeMappings(); - }); - this.closeMappingsMenu = item; - } - menu.addSeparator(); - { - JMenuItem item = new JMenuItem("Rebuild Method Names"); - menu.add(item); - item.addActionListener(event -> this.gui.getController().rebuildMethodNames()); - this.rebuildMethodNamesMenu = item; - } - menu.addSeparator(); - { - JMenuItem item = new JMenuItem("Export Source..."); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile()); - } - }); - this.exportSourceMenu = item; - } - { - JMenuItem item = new JMenuItem("Export Jar..."); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.exportJarFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().exportJar(this.gui.exportJarFileChooser.getSelectedFile()); - } - }); - this.exportJarMenu = item; - } - menu.addSeparator(); - { - JMenuItem item = new JMenuItem("Exit"); - menu.add(item); - item.addActionListener(event -> this.gui.close()); - } - } - { - JMenu menu = new JMenu("Help"); - this.add(menu); - { - JMenuItem item = new JMenuItem("About"); - menu.add(item); - item.addActionListener(event -> AboutDialog.show(this.gui.getFrame())); - } - } - } + }); + this.closeMappingsMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem("Rebuild Method Names"); + menu.add(item); + item.addActionListener(event -> this.gui.getController().rebuildMethodNames()); + this.rebuildMethodNamesMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem("Export Source..."); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile()); + } + }); + this.exportSourceMenu = item; + } + { + JMenuItem item = new JMenuItem("Export Jar..."); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.exportJarFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + this.gui.getController().exportJar(this.gui.exportJarFileChooser.getSelectedFile()); + } + }); + this.exportJarMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem("Exit"); + menu.add(item); + item.addActionListener(event -> this.gui.close()); + } + } + { + JMenu menu = new JMenu("Help"); + this.add(menu); + { + JMenuItem item = new JMenuItem("About"); + menu.add(item); + item.addActionListener(event -> AboutDialog.show(this.gui.getFrame())); + } + } + } } diff --git a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java index e387ed3..2f6d96c 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java @@ -1,79 +1,76 @@ package cuchaz.enigma.gui.elements; -import java.awt.event.KeyEvent; - -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; -import javax.swing.KeyStroke; - import cuchaz.enigma.gui.Gui; +import javax.swing.*; +import java.awt.event.KeyEvent; + public class PopupMenuBar extends JPopupMenu { - public final JMenuItem renameMenu; - public final JMenuItem showInheritanceMenu; - public final JMenuItem showImplementationsMenu; - public final JMenuItem showCallsMenu; - public final JMenuItem openEntryMenu; - public final JMenuItem openPreviousMenu; - public final JMenuItem toggleMappingMenu; + public final JMenuItem renameMenu; + public final JMenuItem showInheritanceMenu; + public final JMenuItem showImplementationsMenu; + public final JMenuItem showCallsMenu; + public final JMenuItem openEntryMenu; + public final JMenuItem openPreviousMenu; + public final JMenuItem toggleMappingMenu; - public PopupMenuBar(Gui gui) { - { - JMenuItem menu = new JMenuItem("Rename"); - menu.addActionListener(event -> gui.startRename()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0)); - menu.setEnabled(false); - this.add(menu); - this.renameMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Show Inheritance"); - menu.addActionListener(event -> gui.showInheritance()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0)); - menu.setEnabled(false); - this.add(menu); - this.showInheritanceMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Show Implementations"); - menu.addActionListener(event -> gui.showImplementations()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0)); - menu.setEnabled(false); - this.add(menu); - this.showImplementationsMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Show Calls"); - menu.addActionListener(event -> gui.showCalls()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0)); - menu.setEnabled(false); - this.add(menu); - this.showCallsMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Go to Declaration"); - menu.addActionListener(event -> gui.navigateTo(gui.reference.entry)); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0)); - menu.setEnabled(false); - this.add(menu); - this.openEntryMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Go to previous"); - menu.addActionListener(event -> gui.getController().openPreviousReference()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0)); - menu.setEnabled(false); - this.add(menu); - this.openPreviousMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Mark as deobfuscated"); - menu.addActionListener(event -> gui.toggleMapping()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0)); - menu.setEnabled(false); - this.add(menu); - this.toggleMappingMenu = menu; - } - } + public PopupMenuBar(Gui gui) { + { + JMenuItem menu = new JMenuItem("Rename"); + menu.addActionListener(event -> gui.startRename()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0)); + menu.setEnabled(false); + this.add(menu); + this.renameMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Show Inheritance"); + menu.addActionListener(event -> gui.showInheritance()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0)); + menu.setEnabled(false); + this.add(menu); + this.showInheritanceMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Show Implementations"); + menu.addActionListener(event -> gui.showImplementations()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0)); + menu.setEnabled(false); + this.add(menu); + this.showImplementationsMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Show Calls"); + menu.addActionListener(event -> gui.showCalls()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0)); + menu.setEnabled(false); + this.add(menu); + this.showCallsMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Go to Declaration"); + menu.addActionListener(event -> gui.navigateTo(gui.reference.entry)); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0)); + menu.setEnabled(false); + this.add(menu); + this.openEntryMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Go to previous"); + menu.addActionListener(event -> gui.getController().openPreviousReference()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0)); + menu.setEnabled(false); + this.add(menu); + this.openPreviousMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Mark as deobfuscated"); + menu.addActionListener(event -> gui.toggleMapping()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0)); + menu.setEnabled(false); + this.add(menu); + this.toggleMappingMenu = menu; + } + } } diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java index 2339334..f5f6628 100644 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java +++ b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java @@ -2,10 +2,9 @@ package cuchaz.enigma.gui.filechooser; import javax.swing.*; -public class FileChooserAny extends JFileChooser -{ - public FileChooserAny() { - this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - this.setAcceptAllFileFilterUsed(false); - } +public class FileChooserAny extends JFileChooser { + public FileChooserAny() { + this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + this.setAcceptAllFileFilterUsed(false); + } } \ No newline at end of file diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java index 62a0f20..cea11a6 100644 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java +++ b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java @@ -1,8 +1,8 @@ package cuchaz.enigma.gui.filechooser; -import javax.swing.JFileChooser; +import javax.swing.*; public class FileChooserFile extends JFileChooser { - public FileChooserFile() { - } + public FileChooserFile() { + } } diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java index 93ca5d6..c16e0af 100644 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java +++ b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java @@ -1,11 +1,11 @@ package cuchaz.enigma.gui.filechooser; -import javax.swing.JFileChooser; +import javax.swing.*; public class FileChooserFolder extends JFileChooser { - public FileChooserFolder() { - this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - this.setAcceptAllFileFilterUsed(false); - } + public FileChooserFolder() { + this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + this.setAcceptAllFileFilterUsed(false); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java index 0a73088..0f64927 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java @@ -8,57 +8,54 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.highlight; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Rectangle; -import java.awt.Shape; +package cuchaz.enigma.gui.highlight; import javax.swing.text.BadLocationException; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; +import java.awt.*; public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter { - private Color fillColor; - private Color borderColor; - - protected BoxHighlightPainter(Color fillColor, Color borderColor) { - this.fillColor = fillColor; - this.borderColor = borderColor; - } - - @Override - public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { - Rectangle bounds = getBounds(text, start, end); - - // fill the area - if (this.fillColor != null) { - g.setColor(this.fillColor); - g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); - } - - // draw a box around the area - g.setColor(this.borderColor); - g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); - } - - public static Rectangle getBounds(JTextComponent text, int start, int end) { - try { - // determine the bounds of the text - Rectangle bounds = text.modelToView(start).union(text.modelToView(end)); - - // adjust the box so it looks nice - bounds.x -= 2; - bounds.width += 2; - bounds.y += 1; - bounds.height -= 2; - - return bounds; - } catch (BadLocationException ex) { - // don't care... just return something - return new Rectangle(0, 0, 0, 0); - } - } + private Color fillColor; + private Color borderColor; + + protected BoxHighlightPainter(Color fillColor, Color borderColor) { + this.fillColor = fillColor; + this.borderColor = borderColor; + } + + public static Rectangle getBounds(JTextComponent text, int start, int end) { + try { + // determine the bounds of the text + Rectangle bounds = text.modelToView(start).union(text.modelToView(end)); + + // adjust the box so it looks nice + bounds.x -= 2; + bounds.width += 2; + bounds.y += 1; + bounds.height -= 2; + + return bounds; + } catch (BadLocationException ex) { + // don't care... just return something + return new Rectangle(0, 0, 0, 0); + } + } + + @Override + public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { + Rectangle bounds = getBounds(text, start, end); + + // fill the area + if (this.fillColor != null) { + g.setColor(this.fillColor); + g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } + + // draw a box around the area + g.setColor(this.borderColor); + g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java index 5d57203..a2d2884 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java @@ -8,13 +8,14 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui.highlight; -import java.awt.Color; +import java.awt.*; public class DeobfuscatedHighlightPainter extends BoxHighlightPainter { - public DeobfuscatedHighlightPainter() { - super(new Color(220, 255, 220), new Color(80, 160, 80)); - } + public DeobfuscatedHighlightPainter() { + super(new Color(220, 255, 220), new Color(80, 160, 80)); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java index ee64d86..0947d4b 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java @@ -8,13 +8,14 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui.highlight; -import java.awt.Color; +import java.awt.*; public class ObfuscatedHighlightPainter extends BoxHighlightPainter { - public ObfuscatedHighlightPainter() { - super(new Color(255, 220, 220), new Color(160, 80, 80)); - } + public ObfuscatedHighlightPainter() { + super(new Color(255, 220, 220), new Color(160, 80, 80)); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java index 43d8352..74e7273 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java @@ -8,13 +8,14 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui.highlight; -import java.awt.Color; +import java.awt.*; public class OtherHighlightPainter extends BoxHighlightPainter { - public OtherHighlightPainter() { - super(null, new Color(180, 180, 180)); - } + public OtherHighlightPainter() { + super(null, new Color(180, 180, 180)); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java index f772284..5cbeabc 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java @@ -8,22 +8,22 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.highlight; -import java.awt.*; +package cuchaz.enigma.gui.highlight; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; +import java.awt.*; public class SelectionHighlightPainter implements Highlighter.HighlightPainter { - @Override - public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { - // draw a thick border - Graphics2D g2d = (Graphics2D) g; - Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); - g2d.setColor(Color.black); - g2d.setStroke(new BasicStroke(2.0f)); - g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); - } + @Override + public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { + // draw a thick border + Graphics2D g2d = (Graphics2D) g; + Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); + g2d.setColor(Color.black); + g2d.setStroke(new BasicStroke(2.0f)); + g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } } diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java index 9f391f2..dc933cd 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java @@ -8,58 +8,59 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.node; -import javax.swing.tree.DefaultMutableTreeNode; +package cuchaz.enigma.gui.node; import cuchaz.enigma.mapping.ClassEntry; +import javax.swing.tree.DefaultMutableTreeNode; + public class ClassSelectorClassNode extends DefaultMutableTreeNode { - private ClassEntry classEntry; + private ClassEntry classEntry; - public ClassSelectorClassNode(ClassEntry classEntry) { - this.classEntry = classEntry; - this.setUserObject(classEntry); - } + public ClassSelectorClassNode(ClassEntry classEntry) { + this.classEntry = classEntry; + this.setUserObject(classEntry); + } - public ClassEntry getClassEntry() { - return this.classEntry; - } + public ClassEntry getClassEntry() { + return this.classEntry; + } - @Override - public String toString() { - return this.classEntry.getSimpleName(); - } + @Override + public String toString() { + return this.classEntry.getSimpleName(); + } - @Override - public boolean equals(Object other) { - return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other); - } + @Override + public boolean equals(Object other) { + return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other); + } - @Override public int hashCode() - { - return 17 + (classEntry != null ? classEntry.hashCode() : 0); - } + @Override + public int hashCode() { + return 17 + (classEntry != null ? classEntry.hashCode() : 0); + } - @Override public void setUserObject(Object userObject) - { - String packageName = ""; - if (classEntry.getPackageName() != null) - packageName = classEntry.getPackageName() + "/"; - if (userObject instanceof String) - this.classEntry = new ClassEntry(packageName + userObject); - else if (userObject instanceof ClassEntry) - this.classEntry = (ClassEntry) userObject; - super.setUserObject(classEntry); - } + @Override + public Object getUserObject() { + return classEntry; + } - @Override public Object getUserObject() - { - return classEntry; - } + @Override + public void setUserObject(Object userObject) { + String packageName = ""; + if (classEntry.getPackageName() != null) + packageName = classEntry.getPackageName() + "/"; + if (userObject instanceof String) + this.classEntry = new ClassEntry(packageName + userObject); + else if (userObject instanceof ClassEntry) + this.classEntry = (ClassEntry) userObject; + super.setUserObject(classEntry); + } - public boolean equals(ClassSelectorClassNode other) { - return this.classEntry.equals(other.classEntry); - } + public boolean equals(ClassSelectorClassNode other) { + return this.classEntry.equals(other.classEntry); + } } diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java index b3eb90e..f80abba 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui.node; import javassist.bytecode.Descriptor; @@ -16,44 +17,44 @@ import javax.swing.tree.DefaultMutableTreeNode; public class ClassSelectorPackageNode extends DefaultMutableTreeNode { - private String packageName; - - public ClassSelectorPackageNode(String packageName) { - this.packageName = packageName != null ? packageName : "(none)"; - } - - public String getPackageName() { - return packageName; - } - - @Override public void setUserObject(Object userObject) - { - if (userObject instanceof String) - this.packageName = (String) userObject; - super.setUserObject(userObject); - } - - @Override public Object getUserObject() - { - return packageName; - } - - @Override - public String toString() { - return !packageName.equals("(none)") ? Descriptor.toJavaName(this.packageName) : "(none)"; - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other); - } - - @Override public int hashCode() - { - return packageName.hashCode(); - } - - public boolean equals(ClassSelectorPackageNode other) { - return other != null && this.packageName.equals(other.packageName); - } + private String packageName; + + public ClassSelectorPackageNode(String packageName) { + this.packageName = packageName != null ? packageName : "(none)"; + } + + public String getPackageName() { + return packageName; + } + + @Override + public Object getUserObject() { + return packageName; + } + + @Override + public void setUserObject(Object userObject) { + if (userObject instanceof String) + this.packageName = (String) userObject; + super.setUserObject(userObject); + } + + @Override + public String toString() { + return !packageName.equals("(none)") ? Descriptor.toJavaName(this.packageName) : "(none)"; + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other); + } + + @Override + public int hashCode() { + return packageName.hashCode(); + } + + public boolean equals(ClassSelectorPackageNode other) { + return other != null && this.packageName.equals(other.packageName); + } } diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java index 4f55175..68cc8e1 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java @@ -1,28 +1,25 @@ package cuchaz.enigma.gui.panels; -import java.awt.BorderLayout; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; - import cuchaz.enigma.gui.ClassSelector; import cuchaz.enigma.gui.Gui; +import javax.swing.*; +import java.awt.*; + public class PanelDeobf extends JPanel { - public final ClassSelector deobfClasses; - private final Gui gui; + public final ClassSelector deobfClasses; + private final Gui gui; - public PanelDeobf(Gui gui) { - this.gui = gui; + public PanelDeobf(Gui gui) { + this.gui = gui; - this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); - this.deobfClasses.setSelectionListener(gui::navigateTo); - this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); + this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); + this.deobfClasses.setSelectionListener(gui::navigateTo); + this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); - this.setLayout(new BorderLayout()); - this.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH); - this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); - } + this.setLayout(new BorderLayout()); + this.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH); + this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); + } } diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java index 914952b..fd30e38 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java @@ -1,73 +1,71 @@ package cuchaz.enigma.gui.panels; +import cuchaz.enigma.gui.BrowserCaret; +import cuchaz.enigma.gui.Gui; + +import javax.swing.*; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import javax.swing.JEditorPane; - -import cuchaz.enigma.gui.BrowserCaret; -import cuchaz.enigma.gui.Gui; - public class PanelEditor extends JEditorPane { - private final Gui gui; + private final Gui gui; - public PanelEditor(Gui gui) { - this.gui = gui; + public PanelEditor(Gui gui) { + this.gui = gui; - this.setEditable(false); - this.setSelectionColor(new Color(31, 46, 90)); - this.setCaret(new BrowserCaret()); - this.addCaretListener(event -> gui.onCaretMove(event.getDot())); - final PanelEditor self = this; - this.addMouseListener(new MouseAdapter() - { - @Override public void mouseReleased(MouseEvent e) - { - if (e.getButton() == MouseEvent.BUTTON3) - self.setCaretPosition(self.viewToModel(e.getPoint())); - } - }); - this.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.VK_R: - gui.popupMenu.renameMenu.doClick(); - break; + this.setEditable(false); + this.setSelectionColor(new Color(31, 46, 90)); + this.setCaret(new BrowserCaret()); + this.addCaretListener(event -> gui.onCaretMove(event.getDot())); + final PanelEditor self = this; + this.addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON3) + self.setCaretPosition(self.viewToModel(e.getPoint())); + } + }); + this.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_R: + gui.popupMenu.renameMenu.doClick(); + break; - case KeyEvent.VK_I: - gui.popupMenu.showInheritanceMenu.doClick(); - break; + case KeyEvent.VK_I: + gui.popupMenu.showInheritanceMenu.doClick(); + break; - case KeyEvent.VK_M: - gui.popupMenu.showImplementationsMenu.doClick(); - break; + case KeyEvent.VK_M: + gui.popupMenu.showImplementationsMenu.doClick(); + break; - case KeyEvent.VK_N: - gui.popupMenu.openEntryMenu.doClick(); - break; + case KeyEvent.VK_N: + gui.popupMenu.openEntryMenu.doClick(); + break; - case KeyEvent.VK_P: - gui.popupMenu.openPreviousMenu.doClick(); - break; + case KeyEvent.VK_P: + gui.popupMenu.openPreviousMenu.doClick(); + break; - case KeyEvent.VK_C: - gui.popupMenu.showCallsMenu.doClick(); - break; + case KeyEvent.VK_C: + gui.popupMenu.showCallsMenu.doClick(); + break; - case KeyEvent.VK_T: - gui.popupMenu.toggleMappingMenu.doClick(); - break; - case KeyEvent.VK_F5: - gui.getController().refreshCurrentClass(); - break; - default: - break; - } - } - }); - } + case KeyEvent.VK_T: + gui.popupMenu.toggleMappingMenu.doClick(); + break; + case KeyEvent.VK_F5: + gui.getController().refreshCurrentClass(); + break; + default: + break; + } + } + }); + } } diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java index faa023b..1bf6887 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java @@ -1,34 +1,30 @@ package cuchaz.enigma.gui.panels; -import java.awt.Dimension; -import java.awt.GridLayout; - -import javax.swing.BorderFactory; -import javax.swing.JLabel; -import javax.swing.JPanel; - import cuchaz.enigma.gui.Gui; import cuchaz.enigma.utils.Utils; +import javax.swing.*; +import java.awt.*; + public class PanelIdentifier extends JPanel { - private final Gui gui; + private final Gui gui; - public PanelIdentifier(Gui gui) { - this.gui = gui; + public PanelIdentifier(Gui gui) { + this.gui = gui; - this.setLayout(new GridLayout(4, 1, 0, 0)); - this.setPreferredSize(new Dimension(0, 100)); - this.setBorder(BorderFactory.createTitledBorder("Identifier Info")); - } + this.setLayout(new GridLayout(4, 1, 0, 0)); + this.setPreferredSize(new Dimension(0, 100)); + this.setBorder(BorderFactory.createTitledBorder("Identifier Info")); + } - public void clearReference() { - this.removeAll(); - JLabel label = new JLabel("No identifier selected"); - Utils.unboldLabel(label); - label.setHorizontalAlignment(JLabel.CENTER); - this.add(label); + public void clearReference() { + this.removeAll(); + JLabel label = new JLabel("No identifier selected"); + Utils.unboldLabel(label); + label.setHorizontalAlignment(JLabel.CENTER); + this.add(label); - gui.redraw(); - } + gui.redraw(); + } } diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java index 27bb70b..4bbd32b 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java @@ -1,39 +1,36 @@ package cuchaz.enigma.gui.panels; -import java.awt.BorderLayout; -import java.util.Comparator; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; - import cuchaz.enigma.gui.ClassSelector; import cuchaz.enigma.gui.Gui; import cuchaz.enigma.mapping.ClassEntry; +import javax.swing.*; +import java.awt.*; +import java.util.Comparator; + public class PanelObf extends JPanel { - private final Gui gui; - public final ClassSelector obfClasses; - - public PanelObf(Gui gui) { - this.gui = gui; - - Comparator obfClassComparator = (a, b) -> { - String aname = a.getName(); - String bname = b.getName(); - if (aname.length() != bname.length()) { - return aname.length() - bname.length(); - } - return aname.compareTo(bname); - }; - - this.obfClasses = new ClassSelector(gui, obfClassComparator, false); - this.obfClasses.setSelectionListener(gui::navigateTo); - this.obfClasses.setRenameSelectionListener(gui::onPanelRename); - - this.setLayout(new BorderLayout()); - this.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH); - this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); - } + public final ClassSelector obfClasses; + private final Gui gui; + + public PanelObf(Gui gui) { + this.gui = gui; + + Comparator obfClassComparator = (a, b) -> { + String aname = a.getName(); + String bname = b.getName(); + if (aname.length() != bname.length()) { + return aname.length() - bname.length(); + } + return aname.compareTo(bname); + }; + + this.obfClasses = new ClassSelector(gui, obfClassComparator, false); + this.obfClasses.setSelectionListener(gui::navigateTo); + this.obfClasses.setRenameSelectionListener(gui::onPanelRename); + + this.setLayout(new BorderLayout()); + this.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH); + this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); + } } -- cgit v1.2.3