Skip to content

Commit

Permalink
feat: select better resource name (#1581)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Jul 25, 2022
1 parent 9100ad1 commit ab4b6f9
Show file tree
Hide file tree
Showing 17 changed files with 234 additions and 16 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ options:
'ignore' - don't read and don't save
--deobf-use-sourcename - use source file name as class name alias
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--deobf-res-name-source - better name source for resources:
'auto' - automatically select best name (default)
'resources' - use resources names
'code' - use R class fields names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
Expand Down
29 changes: 29 additions & 0 deletions jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.args.ResourceNameSource;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;

Expand Down Expand Up @@ -129,6 +130,16 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false;

@Parameter(
names = { "--deobf-res-name-source" },
description = "better name source for resources:"
+ "\n 'auto' - automatically select best name (default)"
+ "\n 'resources' - use resources names"
+ "\n 'code' - use R class fields names",
converter = ResourceNameSourceConverter.class
)
protected ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;

@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
Expand Down Expand Up @@ -262,6 +273,7 @@ public JadxArgs toJadxArgs() {
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setResourceNameSource(resourceNameSource);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
Expand Down Expand Up @@ -378,6 +390,10 @@ public boolean isDeobfuscationParseKotlinMetadata() {
return deobfuscationParseKotlinMetadata;
}

public ResourceNameSource getResourceNameSource() {
return resourceNameSource;
}

public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
Expand Down Expand Up @@ -502,6 +518,19 @@ public DeobfuscationMapFileMode convert(String value) {
}
}

public static class ResourceNameSourceConverter implements IStringConverter<ResourceNameSource> {
@Override
public ResourceNameSource convert(String value) {
try {
return ResourceNameSource.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(ResourceNameSource.values()));
}
}
}

