Commit 19c57258 authored by Skylot's avatar Skylot

fix: improve rename checks and show rename reason (#584)

parent fef3e55c
package jadx.cli;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
......@@ -106,7 +106,7 @@ public class JadxCLIArgs {
+ " 'case' for system case sensitivity,"
+ " 'valid' for java identifiers,"
+ " 'printable' characters,"
+ " 'none' or 'all'",
+ " 'none' or 'all' (default)",
converter = RenameConverter.class
)
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
......@@ -181,7 +181,7 @@ public class JadxCLIArgs {
args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources);
if (singleClass != null) {
args.setClassFilter((className) -> singleClass.equals(className));
args.setClassFilter(className -> singleClass.equals(className));
}
args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode);
......@@ -303,38 +303,14 @@ public class JadxCLIArgs {
return renameFlags.contains(RenameEnum.CASE);
}
public void setRenameCaseSensitive(boolean renameCase) {
if (renameCase && !isRenameCaseSensitive()) {
renameFlags.add(RenameEnum.CASE);
} else if (!renameCase && isRenameCaseSensitive()) {
renameFlags.remove(RenameEnum.CASE);
}
}
public boolean isRenameValid() {
return renameFlags.contains(RenameEnum.VALID);
}
public void setRenameValid(boolean renameValid) {
if (renameValid && !isRenameValid()) {
renameFlags.add(RenameEnum.VALID);
} else if (!renameValid && isRenameValid()) {
renameFlags.remove(RenameEnum.VALID);
}
}
public boolean isRenamePrintable() {
return renameFlags.contains(RenameEnum.PRINTABLE);
}
public void setRenamePrintable(boolean renamePrintable) {
if (renamePrintable && !isRenamePrintable()) {
renameFlags.add(RenameEnum.PRINTABLE);
} else if (!renamePrintable && isRenamePrintable()) {
renameFlags.remove(RenameEnum.PRINTABLE);
}
}
public boolean isFsCaseSensitive() {
return fsCaseSensitive;
}
......@@ -348,23 +324,23 @@ public class JadxCLIArgs {
@Override
public Set<RenameEnum> convert(String value) {
Set<RenameEnum> set = new HashSet<>();
if (value.equalsIgnoreCase("NONE")) {
return EnumSet.noneOf(RenameEnum.class);
}
if (value.equalsIgnoreCase("ALL")) {
set.add(RenameEnum.CASE);
set.add(RenameEnum.VALID);
set.add(RenameEnum.PRINTABLE);
} else if (!value.equalsIgnoreCase("NONE")) {
for (String s : value.split(",")) {
try {
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) {
String values = "'" + RenameEnum.CASE
+ "', '" + RenameEnum.VALID
+ "' and '" + RenameEnum.PRINTABLE + '\'';
throw new IllegalArgumentException(
s + " is unknown for parameter " + paramName
+ ", possible values are " + values.toLowerCase(Locale.ROOT));
}
return EnumSet.allOf(RenameEnum.class);
}
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
for (String s : value.split(",")) {
try {
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) {
String values = Arrays.stream(RenameEnum.values())
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.collect(Collectors.joining(", "));
throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName
+ ", possible values are " + values);
}
}
return set;
......
......@@ -42,8 +42,8 @@ public class RenameConverterTest {
() -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't");
assertEquals("wrong is unknown for parameter someParam, "
+ "possible values are 'case', 'valid' and 'printable'",
assertEquals("'wrong' is unknown for parameter someParam, "
+ "possible values are 'case', 'valid', 'printable'",
thrown.getMessage());
}
}
......@@ -98,7 +98,7 @@ public final class ResourcesLoader {
return ResContainer.textResource(rf.getName(), content);
case ARSC:
return new ResTableParser().decodeFiles(inputStream);
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
case IMG:
return decodeImage(rf, inputStream);
......
......@@ -32,7 +32,7 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.utils.CodegenUtils;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
......@@ -104,7 +104,7 @@ public class ClassGen {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
CodegenUtils.addComments(code, cls);
CodeGenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
addClassDeclaration(code);
addClassBody(code);
......@@ -299,7 +299,7 @@ public class ClassGen {
}
code.add(';');
} else {
CodegenUtils.addComments(code, mth);
CodeGenUtils.addComments(code, mth);
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) {
......@@ -356,11 +356,12 @@ public class ClassGen {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
CodegenUtils.addComments(code, f);
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
code.startLine("/* renamed from: ").add(f.getName()).add(" */");
code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
......@@ -620,7 +621,7 @@ public class ClassGen {
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAlias()) {
code.startLine("/* renamed from: ").add(classInfo.getType().getObject()).add(" */");
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
}
}
......
......@@ -25,6 +25,7 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
......@@ -84,7 +85,7 @@ public class MethodGen {
}
if (mth.getMethodInfo().isRenamed() && !ai.isConstructor()) {
code.startLine("/* renamed from: ").add(mth.getName()).add(" */");
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
}
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
......
......@@ -129,7 +129,7 @@ public class NameGen {
if (NameMapper.isReserved(varName)) {
varName = varName + 'R';
}
if (!NameMapper.isValidIdentifier(varName)) {
if (!NameMapper.isValidAndPrintable(varName)) {
varName = getFallbackName(var);
}
return varName;
......
package jadx.core.deobf;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
......@@ -64,11 +63,6 @@ public class Deobfuscator {
private int fldIndex = 0;
private int mthIndex = 0;
@Deprecated
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
this(args, dexNodes, deobfMapFile.toPath());
}
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, Path deobfMapFile) {
this.args = args;
this.dexNodes = dexNodes;
......@@ -86,8 +80,20 @@ public class Deobfuscator {
initIndexes();
}
process();
}
public void savePresets() {
deobfPresets.save(args.isDeobfuscationForceSave());
clear();
}
public void clear() {
deobfPresets.clear();
clsMap.clear();
fldMap.clear();
mthMap.clear();
ovrd.clear();
ovrdMap.clear();
}
private void initIndexes() {
......@@ -148,16 +154,6 @@ public class Deobfuscator {
}
}
void clear() {
deobfPresets.clear();
clsMap.clear();
fldMap.clear();
mthMap.clear();
ovrd.clear();
ovrdMap.clear();
}
private void resolveOverriding(MethodNode mth) {
Set<ClassNode> clsParents = new LinkedHashSet<>();
collectClassHierarchy(mth.getParentClass(), clsParents);
......@@ -241,7 +237,7 @@ public class Deobfuscator {
} else if (!clsInfo.isInner()) {
// check if package renamed
PackageNode pkgNode = getPackageNode(clsInfo.getPackage(), false);
if (pkgNode.hasAnyAlias()) {
if (pkgNode != null && pkgNode.hasAnyAlias()) {
clsInfo.changePkg(pkgNode.getFullAlias());
}
}
......@@ -259,7 +255,7 @@ public class Deobfuscator {
}
}
public void renameField(FieldNode field) {
private void renameField(FieldNode field) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = getFieldAlias(field);
if (alias != null) {
......@@ -271,7 +267,7 @@ public class Deobfuscator {
field.getFieldInfo().setAlias(makeFieldAlias(field));
}
public void renameMethod(MethodNode mth) {
private void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth);
if (alias != null) {
mth.getMethodInfo().setAlias(alias);
......@@ -408,7 +404,7 @@ public class Deobfuscator {
} else if (name.endsWith(".kt")) {
name = name.substring(0, name.length() - ".kt".length());
}
if (!NameMapper.isValidIdentifier(name) || !NameMapper.isAllCharsPrintable(name)) {
if (!NameMapper.isValidAndPrintable(name)) {
return null;
}
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
......@@ -500,8 +496,7 @@ public class Deobfuscator {
private boolean shouldRename(String s) {
int len = s.length();
return len < minLength || len > maxLength
|| !NameMapper.isValidIdentifier(s);
return len < minLength || len > maxLength;
}
private String prepareNamePart(String name) {
......
......@@ -87,6 +87,10 @@ public class NameMapper {
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches();
}
public static boolean isValidAndPrintable(String str) {
return isValidIdentifier(str) && isAllCharsPrintable(str);
}
public static boolean isValidIdentifierStart(int codePoint) {
return Character.isJavaIdentifierStart(codePoint);
}
......
......@@ -17,6 +17,7 @@ import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.trycatch.CatchAttr;
......@@ -55,6 +56,7 @@ public class AType<T extends IAttribute> {
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
......
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.IAttribute;
public class RenameReasonAttr implements IAttribute {
private String description;
public RenameReasonAttr() {
this.description = "";
}
public RenameReasonAttr(String description) {
this.description = description;
}
public RenameReasonAttr(AttrNode node) {
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
if (renameReasonAttr != null) {
this.description = renameReasonAttr.description;
} else {
this.description = "";
}
}
public RenameReasonAttr(AttrNode node, boolean notValid, boolean notPrintable) {
this(node);
if (notValid) {
notValid();
}
if (notPrintable) {
notPrintable();
}
}
private RenameReasonAttr notValid() {
return append("not valid java name");
}
private RenameReasonAttr notPrintable() {
return append("contains not printable characters");
}
public RenameReasonAttr append(String reason) {
if (description.isEmpty()) {
description += reason;
} else {
description += " and " + reason;
}
return this;
}
public String getDescription() {
return description;
}
@Override
public AType<RenameReasonAttr> getType() {
return AType.RENAME_REASON;
}
@Override
public String toString() {
return "RENAME_REASON:" + description;
}
}
......@@ -87,7 +87,7 @@ public class RootNode {
}
try {
ResourceStorage resStorage = ResourcesLoader.decodeStream(arsc, (size, is) -> {
ResTableParser parser = new ResTableParser();
ResTableParser parser = new ResTableParser(this);
parser.decode(is);
return parser.getResStorage();
});
......
......@@ -212,17 +212,19 @@ public class ClassModifier extends AbstractVisitor {
}
private static boolean removeBridgeMethod(ClassNode cls, MethodNode mth) {
List<InsnNode> allInsns = BlockUtils.collectAllInsns(mth.getBasicBlocks());
if (allInsns.size() == 1) {
InsnNode wrappedInsn = allInsns.get(0);
if (wrappedInsn.getType() == InsnType.RETURN) {
InsnArg arg = wrappedInsn.getArg(0);
if (arg.isInsnWrap()) {
wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
if (cls.root().getArgs().isRenameValid()) {
List<InsnNode> allInsns = BlockUtils.collectAllInsns(mth.getBasicBlocks());
if (allInsns.size() == 1) {
InsnNode wrappedInsn = allInsns.get(0);
if (wrappedInsn.getType() == InsnType.RETURN) {
InsnArg arg = wrappedInsn.getArg(0);
if (arg.isInsnWrap()) {
wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
}
}
if (checkSyntheticWrapper(mth, wrappedInsn)) {
return true;
}
}
if (checkSyntheticWrapper(mth, wrappedInsn)) {
return true;
}
}
return !isMethodUnique(cls, mth);
......
......@@ -152,8 +152,8 @@ public class EnumVisitor extends AbstractVisitor {
String name = getConstString(cls.dex(), co.getArg(0));
if (name != null
&& !fieldInfo.getAlias().equals(name)
&& NameMapper.isValidIdentifier(name)) {
// LOG.debug("Rename enum field: '{}' to '{}' in {}", fieldInfo.getName(), name, cls);
&& NameMapper.isValidAndPrintable(name)
&& cls.root().getArgs().isRenameValid()) {
fieldInfo.setAlias(name);
}
......
......@@ -12,6 +12,7 @@ import jadx.core.Consts;
import jadx.core.deobf.Deobfuscator;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
......@@ -24,8 +25,6 @@ import jadx.core.utils.files.InputFile;
public class RenameVisitor extends AbstractVisitor {
private Deobfuscator deobfuscator;
@Override
public void init(RootNode root) {
List<DexNode> dexNodes = root.getDexNodes();
......@@ -36,24 +35,29 @@ public class RenameVisitor extends AbstractVisitor {
Path inputFilePath = firstInputFile.getFile().toPath();
String inputName = inputFilePath.getFileName().toString();
inputName = inputName.substring(0, inputName.lastIndexOf('.'));
String baseName = inputName.substring(0, inputName.lastIndexOf('.'));
Path deobfMapPath = inputFilePath.getParent().resolve(baseName + ".jobf");
Path deobfMapPath = inputFilePath.getParent().resolve(inputName + ".jobf");
JadxArgs args = root.getArgs();
deobfuscator = new Deobfuscator(args, dexNodes, deobfMapPath);
boolean deobfuscationOn = args.isDeobfuscationOn();
if (deobfuscationOn) {
Deobfuscator deobfuscator = new Deobfuscator(args, dexNodes, deobfMapPath);
if (args.isDeobfuscationOn()) {
deobfuscator.execute();
}
checkClasses(root, args);
checkClasses(deobfuscator, root, args);
if (args.isDeobfuscationOn()) {
deobfuscator.savePresets();
deobfuscator.clear();
}
}
private void checkClasses(RootNode root, JadxArgs args) {
private static void checkClasses(Deobfuscator deobfuscator, RootNode root, JadxArgs args) {
List<ClassNode> classes = root.getClasses(true);
for (ClassNode cls : classes) {
checkClassName(cls, args);
checkFields(cls, args);
checkMethods(cls, args);
checkClassName(deobfuscator, cls, args);
checkFields(deobfuscator, cls, args);
checkMethods(deobfuscator, cls, args);
}
if (!args.isFsCaseSensitive() && args.isRenameCaseSensitive()) {
Set<String> clsFullPaths = new HashSet<>(classes.size());
......@@ -62,20 +66,22 @@ public class RenameVisitor extends AbstractVisitor {
if (!clsFullPaths.add(clsInfo.getAliasFullPath().toLowerCase())) {
String newShortName = deobfuscator.getClsAlias(cls);
clsInfo.changeShortName(newShortName);
cls.addAttr(new RenameReasonAttr(cls).append("case insensitive filesystem"));
clsFullPaths.add(clsInfo.getAliasFullPath().toLowerCase());
}
}
}
processRootPackages(root, classes);
processRootPackages(deobfuscator, root, classes);
}
private void checkClassName(ClassNode cls, JadxArgs args) {
private static void checkClassName(Deobfuscator deobfuscator, ClassNode cls, JadxArgs args) {
ClassInfo classInfo = cls.getClassInfo();
String clsName = classInfo.getAliasShortName();
String newShortName = fixClsShortName(args, clsName);
if (!newShortName.equals(clsName)) {
classInfo.changeShortName(newShortName);
cls.addAttr(new RenameReasonAttr(cls).append("invalid class name"));
}
if (args.isRenameValid()) {
if (classInfo.isInner()) {
......@@ -84,6 +90,7 @@ public class RenameVisitor extends AbstractVisitor {
if (parentClass.getAliasShortName().equals(clsName)) {
String clsAlias = deobfuscator.getClsAlias(cls);
classInfo.changeShortName(clsAlias);
cls.addAttr(new RenameReasonAttr(cls).append("collision with other inner class name"));
break;
}
parentClass = parentClass.getParentClass();
......@@ -91,12 +98,13 @@ public class RenameVisitor extends AbstractVisitor {
} else {
if (classInfo.getAliasPkg().isEmpty()) {
classInfo.changePkg(Consts.DEFAULT_PACKAGE_NAME);
cls.addAttr(new RenameReasonAttr(cls).append("default package"));
}
}
}
}
private String fixClsShortName(JadxArgs args, String clsName) {
private static String fixClsShortName(JadxArgs args, String clsName) {
char firstChar = clsName.charAt(0);
boolean renameValid = args.isRenameValid();
if (Character.isDigit(firstChar) && renameValid) {
......@@ -114,25 +122,33 @@ public class RenameVisitor extends AbstractVisitor {
return cleanClsName;
}
private void checkFields(ClassNode cls, JadxArgs args) {
private static void checkFields(Deobfuscator deobfuscator, ClassNode cls, JadxArgs args) {
Set<String> names = new HashSet<>();
for (FieldNode field : cls.getFields()) {
FieldInfo fieldInfo = field.getFieldInfo();
String fieldName = fieldInfo.getAlias();
if (!names.add(fieldName)
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(fieldName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(fieldName))) {
boolean notUnique = !names.add(fieldName);
boolean notValid = args.isRenameValid() && !NameMapper.isValidIdentifier(fieldName);
boolean notPrintable = args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(fieldName);
if (notUnique || notValid || notPrintable) {
deobfuscator.forceRenameField(field);
field.addAttr(new RenameReasonAttr(field, notValid, notPrintable));
if (notUnique) {
field.addAttr(new RenameReasonAttr(field).append("collision with other field name"));
}
}
}
}
private void checkMethods(ClassNode cls, JadxArgs args) {
private static void checkMethods(Deobfuscator deobfuscator, ClassNode cls, JadxArgs args) {
for (MethodNode mth : cls.getMethods()) {
String alias = mth.getAlias();
if (args.isRenameValid() && !NameMapper.isValidIdentifier(alias)
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(alias))) {
boolean notValid = args.isRenameValid() && !NameMapper.isValidIdentifier(alias);
boolean notPrintable = args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(alias);
if (notValid || notPrintable) {
deobfuscator.forceRenameMethod(mth);
mth.addAttr(new RenameReasonAttr(mth, notValid, notPrintable));
}
}
Set<String> names = new HashSet<>();
......@@ -147,11 +163,12 @@ public class RenameVisitor extends AbstractVisitor {
String signature = mth.getMethodInfo().makeSignature(true, false);
if (!names.add(signature)) {
deobfuscator.forceRenameMethod(mth);
mth.addAttr(new RenameReasonAttr("collision with other method in class"));
}
}
}
private void processRootPackages(RootNode root, List<ClassNode> classes) {
private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List<ClassNode> classes) {
Set<String> rootPkgs = collectRootPkgs(classes);
root.getCacheStorage().setRootPkgs(rootPkgs);
......@@ -161,6 +178,7 @@ public class RenameVisitor extends AbstractVisitor {
for (FieldNode field : cls.getFields()) {
if (rootPkgs.contains(field.getAlias())) {
deobfuscator.forceRenameField(field);
field.addAttr(new RenameReasonAttr("collision with root package name"));
}
}
}
......
......@@ -147,7 +147,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
}
} else {
if (NameMapper.isValidIdentifier(varName)) {
if (NameMapper.isValidAndPrintable(varName)) {
ssaVar.setName(varName);
}
detachDebugInfo(ssaVar.getAssign());
......
......@@ -5,8 +5,9 @@ import java.util.List;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
public class CodegenUtils {
public class CodeGenUtils {
public static void addComments(CodeWriter code, AttrNode node) {
List<String> comments = node.getAll(AType.COMMENTS);
......@@ -15,4 +16,17 @@ public class CodegenUtils {
.forEach(comment -> code.startLine("/* ").addMultiLine(comment).add(" */"));
}
}
public static void addRenamedComment(CodeWriter code, AttrNode node, String origName) {
code.startLine("/* renamed from: ").add(origName);
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
if (renameReasonAttr != null) {
code.add(" reason: ");
code.add(renameReasonAttr.getDescription());
}
code.add(" */");
}
private CodeGenUtils() {
}
}
......@@ -124,7 +124,8 @@ public class AndroidResourcesUtils {
FieldNode fieldNode = resFieldsMap.get(resource.getId());
if (fieldNode != null
&& !fieldNode.getName().equals(resName)
&& NameMapper.isValidIdentifier(resName)) {
&& NameMapper.isValidAndPrintable(resName)
&& resCls.root().getArgs().isRenameValid()) {
fieldNode.add(AFlag.DONT_RENAME);
fieldNode.getFieldInfo().setAlias(resName);
}
......
......@@ -115,7 +115,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
break;
case RES_STRING_POOL_TYPE:
strings = parseStringPoolNoType();
valuesParser = new ValuesParser(strings, resNames);
valuesParser = new ValuesParser(rootNode, strings, resNames);
break;
case RES_XML_RESOURCE_MAP_TYPE:
parseResourceMap();
......
......@@ -11,6 +11,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.xmlgen.entry.EntryConfig;
import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.RawValue;
......@@ -50,8 +53,13 @@ public class ResTableParser extends CommonBinaryParser {
}
}
private String[] strings;
private final RootNode root;
private final ResourceStorage resStorage = new ResourceStorage();
private String[] strings;
public ResTableParser(RootNode root) {
this.root = root;
}
public void decode(InputStream inputStream) throws IOException {
is = new ParserStream(inputStream);
......@@ -62,7 +70,7 @@ public class ResTableParser extends CommonBinaryParser {
public ResContainer decodeFiles(InputStream inputStream) throws IOException {
decode(inputStream);
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ValuesParser vp = new ValuesParser(root, strings, resStorage.getResourcesNames());
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
CodeWriter content = makeXmlDump();
......@@ -222,7 +230,13 @@ public class ResTableParser extends CommonBinaryParser {
String typeName = pkg.getTypeStrings()[typeId - 1];
String keyName = pkg.getKeyStrings()[key];
if (keyName.isEmpty()) {
keyName = "RES_" + resRef; // autogenerate key name
FieldNode constField = root.getConstValues().getGlobalConstFields().get(resRef);
if (constField != null) {
keyName = constField.getName();
constField.add(AFlag.DONT_RENAME);
} else {
keyName = "RES_" + resRef; // autogenerate key name
}
}
ResourceEntry ri = new ResourceEntry(resRef, pkg.getName(), typeName, keyName);
ri.setConfig(config);
......
......@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.nodes.RootNode;
import jadx.core.xmlgen.ParserConstants;
import jadx.core.xmlgen.ResTableParser;
......@@ -25,22 +26,22 @@ public class ValuesParser extends ParserConstants {
private final String[] strings;
private final Map<Integer, String> resMap;
public ValuesParser(String[] strings, Map<Integer, String> resMap) {
public ValuesParser(RootNode root, String[] strings, Map<Integer, String> resMap) {
this.strings = strings;
this.resMap = resMap;
if (androidStrings == null && androidResMap == null) {
try {
decodeAndroid();
decodeAndroid(root);
} catch (Exception e) {
LOG.error("Failed to decode Android Resource file", e);
}
}
}
private static void decodeAndroid() throws IOException {
private static void decodeAndroid(RootNode root) throws IOException {
InputStream inputStream = new BufferedInputStream(ValuesParser.class.getResourceAsStream("/resources.arsc"));
ResTableParser androidParser = new ResTableParser();
ResTableParser androidParser = new ResTableParser(root);
androidParser.decode(inputStream);
androidStrings = androidParser.getStrings();
androidResMap = androidParser.getResStorage().getResourcesNames();
......
......@@ -278,6 +278,14 @@ public class JadxSettings extends JadxCLIArgs {
this.deobfuscationUseSourceNameAsAlias = deobfuscationUseSourceNameAsAlias;
}
public void updateRenameFlag(JadxArgs.RenameEnum flag, boolean enabled) {
if (enabled) {
renameFlags.add(flag);
} else {
renameFlags.remove(flag);
}
}
public void setEscapeUnicode(boolean escapeUnicode) {
this.escapeUnicode = escapeUnicode;
}
......
......@@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory;
import say.swing.JFontChooser;
import jadx.api.JadxArgs;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.FontUtils;
......@@ -184,21 +185,21 @@ public class JadxSettingsWindow extends JDialog {
JCheckBox renameCaseSensitive = new JCheckBox();
renameCaseSensitive.setSelected(settings.isRenameCaseSensitive());
renameCaseSensitive.addItemListener(e -> {
settings.setRenameCaseSensitive(e.getStateChange() == ItemEvent.SELECTED);
settings.updateRenameFlag(JadxArgs.RenameEnum.CASE, e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox renameValid = new JCheckBox();
renameValid.setSelected(settings.isRenameValid());
renameValid.addItemListener(e -> {
settings.setRenameValid(e.getStateChange() == ItemEvent.SELECTED);
settings.updateRenameFlag(JadxArgs.RenameEnum.VALID, e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox renamePrintable = new JCheckBox();
renamePrintable.setSelected(settings.isRenamePrintable());
renamePrintable.addItemListener(e -> {
settings.setRenamePrintable(e.getStateChange() == ItemEvent.SELECTED);
settings.updateRenameFlag(JadxArgs.RenameEnum.PRINTABLE, e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment