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 2f2c982f2..71bde0a93 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java @@ -1,12 +1,10 @@ package jadx.gui.treemodel; import java.util.Comparator; -import java.util.Iterator; import java.util.List; import java.util.Set; import javax.swing.Icon; -import javax.swing.ImageIcon; import javax.swing.JPopupMenu; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; @@ -20,22 +18,14 @@ import jadx.api.data.impl.JadxNodeRef; import jadx.api.metadata.ICodeNodeRef; import jadx.core.deobf.NameMapper; 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.cellrenders.MethodRenderHelper; import jadx.gui.ui.dialog.RenameDialog; -import jadx.gui.utils.Icons; -import jadx.gui.utils.OverlayIcon; import jadx.gui.utils.UiUtils; public class JMethod extends JNode implements JRenameNode { private static final long serialVersionUID = 3834526867464663751L; - private static final ImageIcon ICON_METHOD_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractMethod"); - private static final ImageIcon ICON_METHOD_PRIVATE = UiUtils.openSvgIcon("nodes/privateMethod"); - private static final ImageIcon ICON_METHOD_PROTECTED = UiUtils.openSvgIcon("nodes/protectedMethod"); - private static final ImageIcon ICON_METHOD_PUBLIC = UiUtils.openSvgIcon("nodes/publicMethod"); - private static final ImageIcon ICON_METHOD_CONSTRUCTOR = UiUtils.openSvgIcon("nodes/constructorMethod"); - private static final ImageIcon ICON_METHOD_SYNC = UiUtils.openSvgIcon("nodes/methodReference"); private final transient JavaMethod mth; private final transient JClass jParent; @@ -78,36 +68,7 @@ public class JMethod extends JNode implements JRenameNode { @Override public Icon getIcon() { - AccessInfo accessFlags = mth.getAccessFlags(); - Icon icon = Icons.METHOD; - if (accessFlags.isAbstract()) { - icon = ICON_METHOD_ABSTRACT; - } - if (accessFlags.isConstructor()) { - icon = ICON_METHOD_CONSTRUCTOR; - } - if (accessFlags.isPublic()) { - icon = ICON_METHOD_PUBLIC; - } - if (accessFlags.isPrivate()) { - icon = ICON_METHOD_PRIVATE; - } - if (accessFlags.isProtected()) { - icon = ICON_METHOD_PROTECTED; - } - if (accessFlags.isSynchronized()) { - icon = ICON_METHOD_SYNC; - } - - OverlayIcon overIcon = new OverlayIcon(icon); - if (accessFlags.isFinal()) { - overIcon.add(Icons.FINAL); - } - if (accessFlags.isStatic()) { - overIcon.add(Icons.STATIC); - } - - return overIcon; + return MethodRenderHelper.getIcon(mth); } @Override @@ -121,24 +82,7 @@ public class JMethod extends JNode implements JRenameNode { } String makeBaseString() { - if (mth.isClassInit()) { - return "{...}"; - } - StringBuilder base = new StringBuilder(); - if (mth.isConstructor()) { - base.append(mth.getDeclaringClass().getName()); - } else { - base.append(mth.getName()); - } - base.append('('); - for (Iterator it = mth.getArguments().iterator(); it.hasNext();) { - base.append(UiUtils.typeStr(it.next())); - if (it.hasNext()) { - base.append(", "); - } - } - base.append(')'); - return base.toString(); + return MethodRenderHelper.makeBaseString(mth); } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/ui/action/FridaAction.java b/jadx-gui/src/main/java/jadx/gui/ui/action/FridaAction.java index bab39ed29..028c1f236 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/action/FridaAction.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/action/FridaAction.java @@ -5,6 +5,7 @@ import java.util.Objects; import java.util.stream.Collectors; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; import org.apache.commons.text.StringEscapeUtils; import org.slf4j.Logger; @@ -18,12 +19,14 @@ import jadx.core.codegen.TypeGen; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.MethodNode; +import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JMethod; import jadx.gui.treemodel.JNode; import jadx.gui.ui.codearea.CodeArea; +import jadx.gui.ui.dialog.MethodsDialog; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; @@ -38,9 +41,7 @@ public final class FridaAction extends JNodeAction { @Override public void runAction(JNode node) { try { - String fridaSnippet = generateFridaSnippet(node); - LOG.info("Frida snippet:\n{}", fridaSnippet); - UiUtils.copyToClipboard(fridaSnippet); + generateFridaSnippet(node); } catch (Exception e) { LOG.error("Failed to generate Frida code snippet", e); JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"), @@ -53,17 +54,27 @@ public final class FridaAction extends JNodeAction { return node instanceof JMethod || node instanceof JClass || node instanceof JField; } - private String generateFridaSnippet(JNode node) { + private void generateFridaSnippet(JNode node) { + String fridaSnippet; if (node instanceof JMethod) { - return generateMethodSnippet((JMethod) node); + fridaSnippet = generateMethodSnippet((JMethod) node); + copySnipped(fridaSnippet); + } else if (node instanceof JField) { + fridaSnippet = generateFieldSnippet((JField) node); + copySnipped(fridaSnippet); + } else if (node instanceof JClass) { + SwingUtilities.invokeLater(() -> showMethodSelectionDialog((JClass) node)); + } else { + throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null")); } - if (node instanceof JClass) { - return generateClassAllMethodSnippet((JClass) node); + + } + + private void copySnipped(String fridaSnippet) { + if (!StringUtils.isEmpty(fridaSnippet)) { + LOG.info("Frida snippet:\n{}", fridaSnippet); + UiUtils.copyToClipboard(fridaSnippet); } - if (node instanceof JField) { - return generateFieldSnippet((JField) node); - } - throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null")); } private String generateMethodSnippet(JMethod jMth) { @@ -129,13 +140,20 @@ public final class FridaAction extends JNodeAction { return String.format("let %s = Java.use(\"%s\");", shortClassName, rawClassName); } - private String generateClassAllMethodSnippet(JClass jc) { + private void showMethodSelectionDialog(JClass jc) { JavaClass javaClass = jc.getCls(); - String result = ""; - for (JavaMethod javaMethod : javaClass.getMethods()) { - result = result + generateMethodSnippet(javaMethod, jc) + "\n"; + new MethodsDialog(getCodeArea().getMainWindow(), javaClass.getMethods(), (result) -> { + String fridaSnippet = generateClassAllMethodSnippet(jc, result); + copySnipped(fridaSnippet); + }); + } + + private String generateClassAllMethodSnippet(JClass jc, List methodList) { + StringBuilder result = new StringBuilder(); + for (JavaMethod javaMethod : methodList) { + result.append(generateMethodSnippet(javaMethod, jc)).append("\n"); } - return result; + return result.toString(); } private String generateFieldSnippet(JField jf) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/cellrenders/MethodRenderHelper.java b/jadx-gui/src/main/java/jadx/gui/ui/cellrenders/MethodRenderHelper.java new file mode 100644 index 000000000..ddd0255fb --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/cellrenders/MethodRenderHelper.java @@ -0,0 +1,77 @@ +package jadx.gui.ui.cellrenders; + +import java.util.Iterator; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import jadx.api.JavaMethod; +import jadx.core.dex.info.AccessInfo; +import jadx.core.dex.instructions.args.ArgType; +import jadx.gui.utils.Icons; +import jadx.gui.utils.OverlayIcon; +import jadx.gui.utils.UiUtils; + +public class MethodRenderHelper { + + private static final ImageIcon ICON_METHOD_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractMethod"); + private static final ImageIcon ICON_METHOD_PRIVATE = UiUtils.openSvgIcon("nodes/privateMethod"); + private static final ImageIcon ICON_METHOD_PROTECTED = UiUtils.openSvgIcon("nodes/protectedMethod"); + private static final ImageIcon ICON_METHOD_PUBLIC = UiUtils.openSvgIcon("nodes/publicMethod"); + private static final ImageIcon ICON_METHOD_CONSTRUCTOR = UiUtils.openSvgIcon("nodes/constructorMethod"); + private static final ImageIcon ICON_METHOD_SYNC = UiUtils.openSvgIcon("nodes/methodReference"); + + public static Icon getIcon(JavaMethod mth) { + AccessInfo accessFlags = mth.getAccessFlags(); + Icon icon = Icons.METHOD; + if (accessFlags.isAbstract()) { + icon = ICON_METHOD_ABSTRACT; + } + if (accessFlags.isConstructor()) { + icon = ICON_METHOD_CONSTRUCTOR; + } + if (accessFlags.isPublic()) { + icon = ICON_METHOD_PUBLIC; + } + if (accessFlags.isPrivate()) { + icon = ICON_METHOD_PRIVATE; + } + if (accessFlags.isProtected()) { + icon = ICON_METHOD_PROTECTED; + } + if (accessFlags.isSynchronized()) { + icon = ICON_METHOD_SYNC; + } + + OverlayIcon overIcon = new OverlayIcon(icon); + if (accessFlags.isFinal()) { + overIcon.add(Icons.FINAL); + } + if (accessFlags.isStatic()) { + overIcon.add(Icons.STATIC); + } + + return overIcon; + } + + public static String makeBaseString(JavaMethod mth) { + if (mth.isClassInit()) { + return "{...}"; + } + StringBuilder base = new StringBuilder(); + if (mth.isConstructor()) { + base.append(mth.getDeclaringClass().getName()); + } else { + base.append(mth.getName()); + } + base.append('('); + for (Iterator it = mth.getArguments().iterator(); it.hasNext();) { + base.append(UiUtils.typeStr(it.next())); + if (it.hasNext()) { + base.append(", "); + } + } + base.append(')'); + return base.toString(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/cellrenders/MethodsListRenderer.java b/jadx-gui/src/main/java/jadx/gui/ui/cellrenders/MethodsListRenderer.java new file mode 100644 index 000000000..5448ea0aa --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/cellrenders/MethodsListRenderer.java @@ -0,0 +1,53 @@ +package jadx.gui.ui.cellrenders; + +import java.awt.BorderLayout; +import java.awt.Component; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.ListCellRenderer; + +import jadx.api.JavaMethod; +import jadx.gui.utils.UiUtils; + +public class MethodsListRenderer extends JPanel implements ListCellRenderer { + private final JCheckBox checkBox; + private final JLabel label; + + public MethodsListRenderer() { + setLayout(new BorderLayout(5, 0)); + checkBox = new JCheckBox(); + label = new JLabel(); + + setBorder(BorderFactory.createEmptyBorder(1, 5, 1, 5)); + + add(checkBox, BorderLayout.WEST); + add(label, BorderLayout.CENTER); + + setOpaque(true); + + checkBox.setOpaque(false); + label.setOpaque(false); + } + + @Override + public Component getListCellRendererComponent(JList list, + JavaMethod value, + int index, + boolean isSelected, + boolean cellHasFocus) { + label.setText(UiUtils.typeFormatHtml(MethodRenderHelper.makeBaseString(value), value.getReturnType())); + label.setIcon(MethodRenderHelper.getIcon(value)); + + checkBox.setSelected(isSelected); + + setBackground(isSelected ? list.getSelectionBackground() : list.getBackground()); + setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); + label.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); + + return this; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/MethodsDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/MethodsDialog.java new file mode 100644 index 000000000..159f32d8a --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/MethodsDialog.java @@ -0,0 +1,100 @@ +package jadx.gui.ui.dialog; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.util.List; +import java.util.function.Consumer; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.DefaultListSelectionModel; +import javax.swing.JButton; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.WindowConstants; + +import jadx.api.JavaMethod; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.cellrenders.MethodsListRenderer; +import jadx.gui.utils.NLS; + +public class MethodsDialog extends CommonDialog { + private JList methodList; + + private final Consumer> listConsumer; + + public MethodsDialog(MainWindow mainWindow, List methods, Consumer> listConsumer) { + super(mainWindow); + this.listConsumer = listConsumer; + initUI(methods); + setVisible(true); + } + + private void initUI(List methods) { + setTitle(NLS.str("methods_dialog.title")); + + DefaultListModel defaultListModel = new DefaultListModel<>(); + defaultListModel.addAll(methods); + + methodList = new JList<>(); + methodList.setModel(defaultListModel); + methodList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + methodList.setCellRenderer(new MethodsListRenderer()); + methodList.setSelectionModel(new DefaultListSelectionModel() { + @Override + public void setSelectionInterval(int index0, int index1) { + if (super.isSelectedIndex(index0)) { + super.removeSelectionInterval(index0, index1); + } else { + super.addSelectionInterval(index0, index1); + } + } + + }); + + JScrollPane scrollPane = new JScrollPane(methodList); + JPanel buttonPane = initButtonsPanel(); + + JPanel contentPanel = new JPanel(); + contentPanel.setLayout(new BorderLayout(5, 5)); + contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + contentPanel.add(scrollPane, BorderLayout.CENTER); + contentPanel.add(buttonPane, BorderLayout.PAGE_END); + getContentPane().add(contentPanel); + + pack(); + setSize(500, 300); + setLocationRelativeTo(null); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + } + + protected JPanel initButtonsPanel() { + JButton cancelButton = new JButton(NLS.str("common_dialog.cancel")); + cancelButton.addActionListener(event -> dispose()); + + JButton okBtn = new JButton(NLS.str("common_dialog.ok")); + okBtn.addActionListener(event -> generateForSelected()); + getRootPane().setDefaultButton(okBtn); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); + buttonPane.add(Box.createHorizontalGlue()); + buttonPane.add(okBtn); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(cancelButton); + return buttonPane; + } + + private void generateForSelected() { + List selectedMethods = methodList.getSelectedValuesList(); + if (!selectedMethods.isEmpty()) { + this.listConsumer.accept(selectedMethods); + } + dispose(); + } + +} 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 6cf93e15a..3819d9ab7 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -334,6 +334,8 @@ msg.project_error=Projekt konnte nicht geladen werden msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht. msg.cant_add_comment=Kann hier keinen Kommentar hinzufügen +#methods_dialog.title=Select methods + popup.bytecode_col=Dalvik-Bytecode anzeigen popup.line_wrap=Zeilenumbruch popup.undo=Rückgängig 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 c7dc0ad9e..da3ac443f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -334,6 +334,8 @@ msg.project_error=Project could not be loaded msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist. msg.cant_add_comment=Can't add comment here +methods_dialog.title=Select methods + popup.bytecode_col=Show Dalvik Bytecode popup.line_wrap=Line Wrap popup.undo=Undo 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 79180d2fe..d5146b8a4 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -334,6 +334,8 @@ msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicac #msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist. #msg.cant_add_comment=Can't add comment here +#methods_dialog.title=Select methods + #popup.bytecode_col=Show Dalvik Bytecode #popup.line_wrap=Line Wrap popup.undo=Deshacer diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties index 4c18e4250..0545731e2 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -334,6 +334,8 @@ msg.project_error=Proyek tidak dapat dimuat msg.cmd_select_class_error=Gagal memilih kelas\n%s\nKelas tidak ada. msg.cant_add_comment=Tidak dapat menambahkan komentar di sini +#methods_dialog.title=Select methods + popup.bytecode_col=Tampilkan Bytecode Dalvik popup.line_wrap=Baris Wrap popup.undo=Kembalikan 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 d49df1c72..3a9a55f3a 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -334,6 +334,8 @@ msg.project_error=프로젝트를 로드 할 수 없습니다. msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다. msg.cant_add_comment=여기에 주석을 추가할수 없음 +#methods_dialog.title=Select methods + popup.bytecode_col=Dalvik Bytecode 보이기 popup.line_wrap=줄 바꿈 popup.undo=실행 취소 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 f1b54520c..d77730c14 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -334,6 +334,8 @@ msg.project_error=Projeto não pôde ser carregado msg.cmd_select_class_error=Falha ao selecionar classe\n%s\nA classe não existe. msg.cant_add_comment=Não é possível adicionar comentários aqui +#methods_dialog.title=Select methods + popup.bytecode_col=Mostrar Dalvik Bytecode popup.line_wrap=Quebra de linha popup.undo=Desfazer 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 fd764acd5..73f30cb4a 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -334,6 +334,8 @@ msg.project_error=Проект не может быть загружен msg.cmd_select_class_error=Ошибка выбора класса\n%s\nЭтот класс не существует. msg.cant_add_comment=Невозможно добавить комментарий сюда +#methods_dialog.title=Select methods + popup.bytecode_col=Показать Dalvik байткод popup.line_wrap=Перенос строк popup.undo=Отменить 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 058080497..6fcdb508f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -334,6 +334,8 @@ msg.project_error=项目无法加载 msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。 msg.cant_add_comment=无法在此添加注释 +#methods_dialog.title=Select methods + popup.bytecode_col=显示Dalvik字节码 popup.line_wrap=自动换行 popup.undo=撤销 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 ecae95936..0e73af7f5 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -334,6 +334,8 @@ msg.project_error=無法載入專案 msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。 msg.cant_add_comment=無法在此新增註解 +#methods_dialog.title=Select methods + popup.bytecode_col=顯示 Dalvik 位元組碼 popup.line_wrap=自動換行 popup.undo=復原