From c39a69697755b232fe70050443fc40d2a487e6ab Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 15 Sep 2023 21:50:47 +0100 Subject: [PATCH] feat(script): add methods for apply renames and reload tabs (#2008) --- .../events/types/NodeRenamedByUser.java | 13 ++ .../jadx/api/plugins/gui/JadxGuiContext.java | 15 ++ .../pass/impl/SimpleAfterLoadPass.java | 28 +++ .../gui/events/services/RenameService.java | 178 ++++++++++++++++++ .../gui/plugins/context/GuiPluginContext.java | 48 +++++ .../plugins/script/ScriptContentPanel.java | 3 +- .../src/main/java/jadx/gui/ui/MainWindow.java | 14 +- .../src/main/java/jadx/gui/ui/TabbedPane.java | 10 +- .../jadx/gui/ui/codearea/RenameAction.java | 2 +- .../java/jadx/gui/ui/dialog/RenameDialog.java | 158 +++------------- .../gui/ui/popupmenu/JPackagePopupMenu.java | 2 +- .../resources/i18n/Messages_de_DE.properties | 1 + .../resources/i18n/Messages_en_US.properties | 1 + .../resources/i18n/Messages_es_ES.properties | 1 + .../resources/i18n/Messages_ko_KR.properties | 1 + .../resources/i18n/Messages_pt_BR.properties | 1 + .../resources/i18n/Messages_ru_RU.properties | 1 + .../resources/i18n/Messages_zh_CN.properties | 1 + .../resources/i18n/Messages_zh_TW.properties | 1 + .../scripts/deobf/deobf_method_param.jadx.kts | 2 +- .../plugins/script/runtime/ScriptRuntime.kt | 3 +- .../jadx/plugins/script/runtime/data/Gui.kt | 9 + .../plugins/script/runtime/data/Search.kt | 2 +- 23 files changed, 351 insertions(+), 144 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/api/plugins/pass/impl/SimpleAfterLoadPass.java create mode 100644 jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java diff --git a/jadx-core/src/main/java/jadx/api/plugins/events/types/NodeRenamedByUser.java b/jadx-core/src/main/java/jadx/api/plugins/events/types/NodeRenamedByUser.java index 7580c30aeb1..27b4f83c240 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/events/types/NodeRenamedByUser.java +++ b/jadx-core/src/main/java/jadx/api/plugins/events/types/NodeRenamedByUser.java @@ -11,6 +11,11 @@ public class NodeRenamedByUser implements IJadxEvent { private final String oldName; private final String newName; + /** + * Optional JRenameNode instance + */ + private Object renameNode; + public NodeRenamedByUser(ICodeNodeRef node, String oldName, String newName) { this.node = node; this.oldName = oldName; @@ -29,6 +34,14 @@ public String getNewName() { return newName; } + public Object getRenameNode() { + return renameNode; + } + + public void setRenameNode(Object renameNode) { + this.renameNode = renameNode; + } + @Override public JadxEventType getType() { return JadxEvents.NODE_RENAMED_BY_USER; diff --git a/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java b/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java index fe81d28fec5..dd4cb759c2d 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java +++ b/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java @@ -64,4 +64,19 @@ void addPopupMenuAction(String name, * @return if successfully jumped to the code ref */ boolean open(ICodeNodeRef ref); + + /** + * Reload code in active tab + */ + void reloadActiveTab(); + + /** + * Reload code in all open tabs + */ + void reloadAllTabs(); + + /** + * Save node rename in a project and run all needed UI updates + */ + void applyNodeRename(ICodeNodeRef node); } diff --git a/jadx-core/src/main/java/jadx/api/plugins/pass/impl/SimpleAfterLoadPass.java b/jadx-core/src/main/java/jadx/api/plugins/pass/impl/SimpleAfterLoadPass.java new file mode 100644 index 00000000000..0fbf2bfe320 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/plugins/pass/impl/SimpleAfterLoadPass.java @@ -0,0 +1,28 @@ +package jadx.api.plugins.pass.impl; + +import java.util.function.Consumer; + +import jadx.api.JadxDecompiler; +import jadx.api.plugins.pass.JadxPassInfo; +import jadx.api.plugins.pass.types.JadxAfterLoadPass; + +public class SimpleAfterLoadPass implements JadxAfterLoadPass { + + private final JadxPassInfo info; + private final Consumer init; + + public SimpleAfterLoadPass(String name, Consumer init) { + this.info = new SimpleJadxPassInfo(name); + this.init = init; + } + + @Override + public JadxPassInfo getInfo() { + return info; + } + + @Override + public void init(JadxDecompiler decompiler) { + init.accept(decompiler); + } +} 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 new file mode 100644 index 00000000000..f97283c328b --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java @@ -0,0 +1,178 @@ +package jadx.gui.events.services; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.JadxDecompiler; +import jadx.api.JavaNode; +import jadx.api.data.ICodeRename; +import jadx.api.data.impl.JadxCodeData; +import jadx.api.plugins.events.JadxEvents; +import jadx.api.plugins.events.types.NodeRenamedByUser; +import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.gui.jobs.TaskStatus; +import jadx.gui.settings.JadxProject; +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JNode; +import jadx.gui.treemodel.JRenameNode; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.codearea.ClassCodeContentPanel; +import jadx.gui.ui.codearea.CodeArea; +import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.utils.CacheObject; +import jadx.gui.utils.JNodeCache; +import jadx.gui.utils.NLS; +import jadx.gui.utils.UiUtils; + +/** + * Rename service listen for user rename events. + * For each event: + * - add/update rename entry in project code data + * - update code and/or invalidate cache for related classes + * - apply all needed UI updates (tabs, classes tree) + */ +public class RenameService { + private static final Logger LOG = LoggerFactory.getLogger(RenameService.class); + + public static void init(MainWindow mainWindow) { + RenameService renameService = new RenameService(mainWindow); + mainWindow.events().addListener(JadxEvents.NODE_RENAMED_BY_USER, renameService::process); + } + + private final MainWindow mainWindow; + + private RenameService(MainWindow mainWindow) { + this.mainWindow = mainWindow; + } + + private void process(NodeRenamedByUser event) { + try { + LOG.debug("Applying rename event: {}", event); + JRenameNode node = getRenameNode(event); + updateCodeRenames(set -> processRename(node, event.getNewName(), set)); + refreshState(node); + } catch (Exception e) { + LOG.error("Rename failed", e); + UiUtils.errorMessage(mainWindow, "Rename failed:\n" + Utils.getStackTrace(e)); + } + } + + private @NotNull JRenameNode getRenameNode(NodeRenamedByUser event) { + Object renameNode = event.getRenameNode(); + if (renameNode instanceof JRenameNode) { + return (JRenameNode) renameNode; + } + JadxDecompiler decompiler = mainWindow.getWrapper().getDecompiler(); + JavaNode javaNode = decompiler.getJavaNodeByRef(event.getNode()); + if (javaNode != null) { + JNode node = mainWindow.getCacheObject().getNodeCache().makeFrom(javaNode); + if (node instanceof JRenameNode) { + return (JRenameNode) node; + } + } + throw new JadxRuntimeException("Failed to resolve node: " + event.getNode()); + } + + private void processRename(JRenameNode node, String newName, Set renames) { + ICodeRename rename = node.buildCodeRename(newName, renames); + renames.remove(rename); + if (newName.isEmpty()) { + node.removeAlias(); + } else { + renames.add(rename); + } + } + + private void updateCodeRenames(Consumer> updater) { + JadxProject project = mainWindow.getProject(); + JadxCodeData codeData = project.getCodeData(); + if (codeData == null) { + codeData = new JadxCodeData(); + } + Set set = new HashSet<>(codeData.getRenames()); + updater.accept(set); + List list = new ArrayList<>(set); + Collections.sort(list); + codeData.setRenames(list); + project.setCodeData(codeData); + } + + private void refreshState(JRenameNode node) { + List toUpdate = new ArrayList<>(); + node.addUpdateNodes(toUpdate); + + JNodeCache nodeCache = mainWindow.getCacheObject().getNodeCache(); + Set updatedTopClasses = toUpdate + .stream() + .map(JavaNode::getTopParentClass) + .map(nodeCache::makeFrom) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + LOG.debug("Classes to update: {}", updatedTopClasses); + if (updatedTopClasses.isEmpty()) { + return; + } + mainWindow.getBackgroundExecutor().execute("Refreshing", + () -> { + mainWindow.getWrapper().reloadCodeData(); + UiUtils.uiRunAndWait(() -> refreshTabs(mainWindow.getTabbedPane(), updatedTopClasses)); + refreshClasses(updatedTopClasses); + }, + (status) -> { + if (status == TaskStatus.CANCEL_BY_MEMORY) { + mainWindow.showHeapUsageBar(); + UiUtils.errorMessage(mainWindow, NLS.str("message.memoryLow")); + } + node.reload(mainWindow); + }); + } + + private void refreshClasses(Set updatedTopClasses) { + CacheObject cache = mainWindow.getCacheObject(); + if (updatedTopClasses.size() < 10) { + // small batch => reload + LOG.debug("Classes to reload: {}", updatedTopClasses.size()); + for (JClass cls : updatedTopClasses) { + try { + cls.reload(cache); + } catch (Exception e) { + LOG.error("Failed to reload class: {}", cls.getFullName(), e); + } + } + } else { + // big batch => unload + LOG.debug("Classes to unload: {}", updatedTopClasses.size()); + for (JClass cls : updatedTopClasses) { + try { + cls.unload(cache); + } catch (Exception e) { + LOG.error("Failed to unload class: {}", cls.getFullName(), e); + } + } + } + } + + private void refreshTabs(TabbedPane tabbedPane, Set updatedClasses) { + for (ContentPanel tab : tabbedPane.getTabs()) { + JClass rootClass = tab.getNode().getRootClass(); + if (updatedClasses.remove(rootClass)) { + ClassCodeContentPanel contentPanel = (ClassCodeContentPanel) tab; + CodeArea codeArea = (CodeArea) contentPanel.getJavaCodePanel().getCodeArea(); + codeArea.refreshClass(); + } + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java index d75811d900a..9b9f9a5ab0b 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java @@ -11,7 +11,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.JadxDecompiler; +import jadx.api.JavaClass; +import jadx.api.JavaNode; import jadx.api.metadata.ICodeNodeRef; +import jadx.api.plugins.events.IJadxEvents; +import jadx.api.plugins.events.types.NodeRenamedByUser; import jadx.api.plugins.gui.ISettingsGroup; import jadx.api.plugins.gui.JadxGuiContext; import jadx.api.plugins.gui.JadxGuiSettings; @@ -19,10 +24,12 @@ import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.plugins.PluginContext; +import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.treemodel.JNode; import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.codearea.AbstractCodeContentPanel; import jadx.gui.ui.codearea.CodeArea; +import jadx.gui.ui.panel.ContentPanel; import jadx.gui.utils.JNodeCache; import jadx.gui.utils.UiUtils; @@ -176,4 +183,45 @@ public boolean open(ICodeNodeRef ref) { return true; } + @Override + public void reloadActiveTab() { + UiUtils.uiRun(() -> { + CodeArea codeArea = getCodeArea(); + if (codeArea != null) { + codeArea.refreshClass(); + } + }); + } + + @Override + public void reloadAllTabs() { + UiUtils.uiRun(() -> { + for (ContentPanel contentPane : commonContext.getMainWindow().getTabbedPane().getTabs()) { + if (contentPane instanceof AbstractCodeContentPanel) { + AbstractCodeArea codeArea = ((AbstractCodeContentPanel) contentPane).getCodeArea(); + if (codeArea instanceof CodeArea) { + ((CodeArea) codeArea).refreshClass(); + } + } + } + }); + } + + @Override + public void applyNodeRename(ICodeNodeRef nodeRef) { + JadxDecompiler decompiler = commonContext.getMainWindow().getWrapper().getDecompiler(); + JavaNode javaNode = decompiler.getJavaNodeByRef(nodeRef); + if (javaNode == null) { + throw new JadxRuntimeException("Failed to resolve node ref: " + nodeRef); + } + String newName; + if (javaNode instanceof JavaClass) { + // package can have alias + newName = javaNode.getFullName(); + } else { + newName = javaNode.getName(); + } + IJadxEvents events = commonContext.getMainWindow().events(); + events.send(new NodeRenamedByUser(nodeRef, "", newName)); + } } 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 e32dbd6b626..e9da82bc1d8 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 @@ -144,8 +144,7 @@ private void runScript() { scriptLog.error("Passes reload failed", e); } }, taskStatus -> { - tabbedPane.reloadInactiveTabs(); - mainWindow.reloadTree(); + mainWindow.passesReloaded(); }); } 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 441f7d94b3f..668960c4059 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -97,6 +97,7 @@ import jadx.gui.JadxWrapper; import jadx.gui.cache.manager.CacheManager; import jadx.gui.device.debugger.BreakpointManager; +import jadx.gui.events.services.RenameService; import jadx.gui.jobs.BackgroundExecutor; import jadx.gui.jobs.DecompileTask; import jadx.gui.jobs.ExportTask; @@ -580,7 +581,7 @@ private void onOpen() { initTree(); updateLiveReload(project.isEnableLiveReload()); BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent()); - events().addListener(JadxEvents.RELOAD_PROJECT, ev -> UiUtils.uiRun(this::reopen)); + initEvents(); List openTabs = project.getOpenTabs(this); backgroundExecutor.execute(NLS.str("progress.load"), @@ -593,6 +594,17 @@ 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)); + RenameService.init(this); + } + public void updateLiveReload(boolean state) { if (liveReloadWorker.isStarted() == state) { return; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index 18641e54847..24f7f6f863f 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -429,8 +429,16 @@ public void reloadInactiveTabs() { if (i == current) { continue; } - JNode node = ((ContentPanel) getComponentAt(i)).getNode(); + ContentPanel oldPanel = (ContentPanel) getComponentAt(i); + EditorViewState viewState = null; + if (oldPanel instanceof IViewStateSupport) { + viewState = ((IViewStateSupport) oldPanel).getEditorViewState(); + } + JNode node = oldPanel.getNode(); ContentPanel panel = node.getContentPanel(this); + if (viewState != null && panel instanceof IViewStateSupport) { + ((IViewStateSupport) panel).restoreEditorViewState(viewState); + } FocusManager.listen(panel); tabsMap.put(node, panel); setComponentAt(i, panel); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java index 41a0d75e39b..bf7fe65e7f0 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java @@ -25,6 +25,6 @@ public boolean isActionEnabled(JNode node) { @Override public void runAction(JNode node) { - RenameDialog.rename(getCodeArea().getMainWindow(), getCodeArea().getNode(), (JRenameNode) node); + RenameDialog.rename(getCodeArea().getMainWindow(), (JRenameNode) node); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java index e33ff1020d7..795d43eb674 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java @@ -4,14 +4,6 @@ import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.Consumer; -import java.util.stream.Collectors; import javax.swing.BorderFactory; import javax.swing.Box; @@ -26,29 +18,13 @@ import javax.swing.WindowConstants; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import jadx.api.JavaNode; -import jadx.api.data.ICodeRename; -import jadx.api.data.impl.JadxCodeData; import jadx.api.metadata.ICodeNodeRef; import jadx.api.plugins.events.types.NodeRenamedByUser; -import jadx.core.utils.Utils; -import jadx.gui.jobs.TaskStatus; -import jadx.gui.settings.JadxProject; -import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JPackage; import jadx.gui.treemodel.JRenameNode; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; -import jadx.gui.ui.codearea.ClassCodeContentPanel; -import jadx.gui.ui.codearea.CodeArea; -import jadx.gui.ui.panel.ContentPanel; -import jadx.gui.utils.CacheObject; -import jadx.gui.utils.JNodeCache; import jadx.gui.utils.NLS; import jadx.gui.utils.TextStandardActions; import jadx.gui.utils.UiUtils; @@ -59,17 +35,13 @@ public class RenameDialog extends JDialog { private static final long serialVersionUID = -3269715644416902410L; - private static final Logger LOG = LoggerFactory.getLogger(RenameDialog.class); - private final transient MainWindow mainWindow; - private final transient CacheObject cache; - private final transient @Nullable JNode source; private final transient JRenameNode node; private transient JTextField renameField; private transient JButton renameBtn; - public static boolean rename(MainWindow mainWindow, @Nullable JNode source, JRenameNode node) { - RenameDialog renameDialog = new RenameDialog(mainWindow, source, node); + public static boolean rename(MainWindow mainWindow, JRenameNode node) { + RenameDialog renameDialog = new RenameDialog(mainWindow, node); UiUtils.uiRun(() -> renameDialog.setVisible(true)); UiUtils.uiRun(renameDialog::initRenameField); // wait for UI events to propagate return true; @@ -77,18 +49,16 @@ public static boolean rename(MainWindow mainWindow, @Nullable JNode source, JRen public static JPopupMenu buildRenamePopup(MainWindow mainWindow, JRenameNode node) { JMenuItem jmi = new JMenuItem(NLS.str("popup.rename")); - jmi.addActionListener(action -> RenameDialog.rename(mainWindow, null, node)); + jmi.addActionListener(action -> RenameDialog.rename(mainWindow, node)); jmi.setEnabled(node.canRename()); JPopupMenu menu = new JPopupMenu(); menu.add(jmi); return menu; } - private RenameDialog(MainWindow mainWindow, @Nullable JNode source, JRenameNode node) { + private RenameDialog(MainWindow mainWindow, JRenameNode node) { super(mainWindow); this.mainWindow = mainWindow; - this.cache = mainWindow.getCacheObject(); - this.source = source; this.node = node.replace(); initUI(); } @@ -112,125 +82,42 @@ private boolean checkNewName(String newName) { } private void rename() { - String newName = renameField.getText().trim(); + rename(renameField.getText().trim()); + } + + private void resetName() { + rename(""); + } + + private void rename(String newName) { if (!checkNewName(newName)) { return; } - try { - updateCodeRenames(set -> processRename(newName, set)); - refreshState(); - } catch (Exception e) { - LOG.error("Rename failed", e); - UiUtils.errorMessage(this, "Rename failed:\n" + Utils.getStackTrace(e)); - } - dispose(); - } - - private void processRename(String newName, Set renames) { - ICodeRename rename = node.buildCodeRename(newName, renames); - renames.remove(rename); String oldName = node.getName(); if (newName.isEmpty()) { node.removeAlias(); sendRenameEvent(oldName, node.getJavaNode().getName()); } else { - renames.add(rename); sendRenameEvent(oldName, newName); } + dispose(); } private void sendRenameEvent(String oldName, String newName) { ICodeNodeRef nodeRef = node.getJavaNode().getCodeNodeRef(); - mainWindow.events().send(new NodeRenamedByUser(nodeRef, oldName, newName)); - } - - private void updateCodeRenames(Consumer> updater) { - JadxProject project = mainWindow.getProject(); - JadxCodeData codeData = project.getCodeData(); - if (codeData == null) { - codeData = new JadxCodeData(); - } - Set set = new HashSet<>(codeData.getRenames()); - updater.accept(set); - List list = new ArrayList<>(set); - Collections.sort(list); - codeData.setRenames(list); - project.setCodeData(codeData); - } - - private void refreshState() { - List toUpdate = new ArrayList<>(); - if (source != null && source != node) { - toUpdate.add(source.getJavaNode()); - } - node.addUpdateNodes(toUpdate); - - JNodeCache nodeCache = cache.getNodeCache(); - Set updatedTopClasses = toUpdate - .stream() - .map(JavaNode::getTopParentClass) - .map(nodeCache::makeFrom) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - LOG.debug("Classes to update: {}", updatedTopClasses); - if (updatedTopClasses.isEmpty()) { - return; - } - mainWindow.getBackgroundExecutor().execute("Refreshing", - () -> { - mainWindow.getWrapper().reloadCodeData(); - UiUtils.uiRunAndWait(() -> refreshTabs(mainWindow.getTabbedPane(), updatedTopClasses)); - refreshClasses(updatedTopClasses); - }, - (status) -> { - if (status == TaskStatus.CANCEL_BY_MEMORY) { - mainWindow.showHeapUsageBar(); - UiUtils.errorMessage(this, NLS.str("message.memoryLow")); - } - node.reload(mainWindow); - }); - } - - private void refreshClasses(Set updatedTopClasses) { - if (updatedTopClasses.size() < 10) { - // small batch => reload - LOG.debug("Classes to reload: {}", updatedTopClasses.size()); - for (JClass cls : updatedTopClasses) { - try { - cls.reload(cache); - } catch (Exception e) { - LOG.error("Failed to reload class: {}", cls.getFullName(), e); - } - } - } else { - // big batch => unload - LOG.debug("Classes to unload: {}", updatedTopClasses.size()); - for (JClass cls : updatedTopClasses) { - try { - cls.unload(cache); - } catch (Exception e) { - LOG.error("Failed to unload class: {}", cls.getFullName(), e); - } - } - } - } - - private void refreshTabs(TabbedPane tabbedPane, Set updatedClasses) { - for (ContentPanel tab : tabbedPane.getTabs()) { - JClass rootClass = tab.getNode().getRootClass(); - if (updatedClasses.remove(rootClass)) { - ClassCodeContentPanel contentPanel = (ClassCodeContentPanel) tab; - CodeArea codeArea = (CodeArea) contentPanel.getJavaCodePanel().getCodeArea(); - codeArea.refreshClass(); - } - } + NodeRenamedByUser event = new NodeRenamedByUser(nodeRef, oldName, newName); + event.setRenameNode(node); + mainWindow.events().send(event); } @NotNull protected JPanel initButtonsPanel() { - JButton cancelButton = new JButton(NLS.str("search_dialog.cancel")); + JButton resetButton = new JButton(NLS.str("common_dialog.reset")); + resetButton.addActionListener(event -> resetName()); + + JButton cancelButton = new JButton(NLS.str("common_dialog.cancel")); cancelButton.addActionListener(event -> dispose()); + renameBtn = new JButton(NLS.str("common_dialog.ok")); renameBtn.addActionListener(event -> rename()); getRootPane().setDefaultButton(renameBtn); @@ -238,7 +125,8 @@ protected JPanel initButtonsPanel() { JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); - buttonPane.add(Box.createRigidArea(new Dimension(5, 0))); + buttonPane.add(resetButton); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); buttonPane.add(Box.createHorizontalGlue()); buttonPane.add(renameBtn); buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JPackagePopupMenu.java b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JPackagePopupMenu.java index 4a5744e9503..40d41f2a553 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JPackagePopupMenu.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JPackagePopupMenu.java @@ -50,7 +50,7 @@ private JMenuItem makeRenameMenuItem(JPackage pkg) { private void rename(JRenamePackage pkg) { LOG.debug("Renaming package: {}", pkg); - RenameDialog.rename(mainWindow, null, pkg); + RenameDialog.rename(mainWindow, pkg); } private JMenuItem makeExcludeItem(JPackage pkg) { 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 9c71d9566ee..be297759ed0 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -104,6 +104,7 @@ common_dialog.cancel=Abbrechen common_dialog.add=Hinzufügen common_dialog.update=Aktualisieren common_dialog.remove=Entfernen +#common_dialog.reset=Reset file_dialog.supported_files=Unterstützte Dateien file_dialog.load_dir_title=Verzeichnis laden 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 e441e3b5546..48355561c0f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -104,6 +104,7 @@ common_dialog.cancel=Cancel common_dialog.add=Add common_dialog.update=Update common_dialog.remove=Remove +common_dialog.reset=Reset file_dialog.supported_files=Supported files file_dialog.load_dir_title=Load directory 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 0888a1efcc3..c30126c008c 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -104,6 +104,7 @@ nav.forward=Adelante #common_dialog.add=Add #common_dialog.update=Update #common_dialog.remove=Remove +#common_dialog.reset=Reset #file_dialog.supported_files=Supported files #file_dialog.load_dir_title=Load directory 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 ce7e11274e9..3beda69b4b7 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -104,6 +104,7 @@ common_dialog.cancel=취소 common_dialog.add=추가 common_dialog.update=업데이트 common_dialog.remove=삭제 +#common_dialog.reset=Reset file_dialog.supported_files=지원되는 파일 file_dialog.load_dir_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 f2260912e92..7888a63329b 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -104,6 +104,7 @@ common_dialog.cancel=Cancelar common_dialog.add=Adicionar common_dialog.update=Atualizar common_dialog.remove=Remover +#common_dialog.reset=Reset file_dialog.supported_files=Arquivos suportados file_dialog.load_dir_title=Carregar diretório 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 bd7229e1d55..0bca7b3fa40 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -104,6 +104,7 @@ common_dialog.cancel=Отмена common_dialog.add=Добавить common_dialog.update=Обновить common_dialog.remove=Убрать +#common_dialog.reset=Reset file_dialog.supported_files=Поддерж. файлы file_dialog.load_dir_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 9395ad26c9c..bd510ef1c3d 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -104,6 +104,7 @@ common_dialog.cancel=取消 common_dialog.add=添加 common_dialog.update=更新 common_dialog.remove=移除 +#common_dialog.reset=Reset file_dialog.supported_files=支持的文件 file_dialog.load_dir_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 26d7d9ae7c3..96004f24fcc 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -104,6 +104,7 @@ common_dialog.cancel=取消 common_dialog.add=新增 common_dialog.update=更新 common_dialog.remove=移除 +#common_dialog.reset=Reset file_dialog.supported_files=支援的檔案 file_dialog.load_dir_title=載入目錄 diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf/deobf_method_param.jadx.kts b/jadx-plugins/jadx-script/examples/scripts/deobf/deobf_method_param.jadx.kts index be068377bf4..0eadc654dd4 100644 --- a/jadx-plugins/jadx-script/examples/scripts/deobf/deobf_method_param.jadx.kts +++ b/jadx-plugins/jadx-script/examples/scripts/deobf/deobf_method_param.jadx.kts @@ -18,7 +18,7 @@ jadx.addPass(object : ScriptDecompilePass(jadx, "RenameParams") { // parameter annotations stored in method attribute mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS)?.let { paramsAttr -> for ((paramNum, annAttr) in paramsAttr.paramList.withIndex()) { - val name = annAttr.get(annCls)?.values?.get(annParam)?.value as String + val name = annAttr?.get(annCls)?.values?.get(annParam)?.value as String? if (NameMapper.isValidIdentifier(name)) { mth.argRegs[paramNum].name = name log.info { "Rename param $paramNum to $name in method $mth" } diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/ScriptRuntime.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/ScriptRuntime.kt index fcfa05617a7..e6c32d60b4f 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/ScriptRuntime.kt +++ b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/ScriptRuntime.kt @@ -20,6 +20,7 @@ import jadx.plugins.script.runtime.data.Rename import jadx.plugins.script.runtime.data.Replace import jadx.plugins.script.runtime.data.Search import jadx.plugins.script.runtime.data.Stages +import org.jetbrains.annotations.ApiStatus.Internal import java.io.File const val JADX_SCRIPT_LOG_PREFIX = "JadxScript:" @@ -73,5 +74,5 @@ class JadxScriptInstance( } val internalDecompiler: JadxDecompiler - get() = decompiler + @Internal get() = decompiler } diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt index 632af83c6f2..d4baf5674b8 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt +++ b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt @@ -42,6 +42,10 @@ class Gui( fun open(ref: ICodeNodeRef): Boolean = context().open(ref) + fun reloadActiveTab() = context().reloadActiveTab() + + fun reloadAllTabs() = context().reloadAllTabs() + val nodeUnderCaret: ICodeNodeRef? get() = context().nodeUnderCaret val nodeUnderMouse: ICodeNodeRef? @@ -51,6 +55,11 @@ class Gui( val enclosingNodeUnderMouse: ICodeNodeRef? get() = context().enclosingNodeUnderMouse + /** + * Save node rename in a project and run all needed UI updates + */ + fun applyNodeRename(node: ICodeNodeRef) = context().applyNodeRename(node) + private fun context(): JadxGuiContext = guiContext ?: throw IllegalStateException("GUI plugins context not available!") } diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Search.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Search.kt index f8404bd7536..88bd10c6231 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Search.kt +++ b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Search.kt @@ -3,7 +3,7 @@ package jadx.plugins.script.runtime.data import jadx.core.dex.nodes.ClassNode import jadx.plugins.script.runtime.JadxScriptInstance -class Search(private val jadx: JadxScriptInstance) { +class Search(jadx: JadxScriptInstance) { private val dec = jadx.internalDecompiler fun classByFullName(fullName: String): ClassNode? {