/******************************************************************************* * Copyright (c) 2014 Jeff Martin. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ package cuchaz.enigma.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; import java.lang.Thread.UncaughtExceptionHandler; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Vector; import javax.swing.BorderFactory; import javax.swing.JEditorPane; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.WindowConstants; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.text.BadLocationException; import javax.swing.text.Highlighter; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import jsyntaxpane.DefaultSyntaxKit; import com.google.common.collect.Lists; import cuchaz.enigma.Constants; import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; import cuchaz.enigma.analysis.ClassImplementationsTreeNode; import cuchaz.enigma.analysis.ClassInheritanceTreeNode; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.FieldReferenceTreeNode; import cuchaz.enigma.analysis.MethodImplementationsTreeNode; import cuchaz.enigma.analysis.MethodInheritanceTreeNode; import cuchaz.enigma.analysis.ReferenceTreeNode; import cuchaz.enigma.analysis.Token; import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.IllegalNameException; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.MethodEntry; public class Gui { private GuiController m_controller; // controls private JFrame m_frame; private ClassSelector m_obfClasses; private ClassSelector m_deobfClasses; private JEditorPane m_editor; private JPanel m_classesPanel; private JSplitPane m_splitClasses; private JPanel m_infoPanel; private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter; private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter; private OtherHighlightPainter m_otherHighlightPainter; private SelectionHighlightPainter m_selectionHighlightPainter; private JTree m_inheritanceTree; private JTree m_implementationsTree; private JTree m_callsTree; private JList m_tokens; private JTabbedPane m_tabs; // dynamic menu items private JMenuItem m_closeJarMenu; private JMenuItem m_openMappingsMenu; private JMenuItem m_saveMappingsMenu; private JMenuItem m_saveMappingsAsMenu; private JMenuItem m_closeMappingsMenu; private JMenuItem m_renameMenu; private JMenuItem m_showInheritanceMenu; private JMenuItem m_openEntryMenu; private JMenuItem m_openPreviousMenu; private JMenuItem m_showCallsMenu; private JMenuItem m_showImplementationsMenu; private JMenuItem m_toggleMappingMenu; private JMenuItem m_exportSourceMenu; private JMenuItem m_exportJarMenu; // state private EntryReference m_reference; private JFileChooser m_jarFileChooser; private JFileChooser m_mappingsFileChooser; private JFileChooser m_exportSourceFileChooser; private JFileChooser m_exportJarFileChooser; public Gui( ) { // init frame m_frame = new JFrame( Constants.Name ); final Container pane = m_frame.getContentPane(); pane.setLayout( new BorderLayout() ); if( Boolean.parseBoolean( System.getProperty( "enigma.catchExceptions", "true" ) ) ) { // install a global exception handler to the event thread CrashDialog.init( m_frame ); Thread.setDefaultUncaughtExceptionHandler( new UncaughtExceptionHandler( ) { @Override public void uncaughtException( Thread thread, Throwable ex ) { ex.printStackTrace( System.err ); CrashDialog.show( ex ); } } ); } m_controller = new GuiController( this ); // init file choosers m_jarFileChooser = new JFileChooser(); m_mappingsFileChooser = new JFileChooser(); m_exportSourceFileChooser = new JFileChooser(); m_exportSourceFileChooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); m_exportJarFileChooser = new JFileChooser(); // init obfuscated classes list m_obfClasses = new ClassSelector( ClassSelector.ObfuscatedClassEntryComparator ); m_obfClasses.setListener( new ClassSelectionListener( ) { @Override public void onSelectClass( ClassEntry classEntry ) { navigateTo( classEntry ); } } ); JScrollPane obfScroller = new JScrollPane( m_obfClasses ); JPanel obfPanel = new JPanel(); obfPanel.setLayout( new BorderLayout() ); obfPanel.add( new JLabel( "Obfuscated Classes" ), BorderLayout.NORTH ); obfPanel.add( obfScroller, BorderLayout.CENTER ); // init deobfuscated classes list m_deobfClasses = new ClassSelector( ClassSelector.DeobfuscatedClassEntryComparator ); m_deobfClasses.setListener( new ClassSelectionListener( ) { @Override public void onSelectClass( ClassEntry classEntry ) { navigateTo( classEntry ); } } ); JScrollPane deobfScroller = new JScrollPane( m_deobfClasses ); JPanel deobfPanel = new JPanel(); deobfPanel.setLayout( new BorderLayout() ); deobfPanel.add( new JLabel( "De-obfuscated Classes" ), BorderLayout.NORTH ); deobfPanel.add( deobfScroller, BorderLayout.CENTER ); // set up classes panel (don't add the splitter yet) m_splitClasses = new JSplitPane( JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel ); m_splitClasses.setResizeWeight( 0.3 ); m_classesPanel = new JPanel(); m_classesPanel.setLayout( new BorderLayout() ); m_classesPanel.setPreferredSize( new Dimension( 250, 0 ) ); // init info panel m_infoPanel = new JPanel(); m_infoPanel.setLayout( new GridLayout( 4, 1, 0, 0 ) ); m_infoPanel.setPreferredSize( new Dimension( 0, 100 ) ); m_infoPanel.setBorder( BorderFactory.createTitledBorder( "Identifier Info" ) ); clearReference(); // init editor DefaultSyntaxKit.initKit(); m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter(); m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter(); m_otherHighlightPainter = new OtherHighlightPainter(); m_selectionHighlightPainter = new SelectionHighlightPainter(); m_editor = new JEditorPane(); m_editor.setEditable( false ); m_editor.setCaret( new BrowserCaret() ); JScrollPane sourceScroller = new JScrollPane( m_editor ); m_editor.setContentType( "text/java" ); m_editor.addCaretListener( new CaretListener( ) { @Override public void caretUpdate( CaretEvent event ) { onCaretMove( event.getDot() ); } } ); m_editor.addKeyListener( new KeyAdapter( ) { @Override public void keyPressed( KeyEvent event ) { switch( event.getKeyCode() ) { case KeyEvent.VK_R: m_renameMenu.doClick(); break; case KeyEvent.VK_I: m_showInheritanceMenu.doClick(); break; case KeyEvent.VK_M: m_showImplementationsMenu.doClick(); break; case KeyEvent.VK_N: m_openEntryMenu.doClick(); break; case KeyEvent.VK_P: m_openPreviousMenu.doClick(); break; case KeyEvent.VK_C: m_showCallsMenu.doClick(); break; case KeyEvent.VK_T: m_toggleMappingMenu.doClick(); break; } } } ); // turn off token highlighting (it's wrong most of the time anyway...) DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit(); kit.toggleComponent( m_editor, "jsyntaxpane.components.TokenMarker" ); // init editor popup menu JPopupMenu popupMenu = new JPopupMenu(); m_editor.setComponentPopupMenu( popupMenu ); { JMenuItem menu = new JMenuItem( "Rename" ); menu.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { startRename(); } } ); menu.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_R, 0 ) ); menu.setEnabled( false ); popupMenu.add( menu ); m_renameMenu = menu; } { JMenuItem menu = new JMenuItem( "Show Inheritance" ); menu.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { showInheritance(); } } ); menu.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_I, 0 ) ); menu.setEnabled( false ); popupMenu.add( menu ); m_showInheritanceMenu = menu; } { JMenuItem menu = new JMenuItem( "Show Implementations" ); menu.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { showImplementations(); } } ); menu.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_M, 0 ) ); menu.setEnabled( false ); popupMenu.add( menu ); m_showImplementationsMenu = menu; } { JMenuItem menu = new JMenuItem( "Show Calls" ); menu.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { showCalls(); } } ); menu.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_C, 0 ) ); menu.setEnabled( false ); popupMenu.add( menu ); m_showCallsMenu = menu; } { JMenuItem menu = new JMenuItem( "Go to Declaration" ); menu.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { navigateTo( m_reference.entry ); } } ); menu.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_N, 0 ) ); menu.setEnabled( false ); popupMenu.add( menu ); m_openEntryMenu = menu; } { JMenuItem menu = new JMenuItem( "Go to previous" ); menu.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { m_controller.openPreviousReference(); } } ); menu.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_P, 0 ) ); menu.setEnabled( false ); popupMenu.add( menu ); m_openPreviousMenu = menu; } { JMenuItem menu = new JMenuItem( "Mark as deobfuscated" ); menu.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { toggleMapping(); } } ); menu.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_T, 0 ) ); menu.setEnabled( false ); popupMenu.add( menu ); m_toggleMappingMenu = menu; } // init inheritance panel m_inheritanceTree = new JTree(); m_inheritanceTree.setModel( null ); m_inheritanceTree.addMouseListener( new MouseAdapter( ) { @Override public void mouseClicked( MouseEvent event ) { if( event.getClickCount() == 2 ) { // get the selected node TreePath path = m_inheritanceTree.getSelectionPath(); if( path == null ) { return; } Object node = path.getLastPathComponent(); if( node instanceof ClassInheritanceTreeNode ) { ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node; navigateTo( new ClassEntry( classNode.getObfClassName() ) ); } else if( node instanceof MethodInheritanceTreeNode ) { MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node; if( methodNode.isImplemented() ) { navigateTo( methodNode.getMethodEntry() ); } } } } } ); JPanel inheritancePanel = new JPanel(); inheritancePanel.setLayout( new BorderLayout() ); inheritancePanel.add( new JScrollPane( m_inheritanceTree ) ); // init implementations panel m_implementationsTree = new JTree(); m_implementationsTree.setModel( null ); m_implementationsTree.addMouseListener( new MouseAdapter( ) { @Override public void mouseClicked( MouseEvent event ) { if( event.getClickCount() == 2 ) { // get the selected node TreePath path = m_implementationsTree.getSelectionPath(); if( path == null ) { return; } Object node = path.getLastPathComponent(); if( node instanceof ClassImplementationsTreeNode ) { ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node; navigateTo( classNode.getClassEntry() ); } else if( node instanceof MethodImplementationsTreeNode ) { MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node; navigateTo( methodNode.getMethodEntry() ); } } } } ); JPanel implementationsPanel = new JPanel(); implementationsPanel.setLayout( new BorderLayout() ); implementationsPanel.add( new JScrollPane( m_implementationsTree ) ); // init call panel m_callsTree = new JTree(); m_callsTree.setModel( null ); m_callsTree.addMouseListener( new MouseAdapter( ) { @SuppressWarnings( "unchecked" ) @Override public void mouseClicked( MouseEvent event ) { if( event.getClickCount() == 2 ) { // get the selected node TreePath path = m_callsTree.getSelectionPath(); if( path == null ) { return; } Object node = path.getLastPathComponent(); if( node instanceof ReferenceTreeNode ) { ReferenceTreeNode referenceNode = ((ReferenceTreeNode)node); if( referenceNode.getReference() != null ) { navigateTo( referenceNode.getReference() ); } else { navigateTo( referenceNode.getEntry() ); } } } } } ); m_tokens = new JList(); m_tokens.setCellRenderer( new TokenListCellRenderer( m_controller ) ); m_tokens.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); m_tokens.setLayoutOrientation( JList.VERTICAL ); m_tokens.addMouseListener( new MouseAdapter() { @Override public void mouseClicked( MouseEvent event ) { if( event.getClickCount() == 2 ) { Token selected = m_tokens.getSelectedValue(); if( selected != null ) { showToken( selected ); } } } } ); m_tokens.setPreferredSize( new Dimension( 0, 200 ) ); m_tokens.setMinimumSize( new Dimension( 0, 200 ) ); JSplitPane callPanel = new JSplitPane( JSplitPane.VERTICAL_SPLIT, true, new JScrollPane( m_callsTree ), new JScrollPane( m_tokens ) ); callPanel.setResizeWeight( 1 ); // let the top side take all the slack callPanel.resetToPreferredSizes(); // layout controls JPanel centerPanel = new JPanel(); centerPanel.setLayout( new BorderLayout() ); centerPanel.add( m_infoPanel, BorderLayout.NORTH ); centerPanel.add( sourceScroller, BorderLayout.CENTER ); m_tabs = new JTabbedPane(); m_tabs.setPreferredSize( new Dimension( 250, 0 ) ); m_tabs.addTab( "Inheritance", inheritancePanel ); m_tabs.addTab( "Implementations", implementationsPanel ); m_tabs.addTab( "Call Graph", callPanel ); JSplitPane splitRight = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs ); splitRight.setResizeWeight( 1 ); // let the left side take all the slack splitRight.resetToPreferredSizes(); JSplitPane splitCenter = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight ); splitCenter.setResizeWeight( 0 ); // let the right side take all the slack pane.add( splitCenter, BorderLayout.CENTER ); // init menus JMenuBar menuBar = new JMenuBar(); m_frame.setJMenuBar( menuBar ); { JMenu menu = new JMenu( "File" ); menuBar.add( menu ); { JMenuItem item = new JMenuItem( "Open Jar..." ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { if( m_jarFileChooser.showOpenDialog( m_frame ) == JFileChooser.APPROVE_OPTION ) { // load the jar in a separate thread new Thread( ) { @Override public void run( ) { try { m_controller.openJar( m_jarFileChooser.getSelectedFile() ); } catch( IOException ex ) { throw new Error( ex ); } } }.start(); } } } ); } { JMenuItem item = new JMenuItem( "Close Jar" ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { m_controller.closeJar(); } } ); m_closeJarMenu = item; } menu.addSeparator(); { JMenuItem item = new JMenuItem( "Open Mappings..." ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { if( m_mappingsFileChooser.showOpenDialog( m_frame ) == JFileChooser.APPROVE_OPTION ) { try { m_controller.openMappings( m_mappingsFileChooser.getSelectedFile() ); } catch( IOException ex ) { throw new Error( ex ); } catch( MappingParseException ex ) { JOptionPane.showMessageDialog( m_frame, ex.getMessage() ); } } } } ); m_openMappingsMenu = item; } { JMenuItem item = new JMenuItem( "Save Mappings" ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { try { m_controller.saveMappings( m_mappingsFileChooser.getSelectedFile() ); } catch( IOException ex ) { throw new Error( ex ); } } } ); item.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK ) ); m_saveMappingsMenu = item; } { JMenuItem item = new JMenuItem( "Save Mappings As..." ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { if( m_mappingsFileChooser.showSaveDialog( m_frame ) == JFileChooser.APPROVE_OPTION ) { try { m_controller.saveMappings( m_mappingsFileChooser.getSelectedFile() ); m_saveMappingsMenu.setEnabled( true ); } catch( IOException ex ) { throw new Error( ex ); } } } } ); item.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK ) ); m_saveMappingsAsMenu = item; } { JMenuItem item = new JMenuItem( "Close Mappings" ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { m_controller.closeMappings(); } } ); m_closeMappingsMenu = item; } menu.addSeparator(); { JMenuItem item = new JMenuItem( "Export Source..." ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { if( m_exportSourceFileChooser.showSaveDialog( m_frame ) == JFileChooser.APPROVE_OPTION ) { m_controller.exportSource( m_exportSourceFileChooser.getSelectedFile() ); } } } ); m_exportSourceMenu = item; } { JMenuItem item = new JMenuItem( "Export Jar..." ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { if( m_exportJarFileChooser.showSaveDialog( m_frame ) == JFileChooser.APPROVE_OPTION ) { m_controller.exportJar( m_exportJarFileChooser.getSelectedFile() ); } } } ); m_exportJarMenu = item; } menu.addSeparator(); { JMenuItem item = new JMenuItem( "Exit" ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { close(); } } ); } } { JMenu menu = new JMenu( "Help" ); menuBar.add( menu ); { JMenuItem item = new JMenuItem( "About" ); menu.add( item ); item.addActionListener( new ActionListener( ) { @Override public void actionPerformed( ActionEvent event ) { AboutDialog.show( m_frame ); } } ); } } // init state onCloseJar(); m_frame.addWindowListener( new WindowAdapter( ) { @Override public void windowClosing( WindowEvent event ) { close(); } } ); // show the frame pane.doLayout(); m_frame.setSize( 1024, 576 ); m_frame.setMinimumSize( new Dimension( 640, 480 ) ); m_frame.setVisible( true ); m_frame.setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE ); } public JFrame getFrame( ) { return m_frame; } public GuiController getController( ) { return m_controller; } public void onStartOpenJar( ) { m_classesPanel.removeAll(); JPanel panel = new JPanel(); panel.setLayout( new FlowLayout() ); panel.add( new JLabel( "Loading..." ) ); m_classesPanel.add( panel ); redraw(); } public void onFinishOpenJar( String jarName ) { // update gui m_frame.setTitle( Constants.Name + " - " + jarName ); m_classesPanel.removeAll(); m_classesPanel.add( m_splitClasses ); setSource( null ); // update menu m_closeJarMenu.setEnabled( true ); m_openMappingsMenu.setEnabled( true ); m_saveMappingsMenu.setEnabled( false ); m_saveMappingsAsMenu.setEnabled( true ); m_closeMappingsMenu.setEnabled( true ); m_exportSourceMenu.setEnabled( true ); m_exportJarMenu.setEnabled( true ); redraw(); } public void onCloseJar( ) { // update gui m_frame.setTitle( Constants.Name ); setObfClasses( null ); setDeobfClasses( null ); setSource( null ); m_classesPanel.removeAll(); // update menu m_closeJarMenu.setEnabled( false ); m_openMappingsMenu.setEnabled( false ); m_saveMappingsMenu.setEnabled( false ); m_saveMappingsAsMenu.setEnabled( false ); m_closeMappingsMenu.setEnabled( false ); m_exportSourceMenu.setEnabled( false ); m_exportJarMenu.setEnabled( false ); redraw(); } public void setObfClasses( Collection obfClasses ) { m_obfClasses.setClasses( obfClasses ); } public void setDeobfClasses( Collection deobfClasses ) { m_deobfClasses.setClasses( deobfClasses ); } public void setMappingsFile( File file ) { m_mappingsFileChooser.setSelectedFile( file ); m_saveMappingsMenu.setEnabled( file != null ); } public void setSource( String source ) { m_editor.getHighlighter().removeAllHighlights(); m_editor.setText( source ); } public void showToken( final Token token ) { if( token == null ) { throw new IllegalArgumentException( "Token cannot be null!" ); } // set the caret position to the token m_editor.setCaretPosition( token.start ); m_editor.grabFocus(); try { // make sure the token is visible in the scroll window Rectangle start = m_editor.modelToView( token.start ); Rectangle end = m_editor.modelToView( token.end ); final Rectangle show = start.union( end ); show.grow( start.width*10, start.height*6 ); SwingUtilities.invokeLater( new Runnable( ) { @Override public void run( ) { m_editor.scrollRectToVisible( show ); } } ); } catch( BadLocationException ex ) { throw new Error( ex ); } // highlight the token momentarily final Timer timer = new Timer( 200, new ActionListener( ) { private int m_counter = 0; private Object m_highlight = null; @Override public void actionPerformed( ActionEvent event ) { if( m_counter % 2 == 0 ) { try { m_highlight = m_editor.getHighlighter().addHighlight( token.start, token.end, m_selectionHighlightPainter ); } catch( BadLocationException ex ) { // don't care } } else if( m_highlight != null ) { m_editor.getHighlighter().removeHighlight( m_highlight ); } if( m_counter++ > 6 ) { Timer timer = (Timer)event.getSource(); timer.stop(); } } } ); timer.start(); redraw(); } public void showTokens( Collection tokens ) { Vector sortedTokens = new Vector( tokens ); Collections.sort( sortedTokens ); if( sortedTokens.size() > 1 ) { // sort the tokens and update the tokens panel m_tokens.setListData( sortedTokens ); m_tokens.setSelectedIndex( 0 ); } else { m_tokens.setListData( new Vector() ); } // show the first token showToken( sortedTokens.get( 0 ) ); } public void setHighlightedTokens( Iterable obfuscatedTokens, Iterable deobfuscatedTokens, Iterable otherTokens ) { // remove any old highlighters m_editor.getHighlighter().removeAllHighlights(); // color things based on the index if( obfuscatedTokens != null ) { setHighlightedTokens( obfuscatedTokens, m_obfuscatedHighlightPainter ); } if( deobfuscatedTokens != null ) { setHighlightedTokens( deobfuscatedTokens, m_deobfuscatedHighlightPainter ); } if( otherTokens != null ) { setHighlightedTokens( otherTokens, m_otherHighlightPainter ); } redraw(); } private void setHighlightedTokens( Iterable tokens, Highlighter.HighlightPainter painter ) { for( Token token : tokens ) { try { m_editor.getHighlighter().addHighlight( token.start, token.end, painter ); } catch( BadLocationException ex ) { throw new IllegalArgumentException( ex ); } } } private void clearReference( ) { m_infoPanel.removeAll(); JLabel label = new JLabel( "No identifier selected" ); GuiTricks.unboldLabel( label ); label.setHorizontalAlignment( JLabel.CENTER ); m_infoPanel.add( label ); redraw(); } private void showReference( EntryReference reference ) { if( reference == null ) { clearReference(); return; } m_reference = reference; m_infoPanel.removeAll(); if( reference.entry instanceof ClassEntry ) { showClassEntry( (ClassEntry)m_reference.entry ); } else if( m_reference.entry instanceof FieldEntry ) { showFieldEntry( (FieldEntry)m_reference.entry ); } else if( m_reference.entry instanceof MethodEntry ) { showMethodEntry( (MethodEntry)m_reference.entry ); } else if( m_reference.entry instanceof ConstructorEntry ) { showConstructorEntry( (ConstructorEntry)m_reference.entry ); } else if( m_reference.entry instanceof ArgumentEntry ) { showArgumentEntry( (ArgumentEntry)m_reference.entry ); } else { throw new Error( "Unknown entry type: " + m_reference.entry.getClass().getName() ); } redraw(); } private void showClassEntry( ClassEntry entry ) { addNameValue( m_infoPanel, "Class", entry.getName() ); } private void showFieldEntry( FieldEntry entry ) { addNameValue( m_infoPanel, "Field", entry.getName() ); addNameValue( m_infoPanel, "Class", entry.getClassEntry().getName() ); } private void showMethodEntry( MethodEntry entry ) { addNameValue( m_infoPanel, "Method", entry.getName() ); addNameValue( m_infoPanel, "Class", entry.getClassEntry().getName() ); addNameValue( m_infoPanel, "Signature", entry.getSignature() ); } private void showConstructorEntry( ConstructorEntry entry ) { addNameValue( m_infoPanel, "Constructor", entry.getClassEntry().getName() ); addNameValue( m_infoPanel, "Signature", entry.getSignature() ); } private void showArgumentEntry( ArgumentEntry entry ) { addNameValue( m_infoPanel, "Argument", entry.getName() ); addNameValue( m_infoPanel, "Class", entry.getClassEntry().getName() ); addNameValue( m_infoPanel, "Method", entry.getBehaviorEntry().getName() ); addNameValue( m_infoPanel, "Index", Integer.toString( entry.getIndex() ) ); } private void addNameValue( JPanel container, String name, String value ) { JPanel panel = new JPanel(); panel.setLayout( new FlowLayout( FlowLayout.LEFT, 6, 0 ) ); container.add( panel ); JLabel label = new JLabel( name + ":", JLabel.RIGHT ); label.setPreferredSize( new Dimension( 100, label.getPreferredSize().height ) ); panel.add( label ); panel.add( GuiTricks.unboldLabel( new JLabel( value, JLabel.LEFT ) ) ); } private void onCaretMove( int pos ) { Token token = m_controller.getToken( pos ); boolean isToken = token != null; m_reference = m_controller.getDeobfReference( token ); boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry; boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry; boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry; boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry; boolean isInJar = isToken && m_controller.entryIsInJar( m_reference.entry ); boolean isRenameable = isToken && m_controller.referenceIsRenameable( m_reference ); if( isToken ) { showReference( m_reference ); } else { clearReference(); } m_renameMenu.setEnabled( isRenameable && isToken ); m_showInheritanceMenu.setEnabled( isClassEntry || isMethodEntry || isConstructorEntry ); m_showImplementationsMenu.setEnabled( isClassEntry || isMethodEntry ); m_showCallsMenu.setEnabled( isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry ); m_openEntryMenu.setEnabled( isInJar && ( isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry ) ); m_openPreviousMenu.setEnabled( m_controller.hasPreviousLocation() ); m_toggleMappingMenu.setEnabled( isRenameable && isToken ); if( isToken && m_controller.entryHasDeobfuscatedName( m_reference.entry ) ) { m_toggleMappingMenu.setText( "Reset to obfuscated" ); } else { m_toggleMappingMenu.setText( "Mark as deobfuscated" ); } } private void navigateTo( Entry entry ) { if( !m_controller.entryIsInJar( entry ) ) { // entry is not in the jar. Ignore it return; } if( m_reference != null ) { m_controller.savePreviousReference( m_reference ); } m_controller.openDeclaration( entry ); } private void navigateTo( EntryReference reference ) { if( !m_controller.entryIsInJar( reference.getLocationClassEntry() ) ) { // reference is not in the jar. Ignore it return; } if( m_reference != null ) { m_controller.savePreviousReference( m_reference ); } m_controller.openReference( reference ); } private void startRename( ) { // init the text box final JTextField text = new JTextField(); text.setText( m_reference.getNameableEntry().getName() ); text.setPreferredSize( new Dimension( 360, text.getPreferredSize().height ) ); text.addKeyListener( new KeyAdapter( ) { @Override public void keyPressed( KeyEvent event ) { switch( event.getKeyCode() ) { case KeyEvent.VK_ENTER: finishRename( text, true ); break; case KeyEvent.VK_ESCAPE: finishRename( text, false ); break; } } } ); // find the label with the name and replace it with the text box JPanel panel = (JPanel)m_infoPanel.getComponent( 0 ); panel.remove( panel.getComponentCount() - 1 ); panel.add( text ); text.grabFocus(); text.selectAll(); redraw(); } private void finishRename( JTextField text, boolean saveName ) { String newName = text.getText(); if( saveName && newName != null && newName.length() > 0 ) { try { m_controller.rename( m_reference, newName ); } catch( IllegalNameException ex ) { text.setBorder( BorderFactory.createLineBorder( Color.red, 1 ) ); text.setToolTipText( ex.getReason() ); GuiTricks.showToolTipNow( text ); } return; } // abort the rename JPanel panel = (JPanel)m_infoPanel.getComponent( 0 ); panel.remove( panel.getComponentCount() - 1 ); panel.add( GuiTricks.unboldLabel( new JLabel( m_reference.getNameableEntry().getName(), JLabel.LEFT ) ) ); m_editor.grabFocus(); redraw(); } private void showInheritance( ) { if( m_reference == null ) { return; } m_inheritanceTree.setModel( null ); if( m_reference.entry instanceof ClassEntry ) { // get the class inheritance ClassInheritanceTreeNode classNode = m_controller.getClassInheritance( (ClassEntry)m_reference.entry ); // show the tree at the root TreePath path = getPathToRoot( classNode ); m_inheritanceTree.setModel( new DefaultTreeModel( (TreeNode)path.getPathComponent( 0 ) ) ); m_inheritanceTree.expandPath( path ); m_inheritanceTree.setSelectionRow( m_inheritanceTree.getRowForPath( path ) ); } else if( m_reference.entry instanceof MethodEntry ) { // get the method inheritance MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance( (MethodEntry)m_reference.entry ); // show the tree at the root TreePath path = getPathToRoot( classNode ); m_inheritanceTree.setModel( new DefaultTreeModel( (TreeNode)path.getPathComponent( 0 ) ) ); m_inheritanceTree.expandPath( path ); m_inheritanceTree.setSelectionRow( m_inheritanceTree.getRowForPath( path ) ); } m_tabs.setSelectedIndex( 0 ); redraw(); } private void showImplementations( ) { if( m_reference == null ) { return; } m_implementationsTree.setModel( null ); if( m_reference.entry instanceof ClassEntry ) { // get the class implementations ClassImplementationsTreeNode node = m_controller.getClassImplementations( (ClassEntry)m_reference.entry ); if( node != null ) { // show the tree at the root TreePath path = getPathToRoot( node ); m_implementationsTree.setModel( new DefaultTreeModel( (TreeNode)path.getPathComponent( 0 ) ) ); m_implementationsTree.expandPath( path ); m_implementationsTree.setSelectionRow( m_implementationsTree.getRowForPath( path ) ); } } else if( m_reference.entry instanceof MethodEntry ) { // get the method implementations MethodImplementationsTreeNode node = m_controller.getMethodImplementations( (MethodEntry)m_reference.entry ); if( node != null ) { // show the tree at the root TreePath path = getPathToRoot( node ); m_implementationsTree.setModel( new DefaultTreeModel( (TreeNode)path.getPathComponent( 0 ) ) ); m_implementationsTree.expandPath( path ); m_implementationsTree.setSelectionRow( m_implementationsTree.getRowForPath( path ) ); } } m_tabs.setSelectedIndex( 1 ); redraw(); } private void showCalls( ) { if( m_reference == null ) { return; } if( m_reference.entry instanceof ClassEntry ) { // look for calls to the default constructor // TODO: get a list of all the constructors and find calls to all of them BehaviorReferenceTreeNode node = m_controller.getMethodReferences( new ConstructorEntry( (ClassEntry)m_reference.entry, "()V" ) ); m_callsTree.setModel( new DefaultTreeModel( node ) ); } else if( m_reference.entry instanceof FieldEntry ) { FieldReferenceTreeNode node = m_controller.getFieldReferences( (FieldEntry)m_reference.entry ); m_callsTree.setModel( new DefaultTreeModel( node ) ); } else if( m_reference.entry instanceof MethodEntry ) { BehaviorReferenceTreeNode node = m_controller.getMethodReferences( (MethodEntry)m_reference.entry ); m_callsTree.setModel( new DefaultTreeModel( node ) ); } else if( m_reference.entry instanceof ConstructorEntry ) { BehaviorReferenceTreeNode node = m_controller.getMethodReferences( (ConstructorEntry)m_reference.entry ); m_callsTree.setModel( new DefaultTreeModel( node ) ); } m_tabs.setSelectedIndex( 2 ); redraw(); } private void toggleMapping() { if( m_controller.entryHasDeobfuscatedName( m_reference.entry ) ) { m_controller.removeMapping( m_reference ); } else { m_controller.markAsDeobfuscated( m_reference ); } } private TreePath getPathToRoot( TreeNode node ) { List nodes = Lists.newArrayList(); TreeNode n = node; do { nodes.add( n ); n = n.getParent(); } while( n != null ); Collections.reverse( nodes ); return new TreePath( nodes.toArray() ); } private void close( ) { if( !m_controller.isDirty() ) { // everything is saved, we can exit safely m_frame.dispose(); } else { // ask to save before closing String[] options = { "Save and exit", "Discard changes", "Cancel" }; int response = JOptionPane.showOptionDialog( m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[2] ); switch( response ) { case JOptionPane.YES_OPTION: // save and exit if( m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog( m_frame ) == JFileChooser.APPROVE_OPTION ) { try { m_controller.saveMappings( m_mappingsFileChooser.getSelectedFile() ); m_frame.dispose(); } catch( IOException ex ) { throw new Error( ex ); } } break; case JOptionPane.NO_OPTION: // don't save, exit m_frame.dispose(); break; // cancel means do nothing } } } private void redraw( ) { m_frame.validate(); m_frame.repaint(); } }