Merge branch 'master' into type-inference-wip
# Conflicts: # jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java # jadx-core/src/main/java/jadx/core/dex/visitors/ssa/EliminatePhiNodes.java
This commit is contained in:
@@ -53,13 +53,18 @@ public class JadxArgsValidator {
|
||||
} else {
|
||||
outDir = makeDirFromInput(args);
|
||||
}
|
||||
args.setOutDir(outDir);
|
||||
}
|
||||
if (srcDir == null) {
|
||||
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
|
||||
}
|
||||
if (resDir == null) {
|
||||
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
|
||||
}
|
||||
args.setOutDir(outDir);
|
||||
setFromOut(args);
|
||||
|
||||
checkDir(args.getOutDir());
|
||||
checkDir(args.getOutDirSrc());
|
||||
checkDir(args.getOutDirRes());
|
||||
checkDir(args.getOutDir(), "Output");
|
||||
checkDir(args.getOutDirSrc(), "Source output");
|
||||
checkDir(args.getOutDirRes(), "Resources output");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -79,15 +84,6 @@ public class JadxArgsValidator {
|
||||
return outDir;
|
||||
}
|
||||
|
||||
private static void setFromOut(JadxArgs args) {
|
||||
if (args.getOutDirSrc() == null) {
|
||||
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
|
||||
}
|
||||
if (args.getOutDirRes() == null) {
|
||||
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkFile(File file) {
|
||||
if (!file.exists()) {
|
||||
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
|
||||
@@ -97,9 +93,9 @@ public class JadxArgsValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDir(File dir) {
|
||||
private static void checkDir(File dir, String desc) {
|
||||
if (dir != null && dir.exists() && !dir.isDirectory()) {
|
||||
throw new JadxArgsValidateException("Output directory exists as file " + dir);
|
||||
throw new JadxArgsValidateException(desc + " directory exists as file " + dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,10 +96,10 @@ public class Jadx {
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
|
||||
|
||||
@@ -103,11 +103,11 @@ public class ClsSet {
|
||||
}
|
||||
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
NClass id = names.get(fullName);
|
||||
if (id == null && !names.containsKey(fullName)) {
|
||||
NClass cls = names.get(fullName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Class not found: {}", fullName);
|
||||
}
|
||||
return id;
|
||||
return cls;
|
||||
}
|
||||
|
||||
void save(File output) throws IOException {
|
||||
|
||||
@@ -7,7 +7,7 @@ public class NClass {
|
||||
|
||||
private final String name;
|
||||
private NClass[] parents;
|
||||
private int id;
|
||||
private final int id;
|
||||
|
||||
public NClass(String name, int id) {
|
||||
this.name = name;
|
||||
@@ -22,10 +22,6 @@ public class NClass {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public NClass[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
@@ -624,6 +625,11 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case SUPER:
|
||||
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
|
||||
if (superCallCls != null) {
|
||||
useClass(code, superCallCls);
|
||||
code.add('.');
|
||||
}
|
||||
// use 'super' instead 'this' in 0 arg
|
||||
code.add("super").add('.');
|
||||
k++;
|
||||
@@ -647,6 +653,36 @@ public class InsnGen {
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
|
||||
ClassNode useCls = mth.getParentClass();
|
||||
ClassInfo insnCls = useCls.getAlias();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (insnCls.equals(declClass)) {
|
||||
return null;
|
||||
}
|
||||
ClassNode topClass = useCls.getTopParentClass();
|
||||
if (topClass.getClassInfo().equals(declClass)) {
|
||||
return declClass;
|
||||
}
|
||||
// search call class
|
||||
ClassNode nextParent = useCls;
|
||||
do {
|
||||
ClassInfo nextClsInfo = nextParent.getClassInfo();
|
||||
if (nextClsInfo.equals(declClass)
|
||||
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
|
||||
if (nextParent == useCls) {
|
||||
return null;
|
||||
}
|
||||
return nextClsInfo;
|
||||
}
|
||||
nextParent = nextParent.getParentClass();
|
||||
} while (nextParent != null && nextParent != topClass);
|
||||
|
||||
// search failed, just return parent class
|
||||
return useCls.getParentClass().getClassInfo();
|
||||
}
|
||||
|
||||
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
||||
@Nullable MethodNode callMth) throws CodegenException {
|
||||
int k = startArgNum;
|
||||
@@ -744,6 +780,9 @@ public class InsnGen {
|
||||
return false;
|
||||
}
|
||||
InsnNode inl = mia.getInsn();
|
||||
if (Consts.DEBUG) {
|
||||
code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine();
|
||||
}
|
||||
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
} else {
|
||||
|
||||
@@ -483,21 +483,7 @@ public class Deobfuscator {
|
||||
if (name.length() > maxLength) {
|
||||
return "x" + Integer.toHexString(name.hashCode());
|
||||
}
|
||||
if (!NameMapper.isAllCharsPrintable(name)) {
|
||||
return removeInvalidChars(name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String removeInvalidChars(String name) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
int ch = name.charAt(i);
|
||||
if (NameMapper.isPrintableChar(ch)) {
|
||||
sb.append((char) ch);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
return NameMapper.removeInvalidCharsMiddle(name);
|
||||
}
|
||||
|
||||
private void dumpClassAlias(ClassNode cls) {
|
||||
|
||||
@@ -91,6 +91,14 @@ public class NameMapper {
|
||||
&& isAllCharsPrintable(str);
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifierStart(int codePoint) {
|
||||
return Character.isJavaIdentifierStart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifierPart(int codePoint) {
|
||||
return Character.isJavaIdentifierPart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isPrintableChar(int c) {
|
||||
return 32 <= c && c <= 126;
|
||||
}
|
||||
@@ -105,6 +113,49 @@ public class NameMapper {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return modified string with removed:
|
||||
* <p><ul>
|
||||
* <li> not printable chars (including unicode)
|
||||
* <li> chars not valid for java identifier part
|
||||
* </ul><p>
|
||||
* Note: this 'middle' method must be used with prefixed string:
|
||||
* <p><ul>
|
||||
* <li> can leave invalid chars for java identifier start (i.e numbers)
|
||||
* <li> result not checked for reserved words
|
||||
* </ul><p>
|
||||
*/
|
||||
public static String removeInvalidCharsMiddle(String name) {
|
||||
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
|
||||
return name;
|
||||
}
|
||||
int len = name.length();
|
||||
StringBuilder sb = new StringBuilder(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
int codePoint = name.codePointAt(i);
|
||||
if (isPrintableChar(codePoint) && isValidIdentifierPart(codePoint)) {
|
||||
sb.append((char) codePoint);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string with removed invalid chars, see {@link #removeInvalidCharsMiddle}
|
||||
* <p>
|
||||
* Prepend prefix if first char is not valid as java identifier start char.
|
||||
*/
|
||||
public static String removeInvalidChars(String name, String prefix) {
|
||||
String result = removeInvalidCharsMiddle(name);
|
||||
if (!result.isEmpty()) {
|
||||
int codePoint = result.codePointAt(0);
|
||||
if (!isValidIdentifierStart(codePoint)) {
|
||||
return prefix + result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private NameMapper() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,12 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public String makeSignature(boolean includeRetType) {
|
||||
return makeSignature(false, includeRetType);
|
||||
}
|
||||
|
||||
public String makeSignature(boolean useAlias, boolean includeRetType) {
|
||||
StringBuilder signature = new StringBuilder();
|
||||
signature.append(name);
|
||||
signature.append(useAlias ? alias : name);
|
||||
signature.append('(');
|
||||
for (ArgType arg : args) {
|
||||
signature.append(TypeGen.signature(arg));
|
||||
|
||||
@@ -38,12 +38,12 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
|
||||
|
||||
public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
||||
|
||||
private final DexNode dex;
|
||||
private final ClassInfo clsInfo;
|
||||
private final AccessInfo accessFlags;
|
||||
private AccessInfo accessFlags;
|
||||
private ArgType superClass;
|
||||
private List<ArgType> interfaces;
|
||||
private Map<ArgType, List<ArgType>> genericMap;
|
||||
@@ -409,10 +409,16 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessInfo getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessFlags(AccessInfo accessFlags) {
|
||||
this.accessFlags = accessFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return dex;
|
||||
|
||||
@@ -8,11 +8,11 @@ import jadx.core.dex.info.AccessInfo.AFType;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class FieldNode extends LineAttrNode {
|
||||
public class FieldNode extends LineAttrNode implements ICodeNode {
|
||||
|
||||
private final ClassNode parent;
|
||||
private final FieldInfo fieldInfo;
|
||||
private final AccessInfo accFlags;
|
||||
private AccessInfo accFlags;
|
||||
|
||||
private ArgType type;
|
||||
|
||||
@@ -32,10 +32,16 @@ public class FieldNode extends LineAttrNode {
|
||||
return fieldInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessInfo getAccessFlags() {
|
||||
return accFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessFlags(AccessInfo accFlags) {
|
||||
this.accFlags = accFlags;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return fieldInfo.getName();
|
||||
}
|
||||
@@ -56,6 +62,21 @@ public class FieldNode extends LineAttrNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeName() {
|
||||
return "field";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return parent.dex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootNode root() {
|
||||
return parent.root();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fieldInfo.hashCode();
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
|
||||
public interface ICodeNode extends IDexNode, IAttributeNode {
|
||||
AccessInfo getAccessFlags();
|
||||
|
||||
void setAccessFlags(AccessInfo newAccessFlags);
|
||||
}
|
||||
@@ -46,7 +46,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.Utils.lockList;
|
||||
|
||||
public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
|
||||
|
||||
private final MethodInfo mthInfo;
|
||||
@@ -607,12 +607,14 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
return sVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessInfo getAccessFlags() {
|
||||
return accFlags;
|
||||
}
|
||||
|
||||
public void setAccFlags(AccessInfo accFlags) {
|
||||
this.accFlags = accFlags;
|
||||
@Override
|
||||
public void setAccessFlags(AccessInfo newAccessFlags) {
|
||||
this.accFlags = newAccessFlags;
|
||||
}
|
||||
|
||||
public Region getRegion() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Objects;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
@@ -15,6 +16,7 @@ import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
@@ -140,7 +142,11 @@ public class ClassModifier extends AbstractVisitor {
|
||||
return;
|
||||
}
|
||||
if (removeBridgeMethod(cls, mth)) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
if (Consts.DEBUG) {
|
||||
mth.addAttr(AType.COMMENTS, "Removed as synthetic bridge method");
|
||||
} else {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// remove synthetic constructor for inner classes
|
||||
@@ -219,40 +225,45 @@ public class ClassModifier extends AbstractVisitor {
|
||||
|
||||
private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) {
|
||||
InsnType insnType = insn.getType();
|
||||
if (insnType == InsnType.INVOKE) {
|
||||
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
|
||||
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth);
|
||||
if (wrappedMth != null) {
|
||||
AccessInfo wrappedAccFlags = wrappedMth.getAccessFlags();
|
||||
if (wrappedAccFlags.isStatic()) {
|
||||
return false;
|
||||
}
|
||||
if (callMth.getArgsCount() != mth.getMethodInfo().getArgsCount()) {
|
||||
return false;
|
||||
}
|
||||
// rename method only from current class
|
||||
if (!mth.getParentClass().equals(wrappedMth.getParentClass())) {
|
||||
return false;
|
||||
}
|
||||
// all args must be registers passed from method args (allow only casts insns)
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (!registersAndCastsOnly(arg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
String alias = mth.getAlias();
|
||||
if (Objects.equals(wrappedMth.getAlias(), alias)) {
|
||||
return true;
|
||||
}
|
||||
if (!wrappedAccFlags.isPublic()) {
|
||||
// must be public
|
||||
FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC);
|
||||
}
|
||||
wrappedMth.getMethodInfo().setAlias(alias);
|
||||
return true;
|
||||
if (insnType != InsnType.INVOKE) {
|
||||
return false;
|
||||
}
|
||||
InvokeNode invokeInsn = (InvokeNode) insn;
|
||||
if (invokeInsn.getInvokeType() == InvokeType.SUPER) {
|
||||
return false;
|
||||
}
|
||||
MethodInfo callMth = invokeInsn.getCallMth();
|
||||
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth);
|
||||
if (wrappedMth == null) {
|
||||
return false;
|
||||
}
|
||||
AccessInfo wrappedAccFlags = wrappedMth.getAccessFlags();
|
||||
if (wrappedAccFlags.isStatic()) {
|
||||
return false;
|
||||
}
|
||||
if (callMth.getArgsCount() != mth.getMethodInfo().getArgsCount()) {
|
||||
return false;
|
||||
}
|
||||
// rename method only from current class
|
||||
if (!mth.getParentClass().equals(wrappedMth.getParentClass())) {
|
||||
return false;
|
||||
}
|
||||
// all args must be registers passed from method args (allow only casts insns)
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (!registersAndCastsOnly(arg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
// remove confirmed, change visibility and name if needed
|
||||
if (!wrappedAccFlags.isPublic()) {
|
||||
// must be public
|
||||
FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC);
|
||||
}
|
||||
String alias = mth.getAlias();
|
||||
if (!Objects.equals(wrappedMth.getAlias(), alias)) {
|
||||
wrappedMth.getMethodInfo().setAlias(alias);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean registersAndCastsOnly(InsnArg arg) {
|
||||
@@ -274,8 +285,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
if (otherMth != mth) {
|
||||
MethodInfo omi = otherMth.getMethodInfo();
|
||||
if (omi.getName().equals(mi.getName())
|
||||
&& omi.getArgumentsTypes().size() == mi.getArgumentsTypes().size()) {
|
||||
// TODO: check objects types
|
||||
&& Objects.equals(omi.getArgumentsTypes(), mi.getArgumentsTypes())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
@@ -32,12 +33,12 @@ public class FixAccessModifiers extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
public static void changeVisibility(MethodNode mth, int newVisFlag) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
public static void changeVisibility(ICodeNode node, int newVisFlag) {
|
||||
AccessInfo accessFlags = node.getAccessFlags();
|
||||
AccessInfo newAccFlags = accessFlags.changeVisibility(newVisFlag);
|
||||
if (newAccFlags != accessFlags) {
|
||||
mth.setAccFlags(newAccFlags);
|
||||
mth.addAttr(AType.COMMENTS, "access modifiers changed from: " + accessFlags.rawString());
|
||||
node.setAccessFlags(newAccFlags);
|
||||
node.addAttr(AType.COMMENTS, "access modifiers changed from: " + accessFlags.rawString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,28 +2,44 @@ package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
/**
|
||||
* Inline synthetic methods.
|
||||
*/
|
||||
@JadxVisitor(
|
||||
name = "InlineMethods",
|
||||
desc = "Inline synthetic static methods",
|
||||
runAfter = {
|
||||
FixAccessModifiers.class,
|
||||
ClassModifier.class
|
||||
}
|
||||
)
|
||||
public class MethodInlineVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isSynthetic()
|
||||
&& accessFlags.isStatic()
|
||||
if (accessFlags.isSynthetic() && accessFlags.isStatic()
|
||||
&& mth.getBasicBlocks().size() == 2) {
|
||||
BlockNode returnBlock = mth.getBasicBlocks().get(1);
|
||||
if (returnBlock.contains(AFlag.RETURN) || returnBlock.getInstructions().isEmpty()) {
|
||||
@@ -72,7 +88,47 @@ public class MethodInlineVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static void addInlineAttr(MethodNode mth, InsnNode insn) {
|
||||
mth.addAttr(new MethodInlineAttr(insn));
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
if (fixVisibilityOfInlineCode(mth, insn)) {
|
||||
if (Consts.DEBUG) {
|
||||
mth.addAttr(AType.COMMENTS, "Removed for inline");
|
||||
} else {
|
||||
mth.addAttr(new MethodInlineAttr(insn));
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean fixVisibilityOfInlineCode(MethodNode mth, InsnNode insn) {
|
||||
int newVisFlag = AccessFlags.ACC_PUBLIC; // TODO: calculate more precisely
|
||||
InsnType insnType = insn.getType();
|
||||
if (insnType == InsnType.INVOKE) {
|
||||
InvokeNode invoke = (InvokeNode) insn;
|
||||
MethodNode callMthNode = mth.root().deepResolveMethod(invoke.getCallMth());
|
||||
if (callMthNode != null) {
|
||||
FixAccessModifiers.changeVisibility(callMthNode, newVisFlag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (insnType == InsnType.ONE_ARG) {
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (!arg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
return fixVisibilityOfInlineCode(mth, ((InsnWrapArg) arg).getWrapInsn());
|
||||
}
|
||||
if (insn instanceof IndexInsnNode) {
|
||||
Object indexObj = ((IndexInsnNode) insn).getIndex();
|
||||
if (indexObj instanceof FieldInfo) {
|
||||
FieldNode fieldNode = mth.root().deepResolveField(((FieldInfo) indexObj));
|
||||
if (fieldNode != null) {
|
||||
FixAccessModifiers.changeVisibility(fieldNode, newVisFlag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
mth.addAttr(AType.COMMENTS, "JADX DEBUG: can't inline method, not implemented redirect type: " + insn);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,14 +72,9 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
ClassInfo alias = classInfo.getAlias();
|
||||
String clsName = alias.getShortName();
|
||||
String newShortName = null;
|
||||
char firstChar = clsName.charAt(0);
|
||||
if (Character.isDigit(firstChar)) {
|
||||
newShortName = Consts.ANONYMOUS_CLASS_PREFIX + clsName;
|
||||
} else if (firstChar == '$') {
|
||||
newShortName = "C" + clsName;
|
||||
}
|
||||
if (newShortName != null) {
|
||||
|
||||
String newShortName = fixClsShortName(clsName);
|
||||
if (!newShortName.equals(clsName)) {
|
||||
classInfo.rename(cls.root(), alias.makeFullClsName(newShortName, true));
|
||||
}
|
||||
if (alias.getPackage().isEmpty()) {
|
||||
@@ -89,6 +84,17 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private String fixClsShortName(String clsName) {
|
||||
char firstChar = clsName.charAt(0);
|
||||
if (Character.isDigit(firstChar)) {
|
||||
return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName);
|
||||
}
|
||||
if (firstChar == '$') {
|
||||
return 'C' + NameMapper.removeInvalidCharsMiddle(clsName);
|
||||
}
|
||||
return NameMapper.removeInvalidChars(clsName, "C");
|
||||
}
|
||||
|
||||
private void checkFields(ClassNode cls) {
|
||||
Set<String> names = new HashSet<>();
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
@@ -101,17 +107,22 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private void checkMethods(ClassNode cls) {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (!NameMapper.isValidIdentifier(mth.getAlias())) {
|
||||
deobfuscator.forceRenameMethod(mth);
|
||||
}
|
||||
}
|
||||
Set<String> names = new HashSet<>();
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isConstructor()
|
||||
|| accessFlags.isBridge()
|
||||
|| accessFlags.isSynthetic()
|
||||
|| mth.contains(AFlag.DONT_GENERATE)) {
|
||||
|| mth.contains(AFlag.DONT_GENERATE) /* this flag not set yet */) {
|
||||
continue;
|
||||
}
|
||||
String signature = mth.getMethodInfo().makeSignature(false);
|
||||
if (!names.add(signature) || !NameMapper.isValidIdentifier(mth.getAlias())) {
|
||||
String signature = mth.getMethodInfo().makeSignature(true, false);
|
||||
if (!names.add(signature)) {
|
||||
deobfuscator.forceRenameMethod(mth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package jadx.core.dex.visitors.blocksmaker;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -47,6 +50,7 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
removeJumpAttr(mth);
|
||||
removeInsns(mth);
|
||||
removeEmptyDetachedBlocks(mth);
|
||||
removeUnreachableBlocks(mth);
|
||||
initBlocksInTargetNodes(mth);
|
||||
|
||||
removeJumpAttributes(mth.getInstructions());
|
||||
@@ -335,4 +339,37 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeUnreachableBlocks(MethodNode mth) {
|
||||
Set<BlockNode> toRemove = new LinkedHashSet<>();
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
|
||||
toRemove.add(block);
|
||||
collectSuccessors(block, toRemove);
|
||||
}
|
||||
}
|
||||
if (!toRemove.isEmpty()) {
|
||||
mth.getBasicBlocks().removeIf(toRemove::contains);
|
||||
|
||||
int insnsCount = toRemove.stream().mapToInt(block -> block.getInstructions().size()).sum();
|
||||
mth.addAttr(AType.COMMENTS, "JADX INFO: unreachable blocks removed: " + toRemove.size()
|
||||
+ ", instructions: " + insnsCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectSuccessors(BlockNode startBlock, Set<BlockNode> toRemove) {
|
||||
Deque<BlockNode> stack = new ArrayDeque<>();
|
||||
stack.add(startBlock);
|
||||
while (!stack.isEmpty()) {
|
||||
BlockNode block = stack.pop();
|
||||
if (!toRemove.contains(block)) {
|
||||
for (BlockNode successor : block.getSuccessors()) {
|
||||
if (toRemove.containsAll(successor.getPredecessors())) {
|
||||
stack.push(successor);
|
||||
}
|
||||
}
|
||||
}
|
||||
toRemove.add(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ public class AndroidResourcesUtils {
|
||||
}
|
||||
LOG.info("App 'R' class not found, put all resources ids to : '{}'", fullName);
|
||||
resCls = makeClass(root, fullName, resStorage);
|
||||
if (resCls == null) {
|
||||
// We are in an APK without code therefore we don't have to update an 'R' class with the resources
|
||||
return null;
|
||||
}
|
||||
addResourceFields(resCls, resStorage, false);
|
||||
return resCls;
|
||||
}
|
||||
@@ -81,18 +85,18 @@ public class AndroidResourcesUtils {
|
||||
return rCls;
|
||||
}
|
||||
|
||||
private static void addResourceFields(ClassNode cls, ResourceStorage resStorage, boolean rClsExists) {
|
||||
private static void addResourceFields(ClassNode resCls, ResourceStorage resStorage, boolean rClsExists) {
|
||||
Map<String, ClassNode> innerClsMap = new TreeMap<>();
|
||||
if (rClsExists) {
|
||||
for (ClassNode innerClass : cls.getInnerClasses()) {
|
||||
for (ClassNode innerClass : resCls.getInnerClasses()) {
|
||||
innerClsMap.put(innerClass.getShortName(), innerClass);
|
||||
}
|
||||
}
|
||||
for (ResourceEntry resource : resStorage.getResources()) {
|
||||
ClassNode typeCls = innerClsMap.computeIfAbsent(resource.getTypeName(), name -> {
|
||||
ClassNode newTypeCls = new ClassNode(cls.dex(), cls.getFullName() + "$" + name,
|
||||
ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + "$" + name,
|
||||
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
|
||||
cls.addInnerClass(newTypeCls);
|
||||
resCls.addInnerClass(newTypeCls);
|
||||
if (rClsExists) {
|
||||
newTypeCls.addAttr(AType.COMMENTS, "added by JADX");
|
||||
}
|
||||
|
||||
@@ -77,8 +77,7 @@ public class InputFile {
|
||||
if (skipSources) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DecodeException("Unsupported input file format: " + file);
|
||||
LOG.warn("No dex files found in {}", file);
|
||||
}
|
||||
|
||||
private void addDexFile(Dex dexBuf) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static jadx.core.deobf.NameMapper.isValidIdentifier;
|
||||
import static jadx.core.deobf.NameMapper.removeInvalidChars;
|
||||
import static jadx.core.deobf.NameMapper.removeInvalidCharsMiddle;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class NameMapperTest {
|
||||
|
||||
@Test
|
||||
public void validIdentifiers() {
|
||||
assertThat(isValidIdentifier("ACls"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notValidIdentifiers() {
|
||||
assertThat(isValidIdentifier("1cls"), is(false));
|
||||
assertThat(isValidIdentifier("-cls"), is(false));
|
||||
assertThat(isValidIdentifier("A-cls"), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveInvalidCharsMiddle() {
|
||||
assertThat(removeInvalidCharsMiddle("1cls"), is("1cls"));
|
||||
assertThat(removeInvalidCharsMiddle("-cls"), is("cls"));
|
||||
assertThat(removeInvalidCharsMiddle("A-cls"), is("Acls"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveInvalidChars() {
|
||||
assertThat(removeInvalidChars("1cls", "C"), is("C1cls"));
|
||||
assertThat(removeInvalidChars("-cls", "C"), is("cls"));
|
||||
assertThat(removeInvalidChars("A-cls", "C"), is("Acls"));
|
||||
}
|
||||
}
|
||||
@@ -215,12 +215,14 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
rethrow("Original check failed", ie);
|
||||
}
|
||||
// run 'check' method from decompiled class
|
||||
try {
|
||||
invoke("check");
|
||||
} catch (InvocationTargetException ie) {
|
||||
rethrow("Decompiled check failed", ie);
|
||||
if (compile) {
|
||||
try {
|
||||
invoke("check");
|
||||
} catch (InvocationTargetException ie) {
|
||||
rethrow("Decompiled check failed", ie);
|
||||
}
|
||||
System.out.println("Auto check: PASSED");
|
||||
}
|
||||
System.out.println("Auto check: PASSED");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail("Auto check exception: " + e.getMessage());
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package jadx.tests.integration.inline;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestSyntheticInline2 extends IntegrationTest {
|
||||
|
||||
public static class Base {
|
||||
protected void call() {
|
||||
System.out.println("base call");
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestCls extends Base {
|
||||
public class A {
|
||||
public void invokeCall() {
|
||||
TestCls.this.call();
|
||||
}
|
||||
|
||||
public void invokeSuperCall() {
|
||||
TestCls.super.call();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call() {
|
||||
System.out.println("TestCls call");
|
||||
}
|
||||
|
||||
public void check() {
|
||||
A a = new A();
|
||||
a.invokeSuperCall();
|
||||
a.invokeCall();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation(); // strange java compiler bug
|
||||
ClassNode cls = getClassNode(TestCls.class); // Base class in unknown
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, not(containsString("synthetic")));
|
||||
assertThat(code, not(containsString("access$")));
|
||||
assertThat(code, containsString("TestSyntheticInline2$TestCls.this.call();"));
|
||||
assertThat(code, containsString("TestSyntheticInline2$TestCls.super.call();"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTopClass() {
|
||||
ClassNode cls = getClassNode(TestSyntheticInline2.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsString(indent(1) + "TestCls.super.call();"));
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,6 @@ public class TestDuplicatedNames extends SmaliTest {
|
||||
assertThat(code, containsOne("this.f0fieldName"));
|
||||
|
||||
assertThat(code, containsOne("public Object run() {"));
|
||||
assertThat(code, containsOne("public String m0run() {"));
|
||||
assertThat(code, containsOne("public String m1run() {"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class JadxWrapper {
|
||||
this.decompiler.getArgs().setInputFiles(Collections.singletonList(file));
|
||||
this.decompiler.load();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error load file: {}", file, e);
|
||||
LOG.error("Jadx init error", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -18,20 +19,19 @@ import jadx.cli.JadxCLIArgs;
|
||||
import jadx.gui.ui.codearea.EditorTheme;
|
||||
import jadx.gui.utils.LangLocale;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
import static jadx.gui.utils.Utils.FONT_HACK;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
public class JadxSettings extends JadxCLIArgs {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class);
|
||||
|
||||
private static final String USER_HOME = System.getProperty("user.home");
|
||||
private static final int RECENT_FILES_COUNT = 15;
|
||||
private static final int CURRENT_SETTINGS_VERSION = 6;
|
||||
private static final int CURRENT_SETTINGS_VERSION = 8;
|
||||
|
||||
private static final Font DEFAULT_FONT = FONT_HACK != null ? FONT_HACK : new RSyntaxTextArea().getFont();
|
||||
private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont();
|
||||
|
||||
static final Set<String> SKIP_FIELDS = new HashSet<>(Arrays.asList(
|
||||
"files", "input", "outputDir", "verbose", "printHelp"
|
||||
"files", "input", "outDir", "outDirSrc", "outDirRes", "verbose", "printVersion", "printHelp"
|
||||
));
|
||||
private String lastOpenFilePath = USER_HOME;
|
||||
private String lastSaveFilePath = USER_HOME;
|
||||
@@ -262,8 +262,19 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
return Font.decode(fontStr);
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
this.fontStr = font.getFontName() + addStyleName(font.getStyle()) + "-" + font.getSize();
|
||||
public void setFont(@Nullable Font font) {
|
||||
if (font == null) {
|
||||
this.fontStr = "";
|
||||
return;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(font.getFontName());
|
||||
String fontStyleName = Utils.getFontStyleName(font.getStyle()).replaceAll(" ", "");
|
||||
if (!fontStyleName.isEmpty()) {
|
||||
sb.append('-').append(fontStyleName.toUpperCase());
|
||||
}
|
||||
sb.append('-').append(font.getSize());
|
||||
this.fontStr = sb.toString();
|
||||
}
|
||||
|
||||
public String getEditorThemePath() {
|
||||
@@ -274,19 +285,6 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.editorThemePath = editorThemePath;
|
||||
}
|
||||
|
||||
private static String addStyleName(int style) {
|
||||
switch (style) {
|
||||
case Font.BOLD:
|
||||
return "-BOLD";
|
||||
case Font.PLAIN:
|
||||
return "-PLAIN";
|
||||
case Font.ITALIC:
|
||||
return "-ITALIC";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeSettings(int fromVersion) {
|
||||
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
|
||||
if (fromVersion == 0) {
|
||||
@@ -319,6 +317,18 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
}
|
||||
if (fromVersion == 5) {
|
||||
setRespectBytecodeAccessModifiers(false);
|
||||
fromVersion++;
|
||||
}
|
||||
if (fromVersion == 6) {
|
||||
if (getFont().getFontName().equals("Hack Regular")) {
|
||||
setFont(null);
|
||||
}
|
||||
fromVersion++;
|
||||
}
|
||||
if (fromVersion == 7) {
|
||||
outDir = null;
|
||||
outDirSrc = null;
|
||||
outDirRes = null;
|
||||
}
|
||||
settingsVersion = CURRENT_SETTINGS_VERSION;
|
||||
sync();
|
||||
|
||||
@@ -16,6 +16,7 @@ import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.EditorTheme;
|
||||
import jadx.gui.utils.LangLocale;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
public class JadxSettingsWindow extends JDialog {
|
||||
private static final long serialVersionUID = -1804570470377354148L;
|
||||
@@ -73,6 +74,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
JButton cancelButton = new JButton(NLS.str("preferences.cancel"));
|
||||
cancelButton.addActionListener(event -> {
|
||||
JadxSettingsAdapter.fill(settings, startSettings);
|
||||
mainWindow.loadSettings();
|
||||
dispose();
|
||||
});
|
||||
|
||||
@@ -86,6 +88,8 @@ public class JadxSettingsWindow extends JDialog {
|
||||
if (res == JOptionPane.YES_OPTION) {
|
||||
String defaults = JadxSettingsAdapter.makeString(JadxSettings.makeDefault());
|
||||
JadxSettingsAdapter.fill(settings, defaults);
|
||||
mainWindow.loadSettings();
|
||||
needReload();
|
||||
getContentPane().removeAll();
|
||||
initUI();
|
||||
pack();
|
||||
@@ -103,7 +107,9 @@ public class JadxSettingsWindow extends JDialog {
|
||||
buttonPane.add(cancelButton);
|
||||
|
||||
Container contentPane = getContentPane();
|
||||
contentPane.add(panel, BorderLayout.CENTER);
|
||||
JScrollPane scrollPane = new JScrollPane(panel);
|
||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
contentPane.add(scrollPane, BorderLayout.CENTER);
|
||||
contentPane.add(buttonPane, BorderLayout.PAGE_END);
|
||||
getRootPane().setDefaultButton(saveBtn);
|
||||
}
|
||||
@@ -164,21 +170,6 @@ public class JadxSettingsWindow extends JDialog {
|
||||
|
||||
private SettingsGroup makeEditorGroup() {
|
||||
JButton fontBtn = new JButton(NLS.str("preferences.select_font"));
|
||||
fontBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
JFontChooser fontChooser = new JFontChooser();
|
||||
fontChooser.setSelectedFont(settings.getFont());
|
||||
int result = fontChooser.showDialog(JadxSettingsWindow.this);
|
||||
if (result == JFontChooser.OK_OPTION) {
|
||||
Font font = fontChooser.getSelectedFont();
|
||||
LOG.debug("Selected Font: {}", font);
|
||||
settings.setFont(font);
|
||||
mainWindow.updateFont(font);
|
||||
mainWindow.loadSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EditorTheme[] editorThemes = EditorTheme.getAllThemes();
|
||||
JComboBox<EditorTheme> themesCbx = new JComboBox<>(editorThemes);
|
||||
@@ -196,11 +187,33 @@ public class JadxSettingsWindow extends JDialog {
|
||||
});
|
||||
|
||||
SettingsGroup other = new SettingsGroup(NLS.str("preferences.editor"));
|
||||
other.addRow(NLS.str("preferences.font"), fontBtn);
|
||||
JLabel fontLabel = other.addRow(getFontLabelStr(), fontBtn);
|
||||
other.addRow(NLS.str("preferences.theme"), themesCbx);
|
||||
|
||||
fontBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
JFontChooser fontChooser = new JFontChooser();
|
||||
fontChooser.setSelectedFont(settings.getFont());
|
||||
int result = fontChooser.showDialog(JadxSettingsWindow.this);
|
||||
if (result == JFontChooser.OK_OPTION) {
|
||||
Font font = fontChooser.getSelectedFont();
|
||||
LOG.debug("Selected Font: {}", font);
|
||||
settings.setFont(font);
|
||||
mainWindow.loadSettings();
|
||||
fontLabel.setText(getFontLabelStr());
|
||||
}
|
||||
}
|
||||
});
|
||||
return other;
|
||||
}
|
||||
|
||||
private String getFontLabelStr() {
|
||||
Font font = settings.getFont();
|
||||
String fontStyleName = Utils.getFontStyleName(font.getStyle());
|
||||
return NLS.str("preferences.font") + ": " + font.getFontName() + " " + fontStyleName + " " + font.getSize();
|
||||
}
|
||||
|
||||
private SettingsGroup makeDecompilationGroup() {
|
||||
JCheckBox fallback = new JCheckBox();
|
||||
fallback.setSelected(settings.isFallbackMode());
|
||||
@@ -342,11 +355,11 @@ public class JadxSettingsWindow extends JDialog {
|
||||
c.weighty = 1.0;
|
||||
}
|
||||
|
||||
public void addRow(String label, JComponent comp) {
|
||||
addRow(label, null, comp);
|
||||
public JLabel addRow(String label, JComponent comp) {
|
||||
return addRow(label, null, comp);
|
||||
}
|
||||
|
||||
public void addRow(String label, String tooltip, JComponent comp) {
|
||||
public JLabel addRow(String label, String tooltip, JComponent comp) {
|
||||
c.gridy = row++;
|
||||
JLabel jLabel = new JLabel(label);
|
||||
jLabel.setLabelFor(comp);
|
||||
@@ -371,6 +384,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
add(comp, c);
|
||||
|
||||
comp.addPropertyChangeListener("enabled", evt -> jLabel.setEnabled((boolean) evt.getNewValue()));
|
||||
return jLabel;
|
||||
}
|
||||
|
||||
public void end() {
|
||||
|
||||
@@ -652,10 +652,6 @@ public class MainWindow extends JFrame {
|
||||
setSize((int) (w * WINDOW_RATIO), (int) (h * WINDOW_RATIO));
|
||||
}
|
||||
|
||||
public void updateFont(Font font) {
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
public static void registerBundledFonts() {
|
||||
GraphicsEnvironment grEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
if (Utils.FONT_HACK != null) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.awt.datatransfer.Transferable;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -175,4 +176,19 @@ public class Utils {
|
||||
LOG.error("Failed copy string '{}' to clipboard", text, e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getFontStyleName(int style) {
|
||||
if (style == 0) {
|
||||
return "plain";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if ((style & Font.BOLD) != 0) {
|
||||
sb.append("bold");
|
||||
}
|
||||
if ((style & Font.ITALIC) != 0) {
|
||||
sb.append(" italic");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ preferences.raw_cfg=Generate RAW CFG graphs
|
||||
preferences.font=Editor font
|
||||
preferences.theme=Editor theme
|
||||
preferences.start_jobs=Auto start background decompilation
|
||||
preferences.select_font=Select
|
||||
preferences.select_font=Change
|
||||
preferences.deobfuscation_on=Enable deobfuscation
|
||||
preferences.deobfuscation_force=Force rewrite deobfuscation map file
|
||||
preferences.deobfuscation_min_len=Minimum name length
|
||||
|
||||
Reference in New Issue
Block a user