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