feat: replace Android resource ids with android.R fields (#2119)
This commit is contained in:
@@ -54,6 +54,7 @@ import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
|
||||
import jadx.core.dex.visitors.prepare.AddAndroidConstants;
|
||||
import jadx.core.dex.visitors.prepare.CollectConstValues;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||
@@ -97,6 +98,7 @@ public class Jadx {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new SignatureProcessor());
|
||||
passes.add(new OverrideMethodVisitor());
|
||||
passes.add(new AddAndroidConstants());
|
||||
passes.add(new CollectConstValues());
|
||||
|
||||
// rename and deobfuscation
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package jadx.core.dex.visitors.prepare;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.ConstStorage;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.utils.android.AndroidResourcesMap;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
// TODO: move this pass to separate "Android plugin"
|
||||
@JadxVisitor(
|
||||
name = "AddAndroidConstants",
|
||||
desc = "Insert Android constants from resource mapping file",
|
||||
runBefore = {
|
||||
CollectConstValues.class
|
||||
}
|
||||
)
|
||||
public class AddAndroidConstants extends AbstractVisitor {
|
||||
|
||||
private static final String R_CLS = "android.R";
|
||||
private static final String R_INNER_CLS = R_CLS + '$';
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
if (!root.getArgs().isReplaceConsts()) {
|
||||
return;
|
||||
}
|
||||
if (root.resolveClass(R_CLS) != null) {
|
||||
// Android R class already loaded
|
||||
return;
|
||||
}
|
||||
ConstStorage constStorage = root.getConstValues();
|
||||
AndroidResourcesMap.getMap().forEach((resId, path) -> {
|
||||
int sep = path.indexOf('/');
|
||||
String clsName = R_INNER_CLS + path.substring(0, sep);
|
||||
String resName = path.substring(sep + 1);
|
||||
ClassInfo cls = ClassInfo.fromName(root, clsName);
|
||||
FieldInfo field = FieldInfo.from(root, cls, resName, ArgType.INT);
|
||||
constStorage.addGlobalConstField(field, resId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package jadx.core.utils.android;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
* Store resources id to name mapping
|
||||
*/
|
||||
public class AndroidResourcesMap {
|
||||
private static final Map<Integer, String> RES_MAP = loadBundled();
|
||||
|
||||
public static @Nullable String getResName(int resId) {
|
||||
return RES_MAP.get(resId);
|
||||
}
|
||||
|
||||
public static Map<Integer, String> getMap() {
|
||||
return RES_MAP;
|
||||
}
|
||||
|
||||
private static Map<Integer, String> loadBundled() {
|
||||
try (InputStream is = AndroidResourcesMap.class.getResourceAsStream("/android/res-map.txt")) {
|
||||
return TextResMapFile.read(is);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to load android resource file (res-map.txt)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import jadx.core.dex.info.ConstStorage;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.android.AndroidResourcesMap;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.xmlgen.entry.ValuesParser;
|
||||
|
||||
@@ -390,7 +391,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
// there is no entry uses the values form the XML string pool
|
||||
if (resourceIds != null && 0 <= id && id < resourceIds.length) {
|
||||
int resId = resourceIds[id];
|
||||
String str = ValuesParser.getAndroidResMap().get(resId);
|
||||
String str = AndroidResourcesMap.getResName(resId);
|
||||
if (str != null) {
|
||||
// cut type before /
|
||||
int typeEnd = str.indexOf('/');
|
||||
@@ -427,7 +428,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
}
|
||||
writer.add(resName);
|
||||
} else {
|
||||
String androidResName = ValuesParser.getAndroidResMap().get(attrValData);
|
||||
String androidResName = AndroidResourcesMap.getResName(attrValData);
|
||||
if (androidResName != null) {
|
||||
writer.add("@android:").add(androidResName);
|
||||
} else if (attrValData == 0) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.xmlgen.entry.ValuesParser;
|
||||
import jadx.core.utils.android.AndroidResourcesMap;
|
||||
|
||||
public class ProtoXMLParser extends CommonProtoParser {
|
||||
private Map<String, String> nsMap;
|
||||
@@ -102,7 +102,7 @@ public class ProtoXMLParser extends CommonProtoParser {
|
||||
if (attrName.isEmpty()) {
|
||||
// some optimization tools clear the name because the Android platform doesn't need it
|
||||
int resId = a.getResourceId();
|
||||
String str = ValuesParser.getAndroidResMap().get(resId);
|
||||
String str = AndroidResourcesMap.getResName(resId);
|
||||
if (str != null) {
|
||||
namespace = nsMap.get(ParserConstants.ANDROID_NS_URL);
|
||||
// cut type before /
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.core.xmlgen.entry;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -9,8 +8,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.android.TextResMapFile;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.android.AndroidResourcesMap;
|
||||
import jadx.core.xmlgen.BinaryXMLStrings;
|
||||
import jadx.core.xmlgen.ParserConstants;
|
||||
import jadx.core.xmlgen.XmlGenUtils;
|
||||
@@ -18,30 +16,12 @@ import jadx.core.xmlgen.XmlGenUtils;
|
||||
public class ValuesParser extends ParserConstants {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ValuesParser.class);
|
||||
|
||||
private static Map<Integer, String> androidResMap;
|
||||
|
||||
private final BinaryXMLStrings strings;
|
||||
private final Map<Integer, String> resMap;
|
||||
|
||||
public ValuesParser(BinaryXMLStrings strings, Map<Integer, String> resMap) {
|
||||
this.strings = strings;
|
||||
this.resMap = resMap;
|
||||
getAndroidResMap();
|
||||
}
|
||||
|
||||
public static Map<Integer, String> getAndroidResMap() {
|
||||
if (androidResMap == null) {
|
||||
androidResMap = loadAndroidResMap();
|
||||
}
|
||||
return androidResMap;
|
||||
}
|
||||
|
||||
private static Map<Integer, String> loadAndroidResMap() {
|
||||
try (InputStream is = ValuesParser.class.getResourceAsStream("/android/res-map.txt")) {
|
||||
return TextResMapFile.read(is);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to load android resource file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -128,7 +108,7 @@ public class ValuesParser extends ParserConstants {
|
||||
case TYPE_REFERENCE: {
|
||||
String ri = resMap.get(data);
|
||||
if (ri == null) {
|
||||
String androidRi = androidResMap.get(data);
|
||||
String androidRi = AndroidResourcesMap.getResName(data);
|
||||
if (androidRi != null) {
|
||||
return "@android:" + androidRi;
|
||||
}
|
||||
@@ -143,7 +123,7 @@ public class ValuesParser extends ParserConstants {
|
||||
case TYPE_ATTRIBUTE: {
|
||||
String ri = resMap.get(data);
|
||||
if (ri == null) {
|
||||
String androidRi = androidResMap.get(data);
|
||||
String androidRi = AndroidResourcesMap.getResName(data);
|
||||
if (androidRi != null) {
|
||||
return "?android:" + androidRi;
|
||||
}
|
||||
@@ -178,7 +158,7 @@ public class ValuesParser extends ParserConstants {
|
||||
if (ri != null) {
|
||||
return ri.replace('/', '.');
|
||||
} else {
|
||||
String androidRi = androidResMap.get(ref);
|
||||
String androidRi = AndroidResourcesMap.getResName(ref);
|
||||
if (androidRi != null) {
|
||||
return "android:" + androidRi.replace('/', '.');
|
||||
}
|
||||
|
||||
@@ -4,13 +4,15 @@ import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.core.utils.android.AndroidResourcesMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ValuesParserTest {
|
||||
|
||||
@Test
|
||||
void testResMapLoad() {
|
||||
Map<Integer, String> androidResMap = ValuesParser.getAndroidResMap();
|
||||
Map<Integer, String> androidResMap = AndroidResourcesMap.getMap();
|
||||
assertThat(androidResMap).isNotNull().isNotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package jadx.tests.integration.android;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestResConstReplace extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public int test() {
|
||||
return 0x0101013f; // android.R.attr.minWidth
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("import android.R;")
|
||||
.containsOne("return R.attr.minWidth;");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user