feat: rename without deobfuscation, save renames in project (#1076 #1022)

This commit is contained in:
Skylot
2021-10-26 20:06:14 +01:00
parent 82712776cc
commit dfdc14ea86
72 changed files with 1221 additions and 721 deletions
@@ -16,9 +16,13 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.api.data.ICodeComment;
import jadx.api.data.ICodeRename;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.IJavaNodeRef;
import jadx.api.data.impl.JadxCodeComment;
import jadx.api.data.impl.JadxCodeData;
import jadx.api.data.impl.JadxCodeRef;
import jadx.api.data.impl.JadxCodeRename;
import jadx.api.data.impl.JadxNodeRef;
import jadx.core.utils.GsonUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -34,7 +38,9 @@ public class JadxProject {
private static final Gson GSON = new GsonBuilder()
.registerTypeHierarchyAdapter(Path.class, PathTypeAdapter.singleton())
.registerTypeAdapter(ICodeComment.class, GsonUtils.interfaceReplace(JadxCodeComment.class))
.registerTypeAdapter(ICodeRename.class, GsonUtils.interfaceReplace(JadxCodeRename.class))
.registerTypeAdapter(IJavaNodeRef.class, GsonUtils.interfaceReplace(JadxNodeRef.class))
.registerTypeAdapter(IJavaCodeRef.class, GsonUtils.interfaceReplace(JadxCodeRef.class))
.setPrettyPrinting()
.create();
@@ -8,21 +8,16 @@ import jadx.api.JavaVariable;
public class JVariable extends JNode {
private static final long serialVersionUID = -3002100457834453783L;
JClass cls;
JavaVariable var;
private final JMethod jMth;
private final JavaVariable var;
public JVariable(JavaVariable var, JClass cls) {
this.cls = cls;
public JVariable(JMethod jMth, JavaVariable var) {
this.jMth = jMth;
this.var = var;
}
public JavaVariable getJavaVarNode() {
return (JavaVariable) getJavaNode();
}
@Override
public JClass getRootClass() {
return cls;
return var;
}
@Override
@@ -30,9 +25,14 @@ public class JVariable extends JNode {
return var;
}
@Override
public JClass getRootClass() {
return jMth.getRootClass();
}
@Override
public JClass getJParent() {
return cls;
return jMth.getJParent();
}
@Override
@@ -45,6 +45,11 @@ public class JVariable extends JNode {
return var.getName();
}
@Override
public String makeLongString() {
return var.getFullName();
}
@Override
public boolean canRename() {
return true;
@@ -609,15 +609,6 @@ public class MainWindow extends JFrame {
}
private void saveAll(boolean export) {
JadxArgs decompilerArgs = wrapper.getArgs();
if ((!decompilerArgs.isFsCaseSensitive() && !decompilerArgs.isRenameCaseSensitive())
|| !decompilerArgs.isRenameValid() || !decompilerArgs.isRenamePrintable()) {
JOptionPane.showMessageDialog(
this,
NLS.str("msg.rename_disabled", settings.getLangLocale()),
NLS.str("msg.rename_disabled_title", settings.getLangLocale()),
JOptionPane.INFORMATION_MESSAGE);
}
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
fileChooser.setToolTipText(NLS.str("file.save_all_msg"));
@@ -629,6 +620,7 @@ public class MainWindow extends JFrame {
int ret = fileChooser.showSaveDialog(mainPanel);
if (ret == JFileChooser.APPROVE_OPTION) {
JadxArgs decompilerArgs = wrapper.getArgs();
decompilerArgs.setExportAsGradleProject(export);
if (export) {
decompilerArgs.setSkipSources(false);
@@ -7,6 +7,7 @@ import java.awt.event.MouseEvent;
import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuEvent;
import javax.swing.text.BadLocationException;
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.Token;
@@ -178,6 +179,37 @@ public final class CodeArea extends AbstractCodeArea {
return nodeCache.makeFrom(javaNode);
}
@SuppressWarnings("deprecation")
public CodePosition getMouseCodePos() {
try {
Point mousePos = UiUtils.getMousePosition(this);
return buildCodePosFromOffset(this.viewToModel(mousePos));
} catch (Exception e) {
LOG.error("Failed to get offset at mouse position", e);
return null;
}
}
@Nullable
public CodePosition getCaretCodePos() {
try {
return buildCodePosFromOffset(getCaretPosition());
} catch (Exception e) {
LOG.warn("Failed to get caret position", e);
return null;
}
}
private CodePosition buildCodePosFromOffset(int offset) throws BadLocationException {
int start = getWordStart(offset);
if (start == -1) {
start = offset;
}
int line = getLineOfOffset(start);
int lineOffset = start - getLineStartOffset(line);
return new CodePosition(line + 1, lineOffset + 1, start);
}
public JNode getNodeUnderCaret() {
int start = getWordStart(getCaretPosition());
if (start == -1) {
@@ -16,9 +16,9 @@ import jadx.api.JavaClass;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.api.data.ICodeComment;
import jadx.api.data.annotations.CustomOffsetRef;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.impl.JadxCodeComment;
import jadx.api.data.impl.JadxCodeRef;
import jadx.api.data.impl.JadxNodeRef;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
@@ -123,13 +123,7 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis
JadxNodeRef nodeRef = JadxNodeRef.forMth((JavaMethod) node);
if (ann instanceof InsnCodeOffset) {
int rawOffset = ((InsnCodeOffset) ann).getOffset();
return new JadxCodeComment(nodeRef, "", rawOffset);
}
if (ann instanceof CustomOffsetRef) {
CustomOffsetRef customRef = (CustomOffsetRef) ann;
JadxCodeComment comment = new JadxCodeComment(nodeRef, "", customRef.getOffset());
comment.setAttachType(customRef.getAttachType());
return comment;
return new JadxCodeComment(nodeRef, JadxCodeRef.forInsn(rawOffset), "");
}
}
} catch (Exception e) {
@@ -12,6 +12,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JavaNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.JNodeCache;
class MouseHoverHighlighter extends MouseMotionAdapter {
private static final Logger LOG = LoggerFactory.getLogger(MouseHoverHighlighter.class);
@@ -57,6 +59,7 @@ class MouseHoverHighlighter extends MouseMotionAdapter {
removeHighlight();
tag = codeArea.getHighlighter().addHighlight(tokenOffset, token.getEndOffset(), this.highlighter);
highlightedTokenOffset = tokenOffset;
updateToolTip(nodeAtOffset);
return true;
} catch (Exception exc) {
LOG.error("Mouse hover highlight error", exc);
@@ -69,6 +72,17 @@ class MouseHoverHighlighter extends MouseMotionAdapter {
codeArea.getHighlighter().removeHighlight(tag);
tag = null;
highlightedTokenOffset = -1;
updateToolTip(null);
}
}
private void updateToolTip(JavaNode node) {
if (node == null) {
codeArea.setToolTipText(null);
return;
}
JNodeCache nodeCache = codeArea.getMainWindow().getCacheObject().getNodeCache();
JNode jNode = nodeCache.makeFrom(node);
codeArea.setToolTipText(jNode.makeLongString());
}
}
@@ -26,8 +26,9 @@ public final class RenameAction extends JNodeMenuAction<JNode> {
public RenameAction(CodeArea codeArea) {
super(NLS.str("popup.rename") + " (n)", codeArea);
KeyStroke key = getKeyStroke(VK_N, 0);
codeArea.getInputMap().put(key, "trigger rename");
codeArea.getActionMap().put("trigger rename", new AbstractAction() {
String renameActionId = "trigger rename";
codeArea.getInputMap().put(key, renameActionId);
codeArea.getActionMap().put(renameActionId, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
node = codeArea.getNodeUnderCaret();
@@ -45,7 +46,7 @@ public final class RenameAction extends JNodeMenuAction<JNode> {
if (!node.canRename()) {
UiUtils.showMessageBox(codeArea.getMainWindow(),
NLS.str("msg.rename_node_failed", node.getJavaNode().getFullName()));
LOG.info("node can't be renamed");
LOG.warn("Can't rename node: {}", node);
return;
}
RenameDialog.rename(codeArea.getMainWindow(), node);
@@ -65,6 +65,7 @@ public class CommentDialog extends JDialog {
Collections.sort(list);
codeData.setComments(list);
project.setCodeData(codeData);
codeArea.getMainWindow().getWrapper().getDecompiler().reloadCodeData();
} catch (Exception e) {
LOG.error("Comment action failed", e);
}
@@ -85,8 +86,7 @@ public class CommentDialog extends JDialog {
}
for (ICodeComment comment : codeData.getComments()) {
if (Objects.equals(comment.getNodeRef(), blankComment.getNodeRef())
&& comment.getOffset() == blankComment.getOffset()
&& comment.getAttachType() == blankComment.getAttachType()) {
&& Objects.equals(comment.getCodeRef(), blankComment.getCodeRef())) {
return comment;
}
}
@@ -120,8 +120,7 @@ public class CommentDialog extends JDialog {
}
return;
}
ICodeComment newComment = new JadxCodeComment(comment.getNodeRef(),
newCommentStr, comment.getOffset(), comment.getAttachType());
ICodeComment newComment = new JadxCodeComment(comment.getNodeRef(), comment.getCodeRef(), newCommentStr);
if (updateComment) {
updateCommentsData(codeArea, list -> {
list.remove(comment);
@@ -5,10 +5,13 @@ import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
@@ -17,7 +20,6 @@ import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
@@ -27,22 +29,21 @@ import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeWriter;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.core.deobf.DeobfPresets;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.nodes.MethodNode;
import jadx.api.JavaVariable;
import jadx.api.data.ICodeRename;
import jadx.api.data.impl.JadxCodeData;
import jadx.api.data.impl.JadxCodeRef;
import jadx.api.data.impl.JadxCodeRename;
import jadx.api.data.impl.JadxNodeRef;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.nodes.VariableNode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxProject;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
@@ -71,9 +72,6 @@ public class RenameDialog extends JDialog {
private transient JTextField renameField;
public static boolean rename(MainWindow mainWindow, JNode node) {
if (!checkSettings(mainWindow)) {
return false;
}
RenameDialog renameDialog = new RenameDialog(mainWindow, node);
renameDialog.setVisible(true);
return true;
@@ -87,108 +85,74 @@ public class RenameDialog extends JDialog {
initUI();
}
public static boolean checkSettings(MainWindow mainWindow) {
StringBuilder errorMessage = new StringBuilder();
errorMessage.append(NLS.str("msg.rename_disabled")).append(ICodeWriter.NL);
JadxSettings settings = mainWindow.getSettings();
boolean valid = true;
if (!settings.isDeobfuscationOn()) {
errorMessage.append(" - ").append(NLS.str("msg.rename_disabled_deobfuscation_disabled")).append(ICodeWriter.NL);
valid = false;
}
if (settings.isDeobfuscationForceSave()) {
errorMessage.append(" - ").append(NLS.str("msg.rename_disabled_force_rewrite_enabled")).append(ICodeWriter.NL);
valid = false;
}
if (valid) {
return true;
}
int result = JOptionPane.showConfirmDialog(mainWindow, errorMessage.toString(),
NLS.str("msg.rename_disabled_title"), JOptionPane.OK_CANCEL_OPTION);
if (result != JOptionPane.OK_OPTION) {
return false;
}
settings.setDeobfuscationOn(true);
settings.setDeobfuscationForceSave(false);
settings.sync();
mainWindow.reOpenFile();
return false; // TODO: can't open dialog, 'node' is replaced with new one after reopen
}
private void updateDeobfMap(DeobfPresets deobfPresets, String renameText) {
if (node instanceof JMethod) {
MethodNode mthNode = ((JavaMethod) node.getJavaNode()).getMethodNode();
MethodOverrideAttr overrideAttr = mthNode.get(AType.METHOD_OVERRIDE);
if (overrideAttr != null) {
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
deobfPresets.getMthPresetMap().put(relatedMth.getMethodInfo().getRawFullId(), renameText);
}
}
deobfPresets.getMthPresetMap().put(mthNode.getMethodInfo().getRawFullId(), renameText);
} else if (node instanceof JField) {
JavaField javaField = (JavaField) node.getJavaNode();
deobfPresets.getFldPresetMap().put(javaField.getFieldNode().getFieldInfo().getRawFullId(), renameText);
} else if (node instanceof JClass) {
JavaClass javaClass = (JavaClass) node.getJavaNode();
deobfPresets.getClsPresetMap().put(javaClass.getRawName(), renameText);
} else if (node instanceof JPackage) {
deobfPresets.getPkgPresetMap().put(((JPackage) node).getFullName(), renameText);
} else if (node instanceof JVariable) {
VariableNode varNode = ((JVariable) node).getJavaVarNode().getVariableNode();
deobfPresets.updateVariableName(varNode, renameText);
}
}
private void rename() {
try {
String renameText = renameField.getText();
if (renameText == null || renameText.length() == 0) {
return;
}
RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
if (node == null) {
LOG.error("rename(): rootNode is null!");
dispose();
return;
}
if (!refreshDeobfMapFile(renameText, root)) {
LOG.error("rename(): refreshDeobfMapFile() failed!");
dispose();
return;
}
refreshState(root);
updateCodeRenames(set -> processRename(node, renameField.getText(), set));
refreshState();
} catch (Exception e) {
LOG.error("Rename failed", e);
}
dispose();
}
private boolean refreshDeobfMapFile(String renameText, RootNode root) {
DeobfPresets deobfPresets = DeobfPresets.build(root);
if (deobfPresets == null) {
return false;
private void processRename(JNode node, String newName, Set<ICodeRename> renames) {
JadxCodeRename rename = buildRename(node, newName, renames);
renames.remove(rename);
JavaNode javaNode = node.getJavaNode();
if (javaNode != null) {
javaNode.removeAlias();
}
try {
deobfPresets.load();
} catch (Exception e) {
LOG.error("rename(): readDeobfMap() failed");
return false;
if (!newName.isEmpty()) {
renames.add(rename);
}
updateDeobfMap(deobfPresets, renameText);
try {
deobfPresets.save();
} catch (Exception e) {
LOG.error("rename(): writeDeobfMap() failed");
return false;
}
return true;
}
private void refreshState(RootNode rootNode) {
RenameVisitor renameVisitor = new RenameVisitor();
renameVisitor.init(rootNode);
@NotNull
private JadxCodeRename buildRename(JNode node, String newName, Set<ICodeRename> renames) {
if (node instanceof JMethod) {
JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
List<JavaMethod> relatedMethods = javaMethod.getOverrideRelatedMethods();
if (!relatedMethods.isEmpty()) {
for (JavaMethod relatedMethod : relatedMethods) {
renames.remove(new JadxCodeRename(JadxNodeRef.forMth(relatedMethod), ""));
}
}
return new JadxCodeRename(JadxNodeRef.forMth(javaMethod), newName);
}
if (node instanceof JField) {
return new JadxCodeRename(JadxNodeRef.forFld(((JField) node).getJavaField()), newName);
}
if (node instanceof JClass) {
return new JadxCodeRename(JadxNodeRef.forCls(((JClass) node).getCls()), newName);
}
if (node instanceof JPackage) {
return new JadxCodeRename(JadxNodeRef.forPkg(((JPackage) node).getFullName()), newName);
}
if (node instanceof JVariable) {
JavaVariable javaVar = ((JVariable) node).getJavaVarNode();
return new JadxCodeRename(JadxNodeRef.forMth(javaVar.getMth()), JadxCodeRef.forVar(javaVar), newName);
}
throw new JadxRuntimeException("Failed to build rename node for: " + node);
}
private void updateCodeRenames(Consumer<Set<ICodeRename>> updater) {
JadxProject project = mainWindow.getProject();
JadxCodeData codeData = project.getCodeData();
if (codeData == null) {
codeData = new JadxCodeData();
}
Set<ICodeRename> set = new HashSet<>(codeData.getRenames());
updater.accept(set);
List<ICodeRename> list = new ArrayList<>(set);
Collections.sort(list);
codeData.setRenames(list);
project.setCodeData(codeData);
mainWindow.getWrapper().getDecompiler().reloadCodeData();
}
private void refreshState() {
RootNode rootNode = mainWindow.getWrapper().getDecompiler().getRoot();
new RenameVisitor().init(rootNode);
JNodeCache nodeCache = cache.getNodeCache();
JavaNode javaNode = node.getJavaNode();
@@ -53,7 +53,9 @@ public class JNodeCache {
return new JField((JavaField) node, makeFrom(node.getDeclaringClass()));
}
if (node instanceof JavaVariable) {
return new JVariable((JavaVariable) node, makeFrom(node.getDeclaringClass()));
JavaVariable javaVar = (JavaVariable) node;
JMethod jMth = (JMethod) makeFrom(javaVar.getMth());
return new JVariable(jMth, javaVar);
}
throw new JadxRuntimeException("Unknown type for JavaNode: " + node.getClass());
}
@@ -21,6 +21,7 @@ import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.api.data.ICodeComment;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.IJavaNodeRef;
import jadx.api.data.annotations.ICodeRawOffset;
import jadx.gui.JadxWrapper;
@@ -84,7 +85,7 @@ public class CommentsIndex {
private @NotNull RefCommentNode getCommentNode(ICodeComment comment, JNode refNode) {
IJavaNodeRef nodeRef = comment.getNodeRef();
if (nodeRef.getType() == IJavaNodeRef.RefType.METHOD && comment.getOffset() > 0) {
if (nodeRef.getType() == IJavaNodeRef.RefType.METHOD && comment.getCodeRef() != null) {
return new CodeCommentNode((JMethod) refNode, comment);
}
return new RefCommentNode(refNode, comment.getComment());
@@ -129,7 +130,8 @@ public class CommentsIndex {
public CodeCommentNode(JMethod node, ICodeComment comment) {
super(node, comment.getComment());
this.offset = comment.getOffset();
IJavaCodeRef codeRef = Objects.requireNonNull(comment.getCodeRef(), "Null comment code ref");
this.offset = codeRef.getIndex();
}
@Override
@@ -177,10 +177,6 @@ msg.language_changed=Neue Sprache wird beim nächsten Start der Anwendung angeze
msg.index_not_initialized=Index nicht initialisiert, Suche wird deaktiviert!
msg.project_error_title=Fehler
msg.project_error=Projekt konnte nicht geladen werden
msg.rename_disabled_title=Umbenennen deaktiviert
msg.rename_disabled=Einige der Umbenennungseinstellungen sind deaktiviert, bitte beachten Sie dies.
msg.rename_disabled_force_rewrite_enabled=Deaktivieren Sie zum Umbenennen die Option "Deobfuscationskartendatei umschreiben erzwingen".
msg.rename_disabled_deobfuscation_disabled=Bitte aktivieren Sie die Umbenennung von Deobfuscation.
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
msg.rename_node_disabled=Dieser Knotenpunkt kann nicht umbenannt werden
msg.rename_node_failed=%s umbenennen nicht möglich
@@ -177,10 +177,6 @@ msg.language_changed=New language will be displayed the next time application st
msg.index_not_initialized=Index not initialized, search will be disabled!
msg.project_error_title=Error
msg.project_error=Project could not be loaded
msg.rename_disabled_title=Rename disabled
msg.rename_disabled=Some of rename settings are disabled, next options will be changed:
msg.rename_disabled_force_rewrite_enabled=Disable "Force rewrite deobfuscation map file" option.
msg.rename_disabled_deobfuscation_disabled=Enable deobfuscation.
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
msg.rename_node_disabled=Can't rename this node
msg.rename_node_failed=Can't rename %s
@@ -177,10 +177,6 @@ msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicac
msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivará!
#msg.project_error_title=
#msg.project_error=
#msg.rename_disabled_title=
#msg.rename_disabled=
#msg.rename_disabled_force_rewrite_enabled=
#msg.rename_disabled_deobfuscation_disabled=
#msg.cmd_select_class_error=
#msg.rename_node_disabled=
#msg.rename_node_failed=
@@ -177,10 +177,6 @@ msg.language_changed=다음에 응용 프로그램이 시작되면 새 언어가
msg.index_not_initialized=인덱스가 초기화되지 않았습니다. 검색이 비활성화됩니다!
msg.project_error_title=오류
msg.project_error=프로젝트를 로드 할 수 없습니다.
msg.rename_disabled_title=이름 변경 사용 안함
msg.rename_disabled=일부 이름 바꾸기 설정이 비활성화되고 다음 옵션이 변경됩니다:
msg.rename_disabled_force_rewrite_enabled="난독 해제 맵 파일 강제 다시 쓰기" 옵션을 비활성화합니다.
msg.rename_disabled_deobfuscation_disabled=난독 해제 활성화
msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다.
msg.rename_node_disabled=이 노드의 이름을 바꿀 수 없습니다.
msg.rename_node_failed=%s의 이름을 바꿀 수 없습니다.
@@ -177,10 +177,6 @@ msg.language_changed=在下次启动时将会显示新的语言。
msg.index_not_initialized=索引尚未初始化,无法进行搜索!
msg.project_error_title=错误
msg.project_error=项目无法加载
msg.rename_disabled_title=重命名已禁用
msg.rename_disabled=某些重命名设置已禁用,请将此考虑在内
msg.rename_disabled_force_rewrite_enabled=请禁用“强制覆盖反重构映射文件”选项以重命名。
msg.rename_disabled_deobfuscation_disabled=请启用反混淆以重命名。
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
#msg.rename_node_disabled=
#msg.rename_node_failed=