diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index b6b314778..cd5fd68a1 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -6,11 +6,10 @@ dependencies { api(project(':jadx-plugins:jadx-plugins-api')) implementation 'com.google.code.gson:gson:2.9.0' - implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631' - constraints { - // Force protobuf version to prevent Java-7 issue - implementation 'com.google.protobuf:protobuf-java:3.11.4' - } + + // TODO: move resources decoding to separate plugin module + implementation 'com.android.tools.build:aapt2-proto:7.2.1-7984345' + implementation 'com.google.protobuf:protobuf-java:3.21.2' // forcing latest version testImplementation 'org.apache.commons:commons-lang3:3.12.0' diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 6f77b06d7..c641ea99b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -42,7 +42,8 @@ import jadx.core.utils.StringUtils; import jadx.core.utils.Utils; import jadx.core.utils.android.AndroidResourcesUtils; import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.core.xmlgen.ResTableParser; +import jadx.core.xmlgen.IResParser; +import jadx.core.xmlgen.ResDecoder; import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ValuesParser; @@ -163,23 +164,13 @@ public class RootNode { } public void loadResources(List resources) { - ResourceFile arsc = null; - for (ResourceFile rf : resources) { - if (rf.getType() == ResourceType.ARSC) { - arsc = rf; - break; - } - } + ResourceFile arsc = getResourceFile(resources); if (arsc == null) { LOG.debug("'.arsc' file not found"); return; } try { - ResTableParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> { - ResTableParser tableParser = new ResTableParser(this); - tableParser.decode(is); - return tableParser; - }); + IResParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> ResDecoder.decode(this, arsc, is)); if (parser != null) { processResources(parser.getResStorage()); updateObfuscatedFiles(parser, resources); @@ -189,6 +180,15 @@ public class RootNode { } } + private @Nullable ResourceFile getResourceFile(List resources) { + for (ResourceFile rf : resources) { + if (rf.getType() == ResourceType.ARSC) { + return rf; + } + } + return null; + } + public void processResources(ResourceStorage resStorage) { constValues.setResourcesNames(resStorage.getResourcesNames()); appPackage = resStorage.getAppPackage(); @@ -209,7 +209,7 @@ public class RootNode { } } - private void updateObfuscatedFiles(ResTableParser parser, List resources) { + private void updateObfuscatedFiles(IResParser parser, List resources) { if (args.isSkipResources()) { return; } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java new file mode 100644 index 000000000..5c890c15f --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java @@ -0,0 +1,13 @@ +package jadx.core.xmlgen; + +import java.io.IOException; +import java.io.InputStream; + +public interface IResParser { + + void decode(InputStream inputStream) throws IOException; + + ResourceStorage getResStorage(); + + String[] getStrings(); +} diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResDecoder.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResDecoder.java new file mode 100644 index 000000000..6cd6e7fc0 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResDecoder.java @@ -0,0 +1,31 @@ +package jadx.core.xmlgen; + +import java.io.IOException; +import java.io.InputStream; + +import jadx.api.ResourceFile; +import jadx.api.ResourceType; +import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.exceptions.JadxRuntimeException; + +public class ResDecoder { + + public static IResParser decode(RootNode root, ResourceFile resFile, InputStream is) throws IOException { + if (resFile.getType() != ResourceType.ARSC) { + throw new IllegalArgumentException("Unexpected resource type for decode: " + resFile.getType() + ", expect ARSC"); + } + IResParser parser = null; + String fileName = resFile.getOriginalName(); + if (fileName.endsWith(".arsc")) { + parser = new ResTableParser(root); + } + if (fileName.endsWith(".pb")) { + parser = new ResProtoParser(root); + } + if (parser == null) { + throw new JadxRuntimeException("Unknown type of resource file: " + fileName); + } + parser.decode(is); + return parser; + } +} diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java index 56aaab1d4..1a6aa569e 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java @@ -20,16 +20,16 @@ import com.android.aapt.Resources.Style; import com.android.aapt.Resources.Styleable; import com.android.aapt.Resources.Type; import com.android.aapt.Resources.Value; -import com.google.protobuf.InvalidProtocolBufferException; import jadx.api.ICodeInfo; import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.files.FileUtils; import jadx.core.xmlgen.entry.EntryConfig; import jadx.core.xmlgen.entry.ProtoValue; import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ValuesParser; -public class ResProtoParser { +public class ResProtoParser implements IResParser { private final RootNode root; private final ResourceStorage resStorage = new ResourceStorage(); @@ -38,11 +38,7 @@ public class ResProtoParser { } public ResContainer decodeFiles(InputStream inputStream) throws IOException { - ResourceTable table = decodeProto(inputStream); - for (Package p : table.getPackageList()) { - parse(p); - } - resStorage.finish(); + decode(inputStream); ValuesParser vp = new ValuesParser(new String[0], resStorage.getResourcesNames()); ResXmlGen resGen = new ResXmlGen(resStorage, vp); ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage); @@ -50,6 +46,15 @@ public class ResProtoParser { return ResContainer.resourceTable("res", xmlFiles, content); } + @Override + public void decode(InputStream inputStream) throws IOException { + ResourceTable table = ResourceTable.parseFrom(FileUtils.streamToByteArray(inputStream)); + for (Package p : table.getPackageList()) { + parse(p); + } + resStorage.finish(); + } + private void parse(Package p) { String name = p.getPackageName(); resStorage.setAppPackage(name); @@ -241,8 +246,13 @@ public class ResProtoParser { return ""; } - private ResourceTable decodeProto(InputStream inputStream) - throws InvalidProtocolBufferException, IOException { - return ResourceTable.parseFrom(XmlGenUtils.readData(inputStream)); + @Override + public ResourceStorage getResStorage() { + return resStorage; + } + + @Override + public String[] getStrings() { + return new String[0]; } } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java index 6c5554e0b..00ef0d1ab 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java @@ -24,7 +24,7 @@ import jadx.core.xmlgen.entry.RawValue; import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ValuesParser; -public class ResTableParser extends CommonBinaryParser { +public class ResTableParser extends CommonBinaryParser implements IResParser { private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class); private static final Pattern VALID_RES_KEY_PATTERN = Pattern.compile("[\\w\\d_]+"); @@ -76,6 +76,7 @@ public class ResTableParser extends CommonBinaryParser { this.useRawResName = useRawResNames; } + @Override public void decode(InputStream inputStream) throws IOException { is = new ParserStream(inputStream); decodeTableChunk(); @@ -93,14 +94,6 @@ public class ResTableParser extends CommonBinaryParser { return ResContainer.resourceTable("res", xmlFiles, content); } - public ResourceStorage getResStorage() { - return resStorage; - } - - public String[] getStrings() { - return strings; - } - void decodeTableChunk() throws IOException { is.checkInt16(RES_TABLE_TYPE, "Not a table chunk"); is.checkInt16(0x000c, "Unexpected table header size"); @@ -434,4 +427,14 @@ public class ResTableParser extends CommonBinaryParser { is.skipToPos(start + length, "readScriptOrVariantChar"); return sb.toString(); } + + @Override + public ResourceStorage getResStorage() { + return resStorage; + } + + @Override + public String[] getStrings() { + return strings; + } } diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index 5dd054b84..8f6ced206 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -26,7 +26,7 @@ dependencies { implementation 'io.reactivex.rxjava2:rxjava:2.2.21' implementation "com.github.akarnokd:rxjava2-swing:0.3.7" - implementation 'com.android.tools.build:apksig:4.2.1' + implementation 'com.android.tools.build:apksig:7.2.1' implementation 'io.github.hqktech:jdwp:1.0' // TODO: Switch back to upstream once this PR gets merged: diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index 4be0b9623..452acb7ec 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -226,6 +226,7 @@ public class JResource extends JLoadableNode { case MANIFEST: case XML: + case ARSC: return SyntaxConstants.SYNTAX_STYLE_XML; default: @@ -249,8 +250,7 @@ public class JResource extends JLoadableNode { "yaml", SyntaxConstants.SYNTAX_STYLE_YAML, "properties", SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE, "ini", SyntaxConstants.SYNTAX_STYLE_INI, - "sql", SyntaxConstants.SYNTAX_STYLE_SQL, - "arsc", SyntaxConstants.SYNTAX_STYLE_XML); + "sql", SyntaxConstants.SYNTAX_STYLE_SQL); private String getSyntaxByExtension(String name) { int dot = name.lastIndexOf('.');