diff --git a/jadx-cli/src/main/java/jadx/cli/commands/CommandPlugins.java b/jadx-cli/src/main/java/jadx/cli/commands/CommandPlugins.java index 2c6a516193a..9d925d6df73 100644 --- a/jadx-cli/src/main/java/jadx/cli/commands/CommandPlugins.java +++ b/jadx-cli/src/main/java/jadx/cli/commands/CommandPlugins.java @@ -33,6 +33,12 @@ public class CommandPlugins implements ICommand { @Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId") protected String uninstall; + @Parameter(names = { "--disable" }, description = "disable plugin with pluginId") + protected String disable; + + @Parameter(names = { "--enable" }, description = "enable plugin with pluginId") + protected String enable; + @Parameter(names = { "-h", "--help" }, description = "print this help", help = true) protected boolean printHelp = false; @@ -47,6 +53,10 @@ public void process(JCommanderWrapper jcw, JCommander subCommander) { jcw.printUsage(subCommander); return; } + if (!subCommander.getUnknownOptions().isEmpty()) { + System.out.println("Error: found unknown options: " + subCommander.getUnknownOptions()); + } + if (install != null) { installPlugin(install); } @@ -69,26 +79,54 @@ public void process(JCommanderWrapper jcw, JCommander subCommander) { } } if (list) { - List installed = JadxPluginsTools.getInstance().getInstalled(); - System.out.println("Installed plugins: " + installed.size()); - int i = 1; - for (JadxPluginMetadata plugin : installed) { - System.out.println(" " + (i++) + ") " - + plugin.getPluginId() + " (" + plugin.getVersion() + ") - " - + plugin.getName() + ": " + plugin.getDescription()); - } + printInstalledPlugins(); } if (available) { List availableList = JadxPluginsList.getInstance().get(); System.out.println("Available plugins: " + availableList.size()); - int i = 1; for (JadxPluginMetadata plugin : availableList) { - System.out.println(" " + (i++) + ") " - + plugin.getName() + ": " + plugin.getDescription() + System.out.println(" - " + plugin.getName() + ": " + plugin.getDescription() + " (" + plugin.getLocationId() + ")"); } } + + if (disable != null) { + if (JadxPluginsTools.getInstance().changeDisabledStatus(disable, true)) { + System.out.println("Plugin '" + disable + "' disabled."); + } else { + System.out.println("Plugin '" + disable + "' already disabled."); + } + } + if (enable != null) { + if (JadxPluginsTools.getInstance().changeDisabledStatus(enable, false)) { + System.out.println("Plugin '" + enable + "' enabled."); + } else { + System.out.println("Plugin '" + enable + "' already enabled."); + } + } + } + + private static void printInstalledPlugins() { + List installed = JadxPluginsTools.getInstance().getInstalled(); + System.out.println("Installed plugins: " + installed.size()); + for (JadxPluginMetadata plugin : installed) { + StringBuilder sb = new StringBuilder(); + sb.append(" - "); + sb.append(plugin.getPluginId()); + String version = plugin.getVersion(); + if (version != null) { + sb.append(" (").append(version).append(')'); + } + if (plugin.isDisabled()) { + sb.append(" (disabled)"); + } + sb.append(" - "); + sb.append(plugin.getName()); + sb.append(": "); + sb.append(plugin.getDescription()); + System.out.println(sb); + } } private void installPlugin(String locationId) { diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index aabaa886c0c..c986257905b 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -93,13 +93,14 @@ public final class JadxDecompiler implements Closeable { private List resources; private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(); - private final JadxEventsImpl events = new JadxEventsImpl(); private final ResourcesLoader resourcesLoader = new ResourcesLoader(this); private final List customCodeLoaders = new ArrayList<>(); private final List customResourcesLoaders = new ArrayList<>(); private final Map> customPasses = new HashMap<>(); + private IJadxEvents events = new JadxEventsImpl(); + public JadxDecompiler() { this(new JadxArgs()); } @@ -666,6 +667,10 @@ public IJadxEvents events() { return events; } + public void setEventsImpl(IJadxEvents eventsImpl) { + this.events = eventsImpl; + } + public void addCustomCodeLoader(ICodeLoader customCodeLoader) { customCodeLoaders.add(customCodeLoader); } diff --git a/jadx-core/src/main/java/jadx/api/plugins/events/IJadxEvents.java b/jadx-core/src/main/java/jadx/api/plugins/events/IJadxEvents.java index 429a9600069..2dc9868e446 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/events/IJadxEvents.java +++ b/jadx-core/src/main/java/jadx/api/plugins/events/IJadxEvents.java @@ -15,4 +15,15 @@ public interface IJadxEvents { * For public event types check {@link JadxEvents} class. */ void addListener(JadxEventType eventType, Consumer listener); + + /** + * Remove listener for specific event. + * Listener should be same or equal object. + */ + void removeListener(JadxEventType eventType, Consumer listener); + + /** + * Clear all listeners. + */ + void reset(); } diff --git a/jadx-core/src/main/java/jadx/api/plugins/events/JadxEventType.java b/jadx-core/src/main/java/jadx/api/plugins/events/JadxEventType.java index 98be11ca01d..a1aa9383caf 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/events/JadxEventType.java +++ b/jadx-core/src/main/java/jadx/api/plugins/events/JadxEventType.java @@ -6,4 +6,13 @@ public static JadxEventType create() { return new JadxEventType<>() { }; } + + public static JadxEventType create(String name) { + return new JadxEventType<>() { + @Override + public String toString() { + return name; + } + }; + } } diff --git a/jadx-core/src/main/java/jadx/api/plugins/events/JadxEvents.java b/jadx-core/src/main/java/jadx/api/plugins/events/JadxEvents.java index e9b69815bfc..f44f7033470 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/events/JadxEvents.java +++ b/jadx-core/src/main/java/jadx/api/plugins/events/JadxEvents.java @@ -16,17 +16,17 @@ public class JadxEvents { /** * Notify about renaming done by user (GUI only). */ - public static final JadxEventType NODE_RENAMED_BY_USER = create(); + public static final JadxEventType NODE_RENAMED_BY_USER = create("NODE_RENAMED_BY_USER"); /** * Request reload of a current project (GUI only). */ - public static final JadxEventType RELOAD_PROJECT = create(); + public static final JadxEventType RELOAD_PROJECT = create("RELOAD_PROJECT"); /** * Request reload of a settings window (GUI only). * Useful for a reload custom settings group which was set with * {@link JadxGuiSettings#setCustomSettingsGroup(ISettingsGroup)}. */ - public static final JadxEventType RELOAD_SETTINGS_WINDOW = create(); + public static final JadxEventType RELOAD_SETTINGS_WINDOW = create("RELOAD_SETTINGS_WINDOW"); } diff --git a/jadx-core/src/main/java/jadx/core/Consts.java b/jadx-core/src/main/java/jadx/core/Consts.java index db6154ee3f8..b6f387a33af 100644 --- a/jadx-core/src/main/java/jadx/core/Consts.java +++ b/jadx-core/src/main/java/jadx/core/Consts.java @@ -10,7 +10,7 @@ public class Consts { public static final boolean DEBUG_FINALLY = false; public static final boolean DEBUG_ATTRIBUTES = false; public static final boolean DEBUG_RESTRUCTURE = false; - public static final boolean DEBUG_EVENTS = true; + public static final boolean DEBUG_EVENTS = Jadx.isDevVersion(); public static final String CLASS_OBJECT = "java.lang.Object"; public static final String CLASS_STRING = "java.lang.String"; diff --git a/jadx-core/src/main/java/jadx/core/plugins/events/JadxEventsImpl.java b/jadx-core/src/main/java/jadx/core/plugins/events/JadxEventsImpl.java index ef64c403d20..af509696a6e 100644 --- a/jadx-core/src/main/java/jadx/core/plugins/events/JadxEventsImpl.java +++ b/jadx-core/src/main/java/jadx/core/plugins/events/JadxEventsImpl.java @@ -26,8 +26,20 @@ public void send(IJadxEvent event) { @Override public void addListener(JadxEventType eventType, Consumer listener) { manager.addListener(eventType, listener); + if (Consts.DEBUG_EVENTS) { + LOG.debug("add listener for: {}, stats: {}", eventType, manager.listenersDebugStats()); + } + } + + @Override + public void removeListener(JadxEventType eventType, Consumer listener) { + manager.removeListener(eventType, listener); + if (Consts.DEBUG_EVENTS) { + LOG.debug("remove listener for: {}, stats: {}", eventType, manager.listenersDebugStats()); + } } + @Override public void reset() { manager.reset(); } diff --git a/jadx-core/src/main/java/jadx/core/plugins/events/JadxEventsManager.java b/jadx-core/src/main/java/jadx/core/plugins/events/JadxEventsManager.java index 269e5fdbc8e..10075bc56bd 100644 --- a/jadx-core/src/main/java/jadx/core/plugins/events/JadxEventsManager.java +++ b/jadx-core/src/main/java/jadx/core/plugins/events/JadxEventsManager.java @@ -9,6 +9,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; @@ -35,6 +36,14 @@ public synchronized void addListener(JadxEventType eve .add((Consumer) listener); } + public synchronized boolean removeListener(JadxEventType eventType, Consumer listener) { + List> eventListeners = listeners.get(eventType); + if (eventListeners != null) { + return eventListeners.remove(listener); + } + return false; + } + public synchronized void send(IJadxEvent event) { List> consumers = listeners.get(event.getType()); if (consumers != null) { @@ -58,4 +67,12 @@ public Thread newThread(@NotNull Runnable r) { } }; } + + public String listenersDebugStats() { + return listeners.entrySet() + .stream() + .filter(p -> !p.getValue().isEmpty()) + .map(p -> p.getKey() + ":" + p.getValue().size()) + .collect(Collectors.joining(", ", "[", "]")); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index 1b13438c7ff..65b79593736 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -70,6 +70,8 @@ public void open() { decompiler = new JadxDecompiler(jadxArgs); guiPluginsContext = initGuiPluginsContext(decompiler, mainWindow); initUsageCache(jadxArgs); + decompiler.setEventsImpl(mainWindow.events()); + decompiler.load(); initCodeCache(); } @@ -159,6 +161,11 @@ public void resetGuiPluginsContext() { guiPluginsContext.reset(); } + public void reloadPasses() { + resetGuiPluginsContext(); + decompiler.reloadPasses(); + } + /** * Get the complete list of classes */ diff --git a/jadx-gui/src/main/java/jadx/gui/events/JadxGuiEvents.java b/jadx-gui/src/main/java/jadx/gui/events/JadxGuiEvents.java index ff6d33051f7..69a37b2e730 100644 --- a/jadx-gui/src/main/java/jadx/gui/events/JadxGuiEvents.java +++ b/jadx-gui/src/main/java/jadx/gui/events/JadxGuiEvents.java @@ -7,5 +7,5 @@ public class JadxGuiEvents { - public static final JadxEventType TREE_UPDATE = create(); + public static final JadxEventType TREE_UPDATE = create("TREE_UPDATE"); } diff --git a/jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java b/jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java index ad8406c46fb..5da12d818ea 100644 --- a/jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java +++ b/jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java @@ -48,7 +48,7 @@ public class RenameService { public static void init(MainWindow mainWindow) { RenameService renameService = new RenameService(mainWindow); - mainWindow.events().addListener(JadxEvents.NODE_RENAMED_BY_USER, renameService::process); + mainWindow.events().global().addListener(JadxEvents.NODE_RENAMED_BY_USER, renameService::process); } private final MainWindow mainWindow; diff --git a/jadx-gui/src/main/java/jadx/gui/events/types/JadxGuiEventsImpl.java b/jadx-gui/src/main/java/jadx/gui/events/types/JadxGuiEventsImpl.java new file mode 100644 index 00000000000..bc97f4b4b9b --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/events/types/JadxGuiEventsImpl.java @@ -0,0 +1,43 @@ +package jadx.gui.events.types; + +import java.util.function.Consumer; + +import jadx.api.plugins.events.IJadxEvent; +import jadx.api.plugins.events.IJadxEvents; +import jadx.api.plugins.events.JadxEventType; +import jadx.core.plugins.events.JadxEventsImpl; + +/** + * Special events implementation to operate on both: global UI and project events. + * Project events hold listeners only while a project opened and reset them on close. + */ +public class JadxGuiEventsImpl implements IJadxEvents { + + private final IJadxEvents global = new JadxEventsImpl(); + private final IJadxEvents project = new JadxEventsImpl(); + + public IJadxEvents global() { + return global; + } + + @Override + public void send(IJadxEvent event) { + global.send(event); + project.send(event); + } + + @Override + public void addListener(JadxEventType eventType, Consumer listener) { + project.addListener(eventType, listener); + } + + @Override + public void removeListener(JadxEventType eventType, Consumer listener) { + project.removeListener(eventType, listener); + } + + @Override + public void reset() { + project.reset(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java index 2da8a81403c..e29aebdf83f 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java @@ -22,7 +22,6 @@ import kotlin.script.experimental.api.ScriptDiagnostic; import kotlin.script.experimental.api.ScriptDiagnostic.Severity; -import jadx.gui.JadxWrapper; import jadx.gui.logs.LogOptions; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.LineNumbersMode; @@ -137,9 +136,7 @@ private void runScript() { MainWindow mainWindow = tabbedPane.getMainWindow(); mainWindow.getBackgroundExecutor().execute(NLS.str("script.run"), () -> { try { - JadxWrapper wrapper = mainWindow.getWrapper(); - wrapper.resetGuiPluginsContext(); - wrapper.getDecompiler().reloadPasses(); + mainWindow.getWrapper().reloadPasses(); } catch (Exception e) { scriptLog.error("Passes reload failed", e); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java index 96adbc040a7..143058a26b2 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java @@ -15,6 +15,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.function.Consumer; import javax.swing.BorderFactory; import javax.swing.Box; @@ -54,6 +55,7 @@ import jadx.api.args.ResourceNameSource; import jadx.api.args.UseSourceNameAsClassNameAlias; import jadx.api.plugins.events.JadxEvents; +import jadx.api.plugins.events.types.ReloadSettingsWindow; import jadx.api.plugins.gui.ISettingsGroup; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettingsAdapter; @@ -85,6 +87,7 @@ public class JadxSettingsWindow extends JDialog { private final transient String startSettings; private final transient String startSettingsHash; private final transient LangLocale prevLang; + private final transient Consumer reloadListener; private transient boolean needReload = false; private transient SettingsTree tree; @@ -107,8 +110,8 @@ public JadxSettingsWindow(MainWindow mainWindow, JadxSettings settings) { if (!mainWindow.getSettings().loadWindowPos(this)) { setSize(700, 800); } - mainWindow.events().addListener(JadxEvents.RELOAD_SETTINGS_WINDOW, r -> UiUtils.uiRun(this::reloadUI)); - mainWindow.events().addListener(JadxEvents.RELOAD_PROJECT, r -> UiUtils.uiRun(this::reloadUI)); + reloadListener = ev -> UiUtils.uiRun(this::reloadUI); + mainWindow.events().global().addListener(JadxEvents.RELOAD_SETTINGS_WINDOW, reloadListener); } private void reloadUI() { @@ -773,6 +776,7 @@ public MainWindow getMainWindow() { @Override public void dispose() { + mainWindow.events().global().removeListener(JadxEvents.RELOAD_SETTINGS_WINDOW, reloadListener); settings.saveWindowPos(this); super.dispose(); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/BasePluginListNode.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/BasePluginListNode.java index 34eac926860..b49e61897f6 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/BasePluginListNode.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/BasePluginListNode.java @@ -28,6 +28,10 @@ public String getHomepage() { return null; } + public boolean isDisabled() { + return false; + } + public PluginAction getAction() { return PluginAction.NONE; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/InstalledPluginNode.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/InstalledPluginNode.java index bd2ec630eb0..e4d6a2175f4 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/InstalledPluginNode.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/InstalledPluginNode.java @@ -2,21 +2,18 @@ import org.jetbrains.annotations.Nullable; -import jadx.core.plugins.PluginContext; import jadx.plugins.tools.data.JadxPluginMetadata; public class InstalledPluginNode extends BasePluginListNode { - private final PluginContext plugin; private final JadxPluginMetadata metadata; - public InstalledPluginNode(PluginContext plugin, @Nullable JadxPluginMetadata metadata) { - this.plugin = plugin; + public InstalledPluginNode(JadxPluginMetadata metadata) { this.metadata = metadata; } @Override public @Nullable String getTitle() { - return plugin.getPluginInfo().getName(); + return metadata.getName(); } @Override @@ -26,37 +23,36 @@ public boolean hasDetails() { @Override public String getPluginId() { - return plugin.getPluginId(); + return metadata.getPluginId(); } @Override public String getDescription() { - return plugin.getPluginInfo().getDescription(); + return metadata.getDescription(); } @Override public String getHomepage() { - return plugin.getPluginInfo().getHomepage(); + return metadata.getHomepage(); } @Override public PluginAction getAction() { - if (metadata != null) { - return PluginAction.UNINSTALL; - } - return PluginAction.NONE; + return PluginAction.UNINSTALL; } @Override public @Nullable String getVersion() { - if (metadata != null) { - return metadata.getVersion(); - } - return null; + return metadata.getVersion(); + } + + @Override + public boolean isDisabled() { + return metadata.isDisabled(); } @Override public String toString() { - return plugin.getPluginInfo().getName(); + return metadata.getName(); } } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/LoadedPluginNode.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/LoadedPluginNode.java new file mode 100644 index 00000000000..d99716932f7 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/LoadedPluginNode.java @@ -0,0 +1,43 @@ +package jadx.gui.settings.ui.plugins; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.plugins.PluginContext; + +public class LoadedPluginNode extends BasePluginListNode { + private final PluginContext plugin; + + public LoadedPluginNode(PluginContext plugin) { + this.plugin = plugin; + } + + @Override + public @Nullable String getTitle() { + return plugin.getPluginInfo().getName(); + } + + @Override + public boolean hasDetails() { + return true; + } + + @Override + public String getPluginId() { + return plugin.getPluginId(); + } + + @Override + public String getDescription() { + return plugin.getPluginInfo().getDescription(); + } + + @Override + public String getHomepage() { + return plugin.getPluginInfo().getHomepage(); + } + + @Override + public String toString() { + return plugin.getPluginInfo().getName(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginSettings.java index 91b0116ece1..aaa3b49e192 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginSettings.java @@ -53,9 +53,9 @@ public PluginSettings(MainWindow mainWindow, JadxSettings settings) { } public ISettingsGroup build() { - List installedPlugins = new CollectPlugins(mainWindow).build(); - ISettingsGroup pluginsGroup = new PluginSettingsGroup(this, mainWindow, installedPlugins); - for (PluginContext context : installedPlugins) { + List collectedPlugins = new CollectPlugins(mainWindow).build(); + ISettingsGroup pluginsGroup = new PluginSettingsGroup(this, mainWindow, collectedPlugins); + for (PluginContext context : collectedPlugins) { ISettingsGroup pluginGroup = addPluginGroup(context); if (pluginGroup != null) { pluginsGroup.getSubGroups().add(pluginGroup); @@ -98,6 +98,13 @@ public void uninstall(String pluginId) { }); } + public void changeDisableStatus(String pluginId, boolean disabled) { + mainWindow.getBackgroundExecutor().execute( + NLS.str("preferences.plugins.task.status"), + () -> JadxPluginsTools.getInstance().changeDisabledStatus(pluginId, disabled), + s -> requestReload()); + } + void updateAll() { mainWindow.getBackgroundExecutor().execute(NLS.str("preferences.plugins.task.updating"), () -> { List updates = JadxPluginsTools.getInstance().updateAll(); @@ -113,7 +120,7 @@ void updateAll() { private ISettingsGroup addPluginGroup(PluginContext context) { JadxGuiContext guiContext = context.getGuiContext(); if (guiContext instanceof GuiPluginContext) { - GuiPluginContext pluginGuiContext = ((GuiPluginContext) guiContext); + GuiPluginContext pluginGuiContext = (GuiPluginContext) guiContext; ISettingsGroup customSettingsGroup = pluginGuiContext.getCustomSettingsGroup(); if (customSettingsGroup != null) { return customSettingsGroup; diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginSettingsGroup.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginSettingsGroup.java index 8e0aaa68a2d..5aeeb4153f7 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginSettingsGroup.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginSettingsGroup.java @@ -7,9 +7,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -52,15 +51,15 @@ class PluginSettingsGroup implements ISettingsGroup { private final MainWindow mainWindow; private final String title; private final List subGroups = new ArrayList<>(); - private final List installedPlugins; + private final List collectedPlugins; private JPanel detailsPanel; - public PluginSettingsGroup(PluginSettings pluginSettings, MainWindow mainWindow, List installedPlugins) { + public PluginSettingsGroup(PluginSettings pluginSettings, MainWindow mainWindow, List collectedPlugins) { this.pluginsSettings = pluginSettings; this.mainWindow = mainWindow; this.title = NLS.str("preferences.plugins"); - this.installedPlugins = installedPlugins; + this.collectedPlugins = collectedPlugins; } @Override @@ -124,17 +123,21 @@ private JPanel buildMainSettingsPage() { private void applyData(DefaultListModel listModel) { List installed = JadxPluginsTools.getInstance().getInstalled(); - Map installedMap = new HashMap<>(installed.size()); - installed.forEach(p -> installedMap.put(p.getPluginId(), p)); - - List nodes = new ArrayList<>(installed.size() + 3); - for (PluginContext plugin : installedPlugins) { - nodes.add(new InstalledPluginNode(plugin, installedMap.get(plugin.getPluginId()))); + List nodes = new ArrayList<>(installed.size() + collectedPlugins.size()); + Set installedSet = new HashSet<>(installed.size()); + for (JadxPluginMetadata pluginMetadata : installed) { + installedSet.add(pluginMetadata.getPluginId()); + nodes.add(new InstalledPluginNode(pluginMetadata)); + } + for (PluginContext plugin : collectedPlugins) { + if (!installedSet.contains(plugin.getPluginId())) { + nodes.add(new LoadedPluginNode(plugin)); + } } nodes.sort(Comparator.comparing(BasePluginListNode::getTitle)); fillListModel(listModel, nodes, Collections.emptyList()); - loadAvailablePlugins(listModel, nodes, installedPlugins); + loadAvailablePlugins(listModel, nodes, installedSet); } private static void fillListModel(DefaultListModel listModel, @@ -149,17 +152,14 @@ private static void fillListModel(DefaultListModel listModel } private void loadAvailablePlugins(DefaultListModel listModel, - List nodes, List installedPlugins) { + List nodes, Set installedSet) { mainWindow.getBackgroundExecutor().execute( NLS.str("preferences.plugins.task.downloading_list"), () -> { try { JadxPluginsList.getInstance().get(availablePlugins -> { - Set installed = installedPlugins.stream() - .map(PluginContext::getPluginId) - .collect(Collectors.toSet()); List availableNodes = availablePlugins.stream() - .filter(availablePlugin -> !installed.contains(availablePlugin.getPluginId())) + .filter(availablePlugin -> !installedSet.contains(availablePlugin.getPluginId())) .map(AvailablePluginNode::new) .collect(Collectors.toList()); UiUtils.uiRunAndWait(() -> fillListModel(listModel, nodes, availableNodes)); @@ -200,6 +200,17 @@ private void onSelection(BasePluginListNode node) { if (actionBtn != null) { top.add(actionBtn); } + if (node.getAction() == PluginAction.UNINSTALL) { + // TODO: allow disable bundled plugins + boolean disabled = node.isDisabled(); + String statusChangeLabel = disabled + ? NLS.str("preferences.plugins.enable_btn") + : NLS.str("preferences.plugins.disable_btn"); + JButton statusBtn = new JButton(statusChangeLabel); + statusBtn.addActionListener(ev -> pluginsSettings.changeDisableStatus(node.getPluginId(), !disabled)); + top.add(Box.createHorizontalStrut(10)); + top.add(statusBtn); + } JPanel center = new JPanel(); center.setLayout(new BoxLayout(center, BoxLayout.PAGE_AXIS)); @@ -271,14 +282,19 @@ public PluginsListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, - BasePluginListNode value, int index, boolean isSelected, boolean cellHasFocus) { - if (!value.hasDetails()) { - titleLbl.setText(value.getTitle()); + BasePluginListNode plugin, int index, boolean isSelected, boolean cellHasFocus) { + if (!plugin.hasDetails()) { + titleLbl.setText(plugin.getTitle()); return titleLbl; } - nameLbl.setText(value.getTitle()); - nameLbl.setToolTipText(value.getLocationId()); - versionLbl.setText(Utils.getOrElse(value.getVersion(), "")); + nameLbl.setText(plugin.getTitle()); + nameLbl.setToolTipText(plugin.getLocationId()); + versionLbl.setText(Utils.getOrElse(plugin.getVersion(), "")); + + boolean enabled = !plugin.isDisabled(); + nameLbl.setEnabled(enabled); + versionLbl.setEnabled(enabled); + if (isSelected) { panel.setBackground(list.getSelectionBackground()); nameLbl.setBackground(list.getSelectionBackground()); 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 839b8ba112d..0b4f46bc176 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -77,17 +77,15 @@ import ch.qos.logback.classic.Level; import jadx.api.JadxArgs; -import jadx.api.JadxDecompiler; import jadx.api.JavaClass; import jadx.api.JavaNode; import jadx.api.ResourceFile; -import jadx.api.plugins.events.IJadxEvents; import jadx.api.plugins.events.JadxEvents; import jadx.api.plugins.events.types.ReloadProject; +import jadx.api.plugins.events.types.ReloadSettingsWindow; import jadx.api.plugins.utils.CommonFileUtils; import jadx.core.Jadx; import jadx.core.export.TemplateFile; -import jadx.core.plugins.events.JadxEventsImpl; import jadx.core.utils.ListUtils; import jadx.core.utils.StringUtils; import jadx.core.utils.android.AndroidManifestParser; @@ -99,6 +97,7 @@ import jadx.gui.cache.manager.CacheManager; import jadx.gui.device.debugger.BreakpointManager; import jadx.gui.events.services.RenameService; +import jadx.gui.events.types.JadxGuiEventsImpl; import jadx.gui.jobs.BackgroundExecutor; import jadx.gui.jobs.DecompileTask; import jadx.gui.jobs.ExportTask; @@ -195,6 +194,7 @@ public class MainWindow extends JFrame { private final transient CacheObject cacheObject; private final transient CacheManager cacheManager; private final transient BackgroundExecutor backgroundExecutor; + private final transient JadxGuiEventsImpl events = new JadxGuiEventsImpl(); private final TabsController tabsController; private final NavigationController navController; @@ -271,6 +271,7 @@ public MainWindow(JadxSettings settings) { UiUtils.setWindowIcons(this); this.shortcutsController.registerMouseEventListener(this); loadSettings(); + initEvents(); update(); checkForUpdate(); @@ -500,9 +501,10 @@ public void reopen() { synchronized (ReloadProject.EVENT) { saveAll(); closeAll(); - loadFiles(UiUtils.EMPTY_RUNNABLE); - - menuBar.reloadShortcuts(); + loadFiles(() -> { + menuBar.reloadShortcuts(); + events().send(ReloadSettingsWindow.INSTANCE); + }); } } @@ -525,6 +527,7 @@ private void openProject(Path path, Runnable onFinish) { private void loadFiles(Runnable onFinish) { if (project.getFilePaths().isEmpty()) { tabsController.selectTab(new StartPageNode()); + onFinish.run(); return; } AtomicReference wrapperException = new AtomicReference<>(); @@ -605,7 +608,6 @@ private void onOpen() { initTree(); updateLiveReload(project.isEnableLiveReload()); BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent()); - initEvents(); List openTabs = project.getOpenTabs(this); backgroundExecutor.execute(NLS.str("progress.load"), @@ -619,13 +621,12 @@ private void onOpen() { } public void passesReloaded() { - initEvents(); // TODO: events reset on reload passes on script run tabbedPane.reloadInactiveTabs(); reloadTree(); } private void initEvents() { - events().addListener(JadxEvents.RELOAD_PROJECT, ev -> UiUtils.uiRun(this::reopen)); + events().global().addListener(JadxEvents.RELOAD_PROJECT, ev -> UiUtils.uiRun(this::reopen)); RenameService.init(this); } @@ -1753,14 +1754,7 @@ public CacheManager getCacheManager() { return cacheManager; } - /** - * Events instance if decompiler not yet available - */ - private final IJadxEvents fallbackEvents = new JadxEventsImpl(); - - public IJadxEvents events() { - return wrapper.getCurrentDecompiler() - .map(JadxDecompiler::events) - .orElse(fallbackEvents); + public JadxGuiEventsImpl events() { + return events; } } diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 2b62404d155..6c4d838286a 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -259,6 +259,8 @@ preferences.tab_dnd_appearance=Dragging tab appearance #preferences.plugins.install=Install plugin #preferences.plugins.install_btn=Install #preferences.plugins.uninstall_btn=Uninstall +#preferences.plugins.disable_btn=Disable +#preferences.plugins.enable_btn=Enable #preferences.plugins.location_id_label=Location id: #preferences.plugins.plugin_jar=Select Plugin jar #preferences.plugins.plugin_jar_label=or @@ -268,6 +270,7 @@ preferences.tab_dnd_appearance=Dragging tab appearance #preferences.plugins.task.uninstalling=Uninstalling plugin #preferences.plugins.task.updating=Updating plugins #preferences.plugins.task.downloading_list=Downloading plugins list +#preferences.plugins.task.status=Changing plugin status #preferences.cache=Cache #preferences.cache.location=Cache location 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 a2fb1cbb193..29879d195f9 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -259,6 +259,8 @@ preferences.tab_dnd_appearance=Dragging tab appearance preferences.plugins.install=Install plugin preferences.plugins.install_btn=Install preferences.plugins.uninstall_btn=Uninstall +preferences.plugins.disable_btn=Disable +preferences.plugins.enable_btn=Enable preferences.plugins.location_id_label=Location id: preferences.plugins.plugin_jar=Select Plugin jar preferences.plugins.plugin_jar_label=or @@ -268,6 +270,7 @@ preferences.plugins.task.installing=Installing plugin preferences.plugins.task.uninstalling=Uninstalling plugin preferences.plugins.task.updating=Updating plugins preferences.plugins.task.downloading_list=Downloading plugins list +preferences.plugins.task.status=Changing plugin status preferences.cache=Cache preferences.cache.location=Cache location diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index e0457f2241d..e3d76efcad6 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -259,6 +259,8 @@ preferences.rename_use_source_name_as_class_name_alias=Usar el nombre del source #preferences.plugins.install=Install plugin #preferences.plugins.install_btn=Install #preferences.plugins.uninstall_btn=Uninstall +#preferences.plugins.disable_btn=Disable +#preferences.plugins.enable_btn=Enable #preferences.plugins.location_id_label=Location id: #preferences.plugins.plugin_jar=Select Plugin jar #preferences.plugins.plugin_jar_label=or @@ -268,6 +270,7 @@ preferences.rename_use_source_name_as_class_name_alias=Usar el nombre del source #preferences.plugins.task.uninstalling=Uninstalling plugin #preferences.plugins.task.updating=Updating plugins #preferences.plugins.task.downloading_list=Downloading plugins list +#preferences.plugins.task.status=Changing plugin status #preferences.cache=Cache #preferences.cache.location=Cache location diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties index 8184d575d1c..7127221e208 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -259,6 +259,8 @@ preferences.tab_dnd_appearance=Dragging tab appearance preferences.plugins.install=Instal plugin preferences.plugins.install_btn=Instal preferences.plugins.uninstall_btn=Uninstal +#preferences.plugins.disable_btn=Disable +#preferences.plugins.enable_btn=Enable preferences.plugins.location_id_label=Lokasi id: preferences.plugins.plugin_jar=Pilih berkas jar Plugin preferences.plugins.plugin_jar_label=atau @@ -268,6 +270,7 @@ preferences.plugins.task.installing=Menginstal plugin preferences.plugins.task.uninstalling=Menguninstal plugin preferences.plugins.task.updating=Mengperbarui plugin preferences.plugins.task.downloading_list=Mengunduh daftar plugin +#preferences.plugins.task.status=Changing plugin status preferences.cache=Cache preferences.cache.location=Lokasi cache diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 64c9ab1135a..9367bcf0f15 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -259,6 +259,8 @@ preferences.tab_dnd_appearance=Dragging tab appearance #preferences.plugins.install=Install plugin #preferences.plugins.install_btn=Install #preferences.plugins.uninstall_btn=Uninstall +#preferences.plugins.disable_btn=Disable +#preferences.plugins.enable_btn=Enable #preferences.plugins.location_id_label=Location id: #preferences.plugins.plugin_jar=Select Plugin jar #preferences.plugins.plugin_jar_label=or @@ -268,6 +270,7 @@ preferences.tab_dnd_appearance=Dragging tab appearance #preferences.plugins.task.uninstalling=Uninstalling plugin #preferences.plugins.task.updating=Updating plugins #preferences.plugins.task.downloading_list=Downloading plugins list +#preferences.plugins.task.status=Changing plugin status #preferences.cache=Cache #preferences.cache.location=Cache location diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index ae14aaeaac0..d74a87aa080 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -259,6 +259,8 @@ preferences.tab_dnd_appearance=Dragging tab appearance #preferences.plugins.install=Install plugin #preferences.plugins.install_btn=Install #preferences.plugins.uninstall_btn=Uninstall +#preferences.plugins.disable_btn=Disable +#preferences.plugins.enable_btn=Enable #preferences.plugins.location_id_label=Location id: #preferences.plugins.plugin_jar=Select Plugin jar #preferences.plugins.plugin_jar_label=or @@ -268,6 +270,7 @@ preferences.tab_dnd_appearance=Dragging tab appearance #preferences.plugins.task.uninstalling=Uninstalling plugin #preferences.plugins.task.updating=Updating plugins #preferences.plugins.task.downloading_list=Downloading plugins list +#preferences.plugins.task.status=Changing plugin status #preferences.cache=Cache #preferences.cache.location=Cache location diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties index 6c6a9e6403d..c75507d16d3 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -259,6 +259,8 @@ preferences.tab_dnd_appearance=Dragging tab appearance preferences.plugins.install=Установить плагин preferences.plugins.install_btn=Установить preferences.plugins.uninstall_btn=Удалить +#preferences.plugins.disable_btn=Disable +#preferences.plugins.enable_btn=Enable preferences.plugins.location_id_label=ID источика: preferences.plugins.plugin_jar=Выберите jar-файл плагина preferences.plugins.plugin_jar_label=или @@ -268,6 +270,7 @@ preferences.plugins.task.installing=Установка плагина preferences.plugins.task.uninstalling=Удаление плагина preferences.plugins.task.updating=Обновление плагинов preferences.plugins.task.downloading_list=Загрузка списка плагинов +#preferences.plugins.task.status=Changing plugin status preferences.cache=Кэш preferences.cache.location=Директория кэша diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 72a35e075b2..2dbfc25fb9d 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -259,6 +259,8 @@ preferences.tab_dnd_appearance=拖拽选项卡外观 preferences.plugins.install=安装插件 preferences.plugins.install_btn=安装 preferences.plugins.uninstall_btn=卸载 +#preferences.plugins.disable_btn=Disable +#preferences.plugins.enable_btn=Enable preferences.plugins.location_id_label=位置ID: preferences.plugins.plugin_jar=选择插件 jar preferences.plugins.plugin_jar_label=或 @@ -268,6 +270,7 @@ preferences.plugins.task.installing=安装插件中 preferences.plugins.task.uninstalling=卸载插件中 preferences.plugins.task.updating=更新插件中 preferences.plugins.task.downloading_list=正在下载插件列表 +#preferences.plugins.task.status=Changing plugin status preferences.cache=缓存 preferences.cache.location=缓存位置 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index 261ab855bc2..961acfef6a2 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -259,6 +259,8 @@ preferences.tab_dnd_appearance=Dragging tab appearance preferences.plugins.install=安裝外掛程式 preferences.plugins.install_btn=安裝 preferences.plugins.uninstall_btn=解除安裝 +#preferences.plugins.disable_btn=Disable +#preferences.plugins.enable_btn=Enable preferences.plugins.location_id_label=位置 id: preferences.plugins.plugin_jar=選擇外掛程式 jar preferences.plugins.plugin_jar_label=或 @@ -268,6 +270,7 @@ preferences.plugins.task.installing=正在安裝外掛程式 preferences.plugins.task.uninstalling=正在解除安裝外掛程式 preferences.plugins.task.updating=正在更新外掛程式 preferences.plugins.task.downloading_list=正在下載外掛程式列表 +#preferences.plugins.task.status=Changing plugin status preferences.cache=快取 preferences.cache.location=快取位置 diff --git a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxExternalPluginsLoader.java b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxExternalPluginsLoader.java index 9581df99c82..5338084db43 100644 --- a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxExternalPluginsLoader.java +++ b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxExternalPluginsLoader.java @@ -72,7 +72,7 @@ private void loadFromClsLoader(Map, JadxPlugin> map, } private void loadInstalledPlugins(Map, JadxPlugin> map, ClassLoader classLoader) { - List jars = JadxPluginsTools.getInstance().getAllPluginJars(); + List jars = JadxPluginsTools.getInstance().getEnabledPluginJars(); for (Path jar : jars) { classLoaders.add(loadFromJar(map, classLoader, jar)); } diff --git a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsList.java b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsList.java index b5743b3b350..51399f5c5e5 100644 --- a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsList.java +++ b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsList.java @@ -28,9 +28,6 @@ import static jadx.plugins.tools.utils.PluginFiles.PLUGINS_LIST_CACHE; -/** - * TODO: implement list caching (on disk) with check for new release - */ public class JadxPluginsList { private static final JadxPluginsList INSTANCE = new JadxPluginsList(); diff --git a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsTools.java b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsTools.java index 0766b80f49f..c48bb426fb1 100644 --- a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsTools.java +++ b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsTools.java @@ -124,6 +124,38 @@ public List getAllPluginJars() { return list; } + public List getEnabledPluginJars() { + List list = new ArrayList<>(); + for (JadxPluginMetadata pluginMetadata : loadPluginsJson().getInstalled()) { + if (pluginMetadata.isDisabled()) { + continue; + } + list.add(INSTALLED_DIR.resolve(pluginMetadata.getJar())); + } + collectFromDir(list, DROPINS_DIR); + return list; + } + + /** + * Disable or enable plugin + * + * @return true if disabled status was changed + */ + public boolean changeDisabledStatus(String pluginId, boolean disabled) { + JadxInstalledPlugins data = loadPluginsJson(); + JadxPluginMetadata plugin = data.getInstalled().stream() + .filter(p -> p.getPluginId().equals(pluginId)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Plugin not found: " + pluginId)); + if (plugin.isDisabled() == disabled) { + return false; + } + plugin.setDisabled(disabled); + data.setUpdated(System.currentTimeMillis()); + savePluginsJson(data); + return true; + } + private @Nullable JadxPluginMetadata update(JadxPluginMetadata plugin) { IJadxPluginResolver resolver = ResolversRegistry.getById(plugin.getResolverId()); if (!resolver.isUpdateSupported()) { diff --git a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/data/JadxPluginMetadata.java b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/data/JadxPluginMetadata.java index 955cd3ae471..5e122b8977a 100644 --- a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/data/JadxPluginMetadata.java +++ b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/data/JadxPluginMetadata.java @@ -12,6 +12,7 @@ public class JadxPluginMetadata implements Comparable { private String locationId; private String resolverId; private String jar; + private boolean disabled; public String getPluginId() { return pluginId; @@ -77,6 +78,14 @@ public void setJar(String jar) { this.jar = jar; } + public boolean isDisabled() { + return disabled; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + @Override public boolean equals(Object other) { if (this == other) {