diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index c6aa752a6..12b48e39a 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -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 passes = new ArrayList<>(); passes.add(new SignatureProcessor()); passes.add(new OverrideMethodVisitor()); + passes.add(new AddAndroidConstants()); passes.add(new CollectConstValues()); // rename and deobfuscation diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/prepare/AddAndroidConstants.java b/jadx-core/src/main/java/jadx/core/dex/visitors/prepare/AddAndroidConstants.java new file mode 100644 index 000000000..802f6703e --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/prepare/AddAndroidConstants.java @@ -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); + }); + } +} diff --git a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesMap.java b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesMap.java new file mode 100644 index 000000000..3000c1d89 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesMap.java @@ -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 RES_MAP = loadBundled(); + + public static @Nullable String getResName(int resId) { + return RES_MAP.get(resId); + } + + public static Map getMap() { + return RES_MAP; + } + + private static Map 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); + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java index c455f7b2a..a66ce2d8a 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java index 58b39efde..00dfe291c 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java @@ -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 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 / diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java index b30f3dfff..ef450bc15 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java @@ -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 androidResMap; - private final BinaryXMLStrings strings; private final Map resMap; public ValuesParser(BinaryXMLStrings strings, Map resMap) { this.strings = strings; this.resMap = resMap; - getAndroidResMap(); - } - - public static Map getAndroidResMap() { - if (androidResMap == null) { - androidResMap = loadAndroidResMap(); - } - return androidResMap; - } - - private static Map 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('/', '.'); } diff --git a/jadx-core/src/test/java/jadx/core/xmlgen/entry/ValuesParserTest.java b/jadx-core/src/test/java/jadx/core/xmlgen/entry/ValuesParserTest.java index 8ea0bf531..cfd8b9140 100644 --- a/jadx-core/src/test/java/jadx/core/xmlgen/entry/ValuesParserTest.java +++ b/jadx-core/src/test/java/jadx/core/xmlgen/entry/ValuesParserTest.java @@ -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 androidResMap = ValuesParser.getAndroidResMap(); + Map androidResMap = AndroidResourcesMap.getMap(); assertThat(androidResMap).isNotNull().isNotEmpty(); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/android/TestResConstReplace.java b/jadx-core/src/test/java/jadx/tests/integration/android/TestResConstReplace.java new file mode 100644 index 000000000..fcf7d3586 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestResConstReplace.java @@ -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;"); + } +}