From b85900aa3d324980742fc0adf425312e40c93cb6 Mon Sep 17 00:00:00 2001 From: Andrei Kudryavtsev Date: Sat, 27 Apr 2024 01:54:54 +0500 Subject: [PATCH] feat: move AAB support to separate plugin (PR #2165) * wip: finished with factories * wip: bundleconfig.pb * wip: jadx-aab-input, separate BundleConfig parser * wip: removed test apks * wip: proto xml pretty print * wip: fixed getNamedValues NPE * minor fixes * spotless * enabled zip64 for gui shadow jar * spotless * spotless * reverted manifest identification since signature parsing not working at the moment * replace static methods with new API methods --------- Co-authored-by: Skylot <118523+skylot@users.noreply.github.com> --- jadx-cli/build.gradle.kts | 1 + .../java/jadx/cli/tools/ConvertArscFile.java | 4 +- jadx-core/build.gradle.kts | 8 -- .../main/java/jadx/api/JadxDecompiler.java | 19 ++- .../main/java/jadx/api/ResourcesLoader.java | 109 ++++++++++++++---- .../jadx/api/plugins/JadxPluginContext.java | 6 + .../resources/IResContainerFactory.java | 33 ++++++ .../resources/IResTableParserProvider.java | 30 +++++ .../plugins/resources/IResourcesLoader.java | 8 ++ .../java/jadx/core/dex/nodes/RootNode.java | 21 ++-- .../java/jadx/core/plugins/PluginContext.java | 6 + .../{IResParser.java => IResTableParser.java} | 4 +- .../jadx/core/xmlgen/ManifestAttributes.java | 7 +- .../java/jadx/core/xmlgen/ResDecoder.java | 27 ----- ...eParser.java => ResTableBinaryParser.java} | 13 +-- .../xmlgen/ResTableBinaryParserProvider.java | 25 ++++ jadx-gui/build.gradle.kts | 1 + .../gui/ui/filedialog/FileDialogWrapper.java | 3 +- jadx-plugins/jadx-aab-input/build.gradle.kts | 26 +++++ .../plugins/input/aab/AabInputPlugin.java | 32 +++++ .../aab/ResTableProtoParserProvider.java | 27 +++++ .../ProtoBundleConfigResContainerFactory.java | 27 +++++ .../ProtoTableResContainerFactory.java | 33 ++++++ .../ProtoXmlResContainerFactory.java | 41 +++++++ .../input/aab/parsers}/CommonProtoParser.java | 6 +- .../aab/parsers/ResTableProtoParser.java | 30 +++-- .../input/aab/parsers/ResXmlProtoParser.java | 84 ++++++++++---- .../services/jadx.api.plugins.JadxPlugin | 1 + settings.gradle.kts | 1 + 29 files changed, 500 insertions(+), 133 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/api/plugins/resources/IResContainerFactory.java create mode 100644 jadx-core/src/main/java/jadx/api/plugins/resources/IResTableParserProvider.java create mode 100644 jadx-core/src/main/java/jadx/api/plugins/resources/IResourcesLoader.java rename jadx-core/src/main/java/jadx/core/xmlgen/{IResParser.java => IResTableParser.java} (76%) rename jadx-core/src/main/java/jadx/core/xmlgen/{ResTableParser.java => ResTableBinaryParser.java} (98%) create mode 100644 jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParserProvider.java create mode 100644 jadx-plugins/jadx-aab-input/build.gradle.kts create mode 100644 jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/AabInputPlugin.java create mode 100644 jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/ResTableProtoParserProvider.java create mode 100644 jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoBundleConfigResContainerFactory.java create mode 100644 jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoTableResContainerFactory.java create mode 100644 jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoXmlResContainerFactory.java rename {jadx-core/src/main/java/jadx/core/xmlgen => jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers}/CommonProtoParser.java (97%) rename jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java => jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/ResTableProtoParser.java (83%) rename jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java => jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/ResXmlProtoParser.java (71%) create mode 100644 jadx-plugins/jadx-aab-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin diff --git a/jadx-cli/build.gradle.kts b/jadx-cli/build.gradle.kts index e04fc19e786..045bba83585 100644 --- a/jadx-cli/build.gradle.kts +++ b/jadx-cli/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata")) runtimeOnly(project(":jadx-plugins:jadx-script:jadx-script-plugin")) runtimeOnly(project(":jadx-plugins:jadx-xapk-input")) + runtimeOnly(project(":jadx-plugins:jadx-aab-input")) implementation("org.jcommander:jcommander:1.83") implementation("ch.qos.logback:logback-classic:1.5.6") diff --git a/jadx-cli/src/main/java/jadx/cli/tools/ConvertArscFile.java b/jadx-cli/src/main/java/jadx/cli/tools/ConvertArscFile.java index 350f1c07af0..a4bed122cb2 100644 --- a/jadx-cli/src/main/java/jadx/cli/tools/ConvertArscFile.java +++ b/jadx-cli/src/main/java/jadx/cli/tools/ConvertArscFile.java @@ -20,7 +20,7 @@ import jadx.api.JadxArgs; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.android.TextResMapFile; -import jadx.core.xmlgen.ResTableParser; +import jadx.core.xmlgen.ResTableBinaryParser; /** * Utility class for convert '.arsc' to simple text file with mapping id to resource name @@ -54,7 +54,7 @@ public static void main(String[] args) throws IOException { rewritesCount = 0; for (Path resFile : inputPaths) { LOG.info("Processing {}", resFile); - ResTableParser resTableParser = new ResTableParser(root, true); + ResTableBinaryParser resTableParser = new ResTableBinaryParser(root, true); if (resFile.getFileName().toString().endsWith(".jar")) { // Load resources.arsc from android.jar try (ZipFile zip = new ZipFile(resFile.toFile())) { diff --git a/jadx-core/build.gradle.kts b/jadx-core/build.gradle.kts index 158962475ba..efad45c1a61 100644 --- a/jadx-core/build.gradle.kts +++ b/jadx-core/build.gradle.kts @@ -7,14 +7,6 @@ dependencies { implementation("com.google.code.gson:gson:2.10.1") - // TODO: move resources decoding to separate plugin module - implementation("com.android.tools.build:aapt2-proto:8.3.2-10880808") - implementation("com.google.protobuf:protobuf-java") { - version { - require("3.25.3") // version 4 conflict with `aapt2-proto` - } - } - testImplementation("org.apache.commons:commons-lang3:3.14.0") testImplementation(project(":jadx-plugins:jadx-dex-input")) diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index fc0d4de7f71..1079f2c82ce 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -52,7 +52,6 @@ import jadx.core.utils.files.FileUtils; import jadx.core.utils.tasks.TaskExecutor; import jadx.core.xmlgen.BinaryXMLParser; -import jadx.core.xmlgen.ProtoXMLParser; import jadx.core.xmlgen.ResourcesSaver; /** @@ -94,10 +93,10 @@ public final class JadxDecompiler implements Closeable { private List resources; private BinaryXMLParser binaryXmlParser; - private ProtoXMLParser protoXmlParser; private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(); private final JadxEventsImpl events = new JadxEventsImpl(); + private final ResourcesLoader resourcesLoader = new ResourcesLoader(this); private final List customCodeLoaders = new ArrayList<>(); private final List customResourcesLoaders = new ArrayList<>(); @@ -124,7 +123,7 @@ public void load() { root.mergePasses(customPasses); root.loadClasses(loadedInputs); root.initClassPath(); - root.loadResources(getResources()); + root.loadResources(resourcesLoader, getResources()); root.runPreDecompileStage(); root.initPasses(); loadFinished(); @@ -170,7 +169,6 @@ private void reset() { classes = null; resources = null; binaryXmlParser = null; - protoXmlParser = null; events.reset(); } @@ -430,7 +428,7 @@ public synchronized List getResources() { if (root == null) { return Collections.emptyList(); } - resources = new ResourcesLoader(this).load(); + resources = resourcesLoader.load(root); } return resources; } @@ -476,13 +474,6 @@ synchronized BinaryXMLParser getBinaryXmlParser() { return binaryXmlParser; } - synchronized ProtoXMLParser getProtoXmlParser() { - if (protoXmlParser == null) { - protoXmlParser = new ProtoXMLParser(root); - } - return protoXmlParser; - } - /** * Get JavaClass by ClassNode without loading and decompilation */ @@ -704,6 +695,10 @@ public void addCustomPass(JadxPass pass) { customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass); } + public ResourcesLoader getResourcesLoader() { + return resourcesLoader; + } + @Override public String toString() { return "jadx decompiler " + getVersion(); diff --git a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java index 15c39687b43..9b7edd11259 100644 --- a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java +++ b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java @@ -17,30 +17,42 @@ import jadx.api.ResourceFile.ZipRef; import jadx.api.impl.SimpleCodeInfo; import jadx.api.plugins.CustomResourcesLoader; +import jadx.api.plugins.resources.IResContainerFactory; +import jadx.api.plugins.resources.IResTableParserProvider; +import jadx.api.plugins.resources.IResourcesLoader; import jadx.api.plugins.utils.ZipSecurity; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.Utils; import jadx.core.utils.android.Res9patchStreamDecoder; import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; +import jadx.core.xmlgen.BinaryXMLParser; +import jadx.core.xmlgen.IResTableParser; import jadx.core.xmlgen.ResContainer; -import jadx.core.xmlgen.ResProtoParser; -import jadx.core.xmlgen.ResTableParser; +import jadx.core.xmlgen.ResTableBinaryParserProvider; import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE; import static jadx.core.utils.files.FileUtils.copyStream; // TODO: move to core package -public final class ResourcesLoader { +public final class ResourcesLoader implements IResourcesLoader { private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class); private final JadxDecompiler jadxRef; + private final List resTableParserProviders = new ArrayList<>(); + private final List resContainerFactories = new ArrayList<>(); + + private BinaryXMLParser binaryXmlParser; + ResourcesLoader(JadxDecompiler jadxRef) { this.jadxRef = jadxRef; + this.resTableParserProviders.add(new ResTableBinaryParserProvider()); } - List load() { + List load(RootNode root) { + init(root); List inputFiles = jadxRef.getArgs().getInputFiles(); List list = new ArrayList<>(inputFiles.size()); for (File file : inputFiles) { @@ -49,10 +61,37 @@ List load() { return list; } + private void init(RootNode root) { + for (IResTableParserProvider resTableParserProvider : resTableParserProviders) { + try { + resTableParserProvider.init(root); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to init res table provider: " + resTableParserProvider); + } + } + for (IResContainerFactory resContainerFactory : resContainerFactories) { + try { + resContainerFactory.init(root); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to init res container factory: " + resContainerFactory); + } + } + } + public interface ResourceDecoder { T decode(long size, InputStream is) throws IOException; } + @Override + public void addResContainerFactory(IResContainerFactory resContainerFactory) { + resContainerFactories.add(resContainerFactory); + } + + @Override + public void addResTableParserProvider(IResTableParserProvider resTableParserProvider) { + resTableParserProviders.add(resTableParserProvider); + } + public static T decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException { try { ZipRef zipRef = rf.getZipRef(); @@ -82,7 +121,8 @@ public static T decodeStream(ResourceFile rf, ResourceDecoder decoder) th static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) { try { - return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is)); + ResourcesLoader resLoader = jadxRef.getResourcesLoader(); + return decodeStream(rf, (size, is) -> resLoader.loadContent(rf, is)); } catch (JadxException e) { LOG.error("Decode error", e); ICodeWriter cw = jadxRef.getRoot().makeCodeWriter(); @@ -92,34 +132,46 @@ static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) { } } - private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf, - InputStream inputStream) throws IOException { - RootNode root = jadxRef.getRoot(); - switch (rf.getType()) { - case MANIFEST: - case XML: { - ICodeInfo content; - if (root.isProto()) { - content = jadxRef.getProtoXmlParser().parse(inputStream); - } else { - content = jadxRef.getBinaryXmlParser().parse(inputStream); - } - return ResContainer.textResource(rf.getDeobfName(), content); + private ResContainer loadContent(ResourceFile resFile, InputStream inputStream) throws IOException { + for (IResContainerFactory customFactory : resContainerFactories) { + ResContainer resContainer = customFactory.create(resFile, inputStream); + if (resContainer != null) { + return resContainer; } + } + switch (resFile.getType()) { + case MANIFEST: + case XML: + ICodeInfo content = loadBinaryXmlParser().parse(inputStream); + return ResContainer.textResource(resFile.getDeobfName(), content); case ARSC: - if (root.isProto()) { - return new ResProtoParser(root).decodeFiles(inputStream); - } else { - return new ResTableParser(root).decodeFiles(inputStream); - } + return decodeTable(resFile, inputStream).decodeFiles(); case IMG: - return decodeImage(rf, inputStream); + return decodeImage(resFile, inputStream); default: - return ResContainer.resourceFileLink(rf); + return ResContainer.resourceFileLink(resFile); + } + } + + public IResTableParser decodeTable(ResourceFile resFile, InputStream is) throws IOException { + if (resFile.getType() != ResourceType.ARSC) { + throw new IllegalArgumentException("Unexpected resource type for decode: " + resFile.getType() + ", expect '.pb'/'.arsc'"); + } + IResTableParser parser = null; + for (IResTableParserProvider provider : resTableParserProviders) { + parser = provider.getParser(resFile); + if (parser != null) { + break; + } + } + if (parser == null) { + throw new JadxRuntimeException("Unknown type of resource file: " + resFile.getOriginalName()); } + parser.decode(is); + return parser; } private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) { @@ -184,4 +236,11 @@ public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException { copyStream(is, baos); return new SimpleCodeInfo(baos.toString("UTF-8")); } + + private synchronized BinaryXMLParser loadBinaryXmlParser() { + if (binaryXmlParser == null) { + binaryXmlParser = new BinaryXMLParser(jadxRef.getRoot()); + } + return binaryXmlParser; + } } diff --git a/jadx-core/src/main/java/jadx/api/plugins/JadxPluginContext.java b/jadx-core/src/main/java/jadx/api/plugins/JadxPluginContext.java index 498b1ae1a25..e203df43ce4 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/JadxPluginContext.java +++ b/jadx-core/src/main/java/jadx/api/plugins/JadxPluginContext.java @@ -12,6 +12,7 @@ import jadx.api.plugins.input.JadxCodeInput; import jadx.api.plugins.options.JadxPluginOptions; import jadx.api.plugins.pass.JadxPass; +import jadx.api.plugins.resources.IResourcesLoader; public interface JadxPluginContext { @@ -32,6 +33,11 @@ public interface JadxPluginContext { */ void registerInputsHashSupplier(Supplier supplier); + /** + * Customize resource loading + */ + IResourcesLoader getResourcesLoader(); + /** * Access to jadx-gui specific methods */ diff --git a/jadx-core/src/main/java/jadx/api/plugins/resources/IResContainerFactory.java b/jadx-core/src/main/java/jadx/api/plugins/resources/IResContainerFactory.java new file mode 100644 index 00000000000..9e7fd6cf7d6 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/plugins/resources/IResContainerFactory.java @@ -0,0 +1,33 @@ +package jadx.api.plugins.resources; + +import java.io.IOException; +import java.io.InputStream; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.ResourceFile; +import jadx.core.dex.nodes.RootNode; +import jadx.core.xmlgen.ResContainer; + +/** + * Factory for {@link ResContainer}. Can be used in plugins via + * {@code context.getResourcesLoader().addResContainerFactory()} to implement content parsing in + * files with + * different formats. + */ +public interface IResContainerFactory { + + /** + * Optional init method + */ + default void init(RootNode root) { + } + + /** + * Checks if resource file is of expected format and tries to parse its content. + * + * @return {@link ResContainer} if file is of expected format, {@code null} otherwise. + */ + @Nullable + ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException; +} diff --git a/jadx-core/src/main/java/jadx/api/plugins/resources/IResTableParserProvider.java b/jadx-core/src/main/java/jadx/api/plugins/resources/IResTableParserProvider.java new file mode 100644 index 00000000000..b6cb8bb3fb9 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/plugins/resources/IResTableParserProvider.java @@ -0,0 +1,30 @@ +package jadx.api.plugins.resources; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.ResourceFile; +import jadx.core.dex.nodes.RootNode; +import jadx.core.xmlgen.IResTableParser; + +/** + * Provides the resource table parser instance for specific resource table file format. Can be used + * in plugins via {@code context.getResourcesLoader().addResTableParserProvider()} to parse + * resources from tables + * in different formats. + */ +public interface IResTableParserProvider { + + /** + * Optional init method + */ + default void init(RootNode root) { + } + + /** + * Checks a file format and provides the instance if the format is expected. + * + * @return {@link IResTableParser} if resource table is of expected format, {@code null} otherwise. + */ + @Nullable + IResTableParser getParser(ResourceFile resFile); +} diff --git a/jadx-core/src/main/java/jadx/api/plugins/resources/IResourcesLoader.java b/jadx-core/src/main/java/jadx/api/plugins/resources/IResourcesLoader.java new file mode 100644 index 00000000000..d9a25e6c0c3 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/plugins/resources/IResourcesLoader.java @@ -0,0 +1,8 @@ +package jadx.api.plugins.resources; + +public interface IResourcesLoader { + + void addResContainerFactory(IResContainerFactory resContainerFactory); + + void addResTableParserProvider(IResTableParserProvider resTableParserProvider); +} 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 6cb41d3de1a..45570d96355 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 @@ -54,9 +54,8 @@ import jadx.core.utils.Utils; import jadx.core.utils.android.AndroidResourcesUtils; import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.core.xmlgen.IResParser; +import jadx.core.xmlgen.IResTableParser; import jadx.core.xmlgen.ManifestAttributes; -import jadx.core.xmlgen.ResDecoder; import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ValuesParser; @@ -93,7 +92,6 @@ public class RootNode { private String appPackage; @Nullable private ClassNode appResClass; - private boolean isProto; /** * Optional decompiler reference @@ -109,7 +107,6 @@ public RootNode(JadxArgs args) { this.typeUpdate = new TypeUpdate(this); this.methodUtils = new MethodUtils(this); this.typeUtils = new TypeUtils(this); - this.isProto = args.getInputFiles().size() > 0 && args.getInputFiles().get(0).getName().toLowerCase().endsWith(".aab"); } public void init() { @@ -203,25 +200,25 @@ public void addClassNode(ClassNode clsNode) { rawClsMap.put(clsNode.getRawName(), clsNode); } - public void loadResources(List resources) { + public void loadResources(ResourcesLoader resLoader, List resources) { ResourceFile arsc = getResourceFile(resources); if (arsc == null) { - LOG.debug("'.arsc' file not found"); + LOG.debug("'resources.arsc' or 'resources.pb' file not found"); return; } try { - IResParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> ResDecoder.decode(this, arsc, is)); + IResTableParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> resLoader.decodeTable(arsc, is)); if (parser != null) { processResources(parser.getResStorage()); updateObfuscatedFiles(parser, resources); updateManifestAttribMap(parser); } } catch (Exception e) { - LOG.error("Failed to parse '.arsc' file", e); + LOG.error("Failed to parse 'resources.pb'/'.arsc' file", e); } } - private void updateManifestAttribMap(IResParser parser) { + private void updateManifestAttribMap(IResTableParser parser) { ManifestAttributes manifestAttributes = ManifestAttributes.getInstance(); manifestAttributes.updateAttributes(parser); } @@ -257,7 +254,7 @@ public void initClassPath() { } } - private void updateObfuscatedFiles(IResParser parser, List resources) { + private void updateObfuscatedFiles(IResTableParser parser, List resources) { if (args.isSkipResources()) { return; } @@ -715,10 +712,6 @@ public AttributeStorage getAttributes() { return attributes; } - public boolean isProto() { - return isProto; - } - public GradleInfoStorage getGradleInfoStorage() { return gradleInfoStorage; } diff --git a/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java b/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java index cc5c7b070c9..4fcf4a27686 100644 --- a/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java +++ b/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java @@ -26,6 +26,7 @@ import jadx.api.plugins.options.OptionDescription; import jadx.api.plugins.options.OptionFlag; import jadx.api.plugins.pass.JadxPass; +import jadx.api.plugins.resources.IResourcesLoader; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; @@ -131,6 +132,11 @@ public IJadxEvents events() { return decompiler.events(); } + @Override + public IResourcesLoader getResourcesLoader() { + return decompiler.getResourcesLoader(); + } + @Override public @Nullable JadxGuiContext getGuiContext() { return guiContext; diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/IResTableParser.java similarity index 76% rename from jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java rename to jadx-core/src/main/java/jadx/core/xmlgen/IResTableParser.java index 32301ec0358..3ae4634a35b 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/IResTableParser.java @@ -3,10 +3,12 @@ import java.io.IOException; import java.io.InputStream; -public interface IResParser { +public interface IResTableParser { void decode(InputStream inputStream) throws IOException; + ResContainer decodeFiles(); + ResourceStorage getResStorage(); BinaryXMLStrings getStrings(); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java b/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java index 1c6d43722b7..8b30ac13a96 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java @@ -199,13 +199,18 @@ public String decode(String attrName, long value) { return null; } - public void updateAttributes(IResParser parser) { + public void updateAttributes(IResTableParser parser) { appAttrMap.clear(); ResourceStorage resStorage = parser.getResStorage(); ValuesParser vp = new ValuesParser(parser.getStrings(), resStorage.getResourcesNames()); for (ResourceEntry ri : resStorage.getResources()) { + if (ri.getProtoValue() != null) { + // Aapt proto decoder resolves attributes by itself. + continue; + } + if (ri.getTypeName().equals("attr") && ri.getNamedValues().size() > 1) { RawNamedValue first = ri.getNamedValues().get(0); MAttrType attrTyp; diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResDecoder.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResDecoder.java index 6cd6e7fc082..6c1153e27a4 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResDecoder.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResDecoder.java @@ -1,31 +1,4 @@ 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/ResTableParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParser.java similarity index 98% rename from jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java rename to jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParser.java index a37bf7b797d..d69acfb7af4 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParser.java @@ -32,8 +32,8 @@ import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ValuesParser; -public class ResTableParser extends CommonBinaryParser implements IResParser { - private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class); +public class ResTableBinaryParser extends CommonBinaryParser implements IResTableParser { + private static final Logger LOG = LoggerFactory.getLogger(ResTableBinaryParser.class); private static final Pattern VALID_RES_KEY_PATTERN = Pattern.compile("[\\w\\d_]+"); @@ -75,11 +75,11 @@ public BinaryXMLStrings getKeyStrings() { private final ResourceStorage resStorage = new ResourceStorage(); private BinaryXMLStrings strings; - public ResTableParser(RootNode root) { + public ResTableBinaryParser(RootNode root) { this(root, false); } - public ResTableParser(RootNode root, boolean useRawResNames) { + public ResTableBinaryParser(RootNode root, boolean useRawResNames) { this.root = root; this.useRawResName = useRawResNames; } @@ -96,9 +96,8 @@ public void decode(InputStream inputStream) throws IOException { } } - public ResContainer decodeFiles(InputStream inputStream) throws IOException { - decode(inputStream); - + @Override + public ResContainer decodeFiles() { ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames()); ResXmlGen resGen = new ResXmlGen(resStorage, vp); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParserProvider.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParserProvider.java new file mode 100644 index 00000000000..eb3270b5993 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParserProvider.java @@ -0,0 +1,25 @@ +package jadx.core.xmlgen; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.ResourceFile; +import jadx.api.plugins.resources.IResTableParserProvider; +import jadx.core.dex.nodes.RootNode; + +public class ResTableBinaryParserProvider implements IResTableParserProvider { + private IResTableParser parser; + + @Override + public void init(RootNode root) { + parser = new ResTableBinaryParser(root); + } + + @Override + public synchronized @Nullable IResTableParser getParser(ResourceFile resFile) { + String fileName = resFile.getOriginalName(); + if (!fileName.endsWith(".arsc")) { + return null; + } + return parser; + } +} diff --git a/jadx-gui/build.gradle.kts b/jadx-gui/build.gradle.kts index c042bfdf369..b025143f3d8 100644 --- a/jadx-gui/build.gradle.kts +++ b/jadx-gui/build.gradle.kts @@ -85,6 +85,7 @@ tasks.jar { } tasks.shadowJar { + isZip64 = true mergeServiceFiles() manifest { from(project.tasks.jar.get().manifest) diff --git a/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java b/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java index ac50b8d03da..d406a5108af 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java @@ -83,7 +83,8 @@ private void initForMode(FileOpenMode mode) { case ADD: title = NLS.str("file.add_files_action"); - fileExtList = OPEN_FILES_EXTS; + fileExtList = new ArrayList<>(OPEN_FILES_EXTS); + fileExtList.add("aab"); selectionMode = JFileChooser.FILES_AND_DIRECTORIES; currentDir = mainWindow.getSettings().getLastOpenFilePath(); isOpen = true; diff --git a/jadx-plugins/jadx-aab-input/build.gradle.kts b/jadx-plugins/jadx-aab-input/build.gradle.kts new file mode 100644 index 00000000000..38a34d89e43 --- /dev/null +++ b/jadx-plugins/jadx-aab-input/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + id("jadx-library") +} + +dependencies { + compileOnly(project(":jadx-core")) + + implementation("com.android.tools.build:aapt2-proto:8.3.2-10880808") + implementation("com.google.protobuf:protobuf-java") { + version { + require("3.25.3") // version 4 conflict with `aapt2-proto` + } + } + + implementation("com.android.tools.build:bundletool:1.15.6") { + // All of this is unnecessary for parsing BundleConfig.pb except for protobuf + exclude(group = "com.android.tools.build") + exclude(group = "com.google.protobuf") + exclude(group = "com.google.guava") + exclude(group = "org.bitbucket.b_c") + exclude(group = "org.slf4j") + exclude(group = "com.google.auto.value") + exclude(group = "com.google.dagger") + exclude(group = "com.google.errorprone") + } +} diff --git a/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/AabInputPlugin.java b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/AabInputPlugin.java new file mode 100644 index 00000000000..f51c36084d3 --- /dev/null +++ b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/AabInputPlugin.java @@ -0,0 +1,32 @@ +package jadx.plugins.input.aab; + +import jadx.api.plugins.JadxPlugin; +import jadx.api.plugins.JadxPluginContext; +import jadx.api.plugins.JadxPluginInfo; +import jadx.api.plugins.resources.IResourcesLoader; +import jadx.plugins.input.aab.factories.ProtoBundleConfigResContainerFactory; +import jadx.plugins.input.aab.factories.ProtoTableResContainerFactory; +import jadx.plugins.input.aab.factories.ProtoXmlResContainerFactory; + +public class AabInputPlugin implements JadxPlugin { + public static final String PLUGIN_ID = "aab-input"; + + @Override + public JadxPluginInfo getPluginInfo() { + return new JadxPluginInfo( + PLUGIN_ID, + ".AAB Input", + "Loads .AAB files."); + } + + @Override + public synchronized void init(JadxPluginContext context) { + IResourcesLoader resourcesLoader = context.getResourcesLoader(); + ResTableProtoParserProvider tableParserProvider = new ResTableProtoParserProvider(); + resourcesLoader.addResTableParserProvider(tableParserProvider); + + resourcesLoader.addResContainerFactory(new ProtoTableResContainerFactory(tableParserProvider)); + resourcesLoader.addResContainerFactory(new ProtoXmlResContainerFactory()); + resourcesLoader.addResContainerFactory(new ProtoBundleConfigResContainerFactory()); + } +} diff --git a/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/ResTableProtoParserProvider.java b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/ResTableProtoParserProvider.java new file mode 100644 index 00000000000..cd06f975e25 --- /dev/null +++ b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/ResTableProtoParserProvider.java @@ -0,0 +1,27 @@ +package jadx.plugins.input.aab; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.ResourceFile; +import jadx.api.plugins.resources.IResTableParserProvider; +import jadx.core.dex.nodes.RootNode; +import jadx.core.xmlgen.IResTableParser; +import jadx.plugins.input.aab.parsers.ResTableProtoParser; + +public class ResTableProtoParserProvider implements IResTableParserProvider { + private ResTableProtoParser parser; + + @Override + public void init(RootNode root) { + parser = new ResTableProtoParser(root); + } + + @Override + public synchronized @Nullable IResTableParser getParser(ResourceFile resFile) { + String fileName = resFile.getOriginalName(); + if (!fileName.endsWith("resources.pb")) { + return null; + } + return parser; + } +} diff --git a/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoBundleConfigResContainerFactory.java b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoBundleConfigResContainerFactory.java new file mode 100644 index 00000000000..5e97d5c0b1d --- /dev/null +++ b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoBundleConfigResContainerFactory.java @@ -0,0 +1,27 @@ +package jadx.plugins.input.aab.factories; + +import java.io.IOException; +import java.io.InputStream; + +import org.jetbrains.annotations.Nullable; + +import com.android.bundle.Config.BundleConfig; + +import jadx.api.ICodeInfo; +import jadx.api.ResourceFile; +import jadx.api.impl.SimpleCodeInfo; +import jadx.api.plugins.resources.IResContainerFactory; +import jadx.core.xmlgen.ResContainer; + +public class ProtoBundleConfigResContainerFactory implements IResContainerFactory { + + @Override + public @Nullable ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException { + if (!resFile.getOriginalName().endsWith("BundleConfig.pb")) { + return null; + } + BundleConfig bundleConfig = BundleConfig.parseFrom(inputStream); + ICodeInfo content = new SimpleCodeInfo(bundleConfig.toString()); + return ResContainer.textResource(resFile.getDeobfName(), content); + } +} diff --git a/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoTableResContainerFactory.java b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoTableResContainerFactory.java new file mode 100644 index 00000000000..4ccf5d8e0f2 --- /dev/null +++ b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoTableResContainerFactory.java @@ -0,0 +1,33 @@ +package jadx.plugins.input.aab.factories; + +import java.io.IOException; +import java.io.InputStream; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.ResourceFile; +import jadx.api.ResourceType; +import jadx.api.plugins.resources.IResContainerFactory; +import jadx.api.plugins.resources.IResTableParserProvider; +import jadx.core.xmlgen.IResTableParser; +import jadx.core.xmlgen.ResContainer; + +public class ProtoTableResContainerFactory implements IResContainerFactory { + private final IResTableParserProvider provider; + + public ProtoTableResContainerFactory(IResTableParserProvider provider) { + this.provider = provider; + } + + @Override + public @Nullable ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException { + if (!resFile.getOriginalName().endsWith(".pb") || resFile.getType() != ResourceType.ARSC) { + return null; + } + IResTableParser parser = provider.getParser(resFile); + if (parser == null) { + return null; + } + return parser.decodeFiles(); + } +} diff --git a/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoXmlResContainerFactory.java b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoXmlResContainerFactory.java new file mode 100644 index 00000000000..1c325d8007e --- /dev/null +++ b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/factories/ProtoXmlResContainerFactory.java @@ -0,0 +1,41 @@ +package jadx.plugins.input.aab.factories; + +import java.io.IOException; +import java.io.InputStream; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.ICodeInfo; +import jadx.api.ResourceFile; +import jadx.api.ResourceType; +import jadx.api.plugins.resources.IResContainerFactory; +import jadx.core.dex.nodes.RootNode; +import jadx.core.xmlgen.ResContainer; +import jadx.plugins.input.aab.parsers.ResXmlProtoParser; + +public class ProtoXmlResContainerFactory implements IResContainerFactory { + private ResXmlProtoParser xmlParser; + + @Override + public void init(RootNode root) { + xmlParser = new ResXmlProtoParser(root); + } + + @Override + public @Nullable ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException { + ResourceType type = resFile.getType(); + if (type != ResourceType.XML && type != ResourceType.MANIFEST) { + return null; + } + ResourceFile.ZipRef ref = resFile.getZipRef(); + if (ref == null) { + return null; + } + boolean isFromAab = ref.getZipFile().getPath().contains(".aab"); + if (!isFromAab) { + return null; + } + ICodeInfo content = xmlParser.parse(inputStream); + return ResContainer.textResource(resFile.getDeobfName(), content); + } +} diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/CommonProtoParser.java b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/CommonProtoParser.java similarity index 97% rename from jadx-core/src/main/java/jadx/core/xmlgen/CommonProtoParser.java rename to jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/CommonProtoParser.java index 474309f2bed..9ab8c50fad9 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/CommonProtoParser.java +++ b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/CommonProtoParser.java @@ -1,4 +1,4 @@ -package jadx.core.xmlgen; +package jadx.plugins.input.aab.parsers; import java.util.ArrayList; import java.util.List; @@ -6,10 +6,12 @@ import com.android.aapt.ConfigurationOuterClass; import com.android.aapt.Resources; +import jadx.core.xmlgen.ParserConstants; +import jadx.core.xmlgen.XmlGenUtils; import jadx.core.xmlgen.entry.EntryConfig; import jadx.core.xmlgen.entry.ProtoValue; -public class CommonProtoParser { +public class CommonProtoParser extends ParserConstants { protected ProtoValue parse(Resources.Style s) { List namedValues = new ArrayList<>(s.getEntryCount()); String parent = s.getParent().getName(); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/ResTableProtoParser.java similarity index 83% rename from jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java rename to jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/ResTableProtoParser.java index 0496d122a3c..f32b12f80dc 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java +++ b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/ResTableProtoParser.java @@ -1,4 +1,4 @@ -package jadx.core.xmlgen; +package jadx.plugins.input.aab.parsers; import java.io.IOException; import java.io.InputStream; @@ -14,27 +14,24 @@ import jadx.api.ICodeInfo; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.files.FileUtils; +import jadx.core.xmlgen.BinaryXMLStrings; +import jadx.core.xmlgen.IResTableParser; +import jadx.core.xmlgen.ResContainer; +import jadx.core.xmlgen.ResXmlGen; +import jadx.core.xmlgen.ResourceStorage; +import jadx.core.xmlgen.XmlGenUtils; import jadx.core.xmlgen.entry.ProtoValue; import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ValuesParser; -public class ResProtoParser extends CommonProtoParser implements IResParser { +public class ResTableProtoParser extends CommonProtoParser implements IResTableParser { private final RootNode root; private final ResourceStorage resStorage = new ResourceStorage(); - public ResProtoParser(RootNode root) { + public ResTableProtoParser(RootNode root) { this.root = root; } - public ResContainer decodeFiles(InputStream inputStream) throws IOException { - decode(inputStream); - ValuesParser vp = new ValuesParser(new BinaryXMLStrings(), resStorage.getResourcesNames()); - ResXmlGen resGen = new ResXmlGen(resStorage, vp); - ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage); - List xmlFiles = resGen.makeResourcesXml(root.getArgs()); - return ResContainer.resourceTable("res", xmlFiles, content); - } - @Override public void decode(InputStream inputStream) throws IOException { ResourceTable table = ResourceTable.parseFrom(FileUtils.streamToByteArray(inputStream)); @@ -44,6 +41,15 @@ public void decode(InputStream inputStream) throws IOException { resStorage.finish(); } + @Override + public synchronized ResContainer decodeFiles() { + ValuesParser vp = new ValuesParser(new BinaryXMLStrings(), resStorage.getResourcesNames()); + ResXmlGen resGen = new ResXmlGen(resStorage, vp); + ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage); + List xmlFiles = resGen.makeResourcesXml(root.getArgs()); + return ResContainer.resourceTable("res", xmlFiles, content); + } + private void parse(Package p) { String name = p.getPackageName(); resStorage.setAppPackage(name); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/ResXmlProtoParser.java similarity index 71% rename from jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java rename to jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/ResXmlProtoParser.java index 00dfe291c8d..7d0a538971f 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java +++ b/jadx-plugins/jadx-aab-input/src/main/java/jadx/plugins/input/aab/parsers/ResXmlProtoParser.java @@ -1,4 +1,4 @@ -package jadx.core.xmlgen; +package jadx.plugins.input.aab.parsers; import java.io.IOException; import java.io.InputStream; @@ -18,8 +18,11 @@ import jadx.core.dex.nodes.RootNode; import jadx.core.utils.StringUtils; import jadx.core.utils.android.AndroidResourcesMap; +import jadx.core.xmlgen.XMLChar; +import jadx.core.xmlgen.XmlDeobf; +import jadx.core.xmlgen.XmlGenUtils; -public class ProtoXMLParser extends CommonProtoParser { +public class ResXmlProtoParser extends CommonProtoParser { private Map nsMap; private final Map tagAttrDeobfNames = new HashMap<>(); @@ -28,9 +31,11 @@ public class ProtoXMLParser extends CommonProtoParser { private final RootNode rootNode; private String currentTag; private String appPackageName; + private final boolean isPrettyPrint; - public ProtoXMLParser(RootNode rootNode) { + public ResXmlProtoParser(RootNode rootNode) { this.rootNode = rootNode; + this.isPrettyPrint = !rootNode.getArgs().isSkipXmlPrettyPrint(); } public synchronized ICodeInfo parse(InputStream inputStream) throws IOException { @@ -57,14 +62,9 @@ private void decode(XmlElement e) throws IOException { tag = getValidTagAttributeName(tag); currentTag = tag; writer.startLine('<').add(tag); - for (int i = 0; i < e.getNamespaceDeclarationCount(); i++) { - decode(e.getNamespaceDeclaration(i)); - } - Set attrCache = new HashSet<>(); - for (int i = 0; i < e.getAttributeCount(); i++) { - decode(e.getAttribute(i), attrCache); - } + decodeNamespaces(e); + decodeAttributes(e); if (e.getChildCount() > 0) { writer.add('>'); @@ -77,18 +77,67 @@ private void decode(XmlElement e) throws IOException { writer.decIndent(); writer.startLine("'); } else { - writer.add("/>"); + writer.add(" />"); + } + } + + private void decodeNamespaces(XmlElement e) { + int nsCount = e.getNamespaceDeclarationCount(); + boolean newLine = nsCount != 1 && isPrettyPrint; + if (nsCount > 0) { + writer.add(' '); + } + for (int i = 0; i < nsCount; i++) { + decodeNamespace(e.getNamespaceDeclaration(i), newLine, i == nsCount - 1); + } + } + + private void decodeNamespace(XmlNamespace n, boolean newLine, boolean isLastElement) { + String prefix = n.getPrefix(); + String uri = n.getUri(); + nsMap.put(uri, prefix); + writer.add("xmlns:").add(prefix).add("=\"").add(uri).add('"'); + if (isLastElement) { + return; + } + if (newLine) { + writer.startLine().addIndent(); + } else { + writer.add(' '); } } - private void decode(XmlAttribute a, Set attrCache) { + private void decodeAttributes(XmlElement e) { + int attrsCount = e.getAttributeCount(); + boolean newLine = attrsCount != 1 && isPrettyPrint; + if (attrsCount > 0) { + writer.add(' '); + if (isPrettyPrint) { + writer.startLine().addIndent(); + } + } + Set attrCache = new HashSet<>(); + for (int i = 0; i < attrsCount; i++) { + decodeAttribute(e.getAttribute(i), attrCache, newLine, i == attrsCount - 1); + } + } + + private void decodeAttribute(XmlAttribute a, Set attrCache, boolean newLine, boolean isLastElement) { String name = getAttributeFullName(a); if (XmlDeobf.isDuplicatedAttr(name, attrCache)) { return; } String value = deobfClassName(getAttributeValue(a)); - writer.add(' ').add(name).add("=\"").add(StringUtils.escapeXML(value)).add('\"'); + writer.add(name).add("=\"").add(StringUtils.escapeXML(value)).add('\"'); memorizePackageName(name, value); + if (isLastElement) { + return; + } + if (newLine) { + writer.startLine().addIndent(); + } else { + writer.add(' '); + } } private String getAttributeFullName(XmlAttribute a) { @@ -104,7 +153,7 @@ private String getAttributeFullName(XmlAttribute a) { int resId = a.getResourceId(); String str = AndroidResourcesMap.getResName(resId); if (str != null) { - namespace = nsMap.get(ParserConstants.ANDROID_NS_URL); + namespace = nsMap.get(ANDROID_NS_URL); // cut type before / int typeEnd = str.indexOf('/'); if (typeEnd != -1) { @@ -127,13 +176,6 @@ private String getAttributeValue(XmlAttribute a) { return parse(a.getCompiledItem()); } - private void decode(XmlNamespace n) { - String prefix = n.getPrefix(); - String uri = n.getUri(); - nsMap.put(uri, prefix); - writer.add(" xmlns:").add(prefix).add("=\"").add(uri).add('"'); - } - private void memorizePackageName(String attrName, String attrValue) { if ("manifest".equals(currentTag) && "package".equals(attrName)) { appPackageName = attrValue; diff --git a/jadx-plugins/jadx-aab-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/jadx-plugins/jadx-aab-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin new file mode 100644 index 00000000000..2d26bb1c9e4 --- /dev/null +++ b/jadx-plugins/jadx-aab-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin @@ -0,0 +1 @@ +jadx.plugins.input.aab.AabInputPlugin diff --git a/settings.gradle.kts b/settings.gradle.kts index 09c75126c1f..20b2ebe2d3d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,7 @@ include("jadx-plugins:jadx-java-convert") include("jadx-plugins:jadx-rename-mappings") include("jadx-plugins:jadx-kotlin-metadata") include("jadx-plugins:jadx-xapk-input") +include("jadx-plugins:jadx-aab-input") include("jadx-plugins:jadx-script:jadx-script-plugin") include("jadx-plugins:jadx-script:jadx-script-runtime")