This commit is contained in:
@@ -2,7 +2,7 @@ package jadx.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
|
||||
public interface ICodeWriter {
|
||||
String NL = System.getProperty("line.separator");
|
||||
@@ -40,7 +40,7 @@ public interface ICodeWriter {
|
||||
|
||||
int getLine();
|
||||
|
||||
void attachDefinition(LineAttrNode obj);
|
||||
void attachDefinition(ILineAttributeNode obj);
|
||||
|
||||
void attachAnnotation(Object obj);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
@@ -35,7 +36,6 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
import jadx.core.utils.Utils;
|
||||
@@ -439,6 +439,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
if (javaMethod != null) {
|
||||
return javaMethod;
|
||||
}
|
||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
|
||||
if (javaClass == null) {
|
||||
@@ -522,6 +525,15 @@ public final class JadxDecompiler implements Closeable {
|
||||
|
||||
@Nullable
|
||||
JavaNode convertNode(Object obj) {
|
||||
if (obj instanceof VarRef) {
|
||||
VarRef varRef = (VarRef) obj;
|
||||
MethodNode mthNode = varRef.getMth();
|
||||
JavaMethod mth = getJavaMethodByNode(mthNode);
|
||||
if (mth == null) {
|
||||
return null;
|
||||
}
|
||||
return new JavaVariable(mth, varRef);
|
||||
}
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
}
|
||||
@@ -538,14 +550,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
if (obj instanceof FieldNode) {
|
||||
return getJavaFieldByNode((FieldNode) obj);
|
||||
}
|
||||
if (obj instanceof VariableNode) {
|
||||
VariableNode varNode = (VariableNode) obj;
|
||||
JavaClass javaCls = getJavaClassByNode(varNode.getClassNode().getTopParentClass());
|
||||
if (javaCls == null) {
|
||||
return null;
|
||||
}
|
||||
return new JavaVariable(javaCls, varNode);
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node type: " + obj);
|
||||
}
|
||||
|
||||
@@ -580,6 +584,10 @@ public final class JadxDecompiler implements Closeable {
|
||||
return new CodePosition(defLine, 0, javaNode.getDefPos());
|
||||
}
|
||||
|
||||
public void reloadCodeData() {
|
||||
root.notifyCodeDataListeners();
|
||||
}
|
||||
|
||||
public JadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -248,6 +248,11 @@ public final class JavaClass implements JavaNode {
|
||||
return new JavaMethod(this, methodNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
this.cls.getClassInfo().removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return cls.getDecompiledLine();
|
||||
|
||||
@@ -59,6 +59,11 @@ public final class JavaField implements JavaNode {
|
||||
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
this.field.getFieldInfo().removeAlias();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
@@ -63,13 +64,15 @@ public final class JavaMethod implements JavaNode {
|
||||
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
|
||||
}
|
||||
|
||||
public List<JavaNode> getOverrideRelatedMethods() {
|
||||
public List<JavaMethod> getOverrideRelatedMethods() {
|
||||
MethodOverrideAttr ovrdAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (ovrdAttr == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||
return decompiler.convertNodes(ovrdAttr.getRelatedMthNodes());
|
||||
return ovrdAttr.getRelatedMthNodes().stream()
|
||||
.map(m -> ((JavaMethod) decompiler.convertNode(m)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
@@ -90,6 +93,11 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
this.mth.getMethodInfo().removeAlias();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -17,4 +17,7 @@ public interface JavaNode {
|
||||
int getDefPos();
|
||||
|
||||
List<JavaNode> getUseIn();
|
||||
|
||||
default void removeAlias() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,68 +2,85 @@ package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
|
||||
public class JavaVariable implements JavaNode {
|
||||
private final JavaClass cls;
|
||||
private final VariableNode node;
|
||||
private final JavaMethod mth;
|
||||
private final VarRef varRef;
|
||||
|
||||
public JavaVariable(JavaClass cls, VariableNode node) {
|
||||
this.cls = Objects.requireNonNull(cls);
|
||||
this.node = Objects.requireNonNull(node);
|
||||
public JavaVariable(JavaMethod mth, VarRef varRef) {
|
||||
this.mth = mth;
|
||||
this.varRef = varRef;
|
||||
}
|
||||
|
||||
public VariableNode getVariableNode() {
|
||||
return node;
|
||||
public JavaMethod getMth() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public int getReg() {
|
||||
return varRef.getReg();
|
||||
}
|
||||
|
||||
public int getSsa() {
|
||||
return varRef.getSsa();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return node.getName();
|
||||
return varRef.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return node.getName();
|
||||
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return cls;
|
||||
return mth.getDeclaringClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return cls.getTopParentClass();
|
||||
return mth.getTopParentClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return node.getDecompiledLine();
|
||||
if (varRef instanceof VarDeclareRef) {
|
||||
return ((VarDeclareRef) varRef).getDecompiledLine();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return node.getDefPosition();
|
||||
if (varRef instanceof VarDeclareRef) {
|
||||
return ((VarDeclareRef) varRef).getDefPosition();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return Collections.emptyList();
|
||||
return Collections.singletonList(mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return node.hashCode();
|
||||
return varRef.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof JavaVariable) {
|
||||
return node.equals(((JavaVariable) obj).getVariableNode());
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (!(o instanceof JavaVariable)) {
|
||||
return false;
|
||||
}
|
||||
return varRef.equals(((JavaVariable) o).varRef);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package jadx.api.data;
|
||||
|
||||
public enum CodeRefType {
|
||||
MTH_ARG,
|
||||
VAR,
|
||||
CATCH,
|
||||
INSN,
|
||||
}
|
||||
@@ -6,17 +6,8 @@ public interface ICodeComment extends Comparable<ICodeComment> {
|
||||
|
||||
IJavaNodeRef getNodeRef();
|
||||
|
||||
String getComment();
|
||||
|
||||
/**
|
||||
* Instruction offset inside method
|
||||
*/
|
||||
int getOffset();
|
||||
|
||||
enum AttachType {
|
||||
VAR_DECLARE
|
||||
}
|
||||
|
||||
@Nullable
|
||||
AttachType getAttachType();
|
||||
IJavaCodeRef getCodeRef();
|
||||
|
||||
String getComment();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.util.List;
|
||||
|
||||
public interface ICodeData {
|
||||
|
||||
long getUpdateId();
|
||||
|
||||
List<ICodeComment> getComments();
|
||||
|
||||
List<ICodeRename> getRenames();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package jadx.api.data;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ICodeRename extends Comparable<ICodeRename> {
|
||||
|
||||
IJavaNodeRef getNodeRef();
|
||||
|
||||
@Nullable
|
||||
IJavaCodeRef getCodeRef();
|
||||
|
||||
String getNewName();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package jadx.api.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface IJavaCodeRef extends Comparable<IJavaCodeRef> {
|
||||
|
||||
CodeRefType getAttachType();
|
||||
|
||||
int getIndex();
|
||||
|
||||
@Override
|
||||
default int compareTo(@NotNull IJavaCodeRef o) {
|
||||
return Integer.compare(getIndex(), o.getIndex());
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package jadx.api.data;
|
||||
public interface IJavaNodeRef extends Comparable<IJavaNodeRef> {
|
||||
|
||||
enum RefType {
|
||||
CLASS, FIELD, METHOD
|
||||
CLASS, FIELD, METHOD, PKG
|
||||
}
|
||||
|
||||
RefType getType();
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
|
||||
public class CustomOffsetRef implements ICodeRawOffset {
|
||||
private final int offset;
|
||||
private final ICodeComment.AttachType attachType;
|
||||
|
||||
public CustomOffsetRef(int offset, ICodeComment.AttachType attachType) {
|
||||
this.offset = offset;
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public ICodeComment.AttachType getAttachType() {
|
||||
return attachType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CustomOffsetRef{offset=" + offset + ", attachType=" + attachType + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class VarDeclareRef extends VarRef implements ILineAttributeNode {
|
||||
|
||||
public static VarDeclareRef get(MethodNode mth, CodeVar codeVar) {
|
||||
VarDeclareRef ref = new VarDeclareRef(mth, codeVar);
|
||||
codeVar.setCachedVarRef(ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
private int sourceLine;
|
||||
private int decompiledLine;
|
||||
private int defPosition;
|
||||
|
||||
private VarDeclareRef(MethodNode mth, CodeVar codeVar) {
|
||||
super(mth, codeVar.getAnySsaVar());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSourceLine() {
|
||||
return sourceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSourceLine(int sourceLine) {
|
||||
this.sourceLine = sourceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return decompiledLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPosition() {
|
||||
return defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefPosition(int pos) {
|
||||
this.defPosition = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarDeclareRef{r" + getReg() + 'v' + getSsa() + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class VarRef {
|
||||
|
||||
@Nullable
|
||||
public static VarRef get(MethodNode mth, RegisterArg reg) {
|
||||
SSAVar ssaVar = reg.getSVar();
|
||||
if (ssaVar == null) {
|
||||
return null;
|
||||
}
|
||||
CodeVar codeVar = ssaVar.getCodeVar();
|
||||
VarRef cachedVarRef = codeVar.getCachedVarRef();
|
||||
if (cachedVarRef != null) {
|
||||
if (cachedVarRef.getName() == null) {
|
||||
cachedVarRef.setName(codeVar.getName());
|
||||
}
|
||||
return cachedVarRef;
|
||||
}
|
||||
VarRef newVarRef = new VarRef(mth, ssaVar);
|
||||
codeVar.setCachedVarRef(newVarRef);
|
||||
return newVarRef;
|
||||
}
|
||||
|
||||
private final MethodNode mth;
|
||||
private final int reg;
|
||||
private final int ssa;
|
||||
private final ArgType type;
|
||||
private String name;
|
||||
|
||||
protected VarRef(MethodNode mth, SSAVar ssaVar) {
|
||||
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
||||
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
||||
}
|
||||
|
||||
private VarRef(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
||||
this.mth = mth;
|
||||
this.reg = reg;
|
||||
this.ssa = ssa;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public MethodNode getMth() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public int getReg() {
|
||||
return reg;
|
||||
}
|
||||
|
||||
public int getSsa() {
|
||||
return ssa;
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof VarRef)) {
|
||||
return false;
|
||||
}
|
||||
VarRef other = (VarRef) o;
|
||||
return getReg() == other.getReg()
|
||||
&& getSsa() == other.getSsa()
|
||||
&& getMth().equals(other.getMth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * getReg() + getSsa();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarUseRef{r" + reg + 'v' + ssa + '}';
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,27 @@
|
||||
package jadx.api.data.impl;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
|
||||
public class JadxCodeComment implements ICodeComment {
|
||||
|
||||
private IJavaNodeRef nodeRef;
|
||||
@Nullable
|
||||
private IJavaCodeRef codeRef;
|
||||
private String comment;
|
||||
private int offset;
|
||||
private AttachType attachType;
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
|
||||
this(nodeRef, comment, -1, null);
|
||||
this(nodeRef, null, comment);
|
||||
}
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, int offset) {
|
||||
this(nodeRef, comment, offset, null);
|
||||
}
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, int offset, AttachType attachType) {
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment) {
|
||||
this.nodeRef = nodeRef;
|
||||
this.codeRef = codeRef;
|
||||
this.comment = comment;
|
||||
this.offset = offset;
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
public JadxCodeComment() {
|
||||
@@ -42,6 +37,16 @@ public class JadxCodeComment implements ICodeComment {
|
||||
this.nodeRef = nodeRef;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IJavaCodeRef getCodeRef() {
|
||||
return codeRef;
|
||||
}
|
||||
|
||||
public void setCodeRef(@Nullable IJavaCodeRef codeRef) {
|
||||
this.codeRef = codeRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
return comment;
|
||||
@@ -51,35 +56,23 @@ public class JadxCodeComment implements ICodeComment {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachType getAttachType() {
|
||||
return attachType;
|
||||
}
|
||||
|
||||
public void setAttachType(AttachType attachType) {
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
private static final Comparator<ICodeComment> COMPARATOR = Comparator
|
||||
.comparing(ICodeComment::getNodeRef)
|
||||
.thenComparing(ICodeComment::getOffset);
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ICodeComment other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
|
||||
if (cmpNodeRef != 0) {
|
||||
return cmpNodeRef;
|
||||
}
|
||||
if (this.getCodeRef() != null && other.getCodeRef() != null) {
|
||||
return this.getCodeRef().compareTo(other.getCodeRef());
|
||||
}
|
||||
return this.getComment().compareTo(other.getComment());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxCodeComment{" + nodeRef + ", comment='" + comment + '\'' + ", offset=" + offset + '}';
|
||||
return "JadxCodeComment{" + nodeRef
|
||||
+ ", ref=" + codeRef
|
||||
+ ", comment='" + comment + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,20 +5,11 @@ import java.util.List;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.data.ICodeRename;
|
||||
|
||||
public class JadxCodeData implements ICodeData {
|
||||
|
||||
private long updateId = System.currentTimeMillis();
|
||||
private List<ICodeComment> comments = Collections.emptyList();
|
||||
|
||||
@Override
|
||||
public long getUpdateId() {
|
||||
return updateId;
|
||||
}
|
||||
|
||||
public void markUpdate() {
|
||||
updateId = System.currentTimeMillis();
|
||||
}
|
||||
private List<ICodeRename> renames = Collections.emptyList();
|
||||
|
||||
@Override
|
||||
public List<ICodeComment> getComments() {
|
||||
@@ -26,24 +17,15 @@ public class JadxCodeData implements ICodeData {
|
||||
}
|
||||
|
||||
public void setComments(List<ICodeComment> comments) {
|
||||
markUpdate();
|
||||
this.comments = comments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(updateId);
|
||||
public List<ICodeRename> getRenames() {
|
||||
return renames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JadxCodeData)) {
|
||||
return false;
|
||||
}
|
||||
JadxCodeData that = (JadxCodeData) o;
|
||||
return updateId == that.updateId;
|
||||
public void setRenames(List<ICodeRename> renames) {
|
||||
this.renames = renames;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package jadx.api.data.impl;
|
||||
|
||||
import jadx.api.JavaVariable;
|
||||
import jadx.api.data.CodeRefType;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
|
||||
public class JadxCodeRef implements IJavaCodeRef {
|
||||
|
||||
public static JadxCodeRef forInsn(int offset) {
|
||||
return new JadxCodeRef(CodeRefType.INSN, offset);
|
||||
}
|
||||
|
||||
public static JadxCodeRef forMthArg(int argIndex) {
|
||||
return new JadxCodeRef(CodeRefType.MTH_ARG, argIndex);
|
||||
}
|
||||
|
||||
public static JadxCodeRef forVar(int regNum, int ssaVersion) {
|
||||
return new JadxCodeRef(CodeRefType.VAR, regNum << 16 | ssaVersion);
|
||||
}
|
||||
|
||||
public static JadxCodeRef forVar(JavaVariable javaVariable) {
|
||||
return forVar(javaVariable.getReg(), javaVariable.getSsa());
|
||||
}
|
||||
|
||||
public static JadxCodeRef forVar(VarRef varRef) {
|
||||
return forVar(varRef.getReg(), varRef.getSsa());
|
||||
}
|
||||
|
||||
public static JadxCodeRef forCatch(int handlerOffset) {
|
||||
return new JadxCodeRef(CodeRefType.CATCH, handlerOffset);
|
||||
}
|
||||
|
||||
private CodeRefType attachType;
|
||||
private int index;
|
||||
|
||||
public JadxCodeRef(CodeRefType attachType, int index) {
|
||||
this.attachType = attachType;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public JadxCodeRef() {
|
||||
// used for json serialization
|
||||
}
|
||||
|
||||
public CodeRefType getAttachType() {
|
||||
return attachType;
|
||||
}
|
||||
|
||||
public void setAttachType(CodeRefType attachType) {
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JadxCodeRef)) {
|
||||
return false;
|
||||
}
|
||||
JadxCodeRef other = (JadxCodeRef) o;
|
||||
return getIndex() == other.getIndex()
|
||||
&& getAttachType() == other.getAttachType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * getAttachType().hashCode() + getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxCodeRef{"
|
||||
+ "attachType=" + attachType
|
||||
+ ", index=" + index
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package jadx.api.data.impl;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
|
||||
public class JadxCodeRename implements ICodeRename {
|
||||
private IJavaNodeRef nodeRef;
|
||||
@Nullable
|
||||
private IJavaCodeRef codeRef;
|
||||
private String newName;
|
||||
|
||||
public JadxCodeRename(IJavaNodeRef nodeRef, String newName) {
|
||||
this(nodeRef, null, newName);
|
||||
}
|
||||
|
||||
public JadxCodeRename(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String newName) {
|
||||
this.nodeRef = nodeRef;
|
||||
this.codeRef = codeRef;
|
||||
this.newName = newName;
|
||||
}
|
||||
|
||||
public JadxCodeRename() {
|
||||
// used in json serialization
|
||||
}
|
||||
|
||||
@Override
|
||||
public IJavaNodeRef getNodeRef() {
|
||||
return nodeRef;
|
||||
}
|
||||
|
||||
public void setNodeRef(IJavaNodeRef nodeRef) {
|
||||
this.nodeRef = nodeRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IJavaCodeRef getCodeRef() {
|
||||
return codeRef;
|
||||
}
|
||||
|
||||
public void setCodeRef(IJavaCodeRef codeRef) {
|
||||
this.codeRef = codeRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNewName() {
|
||||
return newName;
|
||||
}
|
||||
|
||||
public void setNewName(String newName) {
|
||||
this.newName = newName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ICodeRename other) {
|
||||
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
|
||||
if (cmpNodeRef != 0) {
|
||||
return cmpNodeRef;
|
||||
}
|
||||
if (this.getCodeRef() != null && other.getCodeRef() != null) {
|
||||
return this.getCodeRef().compareTo(other.getCodeRef());
|
||||
}
|
||||
return this.getNewName().compareTo(other.getNewName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ICodeRename)) {
|
||||
return false;
|
||||
}
|
||||
ICodeRename other = (ICodeRename) o;
|
||||
return getNodeRef().equals(other.getNodeRef())
|
||||
&& Objects.equals(getCodeRef(), other.getCodeRef());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * getNodeRef().hashCode() + Objects.hashCode(getCodeRef());
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public class JadxNodeRef implements IJavaNodeRef {
|
||||
}
|
||||
|
||||
public static JadxNodeRef forCls(JavaClass cls) {
|
||||
return new JadxNodeRef(RefType.CLASS, cls.getClassNode().getClassInfo().getFullName(), null);
|
||||
return new JadxNodeRef(RefType.CLASS, getClassRefStr(cls), null);
|
||||
}
|
||||
|
||||
public static JadxNodeRef forCls(String clsFullName) {
|
||||
@@ -38,16 +38,24 @@ public class JadxNodeRef implements IJavaNodeRef {
|
||||
|
||||
public static JadxNodeRef forMth(JavaMethod mth) {
|
||||
return new JadxNodeRef(RefType.METHOD,
|
||||
mth.getDeclaringClass().getClassNode().getClassInfo().getFullName(),
|
||||
getClassRefStr(mth.getDeclaringClass()),
|
||||
mth.getMethodNode().getMethodInfo().getShortId());
|
||||
}
|
||||
|
||||
public static JadxNodeRef forFld(JavaField fld) {
|
||||
return new JadxNodeRef(RefType.FIELD,
|
||||
fld.getDeclaringClass().getClassNode().getClassInfo().getFullName(),
|
||||
getClassRefStr(fld.getDeclaringClass()),
|
||||
fld.getFieldNode().getFieldInfo().getShortId());
|
||||
}
|
||||
|
||||
public static JadxNodeRef forPkg(String pkgFullName) {
|
||||
return new JadxNodeRef(RefType.PKG, pkgFullName, "");
|
||||
}
|
||||
|
||||
private static String getClassRefStr(JavaClass cls) {
|
||||
return cls.getClassNode().getClassInfo().getRawName();
|
||||
}
|
||||
|
||||
private RefType refType;
|
||||
private String declClass;
|
||||
@Nullable
|
||||
@@ -124,6 +132,7 @@ public class JadxNodeRef implements IJavaNodeRef {
|
||||
public String toString() {
|
||||
switch (refType) {
|
||||
case CLASS:
|
||||
case PKG:
|
||||
return declClass;
|
||||
case FIELD:
|
||||
case METHOD:
|
||||
|
||||
@@ -9,7 +9,7 @@ import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
|
||||
@@ -102,25 +102,31 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
}
|
||||
|
||||
private static final class DefinitionWrapper {
|
||||
private final LineAttrNode node;
|
||||
private final ILineAttributeNode node;
|
||||
|
||||
private DefinitionWrapper(LineAttrNode node) {
|
||||
private DefinitionWrapper(ILineAttributeNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public LineAttrNode getNode() {
|
||||
public ILineAttributeNode getNode() {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachDefinition(LineAttrNode obj) {
|
||||
public void attachDefinition(ILineAttributeNode obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachAnnotation(Object obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
|
||||
}
|
||||
|
||||
@@ -173,7 +179,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
annotations.entrySet().removeIf(entry -> {
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||
ILineAttributeNode l = ((DefinitionWrapper) v).getNode();
|
||||
CodePosition codePos = entry.getKey();
|
||||
l.setDecompiledLine(codePos.getLine());
|
||||
l.setDefPosition(codePos.getPos());
|
||||
|
||||
@@ -10,7 +10,7 @@ import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
@@ -193,7 +193,7 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachDefinition(LineAttrNode obj) {
|
||||
public void attachDefinition(ILineAttributeNode obj) {
|
||||
// no op
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
|
||||
import jadx.core.dex.visitors.ReSugarCode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.ShadowFieldVisitor;
|
||||
import jadx.core.dex.visitors.SignatureProcessor;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
@@ -54,6 +53,8 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
|
||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
@@ -127,6 +128,7 @@ public class Jadx {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
passes.add(new CodeRenameVisitor());
|
||||
if (args.isInlineMethods()) {
|
||||
passes.add(new InlineMethods());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -45,7 +47,6 @@ import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.Named;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
@@ -55,13 +56,11 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.nodes.VariableNode.VarKind;
|
||||
import static jadx.core.utils.android.AndroidResourcesUtils.handleAppResField;
|
||||
|
||||
public class InsnGen {
|
||||
@@ -107,25 +106,16 @@ public class InsnGen {
|
||||
|
||||
public void addArg(ICodeWriter code, InsnArg arg, Set<Flags> flags) throws CodegenException {
|
||||
if (arg.isRegister()) {
|
||||
CodeVar codeVar = CodeGenUtils.getCodeVar((RegisterArg) arg);
|
||||
if (codeVar != null) {
|
||||
VariableNode node = mth.getVariable(codeVar.getIndex());
|
||||
if (node != null) {
|
||||
code.attachAnnotation(node);
|
||||
}
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachAnnotation(VarRef.get(mth, reg));
|
||||
}
|
||||
code.add(mgen.getNameGen().useArg((RegisterArg) arg));
|
||||
code.add(mgen.getNameGen().useArg(reg));
|
||||
} else if (arg.isLiteral()) {
|
||||
code.add(lit((LiteralArg) arg));
|
||||
} else if (arg.isInsnWrap()) {
|
||||
addWrappedArg(code, (InsnWrapArg) arg, flags);
|
||||
} else if (arg.isNamed()) {
|
||||
if (arg instanceof NamedArg) {
|
||||
VariableNode node = mth.getVariable(((NamedArg) arg).getIndex());
|
||||
if (node != null) {
|
||||
code.attachAnnotation(node);
|
||||
}
|
||||
}
|
||||
code.add(((Named) arg).getName());
|
||||
} else {
|
||||
throw new CodegenException("Unknown arg type " + arg);
|
||||
@@ -162,16 +152,10 @@ public class InsnGen {
|
||||
}
|
||||
useType(code, codeVar.getType());
|
||||
code.add(' ');
|
||||
VariableNode node = mth.declareVar(codeVar, mgen.getNameGen(), VarKind.VAR);
|
||||
String name;
|
||||
if (node != null) {
|
||||
code.attachDefinition(node);
|
||||
name = node.getName();
|
||||
codeVar.setName(name);
|
||||
} else {
|
||||
name = mgen.getNameGen().assignArg(codeVar);
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
||||
}
|
||||
code.add(name);
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
@@ -33,7 +34,6 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
@@ -46,7 +46,6 @@ import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.COMMENTED_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.FALLBACK_MODE;
|
||||
import static jadx.core.dex.nodes.VariableNode.VarKind;
|
||||
|
||||
public class MethodGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||
@@ -228,19 +227,10 @@ public class MethodGen {
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
code.add(' ');
|
||||
VariableNode node = mth.declareVar(var, nameGen, VarKind.ARG);
|
||||
String name;
|
||||
if (node != null) {
|
||||
code.attachDefinition(node);
|
||||
name = node.getName();
|
||||
var.setName(name);
|
||||
} else {
|
||||
name = nameGen.assignArg(var);
|
||||
if (code.isMetadataSupported() && ssaVar != null) {
|
||||
code.attachAnnotation(VarDeclareRef.get(mth, var));
|
||||
}
|
||||
if (var.isThis()) {
|
||||
code.attachDefinition(mth.getParentClass());
|
||||
}
|
||||
code.add(name);
|
||||
code.add(nameGen.assignArg(var));
|
||||
|
||||
i++;
|
||||
if (it.hasNext()) {
|
||||
|
||||
@@ -10,9 +10,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.annotations.CustomOffsetRef;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -32,7 +31,6 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SwitchRegion.CaseInfo;
|
||||
@@ -52,8 +50,6 @@ import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.nodes.VariableNode.VarKind;
|
||||
|
||||
public class RegionGen extends InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
|
||||
|
||||
@@ -73,23 +69,11 @@ public class RegionGen extends InsnGen {
|
||||
code.startLine();
|
||||
declareVar(code, v);
|
||||
code.add(';');
|
||||
attachVariableCommentsData(code, v);
|
||||
CodeGenUtils.addCodeComments(code, mth, v.getAnySsaVar().getAssign());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void attachVariableCommentsData(ICodeWriter code, CodeVar v) {
|
||||
RegisterArg assignReg = v.getSsaVars().get(0).getAssign();
|
||||
if (code.isMetadataSupported()) {
|
||||
InsnNode parentInsn = assignReg.getParentInsn();
|
||||
if (parentInsn != null) {
|
||||
int offset = parentInsn.getOffset();
|
||||
code.attachLineAnnotation(new CustomOffsetRef(offset, ICodeComment.AttachType.VAR_DECLARE));
|
||||
}
|
||||
}
|
||||
CodeGenUtils.addCodeComments(code, mth, assignReg);
|
||||
}
|
||||
|
||||
private void makeRegionIndent(ICodeWriter code, IContainer region) throws CodegenException {
|
||||
code.incIndent();
|
||||
makeRegion(code, region);
|
||||
@@ -362,29 +346,13 @@ public class RegionGen extends InsnGen {
|
||||
if (arg == null) {
|
||||
code.add("unknown"); // throwing exception is too late at this point
|
||||
} else if (arg instanceof RegisterArg) {
|
||||
String name;
|
||||
CodeVar codeVar = CodeGenUtils.getCodeVar((RegisterArg) arg);
|
||||
if (codeVar != null) {
|
||||
VariableNode node = mth.declareVar(codeVar, mgen.getNameGen(), VarKind.CATCH_ARG);
|
||||
if (node != null) {
|
||||
code.attachDefinition(node);
|
||||
name = node.getName();
|
||||
codeVar.setName(name);
|
||||
} else {
|
||||
name = mgen.getNameGen().assignArg(codeVar);
|
||||
}
|
||||
} else {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
name = mgen.getNameGen().assignArg(reg.getSVar().getCodeVar());
|
||||
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachAnnotation(VarDeclareRef.get(mth, codeVar));
|
||||
}
|
||||
code.add(name);
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
VariableNode node = mth.declareVar((NamedArg) arg, mgen.getNameGen(), VarKind.CATCH_ARG);
|
||||
if (node != null) {
|
||||
code.add(node.getName());
|
||||
} else {
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
}
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@@ -9,10 +9,8 @@ import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
@@ -23,7 +21,6 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@@ -39,7 +36,6 @@ public class DeobfPresets {
|
||||
private final Map<String, String> clsPresetMap = new HashMap<>();
|
||||
private final Map<String, String> fldPresetMap = new HashMap<>();
|
||||
private final Map<String, String> mthPresetMap = new HashMap<>();
|
||||
private final Map<String, Set<String>> varPresetMap = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
public static DeobfPresets build(RootNode root) {
|
||||
@@ -47,7 +43,7 @@ public class DeobfPresets {
|
||||
if (deobfMapPath == null) {
|
||||
return null;
|
||||
}
|
||||
LOG.info("Deobfuscation map file set to: {}", deobfMapPath);
|
||||
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
|
||||
return new DeobfPresets(deobfMapPath);
|
||||
}
|
||||
|
||||
@@ -106,11 +102,7 @@ public class DeobfPresets {
|
||||
mthPresetMap.put(origName, alias);
|
||||
break;
|
||||
case 'v':
|
||||
String[] mthIDAndVarIndex = origName.split(VariableNode.VAR_SEPARATOR);
|
||||
if (mthIDAndVarIndex.length == 2) {
|
||||
Set<String> nameList = varPresetMap.computeIfAbsent(mthIDAndVarIndex[0], k -> new HashSet<>());
|
||||
nameList.add(makeVarSecIndex(mthIDAndVarIndex[1], alias));
|
||||
}
|
||||
// deprecated
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -119,10 +111,6 @@ public class DeobfPresets {
|
||||
}
|
||||
}
|
||||
|
||||
public static String makeVarSecIndex(String indexes, String name) {
|
||||
return indexes + VariableNode.VAR_SEPARATOR + name;
|
||||
}
|
||||
|
||||
private static String[] splitAndTrim(String str) {
|
||||
String[] v = str.substring(2).split("=");
|
||||
for (int i = 0; i < v.length; i++) {
|
||||
@@ -145,15 +133,6 @@ public class DeobfPresets {
|
||||
for (Map.Entry<String, String> mthEntry : mthPresetMap.entrySet()) {
|
||||
list.add(String.format("m %s = %s", mthEntry.getKey(), mthEntry.getValue()));
|
||||
}
|
||||
for (Map.Entry<String, Set<String>> varEntry : varPresetMap.entrySet()) {
|
||||
for (String val : varEntry.getValue()) {
|
||||
String[] indexAndName = val.split(VariableNode.VAR_SEPARATOR);
|
||||
if (indexAndName.length == 2) {
|
||||
list.add(String.format("v %s%s%s = %s",
|
||||
varEntry.getKey(), VariableNode.VAR_SEPARATOR, indexAndName[0], indexAndName[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(list);
|
||||
Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
|
||||
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
@@ -183,18 +162,10 @@ public class DeobfPresets {
|
||||
return mthPresetMap.get(mth.getRawFullId());
|
||||
}
|
||||
|
||||
public Set<String> getForVars(MethodInfo mth) {
|
||||
if (varPresetMap.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return varPresetMap.get(mth.getRawFullId());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
clsPresetMap.clear();
|
||||
fldPresetMap.clear();
|
||||
mthPresetMap.clear();
|
||||
varPresetMap.clear();
|
||||
}
|
||||
|
||||
public Path getDeobfMapFile() {
|
||||
@@ -216,18 +187,4 @@ public class DeobfPresets {
|
||||
public Map<String, String> getMthPresetMap() {
|
||||
return mthPresetMap;
|
||||
}
|
||||
|
||||
public Map<String, Set<String>> getVarPresetMap() {
|
||||
return varPresetMap;
|
||||
}
|
||||
|
||||
public void updateVariableName(VariableNode node, String name) {
|
||||
String key = node.getRenameKey();
|
||||
key = key.substring(0, key.indexOf(VariableNode.VAR_SEPARATOR));
|
||||
String newIndex = makeVarSecIndex(node.makeVarIndex(), name);
|
||||
String oldIndex = makeVarSecIndex(node.makeVarIndex(), node.getName());
|
||||
Set<String> indexSet = varPresetMap.computeIfAbsent(key, k -> new HashSet<>());
|
||||
indexSet.remove(oldIndex);
|
||||
indexSet.add(newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,14 @@ package jadx.core.deobf;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
@@ -212,11 +219,6 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private void renameMethod(MethodNode mth) {
|
||||
MethodInfo mthInfo = mth.getMethodInfo();
|
||||
Set<String> names = deobfPresets.getForVars(mthInfo);
|
||||
if (names != null) {
|
||||
mthInfo.setVarNameMap(names);
|
||||
}
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
applyMethodAlias(mth, alias);
|
||||
@@ -256,8 +258,10 @@ public class Deobfuscator {
|
||||
/**
|
||||
* Gets package node for full package name
|
||||
*
|
||||
* @param fullPkgName full package name
|
||||
* @param create if {@code true} then will create all absent objects
|
||||
* @param fullPkgName
|
||||
* full package name
|
||||
* @param create
|
||||
* if {@code true} then will create all absent objects
|
||||
* @return package node object or {@code null} if no package found and <b>create</b> set to
|
||||
* {@code false}
|
||||
*/
|
||||
@@ -338,6 +342,21 @@ public class Deobfuscator {
|
||||
|
||||
public String getPkgAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (classInfo.hasAliasPkg()) {
|
||||
// already renamed
|
||||
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
|
||||
// update all parts of package
|
||||
String[] aliasParts = classInfo.getAliasPkg().split("\\.");
|
||||
PackageNode subPkg = pkg;
|
||||
for (int i = aliasParts.length - 1; i >= 0; i--) {
|
||||
String aliasPart = aliasParts[i];
|
||||
if (!subPkg.getName().equals(aliasPart)) {
|
||||
subPkg.setAlias(aliasPart);
|
||||
}
|
||||
subPkg = subPkg.getParentPackage();
|
||||
}
|
||||
return pkg.getFullAlias();
|
||||
}
|
||||
PackageNode pkg;
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
|
||||
@@ -55,6 +55,7 @@ public class PackageNode {
|
||||
|
||||
public void setAlias(String alias) {
|
||||
packageAlias = alias;
|
||||
cachedPackageFullAlias = null;
|
||||
}
|
||||
|
||||
public boolean hasAlias() {
|
||||
@@ -109,7 +110,8 @@ public class PackageNode {
|
||||
/**
|
||||
* Gets inner package node by name
|
||||
*
|
||||
* @param name inner package name
|
||||
* @param name
|
||||
* inner package name
|
||||
* @return package node or {@code null}
|
||||
*/
|
||||
public PackageNode getInnerPackageByName(String name) {
|
||||
@@ -143,6 +145,9 @@ public class PackageNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return packageAlias;
|
||||
if (packageAlias != null) {
|
||||
return packageName + "[alias:" + packageAlias + "]";
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
public interface ILineAttributeNode {
|
||||
int getSourceLine();
|
||||
|
||||
void setSourceLine(int sourceLine);
|
||||
|
||||
int getDecompiledLine();
|
||||
|
||||
void setDecompiledLine(int line);
|
||||
|
||||
int getDefPosition();
|
||||
|
||||
void setDefPosition(int pos);
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
|
||||
public abstract class LineAttrNode extends AttrNode {
|
||||
public abstract class LineAttrNode extends AttrNode implements ILineAttributeNode {
|
||||
|
||||
private int sourceLine;
|
||||
|
||||
@@ -11,26 +12,32 @@ public abstract class LineAttrNode extends AttrNode {
|
||||
// the position exactly where a node declared at in decompiled java code.
|
||||
private int defPosition;
|
||||
|
||||
@Override
|
||||
public int getDefPosition() {
|
||||
return this.defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefPosition(int defPosition) {
|
||||
this.defPosition = defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSourceLine() {
|
||||
return sourceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSourceLine(int sourceLine) {
|
||||
this.sourceLine = sourceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return decompiledLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
}
|
||||
|
||||
@@ -116,6 +116,17 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
return parentClass != null && parentClass.hasAlias();
|
||||
}
|
||||
|
||||
public boolean hasAliasPkg() {
|
||||
if (alias != null) {
|
||||
return !getPackage().equals(getAliasPkg());
|
||||
}
|
||||
return parentClass != null && parentClass.hasAliasPkg();
|
||||
}
|
||||
|
||||
public void removeAlias() {
|
||||
this.alias = null;
|
||||
}
|
||||
|
||||
private void splitAndApplyNames(RootNode root, ArgType type, boolean canBeInner) {
|
||||
String fullObjectName = type.getObject();
|
||||
String clsPkg;
|
||||
|
||||
@@ -52,6 +52,10 @@ public final class FieldInfo {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public void removeAlias() {
|
||||
this.alias = name;
|
||||
}
|
||||
|
||||
public boolean hasAlias() {
|
||||
return !Objects.equals(name, alias);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -13,7 +10,6 @@ import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public final class MethodInfo implements Comparable<MethodInfo> {
|
||||
@@ -27,7 +23,6 @@ public final class MethodInfo implements Comparable<MethodInfo> {
|
||||
private final int hash;
|
||||
|
||||
private String alias;
|
||||
private Map<String, String> varNameMap;
|
||||
|
||||
private MethodInfo(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
this.name = name;
|
||||
@@ -158,33 +153,14 @@ public final class MethodInfo implements Comparable<MethodInfo> {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public void removeAlias() {
|
||||
this.alias = name;
|
||||
}
|
||||
|
||||
public boolean hasAlias() {
|
||||
return !name.equals(alias);
|
||||
}
|
||||
|
||||
public synchronized void setVarNameMap(Set<String> names) {
|
||||
if (varNameMap == null) {
|
||||
varNameMap = new HashMap<>();
|
||||
}
|
||||
for (String name : names) {
|
||||
String[] indexesAndName = name.split(VariableNode.VAR_SEPARATOR);
|
||||
if (indexesAndName.length == 2) {
|
||||
varNameMap.put(indexesAndName[0], indexesAndName[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getVariableName(String indexes) {
|
||||
if (varNameMap != null) {
|
||||
return varNameMap.get(indexes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasVarNameMap() {
|
||||
return varNameMap != null && varNameMap.size() > 0;
|
||||
}
|
||||
|
||||
public int calcHashCode() {
|
||||
return shortId.hashCode() + 31 * declClass.hashCode();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class CodeVar implements VisibleVar {
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
|
||||
public class CodeVar {
|
||||
private String name;
|
||||
private ArgType type; // before type inference can be null and set only for immutable types
|
||||
private List<SSAVar> ssaVars = Collections.emptyList();
|
||||
@@ -13,17 +15,7 @@ public class CodeVar implements VisibleVar {
|
||||
private boolean isThis;
|
||||
private boolean isDeclared;
|
||||
|
||||
private int index = -1;
|
||||
|
||||
@Override
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
private VarRef cachedVarRef; // set and used at codegen stage
|
||||
|
||||
public static CodeVar fromMthArg(RegisterArg mthArg, boolean linkRegister) {
|
||||
CodeVar var = new CodeVar();
|
||||
@@ -38,17 +30,14 @@ public class CodeVar implements VisibleVar {
|
||||
return var;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -74,6 +63,13 @@ public class CodeVar implements VisibleVar {
|
||||
this.ssaVars = ssaVars;
|
||||
}
|
||||
|
||||
public SSAVar getAnySsaVar() {
|
||||
if (ssaVars.isEmpty()) {
|
||||
throw new IllegalStateException("CodeVar without SSA variables attached: " + this);
|
||||
}
|
||||
return ssaVars.get(0);
|
||||
}
|
||||
|
||||
public boolean isFinal() {
|
||||
return isFinal;
|
||||
}
|
||||
@@ -98,6 +94,14 @@ public class CodeVar implements VisibleVar {
|
||||
isDeclared = declared;
|
||||
}
|
||||
|
||||
public VarRef getCachedVarRef() {
|
||||
return cachedVarRef;
|
||||
}
|
||||
|
||||
public void setCachedVarRef(VarRef cachedVarRef) {
|
||||
this.cachedVarRef = cachedVarRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge flags with OR operator
|
||||
*/
|
||||
|
||||
@@ -2,28 +2,16 @@ package jadx.core.dex.instructions.args;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class NamedArg extends InsnArg implements Named, VisibleVar {
|
||||
public final class NamedArg extends InsnArg implements Named {
|
||||
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
private int index = -1;
|
||||
|
||||
public NamedArg(@NotNull String name, @NotNull ArgType type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return name;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
public interface VisibleVar {
|
||||
int getIndex();
|
||||
|
||||
void setIndex(int index);
|
||||
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
|
||||
ArgType getType();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.api.data.ICodeData;
|
||||
|
||||
public interface ICodeDataUpdateListener {
|
||||
|
||||
void updated(ICodeData codeData);
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import jadx.api.plugins.input.data.IDebugInfo;
|
||||
import jadx.api.plugins.input.data.IMethodData;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr;
|
||||
import jadx.core.codegen.NameGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||
@@ -24,13 +23,9 @@ import jadx.core.dex.info.AccessInfo.AFType;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.InsnDecoder;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.args.VisibleVar;
|
||||
import jadx.core.dex.nodes.VariableNode.VarKind;
|
||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
@@ -74,7 +69,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
private Region region;
|
||||
|
||||
private List<MethodNode> useIn = Collections.emptyList();
|
||||
private List<VariableNode> variables = new ArrayList<>();
|
||||
|
||||
public static MethodNode build(ClassNode classNode, IMethodData methodData) {
|
||||
MethodNode methodNode = new MethodNode(classNode, methodData);
|
||||
@@ -102,47 +96,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
unload();
|
||||
}
|
||||
|
||||
public List<VariableNode> getVars() {
|
||||
return new ArrayList<>(variables);
|
||||
}
|
||||
|
||||
public VariableNode getVariable(int index) {
|
||||
if (index >= 0 && index < variables.size()) {
|
||||
return variables.get(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public VariableNode declareVar(VisibleVar var, NameGen nameGen, VarKind varKind) {
|
||||
if (var instanceof CodeVar) {
|
||||
if (((CodeVar) var).isThis()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
VariableNode varNode;
|
||||
int index = var.getIndex();
|
||||
if (index > -1) {
|
||||
varNode = getVariable(var.getIndex());
|
||||
} else {
|
||||
index = variables.size();
|
||||
var.setIndex(index);
|
||||
String name = mthInfo.getVariableName(VariableNode.makeVarIndex(index, varKind));
|
||||
if (name != null) {
|
||||
var.setName(name); // set name with user renamed previously.
|
||||
}
|
||||
if (var instanceof CodeVar) { // let NameGen record this name or gen an valid name.
|
||||
name = nameGen.assignArg((CodeVar) var);
|
||||
} else if (var instanceof NamedArg) {
|
||||
name = nameGen.assignNamedArg((NamedArg) var);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected var type: " + var);
|
||||
}
|
||||
varNode = new VariableNode(this, name, var.getType(), varKind, index);
|
||||
this.variables.add(varNode);
|
||||
}
|
||||
return varNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
loaded = false;
|
||||
|
||||
@@ -18,6 +18,7 @@ import jadx.api.JadxArgs;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.core.Jadx;
|
||||
@@ -51,6 +52,7 @@ public class RootNode {
|
||||
private final JadxArgs args;
|
||||
private final List<IDexTreeVisitor> preDecompilePasses;
|
||||
private final List<IDexTreeVisitor> passes;
|
||||
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
|
||||
|
||||
private final ErrorsCounter errorsCounter = new ErrorsCounter();
|
||||
private final StringUtils stringUtils;
|
||||
@@ -470,6 +472,15 @@ public class RootNode {
|
||||
return jadxArgs.getCodeWriterProvider().apply(jadxArgs);
|
||||
}
|
||||
|
||||
public void registerCodeDataUpdateListener(ICodeDataUpdateListener listener) {
|
||||
this.codeDataUpdateListeners.add(listener);
|
||||
}
|
||||
|
||||
public void notifyCodeDataListeners() {
|
||||
ICodeData codeData = args.getCodeData();
|
||||
codeDataUpdateListeners.forEach(l -> l.updated(codeData));
|
||||
}
|
||||
|
||||
public ClspGraph getClsp() {
|
||||
return clsp;
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class VariableNode extends LineAttrNode {
|
||||
public enum VarKind {
|
||||
// note: better not change the order of these fields,
|
||||
// they are also used for variable renaming
|
||||
VAR, ARG, CATCH_ARG
|
||||
}
|
||||
|
||||
public static final String VAR_SEPARATOR = "--->>"; // do not contain '='
|
||||
private VarKind varKind = VarKind.VAR;
|
||||
private ArgType type;
|
||||
private String name;
|
||||
private int index;
|
||||
MethodNode mth;
|
||||
|
||||
public VariableNode(MethodNode mth, String name, ArgType type, VarKind varKind, int index) {
|
||||
this.mth = mth;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.index = index;
|
||||
this.varKind = varKind;
|
||||
}
|
||||
|
||||
public MethodNode getMethodNode() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public ClassNode getClassNode() {
|
||||
return mth.getParentClass();
|
||||
}
|
||||
|
||||
public VarKind getVarKind() {
|
||||
return this.varKind;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public String getRenameKey() {
|
||||
return mth.getMethodInfo().getRawFullId() + VAR_SEPARATOR + makeVarIndex(index, varKind);
|
||||
}
|
||||
|
||||
public String makeVarIndex() {
|
||||
return makeVarIndex(index, varKind);
|
||||
}
|
||||
|
||||
public static String makeVarIndex(int index, VarKind kind) {
|
||||
return kindToStr(kind) + "@" + kind.ordinal() + "_" + index;
|
||||
}
|
||||
|
||||
private static String kindToStr(VarKind varKind) {
|
||||
String kind;
|
||||
switch (varKind) {
|
||||
case VAR:
|
||||
kind = "var";
|
||||
break;
|
||||
case ARG:
|
||||
kind = "param";
|
||||
break;
|
||||
case CATCH_ARG:
|
||||
kind = "catch";
|
||||
break;
|
||||
default:
|
||||
throw new JadxRuntimeException("Unexpected variable type " + varKind);
|
||||
}
|
||||
return kind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mth.hashCode() + 31 * getDefPosition() + 31 * makeVarIndex().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this == obj;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,27 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.CodeRefType;
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@JadxVisitor(
|
||||
@@ -33,7 +35,13 @@ public class AttachCommentsVisitor extends AbstractVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AttachCommentsVisitor.class);
|
||||
|
||||
private final CommentsData cachedCommentsData = new CommentsData();
|
||||
private Map<String, List<ICodeComment>> clsCommentsMap;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
updateCommentsData(root.getArgs().getCodeData());
|
||||
root.registerCodeDataUpdateListener(this::updateCommentsData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) {
|
||||
@@ -67,14 +75,11 @@ public class AttachCommentsVisitor extends AbstractVisitor {
|
||||
if (methodNode == null) {
|
||||
LOG.warn("Method reference not found: {}", nodeRef);
|
||||
} else {
|
||||
int offset = comment.getOffset();
|
||||
if (offset < 0) {
|
||||
IJavaCodeRef codeRef = comment.getCodeRef();
|
||||
if (codeRef == null) {
|
||||
addComment(methodNode, comment.getComment());
|
||||
} else if (comment.getAttachType() != null) {
|
||||
processCustomAttach(methodNode, comment);
|
||||
} else {
|
||||
InsnNode insn = getInsnByOffset(methodNode, offset);
|
||||
addComment(insn, comment.getComment());
|
||||
processCustomAttach(methodNode, codeRef, comment);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -91,22 +96,14 @@ public class AttachCommentsVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void processCustomAttach(MethodNode mth, ICodeComment comment) {
|
||||
ICodeComment.AttachType attachType = comment.getAttachType();
|
||||
if (attachType == null) {
|
||||
return;
|
||||
}
|
||||
private static void processCustomAttach(MethodNode mth, IJavaCodeRef codeRef, ICodeComment comment) {
|
||||
CodeRefType attachType = codeRef.getAttachType();
|
||||
switch (attachType) {
|
||||
case VAR_DECLARE:
|
||||
InsnNode insn = getInsnByOffset(mth, comment.getOffset());
|
||||
if (insn != null) {
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result != null) {
|
||||
result.addAttr(AType.CODE_COMMENTS, comment.getComment());
|
||||
}
|
||||
}
|
||||
case INSN: {
|
||||
InsnNode insn = getInsnByOffset(mth, codeRef.getIndex());
|
||||
addComment(insn, comment.getComment());
|
||||
break;
|
||||
|
||||
}
|
||||
default:
|
||||
throw new JadxRuntimeException("Unexpected attach type: " + attachType);
|
||||
}
|
||||
@@ -120,37 +117,23 @@ public class AttachCommentsVisitor extends AbstractVisitor {
|
||||
node.addAttr(AType.CODE_COMMENTS, comment);
|
||||
}
|
||||
|
||||
private static final class CommentsData {
|
||||
long updateId;
|
||||
Map<String, List<ICodeComment>> clsCommentsMap;
|
||||
}
|
||||
|
||||
private List<ICodeComment> getCommentsData(ClassNode cls) {
|
||||
ICodeData additionalData = cls.root().getArgs().getCodeData();
|
||||
if (additionalData == null || additionalData.getComments().isEmpty()) {
|
||||
if (clsCommentsMap == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
synchronized (cachedCommentsData) {
|
||||
CommentsData commentsData = this.cachedCommentsData;
|
||||
if (commentsData.updateId != additionalData.getUpdateId()) {
|
||||
updateCommentsData(additionalData, commentsData);
|
||||
}
|
||||
List<ICodeComment> clsComments = commentsData.clsCommentsMap.get(cls.getClassInfo().getFullName());
|
||||
if (clsComments == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return clsComments;
|
||||
List<ICodeComment> clsComments = clsCommentsMap.get(cls.getClassInfo().getRawName());
|
||||
if (clsComments == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return clsComments;
|
||||
}
|
||||
|
||||
private static void updateCommentsData(ICodeData data, CommentsData commentsData) {
|
||||
Map<String, List<ICodeComment>> map = new HashMap<>();
|
||||
for (ICodeComment comment : data.getComments()) {
|
||||
String declClsId = comment.getNodeRef().getDeclaringClass();
|
||||
List<ICodeComment> comments = map.computeIfAbsent(declClsId, s -> new ArrayList<>());
|
||||
comments.add(comment);
|
||||
private void updateCommentsData(@Nullable ICodeData data) {
|
||||
if (data == null) {
|
||||
this.clsCommentsMap = Collections.emptyMap();
|
||||
} else {
|
||||
this.clsCommentsMap = data.getComments().stream()
|
||||
.collect(Collectors.groupingBy(c -> c.getNodeRef().getDeclaringClass()));
|
||||
}
|
||||
commentsData.clsCommentsMap = map;
|
||||
commentsData.updateId = data.getUpdateId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompare;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package jadx.core.dex.visitors.rename;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "ApplyCodeRename",
|
||||
desc = "Rename variables and other entities in methods",
|
||||
runAfter = {
|
||||
InitCodeVariables.class,
|
||||
DebugInfoApplyVisitor.class
|
||||
}
|
||||
)
|
||||
public class CodeRenameVisitor extends AbstractVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeRenameVisitor.class);
|
||||
|
||||
private Map<String, List<ICodeRename>> clsRenamesMap;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
updateRenamesMap(root.getArgs().getCodeData());
|
||||
root.registerCodeDataUpdateListener(this::updateRenamesMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) {
|
||||
List<ICodeRename> renames = getRenames(cls);
|
||||
if (!renames.isEmpty()) {
|
||||
applyRenames(cls, renames);
|
||||
}
|
||||
cls.getInnerClasses().forEach(this::visit);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void applyRenames(ClassNode cls, List<ICodeRename> renames) {
|
||||
for (ICodeRename rename : renames) {
|
||||
IJavaNodeRef nodeRef = rename.getNodeRef();
|
||||
if (nodeRef.getType() == IJavaNodeRef.RefType.METHOD) {
|
||||
MethodNode methodNode = cls.searchMethodByShortId(nodeRef.getShortId());
|
||||
if (methodNode == null) {
|
||||
LOG.warn("Method reference not found: {}", nodeRef);
|
||||
} else {
|
||||
IJavaCodeRef codeRef = rename.getCodeRef();
|
||||
if (codeRef != null) {
|
||||
processRename(methodNode, codeRef, rename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void processRename(MethodNode mth, IJavaCodeRef codeRef, ICodeRename rename) {
|
||||
switch (codeRef.getAttachType()) {
|
||||
case MTH_ARG: {
|
||||
List<RegisterArg> argRegs = mth.getArgRegs();
|
||||
int argNum = codeRef.getIndex();
|
||||
if (argNum < argRegs.size()) {
|
||||
argRegs.get(argNum).getSVar().getCodeVar().setName(rename.getNewName());
|
||||
} else {
|
||||
LOG.warn("Incorrect method arg ref {}, should be less than {}", argNum, argRegs.size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VAR: {
|
||||
int regNum = codeRef.getIndex() >> 16;
|
||||
int ssaVer = codeRef.getIndex() & 0xFFFF;
|
||||
for (SSAVar ssaVar : mth.getSVars()) {
|
||||
if (ssaVar.getRegNum() == regNum && ssaVar.getVersion() == ssaVer) {
|
||||
ssaVar.getCodeVar().setName(rename.getNewName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG.warn("Can't find variable ref by {}_{}", regNum, ssaVer);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG.warn("Rename code ref type {} not yet supported", codeRef.getAttachType());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private List<ICodeRename> getRenames(ClassNode cls) {
|
||||
if (clsRenamesMap == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ICodeRename> clsComments = clsRenamesMap.get(cls.getClassInfo().getFullName());
|
||||
if (clsComments == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return clsComments;
|
||||
}
|
||||
|
||||
private void updateRenamesMap(@Nullable ICodeData data) {
|
||||
if (data == null) {
|
||||
this.clsRenamesMap = Collections.emptyMap();
|
||||
} else {
|
||||
this.clsRenamesMap = data.getRenames().stream()
|
||||
.filter(r -> r.getCodeRef() != null)
|
||||
.collect(Collectors.groupingBy(r -> r.getNodeRef().getDeclaringClass()));
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.core.dex.visitors;
|
||||
package jadx.core.dex.visitors.rename;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@@ -22,6 +22,7 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
|
||||
public class RenameVisitor extends AbstractVisitor {
|
||||
|
||||
@@ -37,11 +38,13 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
private void process(RootNode root) {
|
||||
Deobfuscator deobfuscator = new Deobfuscator(root);
|
||||
JadxArgs args = root.getArgs();
|
||||
|
||||
if (args.isDeobfuscationOn()) {
|
||||
deobfuscator.execute();
|
||||
}
|
||||
|
||||
checkClasses(deobfuscator, root, args);
|
||||
UserRenames.applyForNodes(root);
|
||||
|
||||
if (args.isDeobfuscationOn()) {
|
||||
deobfuscator.savePresets();
|
||||
@@ -0,0 +1,132 @@
|
||||
package jadx.core.dex.visitors.rename;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.InfoStorage;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class UserRenames {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UserRenames.class);
|
||||
|
||||
public static void applyForNodes(RootNode root) {
|
||||
ICodeData codeData = root.getArgs().getCodeData();
|
||||
if (codeData == null || codeData.getRenames().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
InfoStorage infoStorage = root.getInfoStorage();
|
||||
codeData.getRenames().stream()
|
||||
.filter(r -> r.getCodeRef() == null && r.getNodeRef().getType() != IJavaNodeRef.RefType.PKG)
|
||||
.collect(Collectors.groupingBy(r -> r.getNodeRef().getDeclaringClass()))
|
||||
.forEach((clsRawName, renames) -> {
|
||||
ClassInfo clsInfo = infoStorage.getCls(ArgType.object(clsRawName));
|
||||
if (clsInfo != null) {
|
||||
ClassNode cls = root.resolveClass(clsInfo);
|
||||
if (cls != null) {
|
||||
for (ICodeRename rename : renames) {
|
||||
applyRename(cls, rename);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG.warn("Class info with reference '{}' not found", clsRawName);
|
||||
});
|
||||
applyPkgRenames(root, codeData.getRenames());
|
||||
}
|
||||
|
||||
private static void applyRename(ClassNode cls, ICodeRename rename) {
|
||||
IJavaNodeRef nodeRef = rename.getNodeRef();
|
||||
switch (nodeRef.getType()) {
|
||||
case CLASS:
|
||||
cls.getClassInfo().changeShortName(rename.getNewName());
|
||||
break;
|
||||
|
||||
case FIELD:
|
||||
FieldNode fieldNode = cls.searchFieldByShortId(nodeRef.getShortId());
|
||||
if (fieldNode == null) {
|
||||
LOG.warn("Field reference not found: {}", nodeRef);
|
||||
} else {
|
||||
fieldNode.getFieldInfo().setAlias(rename.getNewName());
|
||||
}
|
||||
break;
|
||||
|
||||
case METHOD:
|
||||
MethodNode mth = cls.searchMethodByShortId(nodeRef.getShortId());
|
||||
if (mth == null) {
|
||||
LOG.warn("Method reference not found: {}", nodeRef);
|
||||
} else {
|
||||
IJavaCodeRef codeRef = rename.getCodeRef();
|
||||
if (codeRef == null) {
|
||||
applyMethodRename(mth, rename);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyMethodRename(MethodNode mth, ICodeRename rename) {
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr != null) {
|
||||
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
|
||||
renameMethod(relatedMth, rename);
|
||||
}
|
||||
} else {
|
||||
renameMethod(mth, rename);
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameMethod(MethodNode mth, ICodeRename rename) {
|
||||
mth.getMethodInfo().setAlias(rename.getNewName());
|
||||
}
|
||||
|
||||
// TODO: Very inefficient!!! Add PackageInfo class to build package hierarchy
|
||||
private static void applyPkgRenames(RootNode root, List<ICodeRename> renames) {
|
||||
List<ClassNode> classes = root.getClasses(false);
|
||||
renames.stream()
|
||||
.filter(r -> r.getNodeRef().getType() == IJavaNodeRef.RefType.PKG)
|
||||
.forEach(pkgRename -> {
|
||||
String pkgFullName = pkgRename.getNodeRef().getDeclaringClass();
|
||||
String pkgFullNameDot = pkgFullName + ".";
|
||||
for (ClassNode cls : classes) {
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
String pkg = clsInfo.getPackage();
|
||||
if (pkg.equals(pkgFullName)) {
|
||||
clsInfo.changePkg(cutLastPkgPart(clsInfo.getAliasPkg()) + '.' + pkgRename.getNewName());
|
||||
} else if (pkg.startsWith(pkgFullNameDot)) {
|
||||
clsInfo.changePkg(rebuildPkgMiddle(clsInfo.getAliasPkg(), pkgFullName, pkgRename.getNewName()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String cutLastPkgPart(String pkgFullName) {
|
||||
int lastDotIndex = pkgFullName.lastIndexOf('.');
|
||||
if (lastDotIndex == -1) {
|
||||
return pkgFullName;
|
||||
}
|
||||
return pkgFullName.substring(0, lastDotIndex);
|
||||
}
|
||||
|
||||
private static String rebuildPkgMiddle(String aliasPkg, String renameOriginPkg, String newName) {
|
||||
String[] aliasParts = aliasPkg.split("\\.");
|
||||
String[] renameParts = renameOriginPkg.split("\\.");
|
||||
aliasParts[renameParts.length - 1] = newName;
|
||||
return String.join(".", aliasParts);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||
import jadx.core.utils.input.InsnDataUtils;
|
||||
|
||||
@JadxVisitor(
|
||||
|
||||
@@ -19,7 +19,7 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ public class JadxCodeInfoAssertions extends AbstractObjectAssert<JadxCodeInfoAss
|
||||
|
||||
public JadxCodeInfoAssertions checkCodeOffsets() {
|
||||
long dupOffsetCount = actual.getAnnotations().values().stream()
|
||||
.filter(o -> o instanceof ICodeRawOffset)
|
||||
.filter(ICodeRawOffset.class::isInstance)
|
||||
.collect(Collectors.groupingBy(o -> ((ICodeRawOffset) o).getOffset(), Collectors.toList()))
|
||||
.values().stream()
|
||||
.filter(list -> list.size() > 1)
|
||||
|
||||
@@ -6,9 +6,11 @@ import java.util.Collections;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxCodeRef;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
@@ -17,6 +19,7 @@ import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeComments extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
public static class TestCls {
|
||||
private int intField = 5;
|
||||
|
||||
@@ -32,15 +35,14 @@ public class TestCodeComments extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
int insnOffset = isJavaInput() ? 13 : 11;
|
||||
|
||||
String baseClsId = TestCls.class.getName();
|
||||
ICodeComment clsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId), "class comment");
|
||||
ICodeComment innerClsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId + ".A"), "inner class comment");
|
||||
ICodeComment innerClsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId + "$A"), "inner class comment");
|
||||
ICodeComment fldComment = new JadxCodeComment(new JadxNodeRef(RefType.FIELD, baseClsId, "intField:I"), "field comment");
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test()I");
|
||||
ICodeComment mthComment = new JadxCodeComment(mthRef, "method comment");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "insn comment", insnOffset);
|
||||
IJavaCodeRef insnRef = JadxCodeRef.forInsn(isJavaInput() ? 13 : 11);
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, insnRef, "insn comment");
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
getArgs().setCodeData(codeData);
|
||||
@@ -62,8 +64,9 @@ public class TestCodeComments extends IntegrationTest {
|
||||
.reloadCode(this)
|
||||
.isEqualTo(code);
|
||||
|
||||
ICodeComment updInsnComment = new JadxCodeComment(mthRef, "updated insn comment", insnOffset);
|
||||
ICodeComment updInsnComment = new JadxCodeComment(mthRef, insnRef, "updated insn comment");
|
||||
codeData.setComments(Collections.singletonList(updInsnComment));
|
||||
jadxDecompiler.reloadCodeData();
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.containsOne("System.out.println(\"comment\"); // updated insn comment")
|
||||
|
||||
@@ -5,9 +5,11 @@ import java.util.Arrays;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxCodeRef;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
@@ -31,8 +33,10 @@ public class TestCodeComments2 extends IntegrationTest {
|
||||
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", isJavaInput() ? 13 : 10);
|
||||
ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", isJavaInput() ? 15 : 11);
|
||||
IJavaCodeRef insnRef = JadxCodeRef.forInsn(isJavaInput() ? 13 : 10);
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, insnRef, "return comment");
|
||||
IJavaCodeRef insnRef2 = JadxCodeRef.forInsn(isJavaInput() ? 15 : 11);
|
||||
ICodeComment insnComment2 = new JadxCodeComment(mthRef, insnRef2, "another return comment");
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Arrays.asList(insnComment, insnComment2));
|
||||
|
||||
@@ -6,9 +6,11 @@ import java.util.Random;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxCodeRef;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
@@ -16,6 +18,7 @@ import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeComments2a extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class TestCls {
|
||||
private int f;
|
||||
|
||||
@@ -34,8 +37,10 @@ public class TestCodeComments2a extends IntegrationTest {
|
||||
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", isJavaInput() ? 22 : 18);
|
||||
ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", isJavaInput() ? 27 : 19);
|
||||
IJavaCodeRef insnRef = JadxCodeRef.forInsn(isJavaInput() ? 22 : 18);
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, insnRef, "return comment");
|
||||
IJavaCodeRef insnRef2 = JadxCodeRef.forInsn(isJavaInput() ? 27 : 19);
|
||||
ICodeComment insnComment2 = new JadxCodeComment(mthRef, insnRef2, "another return comment");
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Arrays.asList(insnComment, insnComment2));
|
||||
|
||||
+4
-1
@@ -5,9 +5,11 @@ import java.util.Collections;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxCodeRef;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
@@ -31,7 +33,8 @@ public class TestCodeCommentsMultiline extends IntegrationTest {
|
||||
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "multi\nline\ncomment", isJavaInput() ? 15 : 11);
|
||||
IJavaCodeRef insnRef = JadxCodeRef.forInsn(isJavaInput() ? 15 : 11);
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, insnRef, "multi\nline\ncomment");
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Collections.singletonList(insnComment));
|
||||
|
||||
@@ -32,10 +32,10 @@ public class TestCodeCommentsOverride extends IntegrationTest {
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef iMthRef = new JadxNodeRef(RefType.METHOD, baseClsId + ".I", "mth()V");
|
||||
JadxNodeRef iMthRef = new JadxNodeRef(RefType.METHOD, baseClsId + "$I", "mth()V");
|
||||
ICodeComment iMthComment = new JadxCodeComment(iMthRef, "interface mth comment");
|
||||
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId + ".A", "mth()V");
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId + "$A", "mth()V");
|
||||
ICodeComment mthComment = new JadxCodeComment(mthRef, "mth comment");
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package jadx.tests.integration.rename;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.CodeRefType;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
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.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestUserRenames extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings({ "FieldCanBeLocal", "FieldMayBeFinal" })
|
||||
public static class TestCls {
|
||||
private int intField = 5;
|
||||
|
||||
public static class A {
|
||||
}
|
||||
|
||||
public int test(int x) {
|
||||
int y = x + "test".length();
|
||||
System.out.println(y);
|
||||
int z = y + 1;
|
||||
System.out.println(z);
|
||||
return z;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
getArgs().setDeobfuscationOn(false);
|
||||
|
||||
List<ICodeRename> renames = new ArrayList<>();
|
||||
String baseClsId = TestCls.class.getName();
|
||||
renames.add(new JadxCodeRename(JadxNodeRef.forPkg("jadx.tests"), "renamedPkgTests"));
|
||||
renames.add(new JadxCodeRename(JadxNodeRef.forPkg("jadx.tests.integration.rename"), "renamedPkgRename"));
|
||||
renames.add(new JadxCodeRename(JadxNodeRef.forCls(baseClsId), "RenamedTestCls"));
|
||||
renames.add(new JadxCodeRename(JadxNodeRef.forCls(baseClsId + "$A"), "RenamedInnerCls"));
|
||||
renames.add(new JadxCodeRename(new JadxNodeRef(RefType.FIELD, baseClsId, "intField:I"), "renamedField"));
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(I)I");
|
||||
renames.add(new JadxCodeRename(mthRef, "renamedTestMth"));
|
||||
renames.add(new JadxCodeRename(mthRef, new JadxCodeRef(CodeRefType.MTH_ARG, 0), "renamedX"));
|
||||
JadxCodeRef varDeclareRef = isJavaInput() ? JadxCodeRef.forVar(0, 1) : JadxCodeRef.forVar(0, 0);
|
||||
renames.add(new JadxCodeRename(mthRef, varDeclareRef, "renamedY"));
|
||||
IJavaCodeRef varUseRef = isJavaInput() ? JadxCodeRef.forVar(0, 4) : JadxCodeRef.forVar(1, 0);
|
||||
renames.add(new JadxCodeRename(mthRef, varUseRef, "renamedZ"));
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setRenames(renames);
|
||||
getArgs().setCodeData(codeData);
|
||||
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(cls)
|
||||
.decompile()
|
||||
.checkCodeOffsets()
|
||||
.code()
|
||||
.containsOne("package jadx.renamedPkgTests.integration.renamedPkgRename;")
|
||||
.containsOne("public class RenamedTestCls {")
|
||||
.containsOne("private int renamedField")
|
||||
.containsOne("public static class RenamedInnerCls {")
|
||||
.containsOne("public int renamedTestMth(int renamedX) {")
|
||||
.containsOne("int renamedY = renamedX + \"test\".length();")
|
||||
.containsOne("int renamedZ = renamedY + 1;")
|
||||
.containsOne("return renamedZ;");
|
||||
|
||||
String code = cls.getCode().getCodeStr();
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.isEqualTo(code);
|
||||
|
||||
ICodeRename updVarRename = new JadxCodeRename(mthRef, varUseRef, "anotherZ");
|
||||
codeData.setRenames(Collections.singletonList(updVarRename));
|
||||
jadxDecompiler.reloadCodeData();
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.containsOne("int anotherZ = y + 1;")
|
||||
.doesNotContain("int z")
|
||||
.doesNotContain("int renamedZ");
|
||||
}
|
||||
}
|
||||
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user