package jadx.gui.ui; import jadx.api.ResourceFile; import jadx.gui.JadxWrapper; import jadx.gui.jobs.BackgroundWorker; import jadx.gui.jobs.DecompileJob; import jadx.gui.jobs.IndexJob; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettingsWindow; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JResource; import jadx.gui.treemodel.JRoot; import jadx.gui.ui.SearchDialog.SearchOptions; import jadx.gui.update.JadxUpdate; import jadx.gui.update.JadxUpdate.IUpdateCallback; import jadx.gui.update.data.Release; import jadx.gui.utils.CacheObject; import jadx.gui.utils.Link; import jadx.gui.utils.NLS; import jadx.gui.utils.Position; import jadx.gui.utils.Utils; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.ProgressMonitor; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeWillExpandListener; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.ExpandVetoException; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import java.awt.BorderLayout; import java.awt.Component; import java.awt.DisplayMode; import java.awt.Font; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.util.Arrays; import java.util.EnumSet; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static javax.swing.KeyStroke.getKeyStroke; @SuppressWarnings("serial") public class MainWindow extends JFrame { private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class); private static final String DEFAULT_TITLE = "jadx-gui"; private static final double BORDER_RATIO = 0.15; private static final double WINDOW_RATIO = 1 - BORDER_RATIO * 2; private static final double SPLIT_PANE_RESIZE_WEIGHT = 0.15; private static final ImageIcon ICON_OPEN = Utils.openIcon("folder"); private static final ImageIcon ICON_SAVE_ALL = Utils.openIcon("disk_multiple"); private static final ImageIcon ICON_EXPORT = Utils.openIcon("database_save"); private static final ImageIcon ICON_CLOSE = Utils.openIcon("cross"); private static final ImageIcon ICON_SYNC = Utils.openIcon("sync"); private static final ImageIcon ICON_FLAT_PKG = Utils.openIcon("empty_logical_package_obj"); private static final ImageIcon ICON_SEARCH = Utils.openIcon("wand"); private static final ImageIcon ICON_FIND = Utils.openIcon("magnifier"); private static final ImageIcon ICON_BACK = Utils.openIcon("icon_back"); private static final ImageIcon ICON_FORWARD = Utils.openIcon("icon_forward"); private static final ImageIcon ICON_PREF = Utils.openIcon("wrench"); private static final ImageIcon ICON_DEOBF = Utils.openIcon("lock_edit"); private static final ImageIcon ICON_LOG = Utils.openIcon("report"); private final JadxWrapper wrapper; private final JadxSettings settings; private final CacheObject cacheObject; private JPanel mainPanel; private JTree tree; private DefaultTreeModel treeModel; private JRoot treeRoot; private TabbedPane tabbedPane; private boolean isFlattenPackage; private JToggleButton flatPkgButton; private JCheckBoxMenuItem flatPkgMenuItem; private JToggleButton deobfToggleBtn; private JCheckBoxMenuItem deobfMenuItem; private Link updateLink; private ProgressPanel progressPane; private BackgroundWorker backgroundWorker; private DropTarget dropTarget; public MainWindow(JadxSettings settings) { this.wrapper = new JadxWrapper(settings); this.settings = settings; this.cacheObject = new CacheObject(); resetCache(); initUI(); initMenuAndToolbar(); checkForUpdate(); } public void open() { pack(); setLocationAndPosition(); setVisible(true); setLocationRelativeTo(null); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); if (settings.getInput().isEmpty()) { openFile(); } else { openFile(settings.getInput().get(0)); } } private void checkForUpdate() { if (!settings.isCheckForUpdates()) { return; } JadxUpdate.check(new IUpdateCallback() { @Override public void onUpdate(final Release r) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateLink.setText(String.format(NLS.str("menu.update_label"), r.getName())); updateLink.setVisible(true); } }); } }); } public void openFile() { JFileChooser fileChooser = new JFileChooser(); fileChooser.setAcceptAllFileFilterUsed(true); String[] exts = {"apk", "dex", "jar", "class", "zip", "aar"}; String description = "supported files: " + Arrays.toString(exts).replace('[', '(').replace(']', ')'); fileChooser.setFileFilter(new FileNameExtensionFilter(description, exts)); fileChooser.setToolTipText(NLS.str("file.open")); String currentDirectory = settings.getLastOpenFilePath(); if (!currentDirectory.isEmpty()) { fileChooser.setCurrentDirectory(new File(currentDirectory)); } int ret = fileChooser.showDialog(mainPanel, NLS.str("file.open")); if (ret == JFileChooser.APPROVE_OPTION) { settings.setLastOpenFilePath(fileChooser.getCurrentDirectory().getPath()); openFile(fileChooser.getSelectedFile()); } } public void openFile(File file) { tabbedPane.closeAllTabs(); resetCache(); wrapper.openFile(file); deobfToggleBtn.setSelected(settings.isDeobfuscationOn()); settings.addRecentFile(file.getAbsolutePath()); initTree(); setTitle(DEFAULT_TITLE + " - " + file.getName()); runBackgroundJobs(); } protected void resetCache() { cacheObject.reset(); // TODO: decompilation freezes sometime with several threads int threadsCount = 1; // settings.getThreadsCount(); cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount)); cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount)); } private synchronized void runBackgroundJobs() { cancelBackgroundJobs(); backgroundWorker = new BackgroundWorker(cacheObject, progressPane); if (settings.isAutoStartJobs()) { new Timer().schedule(new TimerTask() { @Override public void run() { backgroundWorker.exec(); } }, 1000); } } public synchronized void cancelBackgroundJobs() { if (backgroundWorker != null) { backgroundWorker.stop(); backgroundWorker = new BackgroundWorker(cacheObject, progressPane); resetCache(); } } public void reOpenFile() { File openedFile = wrapper.getOpenFile(); if (openedFile != null) { openFile(openedFile); } } private void saveAll(boolean export) { settings.setExportAsGradleProject(export); if (export) { settings.setSkipSources(false); settings.setSkipResources(false); } JFileChooser fileChooser = new JFileChooser(); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); fileChooser.setToolTipText(NLS.str("file.save_all_msg")); String currentDirectory = settings.getLastSaveFilePath(); if (!currentDirectory.isEmpty()) { fileChooser.setCurrentDirectory(new File(currentDirectory)); } int ret = fileChooser.showDialog(mainPanel, NLS.str("file.select")); if (ret == JFileChooser.APPROVE_OPTION) { settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().getPath()); ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100); progressMonitor.setMillisToPopup(0); wrapper.saveAll(fileChooser.getSelectedFile(), progressMonitor); } } private void initTree() { treeRoot = new JRoot(wrapper); treeRoot.setFlatPackages(isFlattenPackage); treeModel.setRoot(treeRoot); reloadTree(); } private void reloadTree() { treeModel.reload(); tree.expandRow(1); } private void toggleFlattenPackage() { setFlattenPackage(!isFlattenPackage); } private void setFlattenPackage(boolean value) { isFlattenPackage = value; settings.setFlattenPackage(isFlattenPackage); flatPkgButton.setSelected(isFlattenPackage); flatPkgMenuItem.setState(isFlattenPackage); Object root = treeModel.getRoot(); if (root instanceof JRoot) { JRoot treeRoot = (JRoot) root; treeRoot.setFlatPackages(isFlattenPackage); reloadTree(); } } private void toggleDeobfuscation() { boolean deobfOn = !settings.isDeobfuscationOn(); settings.setDeobfuscationOn(deobfOn); settings.sync(); deobfToggleBtn.setSelected(deobfOn); deobfMenuItem.setState(deobfOn); reOpenFile(); } private void treeClickAction() { try { Object obj = tree.getLastSelectedPathComponent(); if (obj instanceof JResource) { JResource res = (JResource) obj; ResourceFile resFile = res.getResFile(); if (resFile != null && JResource.isSupportedForView(resFile.getType())) { tabbedPane.showResource(res); } } else if (obj instanceof JNode) { JNode node = (JNode) obj; JClass cls = node.getRootClass(); if (cls != null) { tabbedPane.codeJump(new Position(cls, node.getLine())); } } } catch (Exception e) { LOG.error("Content loading error", e); } } private void syncWithEditor() { ContentPanel selectedContentPanel = tabbedPane.getSelectedCodePanel(); if (selectedContentPanel == null) { return; } JNode node = selectedContentPanel.getNode(); if (node.getParent() == null && treeRoot != null) { // node not register in tree node = treeRoot.searchClassInTree(node); if (node == null) { LOG.error("Class not found in tree"); return; } } TreeNode[] pathNodes = treeModel.getPathToRoot(node); if (pathNodes == null) { return; } TreePath path = new TreePath(pathNodes); tree.setSelectionPath(path); tree.makeVisible(path); tree.scrollPathToVisible(path); tree.requestFocus(); } private void initMenuAndToolbar() { Action openAction = new AbstractAction(NLS.str("file.open"), ICON_OPEN) { @Override public void actionPerformed(ActionEvent e) { openFile(); } }; openAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.open")); openAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_DOWN_MASK)); Action saveAllAction = new AbstractAction(NLS.str("file.save_all"), ICON_SAVE_ALL) { @Override public void actionPerformed(ActionEvent e) { saveAll(false); } }; saveAllAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_all")); saveAllAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_DOWN_MASK)); Action exportAction = new AbstractAction(NLS.str("file.export_gradle"), ICON_EXPORT) { @Override public void actionPerformed(ActionEvent e) { saveAll(true); } }; exportAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.export_gradle")); exportAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_DOWN_MASK)); JMenu recentFiles = new JMenu(NLS.str("menu.recent_files")); recentFiles.addMenuListener(new RecentFilesMenuListener(recentFiles)); Action prefsAction = new AbstractAction(NLS.str("menu.preferences"), ICON_PREF) { @Override public void actionPerformed(ActionEvent e) { new JadxSettingsWindow(MainWindow.this, settings).setVisible(true); } }; prefsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.preferences")); prefsAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); Action exitAction = new AbstractAction(NLS.str("file.exit"), ICON_CLOSE) { @Override public void actionPerformed(ActionEvent e) { dispose(); } }; isFlattenPackage = settings.isFlattenPackage(); flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), ICON_FLAT_PKG); flatPkgMenuItem.setState(isFlattenPackage); Action syncAction = new AbstractAction(NLS.str("menu.sync"), ICON_SYNC) { @Override public void actionPerformed(ActionEvent e) { syncWithEditor(); } }; syncAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.sync")); syncAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_T, KeyEvent.CTRL_DOWN_MASK)); Action textSearchAction = new AbstractAction(NLS.str("menu.text_search"), ICON_SEARCH) { @Override public void actionPerformed(ActionEvent e) { new SearchDialog(MainWindow.this, EnumSet.of(SearchOptions.CODE)).setVisible(true); } }; textSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.text_search")); textSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); Action clsSearchAction = new AbstractAction(NLS.str("menu.class_search"), ICON_FIND) { @Override public void actionPerformed(ActionEvent e) { new SearchDialog(MainWindow.this, EnumSet.of(SearchOptions.CLASS)).setVisible(true); } }; clsSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.class_search")); clsSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_DOWN_MASK)); Action deobfAction = new AbstractAction(NLS.str("preferences.deobfuscation"), ICON_DEOBF) { @Override public void actionPerformed(ActionEvent e) { toggleDeobfuscation(); } }; deobfAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("preferences.deobfuscation")); deobfAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); deobfToggleBtn = new JToggleButton(deobfAction); deobfToggleBtn.setSelected(settings.isDeobfuscationOn()); deobfToggleBtn.setText(""); deobfMenuItem = new JCheckBoxMenuItem(deobfAction); deobfMenuItem.setState(settings.isDeobfuscationOn()); Action logAction = new AbstractAction(NLS.str("menu.log"), ICON_LOG) { @Override public void actionPerformed(ActionEvent e) { new LogViewer(settings).setVisible(true); } }; logAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.log")); logAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); Action aboutAction = new AbstractAction(NLS.str("menu.about")) { @Override public void actionPerformed(ActionEvent e) { new AboutDialog().setVisible(true); } }; Action backAction = new AbstractAction(NLS.str("nav.back"), ICON_BACK) { @Override public void actionPerformed(ActionEvent e) { tabbedPane.navBack(); } }; backAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("nav.back")); backAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); Action forwardAction = new AbstractAction(NLS.str("nav.forward"), ICON_FORWARD) { @Override public void actionPerformed(ActionEvent e) { tabbedPane.navForward(); } }; forwardAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("nav.forward")); forwardAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); JMenu file = new JMenu(NLS.str("menu.file")); file.setMnemonic(KeyEvent.VK_F); file.add(openAction); file.add(saveAllAction); file.add(exportAction); file.addSeparator(); file.add(recentFiles); file.addSeparator(); file.add(prefsAction); file.addSeparator(); file.add(exitAction); JMenu view = new JMenu(NLS.str("menu.view")); view.setMnemonic(KeyEvent.VK_V); view.add(flatPkgMenuItem); view.add(syncAction); JMenu nav = new JMenu(NLS.str("menu.navigation")); nav.setMnemonic(KeyEvent.VK_N); nav.add(textSearchAction); nav.add(clsSearchAction); nav.addSeparator(); nav.add(backAction); nav.add(forwardAction); JMenu tools = new JMenu(NLS.str("menu.tools")); tools.setMnemonic(KeyEvent.VK_T); tools.add(deobfMenuItem); tools.add(logAction); JMenu help = new JMenu(NLS.str("menu.help")); help.setMnemonic(KeyEvent.VK_H); help.add(aboutAction); JMenuBar menuBar = new JMenuBar(); menuBar.add(file); menuBar.add(view); menuBar.add(nav); menuBar.add(tools); menuBar.add(help); setJMenuBar(menuBar); flatPkgButton = new JToggleButton(ICON_FLAT_PKG); flatPkgButton.setSelected(isFlattenPackage); ActionListener flatPkgAction = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { toggleFlattenPackage(); } }; flatPkgMenuItem.addActionListener(flatPkgAction); flatPkgButton.addActionListener(flatPkgAction); flatPkgButton.setToolTipText(NLS.str("menu.flatten")); updateLink = new Link("", JadxUpdate.JADX_RELEASES_URL); updateLink.setVisible(false); JToolBar toolbar = new JToolBar(); toolbar.setFloatable(false); toolbar.add(openAction); toolbar.add(saveAllAction); toolbar.add(exportAction); toolbar.addSeparator(); toolbar.add(syncAction); toolbar.add(flatPkgButton); toolbar.addSeparator(); toolbar.add(textSearchAction); toolbar.add(clsSearchAction); toolbar.addSeparator(); toolbar.add(backAction); toolbar.add(forwardAction); toolbar.addSeparator(); toolbar.add(deobfToggleBtn); toolbar.addSeparator(); toolbar.add(logAction); toolbar.addSeparator(); toolbar.add(prefsAction); toolbar.addSeparator(); toolbar.add(Box.createHorizontalGlue()); toolbar.add(updateLink); mainPanel.add(toolbar, BorderLayout.NORTH); } private void initUI() { mainPanel = new JPanel(new BorderLayout()); JSplitPane splitPane = new JSplitPane(); splitPane.setResizeWeight(SPLIT_PANE_RESIZE_WEIGHT); mainPanel.add(splitPane); DefaultMutableTreeNode treeRoot = new DefaultMutableTreeNode(NLS.str("msg.open_file")); treeModel = new DefaultTreeModel(treeRoot); tree = new JTree(treeModel); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { treeClickAction(); } }); tree.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { treeClickAction(); } } }); tree.setCellRenderer(new DefaultTreeCellRenderer() { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean isLeaf, int row, boolean focused) { Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, isLeaf, row, focused); if (value instanceof JNode) { setIcon(((JNode) value).getIcon()); } return c; } }); tree.addTreeWillExpandListener(new TreeWillExpandListener() { @Override public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { TreePath path = event.getPath(); Object node = path.getLastPathComponent(); if (node instanceof JClass) { JClass cls = (JClass) node; cls.getRootClass().load(); } } @Override public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException { } }); progressPane = new ProgressPanel(this, true); JPanel leftPane = new JPanel(new BorderLayout()); leftPane.add(new JScrollPane(tree), BorderLayout.CENTER); leftPane.add(progressPane, BorderLayout.PAGE_END); splitPane.setLeftComponent(leftPane); tabbedPane = new TabbedPane(this); splitPane.setRightComponent(tabbedPane); dropTarget = new DropTarget(this, DnDConstants.ACTION_COPY, new MainDropTarget(this)); setContentPane(mainPanel); setTitle(DEFAULT_TITLE); } public void setLocationAndPosition() { if (this.settings.loadWindowPos(this)) { return; } GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); DisplayMode mode = gd.getDisplayMode(); int w = mode.getWidth(); int h = mode.getHeight(); setLocation((int) (w * BORDER_RATIO), (int) (h * BORDER_RATIO)); setSize((int) (w * WINDOW_RATIO), (int) (h * WINDOW_RATIO)); } public void updateFont(Font font) { setFont(font); tabbedPane.loadSettings(); } @Override public void dispose() { settings.saveWindowPos(this); cancelBackgroundJobs(); super.dispose(); } public JadxWrapper getWrapper() { return wrapper; } public TabbedPane getTabbedPane() { return tabbedPane; } public JadxSettings getSettings() { return settings; } public CacheObject getCacheObject() { return cacheObject; } public BackgroundWorker getBackgroundWorker() { return backgroundWorker; } private class RecentFilesMenuListener implements MenuListener { private final JMenu recentFiles; public RecentFilesMenuListener(JMenu recentFiles) { this.recentFiles = recentFiles; } @Override public void menuSelected(MenuEvent e) { recentFiles.removeAll(); File openFile = wrapper.getOpenFile(); String currentFile = openFile == null ? "" : openFile.getAbsolutePath(); for (final String file : settings.getRecentFiles()) { if (file.equals(currentFile)) { continue; } JMenuItem menuItem = new JMenuItem(file); recentFiles.add(menuItem); menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { openFile(new File(file)); } }); } if (recentFiles.getItemCount() == 0) { recentFiles.add(new JMenuItem(NLS.str("menu.no_recent_files"))); } } @Override public void menuDeselected(MenuEvent e) { } @Override public void menuCanceled(MenuEvent e) { } } }