mirror of
https://github.com/skylot/jadx.git
synced 2024-10-07 01:53:34 +00:00
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>
This commit is contained in:
parent
37a42d1418
commit
b85900aa3d
@ -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")
|
||||
|
@ -20,7 +20,7 @@ import org.slf4j.LoggerFactory;
|
||||
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 class ConvertArscFile {
|
||||
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())) {
|
||||
|
@ -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"))
|
||||
|
@ -52,7 +52,6 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
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<ResourceFile> 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<ICodeLoader> customCodeLoaders = new ArrayList<>();
|
||||
private final List<CustomResourcesLoader> customResourcesLoaders = new ArrayList<>();
|
||||
@ -124,7 +123,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
root.mergePasses(customPasses);
|
||||
root.loadClasses(loadedInputs);
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.loadResources(resourcesLoader, getResources());
|
||||
root.runPreDecompileStage();
|
||||
root.initPasses();
|
||||
loadFinished();
|
||||
@ -170,7 +169,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
classes = null;
|
||||
resources = null;
|
||||
binaryXmlParser = null;
|
||||
protoXmlParser = null;
|
||||
events.reset();
|
||||
}
|
||||
|
||||
@ -430,7 +428,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
resources = new ResourcesLoader(this).load();
|
||||
resources = resourcesLoader.load(root);
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
@ -476,13 +474,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
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 final class JadxDecompiler implements Closeable {
|
||||
customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass);
|
||||
}
|
||||
|
||||
public ResourcesLoader getResourcesLoader() {
|
||||
return resourcesLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "jadx decompiler " + getVersion();
|
||||
|
@ -17,30 +17,42 @@ import org.slf4j.LoggerFactory;
|
||||
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<IResTableParserProvider> resTableParserProviders = new ArrayList<>();
|
||||
private final List<IResContainerFactory> resContainerFactories = new ArrayList<>();
|
||||
|
||||
private BinaryXMLParser binaryXmlParser;
|
||||
|
||||
ResourcesLoader(JadxDecompiler jadxRef) {
|
||||
this.jadxRef = jadxRef;
|
||||
this.resTableParserProviders.add(new ResTableBinaryParserProvider());
|
||||
}
|
||||
|
||||
List<ResourceFile> load() {
|
||||
List<ResourceFile> load(RootNode root) {
|
||||
init(root);
|
||||
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
|
||||
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
||||
for (File file : inputFiles) {
|
||||
@ -49,10 +61,37 @@ public final class ResourcesLoader {
|
||||
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> {
|
||||
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> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
|
||||
try {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
@ -82,7 +121,8 @@ public final class ResourcesLoader {
|
||||
|
||||
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,36 +132,48 @@ public final class ResourcesLoader {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
String name = rf.getOriginalName();
|
||||
if (name.endsWith(".9.png")) {
|
||||
@ -184,4 +236,11 @@ public final class ResourcesLoader {
|
||||
copyStream(is, baos);
|
||||
return new SimpleCodeInfo(baos.toString("UTF-8"));
|
||||
}
|
||||
|
||||
private synchronized BinaryXMLParser loadBinaryXmlParser() {
|
||||
if (binaryXmlParser == null) {
|
||||
binaryXmlParser = new BinaryXMLParser(jadxRef.getRoot());
|
||||
}
|
||||
return binaryXmlParser;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import jadx.api.plugins.gui.JadxGuiContext;
|
||||
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<String> supplier);
|
||||
|
||||
/**
|
||||
* Customize resource loading
|
||||
*/
|
||||
IResourcesLoader getResourcesLoader();
|
||||
|
||||
/**
|
||||
* Access to jadx-gui specific methods
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package jadx.api.plugins.resources;
|
||||
|
||||
public interface IResourcesLoader {
|
||||
|
||||
void addResContainerFactory(IResContainerFactory resContainerFactory);
|
||||
|
||||
void addResTableParserProvider(IResTableParserProvider resTableParserProvider);
|
||||
}
|
@ -54,9 +54,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.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 class RootNode {
|
||||
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 class RootNode {
|
||||
rawClsMap.put(clsNode.getRawName(), clsNode);
|
||||
}
|
||||
|
||||
public void loadResources(List<ResourceFile> resources) {
|
||||
public void loadResources(ResourcesLoader resLoader, List<ResourceFile> 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 class RootNode {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateObfuscatedFiles(IResParser parser, List<ResourceFile> resources) {
|
||||
private void updateObfuscatedFiles(IResTableParser parser, List<ResourceFile> resources) {
|
||||
if (args.isSkipResources()) {
|
||||
return;
|
||||
}
|
||||
@ -715,10 +712,6 @@ public class RootNode {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public boolean isProto() {
|
||||
return isProto;
|
||||
}
|
||||
|
||||
public GradleInfoStorage getGradleInfoStorage() {
|
||||
return gradleInfoStorage;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import jadx.api.plugins.options.JadxPluginOptions;
|
||||
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 class PluginContext implements JadxPluginContext, JadxPluginRuntimeData,
|
||||
return decompiler.events();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IResourcesLoader getResourcesLoader() {
|
||||
return decompiler.getResourcesLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JadxGuiContext getGuiContext() {
|
||||
return guiContext;
|
||||
|
@ -3,10 +3,12 @@ package jadx.core.xmlgen;
|
||||
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();
|
@ -199,13 +199,18 @@ public class ManifestAttributes {
|
||||
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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -32,8 +32,8 @@ import jadx.core.xmlgen.entry.RawValue;
|
||||
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 class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
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 class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -85,6 +85,7 @@ tasks.jar {
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
isZip64 = true
|
||||
mergeServiceFiles()
|
||||
manifest {
|
||||
from(project.tasks.jar.get().manifest)
|
||||
|
@ -83,7 +83,8 @@ public class FileDialogWrapper {
|
||||
|
||||
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;
|
||||
|
26
jadx-plugins/jadx-aab-input/build.gradle.kts
Normal file
26
jadx-plugins/jadx-aab-input/build.gradle.kts
Normal file
@ -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")
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 java.util.List;
|
||||
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<ProtoValue> namedValues = new ArrayList<>(s.getEntryCount());
|
||||
String parent = s.getParent().getName();
|
@ -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 com.android.aapt.Resources.Value;
|
||||
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<ResContainer> 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 class ResProtoParser extends CommonProtoParser implements IResParser {
|
||||
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<ResContainer> xmlFiles = resGen.makeResourcesXml(root.getArgs());
|
||||
return ResContainer.resourceTable("res", xmlFiles, content);
|
||||
}
|
||||
|
||||
private void parse(Package p) {
|
||||
String name = p.getPackageName();
|
||||
resStorage.setAppPackage(name);
|
@ -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.api.ICodeWriter;
|
||||
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<String, String> nsMap;
|
||||
private final Map<String, String> 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 @@ public class ProtoXMLParser extends CommonProtoParser {
|
||||
tag = getValidTagAttributeName(tag);
|
||||
currentTag = tag;
|
||||
writer.startLine('<').add(tag);
|
||||
for (int i = 0; i < e.getNamespaceDeclarationCount(); i++) {
|
||||
decode(e.getNamespaceDeclaration(i));
|
||||
}
|
||||
|
||||
Set<String> 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 @@ public class ProtoXMLParser extends CommonProtoParser {
|
||||
writer.decIndent();
|
||||
writer.startLine("</").add(tag).add('>');
|
||||
} else {
|
||||
writer.add("/>");
|
||||
writer.add(" />");
|
||||
}
|
||||
}
|
||||
|
||||
private void decode(XmlAttribute a, Set<String> attrCache) {
|
||||
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 decodeAttributes(XmlElement e) {
|
||||
int attrsCount = e.getAttributeCount();
|
||||
boolean newLine = attrsCount != 1 && isPrettyPrint;
|
||||
if (attrsCount > 0) {
|
||||
writer.add(' ');
|
||||
if (isPrettyPrint) {
|
||||
writer.startLine().addIndent();
|
||||
}
|
||||
}
|
||||
Set<String> 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<String> 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 @@ public class ProtoXMLParser extends CommonProtoParser {
|
||||
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 @@ public class ProtoXMLParser extends CommonProtoParser {
|
||||
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;
|
@ -0,0 +1 @@
|
||||
jadx.plugins.input.aab.AabInputPlugin
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user