diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 9253789f7..3bcdbe574 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -281,7 +281,7 @@ public final class JadxDecompiler { root.getErrorsCounter().printReport(); } - RootNode getRoot() { + public RootNode getRoot() { return root; } diff --git a/jadx-core/src/main/java/jadx/api/JavaField.java b/jadx-core/src/main/java/jadx/api/JavaField.java index fa7ec75f4..81e85bb71 100644 --- a/jadx-core/src/main/java/jadx/api/JavaField.java +++ b/jadx-core/src/main/java/jadx/api/JavaField.java @@ -46,7 +46,7 @@ public final class JavaField implements JavaNode { return field.getDecompiledLine(); } - FieldNode getFieldNode() { + public FieldNode getFieldNode() { return field; } diff --git a/jadx-core/src/main/java/jadx/api/JavaMethod.java b/jadx-core/src/main/java/jadx/api/JavaMethod.java index 811b3ca7b..d2f0cd98a 100644 --- a/jadx-core/src/main/java/jadx/api/JavaMethod.java +++ b/jadx-core/src/main/java/jadx/api/JavaMethod.java @@ -74,7 +74,7 @@ public final class JavaMethod implements JavaNode { return mth.getDecompiledLine(); } - MethodNode getMethodNode() { + public MethodNode getMethodNode() { return mth; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java new file mode 100644 index 000000000..1480eb3c3 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -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 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 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 readAndUpdateDeobfMap(Path deobfMapPath, String alias) throws IOException { + List 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); + } +} 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 ffa62db9d..8e75db620 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 @@ -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) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java new file mode 100644 index 000000000..80697a2c1 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java @@ -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 { + 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); + } +} 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 c8ee1f5d1..c86609c0a 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -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? 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 acaa0a61a..37c10bb1c 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -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= 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 28770b7a2..95fd49e68 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -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你想替换它吗?