feat: add gradle export templates, support android app/lib and simple java
This commit is contained in:
@@ -91,102 +91,110 @@ commands (use '<command> --help' for command options):
|
|||||||
plugins - manage jadx plugins
|
plugins - manage jadx plugins
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-d, --output-dir - output directory
|
-d, --output-dir - output directory
|
||||||
-ds, --output-dir-src - output directory for sources
|
-ds, --output-dir-src - output directory for sources
|
||||||
-dr, --output-dir-res - output directory for resources
|
-dr, --output-dir-res - output directory for resources
|
||||||
-r, --no-res - do not decode resources
|
-r, --no-res - do not decode resources
|
||||||
-s, --no-src - do not decompile source code
|
-s, --no-src - do not decompile source code
|
||||||
--single-class - decompile a single class, full name, raw or alias
|
-j, --threads-count - processing threads count, default: 4
|
||||||
--single-class-output - file or dir for write if decompile a single class
|
--single-class - decompile a single class, full name, raw or alias
|
||||||
--output-format - can be 'java' or 'json', default: java
|
--single-class-output - file or dir for write if decompile a single class
|
||||||
-e, --export-gradle - save as android gradle project
|
--output-format - can be 'java' or 'json', default: java
|
||||||
-j, --threads-count - processing threads count, default: 4
|
-e, --export-gradle - save as gradle project (set '--export-gradle-type' to 'auto')
|
||||||
-m, --decompilation-mode - code output mode:
|
--export-gradle-type - Gradle project template for export:
|
||||||
'auto' - trying best options (default)
|
'auto' - detect automatically
|
||||||
'restructure' - restore code structure (normal java code)
|
'android-app' - Android Application (apk)
|
||||||
'simple' - simplified instructions (linear, with goto's)
|
'android-library' - Android Library (aar)
|
||||||
'fallback' - raw instructions without modifications
|
'simple-java' - simple Java
|
||||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
-m, --decompilation-mode - code output mode:
|
||||||
--no-xml-pretty-print - do not prettify XML
|
'auto' - trying best options (default)
|
||||||
--no-imports - disable use of imports, always write entire package name
|
'restructure' - restore code structure (normal java code)
|
||||||
--no-debug-info - disable debug info parsing and processing
|
'simple' - simplified instructions (linear, with goto's)
|
||||||
--add-debug-lines - add comments with debug line numbers if available
|
'fallback' - raw instructions without modifications
|
||||||
--no-inline-anonymous - disable anonymous classes inline
|
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||||
--no-inline-methods - disable methods inline
|
--no-xml-pretty-print - do not prettify XML
|
||||||
--no-move-inner-classes - disable move inner classes into parent
|
--no-imports - disable use of imports, always write entire package name
|
||||||
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
|
--no-debug-info - disable debug info parsing and processing
|
||||||
--no-finally - don't extract finally block
|
--add-debug-lines - add comments with debug line numbers if available
|
||||||
--no-restore-switch-over-string - don't restore switch over string
|
--no-inline-anonymous - disable anonymous classes inline
|
||||||
--no-replace-consts - don't replace constant value with matching constant field
|
--no-inline-methods - disable methods inline
|
||||||
--escape-unicode - escape non latin characters in strings (with \u)
|
--no-move-inner-classes - disable move inner classes into parent
|
||||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
|
||||||
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
|
--no-finally - don't extract finally block
|
||||||
--mappings-mode - set mode for handling the deobfuscation mapping file:
|
--no-restore-switch-over-string - don't restore switch over string
|
||||||
'read' - just read, user can always save manually (default)
|
--no-replace-consts - don't replace constant value with matching constant field
|
||||||
'read-and-autosave-every-change' - read and autosave after every change
|
--escape-unicode - escape non latin characters in strings (with \u)
|
||||||
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
|
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||||
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
|
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
|
||||||
--deobf - activate deobfuscation
|
--mappings-mode - set mode for handling the deobfuscation mapping file:
|
||||||
--deobf-min - min length of name, renamed if shorter, default: 3
|
'read' - just read, user can always save manually (default)
|
||||||
--deobf-max - max length of name, renamed if longer, default: 64
|
'read-and-autosave-every-change' - read and autosave after every change
|
||||||
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
|
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
|
||||||
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
|
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
|
||||||
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
|
--deobf - activate deobfuscation
|
||||||
'read' - read if found, don't save (default)
|
--deobf-min - min length of name, renamed if shorter, default: 3
|
||||||
'read-or-save' - read if found, save otherwise (don't overwrite)
|
--deobf-max - max length of name, renamed if longer, default: 64
|
||||||
'overwrite' - don't read, always save
|
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
|
||||||
'ignore' - don't read and don't save
|
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
|
||||||
--deobf-res-name-source - better name source for resources:
|
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
|
||||||
'auto' - automatically select best name (default)
|
'read' - read if found, don't save (default)
|
||||||
'resources' - use resources names
|
'read-or-save' - read if found, save otherwise (don't overwrite)
|
||||||
'code' - use R class fields names
|
'overwrite' - don't read, always save
|
||||||
--use-source-name-as-class-name-alias - use source name as class name alias:
|
'ignore' - don't read and don't save
|
||||||
'always' - always use source name if it's available
|
--deobf-res-name-source - better name source for resources:
|
||||||
'if-better' - use source name if it seems better than the current one
|
'auto' - automatically select best name (default)
|
||||||
'never' - never use source name, even if it's available
|
'resources' - use resources names
|
||||||
|
'code' - use R class fields names
|
||||||
|
--use-source-name-as-class-name-alias - use source name as class name alias:
|
||||||
|
'always' - always use source name if it's available
|
||||||
|
'if-better' - use source name if it seems better than the current one
|
||||||
|
'never' - never use source name, even if it's available
|
||||||
--source-name-repeat-limit - allow using source name if it appears less than a limit number, default: 10
|
--source-name-repeat-limit - allow using source name if it appears less than a limit number, default: 10
|
||||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||||
--rename-flags - fix options (comma-separated list of):
|
--rename-flags - fix options (comma-separated list of):
|
||||||
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||||
'valid' - rename java identifiers to make them valid,
|
'valid' - rename java identifiers to make them valid,
|
||||||
'printable' - remove non-printable chars from identifiers,
|
'printable' - remove non-printable chars from identifiers,
|
||||||
or single 'none' - to disable all renames
|
or single 'none' - to disable all renames
|
||||||
or single 'all' - to enable all (default)
|
or single 'all' - to enable all (default)
|
||||||
--integer-format - how integers are displayed:
|
--integer-format - how integers are displayed:
|
||||||
'auto' - automatically select (default)
|
'auto' - automatically select (default)
|
||||||
'decimal' - use decimal
|
'decimal' - use decimal
|
||||||
'hexadecimal' - use hexadecimal
|
'hexadecimal' - use hexadecimal
|
||||||
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
||||||
--cfg - save methods control flow graph to dot file
|
--cfg - save methods control flow graph to dot file
|
||||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||||
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
|
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
|
||||||
--use-dx - use dx/d8 to convert java bytecode
|
--use-dx - use dx/d8 to convert java bytecode
|
||||||
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
||||||
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
||||||
-v, --verbose - verbose output (set --log-level to DEBUG)
|
-v, --verbose - verbose output (set --log-level to DEBUG)
|
||||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||||
--version - print jadx version
|
--disable-plugins - comma separated list of plugin ids to disable, default:
|
||||||
-h, --help - print this help
|
--version - print jadx version
|
||||||
|
-h, --help - print this help
|
||||||
|
|
||||||
Plugin options (-P<name>=<value>):
|
Plugin options (-P<name>=<value>):
|
||||||
dex-input: Load .dex and .apk files
|
dex-input: Load .dex and .apk files
|
||||||
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
|
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
|
||||||
java-convert: Convert .class, .jar and .aar files to dex
|
java-convert: Convert .class, .jar and .aar files to dex
|
||||||
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
||||||
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
||||||
kotlin-metadata: Use kotlin.Metadata annotation for code generation
|
kotlin-metadata: Use kotlin.Metadata annotation for code generation
|
||||||
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
|
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
|
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
|
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
|
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
|
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
|
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
||||||
|
kotlin-smap: Use kotlin.SourceDebugExtension annotation for rename class alias
|
||||||
|
- kotlin-smap.class-alias-source-dbg - rename class alias from SourceDebugExtension, values: [yes, no], default: no
|
||||||
rename-mappings: various mappings support
|
rename-mappings: various mappings support
|
||||||
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
|
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE, INTELLIJ_MIGRATION_MAP_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
|
||||||
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
|
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
|
||||||
smali-input: Load .smali files
|
smali-input: Load .smali files
|
||||||
- smali-input.api-level - Android API level, default: 27
|
- smali-input.api-level - Android API level, default: 27
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
|
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package jadx.cli;
|
|||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -13,6 +14,8 @@ import java.util.function.Supplier;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import com.beust.jcommander.DynamicParameter;
|
import com.beust.jcommander.DynamicParameter;
|
||||||
import com.beust.jcommander.IStringConverter;
|
import com.beust.jcommander.IStringConverter;
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
@@ -29,13 +32,14 @@ import jadx.api.args.ResourceNameSource;
|
|||||||
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||||
import jadx.api.args.UserRenamesMappingsMode;
|
import jadx.api.args.UserRenamesMappingsMode;
|
||||||
import jadx.core.deobf.conditions.DeobfWhitelist;
|
import jadx.core.deobf.conditions.DeobfWhitelist;
|
||||||
|
import jadx.core.export.ExportGradleType;
|
||||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxCLIArgs {
|
public class JadxCLIArgs {
|
||||||
|
|
||||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .apkm, .jadx.kts)")
|
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .apkm, .jadx.kts)")
|
||||||
protected List<String> files;
|
protected List<String> files = Collections.emptyList();
|
||||||
|
|
||||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||||
protected String outDir;
|
protected String outDir;
|
||||||
@@ -52,6 +56,9 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
||||||
protected boolean skipSources = false;
|
protected boolean skipSources = false;
|
||||||
|
|
||||||
|
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||||
|
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||||
|
|
||||||
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
||||||
protected String singleClass = null;
|
protected String singleClass = null;
|
||||||
|
|
||||||
@@ -61,11 +68,19 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
||||||
protected String outputFormat = "java";
|
protected String outputFormat = "java";
|
||||||
|
|
||||||
@Parameter(names = { "-e", "--export-gradle" }, description = "save as android gradle project")
|
@Parameter(names = { "-e", "--export-gradle" }, description = "save as gradle project (set '--export-gradle-type' to 'auto')")
|
||||||
protected boolean exportAsGradleProject = false;
|
protected boolean exportAsGradleProject = false;
|
||||||
|
|
||||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
@Parameter(
|
||||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
names = { "--export-gradle-type" },
|
||||||
|
description = "Gradle project template for export:"
|
||||||
|
+ "\n 'auto' - detect automatically"
|
||||||
|
+ "\n 'android-app' - Android Application (apk)"
|
||||||
|
+ "\n 'android-library' - Android Library (aar)"
|
||||||
|
+ "\n 'simple-java' - simple Java",
|
||||||
|
converter = ExportGradleTypeConverter.class
|
||||||
|
)
|
||||||
|
protected @Nullable ExportGradleType exportGradleType = null;
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "-m", "--decompilation-mode" },
|
names = { "-m", "--decompilation-mode" },
|
||||||
@@ -344,7 +359,10 @@ public class JadxCLIArgs {
|
|||||||
args.setResourceNameSource(resourceNameSource);
|
args.setResourceNameSource(resourceNameSource);
|
||||||
args.setEscapeUnicode(escapeUnicode);
|
args.setEscapeUnicode(escapeUnicode);
|
||||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||||
args.setExportAsGradleProject(exportAsGradleProject);
|
args.setExportGradleType(exportGradleType);
|
||||||
|
if (exportAsGradleProject && exportGradleType == null) {
|
||||||
|
args.setExportGradleType(ExportGradleType.AUTO);
|
||||||
|
}
|
||||||
args.setSkipXmlPrettyPrint(skipXmlPrettyPrint);
|
args.setSkipXmlPrettyPrint(skipXmlPrettyPrint);
|
||||||
args.setUseImports(useImports);
|
args.setUseImports(useImports);
|
||||||
args.setDebugInfo(debugInfo);
|
args.setDebugInfo(debugInfo);
|
||||||
@@ -649,6 +667,12 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ExportGradleTypeConverter extends BaseEnumConverter<ExportGradleType> {
|
||||||
|
public ExportGradleTypeConverter() {
|
||||||
|
super(ExportGradleType::valueOf, ExportGradleType::values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class LogLevelConverter extends BaseEnumConverter<LogHelper.LogLevelEnum> {
|
public static class LogLevelConverter extends BaseEnumConverter<LogHelper.LogLevelEnum> {
|
||||||
public LogLevelConverter() {
|
public LogLevelConverter() {
|
||||||
super(LogHelper.LogLevelEnum::valueOf, LogHelper.LogLevelEnum::values);
|
super(LogHelper.LogLevelEnum::valueOf, LogHelper.LogLevelEnum::values);
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.plugins.loader.JadxBasePluginLoader;
|
||||||
|
import jadx.core.plugins.files.SingleDirFilesGetter;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
|
||||||
|
public class BaseCliIntegrationTest {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(BaseCliIntegrationTest.class);
|
||||||
|
|
||||||
|
static final PathMatcher LOG_ALL_FILES = path -> {
|
||||||
|
LOG.debug("File in result dir: {}", path);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path testDir;
|
||||||
|
|
||||||
|
Path outputDir;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
outputDir = testDir.resolve("output");
|
||||||
|
}
|
||||||
|
|
||||||
|
int execJadxCli(String sampleName, String... options) {
|
||||||
|
return execJadxCli(buildArgs(List.of(options), sampleName));
|
||||||
|
}
|
||||||
|
|
||||||
|
int execJadxCli(String[] args) {
|
||||||
|
return JadxCLI.execute(args, jadxArgs -> {
|
||||||
|
// don't use global config and plugins
|
||||||
|
jadxArgs.setFilesGetter(new SingleDirFilesGetter(testDir));
|
||||||
|
jadxArgs.setPluginLoader(new JadxBasePluginLoader());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] buildArgs(List<String> options, String... inputSamples) {
|
||||||
|
List<String> args = new ArrayList<>(options);
|
||||||
|
args.add("-v");
|
||||||
|
args.add("-d");
|
||||||
|
args.add(outputDir.toAbsolutePath().toString());
|
||||||
|
|
||||||
|
for (String inputSample : inputSamples) {
|
||||||
|
try {
|
||||||
|
URL resource = getClass().getClassLoader().getResource(inputSample);
|
||||||
|
assertThat(resource).isNotNull();
|
||||||
|
String sampleFile = resource.toURI().getRawPath();
|
||||||
|
args.add(sampleFile);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
fail("Failed to load sample: " + inputSample, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void decompile(String... inputSamples) throws IOException {
|
||||||
|
int result = execJadxCli(buildArgs(List.of(), inputSamples));
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
List<Path> resultJavaFiles = collectJavaFilesInDir(outputDir);
|
||||||
|
assertThat(resultJavaFiles).isNotEmpty();
|
||||||
|
|
||||||
|
// do not copy input files as resources
|
||||||
|
for (Path path : collectFilesInDir(outputDir, LOG_ALL_FILES)) {
|
||||||
|
for (String inputSample : inputSamples) {
|
||||||
|
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printFiles(List<Path> files) {
|
||||||
|
LOG.info("Output files (count: {}):", files.size());
|
||||||
|
for (Path file : files) {
|
||||||
|
LOG.info(" {}", file);
|
||||||
|
}
|
||||||
|
LOG.info("");
|
||||||
|
}
|
||||||
|
|
||||||
|
Path printFileContent(Path file) {
|
||||||
|
try {
|
||||||
|
String content = Files.readString(outputDir.resolve(file));
|
||||||
|
String spacer = Utils.strRepeat("=", 70);
|
||||||
|
LOG.info("File content: {}\n{}\n{}\n{}", file, spacer, content, spacer);
|
||||||
|
return file;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to load file: " + file, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
|
||||||
|
PathMatcher javaMatcher = dir.getFileSystem().getPathMatcher("glob:**.java");
|
||||||
|
return collectFilesInDir(dir, javaMatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Path> collectAllFilesInDir(Path dir) throws IOException {
|
||||||
|
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||||
|
List<Path> files = pathStream
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.map(dir::relativize)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
printFiles(files);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
|
||||||
|
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||||
|
List<Path> files = pathStream
|
||||||
|
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
|
||||||
|
.filter(matcher::matches)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
printFiles(files);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.assertj.core.api.Condition;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class TestExport extends BaseCliIntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicExport() throws Exception {
|
||||||
|
int result = execJadxCli("samples/small.apk");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.map(Path::toString)
|
||||||
|
.haveExactly(2, new Condition<>(f -> f.startsWith("sources/") && f.endsWith(".java"), "sources"))
|
||||||
|
.haveExactly(10, new Condition<>(f -> f.startsWith("resources/"), "resources"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("resources/AndroidManifest.xml"), "manifest"))
|
||||||
|
.hasSize(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradleExportApk() throws Exception {
|
||||||
|
int result = execJadxCli("samples/small.apk", "--export-gradle");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.describedAs("check output files")
|
||||||
|
.map(Path::toString)
|
||||||
|
.haveExactly(2, new Condition<>(f -> f.endsWith(".java"), "java classes"))
|
||||||
|
.haveExactly(0, new Condition<>(f -> f.endsWith("classes.dex"), "dex files"))
|
||||||
|
.hasSize(15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradleExportAAR() throws Exception {
|
||||||
|
int result = execJadxCli("samples/test-lib.aar", "--export-gradle");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.describedAs("check output files")
|
||||||
|
.map(this::printFileContent)
|
||||||
|
.map(Path::toString)
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.startsWith("lib/src/main/java/") && f.endsWith(".java"), "java"))
|
||||||
|
.haveExactly(0, new Condition<>(f -> f.endsWith(".jar"), "jar files"))
|
||||||
|
.hasSize(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradleExportSimpleJava() throws Exception {
|
||||||
|
int result = execJadxCli("samples/HelloWorld.class", "--export-gradle");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.describedAs("check output files")
|
||||||
|
.map(this::printFileContent)
|
||||||
|
.map(Path::toString)
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.endsWith(".java") && f.startsWith("app/src/main/java/"), "java"))
|
||||||
|
.haveExactly(0, new Condition<>(f -> f.endsWith(".class"), "class files"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("settings.gradle.kts"), "settings"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("app/build.gradle.kts"), "build"))
|
||||||
|
.hasSize(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradleExportInvalidType() throws Exception {
|
||||||
|
int result = execJadxCli("samples/HelloWorld.class", "--export-gradle-type", "android-app");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
// expect output in 'android-app' template, but most fields will be set to UNKNOWN.
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.describedAs("check output files")
|
||||||
|
.map(this::printFileContent)
|
||||||
|
.map(Path::toString)
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.endsWith(".java") && f.startsWith("app/src/main/java/"), "java"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("settings.gradle"), "settings"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("build.gradle"), "build"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("app/build.gradle"), "app build"))
|
||||||
|
.hasSize(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,46 +1,14 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.PathMatcher;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.assertj.core.api.Condition;
|
import org.assertj.core.api.Condition;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.plugins.loader.JadxBasePluginLoader;
|
|
||||||
import jadx.core.plugins.files.SingleDirFilesGetter;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public class TestInput {
|
public class TestInput extends BaseCliIntegrationTest {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
|
|
||||||
|
|
||||||
private static final PathMatcher LOG_ALL_FILES = path -> {
|
|
||||||
LOG.debug("File in result dir: {}", path);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path testDir;
|
|
||||||
|
|
||||||
Path outputDir;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() {
|
|
||||||
outputDir = testDir.resolve("output");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHelp() {
|
public void testHelp() {
|
||||||
@@ -52,16 +20,13 @@ public class TestInput {
|
|||||||
public void testApkInput() throws Exception {
|
public void testApkInput() throws Exception {
|
||||||
int result = execJadxCli(buildArgs(List.of(), "samples/small.apk"));
|
int result = execJadxCli(buildArgs(List.of(), "samples/small.apk"));
|
||||||
assertThat(result).isEqualTo(0);
|
assertThat(result).isEqualTo(0);
|
||||||
List<Path> resultFiles = collectAllFilesInDir(outputDir);
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
printFiles(resultFiles);
|
|
||||||
assertThat(resultFiles)
|
|
||||||
.describedAs("check output files")
|
.describedAs("check output files")
|
||||||
.map(p -> p.getFileName().toString())
|
.map(p -> p.getFileName().toString())
|
||||||
.haveExactly(2, new Condition<>(f -> f.endsWith(".java"), "java classes"))
|
.haveExactly(2, new Condition<>(f -> f.endsWith(".java"), "java classes"))
|
||||||
.haveExactly(9, new Condition<>(f -> f.endsWith(".xml"), "xml resources"))
|
.haveExactly(9, new Condition<>(f -> f.endsWith(".xml"), "xml resources"))
|
||||||
.haveExactly(1, new Condition<>(f -> f.equals("classes.dex"), "dex"))
|
|
||||||
.haveExactly(1, new Condition<>(f -> f.equals("AndroidManifest.xml"), "manifest"))
|
.haveExactly(1, new Condition<>(f -> f.equals("AndroidManifest.xml"), "manifest"))
|
||||||
.hasSize(13);
|
.hasSize(12);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -108,70 +73,4 @@ public class TestInput {
|
|||||||
path -> path.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"));
|
path -> path.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"));
|
||||||
assertThat(files).isNotEmpty();
|
assertThat(files).isNotEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decompile(String... inputSamples) throws URISyntaxException, IOException {
|
|
||||||
int result = execJadxCli(buildArgs(List.of(), inputSamples));
|
|
||||||
assertThat(result).isEqualTo(0);
|
|
||||||
List<Path> resultJavaFiles = collectJavaFilesInDir(outputDir);
|
|
||||||
assertThat(resultJavaFiles).isNotEmpty();
|
|
||||||
|
|
||||||
// do not copy input files as resources
|
|
||||||
for (Path path : collectFilesInDir(outputDir, LOG_ALL_FILES)) {
|
|
||||||
for (String inputSample : inputSamples) {
|
|
||||||
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int execJadxCli(String[] args) {
|
|
||||||
return JadxCLI.execute(args, jadxArgs -> {
|
|
||||||
// don't use global config and plugins
|
|
||||||
jadxArgs.setFilesGetter(new SingleDirFilesGetter(testDir));
|
|
||||||
jadxArgs.setPluginLoader(new JadxBasePluginLoader());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] buildArgs(List<String> options, String... inputSamples) throws URISyntaxException {
|
|
||||||
List<String> args = new ArrayList<>(options);
|
|
||||||
args.add("-v");
|
|
||||||
args.add("-d");
|
|
||||||
args.add(outputDir.toAbsolutePath().toString());
|
|
||||||
|
|
||||||
for (String inputSample : inputSamples) {
|
|
||||||
URL resource = getClass().getClassLoader().getResource(inputSample);
|
|
||||||
assertThat(resource).isNotNull();
|
|
||||||
String sampleFile = resource.toURI().getRawPath();
|
|
||||||
args.add(sampleFile);
|
|
||||||
}
|
|
||||||
return args.toArray(new String[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printFiles(List<Path> files) {
|
|
||||||
LOG.info("Output files (count: {}):", files.size());
|
|
||||||
for (Path file : files) {
|
|
||||||
LOG.info(" {}", outputDir.relativize(file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
|
|
||||||
PathMatcher javaMatcher = dir.getFileSystem().getPathMatcher("glob:**.java");
|
|
||||||
return collectFilesInDir(dir, javaMatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Path> collectAllFilesInDir(Path dir) throws IOException {
|
|
||||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
|
||||||
return pathStream
|
|
||||||
.filter(Files::isRegularFile)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
|
|
||||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
|
||||||
return pathStream
|
|
||||||
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
|
|
||||||
.filter(matcher::matches)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -39,6 +39,7 @@ import jadx.api.usage.impl.InMemoryUsageInfoCache;
|
|||||||
import jadx.core.deobf.DeobfAliasProvider;
|
import jadx.core.deobf.DeobfAliasProvider;
|
||||||
import jadx.core.deobf.conditions.DeobfWhitelist;
|
import jadx.core.deobf.conditions.DeobfWhitelist;
|
||||||
import jadx.core.deobf.conditions.JadxRenameConditions;
|
import jadx.core.deobf.conditions.JadxRenameConditions;
|
||||||
|
import jadx.core.export.ExportGradleType;
|
||||||
import jadx.core.plugins.PluginContext;
|
import jadx.core.plugins.PluginContext;
|
||||||
import jadx.core.plugins.files.IJadxFilesGetter;
|
import jadx.core.plugins.files.IJadxFilesGetter;
|
||||||
import jadx.core.plugins.files.TempFilesGetter;
|
import jadx.core.plugins.files.TempFilesGetter;
|
||||||
@@ -133,7 +134,7 @@ public class JadxArgs implements Closeable {
|
|||||||
private boolean escapeUnicode = false;
|
private boolean escapeUnicode = false;
|
||||||
private boolean replaceConsts = true;
|
private boolean replaceConsts = true;
|
||||||
private boolean respectBytecodeAccModifiers = false;
|
private boolean respectBytecodeAccModifiers = false;
|
||||||
private boolean exportAsGradleProject = false;
|
private @Nullable ExportGradleType exportGradleType = null;
|
||||||
|
|
||||||
private boolean restoreSwitchOverString = true;
|
private boolean restoreSwitchOverString = true;
|
||||||
|
|
||||||
@@ -567,11 +568,25 @@ public class JadxArgs implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExportAsGradleProject() {
|
public boolean isExportAsGradleProject() {
|
||||||
return exportAsGradleProject;
|
return exportGradleType != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||||
this.exportAsGradleProject = exportAsGradleProject;
|
if (exportAsGradleProject) {
|
||||||
|
if (exportGradleType == null) {
|
||||||
|
exportGradleType = ExportGradleType.AUTO;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exportGradleType = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ExportGradleType getExportGradleType() {
|
||||||
|
return exportGradleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExportGradleType(@Nullable ExportGradleType exportGradleType) {
|
||||||
|
this.exportGradleType = exportGradleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRestoreSwitchOverString() {
|
public boolean isRestoreSwitchOverString() {
|
||||||
@@ -861,7 +876,7 @@ public class JadxArgs implements Closeable {
|
|||||||
+ ", replaceConsts=" + replaceConsts
|
+ ", replaceConsts=" + replaceConsts
|
||||||
+ ", restoreSwitchOverString=" + restoreSwitchOverString
|
+ ", restoreSwitchOverString=" + restoreSwitchOverString
|
||||||
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
|
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
|
||||||
+ ", exportAsGradleProject=" + exportAsGradleProject
|
+ ", exportGradleType=" + exportGradleType
|
||||||
+ ", skipXmlPrettyPrint=" + skipXmlPrettyPrint
|
+ ", skipXmlPrettyPrint=" + skipXmlPrettyPrint
|
||||||
+ ", fsCaseSensitive=" + fsCaseSensitive
|
+ ", fsCaseSensitive=" + fsCaseSensitive
|
||||||
+ ", renameFlags=" + renameFlags
|
+ ", renameFlags=" + renameFlags
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -42,7 +43,8 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.dex.nodes.PackageNode;
|
import jadx.core.dex.nodes.PackageNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.SaveCode;
|
import jadx.core.dex.visitors.SaveCode;
|
||||||
import jadx.core.export.ExportGradleTask;
|
import jadx.core.export.ExportGradle;
|
||||||
|
import jadx.core.export.OutDirs;
|
||||||
import jadx.core.plugins.JadxPluginManager;
|
import jadx.core.plugins.JadxPluginManager;
|
||||||
import jadx.core.plugins.PluginContext;
|
import jadx.core.plugins.PluginContext;
|
||||||
import jadx.core.plugins.events.JadxEventsImpl;
|
import jadx.core.plugins.events.JadxEventsImpl;
|
||||||
@@ -297,31 +299,28 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
if (root == null) {
|
if (root == null) {
|
||||||
throw new JadxRuntimeException("No loaded files");
|
throw new JadxRuntimeException("No loaded files");
|
||||||
}
|
}
|
||||||
File sourcesOutDir;
|
OutDirs outDirs;
|
||||||
File resOutDir;
|
ExportGradle gradleExport;
|
||||||
ExportGradleTask gradleExportTask;
|
if (args.getExportGradleType() != null) {
|
||||||
if (args.isExportAsGradleProject()) {
|
gradleExport = new ExportGradle(root, args.getOutDir(), getResources());
|
||||||
gradleExportTask = new ExportGradleTask(resources, root, args.getOutDir());
|
outDirs = gradleExport.init();
|
||||||
gradleExportTask.init();
|
|
||||||
sourcesOutDir = gradleExportTask.getSrcOutDir();
|
|
||||||
resOutDir = gradleExportTask.getResOutDir();
|
|
||||||
} else {
|
} else {
|
||||||
sourcesOutDir = args.getOutDirSrc();
|
gradleExport = null;
|
||||||
resOutDir = args.getOutDirRes();
|
outDirs = new OutDirs(args.getOutDirSrc(), args.getOutDirRes());
|
||||||
gradleExportTask = null;
|
outDirs.makeDirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskExecutor executor = new TaskExecutor();
|
TaskExecutor executor = new TaskExecutor();
|
||||||
executor.setThreadsCount(args.getThreadsCount());
|
executor.setThreadsCount(args.getThreadsCount());
|
||||||
if (saveResources) {
|
if (saveResources) {
|
||||||
// save resources first because decompilation can stop or fail
|
// save resources first because decompilation can stop or fail
|
||||||
appendResourcesSaveTasks(executor, resOutDir);
|
appendResourcesSaveTasks(executor, outDirs.getResOutDir());
|
||||||
}
|
}
|
||||||
if (saveSources) {
|
if (saveSources) {
|
||||||
appendSourcesSave(executor, sourcesOutDir);
|
appendSourcesSave(executor, outDirs.getSrcOutDir());
|
||||||
}
|
}
|
||||||
if (gradleExportTask != null) {
|
if (gradleExport != null) {
|
||||||
executor.addSequentialTask(gradleExportTask);
|
executor.addSequentialTask(gradleExport::generateGradleFiles);
|
||||||
}
|
}
|
||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
@@ -340,6 +339,8 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
Set<String> inputFileNames = args.getInputFiles().stream()
|
Set<String> inputFileNames = args.getInputFiles().stream()
|
||||||
.map(File::getAbsolutePath)
|
.map(File::getAbsolutePath)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
Set<String> codeSources = collectCodeSources();
|
||||||
|
|
||||||
List<Runnable> tasks = new ArrayList<>();
|
List<Runnable> tasks = new ArrayList<>();
|
||||||
for (ResourceFile resourceFile : getResources()) {
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
ResourceType resType = resourceFile.getType();
|
ResourceType resType = resourceFile.getType();
|
||||||
@@ -347,9 +348,14 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
// already processed
|
// already processed
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (resType != ResourceType.ARSC
|
String resOriginalName = resourceFile.getOriginalName();
|
||||||
&& inputFileNames.contains(resourceFile.getOriginalName())) {
|
if (resType != ResourceType.ARSC && inputFileNames.contains(resOriginalName)) {
|
||||||
// ignore resource made from input file
|
// ignore resource made from an input file
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (codeSources.contains(resOriginalName)) {
|
||||||
|
// don't output code source resources (.dex, .class, etc)
|
||||||
|
// do not trust file extensions, use only sources set as class inputs
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tasks.add(new ResourcesSaver(this, outDir, resourceFile));
|
tasks.add(new ResourcesSaver(this, outDir, resourceFile));
|
||||||
@@ -357,6 +363,29 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
executor.addParallelTasks(tasks);
|
executor.addParallelTasks(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<String> collectCodeSources() {
|
||||||
|
Set<String> set = new HashSet<>();
|
||||||
|
for (ClassNode cls : root.getClasses(true)) {
|
||||||
|
if (cls.getClsData() == null) {
|
||||||
|
// exclude synthetic classes
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String inputFileName = cls.getInputFileName();
|
||||||
|
if (inputFileName.endsWith(".class")) {
|
||||||
|
// cut .class name to get source .jar file
|
||||||
|
// current template: "<optional input files>:<.jar>:<full class name>"
|
||||||
|
// TODO: add property to set file name or reference to resource name
|
||||||
|
int endIdx = inputFileName.lastIndexOf(':');
|
||||||
|
if (endIdx != -1) {
|
||||||
|
int startIdx = inputFileName.lastIndexOf(':', endIdx - 1) + 1;
|
||||||
|
inputFileName = inputFileName.substring(startIdx, endIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set.add(inputFileName);
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
private void appendSourcesSave(ITaskExecutor executor, File outDir) {
|
private void appendSourcesSave(ITaskExecutor executor, File outDir) {
|
||||||
List<JavaClass> classes = getClasses();
|
List<JavaClass> classes = getClasses();
|
||||||
List<JavaClass> processQueue = filterClasses(classes);
|
List<JavaClass> processQueue = filterClasses(classes);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import jadx.core.xmlgen.ResContainer;
|
||||||
|
|
||||||
|
public class ResourceFileContainer extends ResourceFile {
|
||||||
|
private final ResContainer container;
|
||||||
|
|
||||||
|
public ResourceFileContainer(String name, ResourceType type, ResContainer container) {
|
||||||
|
super(null, name, type);
|
||||||
|
this.container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResContainer loadContent() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package jadx.core.export;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.ResourceFile;
|
||||||
|
import jadx.api.ResourceType;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.export.gen.AndroidGradleGenerator;
|
||||||
|
import jadx.core.export.gen.IExportGradleGenerator;
|
||||||
|
import jadx.core.export.gen.SimpleJavaGradleGenerator;
|
||||||
|
import jadx.core.utils.android.AndroidManifestParser;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
public class ExportGradle {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ExportGradle.class);
|
||||||
|
private final RootNode root;
|
||||||
|
private final File projectDir;
|
||||||
|
private final List<ResourceFile> resources;
|
||||||
|
private IExportGradleGenerator generator;
|
||||||
|
|
||||||
|
public ExportGradle(RootNode root, File projectDir, List<ResourceFile> resources) {
|
||||||
|
this.root = root;
|
||||||
|
this.projectDir = projectDir;
|
||||||
|
this.resources = resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutDirs init() {
|
||||||
|
ExportGradleType exportType = getExportGradleType();
|
||||||
|
LOG.info("Export Gradle project using '{}' template", exportType);
|
||||||
|
switch (exportType) {
|
||||||
|
case ANDROID_APP:
|
||||||
|
case ANDROID_LIBRARY:
|
||||||
|
generator = new AndroidGradleGenerator(root, projectDir, resources, exportType);
|
||||||
|
break;
|
||||||
|
case SIMPLE_JAVA:
|
||||||
|
generator = new SimpleJavaGradleGenerator(root, projectDir, resources);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unexpected export type: " + exportType);
|
||||||
|
}
|
||||||
|
generator.init();
|
||||||
|
OutDirs outDirs = generator.getOutDirs();
|
||||||
|
outDirs.makeDirs();
|
||||||
|
return outDirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExportGradleType getExportGradleType() {
|
||||||
|
ExportGradleType argsExportType = root.getArgs().getExportGradleType();
|
||||||
|
ExportGradleType detectedType = detectExportType(root, resources);
|
||||||
|
if (argsExportType == null
|
||||||
|
|| argsExportType == ExportGradleType.AUTO
|
||||||
|
|| argsExportType == detectedType) {
|
||||||
|
return detectedType;
|
||||||
|
}
|
||||||
|
return argsExportType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExportGradleType detectExportType(RootNode root, List<ResourceFile> resources) {
|
||||||
|
ResourceFile androidManifest = AndroidManifestParser.getAndroidManifest(resources);
|
||||||
|
if (androidManifest != null) {
|
||||||
|
if (resources.stream().anyMatch(r -> r.getOriginalName().equals("classes.jar"))) {
|
||||||
|
return ExportGradleType.ANDROID_LIBRARY;
|
||||||
|
}
|
||||||
|
if (resources.stream().anyMatch(r -> r.getType() == ResourceType.ARSC)) {
|
||||||
|
return ExportGradleType.ANDROID_APP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ExportGradleType.SIMPLE_JAVA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void generateGradleFiles() {
|
||||||
|
generator.generateFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package jadx.core.export;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import jadx.api.ResourceFile;
|
|
||||||
import jadx.core.dex.nodes.RootNode;
|
|
||||||
import jadx.core.utils.android.AndroidManifestParser;
|
|
||||||
import jadx.core.utils.android.AppAttribute;
|
|
||||||
import jadx.core.utils.android.ApplicationParams;
|
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
|
||||||
import jadx.core.xmlgen.ResContainer;
|
|
||||||
|
|
||||||
public class ExportGradleProject {
|
|
||||||
private static final Pattern ILLEGAL_GRADLE_CHARS = Pattern.compile("[/\\\\:>\"?*|]");
|
|
||||||
|
|
||||||
private final RootNode root;
|
|
||||||
private final File projectDir;
|
|
||||||
private final File appDir;
|
|
||||||
private final ApplicationParams applicationParams;
|
|
||||||
|
|
||||||
public ExportGradleProject(RootNode root, File projectDir, ResourceFile androidManifest, ResContainer appStrings) {
|
|
||||||
this.root = root;
|
|
||||||
this.projectDir = projectDir;
|
|
||||||
this.appDir = new File(projectDir, "app");
|
|
||||||
this.applicationParams = getApplicationParams(androidManifest, appStrings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void generateGradleFiles() {
|
|
||||||
try {
|
|
||||||
saveProjectBuildGradle();
|
|
||||||
saveApplicationBuildGradle();
|
|
||||||
saveSettingsGradle();
|
|
||||||
saveGradleProperties();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new JadxRuntimeException("Gradle export failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveGradleProperties() throws IOException {
|
|
||||||
GradleInfoStorage gradleInfo = root.getGradleInfoStorage();
|
|
||||||
/*
|
|
||||||
* For Android Gradle Plugin >=8.0.0 the property "android.nonFinalResIds=false" has to be set in
|
|
||||||
* "gradle.properties" when resource identifiers are used as constant expressions.
|
|
||||||
*/
|
|
||||||
if (gradleInfo.isNonFinalResIds()) {
|
|
||||||
File gradlePropertiesFile = new File(projectDir, "gradle.properties");
|
|
||||||
try (FileOutputStream fos = new FileOutputStream(gradlePropertiesFile)) {
|
|
||||||
fos.write("android.nonFinalResIds=false".getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveProjectBuildGradle() throws IOException {
|
|
||||||
TemplateFile tmpl = TemplateFile.fromResources("/export/build.gradle.tmpl");
|
|
||||||
tmpl.save(new File(projectDir, "build.gradle"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveSettingsGradle() throws IOException {
|
|
||||||
TemplateFile tmpl = TemplateFile.fromResources("/export/settings.gradle.tmpl");
|
|
||||||
|
|
||||||
tmpl.add("applicationName", ILLEGAL_GRADLE_CHARS.matcher(applicationParams.getApplicationName()).replaceAll(""));
|
|
||||||
tmpl.save(new File(projectDir, "settings.gradle"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveApplicationBuildGradle() throws IOException {
|
|
||||||
TemplateFile tmpl = TemplateFile.fromResources("/export/app.build.gradle.tmpl");
|
|
||||||
String appPackage = root.getAppPackage();
|
|
||||||
|
|
||||||
if (appPackage == null) {
|
|
||||||
appPackage = "UNKNOWN";
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer minSdkVersion = applicationParams.getMinSdkVersion();
|
|
||||||
|
|
||||||
tmpl.add("applicationId", appPackage);
|
|
||||||
tmpl.add("minSdkVersion", minSdkVersion);
|
|
||||||
tmpl.add("targetSdkVersion", applicationParams.getTargetSdkVersion());
|
|
||||||
tmpl.add("versionCode", applicationParams.getVersionCode());
|
|
||||||
tmpl.add("versionName", applicationParams.getVersionName());
|
|
||||||
|
|
||||||
List<String> additionalOptions = new ArrayList<>();
|
|
||||||
GradleInfoStorage gradleInfo = root.getGradleInfoStorage();
|
|
||||||
if (gradleInfo.isVectorPathData() && minSdkVersion < 21 || gradleInfo.isVectorFillType() && minSdkVersion < 24) {
|
|
||||||
additionalOptions.add("vectorDrawables.useSupportLibrary = true");
|
|
||||||
}
|
|
||||||
if (gradleInfo.isUseApacheHttpLegacy()) {
|
|
||||||
additionalOptions.add("useLibrary 'org.apache.http.legacy'");
|
|
||||||
}
|
|
||||||
genAdditionalAndroidPluginOptions(tmpl, additionalOptions);
|
|
||||||
|
|
||||||
tmpl.save(new File(appDir, "build.gradle"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void genAdditionalAndroidPluginOptions(TemplateFile tmpl, List<String> additionalOptions) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (String additionalOption : additionalOptions) {
|
|
||||||
sb.append(" ").append(additionalOption).append('\n');
|
|
||||||
}
|
|
||||||
tmpl.add("additionalOptions", sb.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ApplicationParams getApplicationParams(ResourceFile androidManifest, ResContainer appStrings) {
|
|
||||||
AndroidManifestParser parser = new AndroidManifestParser(androidManifest, appStrings, EnumSet.of(
|
|
||||||
AppAttribute.APPLICATION_LABEL,
|
|
||||||
AppAttribute.MIN_SDK_VERSION,
|
|
||||||
AppAttribute.TARGET_SDK_VERSION,
|
|
||||||
AppAttribute.VERSION_CODE,
|
|
||||||
AppAttribute.VERSION_NAME),
|
|
||||||
root.getArgs().getSecurity());
|
|
||||||
return parser.parse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package jadx.core.export;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import jadx.api.ResourceFile;
|
|
||||||
import jadx.api.ResourceType;
|
|
||||||
import jadx.core.dex.nodes.RootNode;
|
|
||||||
import jadx.core.utils.android.AndroidManifestParser;
|
|
||||||
import jadx.core.utils.files.FileUtils;
|
|
||||||
import jadx.core.xmlgen.ResContainer;
|
|
||||||
|
|
||||||
public class ExportGradleTask implements Runnable {
|
|
||||||
|
|
||||||
private final List<ResourceFile> resources;
|
|
||||||
|
|
||||||
private final RootNode root;
|
|
||||||
private final File projectDir;
|
|
||||||
private final File srcOutDir;
|
|
||||||
private final File resOutDir;
|
|
||||||
|
|
||||||
public ExportGradleTask(List<ResourceFile> resources, RootNode root, File projectDir) {
|
|
||||||
this.resources = resources;
|
|
||||||
this.projectDir = projectDir;
|
|
||||||
this.root = root;
|
|
||||||
File appDir = new File(projectDir, "app");
|
|
||||||
this.srcOutDir = new File(appDir, "src/main/java");
|
|
||||||
this.resOutDir = new File(appDir, "src/main");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
FileUtils.makeDirs(srcOutDir);
|
|
||||||
FileUtils.makeDirs(resOutDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ResourceFile androidManifest = AndroidManifestParser.getAndroidManifest(resources);
|
|
||||||
if (androidManifest == null) {
|
|
||||||
throw new IllegalStateException("Could not find AndroidManifest.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ResContainer> resContainers = resources.stream()
|
|
||||||
.filter(resourceFile -> resourceFile.getType() == ResourceType.ARSC)
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(IllegalStateException::new)
|
|
||||||
.loadContent()
|
|
||||||
.getSubFiles();
|
|
||||||
|
|
||||||
ResContainer strings = resContainers
|
|
||||||
.stream()
|
|
||||||
.filter(resContainer -> resContainer.getName().contains("values/strings.xml"))
|
|
||||||
.findFirst()
|
|
||||||
.orElseGet(() -> resContainers.stream()
|
|
||||||
.filter(resContainer -> resContainer.getFileName().contains("strings.xml"))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null));
|
|
||||||
|
|
||||||
ExportGradleProject export = new ExportGradleProject(root, projectDir, androidManifest, strings);
|
|
||||||
export.generateGradleFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getSrcOutDir() {
|
|
||||||
return srcOutDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getResOutDir() {
|
|
||||||
return resOutDir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package jadx.core.export;
|
||||||
|
|
||||||
|
public enum ExportGradleType {
|
||||||
|
AUTO("Auto"),
|
||||||
|
ANDROID_APP("Android App"),
|
||||||
|
ANDROID_LIBRARY("Android Library"),
|
||||||
|
SIMPLE_JAVA("Simple Java");
|
||||||
|
|
||||||
|
private final String desc;
|
||||||
|
|
||||||
|
ExportGradleType(String desc) {
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDesc() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package jadx.core.export;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
public class OutDirs {
|
||||||
|
private final File srcOutDir;
|
||||||
|
private final File resOutDir;
|
||||||
|
|
||||||
|
public OutDirs(File srcOutDir, File resOutDir) {
|
||||||
|
this.srcOutDir = srcOutDir;
|
||||||
|
this.resOutDir = resOutDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getSrcOutDir() {
|
||||||
|
return srcOutDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getResOutDir() {
|
||||||
|
return resOutDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void makeDirs() {
|
||||||
|
FileUtils.makeDirs(srcOutDir);
|
||||||
|
FileUtils.makeDirs(resOutDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package jadx.core.export.gen;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.ResourceFile;
|
||||||
|
import jadx.api.ResourceType;
|
||||||
|
import jadx.api.security.IJadxSecurity;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.export.ExportGradleType;
|
||||||
|
import jadx.core.export.GradleInfoStorage;
|
||||||
|
import jadx.core.export.OutDirs;
|
||||||
|
import jadx.core.export.TemplateFile;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.core.utils.android.AndroidManifestParser;
|
||||||
|
import jadx.core.utils.android.AppAttribute;
|
||||||
|
import jadx.core.utils.android.ApplicationParams;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.core.xmlgen.ResContainer;
|
||||||
|
|
||||||
|
public class AndroidGradleGenerator implements IExportGradleGenerator {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AndroidGradleGenerator.class);
|
||||||
|
private static final Pattern ILLEGAL_GRADLE_CHARS = Pattern.compile("[/\\\\:>\"?*|]");
|
||||||
|
|
||||||
|
private static final ApplicationParams UNKNOWN_APP_PARAMS = new ApplicationParams("UNKNOWN", 0, 0, 0, "UNKNOWN", "UNKNOWN", "UNKNOWN");
|
||||||
|
|
||||||
|
private final RootNode root;
|
||||||
|
private final File projectDir;
|
||||||
|
private final List<ResourceFile> resources;
|
||||||
|
private final boolean exportApp;
|
||||||
|
|
||||||
|
private OutDirs outDirs;
|
||||||
|
private File baseDir;
|
||||||
|
private ApplicationParams applicationParams;
|
||||||
|
|
||||||
|
public AndroidGradleGenerator(RootNode root, File projectDir, List<ResourceFile> resources, ExportGradleType exportType) {
|
||||||
|
this.root = root;
|
||||||
|
this.projectDir = projectDir;
|
||||||
|
this.resources = resources;
|
||||||
|
this.exportApp = exportType == ExportGradleType.ANDROID_APP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
String moduleDir = exportApp ? "app" : "lib";
|
||||||
|
baseDir = new File(projectDir, moduleDir);
|
||||||
|
outDirs = new OutDirs(new File(baseDir, "src/main/java"), new File(baseDir, "src/main"));
|
||||||
|
applicationParams = parseApplicationParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void generateFiles() {
|
||||||
|
try {
|
||||||
|
saveProjectBuildGradle();
|
||||||
|
if (exportApp) {
|
||||||
|
saveApplicationBuildGradle();
|
||||||
|
} else {
|
||||||
|
saveLibraryBuildGradle();
|
||||||
|
}
|
||||||
|
saveSettingsGradle();
|
||||||
|
saveGradleProperties();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Gradle export failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutDirs getOutDirs() {
|
||||||
|
return outDirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApplicationParams parseApplicationParams() {
|
||||||
|
try {
|
||||||
|
ResourceFile androidManifest = AndroidManifestParser.getAndroidManifest(resources);
|
||||||
|
if (androidManifest == null) {
|
||||||
|
LOG.warn("AndroidManifest.xml not found, exported files will contains 'UNKNOWN' fields");
|
||||||
|
return UNKNOWN_APP_PARAMS;
|
||||||
|
}
|
||||||
|
ResContainer strings = null;
|
||||||
|
if (exportApp) {
|
||||||
|
ResourceFile arscFile = resources.stream()
|
||||||
|
.filter(resourceFile -> resourceFile.getType() == ResourceType.ARSC)
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
if (arscFile != null) {
|
||||||
|
List<ResContainer> resContainers = arscFile.loadContent().getSubFiles();
|
||||||
|
strings = resContainers
|
||||||
|
.stream()
|
||||||
|
.filter(resContainer -> resContainer.getName().contains("values/strings.xml"))
|
||||||
|
.findFirst()
|
||||||
|
.orElseGet(() -> resContainers.stream()
|
||||||
|
.filter(resContainer -> resContainer.getName().contains("strings.xml"))
|
||||||
|
.findFirst().orElse(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EnumSet<AppAttribute> attrs = EnumSet.noneOf(AppAttribute.class);
|
||||||
|
attrs.add(AppAttribute.MIN_SDK_VERSION);
|
||||||
|
if (exportApp) {
|
||||||
|
attrs.add(AppAttribute.APPLICATION_LABEL);
|
||||||
|
attrs.add(AppAttribute.TARGET_SDK_VERSION);
|
||||||
|
attrs.add(AppAttribute.VERSION_NAME);
|
||||||
|
attrs.add(AppAttribute.VERSION_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
IJadxSecurity security = root.getArgs().getSecurity();
|
||||||
|
AndroidManifestParser parser = new AndroidManifestParser(androidManifest, strings, attrs, security);
|
||||||
|
return parser.parse();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOG.warn("Failed to parse AndroidManifest.xml", t);
|
||||||
|
return UNKNOWN_APP_PARAMS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveGradleProperties() throws IOException {
|
||||||
|
GradleInfoStorage gradleInfo = root.getGradleInfoStorage();
|
||||||
|
/*
|
||||||
|
* For Android Gradle Plugin >=8.0.0 the property "android.nonFinalResIds=false" has to be set in
|
||||||
|
* "gradle.properties" when resource identifiers are used as constant expressions.
|
||||||
|
*/
|
||||||
|
if (gradleInfo.isNonFinalResIds()) {
|
||||||
|
File gradlePropertiesFile = new File(projectDir, "gradle.properties");
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(gradlePropertiesFile)) {
|
||||||
|
fos.write("android.nonFinalResIds=false".getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveProjectBuildGradle() throws IOException {
|
||||||
|
TemplateFile tmpl = TemplateFile.fromResources("/export/android/build.gradle.tmpl");
|
||||||
|
tmpl.save(new File(projectDir, "build.gradle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveSettingsGradle() throws IOException {
|
||||||
|
TemplateFile tmpl = TemplateFile.fromResources("/export/android/settings.gradle.tmpl");
|
||||||
|
String appName = applicationParams.getApplicationName();
|
||||||
|
String projectName;
|
||||||
|
if (appName != null) {
|
||||||
|
projectName = ILLEGAL_GRADLE_CHARS.matcher(appName).replaceAll("");
|
||||||
|
} else {
|
||||||
|
projectName = GradleGeneratorTools.guessProjectName(root);
|
||||||
|
}
|
||||||
|
tmpl.add("projectName", projectName);
|
||||||
|
tmpl.add("mainModuleName", baseDir.getName());
|
||||||
|
tmpl.save(new File(projectDir, "settings.gradle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveApplicationBuildGradle() throws IOException {
|
||||||
|
String appPackage = Utils.getOrElse(root.getAppPackage(), "UNKNOWN");
|
||||||
|
int minSdkVersion = Utils.getOrElse(applicationParams.getMinSdkVersion(), 0);
|
||||||
|
|
||||||
|
TemplateFile tmpl = TemplateFile.fromResources("/export/android/app.build.gradle.tmpl");
|
||||||
|
tmpl.add("applicationId", appPackage);
|
||||||
|
tmpl.add("minSdkVersion", minSdkVersion);
|
||||||
|
tmpl.add("targetSdkVersion", applicationParams.getTargetSdkVersion());
|
||||||
|
tmpl.add("versionCode", applicationParams.getVersionCode());
|
||||||
|
tmpl.add("versionName", applicationParams.getVersionName());
|
||||||
|
tmpl.add("additionalOptions", genAdditionalAndroidPluginOptions(minSdkVersion));
|
||||||
|
tmpl.save(new File(baseDir, "build.gradle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveLibraryBuildGradle() throws IOException {
|
||||||
|
String pkg = Utils.getOrElse(root.getAppPackage(), "UNKNOWN");
|
||||||
|
int minSdkVersion = Utils.getOrElse(applicationParams.getMinSdkVersion(), 0);
|
||||||
|
|
||||||
|
TemplateFile tmpl = TemplateFile.fromResources("/export/android/lib.build.gradle.tmpl");
|
||||||
|
tmpl.add("packageId", pkg);
|
||||||
|
tmpl.add("minSdkVersion", minSdkVersion);
|
||||||
|
tmpl.add("additionalOptions", genAdditionalAndroidPluginOptions(minSdkVersion));
|
||||||
|
|
||||||
|
tmpl.save(new File(baseDir, "build.gradle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String genAdditionalAndroidPluginOptions(int minSdkVersion) {
|
||||||
|
List<String> additionalOptions = new ArrayList<>();
|
||||||
|
GradleInfoStorage gradleInfo = root.getGradleInfoStorage();
|
||||||
|
if (gradleInfo.isVectorPathData() && minSdkVersion < 21 || gradleInfo.isVectorFillType() && minSdkVersion < 24) {
|
||||||
|
additionalOptions.add("vectorDrawables.useSupportLibrary = true");
|
||||||
|
}
|
||||||
|
if (gradleInfo.isUseApacheHttpLegacy()) {
|
||||||
|
additionalOptions.add("useLibrary 'org.apache.http.legacy'");
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String additionalOption : additionalOptions) {
|
||||||
|
sb.append(" ").append(additionalOption).append('\n');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package jadx.core.export.gen;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
public class GradleGeneratorTools {
|
||||||
|
|
||||||
|
public static String guessProjectName(RootNode root) {
|
||||||
|
List<File> inputFiles = root.getArgs().getInputFiles();
|
||||||
|
if (inputFiles.size() == 1) {
|
||||||
|
return FileUtils.getPathBaseName(inputFiles.get(0).toPath());
|
||||||
|
}
|
||||||
|
// default
|
||||||
|
return "PROJECT_NAME";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package jadx.core.export.gen;
|
||||||
|
|
||||||
|
import jadx.core.export.OutDirs;
|
||||||
|
|
||||||
|
public interface IExportGradleGenerator {
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
OutDirs getOutDirs();
|
||||||
|
|
||||||
|
void generateFiles();
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package jadx.core.export.gen;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.ResourceFile;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.export.OutDirs;
|
||||||
|
import jadx.core.export.TemplateFile;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
public class SimpleJavaGradleGenerator implements IExportGradleGenerator {
|
||||||
|
private final RootNode root;
|
||||||
|
private final File projectDir;
|
||||||
|
private final List<ResourceFile> resources;
|
||||||
|
|
||||||
|
private OutDirs outDirs;
|
||||||
|
private File appDir;
|
||||||
|
|
||||||
|
public SimpleJavaGradleGenerator(RootNode root, File projectDir, List<ResourceFile> resources) {
|
||||||
|
this.root = root;
|
||||||
|
this.projectDir = projectDir;
|
||||||
|
this.resources = resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
appDir = new File(projectDir, "app");
|
||||||
|
File srcOutDir = new File(appDir, "src/main/java");
|
||||||
|
File resOutDir = new File(appDir, "src/main/resources");
|
||||||
|
outDirs = new OutDirs(srcOutDir, resOutDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void generateFiles() {
|
||||||
|
try {
|
||||||
|
saveSettingsGradle();
|
||||||
|
saveBuildGradle();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to generate gradle files", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveSettingsGradle() throws IOException {
|
||||||
|
TemplateFile tmpl = TemplateFile.fromResources("/export/java/settings.gradle.kts.tmpl");
|
||||||
|
tmpl.add("projectName", GradleGeneratorTools.guessProjectName(root));
|
||||||
|
tmpl.save(new File(projectDir, "settings.gradle.kts"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveBuildGradle() throws IOException {
|
||||||
|
TemplateFile tmpl = TemplateFile.fromResources("/export/java/build.gradle.kts.tmpl");
|
||||||
|
tmpl.save(new File(appDir, "build.gradle.kts"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutDirs getOutDirs() {
|
||||||
|
return outDirs;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import java.nio.file.Path;
|
|||||||
|
|
||||||
import jadx.api.plugins.JadxPluginInfo;
|
import jadx.api.plugins.JadxPluginInfo;
|
||||||
import jadx.api.plugins.data.IJadxFiles;
|
import jadx.api.plugins.data.IJadxFiles;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxFilesData implements IJadxFiles {
|
public class JadxFilesData implements IJadxFiles {
|
||||||
private static final String PLUGINS_DATA_DIR = "plugins-data";
|
private static final String PLUGINS_DATA_DIR = "plugins-data";
|
||||||
@@ -32,6 +33,8 @@ public class JadxFilesData implements IJadxFiles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Path toPluginPath(Path dir) {
|
private Path toPluginPath(Path dir) {
|
||||||
return dir.resolve(PLUGINS_DATA_DIR).resolve(pluginInfo.getPluginId());
|
Path dirPath = dir.resolve(PLUGINS_DATA_DIR).resolve(pluginInfo.getPluginId());
|
||||||
|
FileUtils.makeDirs(dirPath);
|
||||||
|
return dirPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import jadx.core.xmlgen.ResContainer;
|
|||||||
|
|
||||||
public class AndroidManifestParser {
|
public class AndroidManifestParser {
|
||||||
private final Document androidManifest;
|
private final Document androidManifest;
|
||||||
private final Document appStrings;
|
private final @Nullable Document appStrings;
|
||||||
private final EnumSet<AppAttribute> parseAttrs;
|
private final EnumSet<AppAttribute> parseAttrs;
|
||||||
private final IJadxSecurity security;
|
private final IJadxSecurity security;
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ public class AndroidManifestParser {
|
|||||||
this(androidManifestRes, null, parseAttrs, security);
|
this(androidManifestRes, null, parseAttrs, security);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AndroidManifestParser(ResourceFile androidManifestRes, ResContainer appStrings,
|
public AndroidManifestParser(ResourceFile androidManifestRes, @Nullable ResContainer appStrings,
|
||||||
EnumSet<AppAttribute> parseAttrs, IJadxSecurity security) {
|
EnumSet<AppAttribute> parseAttrs, IJadxSecurity security) {
|
||||||
this.parseAttrs = parseAttrs;
|
this.parseAttrs = parseAttrs;
|
||||||
this.security = Objects.requireNonNull(security);
|
this.security = Objects.requireNonNull(security);
|
||||||
|
|||||||
@@ -10,17 +10,17 @@ public class ApplicationParams {
|
|||||||
private final Integer targetSdkVersion;
|
private final Integer targetSdkVersion;
|
||||||
private final Integer versionCode;
|
private final Integer versionCode;
|
||||||
private final String versionName;
|
private final String versionName;
|
||||||
private final String mainActivtiy;
|
private final String mainActivity;
|
||||||
private final String application;
|
private final String application;
|
||||||
|
|
||||||
public ApplicationParams(String applicationLabel, Integer minSdkVersion, Integer targetSdkVersion, Integer versionCode,
|
public ApplicationParams(String applicationLabel, Integer minSdkVersion, Integer targetSdkVersion, Integer versionCode,
|
||||||
String versionName, String mainActivtiy, String application) {
|
String versionName, String mainActivity, String application) {
|
||||||
this.applicationLabel = applicationLabel;
|
this.applicationLabel = applicationLabel;
|
||||||
this.minSdkVersion = minSdkVersion;
|
this.minSdkVersion = minSdkVersion;
|
||||||
this.targetSdkVersion = targetSdkVersion;
|
this.targetSdkVersion = targetSdkVersion;
|
||||||
this.versionCode = versionCode;
|
this.versionCode = versionCode;
|
||||||
this.versionName = versionName;
|
this.versionName = versionName;
|
||||||
this.mainActivtiy = mainActivtiy;
|
this.mainActivity = mainActivity;
|
||||||
this.application = application;
|
this.application = application;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,11 +45,11 @@ public class ApplicationParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getMainActivity() {
|
public String getMainActivity() {
|
||||||
return mainActivtiy;
|
return mainActivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JavaClass getMainActivityJavaClass(JadxDecompiler decompiler) {
|
public JavaClass getMainActivityJavaClass(JadxDecompiler decompiler) {
|
||||||
return decompiler.searchJavaClassByOrigFullName(mainActivtiy);
|
return decompiler.searchJavaClassByOrigFullName(mainActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getApplication() {
|
public String getApplication() {
|
||||||
|
|||||||
+1
-1
@@ -38,5 +38,5 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// some dependencies
|
// TODO: dependencies
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace '{{packageId}}'
|
||||||
|
compileSdk 30
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk {{minSdkVersion}}
|
||||||
|
|
||||||
|
{{additionalOptions}}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// TODO: dependencies
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
rootProject.name = '{{projectName}}'
|
||||||
|
|
||||||
|
include '{{mainModuleName}}'
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
plugins {
|
||||||
|
java
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// some dependencies
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
rootProject.name = "{{projectName}}"
|
||||||
|
|
||||||
|
include("app")
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
include ':app'
|
|
||||||
rootProject.name = '{{applicationName}}'
|
|
||||||
@@ -2,6 +2,8 @@ package jadx.tests.api;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -10,19 +12,21 @@ import org.junit.jupiter.api.io.TempDir;
|
|||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.ResourceFile;
|
import jadx.api.ResourceFile;
|
||||||
|
import jadx.api.ResourceFileContainer;
|
||||||
import jadx.api.ResourceFileContent;
|
import jadx.api.ResourceFileContent;
|
||||||
import jadx.api.ResourceType;
|
import jadx.api.ResourceType;
|
||||||
import jadx.api.impl.SimpleCodeInfo;
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.export.ExportGradleProject;
|
import jadx.core.export.ExportGradle;
|
||||||
import jadx.core.export.ExportGradleTask;
|
import jadx.core.export.ExportGradleType;
|
||||||
|
import jadx.core.export.OutDirs;
|
||||||
import jadx.core.xmlgen.ResContainer;
|
import jadx.core.xmlgen.ResContainer;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.fail;
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
|
||||||
public abstract class ExportGradleTest {
|
public abstract class ExportGradleTest {
|
||||||
private static final String MANIFEST_TESTS_DIR = "src/test/manifest";
|
private static final String MANIFEST_TESTS_DIR = "manifest";
|
||||||
|
|
||||||
private final RootNode root = new RootNode(new JadxArgs());
|
private final RootNode root = new RootNode(new JadxArgs());
|
||||||
|
|
||||||
@@ -30,7 +34,7 @@ public abstract class ExportGradleTest {
|
|||||||
private File exportDir;
|
private File exportDir;
|
||||||
|
|
||||||
protected ICodeInfo loadResource(String filename) {
|
protected ICodeInfo loadResource(String filename) {
|
||||||
return new SimpleCodeInfo(loadFileContent(new File(MANIFEST_TESTS_DIR, filename)));
|
return new SimpleCodeInfo(loadResourceContent(MANIFEST_TESTS_DIR, filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String loadFileContent(File filePath) {
|
private static String loadFileContent(File filePath) {
|
||||||
@@ -42,21 +46,37 @@ public abstract class ExportGradleTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String loadResourceContent(String dir, String filename) {
|
||||||
|
String resPath = dir + '/' + filename;
|
||||||
|
try (InputStream in = getClass().getClassLoader().getResourceAsStream(resPath)) {
|
||||||
|
if (in == null) {
|
||||||
|
fail("Resource not found: " + resPath);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail("Loading file failed: " + resPath, e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected RootNode getRootNode() {
|
protected RootNode getRootNode() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void exportGradle(String manifestFilename, String stringsFileName) {
|
protected void exportGradle(String manifestFilename, String stringsFileName) {
|
||||||
ResourceFile androidManifest = new ResourceFileContent(manifestFilename,
|
ResourceFile androidManifest =
|
||||||
ResourceType.XML, loadResource(manifestFilename));
|
new ResourceFileContent("AndroidManifest.xml", ResourceType.MANIFEST, loadResource(manifestFilename));
|
||||||
ResContainer strings = ResContainer.textResource(stringsFileName, loadResource(stringsFileName));
|
ResContainer strings = ResContainer.textResource(stringsFileName, loadResource(stringsFileName));
|
||||||
|
ResContainer arsc = ResContainer.resourceTable("resources.arsc", List.of(strings), new SimpleCodeInfo("empty"));
|
||||||
|
ResourceFile arscFile = new ResourceFileContainer("resources.arsc", ResourceType.ARSC, arsc);
|
||||||
|
List<ResourceFile> resources = List.of(androidManifest, arscFile);
|
||||||
|
|
||||||
ExportGradleTask exportGradleTask = new ExportGradleTask(List.of(androidManifest), root, exportDir);
|
root.getArgs().setExportGradleType(ExportGradleType.ANDROID_APP);
|
||||||
exportGradleTask.init();
|
ExportGradle export = new ExportGradle(root, exportDir, resources);
|
||||||
assertThat(exportGradleTask.getSrcOutDir()).exists();
|
OutDirs outDirs = export.init();
|
||||||
assertThat(exportGradleTask.getResOutDir()).exists();
|
assertThat(outDirs.getSrcOutDir()).exists();
|
||||||
|
assertThat(outDirs.getResOutDir()).exists();
|
||||||
ExportGradleProject export = new ExportGradleProject(root, exportDir, androidManifest, strings);
|
|
||||||
export.generateGradleFiles();
|
export.generateGradleFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +92,7 @@ public abstract class ExportGradleTest {
|
|||||||
return new File(exportDir, "gradle.properties");
|
return new File(exportDir, "gradle.properties");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getGradleProperies() {
|
protected String getGradleProperties() {
|
||||||
return loadFileContent(getGradleProperiesFile());
|
return loadFileContent(getGradleProperiesFile());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ public class TestNonFinalResIds extends ExportGradleTest {
|
|||||||
|
|
||||||
gradleInfo.setNonFinalResIds(true);
|
gradleInfo.setNonFinalResIds(true);
|
||||||
exportGradle("OptionalTargetSdkVersion.xml", "strings.xml");
|
exportGradle("OptionalTargetSdkVersion.xml", "strings.xml");
|
||||||
assertThat(getGradleProperies()).containsOne("android.nonFinalResIds=false");
|
assertThat(getGradleProperties()).containsOne("android.nonFinalResIds=false");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class TemplateFileTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBuildGradle() throws Exception {
|
public void testBuildGradle() throws Exception {
|
||||||
TemplateFile tmpl = TemplateFile.fromResources("/export/app.build.gradle.tmpl");
|
TemplateFile tmpl = TemplateFile.fromResources("/export/android/app.build.gradle.tmpl");
|
||||||
tmpl.add("applicationId", "SOME_ID");
|
tmpl.add("applicationId", "SOME_ID");
|
||||||
tmpl.add("minSdkVersion", 1);
|
tmpl.add("minSdkVersion", 1);
|
||||||
tmpl.add("targetSdkVersion", 2);
|
tmpl.add("targetSdkVersion", 2);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import javax.swing.JOptionPane;
|
|||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.utils.tasks.ITaskExecutor;
|
import jadx.api.utils.tasks.ITaskExecutor;
|
||||||
import jadx.gui.JadxWrapper;
|
import jadx.gui.JadxWrapper;
|
||||||
|
import jadx.gui.cache.code.CodeCacheMode;
|
||||||
import jadx.gui.cache.code.FixedCodeCache;
|
import jadx.gui.cache.code.FixedCodeCache;
|
||||||
import jadx.gui.ui.MainWindow;
|
import jadx.gui.ui.MainWindow;
|
||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
@@ -42,9 +43,11 @@ public class ExportTask extends CancelableBackgroundTask {
|
|||||||
|
|
||||||
private void wrapCodeCache() {
|
private void wrapCodeCache() {
|
||||||
uiCodeCache = wrapper.getArgs().getCodeCache();
|
uiCodeCache = wrapper.getArgs().getCodeCache();
|
||||||
// do not save newly decompiled code in cache to not increase memory usage
|
if (mainWindow.getSettings().getCodeCacheMode() != CodeCacheMode.DISK) {
|
||||||
// TODO: maybe make memory limited cache?
|
// do not save newly decompiled code in cache to not increase memory usage
|
||||||
wrapper.getArgs().setCodeCache(new FixedCodeCache(uiCodeCache));
|
// TODO: maybe make memory limited cache?
|
||||||
|
wrapper.getArgs().setCodeCache(new FixedCodeCache(uiCodeCache));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import jadx.gui.utils.UiUtils;
|
|||||||
|
|
||||||
public class TreeExpansionService {
|
public class TreeExpansionService {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TreeExpansionService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(TreeExpansionService.class);
|
||||||
private static final boolean DEBUG = UiUtils.JADX_GUI_DEBUG;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
private static final Comparator<TreePath> PATH_LENGTH_REVERSE = Comparator.comparingInt(p -> -p.getPathCount());
|
private static final Comparator<TreePath> PATH_LENGTH_REVERSE = Comparator.comparingInt(p -> -p.getPathCount());
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.extras.FlatInspector;
|
||||||
|
import com.formdev.flatlaf.extras.FlatUIDefaultsInspector;
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
import ch.qos.logback.classic.Level;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
@@ -111,7 +114,6 @@ import jadx.gui.plugins.context.CommonGuiPluginsContext;
|
|||||||
import jadx.gui.plugins.context.TreePopupMenuEntry;
|
import jadx.gui.plugins.context.TreePopupMenuEntry;
|
||||||
import jadx.gui.plugins.mappings.RenameMappingsGui;
|
import jadx.gui.plugins.mappings.RenameMappingsGui;
|
||||||
import jadx.gui.plugins.quark.QuarkDialog;
|
import jadx.gui.plugins.quark.QuarkDialog;
|
||||||
import jadx.gui.settings.ExportProjectProperties;
|
|
||||||
import jadx.gui.settings.JadxProject;
|
import jadx.gui.settings.JadxProject;
|
||||||
import jadx.gui.settings.JadxSettings;
|
import jadx.gui.settings.JadxSettings;
|
||||||
import jadx.gui.settings.ui.JadxSettingsWindow;
|
import jadx.gui.settings.ui.JadxSettingsWindow;
|
||||||
@@ -132,9 +134,9 @@ import jadx.gui.ui.codearea.EditorViewState;
|
|||||||
import jadx.gui.ui.dialog.ADBDialog;
|
import jadx.gui.ui.dialog.ADBDialog;
|
||||||
import jadx.gui.ui.dialog.AboutDialog;
|
import jadx.gui.ui.dialog.AboutDialog;
|
||||||
import jadx.gui.ui.dialog.ExceptionDialog;
|
import jadx.gui.ui.dialog.ExceptionDialog;
|
||||||
import jadx.gui.ui.dialog.ExportProjectDialog;
|
|
||||||
import jadx.gui.ui.dialog.LogViewerDialog;
|
import jadx.gui.ui.dialog.LogViewerDialog;
|
||||||
import jadx.gui.ui.dialog.SearchDialog;
|
import jadx.gui.ui.dialog.SearchDialog;
|
||||||
|
import jadx.gui.ui.export.ExportProjectDialog;
|
||||||
import jadx.gui.ui.filedialog.FileDialogWrapper;
|
import jadx.gui.ui.filedialog.FileDialogWrapper;
|
||||||
import jadx.gui.ui.filedialog.FileOpenMode;
|
import jadx.gui.ui.filedialog.FileOpenMode;
|
||||||
import jadx.gui.ui.menu.HiddenMenuItem;
|
import jadx.gui.ui.menu.HiddenMenuItem;
|
||||||
@@ -798,25 +800,23 @@ public class MainWindow extends JFrame {
|
|||||||
backgroundExecutor.cancelAll();
|
backgroundExecutor.cancelAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportProject() {
|
public void exportProject() {
|
||||||
ExportProjectDialog dialog = new ExportProjectDialog(this, this::saveAll);
|
ExportProjectDialog dialog = new ExportProjectDialog(this, props -> {
|
||||||
|
JadxArgs args = wrapper.getArgs();
|
||||||
|
if (props.isAsGradleMode()) {
|
||||||
|
args.setExportGradleType(props.getExportGradleType());
|
||||||
|
args.setSkipSources(false);
|
||||||
|
args.setSkipResources(false);
|
||||||
|
} else {
|
||||||
|
args.setExportGradleType(null);
|
||||||
|
args.setSkipSources(props.isSkipSources());
|
||||||
|
args.setSkipResources(props.isSkipResources());
|
||||||
|
}
|
||||||
|
backgroundExecutor.execute(new ExportTask(this, wrapper, new File(props.getExportPath())));
|
||||||
|
});
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAll(ExportProjectProperties exportProjectProperties) {
|
|
||||||
JadxArgs decompilerArgs = wrapper.getArgs();
|
|
||||||
decompilerArgs.setExportAsGradleProject(exportProjectProperties.isAsGradleMode());
|
|
||||||
if (exportProjectProperties.isAsGradleMode()) {
|
|
||||||
decompilerArgs.setSkipSources(false);
|
|
||||||
decompilerArgs.setSkipResources(false);
|
|
||||||
} else {
|
|
||||||
decompilerArgs.setSkipSources(exportProjectProperties.isSkipSources());
|
|
||||||
decompilerArgs.setSkipResources(exportProjectProperties.isSkipResources());
|
|
||||||
}
|
|
||||||
File saveDir = new File(exportProjectProperties.getExportPath());
|
|
||||||
backgroundExecutor.execute(new ExportTask(this, wrapper, saveDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initTree() {
|
public void initTree() {
|
||||||
treeRoot = new JRoot(wrapper);
|
treeRoot = new JRoot(wrapper);
|
||||||
treeRoot.setFlatPackages(isFlattenPackage);
|
treeRoot.setFlatPackages(isFlattenPackage);
|
||||||
@@ -1422,6 +1422,11 @@ public class MainWindow extends JFrame {
|
|||||||
mainPanel.add(bottomSplitPane, BorderLayout.CENTER);
|
mainPanel.add(bottomSplitPane, BorderLayout.CENTER);
|
||||||
setContentPane(mainPanel);
|
setContentPane(mainPanel);
|
||||||
setTitle(DEFAULT_TITLE);
|
setTitle(DEFAULT_TITLE);
|
||||||
|
|
||||||
|
if (UiUtils.JADX_GUI_DEBUG) {
|
||||||
|
FlatInspector.install("ctrl shift alt X");
|
||||||
|
FlatUIDefaultsInspector.install("ctrl shift alt Y");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocationAndPosition() {
|
public void setLocationAndPosition() {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public abstract class CommonDialog extends JDialog {
|
|||||||
UiUtils.addEscapeShortCutToDispose(this);
|
UiUtils.addEscapeShortCutToDispose(this);
|
||||||
setLocationRelativeTo(null);
|
setLocationRelativeTo(null);
|
||||||
|
|
||||||
pack();
|
UiUtils.uiRunAndWait(this::pack);
|
||||||
Dimension minSize = getSize();
|
Dimension minSize = getSize();
|
||||||
setMinimumSize(minSize);
|
setMinimumSize(minSize);
|
||||||
if (!mainWindow.getSettings().loadWindowPos(this)) {
|
if (!mainWindow.getSettings().loadWindowPos(this)) {
|
||||||
|
|||||||
+94
-58
@@ -1,10 +1,9 @@
|
|||||||
package jadx.gui.ui.dialog;
|
package jadx.gui.ui.export;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Container;
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.event.ItemEvent;
|
import java.awt.event.ItemEvent;
|
||||||
import java.io.File;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -12,16 +11,23 @@ import java.util.function.Consumer;
|
|||||||
import javax.swing.BorderFactory;
|
import javax.swing.BorderFactory;
|
||||||
import javax.swing.Box;
|
import javax.swing.Box;
|
||||||
import javax.swing.BoxLayout;
|
import javax.swing.BoxLayout;
|
||||||
import javax.swing.GroupLayout;
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JCheckBox;
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JComboBox;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JTextField;
|
import javax.swing.JTextField;
|
||||||
|
|
||||||
import jadx.gui.settings.ExportProjectProperties;
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.export.ExportGradle;
|
||||||
|
import jadx.core.export.ExportGradleType;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.ui.MainWindow;
|
import jadx.gui.ui.MainWindow;
|
||||||
|
import jadx.gui.ui.dialog.CommonDialog;
|
||||||
import jadx.gui.ui.filedialog.FileDialogWrapper;
|
import jadx.gui.ui.filedialog.FileDialogWrapper;
|
||||||
import jadx.gui.ui.filedialog.FileOpenMode;
|
import jadx.gui.ui.filedialog.FileOpenMode;
|
||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
@@ -29,6 +35,7 @@ import jadx.gui.utils.TextStandardActions;
|
|||||||
import jadx.gui.utils.ui.DocumentUpdateListener;
|
import jadx.gui.utils.ui.DocumentUpdateListener;
|
||||||
|
|
||||||
public class ExportProjectDialog extends CommonDialog {
|
public class ExportProjectDialog extends CommonDialog {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ExportProjectDialog.class);
|
||||||
|
|
||||||
private final ExportProjectProperties exportProjectProperties = new ExportProjectProperties();
|
private final ExportProjectProperties exportProjectProperties = new ExportProjectProperties();
|
||||||
private final Consumer<ExportProjectProperties> exportListener;
|
private final Consumer<ExportProjectProperties> exportListener;
|
||||||
@@ -40,31 +47,26 @@ public class ExportProjectDialog extends CommonDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initUI() {
|
private void initUI() {
|
||||||
JPanel contentPane = makeContentPane();
|
JPanel contentPanel = new JPanel();
|
||||||
JPanel buttonPane = initButtonsPanel();
|
contentPanel.setLayout(new BorderLayout(5, 5));
|
||||||
Container container = getContentPane();
|
contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
container.add(contentPane, BorderLayout.CENTER);
|
contentPanel.add(makeContentPane(), BorderLayout.PAGE_START);
|
||||||
container.add(buttonPane, BorderLayout.PAGE_END);
|
contentPanel.add(initButtonsPanel(), BorderLayout.PAGE_END);
|
||||||
|
getContentPane().add(contentPanel);
|
||||||
|
|
||||||
setTitle(NLS.str("export_dialog.title"));
|
setTitle(NLS.str("export_dialog.title"));
|
||||||
commonWindowInit();
|
commonWindowInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private JPanel makeContentPane() {
|
private JPanel makeContentPane() {
|
||||||
// top layout
|
JLabel pathLbl = new JLabel(NLS.str("export_dialog.save_path"));
|
||||||
JLabel label = new JLabel(NLS.str("export_dialog.save_path"));
|
|
||||||
JTextField pathField = new JTextField();
|
JTextField pathField = new JTextField();
|
||||||
pathField.setText(mainWindow.getSettings().getLastSaveFilePath().toString());
|
|
||||||
pathField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> setExportProjectPath(pathField)));
|
pathField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> setExportProjectPath(pathField)));
|
||||||
|
pathField.setText(mainWindow.getSettings().getLastSaveFilePath().toString());
|
||||||
TextStandardActions.attach(pathField);
|
TextStandardActions.attach(pathField);
|
||||||
|
|
||||||
JButton browseButton = makeEditorBrowseButton(pathField);
|
JButton browseButton = makeEditorBrowseButton(pathField);
|
||||||
|
|
||||||
// check box layout
|
|
||||||
JPanel exportOptionsPanel = new JPanel();
|
|
||||||
exportOptionsPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("export_dialog.export_options")));
|
|
||||||
exportOptionsPanel.setLayout(new BoxLayout(exportOptionsPanel, BoxLayout.PAGE_AXIS));
|
|
||||||
|
|
||||||
JCheckBox resourceDecode = new JCheckBox(NLS.str("preferences.skipResourcesDecode"));
|
JCheckBox resourceDecode = new JCheckBox(NLS.str("preferences.skipResourcesDecode"));
|
||||||
resourceDecode.setSelected(mainWindow.getSettings().isSkipResources());
|
resourceDecode.setSelected(mainWindow.getSettings().isSkipResources());
|
||||||
resourceDecode.addItemListener(e -> {
|
resourceDecode.addItemListener(e -> {
|
||||||
@@ -77,55 +79,70 @@ public class ExportProjectDialog extends CommonDialog {
|
|||||||
exportProjectProperties.setSkipSources(e.getStateChange() == ItemEvent.SELECTED);
|
exportProjectProperties.setSkipSources(e.getStateChange() == ItemEvent.SELECTED);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
JLabel exportTypeLbl = new JLabel(NLS.str("export_dialog.export_gradle_type"));
|
||||||
|
JComboBox<ExportGradleType> exportTypeComboBox = new JComboBox<>(ExportGradleType.values());
|
||||||
|
exportTypeLbl.setLabelFor(exportTypeComboBox);
|
||||||
|
ExportGradleType initialExportType = getExportGradleType();
|
||||||
|
exportProjectProperties.setExportGradleType(initialExportType);
|
||||||
|
exportTypeComboBox.setSelectedItem(initialExportType);
|
||||||
|
exportTypeComboBox.addItemListener(e -> {
|
||||||
|
exportProjectProperties.setExportGradleType((ExportGradleType) e.getItem());
|
||||||
|
});
|
||||||
|
exportTypeComboBox.setEnabled(false);
|
||||||
|
|
||||||
JCheckBox exportAsGradleProject = new JCheckBox(NLS.str("export_dialog.export_gradle"));
|
JCheckBox exportAsGradleProject = new JCheckBox(NLS.str("export_dialog.export_gradle"));
|
||||||
exportAsGradleProject.addItemListener(e -> {
|
exportAsGradleProject.addItemListener(e -> {
|
||||||
boolean isSelected = e.getStateChange() == ItemEvent.SELECTED;
|
boolean enableGradle = e.getStateChange() == ItemEvent.SELECTED;
|
||||||
|
exportProjectProperties.setAsGradleMode(enableGradle);
|
||||||
exportProjectProperties.setAsGradleMode(isSelected);
|
exportTypeComboBox.setEnabled(enableGradle);
|
||||||
resourceDecode.setEnabled(!isSelected);
|
resourceDecode.setEnabled(!enableGradle);
|
||||||
skipSources.setEnabled(!isSelected);
|
skipSources.setEnabled(!enableGradle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
JPanel pathPanel = new JPanel();
|
||||||
|
pathPanel.setLayout(new BoxLayout(pathPanel, BoxLayout.LINE_AXIS));
|
||||||
|
pathPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||||
|
pathPanel.add(pathLbl);
|
||||||
|
pathPanel.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||||
|
pathPanel.add(pathField);
|
||||||
|
pathPanel.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||||
|
pathPanel.add(browseButton);
|
||||||
|
|
||||||
|
JPanel typePanel = new JPanel();
|
||||||
|
typePanel.setLayout(new BoxLayout(typePanel, BoxLayout.LINE_AXIS));
|
||||||
|
typePanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||||
|
typePanel.add(Box.createRigidArea(new Dimension(20, 0)));
|
||||||
|
typePanel.add(exportTypeLbl);
|
||||||
|
typePanel.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||||
|
typePanel.add(exportTypeComboBox);
|
||||||
|
typePanel.add(Box.createHorizontalGlue());
|
||||||
|
|
||||||
|
JPanel exportOptionsPanel = new JPanel();
|
||||||
|
exportOptionsPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("export_dialog.export_options")));
|
||||||
|
exportOptionsPanel.setLayout(new BoxLayout(exportOptionsPanel, BoxLayout.PAGE_AXIS));
|
||||||
exportOptionsPanel.add(exportAsGradleProject);
|
exportOptionsPanel.add(exportAsGradleProject);
|
||||||
|
exportOptionsPanel.add(typePanel);
|
||||||
exportOptionsPanel.add(resourceDecode);
|
exportOptionsPanel.add(resourceDecode);
|
||||||
exportOptionsPanel.add(skipSources);
|
exportOptionsPanel.add(skipSources);
|
||||||
|
|
||||||
// build group box layout
|
|
||||||
JPanel groupBoxPanel = new JPanel();
|
|
||||||
GroupLayout groupBoxLayout = new GroupLayout(groupBoxPanel);
|
|
||||||
groupBoxLayout.setAutoCreateGaps(true);
|
|
||||||
groupBoxLayout.setAutoCreateContainerGaps(true);
|
|
||||||
groupBoxPanel.setLayout(groupBoxLayout);
|
|
||||||
|
|
||||||
groupBoxLayout.setHorizontalGroup(groupBoxLayout.createParallelGroup()
|
|
||||||
.addComponent(exportOptionsPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Integer.MAX_VALUE));
|
|
||||||
groupBoxLayout.setVerticalGroup(groupBoxLayout.createSequentialGroup()
|
|
||||||
.addComponent(exportOptionsPanel));
|
|
||||||
|
|
||||||
// main layout
|
|
||||||
JPanel mainPanel = new JPanel();
|
JPanel mainPanel = new JPanel();
|
||||||
GroupLayout layout = new GroupLayout(mainPanel);
|
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
|
||||||
mainPanel.setLayout(layout);
|
mainPanel.add(pathPanel);
|
||||||
layout.setAutoCreateGaps(true);
|
mainPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||||
layout.setAutoCreateContainerGaps(true);
|
mainPanel.add(exportOptionsPanel);
|
||||||
|
|
||||||
// arrange components using GroupLayout
|
|
||||||
layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
|
|
||||||
.addGroup(layout.createSequentialGroup()
|
|
||||||
.addComponent(label)
|
|
||||||
.addComponent(pathField)
|
|
||||||
.addComponent(browseButton))
|
|
||||||
.addComponent(groupBoxPanel));
|
|
||||||
|
|
||||||
layout.setVerticalGroup(layout.createSequentialGroup()
|
|
||||||
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
|
|
||||||
.addComponent(label)
|
|
||||||
.addComponent(pathField)
|
|
||||||
.addComponent(browseButton))
|
|
||||||
.addComponent(groupBoxPanel));
|
|
||||||
return mainPanel;
|
return mainPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ExportGradleType getExportGradleType() {
|
||||||
|
try {
|
||||||
|
JadxWrapper wrapper = mainWindow.getWrapper();
|
||||||
|
return ExportGradle.detectExportType(wrapper.getRootNode(), wrapper.getResources());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Failed to detect export type", e);
|
||||||
|
return ExportGradleType.AUTO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setExportProjectPath(JTextField field) {
|
private void setExportProjectPath(JTextField field) {
|
||||||
String path = field.getText();
|
String path = field.getText();
|
||||||
if (!path.isEmpty()) {
|
if (!path.isEmpty()) {
|
||||||
@@ -143,8 +160,6 @@ public class ExportProjectDialog extends CommonDialog {
|
|||||||
|
|
||||||
JPanel buttonPane = new JPanel();
|
JPanel buttonPane = new JPanel();
|
||||||
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
||||||
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
|
||||||
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
|
|
||||||
buttonPane.add(Box.createHorizontalGlue());
|
buttonPane.add(Box.createHorizontalGlue());
|
||||||
buttonPane.add(exportProjectButton);
|
buttonPane.add(exportProjectButton);
|
||||||
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
|
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||||
@@ -168,12 +183,33 @@ public class ExportProjectDialog extends CommonDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void exportProject() {
|
private void exportProject() {
|
||||||
if (!new File(exportProjectProperties.getExportPath()).exists()) {
|
String exportPathStr = exportProjectProperties.getExportPath();
|
||||||
|
if (!validateAndMakeDir(exportPathStr)) {
|
||||||
JOptionPane.showMessageDialog(this, NLS.str("message.enter_valid_path"),
|
JOptionPane.showMessageDialog(this, NLS.str("message.enter_valid_path"),
|
||||||
NLS.str("message.errorTitle"), JOptionPane.WARNING_MESSAGE);
|
NLS.str("message.errorTitle"), JOptionPane.WARNING_MESSAGE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mainWindow.getSettings().setLastSaveFilePath(Path.of(exportPathStr));
|
||||||
|
LOG.debug("Export properties: {}", exportProjectProperties);
|
||||||
exportListener.accept(exportProjectProperties);
|
exportListener.accept(exportProjectProperties);
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean validateAndMakeDir(String exportPath) {
|
||||||
|
if (exportPath == null || exportPath.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Path path = Path.of(exportPath);
|
||||||
|
if (Files.isRegularFile(path)) {
|
||||||
|
// dir exists as a file
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FileUtils.makeDirs(path);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Export path validate error, path string:{}", exportPath, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+24
-1
@@ -1,9 +1,14 @@
|
|||||||
package jadx.gui.settings;
|
package jadx.gui.ui.export;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.core.export.ExportGradleType;
|
||||||
|
|
||||||
public class ExportProjectProperties {
|
public class ExportProjectProperties {
|
||||||
private boolean skipSources;
|
private boolean skipSources;
|
||||||
private boolean skipResources;
|
private boolean skipResources;
|
||||||
private boolean asGradleMode;
|
private boolean asGradleMode;
|
||||||
|
private @Nullable ExportGradleType exportGradleType;
|
||||||
private String exportPath;
|
private String exportPath;
|
||||||
|
|
||||||
public boolean isSkipSources() {
|
public boolean isSkipSources() {
|
||||||
@@ -30,6 +35,14 @@ public class ExportProjectProperties {
|
|||||||
this.asGradleMode = asGradleMode;
|
this.asGradleMode = asGradleMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable ExportGradleType getExportGradleType() {
|
||||||
|
return exportGradleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExportGradleType(@Nullable ExportGradleType exportGradleType) {
|
||||||
|
this.exportGradleType = exportGradleType;
|
||||||
|
}
|
||||||
|
|
||||||
public String getExportPath() {
|
public String getExportPath() {
|
||||||
return exportPath;
|
return exportPath;
|
||||||
}
|
}
|
||||||
@@ -37,4 +50,14 @@ public class ExportProjectProperties {
|
|||||||
public void setExportPath(String exportPath) {
|
public void setExportPath(String exportPath) {
|
||||||
this.exportPath = exportPath;
|
this.exportPath = exportPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ExportProjectProperties{exportPath='" + exportPath + '\''
|
||||||
|
+ ", asGradleMode=" + asGradleMode
|
||||||
|
+ ", exportGradleType=" + exportGradleType
|
||||||
|
+ ", skipSources=" + skipSources
|
||||||
|
+ ", skipResources=" + skipResources
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -180,6 +180,7 @@ export_dialog.save_path=Speicherpfad:
|
|||||||
export_dialog.browse=Durchsuchen
|
export_dialog.browse=Durchsuchen
|
||||||
export_dialog.export_options=Exportoptionen
|
export_dialog.export_options=Exportoptionen
|
||||||
export_dialog.export_gradle=Als Gradle-Projekt exportieren
|
export_dialog.export_gradle=Als Gradle-Projekt exportieren
|
||||||
|
#export_dialog.export_gradle_type=Gradle template:
|
||||||
|
|
||||||
log_viewer.title=Protokollanzeige
|
log_viewer.title=Protokollanzeige
|
||||||
log_viewer.log_level=Protokollstufe:
|
log_viewer.log_level=Protokollstufe:
|
||||||
|
|||||||
@@ -175,11 +175,12 @@ comment_dialog.usage=Use 'Shift + Enter' to start a new line
|
|||||||
|
|
||||||
rename_dialog.class_help=Enter full name to move class to another package. Start with '.' to move to default (empty) package
|
rename_dialog.class_help=Enter full name to move class to another package. Start with '.' to move to default (empty) package
|
||||||
|
|
||||||
export_dialog.title=Export to source code
|
export_dialog.title=Export
|
||||||
export_dialog.save_path=Save path:
|
export_dialog.save_path=Save path:
|
||||||
export_dialog.browse=Browse
|
export_dialog.browse=Browse
|
||||||
export_dialog.export_options=Export options
|
export_dialog.export_options=Export options
|
||||||
export_dialog.export_gradle=Export as gradle project
|
export_dialog.export_gradle=Export as a Gradle project
|
||||||
|
export_dialog.export_gradle_type=Gradle template:
|
||||||
|
|
||||||
log_viewer.title=Log Viewer
|
log_viewer.title=Log Viewer
|
||||||
log_viewer.log_level=Log level:
|
log_viewer.log_level=Log level:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ usage_dialog.label=Usage for:
|
|||||||
#export_dialog.browse=Browse
|
#export_dialog.browse=Browse
|
||||||
#export_dialog.export_options=Export options
|
#export_dialog.export_options=Export options
|
||||||
#export_dialog.export_gradle=Export as gradle project
|
#export_dialog.export_gradle=Export as gradle project
|
||||||
|
#export_dialog.export_gradle_type=Gradle template:
|
||||||
|
|
||||||
log_viewer.title=Visor log
|
log_viewer.title=Visor log
|
||||||
log_viewer.log_level=Nivel log:
|
log_viewer.log_level=Nivel log:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ comment_dialog.usage=Gunakan Shift + Enter untuk memulai baris baru
|
|||||||
#export_dialog.browse=Browse
|
#export_dialog.browse=Browse
|
||||||
#export_dialog.export_options=Export options
|
#export_dialog.export_options=Export options
|
||||||
#export_dialog.export_gradle=Export as gradle project
|
#export_dialog.export_gradle=Export as gradle project
|
||||||
|
#export_dialog.export_gradle_type=Gradle template:
|
||||||
|
|
||||||
log_viewer.title=Pemantau Log
|
log_viewer.title=Pemantau Log
|
||||||
log_viewer.log_level=Tingkat log:
|
log_viewer.log_level=Tingkat log:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ comment_dialog.usage=Shift + Enter 를 입력해 새 라인에 입력
|
|||||||
#export_dialog.browse=Browse
|
#export_dialog.browse=Browse
|
||||||
#export_dialog.export_options=Export options
|
#export_dialog.export_options=Export options
|
||||||
#export_dialog.export_gradle=Export as gradle project
|
#export_dialog.export_gradle=Export as gradle project
|
||||||
|
#export_dialog.export_gradle_type=Gradle template:
|
||||||
|
|
||||||
log_viewer.title=로그 뷰어
|
log_viewer.title=로그 뷰어
|
||||||
log_viewer.log_level=로그 레벨:
|
log_viewer.log_level=로그 레벨:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ comment_dialog.usage=Use Shift + Enter para pular uma linha
|
|||||||
#export_dialog.browse=Browse
|
#export_dialog.browse=Browse
|
||||||
#export_dialog.export_options=Export options
|
#export_dialog.export_options=Export options
|
||||||
#export_dialog.export_gradle=Export as gradle project
|
#export_dialog.export_gradle=Export as gradle project
|
||||||
|
#export_dialog.export_gradle_type=Gradle template:
|
||||||
|
|
||||||
log_viewer.title=Visualizador de log
|
log_viewer.title=Visualizador de log
|
||||||
log_viewer.log_level=Nível do log:
|
log_viewer.log_level=Nível do log:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ rename_dialog.class_help=Введите полный путь к пакету,
|
|||||||
#export_dialog.browse=Browse
|
#export_dialog.browse=Browse
|
||||||
#export_dialog.export_options=Export options
|
#export_dialog.export_options=Export options
|
||||||
#export_dialog.export_gradle=Export as gradle project
|
#export_dialog.export_gradle=Export as gradle project
|
||||||
|
#export_dialog.export_gradle_type=Gradle template:
|
||||||
|
|
||||||
log_viewer.title=Просмотр логов
|
log_viewer.title=Просмотр логов
|
||||||
log_viewer.log_level=Уровень лога:
|
log_viewer.log_level=Уровень лога:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ export_dialog.save_path=保存路径:
|
|||||||
export_dialog.browse=浏览
|
export_dialog.browse=浏览
|
||||||
export_dialog.export_options=导出选项
|
export_dialog.export_options=导出选项
|
||||||
export_dialog.export_gradle=导出为 Gradle 项目
|
export_dialog.export_gradle=导出为 Gradle 项目
|
||||||
|
#export_dialog.export_gradle_type=Gradle template:
|
||||||
|
|
||||||
log_viewer.title=日志查看器
|
log_viewer.title=日志查看器
|
||||||
log_viewer.log_level=日志等级:
|
log_viewer.log_level=日志等级:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ export_dialog.save_path=儲存路徑:
|
|||||||
export_dialog.browse=瀏覽
|
export_dialog.browse=瀏覽
|
||||||
export_dialog.export_options=匯出選項
|
export_dialog.export_options=匯出選項
|
||||||
export_dialog.export_gradle=匯出成 Gradle 專案
|
export_dialog.export_gradle=匯出成 Gradle 專案
|
||||||
|
#export_dialog.export_gradle_type=Gradle template:
|
||||||
|
|
||||||
log_viewer.title=記錄檔檢視器
|
log_viewer.title=記錄檔檢視器
|
||||||
log_viewer.log_level=記錄層級:
|
log_viewer.log_level=記錄層級:
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ data class ScriptAnalyzeResult(
|
|||||||
)
|
)
|
||||||
|
|
||||||
class ScriptServices {
|
class ScriptServices {
|
||||||
private val compileConf = ScriptEval.compileConf
|
private val compileConf = ScriptEval().buildCompileConf()
|
||||||
private val replCompiler = KJvmReplCompilerWithIdeServices(
|
private val replCompiler = KJvmReplCompilerWithIdeServices(
|
||||||
compileConf[ScriptCompilationConfiguration.hostConfiguration]
|
compileConf[ScriptCompilationConfiguration.hostConfiguration]
|
||||||
?: defaultJvmScriptingHostConfiguration,
|
?: defaultJvmScriptingHostConfiguration,
|
||||||
|
|||||||
+6
-7
@@ -7,16 +7,15 @@ import jadx.plugins.script.passes.JadxScriptAfterLoadPass
|
|||||||
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
|
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
|
||||||
|
|
||||||
class JadxScriptPlugin : JadxPlugin {
|
class JadxScriptPlugin : JadxPlugin {
|
||||||
private val scriptOptions = JadxScriptAllOptions()
|
|
||||||
|
|
||||||
override fun getPluginInfo() = JadxPluginInfo("jadx-script", "Jadx Script", "Scripting support for jadx")
|
override fun getPluginInfo() = JadxPluginInfo("jadx-script", "Jadx Script", "Scripting support for jadx")
|
||||||
|
|
||||||
override fun init(init: JadxPluginContext) {
|
override fun init(context: JadxPluginContext) {
|
||||||
init.registerOptions(scriptOptions)
|
val scriptOptions = JadxScriptAllOptions()
|
||||||
val scripts = ScriptEval().process(init, scriptOptions)
|
context.registerOptions(scriptOptions)
|
||||||
|
val scripts = ScriptEval().process(context, scriptOptions)
|
||||||
if (scripts.isNotEmpty()) {
|
if (scripts.isNotEmpty()) {
|
||||||
init.addPass(JadxScriptAfterLoadPass(scripts))
|
context.addPass(JadxScriptAfterLoadPass(scripts))
|
||||||
init.guiContext?.let { JadxScriptOptionsUI.setup(it, scriptOptions) }
|
context.guiContext?.let { JadxScriptOptionsUI.setup(it, scriptOptions) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-7
@@ -1,6 +1,7 @@
|
|||||||
package jadx.plugins.script
|
package jadx.plugins.script
|
||||||
|
|
||||||
import jadx.commons.app.JadxCommonFiles
|
import jadx.api.plugins.JadxPluginContext
|
||||||
|
import jadx.core.utils.files.FileUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import kotlin.script.experimental.api.CompiledScript
|
import kotlin.script.experimental.api.CompiledScript
|
||||||
@@ -14,11 +15,11 @@ import kotlin.script.experimental.jvmhost.saveToJar
|
|||||||
class ScriptCache {
|
class ScriptCache {
|
||||||
private val enableCache = System.getProperty("JADX_SCRIPT_CACHE_ENABLE", "true").equals("true", ignoreCase = true)
|
private val enableCache = System.getProperty("JADX_SCRIPT_CACHE_ENABLE", "true").equals("true", ignoreCase = true)
|
||||||
|
|
||||||
fun build(): CompiledJvmScriptsCache {
|
fun build(context: JadxPluginContext): CompiledJvmScriptsCache {
|
||||||
if (!enableCache) {
|
if (!enableCache) {
|
||||||
return CompiledJvmScriptsCache.NoCache
|
return CompiledJvmScriptsCache.NoCache
|
||||||
}
|
}
|
||||||
return JadxScriptsCache(getCacheDir())
|
return JadxScriptsCache(getCacheDir(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +39,7 @@ class ScriptCache {
|
|||||||
}
|
}
|
||||||
return file.loadScriptFromJar() ?: run {
|
return file.loadScriptFromJar() ?: run {
|
||||||
// invalidate cache if the script cannot be loaded
|
// invalidate cache if the script cannot be loaded
|
||||||
cacheDir.deleteRecursively()
|
FileUtils.deleteDir(cacheDir)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,9 +61,9 @@ class ScriptCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCacheDir(): File {
|
private fun getCacheDir(context: JadxPluginContext): File {
|
||||||
val cacheBaseDir = JadxCommonFiles.getCacheDir().resolve("scripts").toFile()
|
val cacheBaseDir = context.files().pluginCacheDir.resolve("compiled").toFile()
|
||||||
cacheBaseDir.mkdirs()
|
FileUtils.makeDirs(cacheBaseDir)
|
||||||
return cacheBaseDir
|
return cacheBaseDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+31
-35
@@ -4,13 +4,12 @@ import jadx.api.plugins.JadxPluginContext
|
|||||||
import jadx.plugins.script.runtime.JadxScriptData
|
import jadx.plugins.script.runtime.JadxScriptData
|
||||||
import jadx.plugins.script.runtime.JadxScriptTemplate
|
import jadx.plugins.script.runtime.JadxScriptTemplate
|
||||||
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
|
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
|
||||||
import kotlin.script.experimental.api.CompiledScript
|
|
||||||
import kotlin.script.experimental.api.EvaluationResult
|
import kotlin.script.experimental.api.EvaluationResult
|
||||||
import kotlin.script.experimental.api.ResultValue
|
import kotlin.script.experimental.api.ResultValue
|
||||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||||
|
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||||
import kotlin.script.experimental.api.ScriptDiagnostic.Severity
|
import kotlin.script.experimental.api.ScriptDiagnostic.Severity
|
||||||
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
|
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
|
||||||
import kotlin.script.experimental.api.SourceCode
|
|
||||||
import kotlin.script.experimental.api.constructorArgs
|
import kotlin.script.experimental.api.constructorArgs
|
||||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||||
import kotlin.script.experimental.host.toScriptSource
|
import kotlin.script.experimental.host.toScriptSource
|
||||||
@@ -25,52 +24,32 @@ import kotlin.time.toDuration
|
|||||||
|
|
||||||
class ScriptEval {
|
class ScriptEval {
|
||||||
|
|
||||||
companion object {
|
fun process(context: JadxPluginContext, scriptOptions: JadxScriptAllOptions): List<JadxScriptData> {
|
||||||
val scriptingHost = BasicJvmScriptingHost(
|
val jadx = context.decompiler
|
||||||
baseHostConfiguration = ScriptingHostConfiguration {
|
|
||||||
jvm {
|
|
||||||
compilationCache(ScriptCache().build())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
val compileConf = createJvmCompilationConfigurationFromTemplate<JadxScriptTemplate>()
|
|
||||||
|
|
||||||
private val baseEvalConf = createJvmEvaluationConfigurationFromTemplate<JadxScriptTemplate>()
|
|
||||||
|
|
||||||
private fun buildEvalConf(scriptData: JadxScriptData) =
|
|
||||||
ScriptEvaluationConfiguration(baseEvalConf) {
|
|
||||||
constructorArgs(scriptData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun process(init: JadxPluginContext, scriptOptions: JadxScriptAllOptions): List<JadxScriptData> {
|
|
||||||
val jadx = init.decompiler
|
|
||||||
val scripts = jadx.args.inputFiles.filter { f -> f.name.endsWith(".jadx.kts") }
|
val scripts = jadx.args.inputFiles.filter { f -> f.name.endsWith(".jadx.kts") }
|
||||||
if (scripts.isEmpty()) {
|
if (scripts.isEmpty()) {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
val scriptingHost = buildScriptingHost(context)
|
||||||
|
val compileConf = buildCompileConf()
|
||||||
val scriptDataList = mutableListOf<JadxScriptData>()
|
val scriptDataList = mutableListOf<JadxScriptData>()
|
||||||
for (scriptFile in scripts) {
|
for (scriptFile in scripts) {
|
||||||
val scriptData = JadxScriptData(jadx, init, scriptOptions, scriptFile)
|
val scriptData = JadxScriptData(jadx, context, scriptOptions, scriptFile)
|
||||||
scriptDataList.add(scriptData)
|
scriptDataList.add(scriptData)
|
||||||
eval(scriptData)
|
eval(scriptingHost, compileConf, scriptData)
|
||||||
}
|
}
|
||||||
return scriptDataList
|
return scriptDataList
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun compile(script: SourceCode): ResultWithDiagnostics<CompiledScript> {
|
private fun eval(
|
||||||
return scriptingHost.compiler(script, compileConf)
|
scriptingHost: BasicJvmScriptingHost,
|
||||||
}
|
compileConf: ScriptCompilationConfiguration,
|
||||||
|
scriptData: JadxScriptData,
|
||||||
private fun eval(scriptData: JadxScriptData) {
|
) {
|
||||||
scriptData.log.debug { "Loading script: ${scriptData.scriptFile.absolutePath}" }
|
scriptData.log.debug { "Loading script: ${scriptData.scriptFile.absolutePath}" }
|
||||||
|
val evalConf = buildEvalConf(scriptData)
|
||||||
val execTime = measureTimeMillis {
|
val execTime = measureTimeMillis {
|
||||||
val result = scriptingHost.eval(
|
val result = scriptingHost.eval(scriptData.scriptFile.toScriptSource(), compileConf, evalConf)
|
||||||
scriptData.scriptFile.toScriptSource(),
|
|
||||||
compileConf,
|
|
||||||
buildEvalConf(scriptData),
|
|
||||||
)
|
|
||||||
processEvalResult(result, scriptData)
|
processEvalResult(result, scriptData)
|
||||||
}
|
}
|
||||||
scriptData.log.debug { "Script '${scriptData.scriptName}' executed in ${execTime.toDuration(DurationUnit.MILLISECONDS)}" }
|
scriptData.log.debug { "Script '${scriptData.scriptName}' executed in ${execTime.toDuration(DurationUnit.MILLISECONDS)}" }
|
||||||
@@ -103,4 +82,21 @@ class ScriptEval {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildScriptingHost(context: JadxPluginContext) = BasicJvmScriptingHost(
|
||||||
|
baseHostConfiguration = ScriptingHostConfiguration {
|
||||||
|
jvm {
|
||||||
|
compilationCache(ScriptCache().build(context))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fun buildCompileConf() = createJvmCompilationConfigurationFromTemplate<JadxScriptTemplate>()
|
||||||
|
|
||||||
|
fun buildEvalConf(scriptData: JadxScriptData): ScriptEvaluationConfiguration {
|
||||||
|
val baseEvalConf = createJvmEvaluationConfigurationFromTemplate<JadxScriptTemplate>()
|
||||||
|
return ScriptEvaluationConfiguration(baseEvalConf) {
|
||||||
|
constructorArgs(scriptData)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user