diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 54fa704a05024a01d300412b77a990b34b34f69b..bd325a00b66d8866f476285cd2fc2ac349315e20 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -18,7 +18,7 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterDescription; import com.beust.jcommander.ParameterException; -public final class JadxCLIArgs implements IJadxArgs { +public class JadxCLIArgs implements IJadxArgs { @Parameter(description = " (.dex, .apk, .jar or .class)") protected List files; diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java index 6c62c7dcbc1796422a3e1267caa81b41b0e3aed1..1e3136700414452bbbaddd55119b49d073c097da 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -55,7 +55,7 @@ public class Deobfuscator { } public void execute() { - if (deobfMapFile.exists()) { + if (deobfMapFile.exists() && !args.isDeobfuscationForceSave()) { try { load(); } catch (IOException e) { @@ -295,6 +295,7 @@ public class Deobfuscator { if (!deobfMapFile.exists()) { return; } + LOG.info("Loading obfuscation map from: {}", deobfMapFile.getAbsoluteFile()); List lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET); for (String l : lines) { l = l.trim(); diff --git a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java index a624e8471802ebf160b859191dedff652f4609fa..2c8f93604461b678ee5396292785908432a2e32b 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java @@ -1,11 +1,11 @@ package jadx.gui; -import jadx.cli.JadxCLIArgs; +import jadx.gui.settings.JadxSettings; +import jadx.gui.settings.JadxSettingsAdapter; import jadx.gui.ui.MainWindow; import javax.swing.SwingUtilities; import javax.swing.UIManager; -import javax.swing.WindowConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,26 +15,16 @@ public class JadxGUI { public static void main(String[] args) { try { - final JadxCLIArgs jadxArgs = new JadxCLIArgs(); + final JadxSettings jadxArgs = JadxSettingsAdapter.load(); + // overwrite loaded settings by command line arguments if (!jadxArgs.processArgs(args)) { return; } UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); SwingUtilities.invokeLater(new Runnable() { public void run() { - JadxWrapper wrapper = new JadxWrapper(jadxArgs); - MainWindow window = new MainWindow(wrapper); - window.pack(); - window.setLocationAndPosition(); - window.setVisible(true); - window.setLocationRelativeTo(null); - window.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - - if (jadxArgs.getInput().isEmpty()) { - window.openFile(); - } else { - window.openFile(jadxArgs.getInput().get(0)); - } + MainWindow window = new MainWindow(jadxArgs); + window.open(); } }); } catch (Throwable e) { diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java new file mode 100644 index 0000000000000000000000000000000000000000..4b23573c2ca211488507d65def0a627fbcedac66 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -0,0 +1,129 @@ +package jadx.gui.settings; + +import jadx.cli.JadxCLIArgs; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class JadxSettings extends JadxCLIArgs { + + private static final String USER_HOME = System.getProperty("user.home"); + private static final int RECENT_FILES_COUNT = 15; + + static final Set SKIP_FIELDS = new HashSet(Arrays.asList( + "files", "input", "outputDir", "printHelp" + )); + + private String lastOpenFilePath = USER_HOME; + private String lastSaveFilePath = USER_HOME; + private boolean flattenPackage = false; + private boolean checkForUpdates = true; + private List recentFiles = new ArrayList(); + + public void sync() { + JadxSettingsAdapter.store(this); + } + + public String getLastOpenFilePath() { + return lastOpenFilePath; + } + + public void setLastOpenFilePath(String lastOpenFilePath) { + this.lastOpenFilePath = lastOpenFilePath; + sync(); + } + + public String getLastSaveFilePath() { + return lastSaveFilePath; + } + + public void setLastSaveFilePath(String lastSaveFilePath) { + this.lastSaveFilePath = lastSaveFilePath; + sync(); + } + + public boolean isFlattenPackage() { + return flattenPackage; + } + + public void setFlattenPackage(boolean flattenPackage) { + this.flattenPackage = flattenPackage; + sync(); + } + + public boolean isCheckForUpdates() { + return checkForUpdates; + } + + public void setCheckForUpdates(boolean checkForUpdates) { + this.checkForUpdates = checkForUpdates; + sync(); + } + + public Iterable getRecentFiles() { + return recentFiles; + } + + public void addRecentFile(String filePath) { + if (recentFiles.contains(filePath)) { + return; + } + recentFiles.add(filePath); + int count = recentFiles.size(); + if (count > RECENT_FILES_COUNT) { + recentFiles.subList(0, count - RECENT_FILES_COUNT).clear(); + } + sync(); + } + + public void setThreadsCount(int threadsCount) { + this.threadsCount = threadsCount; + } + + public void setFallbackMode(boolean fallbackMode) { + this.fallbackMode = fallbackMode; + } + + public void setSkipResources(boolean skipResources) { + this.skipResources = skipResources; + } + + public void setSkipSources(boolean skipSources) { + this.skipSources = skipSources; + } + + public void setShowInconsistentCode(boolean showInconsistentCode) { + this.showInconsistentCode = showInconsistentCode; + } + + public void setCfgOutput(boolean cfgOutput) { + this.cfgOutput = cfgOutput; + } + + public void setRawCfgOutput(boolean rawCfgOutput) { + this.rawCfgOutput = rawCfgOutput; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public void setDeobfuscationOn(boolean deobfuscationOn) { + this.deobfuscationOn = deobfuscationOn; + } + + public void setDeobfuscationMinLength(int deobfuscationMinLength) { + this.deobfuscationMinLength = deobfuscationMinLength; + } + + public void setDeobfuscationMaxLength(int deobfuscationMaxLength) { + this.deobfuscationMaxLength = deobfuscationMaxLength; + } + + public void setDeobfuscationForceSave(boolean deobfuscationForceSave) { + this.deobfuscationForceSave = deobfuscationForceSave; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsAdapter.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..f048c57ee50dddf4e29bbd394c64a68602c7aded --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsAdapter.java @@ -0,0 +1,88 @@ +package jadx.gui.settings; + +import jadx.gui.JadxGUI; + +import java.lang.reflect.Type; +import java.util.prefs.Preferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.InstanceCreator; + +public class JadxSettingsAdapter { + + private static final Logger LOG = LoggerFactory.getLogger(JadxSettingsAdapter.class); + + private static final String JADX_GUI_KEY = "jadx.gui.settings"; + + private static final Preferences PREFS = Preferences.userNodeForPackage(JadxGUI.class); + + private static ExclusionStrategy EXCLUDE_FIELDS = new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return JadxSettings.SKIP_FIELDS.contains(f.getName()); + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } + }; + private static final GsonBuilder GSON_BUILDER = new GsonBuilder().setExclusionStrategies(EXCLUDE_FIELDS); + private static final Gson GSON = GSON_BUILDER.create(); + + private JadxSettingsAdapter() { + } + + public static JadxSettings load() { + try { + String jsonSettings = PREFS.get(JADX_GUI_KEY, ""); + JadxSettings settings = fromString(jsonSettings); + if (settings == null) { + return new JadxSettings(); + } + LOG.info("Loaded settings: {}", makeString(settings)); + return settings; + } catch (Exception e) { + LOG.error("Error load settings", e); + return new JadxSettings(); + } + } + + public static void store(JadxSettings settings) { + try { + String jsonSettings = makeString(settings); + LOG.debug("Saving settings: {}", jsonSettings); + PREFS.put(JADX_GUI_KEY, jsonSettings); + PREFS.sync(); + } catch (Exception e) { + LOG.error("Error store settings", e); + } + } + + public static JadxSettings fromString(String jsonSettings) { + return GSON.fromJson(jsonSettings, JadxSettings.class); + } + + public static String makeString(JadxSettings settings) { + return GSON.toJson(settings); + } + + public static void fill(JadxSettings settings, String jsonStr) { + populate(GSON_BUILDER, jsonStr, JadxSettings.class, settings); + } + + private static void populate(GsonBuilder builder, String json, Class type, final T into) { + builder.registerTypeAdapter(type, new InstanceCreator() { + @Override + public T createInstance(Type t) { + return into; + } + }).create().fromJson(json, type); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..55aad41491f5b2c73452baf095bbf9df78a6469b --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -0,0 +1,259 @@ +package jadx.gui.settings; + +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.NLS; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SwingConstants; +import javax.swing.WindowConstants; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +public class JadxSettingsWindow extends JDialog { + private static final long serialVersionUID = -1804570470377354148L; + + private final MainWindow mainWindow; + private final JadxSettings settings; + private final String startSettings; + + private boolean needReload = false; + + public JadxSettingsWindow(MainWindow mainWindow, JadxSettings settings) { + this.mainWindow = mainWindow; + this.settings = settings; + this.startSettings = JadxSettingsAdapter.makeString(settings); + + initUI(); + } + + private void initUI() { + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(0, 1, 10, 5)); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + panel.add(makeDeobfuscationGroup()); + panel.add(makeOtherGroup()); + + JButton saveBtn = new JButton(NLS.str("preferences.save")); + saveBtn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + settings.sync(); + if (needReload) { + mainWindow.reOpenFile(); + } + dispose(); + } + }); + JButton cancelButton = new JButton(NLS.str("preferences.cancel")); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + JadxSettingsAdapter.fill(settings, startSettings); + dispose(); + } + }); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + buttonPane.add(Box.createHorizontalGlue()); + buttonPane.add(saveBtn); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(cancelButton); + + Container contentPane = getContentPane(); + contentPane.add(panel, BorderLayout.CENTER); + contentPane.add(buttonPane, BorderLayout.PAGE_END); + getRootPane().setDefaultButton(saveBtn); + + setTitle(NLS.str("preferences.title")); + setSize(400, 550); + setLocationRelativeTo(null); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setModalityType(ModalityType.APPLICATION_MODAL); + pack(); + } + + private SettingsGroup makeDeobfuscationGroup() { + + JCheckBox deobfOn = new JCheckBox(); + deobfOn.setSelected(settings.isDeobfuscationOn()); + deobfOn.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setDeobfuscationOn(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + } + }); + + JCheckBox deobfForce = new JCheckBox(); + deobfForce.setSelected(settings.isDeobfuscationForceSave()); + deobfForce.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setDeobfuscationForceSave(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + } + }); + + final JSpinner minLen = new JSpinner(); + minLen.setValue(settings.getDeobfuscationMinLength()); + minLen.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + settings.setDeobfuscationMinLength((Integer) minLen.getValue()); + needReload(); + } + }); + + final JSpinner maxLen = new JSpinner(); + maxLen.setValue(settings.getDeobfuscationMaxLength()); + maxLen.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + settings.setDeobfuscationMaxLength((Integer) maxLen.getValue()); + needReload(); + } + }); + + SettingsGroup deobfGroup = new SettingsGroup(NLS.str("preferences.deobfuscation")); + deobfGroup.addRow(NLS.str("preferences.deobfuscation_on"), deobfOn); + deobfGroup.addRow(NLS.str("preferences.deobfuscation_force"), deobfForce); + deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLen); + deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLen); + deobfGroup.end(); + return deobfGroup; + } + + private SettingsGroup makeOtherGroup() { + JCheckBox update = new JCheckBox(); + update.setSelected(settings.isCheckForUpdates()); + update.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setCheckForUpdates(e.getStateChange() == ItemEvent.SELECTED); + } + }); + + JCheckBox fallback = new JCheckBox(); + fallback.setSelected(settings.isFallbackMode()); + fallback.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setFallbackMode(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + } + }); + + JCheckBox showInconsistentCode = new JCheckBox(); + showInconsistentCode.setSelected(settings.isShowInconsistentCode()); + showInconsistentCode.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setShowInconsistentCode(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + } + }); + + JCheckBox resourceDecode = new JCheckBox(); + resourceDecode.setSelected(settings.isSkipResources()); + resourceDecode.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setSkipResources(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + } + }); + + final JSpinner threadsCount = new JSpinner(); + threadsCount.setValue(settings.getThreadsCount()); + threadsCount.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + settings.setThreadsCount((Integer) threadsCount.getValue()); + } + }); + + JCheckBox cfg = new JCheckBox(); + cfg.setSelected(settings.isCFGOutput()); + cfg.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setCfgOutput(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + } + }); + + JCheckBox rawCfg = new JCheckBox(); + rawCfg.setSelected(settings.isRawCFGOutput()); + rawCfg.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setRawCfgOutput(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + } + }); + + SettingsGroup other = new SettingsGroup(NLS.str("preferences.other")); + other.addRow(NLS.str("preferences.check_for_updates"), update); + other.addRow(NLS.str("preferences.threads"), threadsCount); + other.addRow(NLS.str("preferences.fallback"), fallback); + other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode); + other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode); + other.addRow(NLS.str("preferences.cfg"), cfg); + other.addRow(NLS.str("preferences.raw_cfg"), rawCfg); + return other; + } + + private void needReload() { + needReload = true; + } + + private static class SettingsGroup extends JPanel { + private static final long serialVersionUID = -6487309975896192544L; + + private final GridBagConstraints c; + private int row; + + public SettingsGroup(String title) { + setBorder(BorderFactory.createTitledBorder(title)); + setLayout(new GridBagLayout()); + c = new GridBagConstraints(); + c.insets = new Insets(5, 5, 5, 5); + c.weighty = 1.0; + } + + public void addRow(String label, JComponent comp) { + c.gridy = row++; + JLabel jLabel = new JLabel(label); + jLabel.setLabelFor(comp); + jLabel.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 0; + c.gridwidth = 1; + c.anchor = GridBagConstraints.LINE_START; + c.weightx = 0.8; + c.fill = GridBagConstraints.NONE; + add(jLabel, c); + c.gridx = 1; + c.gridwidth = GridBagConstraints.REMAINDER; + c.anchor = GridBagConstraints.CENTER; + c.weightx = 0.2; + c.fill = GridBagConstraints.HORIZONTAL; + add(comp, c); + } + + public void end() { + add(Box.createVerticalGlue()); + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index f801e54c8726c4428845cf8df5f397d5ef454275..6c21166fa8b063ac0d75583ef6ecf2f8e7795839 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -1,6 +1,8 @@ package jadx.gui.ui; import jadx.gui.JadxWrapper; +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; @@ -8,7 +10,6 @@ import jadx.gui.treemodel.JRoot; import jadx.gui.update.JadxUpdate; import jadx.gui.update.JadxUpdate.IUpdateCallback; import jadx.gui.update.data.Release; -import jadx.gui.utils.JadxPreferences; import jadx.gui.utils.Link; import jadx.gui.utils.NLS; import jadx.gui.utils.Position; @@ -31,6 +32,9 @@ 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; @@ -53,6 +57,7 @@ import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; +import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,8 +81,11 @@ public class MainWindow extends JFrame { 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 final JadxWrapper wrapper; + private final JadxSettings settings; private JPanel mainPanel; @@ -88,19 +96,37 @@ public class MainWindow extends JFrame { private JCheckBoxMenuItem flatPkgMenuItem; private JToggleButton flatPkgButton; + private JToggleButton deobfToggleBtn; private boolean isFlattenPackage; private Link updateLink; - public MainWindow(JadxWrapper wrapper) { - this.wrapper = wrapper; + public MainWindow(JadxSettings settings) { + this.wrapper = new JadxWrapper(settings); + this.settings = settings; initUI(); initMenuAndToolbar(); checkForUpdate(); } + public void open() { + pack(); + setLocationAndPosition(); + setVisible(true); + setLocationRelativeTo(null); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + if (settings.getInput().isEmpty()) { + openFile(); + } else { + openFile(settings.getInput().get(0)); + } + } + private void checkForUpdate() { - // TODO: add option for disable update checks + if (!settings.isCheckForUpdates()) { + return; + } JadxUpdate.check(new IUpdateCallback() { @Override public void onUpdate(final Release r) { @@ -118,38 +144,50 @@ public class MainWindow extends JFrame { public void openFile() { JFileChooser fileChooser = new JFileChooser(); fileChooser.setAcceptAllFileFilterUsed(true); - fileChooser.setFileFilter(new FileNameExtensionFilter("supported files", "dex", "apk", "jar")); + String[] exts = {"apk", "dex", "jar", "class", "zip"}; + String description = "supported files: " + Arrays.toString(exts).replace('[', '(').replace(']', ')'); + fileChooser.setFileFilter(new FileNameExtensionFilter(description, exts)); fileChooser.setToolTipText(NLS.str("file.open")); - String currentDirectory = JadxPreferences.getLastOpenFilePath(); + 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) { - JadxPreferences.putLastOpenFilePath(fileChooser.getCurrentDirectory().getPath()); + settings.setLastOpenFilePath(fileChooser.getCurrentDirectory().getPath()); openFile(fileChooser.getSelectedFile()); } } public void openFile(File file) { wrapper.openFile(file); + deobfToggleBtn.setSelected(settings.isDeobfuscationOn()); + settings.addRecentFile(file.getAbsolutePath()); initTree(); setTitle(DEFAULT_TITLE + " - " + file.getName()); } + public void reOpenFile() { + File openedFile = wrapper.getOpenFile(); + if (openedFile != null) { + tabbedPane.closeAllTabs(); + openFile(openedFile); + } + } + private void saveAllAction() { JFileChooser fileChooser = new JFileChooser(); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); fileChooser.setToolTipText(NLS.str("file.save_all_msg")); - String currentDirectory = JadxPreferences.getLastSaveFilePath(); + 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) { - JadxPreferences.putLastSaveFilePath(fileChooser.getCurrentDirectory().getPath()); + settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().getPath()); ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100); progressMonitor.setMillisToPopup(500); wrapper.saveAll(fileChooser.getSelectedFile(), progressMonitor); @@ -174,7 +212,7 @@ public class MainWindow extends JFrame { private void setFlattenPackage(boolean value) { isFlattenPackage = value; - JadxPreferences.putFlattenPackage(isFlattenPackage); + settings.setFlattenPackage(isFlattenPackage); flatPkgButton.setSelected(isFlattenPackage); flatPkgMenuItem.setState(isFlattenPackage); @@ -265,15 +303,35 @@ public class MainWindow extends JFrame { } }); + JMenu recentFiles = new JMenu(NLS.str("menu.recent_files")); + recentFiles.addMenuListener(new RecentFilesMenuListener(recentFiles)); + + JMenuItem preferences = new JMenuItem(NLS.str("menu.preferences"), ICON_PREF); + ActionListener prefAction = new ActionListener() { + public void actionPerformed(ActionEvent event) { + final JadxSettingsWindow dialog = new JadxSettingsWindow(MainWindow.this, settings); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + dialog.setVisible(true); + } + }); + } + }; + preferences.addActionListener(prefAction); + file.add(open); file.add(saveAll); file.addSeparator(); + file.add(recentFiles); + file.addSeparator(); + file.add(preferences); + file.addSeparator(); file.add(exit); JMenu view = new JMenu(NLS.str("menu.view")); view.setMnemonic(KeyEvent.VK_V); - isFlattenPackage = JadxPreferences.getFlattenPackage(); + isFlattenPackage = settings.isFlattenPackage(); flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), ICON_FLAT_PKG); view.add(flatPkgMenuItem); @@ -334,13 +392,9 @@ public class MainWindow extends JFrame { menuBar.add(help); setJMenuBar(menuBar); - JToolBar toolbar = new JToolBar(); - toolbar.setFloatable(false); - final JButton openButton = new JButton(ICON_OPEN); openButton.addActionListener(new OpenListener()); openButton.setToolTipText(NLS.str("file.open")); - toolbar.add(openButton); final JButton saveAllButton = new JButton(ICON_SAVE_ALL); saveAllButton.addActionListener(new ActionListener() { @@ -350,9 +404,6 @@ public class MainWindow extends JFrame { } }); saveAllButton.setToolTipText(NLS.str("file.save_all")); - toolbar.add(saveAllButton); - - toolbar.addSeparator(); final JButton syncButton = new JButton(ICON_SYNC); syncButton.addActionListener(new ActionListener() { @@ -362,7 +413,6 @@ public class MainWindow extends JFrame { } }); syncButton.setToolTipText(NLS.str("menu.sync")); - toolbar.add(syncButton); flatPkgButton = new JToggleButton(ICON_FLAT_PKG); flatPkgButton.setSelected(isFlattenPackage); @@ -376,20 +426,14 @@ public class MainWindow extends JFrame { flatPkgMenuItem.addActionListener(flatPkgAction); flatPkgButton.setToolTipText(NLS.str("menu.flatten")); - toolbar.add(flatPkgButton); - toolbar.addSeparator(); final JButton searchButton = new JButton(ICON_SEARCH); searchButton.addActionListener(searchAction); searchButton.setToolTipText(NLS.str("menu.search")); - toolbar.add(searchButton); final JButton findButton = new JButton(ICON_FIND); findButton.addActionListener(findAction); findButton.setToolTipText(NLS.str("menu.find_in_file")); - toolbar.add(findButton); - - toolbar.addSeparator(); final JButton backButton = new JButton(ICON_BACK); backButton.addActionListener(new ActionListener() { @@ -399,7 +443,6 @@ public class MainWindow extends JFrame { } }); backButton.setToolTipText(NLS.str("nav.back")); - toolbar.add(backButton); final JButton forwardButton = new JButton(ICON_FORWARD); forwardButton.addActionListener(new ActionListener() { @@ -409,11 +452,52 @@ public class MainWindow extends JFrame { } }); forwardButton.setToolTipText(NLS.str("nav.forward")); - toolbar.add(forwardButton); - toolbar.add(Box.createHorizontalGlue()); + final JButton prefButton = new JButton(ICON_PREF); + prefButton.addActionListener(prefAction); + prefButton.setToolTipText(NLS.str("menu.preferences")); + + deobfToggleBtn = new JToggleButton(ICON_DEOBF); + deobfToggleBtn.setSelected(settings.isDeobfuscationOn()); + deobfToggleBtn.setToolTipText(NLS.str("preferences.deobfuscation")); + deobfToggleBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + settings.setDeobfuscationOn(deobfToggleBtn.isSelected()); + settings.sync(); + reOpenFile(); + } + }); + updateLink = new Link("", JadxUpdate.JADX_RELEASES_URL); updateLink.setVisible(false); + + JToolBar toolbar = new JToolBar(); + toolbar.setFloatable(false); + + toolbar.add(openButton); + toolbar.add(saveAllButton); + toolbar.addSeparator(); + + toolbar.add(syncButton); + toolbar.add(flatPkgButton); + toolbar.addSeparator(); + + toolbar.add(searchButton); + toolbar.add(findButton); + toolbar.addSeparator(); + + toolbar.add(backButton); + toolbar.add(forwardButton); + toolbar.addSeparator(); + + toolbar.add(deobfToggleBtn); + toolbar.addSeparator(); + + toolbar.add(prefButton); + toolbar.addSeparator(); + + toolbar.add(Box.createHorizontalGlue()); toolbar.add(updateLink); mainPanel.add(toolbar, BorderLayout.NORTH); @@ -495,4 +579,40 @@ public class MainWindow extends JFrame { openFile(); } } + + 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)); + } + }); + } + } + + @Override + public void menuDeselected(MenuEvent e) { + } + + @Override + public void menuCanceled(MenuEvent e) { + } + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java index 96a01eee21d1664df21f60b0dad28d4ee13cb98f..4562450739d46845f50bd90dee2661d71f8af7f8 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java @@ -299,7 +299,7 @@ public class SearchDialog extends JDialog { buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); buttonPane.add(busyBar); - searchPane.add(Box.createRigidArea(new Dimension(5, 0))); + buttonPane.add(Box.createRigidArea(new Dimension(5, 0))); buttonPane.add(Box.createHorizontalGlue()); buttonPane.add(openBtn); buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index a4be0239ee3cfe683b3a20a506dc0e5eee54c3f0..5502e02a298e28750c91a3ecef0c9d8330cdc623 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -200,10 +200,7 @@ class TabbedPane extends JTabbedPane { closeAll.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - List contentPanels = new ArrayList(openTabs.values()); - for (ContentPanel panel : contentPanels) { - closeCodePanel(panel); - } + closeAllTabs(); } }); menu.add(closeAll); @@ -230,4 +227,11 @@ class TabbedPane extends JTabbedPane { } return menu; } + + public void closeAllTabs() { + List contentPanels = new ArrayList(openTabs.values()); + for (ContentPanel panel : contentPanels) { + closeCodePanel(panel); + } + } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/JadxPreferences.java b/jadx-gui/src/main/java/jadx/gui/utils/JadxPreferences.java deleted file mode 100644 index 1d84c4cc26abec48f41c14dbfba2544ba88f8950..0000000000000000000000000000000000000000 --- a/jadx-gui/src/main/java/jadx/gui/utils/JadxPreferences.java +++ /dev/null @@ -1,87 +0,0 @@ -package jadx.gui.utils; - -import java.util.prefs.Preferences; - -public class JadxPreferences { - - private static final String KEY_LAST_OPEN_FILE_PATH = "lastOpenFilePath"; - private static final String KEY_LAST_SAVE_FILE_PATH = "lastSaveFilePath"; - private static final String KEY_FLATTEN_PACKAGE = "flattenPackage"; - - private static Preferences prefs = null; - - public static String getLastOpenFilePath() { - String result = ""; - try { - result = getPreferences().get(KEY_LAST_OPEN_FILE_PATH, ""); - if (result.isEmpty()) { - result = System.getProperty("user.home"); - } - } catch (Exception anyEx) { - /* do nothing, no preferences */ - } - return result; - } - - public static void putLastOpenFilePath(String path) { - try { - Preferences prefs = getPreferences(); - prefs.put(KEY_LAST_OPEN_FILE_PATH, path); - prefs.sync(); - } catch (Exception anyEx) { - /* do nothing, no preferences */ - } - } - - public static String getLastSaveFilePath() { - String result = ""; - try { - result = getPreferences().get(KEY_LAST_SAVE_FILE_PATH, ""); - if (result.isEmpty()) { - result = getLastOpenFilePath(); - } - } catch (Exception anyEx) { - /* do nothing, no preferences */ - } - return result; - } - - public static void putLastSaveFilePath(String path) { - try { - Preferences prefs = getPreferences(); - prefs.put(KEY_LAST_SAVE_FILE_PATH, path); - prefs.sync(); - } catch (Exception anyEx) { - /* do nothing, no preferences */ - } - } - - public static boolean getFlattenPackage() { - boolean result = false; - try { - Preferences prefs = getPreferences(); - result = prefs.getBoolean(KEY_FLATTEN_PACKAGE, false); - } catch (Exception anyEx) { - /* do nothing, no preferences */ - } - return result; - } - - public static void putFlattenPackage(boolean value) { - try { - Preferences prefs = getPreferences(); - prefs.putBoolean(KEY_FLATTEN_PACKAGE, value); - prefs.sync(); - } catch (Exception anyEx) { - /* do nothing, no preferences */ - } - } - - private static Preferences getPreferences() { - if (prefs == null) { - prefs = Preferences.userRoot(); - } - return prefs; - } - -} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/OverlayIcon.java b/jadx-gui/src/main/java/jadx/gui/utils/OverlayIcon.java index 147245b31307dcccf6fb64fa9b4e25c930747efb..bca8acaa38945aa7df2b4576706f7b402985d928 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/OverlayIcon.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/OverlayIcon.java @@ -42,10 +42,10 @@ public class OverlayIcon implements Icon { icon.paintIcon(c, g, x, y); int k = 0; - for (Icon icon : icons) { - int dx = (int) (OVERLAY_POS[k++] * (w - icon.getIconWidth())); - int dy = (int) (OVERLAY_POS[k++] * (h - icon.getIconHeight())); - icon.paintIcon(c, g, x + dx, y + dy); + for (Icon subIcon : icons) { + int dx = (int) (OVERLAY_POS[k++] * (w - subIcon.getIconWidth())); + int dy = (int) (OVERLAY_POS[k++] * (h - subIcon.getIconHeight())); + subIcon.paintIcon(c, g, x + dx, y + dy); } } diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index c1b73f9c3820770cef6221a3542bae4444c44f52..cf363e5eae5b7b9dbdc64459332d1a5ed932e36c 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -1,5 +1,7 @@ menu.file=File menu.view=View +menu.recent_files=Recent Files +menu.preferences=Preferences menu.sync=Sync with editor menu.flatten=Show flatten packages menu.navigation=Navigation @@ -43,5 +45,22 @@ search_dialog.method=Method search_dialog.field=Field search_dialog.code=Code +preferences.title=Preferences +preferences.deobfuscation=Deobfuscation +preferences.other=Other +preferences.check_for_updates=Check for updates on startup +preferences.fallback=Fallback (simple dump) +preferences.showInconsistentCode=Show inconsistent code +preferences.skipResourcesDecode=Don't decode resources +preferences.threads=Processing threads count +preferences.cfg=Generate methods CFG graphs (in 'dot' format) +preferences.raw_cfg=Generate RAW CFG graphs +preferences.deobfuscation_on=Enable deobfuscation +preferences.deobfuscation_force=Force rewrite deobfuscation map file +preferences.deobfuscation_min_len=Minimum name length +preferences.deobfuscation_max_len=Maximum name length +preferences.save=Save +preferences.cancel=Cancel + msg.open_file=Please open file msg.saving_sources=Saving sources diff --git a/jadx-gui/src/main/resources/icons-16/lock_edit.png b/jadx-gui/src/main/resources/icons-16/lock_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..116aa5b7f16df8dc578559b18318e012055898fc Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/lock_edit.png differ