From 18fe9f305c46385e1de324e08ea5cdebb9c2f28b Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 16 Jul 2022 20:41:23 +0100 Subject: [PATCH] feat(gui): support scripts in UI --- .../src/main/java/jadx/gui/JadxWrapper.java | 8 ++ .../gui/plugins/context/PluginsContext.java | 47 ++++++++ .../gui/settings/TabStateViewAdapter.java | 12 +++ .../main/java/jadx/gui/treemodel/JClass.java | 8 ++ .../jadx/gui/treemodel/JEditableNode.java | 35 ++++++ .../main/java/jadx/gui/treemodel/JField.java | 8 ++ .../java/jadx/gui/treemodel/JInputFile.java | 43 ++++++++ .../java/jadx/gui/treemodel/JInputFiles.java | 45 ++++++++ .../java/jadx/gui/treemodel/JInputScript.java | 101 ++++++++++++++++++ .../jadx/gui/treemodel/JInputScripts.java | 46 ++++++++ .../main/java/jadx/gui/treemodel/JInputs.java | 50 +++++++++ .../main/java/jadx/gui/treemodel/JMethod.java | 8 ++ .../main/java/jadx/gui/treemodel/JNode.java | 23 ++++ .../java/jadx/gui/treemodel/JPackage.java | 8 ++ .../java/jadx/gui/treemodel/JResource.java | 7 +- .../main/java/jadx/gui/treemodel/JRoot.java | 37 ++++++- .../src/main/java/jadx/gui/ui/MainWindow.java | 71 +++++++++--- .../main/java/jadx/gui/ui/TabComponent.java | 27 +++-- .../gui/ui/codearea/AbstractCodeArea.java | 43 +++++++- .../java/jadx/gui/ui/codearea/CodeArea.java | 1 + .../gui/ui/codearea/CodeContentPanel.java | 13 ++- .../java/jadx/gui/ui/codearea/SmaliArea.java | 1 + .../java/jadx/gui/ui/dialog/RenameDialog.java | 10 ++ .../gui/ui/filedialog/FileDialogWrapper.java | 35 +++--- .../src/main/java/jadx/gui/utils/Icons.java | 3 + .../gui/utils/ui/DocumentUpdateListener.java | 2 +- .../jadx/gui/utils/ui/SimpleMenuItem.java | 11 ++ .../main/resources/files/script.jadx.kts.tmpl | 5 + .../resources/i18n/Messages_de_DE.properties | 9 ++ .../resources/i18n/Messages_en_US.properties | 9 ++ .../resources/i18n/Messages_es_ES.properties | 9 ++ .../resources/i18n/Messages_ko_KR.properties | 9 ++ .../resources/i18n/Messages_pt_BR.properties | 9 ++ .../resources/i18n/Messages_zh_CN.properties | 9 ++ .../resources/i18n/Messages_zh_TW.properties | 9 ++ .../resources/icons/nodes/kotlin_script.svg | 6 ++ .../resources/icons/nodes/moduleDirectory.svg | 7 ++ .../icons/nodes/projectStructure.svg | 9 ++ .../resources/icons/nodes/scriptsModel.svg | 8 ++ 39 files changed, 752 insertions(+), 49 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/context/PluginsContext.java create mode 100644 jadx-gui/src/main/java/jadx/gui/treemodel/JEditableNode.java create mode 100644 jadx-gui/src/main/java/jadx/gui/treemodel/JInputFile.java create mode 100644 jadx-gui/src/main/java/jadx/gui/treemodel/JInputFiles.java create mode 100644 jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java create mode 100644 jadx-gui/src/main/java/jadx/gui/treemodel/JInputScripts.java create mode 100644 jadx-gui/src/main/java/jadx/gui/treemodel/JInputs.java create mode 100644 jadx-gui/src/main/java/jadx/gui/utils/ui/SimpleMenuItem.java create mode 100644 jadx-gui/src/main/resources/files/script.jadx.kts.tmpl create mode 100644 jadx-gui/src/main/resources/icons/nodes/kotlin_script.svg create mode 100644 jadx-gui/src/main/resources/icons/nodes/moduleDirectory.svg create mode 100644 jadx-gui/src/main/resources/icons/nodes/projectStructure.svg create mode 100644 jadx-gui/src/main/resources/icons/nodes/scriptsModel.svg diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index 7f8f19a1d..380c7bb80 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -28,6 +28,7 @@ import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.rename.RenameVisitor; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; +import jadx.gui.plugins.context.PluginsContext; import jadx.gui.settings.JadxProject; import jadx.gui.settings.JadxSettings; import jadx.gui.ui.MainWindow; @@ -47,6 +48,7 @@ public class JadxWrapper { private final MainWindow mainWindow; private volatile @Nullable JadxDecompiler decompiler; + private PluginsContext pluginsContext; public JadxWrapper(MainWindow mainWindow) { this.mainWindow = mainWindow; @@ -62,6 +64,8 @@ public class JadxWrapper { jadxArgs.setCodeData(project.getCodeData()); this.decompiler = new JadxDecompiler(jadxArgs); + this.pluginsContext = new PluginsContext(mainWindow); + this.decompiler.setJadxGuiContext(pluginsContext); this.decompiler.load(); initCodeCache(); } @@ -87,6 +91,10 @@ public class JadxWrapper { decompiler.close(); decompiler = null; } + if (pluginsContext != null) { + pluginsContext.reset(); + pluginsContext = null; + } } } catch (Exception e) { LOG.error("Jadx decompiler close error", e); diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/PluginsContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/PluginsContext.java new file mode 100644 index 000000000..2a55544a3 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/PluginsContext.java @@ -0,0 +1,47 @@ +package jadx.gui.plugins.context; + +import javax.swing.JMenu; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.plugins.gui.JadxGuiContext; +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.UiUtils; +import jadx.gui.utils.ui.ActionHandler; + +public class PluginsContext implements JadxGuiContext { + private static final Logger LOG = LoggerFactory.getLogger(PluginsContext.class); + + private final MainWindow mainWindow; + + public PluginsContext(MainWindow mainWindow) { + this.mainWindow = mainWindow; + } + + public void reset() { + JMenu pluginsMenu = mainWindow.getPluginsMenu(); + pluginsMenu.removeAll(); + pluginsMenu.setVisible(false); + } + + @Override + public void uiRun(Runnable runnable) { + UiUtils.uiRun(runnable); + } + + @Override + public void addMenuAction(String name, Runnable action) { + ActionHandler item = new ActionHandler(ev -> { + try { + mainWindow.getBackgroundExecutor().execute(name, action); + } catch (Exception e) { + LOG.error("Error running action for menu item: {}", name, e); + } + }); + item.setNameAndDesc(name); + JMenu pluginsMenu = mainWindow.getPluginsMenu(); + pluginsMenu.add(item); + pluginsMenu.setVisible(true); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java b/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java index 35fb68d6c..05275756f 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java @@ -8,6 +8,7 @@ import jadx.api.JavaClass; import jadx.gui.settings.data.TabViewState; import jadx.gui.settings.data.ViewPoint; import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JInputScript; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JResource; import jadx.gui.ui.MainWindow; @@ -52,9 +53,15 @@ public class TabStateViewAdapter { return mw.getCacheObject().getNodeCache().makeFrom(javaClass); } break; + case "resource": JResource tmpNode = new JResource(null, tvs.getTabPath(), JResource.JResType.FILE); return mw.getTreeRoot().searchNode(tmpNode); // equals method in JResource check only name + + case "script": + return mw.getTreeRoot() + .followStaticPath("JInputs", "JInputScripts") + .searchNode(node -> node instanceof JInputScript && node.getName().equals(tvs.getTabPath())); } return null; } @@ -70,6 +77,11 @@ public class TabStateViewAdapter { tvs.setTabPath(node.getName()); return true; } + if (node instanceof JInputScript) { + tvs.setType("script"); + tvs.setTabPath(node.getName()); + return true; + } return false; } } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index ca970af5f..0e9a22a1f 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -2,6 +2,7 @@ package jadx.gui.treemodel; import javax.swing.Icon; import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.jetbrains.annotations.NotNull; @@ -13,8 +14,10 @@ import jadx.api.JavaMethod; import jadx.api.JavaNode; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.info.AccessInfo; +import jadx.gui.ui.MainWindow; import jadx.gui.ui.TabbedPane; import jadx.gui.ui.codearea.ClassCodeContentPanel; +import jadx.gui.ui.dialog.RenameDialog; import jadx.gui.ui.panel.ContentPanel; import jadx.gui.utils.CacheObject; import jadx.gui.utils.NLS; @@ -123,6 +126,11 @@ public class JClass extends JLoadableNode { return SyntaxConstants.SYNTAX_STYLE_JAVA; } + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + return RenameDialog.buildRenamePopup(mainWindow, this); + } + @Override public Icon getIcon() { AccessInfo accessInfo = cls.getAccessInfo(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JEditableNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JEditableNode.java new file mode 100644 index 000000000..8f3164602 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JEditableNode.java @@ -0,0 +1,35 @@ +package jadx.gui.treemodel; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public abstract class JEditableNode extends JNode { + + private volatile boolean changed = false; + private final List> changeListeners = new ArrayList<>(); + + public abstract void save(String newContent); + + @Override + public boolean isEditable() { + return true; + } + + public boolean isChanged() { + return changed; + } + + public void setChanged(boolean changed) { + if (this.changed != changed) { + this.changed = changed; + for (Consumer changeListener : changeListeners) { + changeListener.accept(changed); + } + } + } + + public void addChangeListener(Consumer listener) { + changeListeners.add(listener); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java index 0d03bceda..726fc39ad 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java @@ -4,6 +4,7 @@ import java.util.Comparator; import javax.swing.Icon; import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.jetbrains.annotations.NotNull; @@ -12,6 +13,8 @@ import jadx.api.JavaField; import jadx.api.JavaNode; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.info.AccessInfo; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.dialog.RenameDialog; import jadx.gui.utils.OverlayIcon; import jadx.gui.utils.UiUtils; @@ -54,6 +57,11 @@ public class JField extends JNode { return !field.getFieldNode().contains(AFlag.DONT_RENAME); } + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + return RenameDialog.buildRenamePopup(mainWindow, this); + } + @Override public Icon getIcon() { AccessInfo af = field.getAccessFlags(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFile.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFile.java new file mode 100644 index 000000000..98f576907 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFile.java @@ -0,0 +1,43 @@ +package jadx.gui.treemodel; + +import java.nio.file.Path; + +import javax.swing.Icon; +import javax.swing.JPopupMenu; + +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.Icons; +import jadx.gui.utils.NLS; +import jadx.gui.utils.ui.SimpleMenuItem; + +public class JInputFile extends JNode { + + private final Path filePath; + + public JInputFile(Path filePath) { + this.filePath = filePath; + } + + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + JPopupMenu menu = new JPopupMenu(); + menu.add(new SimpleMenuItem(NLS.str("popup.add_files"), mainWindow::addFiles)); + menu.add(new SimpleMenuItem(NLS.str("popup.remove"), () -> mainWindow.removeInput(filePath))); + return menu; + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return Icons.FILE; + } + + @Override + public String makeString() { + return filePath.getFileName().toString(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFiles.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFiles.java new file mode 100644 index 000000000..023c7bd72 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFiles.java @@ -0,0 +1,45 @@ +package jadx.gui.treemodel; + +import java.nio.file.Path; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; + +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.NLS; +import jadx.gui.utils.UiUtils; +import jadx.gui.utils.ui.SimpleMenuItem; + +public class JInputFiles extends JNode { + private static final ImageIcon INPUT_FILES_ICON = UiUtils.openSvgIcon("nodes/moduleDirectory"); + + public JInputFiles(List files) { + for (Path file : files) { + add(new JInputFile(file)); + } + } + + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + JPopupMenu menu = new JPopupMenu(); + menu.add(new SimpleMenuItem(NLS.str("popup.add_files"), mainWindow::addFiles)); + return menu; + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return INPUT_FILES_ICON; + } + + @Override + public String makeString() { + return NLS.str("tree.input_files"); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java new file mode 100644 index 000000000..b1a8d19b7 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java @@ -0,0 +1,101 @@ +package jadx.gui.treemodel; + +import java.nio.file.Path; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; + +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.ICodeInfo; +import jadx.api.impl.SimpleCodeInfo; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.files.FileUtils; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.codearea.CodeContentPanel; +import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.utils.NLS; +import jadx.gui.utils.UiUtils; +import jadx.gui.utils.ui.SimpleMenuItem; + +public class JInputScript extends JEditableNode { + private static final Logger LOG = LoggerFactory.getLogger(JInputScript.class); + + private static final ImageIcon SCRIPT_ICON = UiUtils.openSvgIcon("nodes/kotlin_script"); + + private final Path scriptPath; + private final String name; + + public JInputScript(Path scriptPath) { + this.scriptPath = scriptPath; + this.name = scriptPath.getFileName().toString().replace(".jadx.kts", ""); + } + + @Override + public ContentPanel getContentPanel(TabbedPane tabbedPane) { + return new CodeContentPanel(tabbedPane, this); + } + + @Override + public @NotNull ICodeInfo getCodeInfo() { + try { + return new SimpleCodeInfo(FileUtils.readFile(scriptPath)); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to read script file: " + scriptPath.toAbsolutePath(), e); + } + } + + @Override + public void save(String newContent) { + try { + FileUtils.writeFile(scriptPath, newContent); + LOG.debug("Script saved: {}", scriptPath.toAbsolutePath()); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to write script file: " + scriptPath.toAbsolutePath(), e); + } + } + + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + JPopupMenu menu = new JPopupMenu(); + menu.add(new SimpleMenuItem(NLS.str("popup.add_scripts"), mainWindow::addFiles)); + menu.add(new SimpleMenuItem(NLS.str("popup.new_script"), mainWindow::addNewScript)); + menu.add(new SimpleMenuItem(NLS.str("popup.remove"), () -> mainWindow.removeInput(scriptPath))); + return menu; + } + + @Override + public boolean isEditable() { + return true; + } + + @Override + public String getSyntaxName() { + return SyntaxConstants.SYNTAX_STYLE_KOTLIN; + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return SCRIPT_ICON; + } + + @Override + public String getName() { + return name; + } + + @Override + public String makeString() { + return name; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScripts.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScripts.java new file mode 100644 index 000000000..eb9cf03f6 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScripts.java @@ -0,0 +1,46 @@ +package jadx.gui.treemodel; + +import java.nio.file.Path; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; + +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.NLS; +import jadx.gui.utils.UiUtils; +import jadx.gui.utils.ui.SimpleMenuItem; + +public class JInputScripts extends JNode { + private static final ImageIcon INPUT_SCRIPTS_ICON = UiUtils.openSvgIcon("nodes/scriptsModel"); + + public JInputScripts(List scripts) { + for (Path script : scripts) { + add(new JInputScript(script)); + } + } + + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + JPopupMenu menu = new JPopupMenu(); + menu.add(new SimpleMenuItem(NLS.str("popup.add_scripts"), mainWindow::addFiles)); + menu.add(new SimpleMenuItem(NLS.str("popup.new_script"), mainWindow::addNewScript)); + return menu; + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return INPUT_SCRIPTS_ICON; + } + + @Override + public String makeString() { + return NLS.str("tree.input_scripts"); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputs.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputs.java new file mode 100644 index 000000000..be8d94392 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputs.java @@ -0,0 +1,50 @@ +package jadx.gui.treemodel; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import jadx.core.utils.files.FileUtils; +import jadx.gui.JadxWrapper; +import jadx.gui.utils.NLS; +import jadx.gui.utils.UiUtils; + +public class JInputs extends JNode { + private static final ImageIcon INPUTS_ICON = UiUtils.openSvgIcon("nodes/projectStructure"); + + public JInputs(JadxWrapper wrapper) { + List inputs = wrapper.getProject().getFilePaths(); + List files = FileUtils.expandDirs(inputs); + List scripts = new ArrayList<>(); + Iterator it = files.iterator(); + while (it.hasNext()) { + Path file = it.next(); + if (file.getFileName().toString().endsWith(".jadx.kts")) { + scripts.add(file); + it.remove(); + } + } + + add(new JInputFiles(files)); + add(new JInputScripts(scripts)); + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return INPUTS_ICON; + } + + @Override + public String makeString() { + return NLS.str("tree.inputs_title"); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java index 95e004c38..15a5557f7 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java @@ -5,6 +5,7 @@ import java.util.Iterator; import javax.swing.Icon; import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.jetbrains.annotations.NotNull; @@ -14,6 +15,8 @@ import jadx.api.JavaNode; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.args.ArgType; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.dialog.RenameDialog; import jadx.gui.utils.Icons; import jadx.gui.utils.OverlayIcon; import jadx.gui.utils.UiUtils; @@ -107,6 +110,11 @@ public class JMethod extends JNode { return !mth.getMethodNode().contains(AFlag.DONT_RENAME); } + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + return RenameDialog.buildRenamePopup(mainWindow, this); + } + String makeBaseString() { if (mth.isClassInit()) { return "{...}"; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 7ab949c25..421377b21 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -1,8 +1,11 @@ package jadx.gui.treemodel; import java.util.Comparator; +import java.util.Enumeration; +import java.util.function.Predicate; import javax.swing.Icon; +import javax.swing.JPopupMenu; import javax.swing.tree.DefaultMutableTreeNode; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; @@ -11,6 +14,7 @@ import org.jetbrains.annotations.Nullable; import jadx.api.ICodeInfo; import jadx.api.JavaNode; +import jadx.gui.ui.MainWindow; import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.ContentPanel; @@ -45,6 +49,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable return ICodeInfo.EMPTY; } + public boolean isEditable() { + return false; + } + public abstract Icon getIcon(); public String getName() { @@ -59,6 +67,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable return false; } + public @Nullable JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + return null; + } + public abstract String makeString(); public String makeStringHtml() { @@ -97,6 +109,17 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable return makeLongStringHtml(); } + public @Nullable JNode searchNode(Predicate filter) { + Enumeration en = this.breadthFirstEnumeration(); + while (en.hasMoreElements()) { + JNode node = (JNode) en.nextElement(); + if (filter.test(node)) { + return node; + } + } + return null; + } + private static final Comparator COMPARATOR = Comparator .comparing(JNode::makeLongString) .thenComparingInt(JNode::getPos); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java index 44c01b50c..ea5f1d317 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java @@ -6,10 +6,13 @@ import java.util.List; import javax.swing.Icon; import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; import jadx.api.JavaPackage; import jadx.core.utils.Utils; import jadx.gui.JadxWrapper; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.popupmenu.JPackagePopupMenu; import jadx.gui.utils.UiUtils; public class JPackage extends JNode { @@ -68,6 +71,11 @@ public class JPackage extends JNode { } } + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + return new JPackagePopupMenu(mainWindow, this); + } + @Override public String getName() { return name; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index 92a056727..49047dae5 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -24,6 +24,7 @@ import jadx.gui.ui.TabbedPane; import jadx.gui.ui.codearea.CodeContentPanel; import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.ImagePanel; +import jadx.gui.utils.Icons; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; import jadx.gui.utils.res.ResTableHelper; @@ -32,8 +33,6 @@ public class JResource extends JLoadableNode { private static final long serialVersionUID = -201018424302612434L; private static final ImageIcon ROOT_ICON = UiUtils.openSvgIcon("nodes/resourcesRoot"); - private static final ImageIcon FOLDER_ICON = UiUtils.openSvgIcon("nodes/folder"); - private static final ImageIcon FILE_ICON = UiUtils.openSvgIcon("nodes/file_any_type"); private static final ImageIcon ARSC_ICON = UiUtils.openSvgIcon("nodes/resourceBundle"); private static final ImageIcon XML_ICON = UiUtils.openSvgIcon("nodes/xml"); private static final ImageIcon IMAGE_ICON = UiUtils.openSvgIcon("nodes/ImagesFileType"); @@ -244,7 +243,7 @@ public class JResource extends JLoadableNode { case ROOT: return ROOT_ICON; case DIR: - return FOLDER_ICON; + return Icons.FOLDER; case FILE: ResourceType resType = resFile.getType(); @@ -266,7 +265,7 @@ public class JResource extends JLoadableNode { } return UNKNOWN_ICON; } - return FILE_ICON; + return Icons.FILE; } public static boolean isSupportedForView(ResourceType type) { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java index cb4e3cb39..02675ecc9 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java @@ -3,17 +3,21 @@ package jadx.gui.treemodel; import java.io.File; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.regex.Pattern; import javax.swing.Icon; import javax.swing.ImageIcon; +import javax.swing.tree.TreeNode; import org.jetbrains.annotations.Nullable; import jadx.api.ResourceFile; +import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.JadxWrapper; +import jadx.gui.settings.JadxProject; import jadx.gui.treemodel.JResource.JResType; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; @@ -35,6 +39,7 @@ public class JRoot extends JNode { public final void update() { removeAllChildren(); + add(new JInputs(wrapper)); add(new JSources(this, wrapper)); List resources = wrapper.getResources(); @@ -87,7 +92,7 @@ public class JRoot extends JNode { return null; } - public JNode searchNode(JNode node) { + public @Nullable JNode searchNode(JNode node) { Enumeration en = this.breadthFirstEnumeration(); while (en.hasMoreElements()) { Object obj = en.nextElement(); @@ -98,6 +103,30 @@ public class JRoot extends JNode { return null; } + public JNode followStaticPath(String... path) { + List list = Arrays.asList(path); + JNode node = getNodeByClsPath(this, 0, list); + if (node == null) { + throw new JadxRuntimeException("Incorrect static path in tree: " + list); + } + return node; + } + + private static @Nullable JNode getNodeByClsPath(JNode start, int pos, List path) { + if (pos >= path.size()) { + return start; + } + String clsName = path.get(pos); + Enumeration en = start.children(); + while (en.hasMoreElements()) { + JNode node = (JNode) en.nextElement(); + if (node.getClass().getSimpleName().equals(clsName)) { + return getNodeByClsPath(node, pos + 1, path); + } + } + return null; + } + public boolean isFlatPackages() { return flatPackages; } @@ -134,7 +163,11 @@ public class JRoot extends JNode { @Override public String makeString() { - List paths = wrapper.getProject().getFilePaths(); + JadxProject project = wrapper.getProject(); + if (project.getProjectPath() != null) { + return project.getName(); + } + List paths = project.getFilePaths(); int count = paths.size(); if (count == 0) { return "File not open"; 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 9f4b51b6c..d71a10fd6 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -87,6 +87,7 @@ import jadx.api.JavaNode; import jadx.api.ResourceFile; import jadx.api.plugins.utils.CommonFileUtils; import jadx.core.Jadx; +import jadx.core.export.TemplateFile; import jadx.core.utils.ListUtils; import jadx.core.utils.StringUtils; import jadx.core.utils.files.FileUtils; @@ -103,9 +104,7 @@ import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettingsWindow; import jadx.gui.treemodel.ApkSignature; import jadx.gui.treemodel.JClass; -import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JLoadableNode; -import jadx.gui.treemodel.JMethod; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JPackage; import jadx.gui.treemodel.JResource; @@ -117,7 +116,6 @@ import jadx.gui.ui.codearea.EditorViewState; import jadx.gui.ui.dialog.ADBDialog; import jadx.gui.ui.dialog.AboutDialog; import jadx.gui.ui.dialog.LogViewerDialog; -import jadx.gui.ui.dialog.RenameDialog; import jadx.gui.ui.dialog.SearchDialog; import jadx.gui.ui.filedialog.FileDialogWrapper; import jadx.gui.ui.filedialog.FileOpenMode; @@ -125,7 +123,6 @@ import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.IssuesPanel; import jadx.gui.ui.panel.JDebuggerPanel; import jadx.gui.ui.panel.ProgressPanel; -import jadx.gui.ui.popupmenu.JPackagePopupMenu; import jadx.gui.ui.treenodes.StartPageNode; import jadx.gui.ui.treenodes.SummaryNode; import jadx.gui.update.JadxUpdate; @@ -215,9 +212,11 @@ public class MainWindow extends JFrame { private JDebuggerPanel debuggerPanel; private JSplitPane verticalSplitter; - private List loadListeners = new ArrayList<>(); + private final List loadListeners = new ArrayList<>(); private boolean loaded; + private JMenu pluginsMenu; + public MainWindow(JadxSettings settings) { this.settings = settings; this.cacheObject = new CacheObject(); @@ -403,6 +402,40 @@ public class MainWindow extends JFrame { s -> update()); } + public void addNewScript() { + FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.CUSTOM_SAVE); + fileDialog.setTitle(NLS.str("file.save")); + Path workingDir = project.getWorkingDir(); + Path baseDir = workingDir != null ? workingDir : settings.getLastSaveFilePath(); + fileDialog.setSelectedFile(baseDir.resolve("script.jadx.kts")); + fileDialog.setFileExtList(Collections.singletonList("jadx.kts")); + fileDialog.setSelectionMode(JFileChooser.FILES_ONLY); + List paths = fileDialog.show(); + if (paths.size() != 1) { + return; + } + Path scriptFile = paths.get(0); + try { + TemplateFile tmpl = TemplateFile.fromResources("/files/script.jadx.kts.tmpl"); + FileUtils.writeFile(scriptFile, tmpl.build()); + } catch (Exception e) { + LOG.error("Failed to save new script file: {}", scriptFile, e); + } + List inputs = project.getFilePaths(); + inputs.add(scriptFile); + project.setFilePaths(inputs); + project.save(); + reopen(); + } + + public void removeInput(Path file) { + List inputs = project.getFilePaths(); + inputs.remove(file); + project.setFilePaths(inputs); + project.save(); + reopen(); + } + public void open(Path path) { open(Collections.singletonList(path), EMPTY_RUNNABLE); } @@ -746,15 +779,12 @@ public class MainWindow extends JFrame { } private void treeRightClickAction(MouseEvent e) { - JNode obj = getJNodeUnderMouse(e); - if (obj instanceof JPackage) { - JPackagePopupMenu menu = new JPackagePopupMenu(this, (JPackage) obj); - menu.show(e.getComponent(), e.getX(), e.getY()); - } else if (obj instanceof JClass || obj instanceof JField || obj instanceof JMethod) { - JMenuItem jmi = new JMenuItem(NLS.str("popup.rename")); - jmi.addActionListener(action -> RenameDialog.rename(this, obj)); - JPopupMenu menu = new JPopupMenu(); - menu.add(jmi); + JNode node = getJNodeUnderMouse(e); + if (node == null) { + return; + } + JPopupMenu menu = node.onTreePopupMenu(this); + if (menu != null) { menu.show(e.getComponent(), e.getX(), e.getY()); } } @@ -903,7 +933,7 @@ public class MainWindow extends JFrame { } }; saveAllAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_all")); - saveAllAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_S, UiUtils.ctrlButton())); + saveAllAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, UiUtils.ctrlButton())); Action exportAction = new AbstractAction(NLS.str("file.export_gradle"), ICON_EXPORT) { @Override @@ -912,7 +942,7 @@ public class MainWindow extends JFrame { } }; exportAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.export_gradle")); - exportAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, UiUtils.ctrlButton())); + exportAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)); JMenu recentProjects = new JMenu(NLS.str("menu.recent_projects")); recentProjects.addMenuListener(new RecentProjectsMenuListener(recentProjects)); @@ -1117,6 +1147,10 @@ public class MainWindow extends JFrame { nav.add(backAction); nav.add(forwardAction); + pluginsMenu = new JMenu(NLS.str("menu.plugins")); + pluginsMenu.setMnemonic(KeyEvent.VK_P); + pluginsMenu.setVisible(false); + JMenu tools = new JMenu(NLS.str("menu.tools")); tools.setMnemonic(KeyEvent.VK_T); tools.add(decompileAllAction); @@ -1142,6 +1176,7 @@ public class MainWindow extends JFrame { menuBar.add(view); menuBar.add(nav); menuBar.add(tools); + menuBar.add(pluginsMenu); menuBar.add(help); setJMenuBar(menuBar); @@ -1614,4 +1649,8 @@ public class MainWindow extends JFrame { public void menuCanceled(MenuEvent e) { } } + + public JMenu getPluginsMenu() { + return pluginsMenu; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java b/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java index db8c9d793..335443f7b 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java @@ -18,6 +18,7 @@ import javax.swing.SwingUtilities; import javax.swing.plaf.basic.BasicButtonUI; import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JEditableNode; import jadx.gui.treemodel.JNode; import jadx.gui.ui.panel.ContentPanel; import jadx.gui.utils.Icons; @@ -53,13 +54,7 @@ public class TabComponent extends JPanel { setOpaque(false); JNode node = contentPanel.getNode(); - String tabTitle; - if (node.getRootClass() != null) { - tabTitle = node.getRootClass().getName(); - } else { - tabTitle = node.makeLongStringHtml(); - } - label = new NodeLabel(tabTitle, node.disableHtml()); + label = new NodeLabel(buildTabTitle(node), node.disableHtml()); label.setFont(getLabelFont()); String toolTip = contentPanel.getTabTooltip(); if (toolTip != null) { @@ -67,6 +62,9 @@ public class TabComponent extends JPanel { } label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10)); label.setIcon(node.getIcon()); + if (node instanceof JEditableNode) { + ((JEditableNode) node).addChangeListener(c -> label.setText(buildTabTitle(node))); + } final JButton closeBtn = new JButton(); closeBtn.setIcon(Icons.CLOSE_INACTIVE); @@ -104,6 +102,21 @@ public class TabComponent extends JPanel { setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); } + private String buildTabTitle(JNode node) { + String tabTitle; + if (node.getRootClass() != null) { + tabTitle = node.getRootClass().getName(); + } else { + tabTitle = node.makeLongStringHtml(); + } + if (node instanceof JEditableNode) { + if (((JEditableNode) node).isChanged()) { + return "*" + tabTitle; + } + } + return tabTitle; + } + private JPopupMenu createTabPopupMenu(final ContentPanel contentPanel) { JPopupMenu menu = new JPopupMenu(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java index 8951beebd..ba093c81e 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java @@ -46,6 +46,7 @@ import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.settings.JadxSettings; import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JEditableNode; import jadx.gui.treemodel.JNode; import jadx.gui.ui.MainWindow; import jadx.gui.ui.panel.ContentPanel; @@ -53,6 +54,7 @@ import jadx.gui.utils.DefaultPopupMenuListener; import jadx.gui.utils.JumpPosition; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; +import jadx.gui.utils.ui.DocumentUpdateListener; import jadx.gui.utils.ui.ZoomActions; public abstract class AbstractCodeArea extends RSyntaxTextArea { @@ -75,12 +77,14 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea { protected ContentPanel contentPanel; protected JNode node; + protected volatile boolean loaded = false; + public AbstractCodeArea(ContentPanel contentPanel, JNode node) { this.contentPanel = contentPanel; this.node = Objects.requireNonNull(node); setMarkOccurrences(false); - setEditable(false); + setEditable(node.isEditable()); setCodeFoldingEnabled(false); setFadeCurrentLineHighlight(true); setCloseCurlyBraces(true); @@ -91,10 +95,16 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea { setLineWrap(settings.isCodeAreaLineWrap()); addWrapLineMenuAction(settings); - addCaretActions(); - addFastCopyAction(); - ZoomActions.register(this, settings, this::loadSettings); + + if (node instanceof JEditableNode) { + JEditableNode editableNode = (JEditableNode) node; + addSaveActions(editableNode); + addChangeUpdates(editableNode); + } else { + addCaretActions(); + addFastCopyAction(); + } } private void addWrapLineMenuAction(JadxSettings settings) { @@ -186,6 +196,26 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea { }); } + private void addSaveActions(JEditableNode node) { + addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_S && UiUtils.isCtrlDown(e)) { + node.save(AbstractCodeArea.this.getText()); + node.setChanged(false); + } + } + }); + } + + private void addChangeUpdates(JEditableNode editableNode) { + getDocument().addDocumentListener(new DocumentUpdateListener(ev -> { + if (loaded) { + editableNode.setChanged(true); + } + })); + } + private String highlightCaretWord(String lastText, int pos) { String text = getWordByPosition(pos); if (StringUtils.isEmpty(text)) { @@ -244,9 +274,14 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea { /** * Implement in this method the code that loads and sets the content to be displayed + * Call `setLoaded()` on load finish. */ public abstract void load(); + public void setLoaded() { + this.loaded = true; + } + /** * Implement in this method the code that reloads node from cache and sets the new content to be * displayed diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java index e75eb6147..264d48a24 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java @@ -99,6 +99,7 @@ public final class CodeArea extends AbstractCodeArea { if (getText().isEmpty()) { setText(getCodeInfo().getCodeStr()); setCaretPosition(0); + setLoaded(); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java index 876ad4e82..913d17d16 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java @@ -3,6 +3,9 @@ package jadx.gui.ui.codearea; import java.awt.BorderLayout; import java.awt.Point; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import jadx.gui.treemodel.JNode; import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.IViewStateSupport; @@ -10,6 +13,8 @@ import jadx.gui.ui.panel.IViewStateSupport; public final class CodeContentPanel extends AbstractCodeContentPanel implements IViewStateSupport { private static final long serialVersionUID = 5310536092010045565L; + private static final Logger LOG = LoggerFactory.getLogger(CodeContentPanel.class); + private final CodePanel codePanel; public CodeContentPanel(TabbedPane panel, JNode jnode) { @@ -59,8 +64,12 @@ public final class CodeContentPanel extends AbstractCodeContentPanel implements @Override public void restoreEditorViewState(EditorViewState viewState) { - codePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint()); - codePanel.getCodeArea().setCaretPosition(viewState.getCaretPos()); + try { + codePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint()); + codePanel.getCodeArea().setCaretPosition(viewState.getCaretPos()); + } catch (Exception e) { + LOG.error("Failed to restore view state", e); + } } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java index 0135f3919..a996670ca 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java @@ -93,6 +93,7 @@ public final class SmaliArea extends AbstractCodeArea { curVersion = shouldUseSmaliPrinterV2(); model.load(); setCaretPosition(0); + setLoaded(); } } 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 63e0a49f7..d02fa3ed0 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 @@ -20,7 +20,9 @@ import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; +import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JTextField; import javax.swing.WindowConstants; @@ -84,6 +86,14 @@ public class RenameDialog extends JDialog { return true; } + public static JPopupMenu buildRenamePopup(MainWindow mainWindow, JNode node) { + JMenuItem jmi = new JMenuItem(NLS.str("popup.rename")); + jmi.addActionListener(action -> RenameDialog.rename(mainWindow, node)); + JPopupMenu menu = new JPopupMenu(); + menu.add(jmi); + return menu; + } + private RenameDialog(MainWindow mainWindow, JNode source, JNode node) { super(mainWindow); this.mainWindow = mainWindow; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java b/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java index 407659de3..afd55c6e7 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java @@ -16,6 +16,9 @@ import jadx.gui.utils.NLS; public class FileDialogWrapper { + private static final List OPEN_FILES_EXTS = Arrays.asList( + "apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc", "jadx.kts"); + private final MainWindow mainWindow; private boolean isOpen; @@ -60,21 +63,27 @@ public class FileDialogWrapper { private void initForMode(FileOpenMode mode) { switch (mode) { - case OPEN: case OPEN_PROJECT: + title = NLS.str("file.open_title"); + fileExtList = Collections.singletonList(JadxProject.PROJECT_EXTENSION); + selectionMode = JFileChooser.FILES_AND_DIRECTORIES; + currentDir = mainWindow.getSettings().getLastOpenFilePath(); + isOpen = true; + break; + + case OPEN: + title = NLS.str("file.open_title"); + fileExtList = new ArrayList<>(OPEN_FILES_EXTS); + fileExtList.add(JadxProject.PROJECT_EXTENSION); + fileExtList.add("aab"); + selectionMode = JFileChooser.FILES_AND_DIRECTORIES; + currentDir = mainWindow.getSettings().getLastOpenFilePath(); + isOpen = true; + break; + case ADD: - if (mode == FileOpenMode.OPEN_PROJECT) { - fileExtList = Collections.singletonList(JadxProject.PROJECT_EXTENSION); - title = NLS.str("file.open_title"); - } else { - fileExtList = new ArrayList<>(Arrays.asList("apk", "dex", "jar", "class", "smali", "zip", "xapk", "aar", "arsc")); - if (mode == FileOpenMode.OPEN) { - fileExtList.addAll(Arrays.asList(JadxProject.PROJECT_EXTENSION, "aab")); - title = NLS.str("file.open_title"); - } else { - title = NLS.str("file.add_files_action"); - } - } + title = NLS.str("file.add_files_action"); + fileExtList = OPEN_FILES_EXTS; selectionMode = JFileChooser.FILES_AND_DIRECTORIES; currentDir = mainWindow.getSettings().getLastOpenFilePath(); isOpen = true; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Icons.java b/jadx-gui/src/main/java/jadx/gui/utils/Icons.java index 2e31e168b..5dc6f9a47 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Icons.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/Icons.java @@ -17,4 +17,7 @@ public class Icons { public static final ImageIcon FINAL = openSvgIcon("nodes/finalMark"); public static final ImageIcon START_PAGE = openSvgIcon("nodes/newWindow"); + + public static final ImageIcon FOLDER = UiUtils.openSvgIcon("nodes/folder"); + public static final ImageIcon FILE = UiUtils.openSvgIcon("nodes/file_any_type"); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/ui/DocumentUpdateListener.java b/jadx-gui/src/main/java/jadx/gui/utils/ui/DocumentUpdateListener.java index aefb41fef..3696f4b5a 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/ui/DocumentUpdateListener.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/ui/DocumentUpdateListener.java @@ -25,6 +25,6 @@ public class DocumentUpdateListener implements DocumentListener { @Override public void changedUpdate(DocumentEvent event) { - this.listener.accept(event); + // ignore attributes change } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/ui/SimpleMenuItem.java b/jadx-gui/src/main/java/jadx/gui/utils/ui/SimpleMenuItem.java new file mode 100644 index 000000000..7ac4a70b9 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/ui/SimpleMenuItem.java @@ -0,0 +1,11 @@ +package jadx.gui.utils.ui; + +import javax.swing.JMenuItem; + +public class SimpleMenuItem extends JMenuItem { + + public SimpleMenuItem(String text, Runnable action) { + super(text); + addActionListener(ev -> action.run()); + } +} diff --git a/jadx-gui/src/main/resources/files/script.jadx.kts.tmpl b/jadx-gui/src/main/resources/files/script.jadx.kts.tmpl new file mode 100644 index 000000000..ba60c3962 --- /dev/null +++ b/jadx-gui/src/main/resources/files/script.jadx.kts.tmpl @@ -0,0 +1,5 @@ +val jadx = getJadxInstance() + +jadx.afterLoad { + log.info { "Hello from jadx script!" } +} 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 425d7a120..3df4c65f8 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -14,6 +14,7 @@ menu.text_search=Textsuche menu.class_search=Klassen-Suche menu.comment_search=Kommentar suchen menu.tools=Tools +#menu.plugins=Plugins #menu.decompile_all=Decompile all classes menu.deobfuscation=Deobfuskierung menu.log=Log-Anzeige @@ -33,6 +34,7 @@ file.live_reload=Live nachladen file.live_reload_desc=Dateien bei Änderungen autom. neuladen file.export_mappings_as=Zuordnungen exportieren als… file.save_all=Alles speichern +#file.save=Save file.export_gradle=Als Gradle-Projekt speichern file.save_all_msg=Verzeichnis für das Speichern dekompilierter Ressourcen auswählen file.exit=Beenden @@ -41,6 +43,9 @@ file.exit=Beenden #start_page.start=Start #start_page.recent=Recent projects +#tree.inputs_title=Inputs +#tree.input_files=Files +#tree.input_scripts=Scripts tree.sources_title=Quelltexte tree.resources_title=Ressourcen tree.loading=Laden… @@ -234,6 +239,10 @@ popup.search_comment=Kommentar suchen popup.rename=Umbennen popup.search=Suche "%s" popup.search_global=Globale Suche "%s" +#popup.remove=Remove +#popup.add_files=Add files +#popup.add_scripts=Add scripts +#popup.new_script=New script exclude_dialog.title=Paketauswahl exclude_dialog.ok=OK 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 dd3bb291b..b679179f9 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -14,6 +14,7 @@ menu.text_search=Text search menu.class_search=Class search menu.comment_search=Comment searchF menu.tools=Tools +menu.plugins=Plugins menu.decompile_all=Decompile all classes menu.deobfuscation=Deobfuscation menu.log=Log Viewer @@ -33,6 +34,7 @@ file.live_reload=Live reload file.live_reload_desc=Auto reload files on changes file.export_mappings_as=Export mappings as... file.save_all=Save all +file.save=Save file.export_gradle=Save as gradle project file.save_all_msg=Select directory for save decompiled sources file.exit=Exit @@ -41,6 +43,9 @@ start_page.title=Start page start_page.start=Start start_page.recent=Recent projects +tree.inputs_title=Inputs +tree.input_files=Files +tree.input_scripts=Scripts tree.sources_title=Source code tree.resources_title=Resources tree.loading=Loading... @@ -234,6 +239,10 @@ popup.search_comment=Search comments popup.rename=Rename popup.search=Search "%s" popup.search_global=Global Search "%s" +popup.remove=Remove +popup.add_files=Add files +popup.add_scripts=Add scripts +popup.new_script=New script exclude_dialog.title=Package Selector exclude_dialog.ok=OK 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 aee3fad68..45b463f1f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -14,6 +14,7 @@ menu.text_search=Buscar texto menu.class_search=Buscar clase #menu.comment_search=Comment search menu.tools=Herramientas +#menu.plugins=Plugins #menu.decompile_all=Decompile all classes menu.deobfuscation=Desofuscación menu.log=Visor log @@ -33,6 +34,7 @@ file.open_title=Abrir archivo #file.live_reload_desc=Auto reload files on changes #file.export_mappings_as= file.save_all=Guardar todo +#file.save=Save file.export_gradle=Guardar como proyecto Gradle file.save_all_msg=Seleccionar carpeta para guardar fuentes descompiladas file.exit=Salir @@ -41,6 +43,9 @@ file.exit=Salir #start_page.start=Start #start_page.recent=Recent projects +#tree.inputs_title=Inputs +#tree.input_files=Files +#tree.input_scripts=Scripts tree.sources_title=Código fuente tree.resources_title=Recursos tree.loading=Cargando... @@ -234,6 +239,10 @@ popup.xposed=Copiar como fragmento de xposed popup.rename=Nimeta ümber #popup.search= #popup.search_global= +#popup.remove=Remove +#popup.add_files=Add files +#popup.add_scripts=Add scripts +#popup.new_script=New script #exclude_dialog.title=Package Selector #exclude_dialog.ok=OK 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 e77473175..14e9f6e9f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -14,6 +14,7 @@ menu.text_search=텍스트 검색 menu.class_search=클래스 검색 menu.comment_search=주석 검색 menu.tools=도구 +#menu.plugins=Plugins #menu.decompile_all=Decompile all classes menu.deobfuscation=난독화 해제 menu.log=로그 뷰어 @@ -33,6 +34,7 @@ file.live_reload=라이브 로드 file.live_reload_desc=파일 내용 변경 시 자동으로 다시 로드 file.export_mappings_as=다른 이름으로 매핑 내보내기... file.save_all=모두 저장 +#file.save=Save file.export_gradle=Gradle 프로젝트로 저장 file.save_all_msg=디컴파일된 소스를 저장할 디렉토리 선택 file.exit=나가기 @@ -41,6 +43,9 @@ start_page.title=페이지 시작 start_page.start=시작 start_page.recent=최근 프로젝트 +#tree.inputs_title=Inputs +#tree.input_files=Files +#tree.input_scripts=Scripts tree.sources_title=소스코드 tree.resources_title=리소스 tree.loading=로딩중... @@ -234,6 +239,10 @@ popup.search_comment=주석 검색 popup.rename=이름 바꾸기 popup.search="%s" 검색 popup.search_global="%s" 전역 검색 +#popup.remove=Remove +#popup.add_files=Add files +#popup.add_scripts=Add scripts +#popup.new_script=New script exclude_dialog.title=패키지 선택기 exclude_dialog.ok=확인 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 2cfe2c9b1..d04d0dafa 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -14,6 +14,7 @@ menu.text_search=Buscar por texto menu.class_search=Buscar por classe menu.comment_search=Busca por comentário menu.tools=Ferramentas +#menu.plugins=Plugins #menu.decompile_all=Decompile all classes menu.deobfuscation=Desofuscar menu.log=Visualizador de log @@ -33,6 +34,7 @@ file.live_reload=Recarregar em tempo real file.live_reload_desc=Recarregar arquivos automaticamente ao serem alterados file.export_mappings_as=Exportar mappings como... file.save_all=Salvar tudo +#file.save=Save file.export_gradle=Salvar como um projeto gradle file.save_all_msg=Selecionar diretório para salvar arquivos descompilados file.exit=Sair @@ -41,6 +43,9 @@ start_page.title=Página inicial start_page.start=Começar start_page.recent=Projetos recentes +#tree.inputs_title=Inputs +#tree.input_files=Files +#tree.input_scripts=Scripts tree.sources_title=Código fonte tree.resources_title=Recursos tree.loading=Carregando... @@ -234,6 +239,10 @@ popup.search_comment=Buscar comentários popup.rename=Renomear popup.search=Buscar "%s" popup.search_global=Busca global "%s" +#popup.remove=Remove +#popup.add_files=Add files +#popup.add_scripts=Add scripts +#popup.new_script=New script exclude_dialog.title=Selecionar pacote exclude_dialog.ok=OK 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 2074a2588..ac8a05d7e 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -14,6 +14,7 @@ menu.text_search=文本搜索 menu.class_search=类名搜索 menu.comment_search=注释搜索 menu.tools=工具 +#menu.plugins=Plugins menu.decompile_all=反编译所有类 menu.deobfuscation=反混淆 menu.log=日志查看器 @@ -33,6 +34,7 @@ file.live_reload=实时重加载 file.live_reload_desc=文件变动时自动重载 file.export_mappings_as=导出映射为… file.save_all=全部保存 +#file.save=Save file.export_gradle=另存为 Gradle 项目 file.save_all_msg=请选择保存反编译资源的目录 file.exit=退出 @@ -41,6 +43,9 @@ start_page.title=开始页面 start_page.start=开始 start_page.recent=最近项目 +#tree.inputs_title=Inputs +#tree.input_files=Files +#tree.input_scripts=Scripts tree.sources_title=源代码 tree.resources_title=资源文件 tree.loading=加载中… @@ -234,6 +239,10 @@ popup.search_comment=搜索注释 popup.rename=重命名 popup.search=搜索 “%s” popup.search_global=全局搜索 “%s” +#popup.remove=Remove +#popup.add_files=Add files +#popup.add_scripts=Add scripts +#popup.new_script=New script exclude_dialog.title=选择要排除的包 exclude_dialog.ok=确定 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 72676c80f..ce15f0b2e 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -14,6 +14,7 @@ menu.text_search=文字搜尋 menu.class_search=類別搜尋 menu.comment_search=註解搜尋 menu.tools=工具 +#menu.plugins=Plugins #menu.decompile_all=Decompile all classes menu.deobfuscation=去模糊化 menu.log=日誌檢視器 @@ -33,6 +34,7 @@ file.live_reload=實時重新載入 file.live_reload_desc=更動後自動重新載入檔案 file.export_mappings_as=匯出對應為... file.save_all=全部儲存 +#file.save=Save file.export_gradle=另存為 gradle 專案 file.save_all_msg=選擇儲存反編譯原始碼的路徑 file.exit=離開 @@ -41,6 +43,9 @@ start_page.title=開始頁面 start_page.start=開始 start_page.recent=近期專案 +#tree.inputs_title=Inputs +#tree.input_files=Files +#tree.input_scripts=Scripts tree.sources_title=原始碼 tree.resources_title=資源 tree.loading=載入中... @@ -234,6 +239,10 @@ popup.search_comment=搜尋註解 popup.rename=重新命名 popup.search=搜尋 "%s" popup.search_global=全域搜尋 "%s" +#popup.remove=Remove +#popup.add_files=Add files +#popup.add_scripts=Add scripts +#popup.new_script=New script exclude_dialog.title=套件選擇 exclude_dialog.ok=OK diff --git a/jadx-gui/src/main/resources/icons/nodes/kotlin_script.svg b/jadx-gui/src/main/resources/icons/nodes/kotlin_script.svg new file mode 100644 index 000000000..91b874463 --- /dev/null +++ b/jadx-gui/src/main/resources/icons/nodes/kotlin_script.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/jadx-gui/src/main/resources/icons/nodes/moduleDirectory.svg b/jadx-gui/src/main/resources/icons/nodes/moduleDirectory.svg new file mode 100644 index 000000000..d1690ceb8 --- /dev/null +++ b/jadx-gui/src/main/resources/icons/nodes/moduleDirectory.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/jadx-gui/src/main/resources/icons/nodes/projectStructure.svg b/jadx-gui/src/main/resources/icons/nodes/projectStructure.svg new file mode 100644 index 000000000..0d2df7f89 --- /dev/null +++ b/jadx-gui/src/main/resources/icons/nodes/projectStructure.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/jadx-gui/src/main/resources/icons/nodes/scriptsModel.svg b/jadx-gui/src/main/resources/icons/nodes/scriptsModel.svg new file mode 100644 index 000000000..f2c1c9154 --- /dev/null +++ b/jadx-gui/src/main/resources/icons/nodes/scriptsModel.svg @@ -0,0 +1,8 @@ + + + + + + + +