public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> {
@Override
public DecompilationMode convert(String value) {
Expand Down
12 changes: 12 additions & 0 deletions jadx-core/src/main/java/jadx/api/JadxArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.slf4j.LoggerFactory;

import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.args.ResourceNameSource;
import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
Expand Down Expand Up @@ -72,6 +73,7 @@ public class JadxArgs {
private File deobfuscationMapFile = null;

private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
private ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;

private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE;
Expand Down Expand Up @@ -369,6 +371,14 @@ public void setDeobfuscationMapFile(File deobfuscationMapFile) {
this.deobfuscationMapFile = deobfuscationMapFile;
}

public ResourceNameSource getResourceNameSource() {
return resourceNameSource;
}

public void setResourceNameSource(ResourceNameSource resourceNameSource) {
this.resourceNameSource = resourceNameSource;
}

public boolean isEscapeUnicode() {
return escapeUnicode;
}
Expand Down Expand Up @@ -540,6 +550,7 @@ public String makeCodeArgsHash() {
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
+ inlineAnonymousClasses + inlineMethods
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
+ resourceNameSource
+ parseKotlinMetadata + useKotlinMethodsForVarNames
+ insertDebugLines + extractFinally
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
Expand All @@ -564,6 +575,7 @@ public String toString() {
+ ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", resourceNameSource=" + resourceNameSource
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
Expand Down
22 changes: 22 additions & 0 deletions jadx-core/src/main/java/jadx/api/args/ResourceNameSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package jadx.api.args;

/**
* Resources original name source (for deobfuscation)
*/
public enum ResourceNameSource {

/**
* Automatically select best name (default)
*/
AUTO,

/**
* Force use resources provided names
*/
RESOURCES,

/**
* Force use resources names from R class
*/
CODE,
}
4 changes: 4 additions & 0 deletions jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,10 @@ public boolean isAnonymous() {
return contains(AType.ANONYMOUS_CLASS);
}

public boolean isSynthetic() {
return contains(AFlag.SYNTHETIC);
}

public boolean isInner() {
return parentClass != this;
}
Expand Down
8 changes: 8 additions & 0 deletions jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public String getAlias() {
return fieldInfo.getAlias();
}

public void rename(String alias) {
fieldInfo.setAlias(alias);
}

public ArgType getType() {
return type;
}
Expand All @@ -73,6 +77,10 @@ public ClassNode getParentClass() {
return parentClass;
}

public ClassNode getTopParentClass() {
return parentClass.getTopParentClass();
}

public List<MethodNode> getUseIn() {
return useIn;
}
Expand Down
62 changes: 62 additions & 0 deletions jadx-core/src/main/java/jadx/core/utils/BetterName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package jadx.core.utils;

import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jadx.core.deobf.NameMapper;
import jadx.core.deobf.TldHelper;

public class BetterName {
private static final Logger LOG = LoggerFactory.getLogger(BetterName.class);

private static final boolean DEBUG = true;

public static String compareAndGet(String first, String second) {
if (Objects.equals(first, second)) {
return first;
}
int firstRating = calcRating(first);
int secondRating = calcRating(second);
boolean firstBetter = firstRating >= secondRating;
if (DEBUG) {
if (firstBetter) {
LOG.info("Better name: '{}' > '{}' ({} > {})", first, second, firstRating, secondRating);
} else {
LOG.info("Better name: '{}' > '{}' ({} > {})", second, first, secondRating, firstRating);
}
}
return firstBetter ? first : second;
}

public static int calcRating(String str) {
int rating = str.length() * 3;
rating += differentCharsCount(str) * 20;

if (NameMapper.isAllCharsPrintable(str)) {
rating += 100;
}
if (NameMapper.isValidIdentifier(str)) {
rating += 50;
}
if (TldHelper.contains(str)) {
rating += 20;
}
if (str.contains("_")) {
// rare in obfuscated names
rating += 100;
}
return rating;
}

private static int differentCharsCount(String str) {
String lower = str.toLowerCase(Locale.ROOT);
Set<Integer> chars = new HashSet<>();
StringUtils.visitCodePoints(lower, chars::add);
return chars.size();
}
}
67 changes: 51 additions & 16 deletions jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jadx.api.ICodeInfo;
import jadx.api.args.ResourceNameSource;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.BetterName;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.entry.EntryConfig;
import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.RawValue;
Expand Down Expand Up @@ -287,35 +291,66 @@ private String getResName(String typeName, int resRef, String origKeyName) {
if (renamedKey != null) {
return renamedKey;
}
// styles might contain dots in name, search for alias only for resources names
if (typeName.equals("style")) {
return origKeyName;
}
FieldNode constField = root.getConstValues().getGlobalConstFields().get(resRef);
String resAlias = getResAlias(resRef, origKeyName, constField);
resStorage.addRename(resRef, resAlias);
if (constField != null) {
constField.rename(resAlias);
constField.add(AFlag.DONT_RENAME);
return constField.getName();
}
// styles might contain dots in name, use VALID_RES_KEY_PATTERN only for resource file name
if (typeName.equals("style")) {
return origKeyName;
} else if (VALID_RES_KEY_PATTERN.matcher(origKeyName).matches()) {
return origKeyName;
return resAlias;
}

private String getResAlias(int resRef, String origKeyName, @Nullable FieldNode constField) {
String name;
if (constField == null || constField.getTopParentClass().isSynthetic()) {
name = origKeyName;
} else {
name = getBetterName(root.getArgs().getResourceNameSource(), origKeyName, constField.getName());
}
Matcher matcher = VALID_RES_KEY_PATTERN.matcher(name);
if (matcher.matches()) {
return name;
}
// Making sure origKeyName compliant with resource file name rules
Matcher m = VALID_RES_KEY_PATTERN.matcher(origKeyName);
String cleanedResName = cleanName(matcher);
String newResName = String.format("res_0x%08x", resRef);
if (cleanedResName.isEmpty()) {
return newResName;
}
// autogenerate key name, appended with cleaned origKeyName to be human-friendly
return newResName + "_" + cleanedResName.toLowerCase();
}

public static String getBetterName(ResourceNameSource nameSource, String resName, String codeName) {
switch (nameSource) {
case AUTO:
return BetterName.compareAndGet(resName, codeName);
case RESOURCES:
return resName;
case CODE:
return codeName;

default:
throw new JadxRuntimeException("Unexpected ResourceNameSource value: " + nameSource);
}
}

private String cleanName(Matcher matcher) {
StringBuilder sb = new StringBuilder();
boolean first = true;
while (m.find()) {
while (matcher.find()) {
if (!first) {
sb.append("_");
}
sb.append(m.group());
sb.append(matcher.group());
first = false;
}
// autogenerate key name, appended with cleaned origKeyName to be human-friendly
String newResName = String.format("res_0x%08x", resRef);
String cleanedResName = sb.toString();
if (!cleanedResName.isEmpty()) {
newResName += "_" + cleanedResName.toLowerCase();
}
return newResName;
return sb.toString();
}

private RawNamedValue parseValueMap() throws IOException {
Expand Down
22 changes: 22 additions & 0 deletions jadx-core/src/test/java/jadx/core/utils/TestBetterName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package jadx.core.utils;

import org.junit.jupiter.api.Test;

import static jadx.core.utils.BetterName.calcRating;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;

public class TestBetterName {

@Test
public void test() {
expectFirst("color_main", "t0");
expectFirst("done", "oOo0oO0o");
}

private void expectFirst(String first, String second) {
String best = BetterName.compareAndGet(first, second);
assertThat(best)
.as(() -> String.format("'%s'=%d, '%s'=%d", first, calcRating(first), second, calcRating(second)))
.isEqualTo(first);
}
}
5 changes: 5 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import jadx.api.DecompilationMode;
import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.args.ResourceNameSource;
import jadx.cli.JadxCLIArgs;
import jadx.cli.LogHelper;
import jadx.gui.ui.MainWindow;
Expand Down Expand Up @@ -348,6 +349,10 @@ public void setUseKotlinMethodsForVarNames(JadxArgs.UseKotlinMethodsForVarNames
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}

public void setResourceNameSource(ResourceNameSource source) {
this.resourceNameSource = source;
}

public void updateRenameFlag(JadxArgs.RenameEnum flag, boolean enabled) {
if (enabled) {
renameFlags.add(flag);
Expand Down
Loading

0 comments on commit ab4b6f9

Please sign in to comment.