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 53196501d..fde1ef54b 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 @@ -1,6 +1,9 @@ package jadx.api.plugins.events; import jadx.api.plugins.events.types.NodeRenamedByUser; +import jadx.api.plugins.events.types.ReloadSettingsWindow; +import jadx.api.plugins.gui.ISettingsGroup; +import jadx.api.plugins.gui.JadxGuiSettings; import static jadx.api.plugins.events.JadxEventType.create; @@ -13,4 +16,11 @@ public class JadxEvents { * Notify about renames done by user (GUI only). */ public static final JadxEventType NODE_RENAMED_BY_USER = create(); + + /** + * Request reload of settings window (GUI only). + * Useful for reload custom settings group set with + * {@link JadxGuiSettings#setCustomSettingsGroup(ISettingsGroup)}. + */ + public static final JadxEventType RELOAD_SETTINGS_WINDOW = create(); } diff --git a/jadx-core/src/main/java/jadx/api/plugins/events/types/ReloadSettingsWindow.java b/jadx-core/src/main/java/jadx/api/plugins/events/types/ReloadSettingsWindow.java new file mode 100644 index 000000000..0f03f60a8 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/plugins/events/types/ReloadSettingsWindow.java @@ -0,0 +1,24 @@ +package jadx.api.plugins.events.types; + +import jadx.api.plugins.events.IJadxEvent; +import jadx.api.plugins.events.JadxEventType; +import jadx.api.plugins.events.JadxEvents; + +public class ReloadSettingsWindow implements IJadxEvent { + + public static final ReloadSettingsWindow INSTANCE = new ReloadSettingsWindow(); + + private ReloadSettingsWindow() { + // singleton + } + + @Override + public JadxEventType getType() { + return JadxEvents.RELOAD_SETTINGS_WINDOW; + } + + @Override + public String toString() { + return "RELOAD_SETTINGS_WINDOW"; + } +} diff --git a/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiSettings.java b/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiSettings.java index 5454c3132..72ceef618 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiSettings.java +++ b/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiSettings.java @@ -9,7 +9,7 @@ public interface JadxGuiSettings { /** * Set plugin custom settings page */ - void setCustomSettings(ISettingsGroup group); + void setCustomSettingsGroup(ISettingsGroup group); /** * Helper method to build options group only for provided option list diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/TaskStatus.java b/jadx-gui/src/main/java/jadx/gui/jobs/TaskStatus.java index 406033434..defcd7196 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/TaskStatus.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/TaskStatus.java @@ -7,5 +7,5 @@ public enum TaskStatus { CANCEL_BY_USER, CANCEL_BY_TIMEOUT, CANCEL_BY_MEMORY, - ERROR; + ERROR } diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java index 2f65d0e00..33bc8fd01 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java @@ -5,8 +5,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.swing.JMenu; - import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,9 +39,7 @@ public class CommonGuiPluginsContext { public void reset() { codePopupActionList.clear(); - JMenu pluginsMenu = mainWindow.getPluginsMenu(); - pluginsMenu.removeAll(); - pluginsMenu.setVisible(false); + mainWindow.resetPluginsMenu(); } public MainWindow getMainWindow() { @@ -63,9 +59,7 @@ public class CommonGuiPluginsContext { } }); item.setNameAndDesc(name); - JMenu pluginsMenu = mainWindow.getPluginsMenu(); - pluginsMenu.add(item); - pluginsMenu.setVisible(true); + mainWindow.addToPluginsMenu(item); } public void appendPopupMenus(CodeArea codeArea, JNodePopupBuilder popup) { diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiSettingsContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiSettingsContext.java index 9aa7837f8..153676b9c 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiSettingsContext.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiSettingsContext.java @@ -5,8 +5,8 @@ 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.settings.ui.plugins.PluginsSettings; import jadx.gui.ui.MainWindow; public class GuiSettingsContext implements JadxGuiSettings { @@ -17,7 +17,7 @@ public class GuiSettingsContext implements JadxGuiSettings { } @Override - public void setCustomSettings(ISettingsGroup group) { + public void setCustomSettingsGroup(ISettingsGroup group) { guiPluginContext.setCustomSettings(group); } 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 992c73b13..d11f29170 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 @@ -37,6 +37,7 @@ import javax.swing.ScrollPaneConstants; import javax.swing.SpinnerNumberModel; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; +import javax.swing.tree.TreePath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,12 +55,14 @@ import jadx.api.JadxDecompiler; import jadx.api.args.GeneratedRenamesMappingFileMode; import jadx.api.args.IntegerFormat; import jadx.api.args.ResourceNameSource; +import jadx.api.plugins.events.JadxEvents; import jadx.api.plugins.gui.ISettingsGroup; import jadx.gui.cache.code.CodeCacheMode; import jadx.gui.cache.usage.UsageCacheMode; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettingsAdapter; import jadx.gui.settings.LineNumbersMode; +import jadx.gui.settings.ui.plugins.PluginsSettings; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.FontUtils; @@ -82,6 +85,7 @@ public class JadxSettingsWindow extends JDialog { private final transient LangLocale prevLang; private transient boolean needReload = false; + private SettingsTree tree; public JadxSettingsWindow(MainWindow mainWindow, JadxSettings settings) { this.mainWindow = mainWindow; @@ -91,6 +95,7 @@ public class JadxSettingsWindow extends JDialog { this.prevLang = settings.getLangLocale(); initUI(); + mainWindow.events().addListener(JadxEvents.RELOAD_SETTINGS_WINDOW, r -> UiUtils.uiRun(this::reloadUI)); setTitle(NLS.str("preferences.title")); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); @@ -103,10 +108,18 @@ public class JadxSettingsWindow extends JDialog { } } + private void reloadUI() { + TreePath selectionPath = tree.getSelectionPath(); + mainWindow.getSettings().saveWindowPos(this); + getContentPane().removeAll(); + initUI(); + pack(); + mainWindow.getSettings().loadWindowPos(this); + tree.setSelectionPath(selectionPath); + } + private void initUI() { - JPanel groupPanel = new JPanel(); - groupPanel.setLayout(new BoxLayout(groupPanel, BoxLayout.LINE_AXIS)); - groupPanel.setBorder(BorderFactory.createEmptyBorder(10, 3, 3, 10)); + JPanel wrapGroupPanel = new JPanel(new BorderLayout(10, 10)); List groups = new ArrayList<>(); groups.add(makeDecompilationGroup()); @@ -118,18 +131,15 @@ public class JadxSettingsWindow extends JDialog { groups.add(new PluginsSettings(mainWindow, settings).build()); groups.add(makeOtherGroup()); - SettingsTree tree = new SettingsTree(); - tree.init(groupPanel, groups); + tree = new SettingsTree(); + tree.init(wrapGroupPanel, groups); JScrollPane leftPane = new JScrollPane(tree); leftPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 3, 3)); - JPanel wrapGroupPanel = new JPanel(new BorderLayout()); - wrapGroupPanel.add(groupPanel, BorderLayout.PAGE_START); - JScrollPane rightPane = new JScrollPane(wrapGroupPanel); rightPane.getVerticalScrollBar().setUnitIncrement(16); rightPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - rightPane.setBorder(BorderFactory.createEmptyBorder()); + rightPane.setBorder(BorderFactory.createEmptyBorder(10, 3, 3, 10)); JSplitPane splitPane = new JSplitPane(); splitPane.setResizeWeight(0.2); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/SettingsGroup.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/SettingsGroup.java index 53d4ba97c..6946f1bef 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/SettingsGroup.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/SettingsGroup.java @@ -1,5 +1,6 @@ package jadx.gui.settings.ui; +import java.awt.BorderLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -18,16 +19,21 @@ public class SettingsGroup implements ISettingsGroup { private final String title; private final JPanel panel; + private final JPanel gridPanel; private final GridBagConstraints c; private int row; public SettingsGroup(String title) { this.title = title; - panel = new JPanel(new GridBagLayout()); - panel.setBorder(BorderFactory.createTitledBorder(title)); + gridPanel = new JPanel(new GridBagLayout()); c = new GridBagConstraints(); c.insets = new Insets(5, 5, 5, 5); c.weighty = 1.0; + + panel = new JPanel(); + panel.setLayout(new BorderLayout(5, 5)); + panel.setBorder(BorderFactory.createTitledBorder(title)); + panel.add(gridPanel, BorderLayout.PAGE_START); } public JLabel addRow(String label, JComponent comp) { @@ -36,15 +42,15 @@ public class SettingsGroup implements ISettingsGroup { public JLabel addRow(String label, String tooltip, JComponent comp) { c.gridy = row++; - JLabel jLabel = new JLabel(label); - jLabel.setLabelFor(comp); - jLabel.setHorizontalAlignment(SwingConstants.LEFT); + JLabel rowLbl = new JLabel(label); + rowLbl.setLabelFor(comp); + rowLbl.setHorizontalAlignment(SwingConstants.LEFT); c.gridx = 0; c.gridwidth = 1; c.anchor = GridBagConstraints.LINE_START; c.weightx = 0.8; c.fill = GridBagConstraints.NONE; - panel.add(jLabel, c); + gridPanel.add(rowLbl, c); c.gridx = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.anchor = GridBagConstraints.CENTER; @@ -52,18 +58,16 @@ public class SettingsGroup implements ISettingsGroup { c.fill = GridBagConstraints.HORIZONTAL; if (tooltip != null) { - jLabel.setToolTipText(tooltip); + rowLbl.setToolTipText(tooltip); comp.setToolTipText(tooltip); } - - panel.add(comp, c); - - comp.addPropertyChangeListener("enabled", evt -> jLabel.setEnabled((boolean) evt.getNewValue())); - return jLabel; + gridPanel.add(comp, c); + comp.addPropertyChangeListener("enabled", evt -> rowLbl.setEnabled((boolean) evt.getNewValue())); + return rowLbl; } public void end() { - panel.add(Box.createVerticalGlue()); + gridPanel.add(Box.createVerticalGlue()); } @Override 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 new file mode 100644 index 000000000..2aa3b61a2 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/BasePluginListNode.java @@ -0,0 +1,20 @@ +package jadx.gui.settings.ui.plugins; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.JadxPluginInfo; + +abstract class BasePluginListNode { + + public @Nullable String getTitle() { + return null; + } + + public JadxPluginInfo getPluginInfo() { + return null; + } + + public @Nullable String getVersion() { + return null; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/InstallPluginDialog.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/InstallPluginDialog.java new file mode 100644 index 000000000..9107de12f --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/InstallPluginDialog.java @@ -0,0 +1,140 @@ +package jadx.gui.settings.ui.plugins; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.WindowConstants; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.formdev.flatlaf.FlatClientProperties; + +import ch.qos.logback.classic.Level; + +import jadx.api.plugins.events.types.ReloadSettingsWindow; +import jadx.gui.logs.LogOptions; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.filedialog.FileDialogWrapper; +import jadx.gui.ui.filedialog.FileOpenMode; +import jadx.gui.utils.NLS; +import jadx.gui.utils.TextStandardActions; +import jadx.gui.utils.UiUtils; +import jadx.plugins.tools.JadxPluginsTools; +import jadx.plugins.tools.data.JadxPluginMetadata; + +public class InstallPluginDialog extends JDialog { + private static final Logger LOG = LoggerFactory.getLogger(InstallPluginDialog.class); + private static final long serialVersionUID = 5304314264730563853L; + + private final MainWindow mainWindow; + private JTextField locationFld; + + public InstallPluginDialog(MainWindow mainWindow) { + super(mainWindow, NLS.str("preferences.plugins.install")); + this.mainWindow = mainWindow; + init(); + } + + private void init() { + locationFld = new JTextField(); + locationFld.setAlignmentX(LEFT_ALIGNMENT); + locationFld.setColumns(50); + TextStandardActions.attach(locationFld); + locationFld.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true); + + JLabel locationLbl = new JLabel(NLS.str("preferences.plugins.location_id_label")); + locationLbl.setLabelFor(locationFld); + + JPanel locationPanel = new JPanel(); + locationPanel.setLayout(new BoxLayout(locationPanel, BoxLayout.LINE_AXIS)); + locationPanel.add(locationLbl); + locationPanel.add(Box.createRigidArea(new Dimension(5, 0))); + locationPanel.add(locationFld); + + JButton fileBtn = new JButton(NLS.str("preferences.plugins.plugin_jar")); + fileBtn.addActionListener(ev -> openPluginJar()); + JLabel fileLbl = new JLabel(NLS.str("preferences.plugins.plugin_jar_label")); + fileLbl.setLabelFor(fileBtn); + + JPanel filePanel = new JPanel(); + filePanel.setLayout(new BoxLayout(filePanel, BoxLayout.LINE_AXIS)); + filePanel.add(fileLbl); + filePanel.add(Box.createRigidArea(new Dimension(5, 0))); + filePanel.add(fileBtn); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.add(locationPanel); + mainPanel.add(Box.createRigidArea(new Dimension(0, 5))); + mainPanel.add(filePanel); + + JButton installBtn = new JButton(NLS.str("preferences.plugins.install_btn")); + installBtn.addActionListener(ev -> install()); + JButton cancelBtn = new JButton(NLS.str("preferences.cancel")); + cancelBtn.addActionListener(ev -> dispose()); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); + // TODO: add operation progress + buttonPane.add(Box.createHorizontalGlue()); + buttonPane.add(installBtn); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(cancelBtn); + getRootPane().setDefaultButton(installBtn); + + JPanel contentPanel = new JPanel(); + contentPanel.setLayout(new BorderLayout(5, 5)); + contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + contentPanel.add(mainPanel, BorderLayout.PAGE_START); + contentPanel.add(buttonPane, BorderLayout.PAGE_END); + getContentPane().add(contentPanel); + + pack(); + setLocationRelativeTo(null); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setModalityType(ModalityType.APPLICATION_MODAL); + UiUtils.addEscapeShortCutToDispose(this); + } + + private void openPluginJar() { + FileDialogWrapper fd = new FileDialogWrapper(mainWindow, FileOpenMode.CUSTOM_OPEN); + fd.setTitle(NLS.str("preferences.plugins.plugin_jar")); + fd.setFileExtList(Collections.singletonList("jar")); + fd.setSelectionMode(JFileChooser.FILES_ONLY); + List files = fd.show(); + if (files.size() == 1) { + locationFld.setText("file:" + files.get(0).toAbsolutePath()); + } + } + + private void install() { + mainWindow.getBackgroundExecutor().execute(NLS.str("preferences.plugins.task.installing"), + () -> { + try { + JadxPluginMetadata metadata = JadxPluginsTools.getInstance().install(locationFld.getText()); + LOG.info("Plugin installed: {}", metadata); + } catch (Exception e) { + LOG.error("Install failed", e); + mainWindow.showLogViewer(LogOptions.forLevel(Level.ERROR)); + } + }, + status -> { + mainWindow.events().send(ReloadSettingsWindow.INSTANCE); + UiUtils.uiRun(mainWindow::reopen); + }); + dispose(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginListNode.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginListNode.java new file mode 100644 index 000000000..bd5a37b8f --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginListNode.java @@ -0,0 +1,39 @@ +package jadx.gui.settings.ui.plugins; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.JadxPluginInfo; +import jadx.core.plugins.PluginContext; +import jadx.plugins.tools.data.JadxPluginMetadata; + +public class PluginListNode extends BasePluginListNode { + private final PluginContext plugin; + private final JadxPluginMetadata metadata; + + public PluginListNode(PluginContext plugin, @Nullable JadxPluginMetadata metadata) { + this.plugin = plugin; + this.metadata = metadata; + } + + @Override + public JadxPluginInfo getPluginInfo() { + return plugin.getPluginInfo(); + } + + public @Nullable JadxPluginMetadata getMetadata() { + return metadata; + } + + @Override + public @Nullable String getVersion() { + if (metadata != null) { + return metadata.getVersion(); + } + return null; + } + + @Override + public String toString() { + return plugin.getPluginInfo().getName(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/PluginsSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettings.java similarity index 76% rename from jadx-gui/src/main/java/jadx/gui/settings/ui/PluginsSettings.java rename to jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettings.java index feaa3992b..d5f8c3636 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/PluginsSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettings.java @@ -1,4 +1,4 @@ -package jadx.gui.settings.ui; +package jadx.gui.settings.ui.plugins; import java.awt.event.ItemEvent; import java.util.List; @@ -11,13 +11,13 @@ 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.events.types.ReloadSettingsWindow; import jadx.api.plugins.gui.ISettingsGroup; import jadx.api.plugins.gui.JadxGuiContext; import jadx.api.plugins.options.JadxPluginOptions; @@ -25,13 +25,18 @@ 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.core.utils.Utils; import jadx.gui.plugins.context.GuiPluginContext; import jadx.gui.settings.JadxProject; import jadx.gui.settings.JadxSettings; +import jadx.gui.settings.ui.SettingsGroup; import jadx.gui.ui.MainWindow; import jadx.gui.utils.NLS; -import jadx.gui.utils.plugins.CollectPluginOptions; +import jadx.gui.utils.UiUtils; +import jadx.gui.utils.plugins.CollectPlugins; import jadx.gui.utils.ui.DocumentUpdateListener; +import jadx.plugins.tools.JadxPluginsTools; +import jadx.plugins.tools.data.JadxPluginUpdate; public class PluginsSettings { private static final Logger LOG = LoggerFactory.getLogger(PluginsSettings.class); @@ -44,12 +49,11 @@ public class PluginsSettings { this.settings = settings; } - public SettingsGroup build() { - SettingsGroup pluginsGroup = new SubSettingsGroup(NLS.str("preferences.plugins")); - fillMainSettings(pluginsGroup); - List list = new CollectPluginOptions(mainWindow).build(); + public ISettingsGroup build() { + List list = new CollectPlugins(mainWindow).build(); + ISettingsGroup pluginsGroup = new PluginsSettingsGroup(this, list); for (PluginContext context : list) { - ISettingsGroup pluginGroup = buildPluginGroup(context); + ISettingsGroup pluginGroup = addPluginGroup(context); if (pluginGroup != null) { pluginsGroup.getSubGroups().add(pluginGroup); } @@ -57,12 +61,37 @@ public class PluginsSettings { return pluginsGroup; } - private void fillMainSettings(SettingsGroup settingsGroup) { - JPanel panel = settingsGroup.getPanel(); - panel.add(new JPanel()); + public void addPlugin() { + new InstallPluginDialog(mainWindow).setVisible(true); } - private ISettingsGroup buildPluginGroup(PluginContext context) { + public void uninstall(String pluginId) { + mainWindow.getBackgroundExecutor().execute(NLS.str("preferences.plugins.task.uninstalling"), () -> { + boolean success = JadxPluginsTools.getInstance().uninstall(pluginId); + if (success) { + LOG.debug("Uninstall complete"); + mainWindow.events().send(ReloadSettingsWindow.INSTANCE); + UiUtils.uiRun(mainWindow::reopen); + } else { + LOG.debug("Uninstall failed"); + } + }); + } + + void updateAll() { + mainWindow.getBackgroundExecutor().execute(NLS.str("preferences.plugins.task.updating"), () -> { + List updates = JadxPluginsTools.getInstance().updateAll(); + if (!updates.isEmpty()) { + LOG.debug("Updates: {}\n ", Utils.listToString(updates, "\n ")); + mainWindow.events().send(ReloadSettingsWindow.INSTANCE); + UiUtils.uiRun(mainWindow::reopen); + } else { + LOG.debug("No updates found"); + } + }); + } + + private ISettingsGroup addPluginGroup(PluginContext context) { JadxGuiContext guiContext = context.getGuiContext(); if (guiContext instanceof GuiPluginContext) { GuiPluginContext pluginGuiContext = ((GuiPluginContext) guiContext); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettingsGroup.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettingsGroup.java new file mode 100644 index 000000000..d9b37cb84 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettingsGroup.java @@ -0,0 +1,211 @@ +package jadx.gui.settings.ui.plugins; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextPane; +import javax.swing.ListCellRenderer; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; + +import jadx.api.plugins.JadxPluginInfo; +import jadx.api.plugins.gui.ISettingsGroup; +import jadx.core.plugins.PluginContext; +import jadx.core.utils.Utils; +import jadx.gui.utils.NLS; +import jadx.plugins.tools.JadxPluginsTools; +import jadx.plugins.tools.data.JadxPluginMetadata; + +class PluginsSettingsGroup implements ISettingsGroup { + private final PluginsSettings pluginsSettings; + private final String title; + private final List subGroups = new ArrayList<>(); + private final List pluginsList; + + private PluginListNode selectedPlugin; + private JPanel detailsPanel; + + public PluginsSettingsGroup(PluginsSettings pluginsSettings, List pluginsList) { + this.pluginsSettings = pluginsSettings; + this.title = NLS.str("preferences.plugins"); + this.pluginsList = pluginsList; + } + + @Override + public String getTitle() { + return title; + } + + @Override + public List getSubGroups() { + return subGroups; + } + + @Override + public JComponent buildComponent() { + // lazy load main page + return buildMainSettingsPage(); + } + + private JPanel buildMainSettingsPage() { + JButton installPluginBtn = new JButton(NLS.str("preferences.plugins.install")); + installPluginBtn.addActionListener(ev -> pluginsSettings.addPlugin()); + + JButton updateAllBtn = new JButton(NLS.str("preferences.plugins.update_all")); + updateAllBtn.addActionListener(ev -> pluginsSettings.updateAll()); + + JPanel actionsPanel = new JPanel(); + actionsPanel.setLayout(new BoxLayout(actionsPanel, BoxLayout.LINE_AXIS)); + actionsPanel.add(installPluginBtn); + actionsPanel.add(Box.createRigidArea(new Dimension(5, 0))); + actionsPanel.add(updateAllBtn); + + 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 : pluginsList) { + nodes.add(new PluginListNode(plugin, installedMap.get(plugin.getPluginId()))); + } + nodes.sort(Comparator.comparing(n -> n.getPluginInfo().getName())); + + DefaultListModel listModel = new DefaultListModel<>(); + listModel.addElement(new TitleNode("Installed")); + nodes.stream().filter(n -> n.getVersion() != null).forEach(listModel::addElement); + listModel.addElement(new TitleNode("Bundled")); + nodes.stream().filter(n -> n.getVersion() == null).forEach(listModel::addElement); + // TODO: load external plugins list + // listModel.addElement(new TitleNode("Available")); + + JList pluginsList = new JList<>(listModel); + pluginsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + pluginsList.setCellRenderer(new PluginsListCellRenderer(pluginsList)); + pluginsList.addListSelectionListener(ev -> onSelection(pluginsList.getSelectedValue())); + + JScrollPane scrollPane = new JScrollPane(pluginsList); + + detailsPanel = new JPanel(new BorderLayout(5, 5)); + detailsPanel.setBorder(BorderFactory.createTitledBorder("Plugin details")); + detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.PAGE_AXIS)); + detailsPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + JSplitPane splitPanel = new JSplitPane(); + splitPanel.setBorder(BorderFactory.createEmptyBorder(10, 2, 2, 2)); + splitPanel.setLeftComponent(scrollPane); + splitPanel.setRightComponent(detailsPanel); + splitPanel.setDividerLocation(0.4); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout(5, 5)); + mainPanel.setBorder(BorderFactory.createTitledBorder(title)); + mainPanel.add(actionsPanel, BorderLayout.PAGE_START); + mainPanel.add(splitPanel, BorderLayout.CENTER); + return mainPanel; + } + + private void onSelection(BasePluginListNode node) { + detailsPanel.removeAll(); + JadxPluginInfo pluginInfo = node.getPluginInfo(); + if (pluginInfo != null) { + JButton uninstallBtn = new JButton("Uninstall"); + if (node.getVersion() != null) { + uninstallBtn.addActionListener(ev -> pluginsSettings.uninstall(pluginInfo.getPluginId())); + } else { + uninstallBtn.setEnabled(false); + } + JLabel nameLbl = new JLabel(pluginInfo.getName()); + Font baseFont = nameLbl.getFont(); + nameLbl.setFont(baseFont.deriveFont(Font.BOLD, baseFont.getSize2D() + 2)); + + JTextPane descArea = new JTextPane(); + descArea.setText(pluginInfo.getDescription()); + descArea.setFont(baseFont.deriveFont(baseFont.getSize2D() + 1)); + descArea.setEditable(false); + descArea.setBorder(BorderFactory.createEmptyBorder()); + descArea.setOpaque(true); + + JPanel top = new JPanel(); + top.setLayout(new BoxLayout(top, BoxLayout.LINE_AXIS)); + top.setBorder(BorderFactory.createEmptyBorder(10, 2, 10, 2)); + top.add(nameLbl); + top.add(Box.createHorizontalGlue()); + top.add(uninstallBtn); + + detailsPanel.add(top, BorderLayout.PAGE_START); + detailsPanel.add(descArea, BorderLayout.CENTER); + } + detailsPanel.updateUI(); + } + + private static class PluginsListCellRenderer implements ListCellRenderer { + private final JPanel panel; + private final JLabel nameLbl; + private final JLabel versionLbl; + private final JLabel titleLbl; + + public PluginsListCellRenderer(JList pluginsList) { + panel = new JPanel(); + panel.setOpaque(true); + panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS)); + panel.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10)); + + nameLbl = new JLabel(""); + nameLbl.setFont(nameLbl.getFont().deriveFont(Font.BOLD)); + nameLbl.setOpaque(true); + versionLbl = new JLabel(""); + versionLbl.setOpaque(true); + + panel.add(nameLbl); + panel.add(Box.createHorizontalGlue()); + panel.add(versionLbl); + + titleLbl = new JLabel(); + titleLbl.setHorizontalAlignment(SwingConstants.CENTER); + titleLbl.setEnabled(false); + } + + @Override + public Component getListCellRendererComponent(JList list, + BasePluginListNode value, int index, boolean isSelected, boolean cellHasFocus) { + String title = value.getTitle(); + if (title != null) { + titleLbl.setText(title); + return titleLbl; + } + nameLbl.setText(value.getPluginInfo().getName()); + nameLbl.setToolTipText(value.getPluginInfo().getDescription()); + versionLbl.setText(Utils.getOrElse(value.getVersion(), "")); + if (isSelected) { + panel.setBackground(list.getSelectionBackground()); + nameLbl.setBackground(list.getSelectionBackground()); + nameLbl.setForeground(list.getSelectionForeground()); + versionLbl.setBackground(list.getSelectionBackground()); + } else { + panel.setBackground(list.getBackground()); + nameLbl.setBackground(list.getBackground()); + nameLbl.setForeground(list.getForeground()); + versionLbl.setBackground(list.getBackground()); + } + return panel; + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/TitleNode.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/TitleNode.java new file mode 100644 index 000000000..d78f71e2c --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/TitleNode.java @@ -0,0 +1,14 @@ +package jadx.gui.settings.ui.plugins; + +public class TitleNode extends BasePluginListNode { + private final String title; + + public TitleNode(String title) { + this.title = title; + } + + @Override + public String getTitle() { + return title; + } +} 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 030ab3cd7..037f38170 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -104,6 +104,7 @@ import jadx.gui.plugins.quark.QuarkDialog; import jadx.gui.settings.JadxProject; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.ui.JadxSettingsWindow; +import jadx.gui.settings.ui.plugins.InstallPluginDialog; import jadx.gui.treemodel.ApkSignature; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JLoadableNode; @@ -482,6 +483,7 @@ public class MainWindow extends JFrame { private void loadFiles(Runnable onFinish) { if (project.getFilePaths().isEmpty()) { + tabbedPane.showNode(new StartPageNode()); return; } AtomicReference wrapperException = new AtomicReference<>(); @@ -1123,7 +1125,7 @@ public class MainWindow extends JFrame { pluginsMenu = new JMenu(NLS.str("menu.plugins")); pluginsMenu.setMnemonic(KeyEvent.VK_P); - pluginsMenu.setVisible(false); + resetPluginsMenu(); JMenu tools = new JMenu(NLS.str("menu.tools")); tools.setMnemonic(KeyEvent.VK_T); @@ -1639,6 +1641,19 @@ public class MainWindow extends JFrame { return pluginsMenu; } + public void resetPluginsMenu() { + pluginsMenu.removeAll(); + pluginsMenu.add(new ActionHandler(() -> new InstallPluginDialog(this).setVisible(true)) + .withNameAndDesc(NLS.str("preferences.plugins.install"))); + } + + public void addToPluginsMenu(Action item) { + if (pluginsMenu.getMenuComponentCount() == 1) { + pluginsMenu.addSeparator(); + } + pluginsMenu.add(item); + } + public RenameMappingsGui getRenameMappings() { return renameMappings; } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/LafManager.java b/jadx-gui/src/main/java/jadx/gui/utils/LafManager.java index cc06f216e..4936d6e66 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/LafManager.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/LafManager.java @@ -40,8 +40,8 @@ public class LafManager { public static void updateLaf(JadxSettings settings) { if (setupLaf(getThemeClass(settings))) { // update all components - FlatLaf.updateUI(); FlatAnimatedLafChange.hideSnapshotWithAnimation(); + FlatLaf.updateUI(); } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/plugins/CollectPluginOptions.java b/jadx-gui/src/main/java/jadx/gui/utils/plugins/CollectPlugins.java similarity index 85% rename from jadx-gui/src/main/java/jadx/gui/utils/plugins/CollectPluginOptions.java rename to jadx-gui/src/main/java/jadx/gui/utils/plugins/CollectPlugins.java index 8b88c1a5f..5d856e3ab 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/plugins/CollectPluginOptions.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/plugins/CollectPlugins.java @@ -1,9 +1,9 @@ package jadx.gui.utils.plugins; +import java.util.ArrayList; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; -import java.util.stream.Collectors; import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; @@ -15,15 +15,15 @@ import jadx.gui.ui.MainWindow; import jadx.plugins.tools.JadxExternalPluginsLoader; /** - * Collect options from all plugins. + * Collect all plugins. * Init not yet loaded plugins in new temporary context. * Support case if decompiler in wrapper not initialized yet. */ -public class CollectPluginOptions { +public class CollectPlugins { private final MainWindow mainWindow; - public CollectPluginOptions(MainWindow mainWindow) { + public CollectPlugins(MainWindow mainWindow) { this.mainWindow = mainWindow; } @@ -50,9 +50,6 @@ public class CollectPluginOptions { pluginManager.init(missingPlugins); allPlugins.addAll(missingPlugins); } - return allPlugins.stream() - .filter(context -> context.getOptions() != null) - .sorted() - .collect(Collectors.toList()); + return new ArrayList<>(allPlugins); } } 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 8e5a4ea2a..03762e6ac 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -222,6 +222,16 @@ preferences.search_group_title=Ressourcen durchsuchen preferences.res_file_ext=Dateierweiterungen (z.B. .xml|.html), * bedeutet alle preferences.res_skip_file=Dateien überspringen (MB) +#preferences.plugins.install=Install plugin +#preferences.plugins.install_btn=Install +#preferences.plugins.location_id_label=Location id: +#preferences.plugins.plugin_jar=Select Plugin jar +#preferences.plugins.plugin_jar_label=or +#preferences.plugins.update_all=Update All +#preferences.plugins.task.installing=Installing plugin +#preferences.plugins.task.uninstalling=Uninstalling plugin +#preferences.plugins.task.updating=Updating plugins + msg.open_file=Bitte Datei öffnen msg.saving_sources=Quelltexte speichern msg.language_changed_title=Sprache speichern 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 63a6161da..a92472ae7 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -222,6 +222,16 @@ preferences.search_results_per_page=Results per page (0 - no limit) preferences.res_file_ext=Resource files extensions ('xml|html', * for all) preferences.res_skip_file=Skip resources files if larger (MB) (0 - disable) +preferences.plugins.install=Install plugin +preferences.plugins.install_btn=Install +preferences.plugins.location_id_label=Location id: +preferences.plugins.plugin_jar=Select Plugin jar +preferences.plugins.plugin_jar_label=or +preferences.plugins.update_all=Update All +preferences.plugins.task.installing=Installing plugin +preferences.plugins.task.uninstalling=Uninstalling plugin +preferences.plugins.task.updating=Updating plugins + msg.open_file=Please open file msg.saving_sources=Saving sources msg.language_changed_title=Language changed 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 a5ee97d1a..4d82ef0ce 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -222,6 +222,16 @@ preferences.reset_title=Reestablecer preferencias #preferences.res_file_ext=Resource files extensions ('xml|html', * for all) #preferences.res_skip_file=Skip resources files if larger (MB) +#preferences.plugins.install=Install plugin +#preferences.plugins.install_btn=Install +#preferences.plugins.location_id_label=Location id: +#preferences.plugins.plugin_jar=Select Plugin jar +#preferences.plugins.plugin_jar_label=or +#preferences.plugins.update_all=Update All +#preferences.plugins.task.installing=Installing plugin +#preferences.plugins.task.uninstalling=Uninstalling plugin +#preferences.plugins.task.updating=Updating plugins + msg.open_file=Por favor, abra un archivo msg.saving_sources=Guardando fuente msg.language_changed_title=Idioma cambiado 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 6b7b41871..6c0b4f8dd 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -222,6 +222,16 @@ preferences.search_group_title=리소스 검색 preferences.res_file_ext=파일 확장자 (예: .xml|.html) (* 은 전체를 의미) preferences.res_skip_file=이 옵션보다 큰 파일 건너 뛰기 (MB) +#preferences.plugins.install=Install plugin +#preferences.plugins.install_btn=Install +#preferences.plugins.location_id_label=Location id: +#preferences.plugins.plugin_jar=Select Plugin jar +#preferences.plugins.plugin_jar_label=or +#preferences.plugins.update_all=Update All +#preferences.plugins.task.installing=Installing plugin +#preferences.plugins.task.uninstalling=Uninstalling plugin +#preferences.plugins.task.updating=Updating plugins + msg.open_file=파일을 여십시오 msg.saving_sources=소스 저장 중 msg.language_changed_title=언어 변경됨 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 2486d0d8b..d7bf1c59a 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -222,6 +222,16 @@ preferences.search_group_title=Buscar recursos preferences.res_file_ext=Extensões de arquivos (ex: .xml|.html), * significa todas preferences.res_skip_file=Pular arquivos excedidos +#preferences.plugins.install=Install plugin +#preferences.plugins.install_btn=Install +#preferences.plugins.location_id_label=Location id: +#preferences.plugins.plugin_jar=Select Plugin jar +#preferences.plugins.plugin_jar_label=or +#preferences.plugins.update_all=Update All +#preferences.plugins.task.installing=Installing plugin +#preferences.plugins.task.uninstalling=Uninstalling plugin +#preferences.plugins.task.updating=Updating plugins + msg.open_file=Abra um arquivo msg.saving_sources=Salvando recursos msg.language_changed_title=Idioma alterado 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 71d2e2cf9..cb2f6421e 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -222,6 +222,16 @@ preferences.search_results_per_page=Результатов на страницу preferences.res_file_ext=Расширения файлов ресурсов ('xml|html', * для всех) preferences.res_skip_file=Пропускать ресурсы больше чем (в МБ) +#preferences.plugins.install=Install plugin +#preferences.plugins.install_btn=Install +#preferences.plugins.location_id_label=Location id: +#preferences.plugins.plugin_jar=Select Plugin jar +#preferences.plugins.plugin_jar_label=or +#preferences.plugins.update_all=Update All +#preferences.plugins.task.installing=Installing plugin +#preferences.plugins.task.uninstalling=Uninstalling plugin +#preferences.plugins.task.updating=Updating plugins + msg.open_file=Пожалуйста, откройте файл msg.saving_sources=Сохранение ресурсов msg.language_changed_title=Язык изменен 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 30615d097..4f7d25aba 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -222,6 +222,16 @@ preferences.search_results_per_page=每页结果数(0 - 无限制) preferences.res_file_ext=文件扩展名(e.g. .xml|.html),* 表示所有 preferences.res_skip_file=跳过文件大小(MB) +#preferences.plugins.install=Install plugin +#preferences.plugins.install_btn=Install +#preferences.plugins.location_id_label=Location id: +#preferences.plugins.plugin_jar=Select Plugin jar +#preferences.plugins.plugin_jar_label=or +#preferences.plugins.update_all=Update All +#preferences.plugins.task.installing=Installing plugin +#preferences.plugins.task.uninstalling=Uninstalling plugin +#preferences.plugins.task.updating=Updating plugins + msg.open_file=请打开文件 msg.saving_sources=正在导出源代码 msg.language_changed_title=语言已更改 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 415d82efc..8d521f3be 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -222,6 +222,16 @@ preferences.search_results_per_page=每頁的搜尋結果數 (0 - 無限制) preferences.res_file_ext=副檔名 (e.g. .xml|.html), * 表示全部 preferences.res_skip_file=略過大於此值的檔案 (MB) +#preferences.plugins.install=Install plugin +#preferences.plugins.install_btn=Install +#preferences.plugins.location_id_label=Location id: +#preferences.plugins.plugin_jar=Select Plugin jar +#preferences.plugins.plugin_jar_label=or +#preferences.plugins.update_all=Update All +#preferences.plugins.task.installing=Installing plugin +#preferences.plugins.task.uninstalling=Uninstalling plugin +#preferences.plugins.task.updating=Updating plugins + msg.open_file=請開啟檔案 msg.saving_sources=正在儲存原始碼 msg.language_changed_title=已更改語言 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 ede725434..c2b56b90b 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 @@ -4,7 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.Writer; -import java.net.URL; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -46,7 +46,7 @@ public class JadxPluginsTools { private JadxPluginsTools() { ProjectDirectories jadxDirs = ProjectDirectories.from("io.github", "skylot", "jadx"); - Path plugins = Paths.get(jadxDirs.configDir, "plugins"); // TODO: use dataDir? + Path plugins = Paths.get(jadxDirs.configDir, "plugins"); makeDirs(plugins); pluginsJson = plugins.resolve("plugins.json"); dropins = plugins.resolve("dropins"); @@ -165,7 +165,9 @@ public class JadxPluginsTools { } fillPluginInfoFromJar(metadata, tmpJar); - Path pluginJar = installed.resolve(metadata.getPluginId() + '-' + metadata.getVersion() + ".jar"); + String version = metadata.getVersion(); + String fileName = metadata.getPluginId() + (version != null ? '-' + version : "") + ".jar"; + Path pluginJar = installed.resolve(fileName); copyJar(tmpJar, pluginJar); metadata.setJar(installed.relativize(pluginJar).toString()); @@ -195,7 +197,7 @@ public class JadxPluginsTools { } private void downloadJar(String sourceJar, Path destPath) { - try (InputStream in = new URL(sourceJar).openStream()) { + try (InputStream in = URI.create(sourceJar).toURL().openStream()) { Files.copy(in, destPath, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { throw new RuntimeException("Failed to download jar: " + sourceJar, e); 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 ed5c9ee5b..8b2919505 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 @@ -1,12 +1,13 @@ package jadx.plugins.tools.data; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class JadxPluginMetadata implements Comparable { private String pluginId; private String name; private String description; - private String version; + private @Nullable String version; private String locationId; private String resolverId; private String jar; @@ -27,7 +28,7 @@ public class JadxPluginMetadata implements Comparable { this.name = name; } - public String getVersion() { + public @Nullable String getVersion() { return version; } @@ -93,7 +94,7 @@ public class JadxPluginMetadata implements Comparable { return "JadxPluginMetadata{" + "id=" + pluginId + ", name=" + name - + ", version=" + version + + ", version=" + (version != null ? version : "?") + ", locationId=" + locationId + ", jar=" + jar + '}'; diff --git a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/resolvers/README.md b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/resolvers/README.md index 0d9194803..1db7d2b76 100644 --- a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/resolvers/README.md +++ b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/resolvers/README.md @@ -10,10 +10,9 @@ Examples: `github:skylot:jadx`, `github:skylot:jadx:sample-plugin` or `github:sk `` - exact version to install (optional), should be equal to release name -Artifact should have a name: `-.jar`. +Artifact name pattern: `[-].jar`. -Default value for `` is a repo name, -`release-version-name` should have a `x.x.x` format. +Default value for `` is a repo name, `-` is optional. --- diff --git a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/resolvers/github/GithubReleaseResolver.java b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/resolvers/github/GithubReleaseResolver.java index 576814213..8769d9247 100644 --- a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/resolvers/github/GithubReleaseResolver.java +++ b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/resolvers/github/GithubReleaseResolver.java @@ -64,7 +64,7 @@ public class GithubReleaseResolver implements IJadxPluginResolver { Asset asset = release.getAssets().stream() .filter(a -> a.getName().equals(artifactName)) .findFirst() - .orElseThrow(() -> new RuntimeException("Release artifact with name '" + artifactName + "' not found")); + .orElse(searchIgnoringVersion(release, artifactPrefix)); JadxPluginMetadata metadata = new JadxPluginMetadata(); metadata.setResolverId(id()); @@ -74,6 +74,13 @@ public class GithubReleaseResolver implements IJadxPluginResolver { return Optional.of(metadata); } + private @NotNull Asset searchIgnoringVersion(Release release, String artifactPrefix) { + return release.getAssets().stream() + .filter(a -> a.getName().startsWith(artifactPrefix) && a.getName().startsWith(".jar")) + .findFirst() + .orElseThrow(() -> new RuntimeException("Release artifact with prefix '" + artifactPrefix + "' not found")); + } + @NotNull private static String buildLocationId(String owner, String project, String artifactPrefix) { if (project.equals(artifactPrefix)) { diff --git a/jadx-plugins/jadx-rename-mappings/src/main/java/jadx/plugins/mappings/RenameMappingsOptions.java b/jadx-plugins/jadx-rename-mappings/src/main/java/jadx/plugins/mappings/RenameMappingsOptions.java index 662ac3218..a9b0f23c9 100644 --- a/jadx-plugins/jadx-rename-mappings/src/main/java/jadx/plugins/mappings/RenameMappingsOptions.java +++ b/jadx-plugins/jadx-rename-mappings/src/main/java/jadx/plugins/mappings/RenameMappingsOptions.java @@ -38,7 +38,7 @@ public class RenameMappingsOptions extends BaseOptionsParser { public List getOptionsDescriptions() { return Arrays.asList( new JadxOptionDescription(FORMAT_OPT, "mapping format", "auto", getMappingFormats()) - .withFlag(OptionFlag.PER_PROJECT), + .withFlags(OptionFlag.PER_PROJECT, OptionFlag.DISABLE_IN_GUI), JadxOptionDescription.booleanOption(INVERT_OPT, "invert mapping", false) .withFlag(OptionFlag.PER_PROJECT)); } diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf.jadx.kts b/jadx-plugins/jadx-script/examples/scripts/deobf.jadx.kts index febcdb810..7dfa76513 100644 --- a/jadx-plugins/jadx-script/examples/scripts/deobf.jadx.kts +++ b/jadx-plugins/jadx-script/examples/scripts/deobf.jadx.kts @@ -4,7 +4,14 @@ val jadx = getJadxInstance() jadx.args.isDeobfuscationOn = false jadx.args.renameFlags = emptySet() -val regex = """[Oo0]+""".toRegex() +val regexOpt = jadx.options.registerString( + "regex", + "Apply rename for names matches regex", + values = listOf(), + defaultValue = "[Oo0]+", +) + +val regex = regexOpt.value.toRegex() var n = 0 jadx.rename.all { name, node -> when { diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptOptionsUI.kt b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptOptionsUI.kt index fe7aca9e9..4fd452603 100644 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptOptionsUI.kt +++ b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptOptionsUI.kt @@ -13,7 +13,7 @@ object JadxScriptOptionsUI { .groupBy { it.script } .map { (script, options) -> settings.buildSettingsGroupForOptions(script, options) } .toList() - settings.setCustomSettings(EmptyRootGroup("Scripts", subGroups)) + settings.setCustomSettingsGroup(EmptyRootGroup("Scripts", subGroups)) } } diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptEval.kt b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptEval.kt index dcb3daedb..1fd4b16b4 100644 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptEval.kt +++ b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptEval.kt @@ -1,6 +1,5 @@ package jadx.plugins.script -import jadx.api.JadxDecompiler import jadx.api.plugins.JadxPluginContext import jadx.plugins.script.runtime.JadxScriptData import jadx.plugins.script.runtime.JadxScriptTemplate @@ -18,13 +17,15 @@ import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate import kotlin.system.measureTimeMillis +import kotlin.time.DurationUnit +import kotlin.time.toDuration class ScriptEval { private val scriptingHost = BasicJvmScriptingHost() fun process(init: JadxPluginContext, scriptOptions: JadxScriptAllOptions): List { - val jadx = init.decompiler as JadxDecompiler + val jadx = init.decompiler val scripts = jadx.args.inputFiles.filter { f -> f.name.endsWith(".jadx.kts") } if (scripts.isEmpty()) { return emptyList() @@ -60,7 +61,7 @@ class ScriptEval { val result = scriptingHost.eval(scriptData.scriptFile.toScriptSource(), compilationConf, evalConf) processEvalResult(result, scriptData) } - scriptData.log.debug { "Script '${scriptData.scriptName}' executed in $execTime ms" } + scriptData.log.debug { "Script '${scriptData.scriptName}' executed in ${execTime.toDuration(DurationUnit.MILLISECONDS)}" } } private fun processEvalResult(res: ResultWithDiagnostics, scriptData: JadxScriptData) {