Commit 229d78f1 authored by Skylot's avatar Skylot

gui: add preferences dialog

parent f770e4ef
......@@ -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 = "<input file> (.dex, .apk, .jar or .class)")
protected List<String> files;
......
......@@ -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<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET);
for (String l : lines) {
l = l.trim();
......
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) {
......
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<String> SKIP_FIELDS = new HashSet<String>(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<String> recentFiles = new ArrayList<String>();
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<String> 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;
}
}
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 <T> void populate(GsonBuilder builder, String json, Class<T> type, final T into) {
builder.registerTypeAdapter(type, new InstanceCreator<T>() {
@Override
public T createInstance(Type t) {
return into;
}
}).create().fromJson(json, type);
}
}
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());
}
}
}
......@@ -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)));
......
......@@ -200,10 +200,7 @@ class TabbedPane extends JTabbedPane {
closeAll.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<ContentPanel> contentPanels = new ArrayList<ContentPanel>(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<ContentPanel> contentPanels = new ArrayList<ContentPanel>(openTabs.values());
for (ContentPanel panel : contentPanels) {
closeCodePanel(panel);
}
}
}
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;
}
}
......@@ -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);
}
}
......
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment