* Add getRealFullName() to ClassNode and JavaClass and searchJavaClassByRealName() to JadxWrapper Those methods is like getFullName() and searchJavaClassByClassName(), but for class names without aliases. It is necessary for renaming classes/methods/fields. * core: Make getFieldNode(), getMethodNode() and getRoot() public This is necessary for renaming functionality * jadx-gui: Add Rename popup menu entry (renames classes, methods and fields) It allows user to rename classes, methods and fields. It updates deobfuscation map and reload file. This may be suboptimal, and maybe some RenameVisitor should be added. Deobfuscation should be enabled in order to allow this.
This commit is contained in:
@@ -281,7 +281,7 @@ public final class JadxDecompiler {
|
||||
root.getErrorsCounter().printReport();
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
public RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public final class JavaField implements JavaNode {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
FieldNode getFieldNode() {
|
||||
public FieldNode getFieldNode() {
|
||||
return field;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
MethodNode getMethodNode() {
|
||||
public MethodNode getMethodNode() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.gui.treemodel.*;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
|
||||
public class RenameDialog extends JDialog {
|
||||
private static final long serialVersionUID = -3269715644416902410L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RenameDialog.class);
|
||||
|
||||
protected final transient MainWindow mainWindow;
|
||||
|
||||
private final transient JNode node;
|
||||
|
||||
private JTextField renameField;
|
||||
|
||||
private CodeArea codeArea;
|
||||
|
||||
public RenameDialog(CodeArea codeArea, JNode node) {
|
||||
super(codeArea.getMainWindow());
|
||||
mainWindow = codeArea.getMainWindow();
|
||||
this.codeArea = codeArea;
|
||||
this.node = node;
|
||||
initUI();
|
||||
loadWindowPos();
|
||||
}
|
||||
|
||||
private void loadWindowPos() {
|
||||
mainWindow.getSettings().loadWindowPos(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
mainWindow.getSettings().saveWindowPos(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private Path getDeobfMapPath(RootNode root) {
|
||||
List<DexNode> dexNodes = root.getDexNodes();
|
||||
if (dexNodes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
InputFile firstInputFile = dexNodes.get(0).getDexFile().getInputFile();
|
||||
Path inputFilePath = firstInputFile.getFile().getAbsoluteFile().toPath();
|
||||
|
||||
String inputName = inputFilePath.getFileName().toString();
|
||||
String baseName = inputName.substring(0, inputName.lastIndexOf('.'));
|
||||
return inputFilePath.getParent().resolve(baseName + ".jobf");
|
||||
}
|
||||
|
||||
private String getNodeAlias(String renameText) {
|
||||
String type = "";
|
||||
String id = "";
|
||||
if (node instanceof JMethod) {
|
||||
JavaMethod javaMethod = (JavaMethod) node.getJavaNode();
|
||||
type = "m";
|
||||
id = javaMethod.getMethodNode().getMethodInfo().getRawFullId();
|
||||
} else if (node instanceof JField) {
|
||||
JavaField javaField = (JavaField) node.getJavaNode();
|
||||
type = "f";
|
||||
id = javaField.getFieldNode().getFieldInfo().getRawFullId();
|
||||
} else if (node instanceof JClass) {
|
||||
type = "c";
|
||||
JavaNode javaNode = node.getJavaNode();
|
||||
id = javaNode.getFullName();
|
||||
if (javaNode instanceof JavaClass) {
|
||||
JavaClass javaClass = (JavaClass) javaNode;
|
||||
id = javaClass.getRealFullName();
|
||||
}
|
||||
|
||||
} else if (node instanceof JPackage) {
|
||||
type = "p";
|
||||
id = node.getJavaNode().getFullName();
|
||||
}
|
||||
return String.format("%s %s = %s", type, id, renameText);
|
||||
}
|
||||
|
||||
private boolean updateDeobfMap(String renameText, RootNode root) {
|
||||
Path deobfMapPath = getDeobfMapPath(root);
|
||||
if (deobfMapPath == null) {
|
||||
LOG.error("rename(): Failed deofbMapFile is null");
|
||||
return false;
|
||||
}
|
||||
String alias = getNodeAlias(renameText);
|
||||
LOG.info("rename(): " + alias);
|
||||
|
||||
try {
|
||||
List<String> deobfMap = readAndUpdateDeobfMap(deobfMapPath, alias);
|
||||
File tmpFile = File.createTempFile("deobf_tmp_", ".txt");
|
||||
FileOutputStream fileOut = new FileOutputStream(tmpFile);
|
||||
for (String entry : deobfMap) {
|
||||
fileOut.write(entry.getBytes());
|
||||
fileOut.write(System.lineSeparator().getBytes());
|
||||
}
|
||||
fileOut.close();
|
||||
File oldMap = File.createTempFile("deobf_bak_", ".txt");
|
||||
Files.copy(deobfMapPath, oldMap.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.copy(tmpFile.toPath(), deobfMapPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.delete(oldMap.toPath());
|
||||
|
||||
} catch (IOException e) {
|
||||
LOG.error("rename(): Failed to write deofbMapFile {}", deobfMapPath);
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<String> readAndUpdateDeobfMap(Path deobfMapPath, String alias) throws IOException {
|
||||
List<String> deobfMap = Files.readAllLines(deobfMapPath, StandardCharsets.UTF_8);
|
||||
String id = alias.split("=")[0];
|
||||
LOG.info("Id = " + id);
|
||||
int i = 0;
|
||||
while (i < deobfMap.size()) {
|
||||
if (deobfMap.get(i).startsWith(id)) {
|
||||
LOG.info("Removing entry " + deobfMap.get(i));
|
||||
deobfMap.remove(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
deobfMap.add(alias);
|
||||
return deobfMap;
|
||||
}
|
||||
|
||||
private void rename() {
|
||||
String renameText = renameField.getText();
|
||||
if (renameText == null || renameText.length() == 0 || codeArea.getText() == null) {
|
||||
return;
|
||||
}
|
||||
RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
|
||||
if (node == null) {
|
||||
LOG.error("rename(): rootNode is null!");
|
||||
dispose();
|
||||
return;
|
||||
}
|
||||
if (!updateDeobfMap(renameText, root)) {
|
||||
LOG.error("rename(): updateDeobfMap() failed");
|
||||
dispose();
|
||||
return;
|
||||
}
|
||||
mainWindow.reOpenFile();
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void initCommon() {
|
||||
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
||||
getRootPane().registerKeyboardAction(e -> dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private JPanel initButtonsPanel() {
|
||||
JButton cancelButton = new JButton(NLS.str("search_dialog.cancel"));
|
||||
cancelButton.addActionListener(event -> dispose());
|
||||
JButton renameBtn = new JButton(NLS.str("popup.rename"));
|
||||
renameBtn.addActionListener(event -> rename());
|
||||
getRootPane().setDefaultButton(renameBtn);
|
||||
|
||||
JPanel buttonPane = new JPanel();
|
||||
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
||||
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||
buttonPane.add(Box.createHorizontalGlue());
|
||||
buttonPane.add(renameBtn);
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
buttonPane.add(cancelButton);
|
||||
return buttonPane;
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
JLabel lbl = new JLabel(NLS.str("popup.rename"));
|
||||
JLabel nodeLabel = new JLabel(this.node.makeLongString(), this.node.getIcon(), SwingConstants.LEFT);
|
||||
lbl.setLabelFor(nodeLabel);
|
||||
|
||||
renameField = new JTextField(40);
|
||||
renameField.addActionListener(e -> rename());
|
||||
renameField.setText(node.getName());
|
||||
renameField.selectAll();
|
||||
new TextStandardActions(renameField);
|
||||
|
||||
JPanel renamePane = new JPanel();
|
||||
renamePane.setLayout(new FlowLayout(FlowLayout.LEFT));
|
||||
renamePane.add(lbl);
|
||||
renamePane.add(nodeLabel);
|
||||
renamePane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
JPanel textPane = new JPanel();
|
||||
textPane.setLayout(new FlowLayout(FlowLayout.LEFT));
|
||||
textPane.add(renameField);
|
||||
textPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
initCommon();
|
||||
JPanel buttonPane = initButtonsPanel();
|
||||
|
||||
Container contentPane = getContentPane();
|
||||
contentPane.add(renamePane, BorderLayout.PAGE_START);
|
||||
contentPane.add(textPane, BorderLayout.CENTER);
|
||||
contentPane.add(buttonPane, BorderLayout.PAGE_END);
|
||||
|
||||
setTitle(NLS.str("popup.rename"));
|
||||
pack();
|
||||
setSize(800, 80);
|
||||
setLocationRelativeTo(null);
|
||||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setModalityType(ModalityType.MODELESS);
|
||||
}
|
||||
}
|
||||
@@ -54,13 +54,16 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
private void addMenuItems() {
|
||||
FindUsageAction findUsage = new FindUsageAction(this);
|
||||
GoToDeclarationAction goToDeclaration = new GoToDeclarationAction(this);
|
||||
RenameAction rename = new RenameAction(this);
|
||||
|
||||
JPopupMenu popup = getPopupMenu();
|
||||
popup.addSeparator();
|
||||
popup.add(findUsage);
|
||||
popup.add(goToDeclaration);
|
||||
popup.add(rename);
|
||||
popup.addPopupMenuListener(findUsage);
|
||||
popup.addPopupMenuListener(goToDeclaration);
|
||||
popup.addPopupMenuListener(rename);
|
||||
}
|
||||
|
||||
public int adjustOffsetForToken(@Nullable Token token) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.RenameDialog;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
public final class RenameAction extends JNodeMenuAction<JNode> {
|
||||
private static final long serialVersionUID = -4680872086148463289L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RenameAction.class);
|
||||
|
||||
public RenameAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.rename"), codeArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (node == null) {
|
||||
LOG.info("node == null!");
|
||||
return;
|
||||
}
|
||||
RenameDialog renameDialog = new RenameDialog(codeArea, node);
|
||||
renameDialog.setVisible(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JNode getNodeByOffset(int offset) {
|
||||
return codeArea.getJNodeAtOffset(offset);
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,7 @@ popup.select_all=Select All
|
||||
popup.find_usage=Find Usage
|
||||
popup.go_to_declaration=Go to declaration
|
||||
popup.exclude=Exclude
|
||||
popup.rename=Rename
|
||||
|
||||
confirm.save_as_title=Confirm Save as
|
||||
confirm.save_as_message=%s already exists.\nDo you want to replace it?
|
||||
|
||||
@@ -145,6 +145,7 @@ popup.select_all=Seleccionar todo
|
||||
#popup.find_usage=
|
||||
#popup.go_to_declaration=
|
||||
#popup.exclude=
|
||||
popup.rename=Nimeta ümber
|
||||
|
||||
#confirm.save_as_title=
|
||||
#confirm.save_as_message=
|
||||
|
||||
@@ -145,6 +145,7 @@ popup.select_all=全选
|
||||
popup.find_usage=查找用例
|
||||
popup.go_to_declaration=跳到声明
|
||||
popup.exclude=排除
|
||||
popup.rename=改名
|
||||
|
||||
confirm.save_as_title=确认另存为
|
||||
confirm.save_as_message=%s 已存在。\n你想替换它吗?
|
||||
|
||||
Reference in New Issue
Block a user