feat(gui): added dialog for select methods for generate frida class snippet (PR #2487)
feat(gui): add dialog for select methods for generate frida snippet
This commit is contained in:
@@ -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<ArgType> 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
|
||||
|
||||
@@ -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<JavaMethod> 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) {
|
||||
|
||||
@@ -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<ArgType> it = mth.getArguments().iterator(); it.hasNext();) {
|
||||
base.append(UiUtils.typeStr(it.next()));
|
||||
if (it.hasNext()) {
|
||||
base.append(", ");
|
||||
}
|
||||
}
|
||||
base.append(')');
|
||||
return base.toString();
|
||||
}
|
||||
}
|
||||
@@ -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<JavaMethod> {
|
||||
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<? extends JavaMethod> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<JavaMethod> methodList;
|
||||
|
||||
private final Consumer<List<JavaMethod>> listConsumer;
|
||||
|
||||
public MethodsDialog(MainWindow mainWindow, List<JavaMethod> methods, Consumer<List<JavaMethod>> listConsumer) {
|
||||
super(mainWindow);
|
||||
this.listConsumer = listConsumer;
|
||||
initUI(methods);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
private void initUI(List<JavaMethod> methods) {
|
||||
setTitle(NLS.str("methods_dialog.title"));
|
||||
|
||||
DefaultListModel<JavaMethod> 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<JavaMethod> selectedMethods = methodList.getSelectedValuesList();
|
||||
if (!selectedMethods.isEmpty()) {
|
||||
this.listConsumer.accept(selectedMethods);
|
||||
}
|
||||
dispose();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=실행 취소
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=Отменить
|
||||
|
||||
@@ -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=撤销
|
||||
|
||||
@@ -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=復原
|
||||
|
||||
Reference in New Issue
Block a user