feat(plugins): allow to set custom settings page in jadx-gui

This commit is contained in:
Skylot
2023-06-02 20:05:38 +01:00
parent 683cd76cc5
commit a72e6aeafe
31 changed files with 669 additions and 286 deletions
+16 -2
View File
@@ -12,7 +12,9 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,6 +33,7 @@ import jadx.api.usage.IUsageInfoCache;
import jadx.api.usage.impl.InMemoryUsageInfoCache;
import jadx.core.deobf.DeobfAliasProvider;
import jadx.core.deobf.DeobfCondition;
import jadx.core.plugins.PluginContext;
import jadx.core.utils.files.FileUtils;
public class JadxArgs implements Closeable {
@@ -652,7 +655,7 @@ public class JadxArgs implements Closeable {
/**
* Hash of all options that can change result code
*/
public String makeCodeArgsHash() {
public String makeCodeArgsHash(@Nullable JadxDecompiler decompiler) {
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
+ inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
@@ -661,10 +664,21 @@ public class JadxArgs implements Closeable {
+ insertDebugLines + extractFinally
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
+ commentsLevel + useDxInput + integerFormat;
+ commentsLevel + useDxInput + integerFormat
+ "|" + buildPluginsHash(decompiler);
return FileUtils.md5Sum(argStr);
}
private static String buildPluginsHash(@Nullable JadxDecompiler decompiler) {
if (decompiler == null) {
return "";
}
return decompiler.getPluginManager().getResolvedPluginContexts()
.stream()
.map(PluginContext::getInputsHash)
.collect(Collectors.joining(":"));
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -26,8 +26,8 @@ public interface JadxPluginContext {
/**
* Function to calculate hash of all options which can change output code.
* Hash for input files ({@link JadxArgs#getInputFiles()}) already calculated,
* so this method can omit these files.
* Hash for input files ({@link JadxArgs#getInputFiles()}) and registered options
* calculated by default implementations.
*/
void registerInputsHashSupplier(Supplier<String> supplier);
@@ -0,0 +1,29 @@
package jadx.api.plugins.gui;
import java.util.Collections;
import java.util.List;
import javax.swing.JComponent;
/**
* Settings page customization
*/
public interface ISettingsGroup {
/**
* Node name
*/
String getTitle();
/**
* Custom page component
*/
JComponent buildComponent();
/**
* Optional child nodes list
*/
default List<ISettingsGroup> getSubGroups() {
return Collections.emptyList();
}
}
@@ -44,4 +44,9 @@ public interface JadxGuiContext {
boolean registerGlobalKeyBinding(String id, String keyBinding, Runnable action);
void copyToClipboard(String str);
/**
* Access to GUI settings
*/
JadxGuiSettings settings();
}
@@ -0,0 +1,18 @@
package jadx.api.plugins.gui;
import java.util.List;
import jadx.api.plugins.options.OptionDescription;
public interface JadxGuiSettings {
/**
* Set plugin custom settings page
*/
void setCustomSettings(ISettingsGroup group);
/**
* Helper method to build options group only for provided option list
*/
ISettingsGroup buildSettingsGroupForOptions(String title, List<OptionDescription> options);
}
@@ -25,19 +25,10 @@ public interface OptionDescription {
@Nullable
String defaultValue();
enum OptionType {
STRING, NUMBER, BOOLEAN
}
default OptionType getType() {
return OptionType.STRING;
}
enum OptionFlag {
PER_PROJECT, // store in project settings instead global (for jadx-gui)
HIDE_IN_GUI, // do not show this option in jadx-gui (useful if option is configured with custom ui)
}
default Set<OptionFlag> getFlags() {
return Collections.emptySet();
}
@@ -0,0 +1,24 @@
package jadx.api.plugins.options;
public enum OptionFlag {
/**
* Store in project settings instead global (for jadx-gui)
*/
PER_PROJECT,
/**
* Do not show this option in jadx-gui (useful if option is configured with custom ui)
*/
HIDE_IN_GUI,
/**
* Do not show this option in jadx-gui (useful if option is configured with custom ui)
*/
DISABLE_IN_GUI,
/**
* Add this flag only if option do not affect generated code.
* If added, option value change will not cause code cache reset.
*/
NOT_CHANGING_CODE,
}
@@ -0,0 +1,7 @@
package jadx.api.plugins.options;
public enum OptionType {
STRING,
NUMBER,
BOOLEAN
}
@@ -9,6 +9,8 @@ import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.OptionFlag;
import jadx.api.plugins.options.OptionType;
public class JadxOptionDescription implements OptionDescription {
@@ -7,15 +7,14 @@ import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.loader.JadxPluginLoader;
import jadx.api.plugins.options.JadxPluginOptions;
@@ -30,7 +29,7 @@ public class JadxPluginManager {
private final SortedSet<PluginContext> resolvedPlugins = new TreeSet<>();
private final Map<String, String> provideSuggestions = new TreeMap<>();
private @Nullable JadxGuiContext guiContext;
private final List<Consumer<PluginContext>> addPluginListeners = new ArrayList<>();
public JadxPluginManager(JadxDecompiler decompiler) {
this.decompiler = decompiler;
@@ -64,7 +63,7 @@ public class JadxPluginManager {
if (!allPlugins.add(pluginContext)) {
throw new IllegalArgumentException("Duplicate plugin id: " + pluginContext + ", class " + plugin.getClass());
}
pluginContext.setGuiContext(guiContext);
addPluginListeners.forEach(l -> l.accept(pluginContext));
return pluginContext;
}
@@ -166,10 +165,9 @@ public class JadxPluginManager {
.collect(Collectors.toList());
}
public void setGuiContext(JadxGuiContext guiContext) {
this.guiContext = guiContext;
for (PluginContext context : getAllPluginContexts()) {
context.setGuiContext(guiContext);
}
public void registerAddPluginListener(Consumer<PluginContext> listener) {
this.addPluginListeners.add(listener);
// run for already added plugins
getAllPluginContexts().forEach(listener);
}
}
@@ -2,6 +2,7 @@ package jadx.core.plugins;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
@@ -16,8 +17,11 @@ import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.OptionFlag;
import jadx.api.plugins.pass.JadxPass;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class PluginContext implements JadxPluginContext, Comparable<PluginContext> {
private final JadxDecompiler decompiler;
@@ -86,14 +90,28 @@ public class PluginContext implements JadxPluginContext, Comparable<PluginContex
}
public String getInputsHash() {
if (inputsHashSupplier != null) {
try {
return inputsHashSupplier.get();
} catch (Exception e) {
throw new JadxRuntimeException("Failed to get inputs hash for plugin: " + getPluginId(), e);
if (inputsHashSupplier == null) {
return defaultOptionsHash();
}
try {
return inputsHashSupplier.get();
} catch (Exception e) {
throw new JadxRuntimeException("Failed to get inputs hash for plugin: " + getPluginId(), e);
}
}
private String defaultOptionsHash() {
if (options == null) {
return "";
}
Map<String, String> allOptions = getArgs().getPluginOptions();
StringBuilder sb = new StringBuilder();
for (OptionDescription optDesc : options.getOptionsDescriptions()) {
if (!optDesc.getFlags().contains(OptionFlag.NOT_CHANGING_CODE)) {
sb.append(':').append(allOptions.get(optDesc.name()));
}
}
return "";
return FileUtils.md5Sum(sb.toString());
}
@Override
@@ -31,7 +31,8 @@ import jadx.gui.cache.code.CodeStringCache;
import jadx.gui.cache.code.disk.BufferCodeCache;
import jadx.gui.cache.code.disk.DiskCodeCache;
import jadx.gui.cache.usage.UsageInfoCache;
import jadx.gui.plugins.context.GuiPluginsContext;
import jadx.gui.plugins.context.CommonGuiPluginsContext;
import jadx.gui.plugins.context.GuiPluginContext;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
@@ -50,7 +51,7 @@ public class JadxWrapper {
private final MainWindow mainWindow;
private volatile @Nullable JadxDecompiler decompiler;
private GuiPluginsContext guiPluginsContext;
private CommonGuiPluginsContext guiPluginsContext;
public JadxWrapper(MainWindow mainWindow) {
this.mainWindow = mainWindow;
@@ -139,11 +140,14 @@ public class JadxWrapper {
}
private void initGuiPluginsContext() {
guiPluginsContext = new GuiPluginsContext(mainWindow);
decompiler.getPluginManager().setGuiContext(guiPluginsContext);
guiPluginsContext = new CommonGuiPluginsContext(mainWindow);
decompiler.getPluginManager().registerAddPluginListener(pluginContext -> {
GuiPluginContext guiContext = guiPluginsContext.buildForPlugin(pluginContext);
pluginContext.setGuiContext(guiContext);
});
}
public GuiPluginsContext getGuiPluginsContext() {
public CommonGuiPluginsContext getGuiPluginsContext() {
return guiPluginsContext;
}
@@ -291,8 +295,7 @@ public class JadxWrapper {
}
/**
* @param fullName
* Full name of an outer class. Inner classes are not supported.
* @param fullName Full name of an outer class. Inner classes are not supported.
*/
public @Nullable JavaClass searchJavaClassByFullAlias(String fullName) {
return getDecompiler().getClasses().stream()
@@ -306,8 +309,7 @@ public class JadxWrapper {
}
/**
* @param rawName
* Full raw name of an outer class. Inner classes are not supported.
* @param rawName Full raw name of an outer class. Inner classes are not supported.
*/
public @Nullable JavaClass searchJavaClassByRawName(String rawName) {
return getDecompiler().getClasses().stream()
@@ -20,7 +20,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -33,7 +32,6 @@ import jadx.api.JadxDecompiler;
import jadx.core.Jadx;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.plugins.PluginContext;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
@@ -204,19 +202,8 @@ public class DiskCodeCache implements ICodeCache {
}
return DATA_FORMAT_VERSION
+ ":" + Jadx.getVersion()
+ ":" + args.makeCodeArgsHash()
+ ":" + FileUtils.buildInputsHash(Utils.collectionMap(inputFiles, File::toPath))
+ ":" + FileUtils.md5Sum(buildPluginsHash(decompiler));
}
private String buildPluginsHash(JadxDecompiler decompiler) {
if (decompiler == null) {
return "";
}
return decompiler.getPluginManager().getResolvedPluginContexts()
.stream()
.map(PluginContext::getInputsHash)
.collect(Collectors.joining());
+ ":" + args.makeCodeArgsHash(decompiler)
+ ":" + FileUtils.buildInputsHash(Utils.collectionMap(inputFiles, File::toPath));
}
private int getClsId(String clsFullName) {
@@ -1,37 +1,44 @@
package jadx.gui.plugins.context;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.Map;
import javax.swing.JMenu;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.core.plugins.PluginContext;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.ui.codearea.JNodePopupBuilder;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.ActionHandler;
public class GuiPluginsContext implements JadxGuiContext {
private static final Logger LOG = LoggerFactory.getLogger(GuiPluginsContext.class);
public class CommonGuiPluginsContext {
private static final Logger LOG = LoggerFactory.getLogger(CommonGuiPluginsContext.class);
private final MainWindow mainWindow;
private final Map<PluginContext, GuiPluginContext> pluginsMap = new HashMap<>();
private final List<CodePopupAction> codePopupActionList = new ArrayList<>();
public GuiPluginsContext(MainWindow mainWindow) {
public CommonGuiPluginsContext(MainWindow mainWindow) {
this.mainWindow = mainWindow;
}
public GuiPluginContext buildForPlugin(PluginContext pluginContext) {
GuiPluginContext guiPluginContext = new GuiPluginContext(this, pluginContext);
pluginsMap.put(pluginContext, guiPluginContext);
return guiPluginContext;
}
public @Nullable GuiPluginContext getPluginGuiContext(PluginContext pluginContext) {
return pluginsMap.get(pluginContext);
}
public void reset() {
codePopupActionList.clear();
JMenu pluginsMenu = mainWindow.getPluginsMenu();
@@ -39,12 +46,14 @@ public class GuiPluginsContext implements JadxGuiContext {
pluginsMenu.setVisible(false);
}
@Override
public void uiRun(Runnable runnable) {
UiUtils.uiRun(runnable);
public MainWindow getMainWindow() {
return mainWindow;
}
public List<CodePopupAction> getCodePopupActionList() {
return codePopupActionList;
}
@Override
public void addMenuAction(String name, Runnable action) {
ActionHandler item = new ActionHandler(ev -> {
try {
@@ -59,12 +68,6 @@ public class GuiPluginsContext implements JadxGuiContext {
pluginsMenu.setVisible(true);
}
@Override
public void addPopupMenuAction(String name, @Nullable Function<ICodeNodeRef, Boolean> enabled,
@Nullable String keyBinding, Consumer<ICodeNodeRef> action) {
codePopupActionList.add(new CodePopupAction(name, enabled, keyBinding, action));
}
public void appendPopupMenus(CodeArea codeArea, JNodePopupBuilder popup) {
if (codePopupActionList.isEmpty()) {
return;
@@ -74,24 +77,4 @@ public class GuiPluginsContext implements JadxGuiContext {
popup.add(codePopupAction.buildAction(codeArea));
}
}
@Override
public boolean registerGlobalKeyBinding(String id, String keyBinding, Runnable action) {
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyBinding);
if (keyStroke == null) {
throw new IllegalArgumentException("Failed to parse key binding: " + keyBinding);
}
JPanel mainPanel = (JPanel) mainWindow.getContentPane();
Object prevBinding = mainPanel.getInputMap().get(keyStroke);
if (prevBinding != null) {
return false;
}
UiUtils.addKeyBinding(mainPanel, keyStroke, id, action);
return true;
}
@Override
public void copyToClipboard(String str) {
UiUtils.copyToClipboard(str);
}
}
@@ -0,0 +1,89 @@
package jadx.gui.plugins.context;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.gui.JadxGuiSettings;
import jadx.core.plugins.PluginContext;
import jadx.gui.utils.UiUtils;
public class GuiPluginContext implements JadxGuiContext {
private static final Logger LOG = LoggerFactory.getLogger(GuiPluginContext.class);
private final CommonGuiPluginsContext commonContext;
private final PluginContext pluginContext;
private @Nullable ISettingsGroup customSettingsGroup;
public GuiPluginContext(CommonGuiPluginsContext commonContext, PluginContext pluginContext) {
this.commonContext = commonContext;
this.pluginContext = pluginContext;
}
public CommonGuiPluginsContext getCommonContext() {
return commonContext;
}
public PluginContext getPluginContext() {
return pluginContext;
}
@Override
public void uiRun(Runnable runnable) {
UiUtils.uiRun(runnable);
}
@Override
public void addMenuAction(String name, Runnable action) {
commonContext.addMenuAction(name, action);
}
@Override
public void addPopupMenuAction(String name, @Nullable Function<ICodeNodeRef, Boolean> enabled,
@Nullable String keyBinding, Consumer<ICodeNodeRef> action) {
commonContext.getCodePopupActionList().add(new CodePopupAction(name, enabled, keyBinding, action));
}
@Override
public boolean registerGlobalKeyBinding(String id, String keyBinding, Runnable action) {
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyBinding);
if (keyStroke == null) {
throw new IllegalArgumentException("Failed to parse key binding: " + keyBinding);
}
JPanel mainPanel = (JPanel) commonContext.getMainWindow().getContentPane();
Object prevBinding = mainPanel.getInputMap().get(keyStroke);
if (prevBinding != null) {
return false;
}
UiUtils.addKeyBinding(mainPanel, keyStroke, id, action);
return true;
}
@Override
public void copyToClipboard(String str) {
UiUtils.copyToClipboard(str);
}
@Override
public JadxGuiSettings settings() {
return new GuiSettingsContext(this);
}
void setCustomSettings(ISettingsGroup customSettingsGroup) {
this.customSettingsGroup = customSettingsGroup;
}
public @Nullable ISettingsGroup getCustomSettingsGroup() {
return customSettingsGroup;
}
}
@@ -0,0 +1,32 @@
package jadx.gui.plugins.context;
import java.util.List;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.api.plugins.gui.JadxGuiSettings;
import jadx.api.plugins.options.OptionDescription;
import jadx.gui.settings.ui.PluginsSettings;
import jadx.gui.settings.ui.SubSettingsGroup;
import jadx.gui.ui.MainWindow;
public class GuiSettingsContext implements JadxGuiSettings {
private final GuiPluginContext guiPluginContext;
public GuiSettingsContext(GuiPluginContext guiPluginContext) {
this.guiPluginContext = guiPluginContext;
}
@Override
public void setCustomSettings(ISettingsGroup group) {
guiPluginContext.setCustomSettings(group);
}
@Override
public ISettingsGroup buildSettingsGroupForOptions(String title, List<OptionDescription> options) {
MainWindow mainWindow = guiPluginContext.getCommonContext().getMainWindow();
PluginsSettings pluginsSettings = new PluginsSettings(mainWindow, mainWindow.getSettings());
SubSettingsGroup settingsGroup = new SubSettingsGroup(title);
pluginsSettings.addOptions(settingsGroup, options);
return settingsGroup;
}
}
@@ -15,9 +15,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import javax.swing.BorderFactory;
import javax.swing.Box;
@@ -53,16 +50,13 @@ import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler;
import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.OptionDescription.OptionFlag;
import jadx.core.plugins.PluginContext;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.gui.cache.code.CodeCacheMode;
import jadx.gui.cache.usage.UsageCacheMode;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.settings.LineNumbersMode;
@@ -73,7 +67,6 @@ import jadx.gui.utils.LafManager;
import jadx.gui.utils.LangLocale;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.plugins.CollectPluginOptions;
import jadx.gui.utils.ui.ActionHandler;
import jadx.gui.utils.ui.DocumentUpdateListener;
@@ -85,6 +78,7 @@ public class JadxSettingsWindow extends JDialog {
private final transient MainWindow mainWindow;
private final transient JadxSettings settings;
private final transient String startSettings;
private final transient String startSettingsHash;
private final transient LangLocale prevLang;
private transient boolean needReload = false;
@@ -93,6 +87,7 @@ public class JadxSettingsWindow extends JDialog {
this.mainWindow = mainWindow;
this.settings = settings;
this.startSettings = JadxSettingsAdapter.makeString(settings);
this.startSettingsHash = calcSettingsHash();
this.prevLang = settings.getLangLocale();
initUI();
@@ -113,14 +108,14 @@ public class JadxSettingsWindow extends JDialog {
groupPanel.setLayout(new BoxLayout(groupPanel, BoxLayout.LINE_AXIS));
groupPanel.setBorder(BorderFactory.createEmptyBorder(10, 3, 3, 10));
List<SettingsGroupPanel> groups = new ArrayList<>();
List<ISettingsGroup> groups = new ArrayList<>();
groups.add(makeDecompilationGroup());
groups.add(makeDeobfuscationGroup());
groups.add(makeRenameGroup());
groups.add(makeAppearanceGroup());
groups.add(makeSearchResGroup());
groups.add(makeProjectGroup());
groups.add(makePluginOptionsGroup());
groups.add(new PluginsSettings(mainWindow, settings).build());
groups.add(makeOtherGroup());
SettingsTree tree = new SettingsTree();
@@ -187,7 +182,7 @@ public class JadxSettingsWindow extends JDialog {
}
}
private SettingsGroupPanel makeDeobfuscationGroup() {
private SettingsGroup makeDeobfuscationGroup() {
JCheckBox deobfOn = new JCheckBox();
deobfOn.setSelected(settings.isDeobfuscationOn());
deobfOn.addItemListener(e -> {
@@ -228,7 +223,7 @@ public class JadxSettingsWindow extends JDialog {
}
});
SettingsGroupPanel deobfGroup = new SettingsGroupPanel(NLS.str("preferences.deobfuscation"));
SettingsGroup deobfGroup = new SettingsGroup(NLS.str("preferences.deobfuscation"));
deobfGroup.addRow(NLS.str("preferences.deobfuscation_on"), deobfOn);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLenSpinner);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLenSpinner);
@@ -242,7 +237,7 @@ public class JadxSettingsWindow extends JDialog {
return deobfGroup;
}
private SettingsGroupPanel makeRenameGroup() {
private SettingsGroup makeRenameGroup() {
JCheckBox renameCaseSensitive = new JCheckBox();
renameCaseSensitive.setSelected(settings.isRenameCaseSensitive());
renameCaseSensitive.addItemListener(e -> {
@@ -271,7 +266,7 @@ public class JadxSettingsWindow extends JDialog {
needReload();
});
SettingsGroupPanel group = new SettingsGroupPanel(NLS.str("preferences.rename"));
SettingsGroup group = new SettingsGroup(NLS.str("preferences.rename"));
group.addRow(NLS.str("preferences.rename_case"), renameCaseSensitive);
group.addRow(NLS.str("preferences.rename_valid"), renameValid);
group.addRow(NLS.str("preferences.rename_printable"), renamePrintable);
@@ -283,18 +278,18 @@ public class JadxSettingsWindow extends JDialog {
connectedComponents.forEach(comp -> comp.setEnabled(enabled));
}
private SettingsGroupPanel makeProjectGroup() {
private SettingsGroup makeProjectGroup() {
JCheckBox autoSave = new JCheckBox();
autoSave.setSelected(settings.isAutoSaveProject());
autoSave.addItemListener(e -> settings.setAutoSaveProject(e.getStateChange() == ItemEvent.SELECTED));
SettingsGroupPanel group = new SettingsGroupPanel(NLS.str("preferences.project"));
SettingsGroup group = new SettingsGroup(NLS.str("preferences.project"));
group.addRow(NLS.str("preferences.autoSave"), autoSave);
return group;
}
private SettingsGroupPanel makeAppearanceGroup() {
private SettingsGroup makeAppearanceGroup() {
JComboBox<LangLocale> languageCbx = new JComboBox<>(NLS.getLangLocales());
for (LangLocale locale : NLS.getLangLocales()) {
if (locale.equals(settings.getLangLocale())) {
@@ -329,7 +324,7 @@ public class JadxSettingsWindow extends JDialog {
mainWindow.loadSettings();
});
SettingsGroupPanel group = new SettingsGroupPanel(NLS.str("preferences.appearance"));
SettingsGroup group = new SettingsGroup(NLS.str("preferences.appearance"));
group.addRow(NLS.str("preferences.language"), languageCbx);
group.addRow(NLS.str("preferences.laf_theme"), lafCbx);
group.addRow(NLS.str("preferences.theme"), themesCbx);
@@ -382,7 +377,7 @@ public class JadxSettingsWindow extends JDialog {
return NLS.str("preferences.smali_font") + ": " + font.getFontName() + ' ' + fontStyleName + ' ' + font.getSize();
}
private SettingsGroupPanel makeDecompilationGroup() {
private SettingsGroup makeDecompilationGroup() {
JCheckBox useDx = new JCheckBox();
useDx.setSelected(settings.isUseDx());
useDx.addItemListener(e -> {
@@ -552,7 +547,7 @@ public class JadxSettingsWindow extends JDialog {
needReload();
});
SettingsGroupPanel other = new SettingsGroupPanel(NLS.str("preferences.decompile"));
SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile"));
other.addRow(NLS.str("preferences.threads"), threadsCount);
other.addRow(NLS.str("preferences.excludedPackages"),
NLS.str("preferences.excludedPackages.tooltip"), editExcludedPackages);
@@ -580,109 +575,7 @@ public class JadxSettingsWindow extends JDialog {
return other;
}
private SettingsGroupPanel makePluginOptionsGroup() {
SettingsGroupPanel pluginsGroup = new SettingsGroupPanel(NLS.str("preferences.plugins"));
List<PluginContext> list = new CollectPluginOptions(mainWindow.getWrapper()).build();
for (PluginContext context : list) {
addPluginOptions(pluginsGroup, context);
}
return pluginsGroup;
}
private void addPluginOptions(SettingsGroupPanel pluginsGroup, PluginContext context) {
JadxPluginOptions options = context.getOptions();
if (options == null) {
return;
}
String pluginId = context.getPluginId();
for (OptionDescription opt : options.getOptionsDescriptions()) {
if (opt.getFlags().contains(OptionFlag.HIDE_IN_GUI)) {
continue;
}
String optName = opt.name();
String title;
if (pluginId.equals("jadx-script")) {
title = '[' + optName.replace("jadx-script.", "script:") + "] " + opt.description();
} else {
title = '[' + pluginId + "] " + opt.description();
}
Consumer<String> updateFunc;
String curValue;
if (opt.getFlags().contains(OptionFlag.PER_PROJECT)) {
JadxProject project = mainWindow.getProject();
updateFunc = value -> project.updatePluginOptions(m -> m.put(optName, value));
curValue = project.getPluginOption(optName);
} else {
Map<String, String> optionsMap = settings.getPluginOptions();
updateFunc = value -> optionsMap.put(optName, value);
curValue = optionsMap.get(optName);
}
String value = curValue != null ? curValue : opt.defaultValue();
if (opt.values().isEmpty() || opt.getType() == OptionDescription.OptionType.BOOLEAN) {
try {
pluginsGroup.addRow(title, getPluginOptionEditor(opt, value, updateFunc));
} catch (Exception e) {
LOG.error("Failed to add editor for plugin option: {}", optName, e);
}
} else {
JComboBox<String> combo = new JComboBox<>(opt.values().toArray(new String[0]));
combo.setSelectedItem(value);
combo.addActionListener(e -> {
updateFunc.accept((String) combo.getSelectedItem());
needReload();
});
pluginsGroup.addRow(title, combo);
}
}
}
private JComponent getPluginOptionEditor(OptionDescription opt, String value, Consumer<String> updateFunc) {
switch (opt.getType()) {
case STRING:
JTextField textField = new JTextField();
textField.setText(value == null ? "" : value);
textField.getDocument().addDocumentListener(new DocumentUpdateListener(event -> {
updateFunc.accept(textField.getText());
needReload();
}));
return textField;
case NUMBER:
JSpinner numberField = new JSpinner();
numberField.setValue(safeStringToInt(value, 0));
numberField.addChangeListener(e -> {
updateFunc.accept(numberField.getValue().toString());
needReload();
});
return numberField;
case BOOLEAN:
JCheckBox boolField = new JCheckBox();
boolField.setSelected(Objects.equals(value, "yes") || Objects.equals(value, "true"));
boolField.addItemListener(e -> {
boolean editorValue = e.getStateChange() == ItemEvent.SELECTED;
updateFunc.accept(editorValue ? "yes" : "no");
needReload();
});
return boolField;
}
return null;
}
private int safeStringToInt(String value, int defValue) {
if (value == null) {
return defValue;
}
try {
return Integer.parseInt(value);
} catch (Exception e) {
LOG.warn("Failed parse string to int: {}", value, e);
return defValue;
}
}
private SettingsGroupPanel makeOtherGroup() {
private SettingsGroup makeOtherGroup() {
JComboBox<LineNumbersMode> lineNumbersMode = new JComboBox<>(LineNumbersMode.values());
lineNumbersMode.setSelectedItem(settings.getLineNumbersMode());
lineNumbersMode.addActionListener(e -> {
@@ -716,7 +609,7 @@ public class JadxSettingsWindow extends JDialog {
needReload();
});
SettingsGroupPanel group = new SettingsGroupPanel(NLS.str("preferences.other"));
SettingsGroup group = new SettingsGroup(NLS.str("preferences.other"));
group.addRow(NLS.str("preferences.lineNumbersMode"), lineNumbersMode);
group.addRow(NLS.str("preferences.jumpOnDoubleClick"), jumpOnDoubleClick);
group.addRow(NLS.str("preferences.useAlternativeFileDialog"), useAltFileDialog);
@@ -726,7 +619,7 @@ public class JadxSettingsWindow extends JDialog {
return group;
}
private SettingsGroupPanel makeSearchResGroup() {
private SettingsGroup makeSearchResGroup() {
JSpinner resultsPerPage = new JSpinner(
new SpinnerNumberModel(settings.getSearchResultsPerPage(), 0, Integer.MAX_VALUE, 1));
resultsPerPage.addChangeListener(ev -> settings.setSearchResultsPerPage((Integer) resultsPerPage.getValue()));
@@ -742,7 +635,7 @@ public class JadxSettingsWindow extends JDialog {
}));
fileExtField.setText(settings.getSrhResourceFileExt());
SettingsGroupPanel searchGroup = new SettingsGroupPanel(NLS.str("preferences.search_group_title"));
SettingsGroup searchGroup = new SettingsGroup(NLS.str("preferences.search_group_title"));
searchGroup.addRow(NLS.str("preferences.search_results_per_page"), resultsPerPage);
searchGroup.addRow(NLS.str("preferences.res_skip_file"), sizeLimit);
searchGroup.addRow(NLS.str("preferences.res_file_ext"), fileExtField);
@@ -753,7 +646,7 @@ public class JadxSettingsWindow extends JDialog {
settings.sync();
enableComponents(this, false);
SwingUtilities.invokeLater(() -> {
if (needReload) {
if (shouldReload()) {
mainWindow.reopen();
}
if (!settings.getLangLocale().equals(prevLang)) {
@@ -809,10 +702,20 @@ public class JadxSettingsWindow extends JDialog {
NLS.str("preferences.copy_message"));
}
private void needReload() {
void needReload() {
needReload = true;
}
private boolean shouldReload() {
return needReload || !startSettingsHash.equals(calcSettingsHash());
}
@SuppressWarnings("resource")
private String calcSettingsHash() {
JadxDecompiler decompiler = mainWindow.getWrapper().getCurrentDecompiler().orElse(null);
return settings.toJadxArgs().makeCodeArgsHash(decompiler);
}
@Override
public void dispose() {
settings.saveWindowPos(this);
@@ -0,0 +1,171 @@
package jadx.gui.settings.ui;
import java.awt.event.ItemEvent;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.IntSupplier;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.OptionFlag;
import jadx.api.plugins.options.OptionType;
import jadx.core.plugins.PluginContext;
import jadx.gui.plugins.context.GuiPluginContext;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.NLS;
import jadx.gui.utils.plugins.CollectPluginOptions;
import jadx.gui.utils.ui.DocumentUpdateListener;
public class PluginsSettings {
private static final Logger LOG = LoggerFactory.getLogger(PluginsSettings.class);
private final MainWindow mainWindow;
private final JadxSettings settings;
public PluginsSettings(MainWindow mainWindow, JadxSettings settings) {
this.mainWindow = mainWindow;
this.settings = settings;
}
public SettingsGroup build() {
SettingsGroup pluginsGroup = new SubSettingsGroup(NLS.str("preferences.plugins"));
fillMainSettings(pluginsGroup);
List<PluginContext> list = new CollectPluginOptions(mainWindow).build();
for (PluginContext context : list) {
ISettingsGroup pluginGroup = buildPluginGroup(context);
if (pluginGroup != null) {
pluginsGroup.getSubGroups().add(pluginGroup);
}
}
return pluginsGroup;
}
private void fillMainSettings(SettingsGroup settingsGroup) {
JPanel panel = settingsGroup.getPanel();
panel.add(new JPanel());
}
private ISettingsGroup buildPluginGroup(PluginContext context) {
JadxGuiContext guiContext = context.getGuiContext();
if (guiContext instanceof GuiPluginContext) {
GuiPluginContext pluginGuiContext = ((GuiPluginContext) guiContext);
ISettingsGroup customSettingsGroup = pluginGuiContext.getCustomSettingsGroup();
if (customSettingsGroup != null) {
return customSettingsGroup;
}
}
JadxPluginOptions options = context.getOptions();
if (options == null) {
return null;
}
List<OptionDescription> optionsDescriptions = options.getOptionsDescriptions();
if (optionsDescriptions.isEmpty()) {
return null;
}
SettingsGroup settingsGroup = new SettingsGroup(context.getPluginInfo().getName());
addOptions(settingsGroup, optionsDescriptions);
return settingsGroup;
}
public void addOptions(SettingsGroup pluginGroup, List<OptionDescription> optionsDescriptions) {
for (OptionDescription opt : optionsDescriptions) {
if (opt.getFlags().contains(OptionFlag.HIDE_IN_GUI)) {
continue;
}
String optName = opt.name();
String title = opt.description();
Consumer<String> updateFunc;
String curValue;
if (opt.getFlags().contains(OptionFlag.PER_PROJECT)) {
JadxProject project = mainWindow.getProject();
updateFunc = value -> project.updatePluginOptions(m -> m.put(optName, value));
curValue = project.getPluginOption(optName);
} else {
Map<String, String> optionsMap = settings.getPluginOptions();
updateFunc = value -> optionsMap.put(optName, value);
curValue = optionsMap.get(optName);
}
String value = curValue != null ? curValue : opt.defaultValue();
JComponent editor = null;
if (opt.values().isEmpty() || opt.getType() == OptionType.BOOLEAN) {
try {
editor = getPluginOptionEditor(opt, value, updateFunc);
} catch (Exception e) {
LOG.error("Failed to add editor for plugin option: {}", optName, e);
}
} else {
JComboBox<String> combo = new JComboBox<>(opt.values().toArray(new String[0]));
combo.setSelectedItem(value);
combo.addActionListener(e -> updateFunc.accept((String) combo.getSelectedItem()));
editor = combo;
}
if (editor != null) {
JLabel label = pluginGroup.addRow(title, editor);
boolean enabled = !opt.getFlags().contains(OptionFlag.DISABLE_IN_GUI);
if (!enabled) {
label.setEnabled(false);
editor.setEnabled(false);
}
}
}
}
private JComponent getPluginOptionEditor(OptionDescription opt, String value, Consumer<String> updateFunc) {
switch (opt.getType()) {
case STRING:
JTextField textField = new JTextField();
textField.setText(value == null ? "" : value);
textField.getDocument().addDocumentListener(
new DocumentUpdateListener(event -> updateFunc.accept(textField.getText())));
return textField;
case NUMBER:
JSpinner numberField = new JSpinner();
numberField.setValue(safeStringToInt(value, () -> safeStringToInt(opt.defaultValue(), () -> {
throw new IllegalArgumentException("Failed to parse integer default value: " + opt.defaultValue());
})));
numberField.addChangeListener(e -> updateFunc.accept(numberField.getValue().toString()));
return numberField;
case BOOLEAN:
JCheckBox boolField = new JCheckBox();
boolField.setSelected(Objects.equals(value, "yes") || Objects.equals(value, "true"));
boolField.addItemListener(e -> {
boolean editorValue = e.getStateChange() == ItemEvent.SELECTED;
updateFunc.accept(editorValue ? "yes" : "no");
});
return boolField;
}
return null;
}
private static int safeStringToInt(String value, IntSupplier defValueSupplier) {
if (value == null) {
return defValueSupplier.getAsInt();
}
try {
return Integer.parseInt(value);
} catch (Exception e) {
LOG.warn("Failed parse string to int: {}", value, e);
return defValueSupplier.getAsInt();
}
}
}
@@ -11,17 +11,20 @@ import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
public class SettingsGroupPanel extends JPanel {
import jadx.api.plugins.gui.ISettingsGroup;
public class SettingsGroup implements ISettingsGroup {
private static final long serialVersionUID = -6487309975896192544L;
private final String title;
private final JPanel panel;
private final GridBagConstraints c;
private int row;
public SettingsGroupPanel(String title) {
public SettingsGroup(String title) {
this.title = title;
setBorder(BorderFactory.createTitledBorder(title));
setLayout(new GridBagLayout());
panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createTitledBorder(title));
c = new GridBagConstraints();
c.insets = new Insets(5, 5, 5, 5);
c.weighty = 1.0;
@@ -41,7 +44,7 @@ public class SettingsGroupPanel extends JPanel {
c.anchor = GridBagConstraints.LINE_START;
c.weightx = 0.8;
c.fill = GridBagConstraints.NONE;
add(jLabel, c);
panel.add(jLabel, c);
c.gridx = 1;
c.gridwidth = GridBagConstraints.REMAINDER;
c.anchor = GridBagConstraints.CENTER;
@@ -53,20 +56,30 @@ public class SettingsGroupPanel extends JPanel {
comp.setToolTipText(tooltip);
}
add(comp, c);
panel.add(comp, c);
comp.addPropertyChangeListener("enabled", evt -> jLabel.setEnabled((boolean) evt.getNewValue()));
return jLabel;
}
public void end() {
add(Box.createVerticalGlue());
panel.add(Box.createVerticalGlue());
}
@Override
public JComponent buildComponent() {
return panel;
}
@Override
public String getTitle() {
return title;
}
public JPanel getPanel() {
return panel;
}
@Override
public String toString() {
return title;
@@ -3,6 +3,7 @@ package jadx.gui.settings.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javax.swing.JPanel;
import javax.swing.JTree;
@@ -15,43 +16,41 @@ import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.gui.utils.NLS;
public class SettingsTree extends JTree {
public void init(JPanel groupPanel, List<SettingsGroupPanel> groups) {
public void init(JPanel groupPanel, List<ISettingsGroup> groups) {
DefaultMutableTreeNode treeRoot = new DefaultMutableTreeNode(NLS.str("preferences.title"));
for (SettingsGroupPanel group : groups) {
treeRoot.add(new DefaultMutableTreeNode(group));
}
addGroups(treeRoot, groups);
setModel(new DefaultTreeModel(treeRoot));
getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
setFocusable(false);
addTreeSelectionListener(e -> {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) getLastSelectedPathComponent();
Object obj = node.getUserObject();
groupPanel.removeAll();
if (obj instanceof SettingsGroupPanel) {
SettingsGroupPanel panel = (SettingsGroupPanel) obj;
groupPanel.add(panel);
}
groupPanel.updateUI();
});
addTreeSelectionListener(e -> switchGroup(groupPanel));
// expand all nodes and disallow collapsing
setNodeExpandedState(this, treeRoot, true);
addTreeWillExpandListener(new TreeWillExpandListener() {
@Override
public void treeWillExpand(TreeExpansionEvent event) {
}
@Override
public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
throw new ExpandVetoException(event, "Collapsing tree not allowed");
}
});
addTreeWillExpandListener(new DisableRootCollapseListener(treeRoot));
addSelectionRow(1);
}
private static void addGroups(DefaultMutableTreeNode base, List<ISettingsGroup> groups) {
for (ISettingsGroup group : groups) {
SettingsTreeNode node = new SettingsTreeNode(group);
base.add(node);
addGroups(node, group.getSubGroups());
}
}
private void switchGroup(JPanel groupPanel) {
Object selected = getLastSelectedPathComponent();
groupPanel.removeAll();
if (selected instanceof SettingsTreeNode) {
groupPanel.add(((SettingsTreeNode) selected).getGroup().buildComponent());
}
groupPanel.updateUI();
}
private static void setNodeExpandedState(JTree tree, TreeNode node, boolean expanded) {
ArrayList<? extends TreeNode> list = Collections.list(node.children());
for (TreeNode treeNode : list) {
@@ -68,4 +67,24 @@ public class SettingsTree extends JTree {
tree.collapsePath(path);
}
}
private static class DisableRootCollapseListener implements TreeWillExpandListener {
private final DefaultMutableTreeNode treeRoot;
public DisableRootCollapseListener(DefaultMutableTreeNode treeRoot) {
this.treeRoot = treeRoot;
}
@Override
public void treeWillExpand(TreeExpansionEvent event) {
}
@Override
public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
Object current = event.getPath().getLastPathComponent();
if (Objects.equals(current, treeRoot)) {
throw new ExpandVetoException(event, "Root collapsing not allowed");
}
}
}
}
@@ -0,0 +1,22 @@
package jadx.gui.settings.ui;
import javax.swing.tree.DefaultMutableTreeNode;
import jadx.api.plugins.gui.ISettingsGroup;
public class SettingsTreeNode extends DefaultMutableTreeNode {
private final ISettingsGroup group;
public SettingsTreeNode(ISettingsGroup group) {
this.group = group;
}
public ISettingsGroup getGroup() {
return group;
}
@Override
public String toString() {
return group.getTitle();
}
}
@@ -0,0 +1,20 @@
package jadx.gui.settings.ui;
import java.util.ArrayList;
import java.util.List;
import jadx.api.plugins.gui.ISettingsGroup;
public class SubSettingsGroup extends SettingsGroup {
private final List<ISettingsGroup> groups = new ArrayList<>();
public SubSettingsGroup(String title) {
super(title);
}
@Override
public List<ISettingsGroup> getSubGroups() {
return groups;
}
}
@@ -9,7 +9,9 @@ import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.core.plugins.JadxPluginManager;
import jadx.core.plugins.PluginContext;
import jadx.gui.JadxWrapper;
import jadx.gui.plugins.context.CommonGuiPluginsContext;
import jadx.gui.plugins.context.GuiPluginContext;
import jadx.gui.ui.MainWindow;
import jadx.plugins.tools.JadxExternalPluginsLoader;
/**
@@ -19,21 +21,26 @@ import jadx.plugins.tools.JadxExternalPluginsLoader;
*/
public class CollectPluginOptions {
private final JadxWrapper wrapper;
private final MainWindow mainWindow;
public CollectPluginOptions(JadxWrapper wrapper) {
this.wrapper = wrapper;
public CollectPluginOptions(MainWindow mainWindow) {
this.mainWindow = mainWindow;
}
public List<PluginContext> build() {
SortedSet<PluginContext> allPlugins = new TreeSet<>();
wrapper.getCurrentDecompiler()
mainWindow.getWrapper().getCurrentDecompiler()
.ifPresent(decompiler -> allPlugins.addAll(decompiler.getPluginManager().getResolvedPluginContexts()));
// collect and init not loaded plugins in new context
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
JadxPluginManager pluginManager = decompiler.getPluginManager();
pluginManager.load(new JadxExternalPluginsLoader());
CommonGuiPluginsContext guiPluginsContext = new CommonGuiPluginsContext(mainWindow);
decompiler.getPluginManager().registerAddPluginListener(pluginContext -> {
GuiPluginContext guiContext = guiPluginsContext.buildForPlugin(pluginContext);
pluginContext.setGuiContext(guiContext);
});
SortedSet<PluginContext> missingPlugins = new TreeSet<>();
for (PluginContext context : pluginManager.getAllPluginContexts()) {
if (!allPlugins.contains(context)) {
@@ -23,7 +23,7 @@ public class DexInputPlugin implements JadxPlugin {
@Override
public JadxPluginInfo getPluginInfo() {
return new JadxPluginInfo(PLUGIN_ID, "DexInput", "Load .dex and .apk files");
return new JadxPluginInfo(PLUGIN_ID, "Dex Input", "Load .dex and .apk files");
}
@Override
@@ -23,7 +23,7 @@ public class JavaConvertPlugin implements JadxPlugin, JadxCodeInput {
public JadxPluginInfo getPluginInfo() {
return new JadxPluginInfo(
PLUGIN_ID,
"JavaConvert",
"Java Convert",
"Convert .class, .jar and .aar files to dex",
"java-input");
}
@@ -2,7 +2,7 @@ package jadx.plugins.kotlin.metadata
import jadx.api.plugins.options.OptionDescription
import jadx.api.plugins.options.impl.BaseOptionsParser
import jadx.api.plugins.options.impl.JadxOptionDescription
import jadx.api.plugins.options.impl.JadxOptionDescription.booleanOption
import jadx.plugins.kotlin.metadata.KotlinMetadataPlugin.Companion.PLUGIN_ID
class KotlinMetadataOptions : BaseOptionsParser() {
@@ -33,27 +33,16 @@ class KotlinMetadataOptions : BaseOptionsParser() {
override fun getOptionsDescriptions(): List<OptionDescription> {
return listOf(
JadxOptionDescription.booleanOption(CLASS_ALIAS_OPT, "rename class alias", true)
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
JadxOptionDescription.booleanOption(METHOD_ARGS_OPT, "rename function arguments", true)
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
JadxOptionDescription.booleanOption(FIELDS_OPT, "rename fields", true)
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
JadxOptionDescription.booleanOption(COMPANION_OPT, "rename companion object", true)
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
JadxOptionDescription.booleanOption(DATA_CLASS_OPT, "add data class modifier", true)
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
JadxOptionDescription.booleanOption(TO_STRING_OPT, "rename fields using toString", true)
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
JadxOptionDescription.booleanOption(GETTERS_OPT, "rename simple getters to field names", true)
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
booleanOption(CLASS_ALIAS_OPT, "rename class alias", true),
booleanOption(METHOD_ARGS_OPT, "rename function arguments", true),
booleanOption(FIELDS_OPT, "rename fields", true),
booleanOption(COMPANION_OPT, "rename companion object", true),
booleanOption(DATA_CLASS_OPT, "add data class modifier", true),
booleanOption(TO_STRING_OPT, "rename fields using toString", true),
booleanOption(GETTERS_OPT, "rename simple getters to field names", true),
)
}
override fun toString(): String {
return "KotlinMetadataOptions(isClassAlias=$isClassAlias, isMethodArgs=$isMethodArgs, isFields=$isFields, isCompanion=$isCompanion, isDataClass=$isDataClass, isToString=$isToString, isGetters=$isGetters)"
}
companion object {
const val CLASS_ALIAS_OPT = "$PLUGIN_ID.class-alias"
const val METHOD_ARGS_OPT = "$PLUGIN_ID.method-args"
@@ -10,7 +10,7 @@ import org.jetbrains.annotations.Nullable;
import net.fabricmc.mappingio.format.MappingFormat;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.OptionDescription.OptionFlag;
import jadx.api.plugins.options.OptionFlag;
import jadx.api.plugins.options.impl.BaseOptionsParser;
import jadx.api.plugins.options.impl.JadxOptionDescription;
@@ -0,0 +1,30 @@
package jadx.plugins.script
import jadx.api.plugins.gui.ISettingsGroup
import jadx.api.plugins.gui.JadxGuiContext
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
import javax.swing.JPanel
object JadxScriptOptionsUI {
fun setup(guiContext: JadxGuiContext, scriptOptions: JadxScriptAllOptions) {
val settings = guiContext.settings()
val subGroups = scriptOptions.descriptions
.groupBy { it.script }
.map { (script, options) -> settings.buildSettingsGroupForOptions(script, options) }
.toList()
settings.setCustomSettings(EmptyRootGroup("Scripts", subGroups))
}
}
private class EmptyRootGroup(
private val title: String,
private val subGroups: List<ISettingsGroup>,
) : ISettingsGroup {
override fun getTitle() = title
override fun buildComponent() = JPanel()
override fun getSubGroups() = subGroups
}
@@ -16,6 +16,7 @@ class JadxScriptPlugin : JadxPlugin {
val scripts = ScriptEval().process(init, scriptOptions)
if (scripts.isNotEmpty()) {
init.addPass(JadxScriptAfterLoadPass(scripts))
init.guiContext?.let { JadxScriptOptionsUI.setup(it, scriptOptions) }
}
}
}
@@ -2,21 +2,30 @@ package jadx.plugins.script.runtime.data
import jadx.api.plugins.options.JadxPluginOptions
import jadx.api.plugins.options.OptionDescription
import jadx.api.plugins.options.OptionDescription.OptionType
import jadx.api.plugins.options.OptionType
import jadx.api.plugins.options.impl.JadxOptionDescription
import jadx.plugins.script.runtime.JadxScriptInstance
class JadxScriptAllOptions : JadxPluginOptions {
lateinit var values: Map<String, String>
val descriptions: MutableList<OptionDescription> = mutableListOf()
val descriptions: MutableList<ScriptOptionDesc> = mutableListOf()
override fun setOptions(options: Map<String, String>) {
values = options
}
override fun getOptionsDescriptions(): MutableList<OptionDescription> = descriptions
override fun getOptionsDescriptions(): List<OptionDescription> = descriptions
}
class ScriptOptionDesc(
val script: String,
optName: String,
desc: String,
defaultValue: String?,
values: List<String>,
type: OptionType,
) : JadxOptionDescription("jadx-script.$script.$optName", desc, defaultValue, values, type)
class ScriptOption<T>(
val name: String,
val id: String,
@@ -53,9 +62,10 @@ class JadxScriptOptions(
type: OptionType = OptionType.STRING,
convert: (String?) -> T,
): ScriptOption<T> {
val id = "jadx-script.${jadx.scriptName}.$name"
options.descriptions.add(JadxOptionDescription(id, desc, defaultValue, values, type))
return ScriptOption(name, id) { convert.invoke(options.values[id]) }
val optDesc = ScriptOptionDesc(jadx.scriptName, name, desc, defaultValue, values, type)
options.descriptions.add(optDesc)
val optId = optDesc.name()
return ScriptOption(name, optId) { convert.invoke(options.values[optId]) }
}
fun registerString(