diff --git a/jadx-with-jadxecute/.github/ISSUE_TEMPLATE/decompilation-error.md b/jadx-with-jadxecute/.github/ISSUE_TEMPLATE/decompilation-error.md
new file mode 100644
index 0000000..ad5b3a3
--- /dev/null
+++ b/jadx-with-jadxecute/.github/ISSUE_TEMPLATE/decompilation-error.md
@@ -0,0 +1,19 @@
+---
+name: Decompilation error
+about: Create a report to help us improve jadx decompiler
+title: "[core]"
+labels: Core, bug
+assignees: ''
+
+---
+
+**Checks before report**
+- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
+- search existing issues by exception message
+
+**Describe error**
+- full name of method or class with error
+- full java stacktrace (no need to copy method fallback code (commented pseudocode))
+- **IMPORTANT!** attach or provide link to apk file (double check apk version)
+
+ **Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
diff --git a/jadx-with-jadxecute/.github/ISSUE_TEMPLATE/feature-request.md b/jadx-with-jadxecute/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100644
index 0000000..7c8d3f0
--- /dev/null
+++ b/jadx-with-jadxecute/.github/ISSUE_TEMPLATE/feature-request.md
@@ -0,0 +1,10 @@
+---
+name: Feature Request
+about: Suggest an idea for jadx
+title: "[feature]"
+labels: new feature
+assignees: ''
+
+---
+
+*Describe your idea:*
diff --git a/jadx-with-jadxecute/.github/dependabot.yml b/jadx-with-jadxecute/.github/dependabot.yml
new file mode 100644
index 0000000..e261d24
--- /dev/null
+++ b/jadx-with-jadxecute/.github/dependabot.yml
@@ -0,0 +1,7 @@
+version: 2
+updates:
+ # Set update schedule for GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/jadx-with-jadxecute/.github/pull_request_template.md b/jadx-with-jadxecute/.github/pull_request_template.md
new file mode 100644
index 0000000..12ce91e
--- /dev/null
+++ b/jadx-with-jadxecute/.github/pull_request_template.md
@@ -0,0 +1,5 @@
+:exclamation: Please review the [guidelines for contributing](https://github.com/skylot/jadx/blob/master/CONTRIBUTING.md#Pull-Request-Process)
+
+### Description
+Please describe your pull request.
+Reference issue it fixes.
diff --git a/jadx-with-jadxecute/.github/workflows/build-artifacts.yml b/jadx-with-jadxecute/.github/workflows/build-artifacts.yml
new file mode 100644
index 0000000..c1323ab
--- /dev/null
+++ b/jadx-with-jadxecute/.github/workflows/build-artifacts.yml
@@ -0,0 +1,88 @@
+name: Build Artifacts
+
+on:
+ push:
+ branches: [ master, build-test ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Set up JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'adopt'
+ java-version: 8
+
+ - name: Set jadx version
+ run: |
+ JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
+ JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
+ echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
+
+ - uses: burrunan/gradle-cache-action@v1
+ name: Build with Gradle
+ env:
+ TERM: dumb
+ with:
+ arguments: clean dist copyExe
+
+ - name: Save bundle artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
+ # Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
+ # Upload unpacked files for now
+ path: build/jadx/**/*
+ if-no-files-found: error
+ retention-days: 30
+
+ - name: Save exe artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
+ path: build/*.exe
+ if-no-files-found: error
+ retention-days: 30
+
+ build-win-bundle:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Set up JDK
+ uses: oracle-actions/setup-java@v1
+ with:
+ release: 17
+
+ - name: Print Java version
+ shell: bash
+ run: java -version
+
+ - name: Set jadx version
+ shell: bash
+ run: |
+ JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
+ JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
+ echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
+
+ - uses: gradle/gradle-build-action@v2
+ name: Build with Gradle
+ env:
+ TERM: dumb
+ with:
+ arguments: clean dist -PbundleJRE=true
+
+ - name: Save exe bundle artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
+ path: jadx-gui/build/*-with-jre-win/*
+ if-no-files-found: error
+ retention-days: 30
diff --git a/jadx-with-jadxecute/.github/workflows/build-test.yml b/jadx-with-jadxecute/.github/workflows/build-test.yml
new file mode 100644
index 0000000..bb3ad56
--- /dev/null
+++ b/jadx-with-jadxecute/.github/workflows/build-test.yml
@@ -0,0 +1,28 @@
+name: Build Test
+
+on:
+ push:
+ branches: [ master, build-test ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Set up JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'adopt'
+ java-version: 8
+
+ - uses: burrunan/gradle-cache-action@v1
+ name: Build with Gradle
+ env:
+ TERM: dumb
+ with:
+ arguments: clean build dist copyExe --warning-mode=all
diff --git a/jadx-with-jadxecute/.github/workflows/codeql-analysis.yml b/jadx-with-jadxecute/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..ed161fa
--- /dev/null
+++ b/jadx-with-jadxecute/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,41 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [master]
+ schedule:
+ - cron: '0 9 * * 5'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: ['java']
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ queries: +security-extended
+ languages: ${{ matrix.language }}
+
+ # Don't build tests in jadx-core also skip tests execution and checkstyle tasks
+ - run: |
+ ./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
diff --git a/jadx-with-jadxecute/.github/workflows/gradle-wrapper-validation.yml b/jadx-with-jadxecute/.github/workflows/gradle-wrapper-validation.yml
new file mode 100644
index 0000000..c6df11b
--- /dev/null
+++ b/jadx-with-jadxecute/.github/workflows/gradle-wrapper-validation.yml
@@ -0,0 +1,10 @@
+name: "Validate Gradle Wrapper"
+on: [push]
+
+jobs:
+ validation:
+ name: "Validation"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: gradle/wrapper-validation-action@v1
diff --git a/jadx-with-jadxecute/README.md b/jadx-with-jadxecute/README.md
new file mode 100644
index 0000000..a6878d7
--- /dev/null
+++ b/jadx-with-jadxecute/README.md
@@ -0,0 +1,169 @@
+
+
+## JADX
+
+[![Build status](https://github.com/skylot/jadx/workflows/Build/badge.svg)](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
+![GitHub contributors](https://img.shields.io/github/contributors/skylot/jadx)
+![GitHub all releases](https://img.shields.io/github/downloads/skylot/jadx/total)
+![GitHub release (latest by SemVer)](https://img.shields.io/github/downloads/skylot/jadx/latest/total)
+![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)
+[![Maven Central](https://img.shields.io/maven-central/v/io.github.skylot/jadx-core)](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
+[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
+
+**jadx** - Dex to Java decompiler
+
+Command line and GUI tools for producing Java source code from Android Dex and Apk files
+
+:exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
+
+**Main features:**
+- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
+- decode `AndroidManifest.xml` and other resources from `resources.arsc`
+- deobfuscator included
+
+**jadx-gui features:**
+- view decompiled code with highlighted syntax
+- jump to declaration
+- find usage
+- full text search
+- smali debugger, check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
+
+Jadx-gui key bindings can be found [here](https://github.com/skylot/jadx/wiki/JADX-GUI-Key-bindings)
+
+See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
+
+
+
+### Download
+- release
+ from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
+- latest [unstable build ![GitHub commits since tagged version (branch)](https://img.shields.io/github/commits-since/skylot/jadx/latest/master)](https://nightly.link/skylot/jadx/workflows/build-artifacts/master)
+
+After download unpack zip file go to `bin` directory and run:
+- `jadx` - command line version
+- `jadx-gui` - UI version
+
+On Windows run `.bat` files with double-click\
+**Note:** ensure you have installed Java 11 or later 64-bit version.
+For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
+
+### Install
+1. Arch linux ![Arch Linux package](https://img.shields.io/archlinux/v/community/any/jadx?label=)
+ ```bash
+ sudo pacman -S jadx
+ ```
+2. macOS ![homebrew version](https://img.shields.io/homebrew/v/jadx?label=)
+ ```bash
+ brew install jadx
+ ```
+3. [Flathub ![Flathub](https://img.shields.io/flathub/v/com.github.skylot.jadx?label=)](https://flathub.org/apps/details/com.github.skylot.jadx)
+ ```bash
+ flatpak install flathub com.github.skylot.jadx
+ ```
+
+### Use jadx as a library
+You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
+
+### Build from source
+JDK 8 or higher must be installed:
+```
+git clone https://github.com/skylot/jadx.git
+cd jadx
+./gradlew dist
+```
+
+(on Windows, use `gradlew.bat` instead of `./gradlew`)
+
+Scripts for run jadx will be placed in `build/jadx/bin`
+and also packed to `build/jadx-.zip`
+
+### Usage
+```
+jadx[-gui] [options] (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)
+options:
+ -d, --output-dir - output directory
+ -ds, --output-dir-src - output directory for sources
+ -dr, --output-dir-res - output directory for resources
+ -r, --no-res - do not decode resources
+ -s, --no-src - do not decompile source code
+ --single-class - decompile a single class, full name, raw or alias
+ --single-class-output - file or dir for write if decompile a single class
+ --output-format - can be 'java' or 'json', default: java
+ -e, --export-gradle - save as android gradle project
+ -j, --threads-count - processing threads count, default: 4
+ -m, --decompilation-mode - code output mode:
+ 'auto' - trying best options (default)
+ 'restructure' - restore code structure (normal java code)
+ 'simple' - simplified instructions (linear, with goto's)
+ 'fallback' - raw instructions without modifications
+ --show-bad-code - show inconsistent code (incorrectly decompiled)
+ --no-imports - disable use of imports, always write entire package name
+ --no-debug-info - disable debug info
+ --add-debug-lines - add comments with debug line numbers if available
+ --no-inline-anonymous - disable anonymous classes inline
+ --no-inline-methods - disable methods inline
+ --no-finally - don't extract finally block
+ --no-replace-consts - don't replace constant value with matching constant field
+ --escape-unicode - escape non latin characters in strings (with \u)
+ --respect-bytecode-access-modifiers - don't change original access modifiers
+ --deobf - activate deobfuscation
+ --deobf-min - min length of name, renamed if shorter, default: 3
+ --deobf-max - max length of name, renamed if longer, default: 64
+ --deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
+ --deobf-cfg-file-mode - set mode for handle deobfuscation map file:
+ 'read' - read if found, don't save (default)
+ 'read-or-save' - read if found, save otherwise (don't overwrite)
+ 'overwrite' - don't read, always save
+ 'ignore' - don't read and don't save
+ --deobf-use-sourcename - use source file name as class name alias
+ --deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
+ --deobf-res-name-source - better name source for resources:
+ 'auto' - automatically select best name (default)
+ 'resources' - use resources names
+ 'code' - use R class fields names
+ --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):
+ 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
+ 'valid' - rename java identifiers to make them valid,
+ 'printable' - remove non-printable chars from identifiers,
+ or single 'none' - to disable all renames
+ or single 'all' - to enable all (default)
+ --fs-case-sensitive - treat filesystem as case sensitive, false by default
+ --cfg - save methods control flow graph to dot file
+ --raw-cfg - save methods control flow graph (use raw instructions)
+ -f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
+ --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
+ --log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
+ -v, --verbose - verbose output (set --log-level to DEBUG)
+ -q, --quiet - turn off output (set --log-level to QUIET)
+ --version - print jadx version
+ -h, --help - print this help
+
+Plugin options (-P=):
+ 1) dex-input: Load .dex and .apk files
+ - dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
+ 2) java-convert: Convert .class, .jar and .aar files to dex
+ - java-convert.mode - convert mode, values: [dx, d8, both], default: both
+ - java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
+
+Examples:
+ jadx -d out classes.dex
+ jadx --rename-flags "none" classes.dex
+ jadx --rename-flags "valid, printable" classes.dex
+ jadx --log-level ERROR app.apk
+ jadx -Pdex-input.verify-checksum=no app.apk
+```
+These options also worked on jadx-gui running from command line and override options from preferences dialog
+
+### Troubleshooting
+Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
+
+### Contributing
+To support this project you can:
+ - Post thoughts about new features/optimizations that important to you
+ - Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
+ - Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
+
+---------------------------------------
+*Licensed under the Apache 2.0 License*
diff --git a/jadx-with-jadxecute/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-with-jadxecute/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
index 67f1056..686482e 100644
--- a/jadx-with-jadxecute/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
+++ b/jadx-with-jadxecute/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
@@ -7,6 +7,8 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -89,6 +91,9 @@ public class JadxCLIArgs {
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
protected boolean inlineMethods = true;
+ @Parameter(names = { "--no-inline-kotlin-lambda" }, description = "disable inline for Kotlin lambdas")
+ protected boolean allowInlineKotlinLambda = true;
+
@Parameter(names = "--no-finally", description = "don't extract finally block")
protected boolean extractFinally = true;
@@ -187,7 +192,7 @@ public class JadxCLIArgs {
@Parameter(
names = { "--log-level" },
description = "set log level, values: quiet, progress, error, warn, info, debug",
- converter = LogHelper.LogLevelConverter.class
+ converter = LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@@ -285,6 +290,7 @@ public JadxArgs toJadxArgs() {
args.setInsertDebugLines(addDebugLines);
args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setInlineMethods(inlineMethods);
+ args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
args.setExtractFinally(extractFinally);
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
@@ -366,6 +372,10 @@ public boolean isInlineMethods() {
return inlineMethods;
}
+ public boolean isAllowInlineKotlinLambda() {
+ return allowInlineKotlinLambda;
+ }
+
public boolean isExtractFinally() {
return extractFinally;
}
@@ -487,67 +497,58 @@ public Set convert(String value) {
}
}
- public static class CommentsLevelConverter implements IStringConverter {
- @Override
- public CommentsLevel convert(String value) {
- try {
- return CommentsLevel.valueOf(value.toUpperCase());
- } catch (Exception e) {
- throw new IllegalArgumentException(
- '\'' + value + "' is unknown comments level, possible values are: "
- + JadxCLIArgs.enumValuesString(CommentsLevel.values()));
- }
+ public static class CommentsLevelConverter extends BaseEnumConverter {
+ public CommentsLevelConverter() {
+ super(CommentsLevel::valueOf, CommentsLevel::values);
}
}
- public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter {
- @Override
- public UseKotlinMethodsForVarNames convert(String value) {
- try {
- return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
- } catch (Exception e) {
- throw new IllegalArgumentException(
- '\'' + value + "' is unknown, possible values are: "
- + JadxCLIArgs.enumValuesString(CommentsLevel.values()));
- }
+ public static class UseKotlinMethodsForVarNamesConverter extends BaseEnumConverter {
+ public UseKotlinMethodsForVarNamesConverter() {
+ super(UseKotlinMethodsForVarNames::valueOf, UseKotlinMethodsForVarNames::values);
}
}
- public static class DeobfuscationMapFileModeConverter implements IStringConverter {
- @Override
- public DeobfuscationMapFileMode convert(String value) {
- try {
- return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
- } catch (Exception e) {
- throw new IllegalArgumentException(
- '\'' + value + "' is unknown, possible values are: "
- + JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values()));
- }
+ public static class DeobfuscationMapFileModeConverter extends BaseEnumConverter {
+ public DeobfuscationMapFileModeConverter() {
+ super(DeobfuscationMapFileMode::valueOf, DeobfuscationMapFileMode::values);
}
}
- public static class ResourceNameSourceConverter implements IStringConverter {
- @Override
- public ResourceNameSource convert(String value) {
- try {
- return ResourceNameSource.valueOf(value.toUpperCase());
- } catch (Exception e) {
- throw new IllegalArgumentException(
- '\'' + value + "' is unknown, possible values are: "
- + JadxCLIArgs.enumValuesString(ResourceNameSource.values()));
- }
+ public static class ResourceNameSourceConverter extends BaseEnumConverter {
+ public ResourceNameSourceConverter() {
+ super(ResourceNameSource::valueOf, ResourceNameSource::values);
+ }
+ }
+
+ public static class DecompilationModeConverter extends BaseEnumConverter {
+ public DecompilationModeConverter() {
+ super(DecompilationMode::valueOf, DecompilationMode::values);
}
}
- public static class DecompilationModeConverter implements IStringConverter {
+ public static class LogLevelConverter extends BaseEnumConverter {
+ public LogLevelConverter() {
+ super(LogHelper.LogLevelEnum::valueOf, LogHelper.LogLevelEnum::values);
+ }
+ }
+
+ public abstract static class BaseEnumConverter> implements IStringConverter {
+ private final Function parse;
+ private final Supplier values;
+
+ public BaseEnumConverter(Function parse, Supplier values) {
+ this.parse = parse;
+ this.values = values;
+ }
+
@Override
- public DecompilationMode convert(String value) {
+ public E convert(String value) {
try {
- return DecompilationMode.valueOf(value.toUpperCase());
+ return parse.apply(stringAsEnumName(value));
} catch (Exception e) {
throw new IllegalArgumentException(
- '\'' + value + "' is unknown, possible values are: "
- + JadxCLIArgs.enumValuesString(DecompilationMode.values()));
+ '\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
}
}
}
@@ -557,4 +558,9 @@ public static String enumValuesString(Enum>[] values) {
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", "));
}
+
+ private static String stringAsEnumName(String value) {
+ // inverse of enumValuesString conversion
+ return value.replace('-', '_').toUpperCase(Locale.ROOT);
+ }
}
diff --git a/jadx-with-jadxecute/jadx-cli/src/main/java/jadx/cli/LogHelper.java b/jadx-with-jadxecute/jadx-cli/src/main/java/jadx/cli/LogHelper.java
index 6ab9b14..44afa17 100644
--- a/jadx-with-jadxecute/jadx-cli/src/main/java/jadx/cli/LogHelper.java
+++ b/jadx-with-jadxecute/jadx-cli/src/main/java/jadx/cli/LogHelper.java
@@ -4,8 +4,6 @@
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory;
-import com.beust.jcommander.IStringConverter;
-
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
@@ -119,18 +117,4 @@ private static boolean isCustomLogConfig() {
}
return false;
}
-
- public static class LogLevelConverter implements IStringConverter {
-
- @Override
- public LogLevelEnum convert(String value) {
- try {
- return LogLevelEnum.valueOf(value.toUpperCase());
- } catch (Exception e) {
- throw new IllegalArgumentException(
- '\'' + value + "' is unknown log level, possible values are "
- + JadxCLIArgs.enumValuesString(LogLevelEnum.values()));
- }
- }
- }
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/JadxArgs.java
index aa94c97..8f32e8a 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/JadxArgs.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/JadxArgs.java
@@ -53,6 +53,7 @@ public class JadxArgs {
private boolean extractFinally = true;
private boolean inlineAnonymousClasses = true;
private boolean inlineMethods = true;
+ private boolean allowInlineKotlinLambda = true;
private boolean skipResources = false;
private boolean skipSources = false;
@@ -263,6 +264,14 @@ public void setInlineMethods(boolean inlineMethods) {
this.inlineMethods = inlineMethods;
}
+ public boolean isAllowInlineKotlinLambda() {
+ return allowInlineKotlinLambda;
+ }
+
+ public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
+ this.allowInlineKotlinLambda = allowInlineKotlinLambda;
+ }
+
public boolean isExtractFinally() {
return extractFinally;
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/impl/AnnotatedCodeInfo.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/impl/AnnotatedCodeInfo.java
index bdd6359..1b65191 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/impl/AnnotatedCodeInfo.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/impl/AnnotatedCodeInfo.java
@@ -9,7 +9,7 @@
public class AnnotatedCodeInfo implements ICodeInfo {
- private String code;
+ private final String code;
private final ICodeMetadata metadata;
public AnnotatedCodeInfo(String code, Map lineMapping, Map annotations) {
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/impl/SimpleCodeInfo.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/impl/SimpleCodeInfo.java
index 8d4558a..4422cf8 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/impl/SimpleCodeInfo.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/api/impl/SimpleCodeInfo.java
@@ -5,7 +5,7 @@
public class SimpleCodeInfo implements ICodeInfo {
- private String code;
+ private final String code;
public SimpleCodeInfo(String code) {
this.code = code;
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java
index e5250e8..80c0ecd 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java
@@ -17,6 +17,7 @@
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
+import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
@@ -210,7 +211,31 @@ private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throw
}
}
+ protected void staticField(ICodeWriter code, FieldInfo field) throws CodegenException {
+ FieldNode fieldNode = root.resolveField(field);
+ if (fieldNode != null
+ && fieldNode.contains(AFlag.INLINE_INSTANCE_FIELD)
+ && fieldNode.getParentClass().contains(AType.ANONYMOUS_CLASS)) {
+ FieldInitInsnAttr initInsnAttr = fieldNode.get(AType.FIELD_INIT_INSN);
+ if (initInsnAttr != null) {
+ InsnNode insn = initInsnAttr.getInsn();
+ if (insn instanceof ConstructorInsn) {
+ fieldNode.add(AFlag.DONT_GENERATE);
+ inlineAnonymousConstructor(code, fieldNode.getParentClass(), (ConstructorInsn) insn);
+ return;
+ }
+ }
+ }
+ makeStaticFieldAccess(code, field, fieldNode, mgen.getClassGen());
+ }
+
public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, ClassGen clsGen) {
+ FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
+ makeStaticFieldAccess(code, field, fieldNode, clsGen);
+ }
+
+ private static void makeStaticFieldAccess(ICodeWriter code,
+ FieldInfo field, @Nullable FieldNode fieldNode, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
// TODO
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
@@ -221,7 +246,6 @@ public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, Clas
}
code.add('.');
}
- FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
@@ -232,10 +256,6 @@ public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, Clas
}
}
- protected void staticField(ICodeWriter code, FieldInfo field) {
- makeStaticFieldAccess(code, field, mgen.getClassGen());
- }
-
public void useClass(ICodeWriter code, ArgType type) {
mgen.getClassGen().useClass(code, type);
}
@@ -695,9 +715,7 @@ private void filledNewArray(FilledNewArrayNode insn, ICodeWriter code) throws Co
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
ClassNode cls = mth.root().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) {
- cls.ensureProcessed();
inlineAnonymousConstructor(code, cls, insn);
- mth.getParentClass().addInlinedClass(cls);
return;
}
if (insn.isSelf()) {
@@ -748,6 +766,7 @@ private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws Code
}
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
+ cls.ensureProcessed();
if (this.mth.getParentClass() == cls) {
cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE);
@@ -786,6 +805,8 @@ private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, Constru
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
classGen.setOuterNameGen(mgen.getNameGen());
classGen.addClassBody(code, true);
+
+ mth.getParentClass().addInlinedClass(cls);
}
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
@@ -993,9 +1014,10 @@ private void makeInlinedLambdaMethod(ICodeWriter code, InvokeCustomNode customNo
// force set external arg names into call method args
int extArgsCount = customNode.getArgsCount();
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
+ int callArg = 0;
for (int i = startArg; i < extArgsCount; i++) {
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
- RegisterArg callRegArg = callArgs.get(i);
+ RegisterArg callRegArg = callArgs.get(callArg++);
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
}
code.add(" -> {");
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java
index 03bd5e7..6cb28ea 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java
@@ -270,7 +270,7 @@ public void makeSwitch(SwitchRegion sw, ICodeWriter code) throws CodegenExceptio
code.startLine('}');
}
- private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) {
+ private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java
index 113888f..1815774 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java
@@ -37,7 +37,9 @@ public enum AFlag {
SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
+
ANONYMOUS_CONSTRUCTOR,
+ INLINE_INSTANCE_FIELD,
THIS,
SUPER,
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassAttr.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassAttr.java
index 0f34bf1..ad4a08a 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassAttr.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassAttr.java
@@ -7,12 +7,19 @@
public class AnonymousClassAttr extends PinnedAttribute {
+ public enum InlineType {
+ CONSTRUCTOR,
+ INSTANCE_FIELD,
+ }
+
private final ClassNode outerCls;
private final ArgType baseType;
+ private final InlineType inlineType;
- public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) {
+ public AnonymousClassAttr(ClassNode outerCls, ArgType baseType, InlineType inlineType) {
this.outerCls = outerCls;
this.baseType = baseType;
+ this.inlineType = inlineType;
}
public ClassNode getOuterCls() {
@@ -23,6 +30,10 @@ public ArgType getBaseType() {
return baseType;
}
+ public InlineType getInlineType() {
+ return inlineType;
+ }
+
@Override
public AType getAttrType() {
return AType.ANONYMOUS_CLASS;
@@ -30,6 +41,6 @@ public AType getAttrType() {
@Override
public String toString() {
- return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
+ return "AnonymousClass{" + outerCls + ", base: " + baseType + ", inline type: " + inlineType + '}';
}
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java
index 4a7eb8b..864d95b 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java
@@ -1,5 +1,7 @@
package jadx.core.dex.info;
+import org.intellij.lang.annotations.MagicConstant;
+
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -20,10 +22,21 @@ public AccessInfo(int accessFlags, AFType type) {
this.type = type;
}
+ @MagicConstant(valuesFromClass = AccessFlags.class)
public boolean containsFlag(int flag) {
return (accFlags & flag) != 0;
}
+ @MagicConstant(valuesFromClass = AccessFlags.class)
+ public boolean containsFlags(int... flags) {
+ for (int flag : flags) {
+ if ((accFlags & flag) == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public AccessInfo remove(int flag) {
if (containsFlag(flag)) {
return new AccessInfo(accFlags & ~flag, type);
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java
index 7968bf9..ed6b224 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java
@@ -82,14 +82,22 @@ public void processConstFields(ClassNode cls, List staticFields) {
return;
}
for (FieldNode f : staticFields) {
- AccessInfo accFlags = f.getAccessFlags();
- if (accFlags.isStatic() && accFlags.isFinal()) {
- EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
- if (constVal != null && constVal.getValue() != null) {
- addConstField(cls, f, constVal.getValue(), accFlags.isPublic());
- }
+ Object value = getFieldConstValue(f);
+ if (value != null) {
+ addConstField(cls, f, value, f.getAccessFlags().isPublic());
+ }
+ }
+ }
+
+ public static @Nullable Object getFieldConstValue(FieldNode fld) {
+ AccessInfo accFlags = fld.getAccessFlags();
+ if (accFlags.isStatic() && accFlags.isFinal()) {
+ EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
+ if (constVal != null) {
+ return constVal.getValue();
}
}
+ return null;
}
public void removeForClass(ClassNode cls) {
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java
index ccb6e83..8b18416 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java
@@ -264,6 +264,13 @@ public boolean isSameConst(InsnArg other) {
return false;
}
+ public boolean isSameVar(RegisterArg arg) {
+ if (isRegister()) {
+ return ((RegisterArg) this).sameRegAndSVar(arg);
+ }
+ return false;
+ }
+
protected final T copyCommonParams(T copy) {
copy.copyAttributesFrom(this);
copy.setParentInsn(parentInsn);
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
index 6a981dd..5d41304 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
@@ -13,8 +13,6 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import jadx.api.DecompilationMode;
import jadx.api.ICodeCache;
@@ -56,8 +54,6 @@
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable {
- private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
-
private final RootNode root;
private final IClassData clsData;
@@ -174,10 +170,10 @@ private ArgType checkSuperType(IClassData cls) {
return ArgType.object(superType);
}
- public void updateGenericClsData(ArgType superClass, List interfaces, List generics) {
+ public void updateGenericClsData(List generics, ArgType superClass, List interfaces) {
+ this.generics = generics;
this.superClass = superClass;
this.interfaces = interfaces;
- this.generics = generics;
}
private static void processAttributes(ClassNode cls) {
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
index c641ea9..c176bf7 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
@@ -43,6 +43,7 @@
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.IResParser;
+import jadx.core.xmlgen.ManifestAttributes;
import jadx.core.xmlgen.ResDecoder;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
@@ -174,12 +175,18 @@ public void loadResources(List resources) {
if (parser != null) {
processResources(parser.getResStorage());
updateObfuscatedFiles(parser, resources);
+ updateManifestAttribMap(parser);
}
} catch (Exception e) {
LOG.error("Failed to parse '.arsc' file", e);
}
}
+ private void updateManifestAttribMap(IResParser parser) {
+ ManifestAttributes manifestAttributes = ManifestAttributes.getInstance();
+ manifestAttributes.updateAttributes(parser);
+ }
+
private @Nullable ResourceFile getResourceFile(List resources) {
for (ResourceFile rf : resources) {
if (rf.getType() == ResourceType.ARSC) {
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java
index ab1d3a5..564584d 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java
@@ -125,17 +125,30 @@ private static boolean forbidNullInlines(SSAVar sVar) {
int k = 0;
for (RegisterArg useArg : useList) {
InsnNode insn = useArg.getParentInsn();
- if (insn == null) {
- continue;
- }
- if (!canUseNull(insn, useArg)) {
- useArg.add(AFlag.DONT_INLINE_CONST);
+ if (insn != null && forbidNullArgInline(insn, useArg)) {
k++;
}
}
return k == useList.size();
}
+ private static boolean forbidNullArgInline(InsnNode insn, RegisterArg useArg) {
+ switch (insn.getType()) {
+ case MOVE:
+ case CAST:
+ case CHECK_CAST:
+ // result is null, chain checks
+ return forbidNullInlines(insn.getResult().getSVar());
+
+ default:
+ if (!canUseNull(insn, useArg)) {
+ useArg.add(AFlag.DONT_INLINE_CONST);
+ return true;
+ }
+ return false;
+ }
+ }
+
private static boolean canUseNull(InsnNode insn, RegisterArg useArg) {
switch (insn.getType()) {
case INVOKE:
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java
index e83fd44..6ba3fbb 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java
@@ -384,8 +384,14 @@ private static List getConstructorsList(ClassNode cls) {
return list;
}
- private static void addFieldInitAttr(MethodNode mth, FieldNode field, InsnNode insn) {
- InsnNode assignInsn = InsnNode.wrapArg(insn.getArg(0));
+ private static void addFieldInitAttr(MethodNode mth, FieldNode field, IndexInsnNode putInsn) {
+ InsnNode assignInsn;
+ InsnArg fldArg = putInsn.getArg(0);
+ if (fldArg.isInsnWrap()) {
+ assignInsn = ((InsnWrapArg) fldArg).getWrapInsn();
+ } else {
+ assignInsn = InsnNode.wrapArg(fldArg);
+ }
field.addAttr(new FieldInitInsnAttr(mth, assignInsn));
}
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java
index 46a012e..6df5e76 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java
@@ -80,13 +80,47 @@ private static MethodInlineAttr inlineMth(MethodNode mth) {
return addInlineAttr(mth, insn);
}
if (insnsCount == 2 && insns.get(1).getType() == InsnType.RETURN) {
- // synthetic field setter
- return addInlineAttr(mth, insns.get(0));
+ InsnNode firstInsn = insns.get(0);
+ InsnNode retInsn = insns.get(1);
+ if (retInsn.getArgsCount() == 0
+ || isSyntheticAccessPattern(mth, firstInsn, retInsn)) {
+ return addInlineAttr(mth, firstInsn);
+ }
}
// TODO: inline field arithmetics. Disabled tests: TestAnonymousClass3a and TestAnonymousClass5
return null;
}
+ private static boolean isSyntheticAccessPattern(MethodNode mth, InsnNode firstInsn, InsnNode retInsn) {
+ List mthRegs = mth.getArgRegs();
+ switch (firstInsn.getType()) {
+ case IGET:
+ return mthRegs.size() == 1
+ && retInsn.getArg(0).isSameVar(firstInsn.getResult())
+ && firstInsn.getArg(0).isSameVar(mthRegs.get(0));
+ case SGET:
+ return mthRegs.size() == 0
+ && retInsn.getArg(0).isSameVar(firstInsn.getResult());
+
+ case IPUT:
+ return mthRegs.size() == 2
+ && retInsn.getArg(0).isSameVar(mthRegs.get(1))
+ && firstInsn.getArg(0).isSameVar(mthRegs.get(1))
+ && firstInsn.getArg(1).isSameVar(mthRegs.get(0));
+ case SPUT:
+ return mthRegs.size() == 1
+ && retInsn.getArg(0).isSameVar(mthRegs.get(0))
+ && firstInsn.getArg(0).isSameVar(mthRegs.get(0));
+
+ case INVOKE:
+ return mthRegs.size() >= 1
+ && firstInsn.getArg(0).isSameVar(mthRegs.get(0))
+ && retInsn.getArg(0).isSameVar(firstInsn.getResult());
+ default:
+ return false;
+ }
+ }
+
private static MethodInlineAttr addInlineAttr(MethodNode mth, InsnNode insn) {
if (!fixVisibilityOfInlineCode(mth, insn)) {
return null;
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java
index 1b10ac1..cb5350f 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java
@@ -10,9 +10,11 @@
import org.jetbrains.annotations.Nullable;
+import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
+import jadx.core.dex.attributes.nodes.AnonymousClassAttr.InlineType;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
@@ -30,6 +32,7 @@
UsageInfoVisitor.class
}
)
+@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public class ProcessAnonymous extends AbstractVisitor {
private boolean inlineAnonymousClasses;
@@ -64,17 +67,26 @@ private static void markAnonymousClass(ClassNode cls) {
if (!canBeAnonymous(cls)) {
return;
}
- MethodNode anonymousConstructor = checkUsage(cls);
+ MethodNode anonymousConstructor = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
if (anonymousConstructor == null) {
return;
}
+ InlineType inlineType = checkUsage(cls, anonymousConstructor);
+ if (inlineType == null) {
+ return;
+ }
ArgType baseType = getBaseType(cls);
if (baseType == null) {
return;
}
- ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
+ ClassNode outerCls;
+ if (inlineType == InlineType.INSTANCE_FIELD) {
+ outerCls = cls.getUseInMth().get(0).getParentClass();
+ } else {
+ outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
+ }
outerCls.addInlinedClass(cls);
- cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
+ cls.addAttr(new AnonymousClassAttr(outerCls, baseType, inlineType));
cls.add(AFlag.DONT_GENERATE);
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
@@ -202,14 +214,11 @@ private static boolean canBeAnonymous(ClassNode cls) {
* Checks:
* - class have only one constructor which used only once (allow common code for field init)
* - methods or fields not used outside (allow only nested inner classes with synthetic usage)
+ * - if constructor used only in class init check if possible inline by instance field
*
- * @return anonymous constructor method
+ * @return decided inline type
*/
- private static MethodNode checkUsage(ClassNode cls) {
- MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
- if (ctr == null) {
- return null;
- }
+ private static InlineType checkUsage(ClassNode cls, MethodNode ctr) {
if (ctr.getUseIn().size() != 1) {
// check if used in common field init in all constructors
if (!checkForCommonFieldInit(ctr)) {
@@ -219,6 +228,9 @@ private static MethodNode checkUsage(ClassNode cls) {
MethodNode ctrUseMth = ctr.getUseIn().get(0);
ClassNode ctrUseCls = ctrUseMth.getParentClass();
if (ctrUseCls.equals(cls)) {
+ if (checkForInstanceFieldUsage(cls, ctr)) {
+ return InlineType.INSTANCE_FIELD;
+ }
// exclude self usage
return null;
}
@@ -226,6 +238,20 @@ private static MethodNode checkUsage(ClassNode cls) {
// exclude usage inside inner classes
return null;
}
+ if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
+ return null;
+ }
+ for (FieldNode field : cls.getFields()) {
+ for (MethodNode useMth : field.getUseIn()) {
+ if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
+ return null;
+ }
+ }
+ }
+ return InlineType.CONSTRUCTOR;
+ }
+
+ private static boolean checkMethodsUsage(ClassNode cls, MethodNode ctr, MethodNode ctrUseMth) {
for (MethodNode mth : cls.getMethods()) {
if (mth == ctr) {
continue;
@@ -235,18 +261,46 @@ private static MethodNode checkUsage(ClassNode cls) {
continue;
}
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
- return null;
+ return false;
}
}
}
+ return true;
+ }
+
+ private static boolean checkForInstanceFieldUsage(ClassNode cls, MethodNode ctr) {
+ MethodNode ctrUseMth = ctr.getUseIn().get(0);
+ if (!ctrUseMth.getMethodInfo().isClassInit()) {
+ return false;
+ }
+ FieldNode instFld = ListUtils.filterOnlyOne(cls.getFields(),
+ f -> f.getAccessFlags().containsFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
+ && f.getFieldInfo().getType().equals(cls.getClassInfo().getType()));
+ if (instFld == null) {
+ return false;
+ }
+ List instFldUseIn = instFld.getUseIn();
+ if (instFldUseIn.size() != 2
+ || !instFldUseIn.contains(ctrUseMth) // initialized in class init
+ || !instFldUseIn.containsAll(cls.getUseInMth()) // class used only with this field
+ ) {
+ return false;
+ }
+ if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
+ return false;
+ }
for (FieldNode field : cls.getFields()) {
+ if (field == instFld) {
+ continue;
+ }
for (MethodNode useMth : field.getUseIn()) {
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
- return null;
+ return false;
}
}
}
- return ctr;
+ instFld.add(AFlag.INLINE_INSTANCE_FIELD);
+ return true;
}
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
@@ -297,6 +351,13 @@ private static ArgType getBaseType(ClassNode cls) {
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
return superCls;
}
+ if (cls.root().getArgs().isAllowInlineKotlinLambda()) {
+ if (superCls.getObject().equals("kotlin.jvm.internal.Lambda")) {
+ // Inline such class with have different semantic: missing 'arity' property.
+ // For now, it is unclear how it may affect code execution.
+ return interfaceType;
+ }
+ }
return null;
}
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java
index 6dd492d..3944ee8 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java
@@ -2,8 +2,12 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
+
+import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
@@ -18,7 +22,6 @@
import jadx.core.utils.exceptions.JadxException;
public class SignatureProcessor extends AbstractVisitor {
-
private RootNode root;
@Override
@@ -55,12 +58,54 @@ private void parseClassSignature(ClassNode cls) {
break;
}
}
- cls.updateGenericClsData(superClass, interfaces, generics);
+ generics = fixTypeParamDeclarations(cls, generics, superClass, interfaces);
+ cls.updateGenericClsData(generics, superClass, interfaces);
} catch (Exception e) {
cls.addWarnComment("Failed to parse class signature: " + sp.getSignature(), e);
}
}
+ /**
+ * Add missing type parameters from super type and interfaces to make code compilable
+ */
+ private static List fixTypeParamDeclarations(ClassNode cls,
+ List generics, ArgType superClass, List interfaces) {
+ if (interfaces.isEmpty() && superClass.equals(ArgType.OBJECT)) {
+ return generics;
+ }
+ Set typeParams = new HashSet<>();
+ superClass.visitTypes(t -> addGenericType(typeParams, t));
+ interfaces.forEach(i -> i.visitTypes(t -> addGenericType(typeParams, t)));
+ if (typeParams.isEmpty()) {
+ return generics;
+ }
+ List knownTypeParams;
+ if (cls.isInner()) {
+ knownTypeParams = new ArrayList<>(generics);
+ cls.visitParentClasses(p -> knownTypeParams.addAll(p.getGenericTypeParameters()));
+ } else {
+ knownTypeParams = generics;
+ }
+ for (ArgType declTypeParam : knownTypeParams) {
+ typeParams.remove(declTypeParam.getObject());
+ }
+ if (typeParams.isEmpty()) {
+ return generics;
+ }
+ cls.addInfoComment("Add missing generic type declarations: " + typeParams);
+ List fixedGenerics = new ArrayList<>(generics.size() + typeParams.size());
+ fixedGenerics.addAll(generics);
+ typeParams.stream().sorted().map(ArgType::genericType).forEach(fixedGenerics::add);
+ return fixedGenerics;
+ }
+
+ private static @Nullable Object addGenericType(Set usedTypeParameters, ArgType t) {
+ if (t.isGenericType()) {
+ usedTypeParameters.add(t.getObject());
+ }
+ return null;
+ }
+
private ArgType validateClsType(ClassNode cls, ArgType candidateType, ArgType currentType) {
if (!candidateType.isObject()) {
cls.addWarnComment("Incorrect class signature, class is not object: " + SignatureParser.getSignature(cls));
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java
index 367a13e..496abee 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java
@@ -9,6 +9,8 @@
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType;
+import jadx.core.dex.instructions.InvokeCustomNode;
+import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.Named;
@@ -123,6 +125,9 @@ private static void checkInline(MethodNode mth, BlockNode block, InsnList insnLi
return;
}
}
+ if (!checkLambdaInline(arg, assignInsn)) {
+ return;
+ }
int assignPos = insnList.getIndex(assignInsn);
if (assignPos != -1) {
@@ -145,6 +150,26 @@ && canMoveBetweenBlocks(mth, assignInsn, assignBlock, block, argsInfo.getInsn())
}
}
+ /**
+ * Forbid inline lambda into invoke as an instance arg, i.e. this will not compile:
+ * {@code () -> { ... }.apply(); }
+ */
+ private static boolean checkLambdaInline(RegisterArg arg, InsnNode assignInsn) {
+ if (assignInsn.getType() == InsnType.INVOKE && assignInsn instanceof InvokeCustomNode) {
+ for (RegisterArg useArg : arg.getSVar().getUseList()) {
+ InsnNode parentInsn = useArg.getParentInsn();
+ if (parentInsn != null && parentInsn.getType() == InsnType.INVOKE) {
+ InvokeNode invokeNode = (InvokeNode) parentInsn;
+ InsnArg instArg = invokeNode.getInstanceArg();
+ if (instArg != null && instArg == useArg) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
private static boolean varWithSameNameExists(MethodNode mth, SSAVar inlineVar) {
for (SSAVar ssaVar : mth.getSVars()) {
if (ssaVar == inlineVar || ssaVar.getCodeVar() == inlineVar.getCodeVar()) {
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java
index 37fc056..afbe2bb 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java
@@ -325,7 +325,7 @@ private ArgType replaceAnonymousType(ConstructorInsn ctr) {
ClassNode ctrCls = root.resolveClass(ctr.getClassType());
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS);
- if (baseTypeAttr != null) {
+ if (baseTypeAttr != null && baseTypeAttr.getInlineType() == AnonymousClassAttr.InlineType.CONSTRUCTOR) {
return baseTypeAttr.getBaseType();
}
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java
index 3850d34..d5bb197 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java
@@ -6,6 +6,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
+import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
@@ -29,6 +30,8 @@ public class ExportGradleProject {
private static final Logger LOG = LoggerFactory.getLogger(ExportGradleProject.class);
+ private static final Pattern ILLEGAL_GRADLE_CHARS = Pattern.compile("[/\\\\:>\"?*|]");
+
private static final Set IGNORE_CLS_NAMES = new HashSet<>(Arrays.asList(
"R",
"BuildConfig"));
@@ -72,7 +75,7 @@ private void saveProjectBuildGradle() throws IOException {
private void saveSettingsGradle() throws IOException {
TemplateFile tmpl = TemplateFile.fromResources("/export/settings.gradle.tmpl");
- tmpl.add("applicationName", applicationParams.getApplicationName());
+ tmpl.add("applicationName", ILLEGAL_GRADLE_CHARS.matcher(applicationParams.getApplicationName()).replaceAll(""));
tmpl.save(new File(projectDir, "settings.gradle"));
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java
index 26e076c..522888c 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java
@@ -139,11 +139,13 @@ private static List> buildFallback(List classes) {
}
private void dumpBatchesStats(List classes, List> result, List deps) {
+ int clsInBatches = result.stream().mapToInt(List::size).sum();
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
int maxSingleDeps = classes.stream().mapToInt(JavaClass::getTotalDepsCount).max().orElse(-1);
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
LOG.info("Batches stats:"
+ "\n input classes: " + classes.size()
+ + ",\n classes in batches: " + clsInBatches
+ ",\n batches: " + result.size()
+ ",\n average batch size: " + String.format("%.2f", avg)
+ ",\n max single deps count: " + maxSingleDeps
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java
index 3377f61..41d9ad1 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java
@@ -26,7 +26,7 @@ protected String[] parseStringPoolNoType() throws IOException {
int[] stringsOffset = is.readInt32Array(stringCount);
int[] stylesOffset = is.readInt32Array(styleCount);
- is.checkPos(start + stringsStart, "Expected strings start");
+ is.skipToPos(start + stringsStart, "Expected strings start");
String[] strings = new String[stringCount];
byte[] strData = is.readInt8Array((int) (chunkEnd - is.getPos()));
if ((flags & UTF8_FLAG) != 0) {
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java
index 283d1a0..ff03141 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java
@@ -17,6 +17,9 @@
import org.w3c.dom.NodeList;
import jadx.core.utils.exceptions.JadxRuntimeException;
+import jadx.core.xmlgen.entry.RawNamedValue;
+import jadx.core.xmlgen.entry.ResourceEntry;
+import jadx.core.xmlgen.entry.ValuesParser;
public class ManifestAttributes {
private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class);
@@ -52,6 +55,8 @@ public String toString() {
private final Map attrMap = new HashMap<>();
+ private final Map appAttrMap = new HashMap<>();
+
private static ManifestAttributes instance;
public static ManifestAttributes getInstance() {
@@ -168,7 +173,10 @@ private void parseValues(String name, NodeList nodeList) {
public String decode(String attrName, long value) {
MAttr attr = attrMap.get(attrName);
if (attr == null) {
- return null;
+ attr = appAttrMap.get(attrName);
+ if (attr == null) {
+ return null;
+ }
}
if (attr.getType() == MAttrType.ENUM) {
return attr.getValues().get(value);
@@ -190,4 +198,32 @@ public String decode(String attrName, long value) {
}
return null;
}
+
+ public void updateAttributes(IResParser parser) {
+ appAttrMap.clear();
+
+ ResourceStorage resStorage = parser.getResStorage();
+ ValuesParser vp = new ValuesParser(parser.getStrings(), resStorage.getResourcesNames());
+
+ for (ResourceEntry ri : resStorage.getResources()) {
+ if (ri.getTypeName().equals("attr") && ri.getNamedValues().size() > 1) {
+ RawNamedValue first = ri.getNamedValues().get(0);
+ MAttrType attrTyp;
+ if (first.getRawValue().getData() == ValuesParser.ATTR_TYPE_FLAGS) {
+ attrTyp = MAttrType.FLAG;
+ } else if (first.getRawValue().getData() == ValuesParser.ATTR_TYPE_ENUM || first.getRawValue().getData() == 65600) {
+ attrTyp = MAttrType.ENUM;
+ } else {
+ continue;
+ }
+ MAttr attr = new MAttr(attrTyp);
+ for (int i = 1; i < ri.getNamedValues().size(); i++) {
+ RawNamedValue rv = ri.getNamedValues().get(i);
+ String value = vp.decodeNameRef(rv.getNameRef());
+ attr.getValues().put((long) rv.getRawValue().getData(), value.startsWith("id.") ? value.substring(3) : value);
+ }
+ appAttrMap.put(ri.getKeyName(), attr);
+ }
+ }
+ }
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ParserConstants.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ParserConstants.java
index 49fe38c..6480208 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ParserConstants.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ParserConstants.java
@@ -33,7 +33,8 @@ protected ParserConstants() {
protected static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202; // 514
protected static final int RES_TABLE_TYPE_LIBRARY = 0x0203; // 515
protected static final int RES_TABLE_TYPE_OVERLAY = 0x0204; // 516
- protected static final int RES_TABLE_TYPE_STAGED_ALIAS = 0x0206; // 517
+ protected static final int RES_TABLE_TYPE_OVERLAY_POLICY = 0x0205; // 517
+ protected static final int RES_TABLE_TYPE_STAGED_ALIAS = 0x0206; // 518
/**
* Type constants
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java
index a50d45d..88d0d0d 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java
@@ -127,6 +127,10 @@ public void checkPos(long expectedOffset, String error) throws IOException {
public void skipToPos(long expectedOffset, String error) throws IOException {
long pos = getPos();
+ if (pos > expectedOffset) {
+ throw new IOException(error + ", expected offset not reachable: 0x" + Long.toHexString(expectedOffset)
+ + ", actual: 0x" + Long.toHexString(getPos()));
+ }
if (pos < expectedOffset) {
skip(expectedOffset - pos);
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java
index 9dcae36..751ae65 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java
@@ -4,7 +4,9 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
@@ -168,11 +170,14 @@ private PackageChunk parsePackage() throws IOException {
parseLibraryTypeChunk(chunkStart);
break;
case RES_TABLE_TYPE_OVERLAY: // 0x0204
+ parseOverlayTypeChunk(chunkStart);
+ break;
+ case RES_TABLE_TYPE_OVERLAY_POLICY: // 0x0205
throw new IOException(
- String.format("Encountered unsupported chunk type TYPE_OVERLAY at offset 0x%x ", chunkStart));
+ String.format("Encountered unsupported chunk type RES_TABLE_TYPE_OVERLAY_POLICY at offset 0x%x ", chunkStart));
case RES_TABLE_TYPE_STAGED_ALIAS: // 0x0206
- throw new IOException(
- String.format("Encountered unsupported chunk type TYPE_STAGED_ALIAS at offset 0x%x ", chunkStart));
+ parseStagedAliasChunk(chunkStart);
+ break;
default:
LOG.warn("Unknown chunk type {} encountered at offset {}", type, chunkStart);
}
@@ -242,6 +247,13 @@ private void parseLibraryTypeChunk(long chunkStart) throws IOException {
}
}
+ /**
+ * Parse an ResTable_type
(except for the 2 bytes uint16_t
+ * from ResChunk_header
).
+ *
+ * @see ResourceTypes.h
+ */
private void parseTypeChunk(long start, PackageChunk pkg) throws IOException {
/* int headerSize = */
is.readInt16();
@@ -249,9 +261,13 @@ private void parseTypeChunk(long start, PackageChunk pkg) throws IOException {
long chunkSize = is.readUInt32();
long chunkEnd = start + chunkSize;
+ // The type identifier this chunk is holding. Type IDs start at 1 (corresponding
+ // to the value of the type bits in a resource identifier). 0 is invalid.
int id = is.readInt8();
- is.checkInt8(0, "type chunk, res0");
- is.checkInt16(0, "type chunk, res1");
+ int flags = is.readInt8(); // 0 or 1
+ boolean flagSparse = (flags == 1);
+
+ is.checkInt16(0, "type chunk, reserved");
int entryCount = is.readInt32();
long entriesStart = start + is.readInt32();
@@ -262,21 +278,30 @@ private void parseTypeChunk(long start, PackageChunk pkg) throws IOException {
LOG.warn("Invalid config flags detected: {}{}", typeName, config.getQualifiers());
}
- int[] entryIndexes = new int[entryCount];
- for (int i = 0; i < entryCount; i++) {
- entryIndexes[i] = is.readInt32();
+ Map entryOffsetMap = new LinkedHashMap<>(entryCount);
+ if (flagSparse) {
+ for (int i = 0; i < entryCount; i++) {
+ entryOffsetMap.put(is.readInt16(), is.readInt16());
+ }
+ } else {
+ for (int i = 0; i < entryCount; i++) {
+ entryOffsetMap.put(i, is.readInt32());
+ }
}
is.checkPos(entriesStart, "Expected entry start");
- for (int i = 0; i < entryCount; i++) {
- if (entryIndexes[i] != NO_ENTRY) {
+ int processed = 0;
+ for (int index : entryOffsetMap.keySet()) {
+ int offset = entryOffsetMap.get(index);
+ if (offset != NO_ENTRY) {
if (is.getPos() >= chunkEnd) {
// Certain resource obfuscated apps like com.facebook.orca have more entries defined
// than actually fit into the chunk size -> ignore the remaining entries
- LOG.warn("End of chunk reached - ignoring remaining {} entries", entryCount - i);
+ LOG.warn("End of chunk reached - ignoring remaining {} entries", entryCount - processed);
break;
}
- parseEntry(pkg, id, i, config.getQualifiers());
+ parseEntry(pkg, id, index, config.getQualifiers());
}
+ processed++;
}
if (chunkEnd > is.getPos()) {
// Skip remaining unknown data in this chunk (e.g. type 8 entries")
@@ -287,6 +312,36 @@ private void parseTypeChunk(long start, PackageChunk pkg) throws IOException {
}
}
+ private void parseOverlayTypeChunk(long chunkStart) throws IOException {
+ LOG.trace("parsing overlay type chunk starting at offset {}", chunkStart);
+ // read ResTable_overlayable_header
+ /* headerSize = */ is.readInt16(); // usually 1032 bytes
+ int chunkSize = is.readInt32(); // e.g. 1056 bytes
+ long expectedEndPos = chunkStart + chunkSize;
+ String name = is.readString16Fixed(256); // 512 bytes
+ String actor = is.readString16Fixed(256); // 512 bytes
+ LOG.trace("Overlay header data: name={} actor={}", name, actor);
+ // skip: ResTable_overlayable_policy_header + ResTable_ref * x
+ is.skipToPos(expectedEndPos, "overlay chunk end");
+ }
+
+ private void parseStagedAliasChunk(long chunkStart) throws IOException {
+ // read ResTable_staged_alias_header
+ LOG.trace("parsing staged alias chunk starting at offset {}", chunkStart);
+ /* headerSize = */ is.readInt16();
+ int chunkSize = is.readInt32();
+ long expectedEndPos = chunkStart + chunkSize;
+ int count = is.readInt32();
+
+ for (int i = 0; i < count; i++) {
+ // read ResTable_staged_alias_entry
+ int stagedResId = is.readInt32();
+ int finalizedResId = is.readInt32();
+ LOG.debug("Staged alias: stagedResId {} finalizedResId {}", stagedResId, finalizedResId);
+ }
+ is.skipToPos(expectedEndPos, "staged alias chunk end");
+ }
+
private void parseEntry(PackageChunk pkg, int typeId, int entryId, String config) throws IOException {
int size = is.readInt16();
int flags = is.readInt16();
diff --git a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java
index 924c34e..5d8556f 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java
+++ b/jadx-with-jadxecute/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java
@@ -109,10 +109,10 @@ private void addValue(ICodeWriter cw, ResourceEntry ri) {
addSimpleValue(cw, ri.getTypeName(), ri.getTypeName(), "name", ri.getKeyName(), valueStr);
} else {
cw.startLine();
- cw.add('<').add(ri.getTypeName()).add(' ');
+ cw.add('<').add(ri.getTypeName()).add(" name=\"");
String itemTag = "item";
if (ri.getTypeName().equals("attr") && !ri.getNamedValues().isEmpty()) {
- cw.add("name=\"").add(ri.getKeyName());
+ cw.add(ri.getKeyName());
int type = ri.getNamedValues().get(0).getRawValue().getData();
if ((type & ValuesParser.ATTR_TYPE_ENUM) != 0) {
itemTag = "enum";
@@ -123,15 +123,17 @@ private void addValue(ICodeWriter cw, ResourceEntry ri) {
if (formatValue != null) {
cw.add("\" format=\"").add(formatValue);
}
- cw.add("\"");
} else {
- cw.add("name=\"").add(ri.getKeyName()).add('\"');
+ cw.add(ri.getKeyName());
}
- if (ri.getParentRef() != 0) {
- String parent = vp.decodeValue(TYPE_REFERENCE, ri.getParentRef());
- cw.add(" parent=\"").add(parent).add('\"');
+ if (ri.getTypeName().equals("style") || ri.getParentRef() != 0) {
+ cw.add("\" parent=\"");
+ if (ri.getParentRef() != 0) {
+ String parent = vp.decodeValue(TYPE_REFERENCE, ri.getParentRef());
+ cw.add(parent);
+ }
}
- cw.add(">");
+ cw.add("\">");
cw.incIndent();
for (RawNamedValue value : ri.getNamedValues()) {
@@ -177,7 +179,18 @@ private void addItem(ICodeWriter cw, String itemTag, String typeName, RawNamedVa
if (dataType == ParserConstants.TYPE_INT_DEC && nameStr != null) {
try {
int intVal = Integer.parseInt(valueStr);
- String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:attr.", ""), intVal);
+ String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:", "").replace("attr.", ""), intVal);
+ if (newVal != null) {
+ valueStr = newVal;
+ }
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+ }
+ if (dataType == ParserConstants.TYPE_INT_HEX && nameStr != null) {
+ try {
+ int intVal = Integer.decode(valueStr);
+ String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:", "").replace("attr.", ""), intVal);
if (newVal != null) {
valueStr = newVal;
}
diff --git a/jadx-with-jadxecute/jadx-core/src/main/resources/export/build.gradle.tmpl b/jadx-with-jadxecute/jadx-core/src/main/resources/export/build.gradle.tmpl
index c95e445..134898a 100644
--- a/jadx-with-jadxecute/jadx-core/src/main/resources/export/build.gradle.tmpl
+++ b/jadx-with-jadxecute/jadx-core/src/main/resources/export/build.gradle.tmpl
@@ -4,7 +4,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.0.0'
+ classpath 'com.android.tools.build:gradle:4.2.2'
}
}
diff --git a/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/api/ExportGradleTest.java b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/api/ExportGradleTest.java
index 71ca7a2..6fa37e1 100644
--- a/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/api/ExportGradleTest.java
+++ b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/api/ExportGradleTest.java
@@ -67,4 +67,10 @@ protected String getAppGradleBuild() {
assertThat(appBuildGradle.exists());
return loadFileContent(appBuildGradle);
}
+
+ protected String getSettingsGradle() {
+ File settingsGradle = new File(exportDir, "settings.gradle");
+ assertThat(settingsGradle.exists());
+ return loadFileContent(settingsGradle);
+ }
}
diff --git a/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
index f78a61b..2f3e876 100644
--- a/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
+++ b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
@@ -224,6 +224,11 @@ public List decompileFiles(List files) {
return sortedClsNodes;
}
+ @NotNull
+ public ClassNode searchTestCls(List list, String shortClsName) {
+ return searchCls(list, getTestPkg() + '.' + shortClsName);
+ }
+
@NotNull
public ClassNode searchCls(List list, String clsName) {
for (ClassNode cls : list) {
diff --git a/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/export/IllegalCharsForGradleWrapper.java b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/export/IllegalCharsForGradleWrapper.java
new file mode 100644
index 0000000..b73e442
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/export/IllegalCharsForGradleWrapper.java
@@ -0,0 +1,17 @@
+package jadx.tests.export;
+
+import org.junit.jupiter.api.Test;
+
+import jadx.tests.api.ExportGradleTest;
+
+import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
+
+class IllegalCharsForGradleWrapper extends ExportGradleTest {
+
+ @Test
+ void test() {
+ exportGradle("IllegalCharsForGradleWrapper.xml", "strings.xml");
+
+ assertThat(getSettingsGradle()).contains("'JadxTestApp'");
+ }
+}
diff --git a/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/inline/TestInstanceLambda.java b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/inline/TestInstanceLambda.java
new file mode 100644
index 0000000..43e232e
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/inline/TestInstanceLambda.java
@@ -0,0 +1,77 @@
+package jadx.tests.integration.inline;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.Test;
+
+import jadx.core.dex.attributes.AFlag;
+import jadx.core.dex.nodes.ClassNode;
+import jadx.core.dex.visitors.ProcessAnonymous;
+import jadx.core.utils.ListUtils;
+import jadx.tests.api.SmaliTest;
+
+import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
+
+public class TestInstanceLambda extends SmaliTest {
+
+ @SuppressWarnings({ "unchecked", "rawtypes", "SameParameterValue" })
+ public static class TestCls {
+
+ public Map test(List extends T> list) {
+ return toMap(list, Lambda$1.INSTANCE);
+ }
+
+ /**
+ * Smali test missing 'T' definition in 'Lambda'
+ * Note: use '$1' so class looks like generated by compiler and pass check in
+ * {@link ProcessAnonymous#canBeAnonymous(ClassNode)}
+ */
+ @SuppressWarnings({ "CheckStyle", "checkstyle:TypeName" })
+ private static class Lambda$1 implements Function {
+ public static final Lambda$1 INSTANCE = new Lambda$1();
+
+ @Override
+ public T apply(T t) {
+ return t;
+ }
+ }
+
+ private static Map toMap(List extends T> list, Function valueMap) {
+ return null;
+ }
+ }
+
+ @Test
+ public void test() {
+ useJavaInput();
+ noDebugInfo();
+ assertThat(getClassNode(TestCls.class))
+ .code();
+ }
+
+ @Test
+ public void testSmaliDisableInline() {
+ args.setInlineAnonymousClasses(false);
+ List classNodes = loadFromSmaliFiles();
+ assertThat(searchTestCls(classNodes, "Lambda$1"))
+ .code()
+ .containsOne("class Lambda$1 implements Function {");
+ assertThat(searchTestCls(classNodes, "TestCls"))
+ .code()
+ .containsOne("Lambda$1.INSTANCE");
+ }
+
+ @Test
+ public void testSmali() {
+ List classNodes = loadFromSmaliFiles();
+ assertThat(ListUtils.filter(classNodes, c -> !c.contains(AFlag.DONT_GENERATE)))
+ .describedAs("Expect lambda to be inlined")
+ .hasSize(1);
+ assertThat(searchTestCls(classNodes, "TestCls"))
+ .code()
+ .doesNotContain("Lambda$1.INSTANCE")
+ .containsOne("toMap(list, new Function() {");
+ }
+}
diff --git a/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaInstance2.java b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaInstance2.java
new file mode 100644
index 0000000..c1cefa3
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaInstance2.java
@@ -0,0 +1,36 @@
+package jadx.tests.integration.java8;
+
+import org.junit.jupiter.api.Test;
+
+import jadx.tests.api.IntegrationTest;
+
+import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
+
+public class TestLambdaInstance2 extends IntegrationTest {
+
+ public static class TestCls {
+ private String field;
+
+ public Runnable test(String str, int i) {
+ return () -> call(str, i);
+ }
+
+ public void call(String str, int i) {
+ field = str + '=' + i;
+ }
+
+ public void check() throws Exception {
+ field = "";
+ test("num", 7).run();
+ assertThat(field).isEqualTo("num=7");
+ }
+ }
+
+ @Test
+ public void test() {
+ assertThat(getClassNode(TestCls.class))
+ .code()
+ .doesNotContain("lambda$")
+ .containsOne("call(str, i)");
+ }
+}
diff --git a/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaReturn.java b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaReturn.java
new file mode 100644
index 0000000..3a5ec82
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaReturn.java
@@ -0,0 +1,61 @@
+package jadx.tests.integration.java8;
+
+import org.junit.jupiter.api.Test;
+
+import jadx.tests.api.IntegrationTest;
+import jadx.tests.api.extensions.profiles.TestProfile;
+import jadx.tests.api.extensions.profiles.TestWithProfiles;
+
+import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
+
+public class TestLambdaReturn extends IntegrationTest {
+
+ @SuppressWarnings("unused")
+ public static class TestCls {
+ interface Function0 {
+ R apply();
+ }
+
+ public static class T2 {
+ public long l;
+
+ public T2(long l) {
+ this.l = l;
+ }
+
+ public void w() {
+ }
+ }
+
+ public Byte test(Byte b1) {
+ Function0 f1 = () -> {
+ new T2(94L).w();
+ return null;
+ };
+ f1.apply();
+ return null;
+ }
+ }
+
+ @TestWithProfiles(TestProfile.DX_J8)
+ public void test() {
+ assertThat(getClassNode(TestCls.class))
+ .code()
+ .containsLines(2,
+ "Function0 f1 = () -> {",
+ indent() + "new T2(94L).w();",
+ indent() + "return null;",
+ "};");
+ }
+
+ @TestWithProfiles(TestProfile.D8_J11_DESUGAR)
+ public void testLambda() {
+ getClassNode(TestCls.class);
+ }
+
+ @Test
+ public void testNoDebug() {
+ noDebugInfo();
+ getClassNode(TestCls.class);
+ }
+}
diff --git a/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/others/TestNullInline.java b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/others/TestNullInline.java
new file mode 100644
index 0000000..92979c6
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-core/src/test/java/jadx/tests/integration/others/TestNullInline.java
@@ -0,0 +1,43 @@
+package jadx.tests.integration.others;
+
+import org.junit.jupiter.api.Test;
+
+import jadx.tests.api.IntegrationTest;
+
+import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
+
+public class TestNullInline extends IntegrationTest {
+
+ @SuppressWarnings({ "RedundantCast", "DataFlowIssue", "unused" })
+ public static class TestCls {
+ public static Long test(Double d1) {
+ T1 t1 = (T1) null;
+ return t1.t2.l;
+ }
+
+ static class T2 {
+ public long l;
+ }
+
+ static class T1 {
+ public T2 t2;
+
+ public T1(T2 t2) {
+ this.t2 = t2;
+ }
+ }
+ }
+
+ @Test
+ public void test() {
+ assertThat(getClassNode(TestCls.class))
+ .code()
+ .containsOne("Long.valueOf(t1.t2.l);");
+ }
+
+ @Test
+ public void testNoDebug() {
+ noDebugInfo();
+ getClassNode(TestCls.class);
+ }
+}
diff --git a/jadx-with-jadxecute/jadx-core/src/test/manifest/IllegalCharsForGradleWrapper.xml b/jadx-with-jadxecute/jadx-core/src/test/manifest/IllegalCharsForGradleWrapper.xml
new file mode 100644
index 0000000..2e1a1cd
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-core/src/test/manifest/IllegalCharsForGradleWrapper.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/jadx-with-jadxecute/jadx-core/src/test/smali/inline/TestInstanceLambda/Lambda.smali b/jadx-with-jadxecute/jadx-core/src/test/smali/inline/TestInstanceLambda/Lambda.smali
new file mode 100644
index 0000000..79c83b1
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-core/src/test/smali/inline/TestInstanceLambda/Lambda.smali
@@ -0,0 +1,40 @@
+.class public Linline/Lambda$1;
+.super Ljava/lang/Object;
+
+.implements Ljava/util/function/Function;
+
+
+.annotation system Ldalvik/annotation/Signature;
+ value = {
+ "Ljava/lang/Object;",
+ "Ljava/util/function/Function",
+ ";"
+ }
+.end annotation
+
+.field public static final INSTANCE:Linline/Lambda$1;
+
+.method static constructor ()V
+ .registers 1
+ new-instance v0, Linline/Lambda$1;
+ invoke-direct {v0}, Linline/Lambda$1;->()V
+ sput-object v0, Linline/Lambda$1;->INSTANCE:Linline/Lambda$1;
+ return-void
+.end method
+
+.method private constructor ()V
+ .registers 1
+ invoke-direct {p0}, Ljava/lang/Object;->()V
+ return-void
+.end method
+
+.method public final apply(Ljava/lang/Object;)Ljava/lang/Object;
+ .registers 2
+ .annotation system Ldalvik/annotation/Signature;
+ value = {
+ "(TT;)TT;"
+ }
+ .end annotation
+
+ return-object p1
+.end method
diff --git a/jadx-with-jadxecute/jadx-core/src/test/smali/inline/TestInstanceLambda/TestCls.smali b/jadx-with-jadxecute/jadx-core/src/test/smali/inline/TestInstanceLambda/TestCls.smali
new file mode 100644
index 0000000..325ab0c
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-core/src/test/smali/inline/TestInstanceLambda/TestCls.smali
@@ -0,0 +1,42 @@
+.class public Linline/TestCls;
+.super Ljava/lang/Object;
+
+.method public test(Ljava/util/List;)Ljava/util/Map;
+ .registers 3
+ .annotation system Ldalvik/annotation/Signature;
+ value = {
+ "(",
+ "Ljava/util/List",
+ "<+TT;>;)",
+ "Ljava/util/Map",
+ ";"
+ }
+ .end annotation
+
+ sget-object v0, Linline/Lambda$1;->INSTANCE:Linline/Lambda$1;
+ invoke-static {p1, v0}, Linline/TestCls;->toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map;
+ move-result-object v0
+ return-object v0
+.end method
+
+.method private static toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map;
+ .registers 4
+ .annotation system Ldalvik/annotation/Signature;
+ value = {
+ "(",
+ "Ljava/util/List",
+ "<+TT;>;",
+ "Ljava/util/function/Function",
+ ";)",
+ "Ljava/util/Map",
+ ";"
+ }
+ .end annotation
+
+ const/4 v0, 0x0
+ return-object v0
+.end method
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java
index 86a206d..129b4a9 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java
@@ -14,12 +14,10 @@
import javax.swing.JOptionPane;
import javax.swing.tree.DefaultMutableTreeNode;
+import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import io.reactivex.annotations.NonNull;
-import io.reactivex.annotations.Nullable;
-
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
@@ -42,6 +40,7 @@
import jadx.gui.ui.panel.JDebuggerPanel.IListElement;
import jadx.gui.ui.panel.JDebuggerPanel.ValueTreeNode;
import jadx.gui.utils.NLS;
+import jadx.gui.utils.UiUtils;
public final class DebugController implements SmaliDebugger.SuspendListener, IDebugController {
@@ -78,7 +77,7 @@ public boolean startDebugger(JDebuggerPanel debuggerPanel, String adbHost, int a
initTypeMap();
}
this.debuggerPanel = debuggerPanel;
- debuggerPanel.resetUI();
+ UiUtils.uiRunAndWait(debuggerPanel::resetUI);
try {
debugger = SmaliDebugger.attach(adbHost, adbPort, this);
} catch (SmaliDebuggerException e) {
@@ -251,7 +250,6 @@ private RuntimeType castType(ArgType type) {
throw new JadxRuntimeException("Unexpected type: " + type);
}
- @NonNull
protected static RuntimeType castType(String type) {
RuntimeType rt = null;
if (!StringUtils.isEmpty(type)) {
@@ -663,22 +661,22 @@ private void updateAllFieldValues(long thisID, FrameNode frame) {
}
private void updateAllRegisters(FrameNode frame) {
- if (buildRegTreeNodes(frame).size() > 0) {
- fetchAllRegisters(frame);
- }
+ UiUtils.uiRun(() -> {
+ if (!buildRegTreeNodes(frame).isEmpty()) {
+ fetchAllRegisters(frame);
+ }
+ });
}
private void fetchAllRegisters(FrameNode frame) {
List regs = cur.regAdapter.getInitializedList(frame.getCodeOffset());
for (SmaliRegister reg : regs) {
- lazyQueue.execute(() -> {
- Entry info = cur.regAdapter.getInfo(reg.getRuntimeRegNum(), frame.getCodeOffset());
- RegTreeNode regNode = frame.getRegNodes().get(reg.getRegNum());
- if (info != null) {
- applyDbgInfo(regNode, info);
- }
- updateRegister(regNode, null, true);
- });
+ Entry info = cur.regAdapter.getInfo(reg.getRuntimeRegNum(), frame.getCodeOffset());
+ RegTreeNode regNode = frame.getRegNodes().get(reg.getRegNum());
+ if (info != null) {
+ applyDbgInfo(regNode, info);
+ }
+ updateRegister(regNode, null, true);
}
}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java
index 8c1e0b7..114da9b 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java
@@ -20,7 +20,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.panel.ProgressPanel;
@@ -60,14 +59,6 @@ public synchronized Future execute(IBackgroundTask task) {
return taskWorker;
}
- public TaskStatus executeAndWait(IBackgroundTask task) {
- try {
- return execute(task).get();
- } catch (Exception e) {
- throw new JadxRuntimeException("Task execution error", e);
- }
- }
-
public synchronized void cancelAll() {
try {
taskRunning.values().forEach(Cancelable::cancel);
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/jobs/DecompileTask.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/jobs/DecompileTask.java
index b288df2..01bd04d 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/jobs/DecompileTask.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/jobs/DecompileTask.java
@@ -5,12 +5,15 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import javax.swing.JOptionPane;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeCache;
import jadx.api.JavaClass;
import jadx.gui.JadxWrapper;
+import jadx.gui.ui.MainWindow;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
@@ -23,14 +26,16 @@ public static int calcDecompileTimeLimit(int classCount) {
return classCount * CLS_LIMIT + 5000;
}
+ private final MainWindow mainWindow;
private final JadxWrapper wrapper;
private final AtomicInteger complete = new AtomicInteger(0);
private int expectedCompleteCount;
private ProcessResult result;
- public DecompileTask(JadxWrapper wrapper) {
- this.wrapper = wrapper;
+ public DecompileTask(MainWindow mainWindow) {
+ this.mainWindow = mainWindow;
+ this.wrapper = mainWindow.getWrapper();
}
@Override
@@ -40,6 +45,10 @@ public String getTitle() {
@Override
public List scheduleJobs() {
+ if (mainWindow.getCacheObject().isFullDecompilationFinished()) {
+ return Collections.emptyList();
+ }
+
List classes = wrapper.getIncludedClasses();
expectedCompleteCount = classes.size();
complete.set(0);
@@ -87,7 +96,41 @@ public void onDone(ITaskInfo taskInfo) {
+ ", time limit:{ total: " + timeLimit + "ms, per cls: " + CLS_LIMIT + "ms }"
+ ", status: " + taskInfo.getStatus());
}
- this.result = new ProcessResult(skippedCls, taskInfo.getStatus(), timeLimit);
+ result = new ProcessResult(skippedCls, taskInfo.getStatus(), timeLimit);
+
+ wrapper.unloadClasses();
+ processDecompilationResults();
+ System.gc();
+
+ mainWindow.getCacheObject().setFullDecompilationFinished(skippedCls == 0);
+ }
+
+ private void processDecompilationResults() {
+ int skippedCls = result.getSkipped();
+ if (skippedCls == 0) {
+ return;
+ }
+ TaskStatus status = result.getStatus();
+ LOG.warn("Decompile and indexing of some classes skipped: {}, status: {}", skippedCls, status);
+ switch (status) {
+ case CANCEL_BY_USER: {
+ String reason = NLS.str("message.userCancelTask");
+ String message = NLS.str("message.indexIncomplete", reason, skippedCls);
+ JOptionPane.showMessageDialog(mainWindow, message);
+ break;
+ }
+ case CANCEL_BY_TIMEOUT: {
+ String reason = NLS.str("message.taskTimeout", result.getTimeLimit());
+ String message = NLS.str("message.indexIncomplete", reason, skippedCls);
+ JOptionPane.showMessageDialog(mainWindow, message);
+ break;
+ }
+ case CANCEL_BY_MEMORY: {
+ mainWindow.showHeapUsageBar();
+ JOptionPane.showMessageDialog(mainWindow, NLS.str("message.indexingClassesSkipped", skippedCls));
+ break;
+ }
+ }
}
@Override
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/AddCommentHelper.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/AddCommentHelper.java
deleted file mode 100644
index 1c70726..0000000
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/AddCommentHelper.java
+++ /dev/null
@@ -1,260 +0,0 @@
-package jadx.gui.plugins.jadxecute;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import jadx.api.JavaNode;
-import jadx.api.data.ICodeComment;
-import jadx.api.data.impl.JadxCodeComment;
-import jadx.api.data.impl.JadxCodeData;
-import jadx.api.data.impl.JadxCodeRef;
-import jadx.api.data.impl.JadxNodeRef;
-import jadx.gui.treemodel.JClass;
-import jadx.gui.ui.MainWindow;
-import jadx.gui.ui.codearea.CodeArea;
-import jadx.gui.settings.JadxProject;
-
-import jadx.api.JavaVariable;
-import jadx.core.utils.exceptions.JadxRuntimeException;
-import jadx.gui.jobs.TaskStatus;
-import jadx.gui.treemodel.JMethod;
-import jadx.gui.treemodel.JNode;
-import jadx.gui.treemodel.JPackage;
-import jadx.gui.ui.TabbedPane;
-import jadx.gui.ui.codearea.ClassCodeContentPanel;
-import jadx.gui.ui.panel.ContentPanel;
-import jadx.gui.utils.CacheObject;
-import jadx.gui.utils.JNodeCache;
-
-public class AddCommentHelper {
- private static final Logger LOG = LoggerFactory.getLogger(AddCommentHelper.class);
-
- private MainWindow mainWindow;
- private CacheObject cache;
- private String newCommentString;
- private JavaNode node;
- private boolean updateComment;
-
- public AddCommentHelper(MainWindow mainWindow, JavaNode node) {
- this.mainWindow = mainWindow;
- this.cache = mainWindow.getCacheObject();
- this.node = node;
- }
-
- // To be implemented
- /*
- public String addVariableComment(String newCommentString, JavaVariable javaVariable) {
- this.newCommentString = newCommentString;
-
- ICodeComment blankComment = new JadxCodeComment(JadxNodeRef.forJavaNode(node), JadxCodeRef.forVar(javaVariable), "");
- if (blankComment == null) {
- return "Failed to add comment";
- }
-
- ICodeComment existComment = searchForExistComment(blankComment);
- if (existComment != null) {
- this.updateComment = true;
- apply(existComment, javaVariable);
- } else {
- this.updateComment = false;
- apply(blankComment, javaVariable);
- }
-
- return "Added comment in " + node.getFullName() + " for variable " + javaVariable.getName();
- }
- */
-
- public String addInstructionComment(String newCommentString, int commentSmaliOffset) {
- this.newCommentString = newCommentString;
-
- ICodeComment blankComment = new JadxCodeComment(JadxNodeRef.forJavaNode(node), JadxCodeRef.forInsn(commentSmaliOffset), "");
- if (blankComment == null) {
- return "Failed to add comment";
- }
-
- ICodeComment existComment = searchForExistComment(blankComment);
- if (existComment != null) {
- this.updateComment = true;
- apply(existComment, commentSmaliOffset);
- } else {
- this.updateComment = false;
- apply(blankComment, commentSmaliOffset);
- }
-
- return "Added comment in " + node.getFullName() + " at offset " + commentSmaliOffset;
- }
-
- private void apply(ICodeComment comment, int commentOffset) {
- if (newCommentString.isEmpty()) {
- if (updateComment) {
- updateCommentsData(list -> list.removeIf(c -> c == comment));
- }
- return;
- }
-
- ICodeComment newComment = new JadxCodeComment(JadxNodeRef.forJavaNode(node), JadxCodeRef.forInsn(commentOffset), newCommentString);
- if (updateComment) {
- updateCommentsData(list -> {
- list.remove(comment);
- list.add(newComment);
- });
- } else {
- updateCommentsData(list -> list.add(newComment));
- }
- }
-
- private void apply(ICodeComment comment, JavaVariable javaVariable) {
- if (newCommentString.isEmpty()) {
- if (updateComment) {
- updateCommentsData(list -> list.removeIf(c -> c == comment));
- }
- return;
- }
-
- ICodeComment newComment = new JadxCodeComment(JadxNodeRef.forJavaNode(node), JadxCodeRef.forVar(javaVariable), newCommentString);
- if (updateComment) {
- updateCommentsData(list -> {
- list.remove(comment);
- list.add(newComment);
- });
- } else {
- updateCommentsData(list -> list.add(newComment));
- }
- }
-
- private ICodeComment searchForExistComment(ICodeComment blankComment) {
- try {
- JadxProject project = mainWindow.getProject();
- JadxCodeData codeData = project.getCodeData();
- if (codeData == null || codeData.getComments().isEmpty()) {
- return null;
- }
- for (ICodeComment comment : codeData.getComments()) {
- if (Objects.equals(comment.getNodeRef(), blankComment.getNodeRef())
- && Objects.equals(comment.getCodeRef(), blankComment.getCodeRef())) {
- return comment;
- }
- }
- } catch (Exception e) {
- LOG.error("Error searching for exists comment", e);
- }
- return null;
- }
-
- private String updateCommentsData(Consumer> updater) {
- JadxProject project = mainWindow.getProject();
- JadxCodeData codeData = project.getCodeData();
-
- try {
- if (codeData == null) {
- codeData = new JadxCodeData();
- }
- List list = new ArrayList<>(codeData.getComments());
-
- updater.accept(list);
- Collections.sort(list);
- codeData.setComments(list);
- project.setCodeData(codeData);
- mainWindow.getWrapper().reloadCodeData();
- } catch (Exception e) {
- LOG.error("Comment action failed", e);
- }
- try {
- refreshState();
- } catch (Exception e) {
- LOG.error("Failed to reload code", e);
- }
-
- String retval = "";
-
- for (ICodeComment comment : codeData.getComments()) {
- retval += comment.getComment() + "\n";
- }
- return retval;
- }
-
- private void refreshState() {
- mainWindow.getWrapper().reInitRenameVisitor();
-
- JNodeCache nodeCache = cache.getNodeCache();
- JavaNode javaNode = node;
-
- List toUpdate = new ArrayList<>();
- if (javaNode != null) {
- toUpdate.add(javaNode);
- if (node instanceof JMethod) {
- toUpdate.addAll(((JMethod) node).getJavaMethod().getOverrideRelatedMethods());
- }
- } else {
- throw new JadxRuntimeException("Unexpected node type: " + node);
- }
- Set updatedTopClasses = toUpdate
- .stream()
- .map(JavaNode::getTopParentClass)
- .map(nodeCache::makeFrom)
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
-
- LOG.debug("Classes to update: {}", updatedTopClasses);
-
- refreshTabs(mainWindow.getTabbedPane(), updatedTopClasses);
-
- if (!updatedTopClasses.isEmpty()) {
- mainWindow.getBackgroundExecutor().execute("Refreshing",
- () -> refreshClasses(updatedTopClasses),
- (status) -> {
- if (status == TaskStatus.CANCEL_BY_MEMORY) {
- mainWindow.showHeapUsageBar();
- }
- if (node instanceof JPackage) {
- mainWindow.getTreeRoot().update();
- }
- mainWindow.reloadTree();
- });
- }
- }
-
- private void refreshClasses(Set updatedTopClasses) {
- if (updatedTopClasses.size() < 10) {
- // small batch => reload
- LOG.debug("Classes to reload: {}", updatedTopClasses.size());
- for (JClass cls : updatedTopClasses) {
- try {
- cls.reload(cache);
- } catch (Exception e) {
- LOG.error("Failed to reload class: {}", cls.getFullName(), e);
- }
- }
- } else {
- // big batch => unload
- LOG.debug("Classes to unload: {}", updatedTopClasses.size());
- for (JClass cls : updatedTopClasses) {
- try {
- cls.unload(cache);
- } catch (Exception e) {
- LOG.error("Failed to unload class: {}", cls.getFullName(), e);
- }
- }
- }
- }
-
- private void refreshTabs(TabbedPane tabbedPane, Set updatedClasses) {
- for (Map.Entry entry : tabbedPane.getOpenTabs().entrySet()) {
- JClass rootClass = entry.getKey().getRootClass();
- if (updatedClasses.remove(rootClass)) {
- ClassCodeContentPanel contentPanel = (ClassCodeContentPanel) entry.getValue();
- CodeArea codeArea = (CodeArea) contentPanel.getJavaCodePanel().getCodeArea();
- codeArea.refreshClass();
- }
- }
- }
-}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/ErrorHighlightingLinter.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/ErrorHighlightingLinter.java
deleted file mode 100644
index d32047c..0000000
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/ErrorHighlightingLinter.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package jadx.gui.plugins.jadxecute;
-
-public class ErrorHighlightingLinter {
-
-}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/JadxecuteDialog.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/JadxecuteDialog.java
deleted file mode 100644
index 6819c2d..0000000
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/JadxecuteDialog.java
+++ /dev/null
@@ -1,350 +0,0 @@
-package jadx.gui.plugins.jadxecute;
-
-import java.awt.Container;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.WindowConstants;
-import javax.swing.JTextArea;
-import javax.swing.JTextField;
-import javax.swing.BoxLayout;
-import javax.swing.border.EmptyBorder;
-
-import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
-import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.Font;
-
-import javax.swing.JFileChooser;
-import javax.swing.JList;
-import javax.swing.JScrollPane;
-import javax.swing.ListSelectionModel;
-
-import jadx.gui.jobs.BackgroundExecutor;
-import jadx.gui.settings.JadxSettings;
-import jadx.gui.ui.MainWindow;
-import jadx.gui.utils.UiUtils;
-
-public class JadxecuteDialog extends JDialog {
- private static final Logger LOG = LoggerFactory.getLogger(JadxecuteDialog.class);
-
- private final transient JadxSettings settings;
- private final transient MainWindow mainWindow;
-
- private static final int DEFAULT_FONT_SIZE = 12;
-
- public JadxecuteDialog(MainWindow mainWindow) {
- super(mainWindow, "JADXexecute", true);
- this.mainWindow = mainWindow;
- this.settings = mainWindow.getSettings();
-
- initUI();
- }
-
- private void initUI() {
- JPanel mainPanel = new JPanel();
- mainPanel.setLayout(new BorderLayout());
- mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
-
- // Input and output code areas
- JLabel codeInputDescription = new JLabel("Java Input");
- codeInputDescription.setFont(new Font(null, Font.BOLD, DEFAULT_FONT_SIZE));
- codeInputDescription.setPreferredSize(new Dimension(100, 16));
- RSyntaxTextArea codeInputArea = new RSyntaxTextArea(getDefaultCodeInputText());
- codeInputArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
- JScrollPane codeInputScrollPanel = new JScrollPane(codeInputArea);
- codeInputScrollPanel.setPreferredSize(new Dimension(600, 200));
-
- JLabel consoleOutputDescription = new JLabel("Console Output");
- consoleOutputDescription.setFont(new Font(null, Font.BOLD, DEFAULT_FONT_SIZE));
- consoleOutputDescription.setPreferredSize(new Dimension(80, 16));
- consoleOutputDescription.setBorder(new EmptyBorder(10, 0, 10, 0));
- JTextArea consoleOutputArea = new JTextArea(" ");
- consoleOutputArea.setEditable(false);
- consoleOutputArea.setBackground(Color.BLACK);
- consoleOutputArea.setForeground(Color.WHITE);
-
- JScrollPane consoleOutputScrollPanel = new JScrollPane(consoleOutputArea);
- consoleOutputScrollPanel.setPreferredSize(new Dimension(550, 100));
-
- // Input and output code areas
- JPanel codePanel = initCodePanel(codeInputDescription, codeInputScrollPanel, consoleOutputDescription, consoleOutputScrollPanel);
- JPanel filePanel = initFilePanel(codeInputArea);
-
- // Buttons for running and closing the jadxecute dialog
- JPanel bottomPan = new JPanel();
- bottomPan.setLayout(new BorderLayout());
- JPanel buttonPane = new JPanel();
- JLabel statusLabel = new JLabel("Status: Ready");
- statusLabel.setPreferredSize(new Dimension(100, 16));
- JButton run = new JButton("Run");
- JButton close = new JButton("Close");
- close.addActionListener(event -> close());
-
- // run code
- run.addActionListener(event -> runUserCode(codeInputArea, consoleOutputArea, statusLabel, run));
-
- buttonPane.add(run);
- buttonPane.add(close);
- bottomPan.add(statusLabel, BorderLayout.WEST);
- bottomPan.add(buttonPane, BorderLayout.CENTER);
- getRootPane().setDefaultButton(close);
-
- // Add file panel and code panel to a sub panel
- JPanel leftPanel = new JPanel();
- leftPanel.setLayout(new BorderLayout());
- leftPanel.add(codePanel, BorderLayout.CENTER);
-
- // Add left panel and buttons panel to main panel
- mainPanel.add(leftPanel, BorderLayout.WEST);
- mainPanel.add(bottomPan, BorderLayout.SOUTH);
-
- // Add code examples panel to east position of main panel
- mainPanel.add(initCodeExamplesPanel(codeInputArea, filePanel), BorderLayout.EAST);
- finishUI(mainPanel);
-
- }
-
- private JPanel initCodePanel(JLabel codeInputDescription, JScrollPane codeInputScrollPanel,
- JLabel consoleOutputDescription, JScrollPane consoleOutputScrollPanel) {
-
- JPanel codePanel = new JPanel();
- codePanel.setLayout(new BoxLayout(codePanel, BoxLayout.Y_AXIS));
- codePanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-
- codeInputDescription.setAlignmentX(Component.LEFT_ALIGNMENT);
- codeInputScrollPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
- consoleOutputDescription.setAlignmentX(Component.LEFT_ALIGNMENT);
- consoleOutputScrollPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-
- codePanel.add(codeInputDescription);
- codePanel.add(codeInputScrollPanel);
- codePanel.add(consoleOutputDescription);
- codePanel.add(consoleOutputScrollPanel);
-
- return codePanel;
- }
-
- private JPanel initCodeExamplesPanel(RSyntaxTextArea codeInputArea, JPanel filePanel) {
- // Add options on the right to display different code examples
- JPanel codeExamplesPanel = new JPanel();
- codeExamplesPanel.setLayout(new BorderLayout());
- codeExamplesPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
- JLabel scriptSelection = new JLabel("Select Template:");
- scriptSelection.setFont(new Font(null, Font.BOLD, DEFAULT_FONT_SIZE));
-
- JScrollPane exampleScrollPane = initCodeExamplesListeners(codeInputArea);
- JPanel southExamplesPanel = new JPanel();
- southExamplesPanel.setLayout(new BorderLayout());
- southExamplesPanel.add(scriptSelection, BorderLayout.NORTH);
- southExamplesPanel.add(exampleScrollPane, BorderLayout.CENTER);
- codeExamplesPanel.add(filePanel, BorderLayout.NORTH);
- codeExamplesPanel.add(southExamplesPanel, BorderLayout.CENTER);
- codeExamplesPanel.setPreferredSize(new Dimension(300, 400));
-
- return codeExamplesPanel;
- }
-
- private void finishUI(JPanel mainPanel) {
- Container contentPane = getContentPane();
- contentPane.add(mainPanel);
-
- setTitle("JADXecute");
- pack();
-
- // set modal size
- setSize(1024, 768);
-
- setLocationRelativeTo(mainPanel);
- this.setModal(false);
- setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
- // setModalityType(ModalityType.APPLICATION_MODAL);
- UiUtils.addEscapeShortCutToDispose(this);
- }
-
- private JScrollPane initCodeExamplesListeners(RSyntaxTextArea codeInputArea) {
- // Populate code examples
- Map keyValuePairs = addCodeExamplesList();
- String[] keyArray = keyValuePairs.keySet().toArray(new String[0]);
- JList exampleList = new JList<>(keyArray);
- // loop through key-value pair and create action listener for each
- for (int i = 0; i < keyArray.length; i++) {
- exampleList.addListSelectionListener(event -> {
- if (!event.getValueIsAdjusting()) {
- codeInputArea.setText(keyValuePairs.get(exampleList.getSelectedValue()));
- }
- });
- }
-
- exampleList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-
- return new JScrollPane(exampleList);
- }
-
- private JPanel initFilePanel(RSyntaxTextArea codeInputArea) {
- JPanel filePanel = new JPanel();
- filePanel.setLayout(new BorderLayout());
- filePanel.setBorder(new EmptyBorder(10, 0, 10, 0));
-
- JLabel fileLabel = new JLabel("Input Java File: ");
- fileLabel.setFont(new Font(null, Font.BOLD, DEFAULT_FONT_SIZE));
- JTextField fileField = new JTextField(20);
- JButton fileButton = new JButton("Browse");
- fileButton.addActionListener(e -> {
- JFileChooser fileChooser = new JFileChooser();
- int returnValue = fileChooser.showOpenDialog(null);
- if (returnValue == JFileChooser.APPROVE_OPTION) {
- File selectedFile = fileChooser.getSelectedFile();
- fileField.setText(selectedFile.getAbsolutePath());
-
- // Now read in the file and populate the Java input area
- try (BufferedReader br = new BufferedReader(new FileReader(selectedFile))) {
- String line;
- StringBuilder sb = new StringBuilder();
- while ((line = br.readLine()) != null) {
- sb.append(line);
- sb.append(System.lineSeparator());
- }
- codeInputArea.setText(sb.toString());
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- });
- filePanel.add(fileLabel, BorderLayout.NORTH);
- filePanel.add(fileField, BorderLayout.CENTER);
- filePanel.add(fileButton, BorderLayout.EAST);
-
- return filePanel;
- }
-
- private Map addCodeExamplesList() {
- String resourceName = "/jadxecute/codeexamples.properties";
- try (InputStream is = JadxecuteDialog.class.getResourceAsStream(resourceName);
- BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
-
- Map keyValuePairs = new HashMap<>();
-
- String line;
- String currentKey = null;
- StringBuilder currentValue = new StringBuilder();
-
- boolean readingValue = false;
-
- while ((line = reader.readLine()) != null) {
- if (line.startsWith("###")) {
- // store the current key-value pair
- if (currentKey != null && currentValue.length() > 0) {
- keyValuePairs.put(currentKey, currentValue.toString());
- currentValue = new StringBuilder();
- }
- // update the current key
- currentKey = line.substring(3);
- } else if ((readingValue || line.startsWith("```")) && currentKey != null) {
- if (!readingValue) {
- readingValue = true;
- } else if(readingValue && line.startsWith("```")) {
- readingValue = false;
- } else if (readingValue) {
- // add the line to the current value
- currentValue.append(line + "\n");
- }
- }
- }
-
- // store the last key-value pair
- if (currentKey != null && currentValue.length() > 0) {
- keyValuePairs.put(currentKey, currentValue.toString());
- }
-
- // print the key-value pairs
- for (Map.Entry entry : keyValuePairs.entrySet()) {
- LOG.info("Key: " + entry.getKey());
- LOG.info("Value: " + entry.getValue());
- }
-
- return keyValuePairs;
-
- } catch (IOException e) {
- LOG.error("Failed reading codeexamples.properties file.");
- e.printStackTrace();
- }
-
- return null;
- }
-
- // Set the default text to populate the code input field
- private String getDefaultCodeInputText() {
- String defaultText = "import jadx.gui.ui.MainWindow;\n" +
- "\n" +
- "public class UserCodeClass {\n" +
- " public static String userCodeMain(MainWindow mainWindow) {\n" +
- " String jadxecuteOutput = \"Example output...\";\n" +
- "\n" +
- " // Your code goes here!\n" +
- "\n" +
- " return jadxecuteOutput;\n" +
- " }\n" +
- "}\n";
- return defaultText;
- }
-
- private void runUserCode(RSyntaxTextArea codeInputArea, JTextArea consoleOutputArea, JLabel statusLabel, JButton run) {
- statusLabel.setText("Status: Running...");
- statusLabel.setForeground(Color.ORANGE);
-
- BackgroundExecutor executor = mainWindow.getBackgroundExecutor();
- executor.execute("Jadexecute Task", () -> executeBackgroundTask(codeInputArea, consoleOutputArea, statusLabel),
- analysisStatus -> finishTask(consoleOutputArea, statusLabel));
- }
-
- private void finishTask(JTextArea consoleOutputArea, JLabel statusLabel) {
- if (consoleOutputArea.getText().contains("Java compilation error")) {
- statusLabel.setText("Status: Error");
- statusLabel.setForeground(Color.RED);
- } else {
- statusLabel.setText("Status: Done");
- statusLabel.setForeground(Color.GREEN);
- }
- }
-
- private void executeBackgroundTask(RSyntaxTextArea codeInputArea, JTextArea consoleOutputArea, JLabel statusLabel) {
- String codeInput = codeInputArea.getText();
-
- try{
- UserCodeLoader userCodeLoader = new UserCodeLoader();
- consoleOutputArea.setText(userCodeLoader.runInputCode(codeInput, mainWindow));
- } catch (Exception e) {
- e.printStackTrace();
- consoleOutputArea.setText("Error running user code");
- statusLabel.setText("Status: Error");
- statusLabel.setForeground(Color.RED);
- }
- }
-
- private void close() {
- dispose();
- }
-
- @Override
- public void dispose() {
- settings.saveWindowPos(this);
- super.dispose();
- }
-}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/RenameObjectHelper.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/RenameObjectHelper.java
deleted file mode 100644
index f56b4cf..0000000
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/RenameObjectHelper.java
+++ /dev/null
@@ -1,257 +0,0 @@
-package jadx.gui.plugins.jadxecute;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-
-import org.jetbrains.annotations.NotNull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import jadx.api.JavaClass;
-import jadx.api.JavaMethod;
-import jadx.api.JavaNode;
-import jadx.api.JavaVariable;
-import jadx.api.data.ICodeRename;
-import jadx.api.data.impl.JadxCodeData;
-import jadx.api.data.impl.JadxCodeRef;
-import jadx.api.data.impl.JadxCodeRename;
-import jadx.api.data.impl.JadxNodeRef;
-import jadx.core.deobf.NameMapper;
-import jadx.core.utils.exceptions.JadxRuntimeException;
-import jadx.gui.jobs.TaskStatus;
-import jadx.gui.settings.JadxProject;
-import jadx.gui.treemodel.JClass;
-import jadx.gui.treemodel.JField;
-import jadx.gui.treemodel.JMethod;
-import jadx.gui.treemodel.JNode;
-import jadx.gui.treemodel.JPackage;
-import jadx.gui.treemodel.JVariable;
-import jadx.gui.ui.MainWindow;
-import jadx.gui.ui.TabbedPane;
-import jadx.gui.ui.codearea.ClassCodeContentPanel;
-import jadx.gui.ui.codearea.CodeArea;
-import jadx.gui.ui.panel.ContentPanel;
-import jadx.gui.utils.CacheObject;
-import jadx.gui.utils.JNodeCache;
-
-public class RenameObjectHelper {
- private static final Logger LOG = LoggerFactory.getLogger(RenameObjectHelper.class);
-
- private MainWindow mainWindow;
- private CacheObject cache;
- private JNode source;
- private JNode node;
- private String newObjectName;
- private String oldObjectName;
-
- // Rename class, method, or field
- public String renameObject(MainWindow mainWindow, JNode node, String newObjectName) {
- this.oldObjectName = node.getName();
- this.mainWindow = mainWindow;
- this.cache = mainWindow.getCacheObject();
- this.source = node;
- this.node = replaceNode(node);
- this.newObjectName = newObjectName;
- rename(newObjectName);
-
- return "Renamed " + oldObjectName + " to " + newObjectName;
- }
-
- // Need the methods from jadx.gui.ui.dialog.RenameDialog but they
- // are private and let's not change all the access modifiers
- private JNode replaceNode(JNode node) {
- if (node instanceof JMethod) {
- JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
- if (javaMethod.isClassInit()) {
- throw new JadxRuntimeException("Can't rename class init method: " + node);
- }
- if (javaMethod.isConstructor()) {
- // rename class instead constructor
- return node.getJParent();
- }
- }
- return node;
- }
-
- private void rename(String newName) {
- if (!checkNewName()) {
- return;
- }
- try {
- updateCodeRenames(set -> processRename(node, newName, set));
- refreshState();
- } catch (Exception e) {
- LOG.error("Rename failed", e);
- }
- }
-
- private boolean checkNewName() {
- String newName = newObjectName;
- if (newName.isEmpty()) {
- // use empty name to reset rename (revert to original)
- return true;
- }
-
- boolean valid = NameMapper.isValidIdentifier(newName);
- return valid;
- }
-
- private void processRename(JNode node, String newName, Set renames) {
- JadxCodeRename rename = buildRename(node, newName, renames);
- renames.remove(rename);
- JavaNode javaNode = node.getJavaNode();
- if (javaNode != null) {
- javaNode.removeAlias();
- }
- if (!newName.isEmpty()) {
- renames.add(rename);
- }
- }
-
- @NotNull
- private JadxCodeRename buildRename(JNode node, String newName, Set renames) {
- if (node instanceof JMethod) {
- JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
- List relatedMethods = javaMethod.getOverrideRelatedMethods();
- if (!relatedMethods.isEmpty()) {
- for (JavaMethod relatedMethod : relatedMethods) {
- renames.remove(new JadxCodeRename(JadxNodeRef.forMth(relatedMethod), ""));
- }
- }
- return new JadxCodeRename(JadxNodeRef.forMth(javaMethod), newName);
- }
- if (node instanceof JField) {
- return new JadxCodeRename(JadxNodeRef.forFld(((JField) node).getJavaField()), newName);
- }
- if (node instanceof JClass) {
- return new JadxCodeRename(JadxNodeRef.forCls(((JClass) node).getCls()), newName);
- }
- if (node instanceof JPackage) {
- return new JadxCodeRename(JadxNodeRef.forPkg(((JPackage) node).getFullName()), newName);
- }
- if (node instanceof JVariable) {
- JavaVariable javaVar = ((JVariable) node).getJavaVarNode();
- return new JadxCodeRename(JadxNodeRef.forMth(javaVar.getMth()), JadxCodeRef.forVar(javaVar), newName);
- }
- throw new JadxRuntimeException("Failed to build rename node for: " + node);
- }
-
- private void updateCodeRenames(Consumer> updater) {
- JadxProject project = mainWindow.getProject();
- JadxCodeData codeData = project.getCodeData();
- if (codeData == null) {
- codeData = new JadxCodeData();
- }
- Set set = new HashSet<>(codeData.getRenames());
- updater.accept(set);
- List list = new ArrayList<>(set);
- Collections.sort(list);
- codeData.setRenames(list);
- project.setCodeData(codeData);
- mainWindow.getWrapper().reloadCodeData();
- }
-
- private void refreshState() {
- mainWindow.getWrapper().reInitRenameVisitor();
-
- JNodeCache nodeCache = cache.getNodeCache();
- JavaNode javaNode = node.getJavaNode();
-
- List toUpdate = new ArrayList<>();
- if (source != null && source != node) {
- toUpdate.add(source.getJavaNode());
- }
- if (javaNode != null) {
- toUpdate.add(javaNode);
- toUpdate.addAll(javaNode.getUseIn());
- if (node instanceof JMethod) {
- toUpdate.addAll(((JMethod) node).getJavaMethod().getOverrideRelatedMethods());
- }
- } else if (node instanceof JPackage) {
- processPackage(toUpdate);
- } else {
- throw new JadxRuntimeException("Unexpected node type: " + node);
- }
- Set updatedTopClasses = toUpdate
- .stream()
- .map(JavaNode::getTopParentClass)
- .map(nodeCache::makeFrom)
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
-
- LOG.debug("Classes to update: {}", updatedTopClasses);
-
- refreshTabs(mainWindow.getTabbedPane(), updatedTopClasses);
-
- if (!updatedTopClasses.isEmpty()) {
- mainWindow.getBackgroundExecutor().execute("Refreshing",
- () -> refreshClasses(updatedTopClasses),
- (status) -> {
- if (status == TaskStatus.CANCEL_BY_MEMORY) {
- mainWindow.showHeapUsageBar();
- }
- if (node instanceof JPackage) {
- mainWindow.getTreeRoot().update();
- }
- mainWindow.reloadTree();
- });
- }
- }
-
- private void processPackage(List toUpdate) {
- String rawFullPkg = ((JPackage) node).getFullName();
- String rawFullPkgDot = rawFullPkg + ".";
- for (JavaClass cls : mainWindow.getWrapper().getClasses()) {
- String clsPkg = cls.getClassNode().getClassInfo().getPackage();
- // search all classes in package
- if (clsPkg.equals(rawFullPkg) || clsPkg.startsWith(rawFullPkgDot)) {
- toUpdate.add(cls);
- // also include all usages (for import fix)
- toUpdate.addAll(cls.getUseIn());
- }
- }
- }
-
- private void refreshClasses(Set updatedTopClasses) {
- if (updatedTopClasses.size() < 10) {
- // small batch => reload
- LOG.debug("Classes to reload: {}", updatedTopClasses.size());
- for (JClass cls : updatedTopClasses) {
- try {
- cls.reload(cache);
- } catch (Exception e) {
- LOG.error("Failed to reload class: {}", cls.getFullName(), e);
- }
- }
- } else {
- // big batch => unload
- LOG.debug("Classes to unload: {}", updatedTopClasses.size());
- for (JClass cls : updatedTopClasses) {
- try {
- cls.unload(cache);
- } catch (Exception e) {
- LOG.error("Failed to unload class: {}", cls.getFullName(), e);
- }
- }
- }
- }
-
- private void refreshTabs(TabbedPane tabbedPane, Set updatedClasses) {
- for (Map.Entry entry : tabbedPane.getOpenTabs().entrySet()) {
- JClass rootClass = entry.getKey().getRootClass();
- if (updatedClasses.remove(rootClass)) {
- ClassCodeContentPanel contentPanel = (ClassCodeContentPanel) entry.getValue();
- CodeArea codeArea = (CodeArea) contentPanel.getJavaCodePanel().getCodeArea();
- codeArea.refreshClass();
- }
- }
- }
-}
-
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/UserCodeLoader.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/UserCodeLoader.java
deleted file mode 100644
index cceae5c..0000000
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/plugins/jadxecute/UserCodeLoader.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package jadx.gui.plugins.jadxecute;
-
-import javax.tools.*;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.lang.reflect.Method;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-import jadx.gui.ui.MainWindow;
-
-public class UserCodeLoader {
- public String runInputCode(String program, MainWindow mainWindow) throws Exception {
- // Compile the input code
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- DiagnosticCollector diagnostics = new DiagnosticCollector<>();
- JavaFileObject compilationUnit = new StringJavaFileObject("UserCodeClass", program);
- SimpleJavaFileManager fileManager = new SimpleJavaFileManager(compiler.getStandardFileManager(diagnostics, null, null));
- JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, fileManager, diagnostics, null, null, Arrays.asList(compilationUnit));
- boolean success = compilationTask.call();
- CompiledClassLoader classLoader = new CompiledClassLoader(fileManager.getGeneratedOutputFiles());
-
- // Check for compilation errors
- if (!success) {
- // Append error message to a string
- // Need to using html tags for JPanel to respect newlines
- String errorMessage = "Java compilation error:\n";
- for (Diagnostic extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
- errorMessage += diagnostic.getMessage(null) + "\n";
- }
- return errorMessage;
- }
-
- // Input code must implement method "public static String userCodeMain(MainWindow mainWindow)"
- // Return string will be console output
- // This method can invoke other methods of user code placed inside
- Class> userCodeClass = classLoader.loadClass("UserCodeClass");
- Method method = userCodeClass.getMethod("userCodeMain", MainWindow.class);
-
- return (String) method.invoke(null, mainWindow);
- }
-
- private static class StringJavaFileObject extends SimpleJavaFileObject {
- private final String code;
-
- public StringJavaFileObject(String name, String code) {
- super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension),
- Kind.SOURCE);
- this.code = code;
- }
-
- @Override
- public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
- return code;
- }
- }
-
- private static class ClassJavaFileObject extends SimpleJavaFileObject {
- private final ByteArrayOutputStream outputStream;
- private final String className;
-
- protected ClassJavaFileObject(String className, Kind kind) {
- super(URI.create("mem:///" + className.replace('.', '/') + kind.extension), kind);
- this.className = className;
- outputStream = new ByteArrayOutputStream();
- }
-
- @Override
- public OutputStream openOutputStream() throws IOException {
- return outputStream;
- }
-
- public byte[] getBytes() {
- return outputStream.toByteArray();
- }
-
- public String getClassName() {
- return className;
- }
- }
-
- private static class SimpleJavaFileManager extends ForwardingJavaFileManager {
- private final List outputFiles;
-
- protected SimpleJavaFileManager(JavaFileManager fileManager) {
- super(fileManager);
- outputFiles = new ArrayList();
- }
-
- @Override
- public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
- ClassJavaFileObject file = new ClassJavaFileObject(className, kind);
- outputFiles.add(file);
- return file;
- }
-
- public List getGeneratedOutputFiles() {
- return outputFiles;
- }
- }
-
- private static class CompiledClassLoader extends ClassLoader {
- private final List files;
-
- private CompiledClassLoader(List files) {
- this.files = files;
- }
-
- @Override
- protected Class> findClass(String name) throws ClassNotFoundException {
- Iterator itr = files.iterator();
- while (itr.hasNext()) {
- ClassJavaFileObject file = itr.next();
- if (file.getClassName().equals(name)) {
- itr.remove();
- byte[] bytes = file.getBytes();
- return super.defineClass(name, bytes, 0, bytes.length);
- }
- }
- return super.findClass(name);
- }
- }
-}
\ No newline at end of file
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/search/SearchSettings.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/search/SearchSettings.java
index a9139f7..b2d4bcf 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/search/SearchSettings.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/search/SearchSettings.java
@@ -5,6 +5,7 @@
import org.jetbrains.annotations.Nullable;
import jadx.gui.treemodel.JClass;
+import jadx.gui.treemodel.JResource;
public class SearchSettings {
@@ -13,6 +14,7 @@ public class SearchSettings {
private final boolean ignoreCase;
private JClass activeCls;
+ private JResource activeResource;
private Pattern regexPattern;
private ISearchMethod searchMethod;
@@ -64,6 +66,14 @@ public void setActiveCls(JClass activeCls) {
this.activeCls = activeCls;
}
+ public JResource getActiveResource() {
+ return activeResource;
+ }
+
+ public void setActiveResource(JResource activeResource) {
+ this.activeResource = activeResource;
+ }
+
public ISearchMethod getSearchMethod() {
return searchMethod;
}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java
index ecb9b20..c9b3115 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java
@@ -1,6 +1,7 @@
package jadx.gui.search.providers;
import java.util.ArrayDeque;
+import java.util.Collections;
import java.util.Deque;
import java.util.Enumeration;
import java.util.HashSet;
@@ -24,12 +25,15 @@
import jadx.gui.treemodel.JResource;
import jadx.gui.treemodel.JRoot;
import jadx.gui.ui.MainWindow;
+import jadx.gui.ui.dialog.SearchDialog;
+import jadx.gui.utils.NLS;
public class ResourceSearchProvider implements ISearchProvider {
private static final Logger LOG = LoggerFactory.getLogger(ResourceSearchProvider.class);
private final SearchSettings searchSettings;
private final Set extSet;
+ private final SearchDialog searchDialog;
private final int sizeLimit;
private boolean anyExt;
@@ -39,11 +43,20 @@ public class ResourceSearchProvider implements ISearchProvider {
private final Deque resQueue;
private int pos;
- public ResourceSearchProvider(MainWindow mw, SearchSettings searchSettings) {
+ private int loadErrors = 0;
+ private int skipBySize = 0;
+
+ public ResourceSearchProvider(MainWindow mw, SearchSettings searchSettings, SearchDialog searchDialog) {
this.searchSettings = searchSettings;
this.sizeLimit = mw.getSettings().getSrhResourceSkipSize() * 1048576;
this.extSet = buildAllowedFilesExtensions(mw.getSettings().getSrhResourceFileExt());
- this.resQueue = initResQueue(mw);
+ this.searchDialog = searchDialog;
+ JResource activeResource = searchSettings.getActiveResource();
+ if (activeResource != null) {
+ this.resQueue = new ArrayDeque<>(Collections.singleton(activeResource));
+ } else {
+ this.resQueue = initResQueue(mw);
+ }
}
@Override
@@ -93,32 +106,49 @@ private JNode search(JResource resNode) {
private @Nullable JResource getNextResFile(Cancelable cancelable) {
while (true) {
JResource node = resQueue.peekLast();
- if (node == null) {
- return null;
- }
- try {
- node.loadNode();
- } catch (Exception e) {
- LOG.error("Error load resource node: {}", node, e);
- resQueue.removeLast();
- continue;
- }
- if (cancelable.isCanceled()) {
+ if (node == null || cancelable.isCanceled()) {
return null;
}
if (node.getType() == JResource.JResType.FILE) {
- if (shouldProcess(node)) {
+ if (shouldProcess(node) && loadResNode(node)) {
return node;
}
resQueue.removeLast();
} else {
// dir
resQueue.removeLast();
+ loadResNode(node);
addChildren(node);
}
}
}
+ private void updateProgressInfo() {
+ StringBuilder sb = new StringBuilder();
+ if (loadErrors != 0) {
+ sb.append(" ").append(NLS.str("search_dialog.resources_load_errors", loadErrors));
+ }
+ if (skipBySize != 0) {
+ sb.append(" ").append(NLS.str("search_dialog.resources_skip_by_size", skipBySize));
+ }
+ if (sb.length() != 0) {
+ sb.append(" ").append(NLS.str("search_dialog.resources_check_logs"));
+ }
+ searchDialog.updateProgressLabel(sb.toString());
+ }
+
+ private boolean loadResNode(JResource node) {
+ try {
+ node.loadNode();
+ return true;
+ } catch (Exception e) {
+ LOG.error("Error load resource node: {}", node, e);
+ loadErrors++;
+ updateProgressInfo();
+ return false;
+ }
+ }
+
private void addChildren(JResource resNode) {
resQueue.addAll(resNode.getSubNodes());
}
@@ -167,19 +197,24 @@ private boolean shouldProcess(JResource resNode) {
return false;
}
}
- if (sizeLimit == 0) {
+ if (sizeLimit <= 0) {
return true;
}
try {
int charsCount = resNode.getCodeInfo().getCodeStr().length();
long size = charsCount * 8L;
if (size > sizeLimit) {
- LOG.debug("Resource search skipped because of size limit: {} res size {} bytes", resNode, size);
+ LOG.info("Resource search skipped because of size limit. Resource '{}' size {} bytes, limit: {}",
+ resNode.getName(), size, sizeLimit);
+ skipBySize++;
+ updateProgressInfo();
return false;
}
return true;
} catch (Exception e) {
LOG.warn("Resource load error: {}", resNode, e);
+ loadErrors++;
+ updateProgressInfo();
return false;
}
}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
index 9876e2d..6a8359a 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
@@ -401,6 +401,10 @@ public void setInlineMethods(boolean inlineMethods) {
this.inlineMethods = inlineMethods;
}
+ public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
+ this.allowInlineKotlinLambda = allowInlineKotlinLambda;
+ }
+
public void setExtractFinally(boolean extractFinally) {
this.extractFinally = extractFinally;
}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
index c1702ec..ab748e0 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
@@ -538,6 +538,13 @@ private SettingsGroup makeDecompilationGroup() {
needReload();
});
+ JCheckBox inlineKotlinLambdas = new JCheckBox();
+ inlineKotlinLambdas.setSelected(settings.isAllowInlineKotlinLambda());
+ inlineKotlinLambdas.addItemListener(e -> {
+ settings.setAllowInlineKotlinLambda(e.getStateChange() == ItemEvent.SELECTED);
+ needReload();
+ });
+
JCheckBox extractFinally = new JCheckBox();
extractFinally.setSelected(settings.isExtractFinally());
extractFinally.addItemListener(e -> {
@@ -581,6 +588,7 @@ private SettingsGroup makeDecompilationGroup() {
other.addRow(NLS.str("preferences.useDebugInfo"), useDebugInfo);
other.addRow(NLS.str("preferences.inlineAnonymous"), inlineAnonymous);
other.addRow(NLS.str("preferences.inlineMethods"), inlineMethods);
+ other.addRow(NLS.str("preferences.inlineKotlinLambdas"), inlineKotlinLambdas);
other.addRow(NLS.str("preferences.extractFinally"), extractFinally);
other.addRow(NLS.str("preferences.fsCaseSensitive"), fsCaseSensitive);
other.addRow(NLS.str("preferences.useDx"), useDx);
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
index 7e287ae..9f4b51b 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
@@ -95,11 +95,9 @@
import jadx.gui.jobs.BackgroundExecutor;
import jadx.gui.jobs.DecompileTask;
import jadx.gui.jobs.ExportTask;
-import jadx.gui.jobs.ProcessResult;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.plugins.mappings.MappingExporter;
import jadx.gui.plugins.quark.QuarkDialog;
-import jadx.gui.plugins.jadxecute.JadxecuteDialog;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsWindow;
@@ -172,9 +170,9 @@ public class MainWindow extends JFrame {
private static final ImageIcon ICON_BACK = UiUtils.openSvgIcon("ui/left");
private static final ImageIcon ICON_FORWARD = UiUtils.openSvgIcon("ui/right");
private static final ImageIcon ICON_QUARK = UiUtils.openSvgIcon("ui/quark");
- private static final ImageIcon ICON_JADXECUTE = UiUtils.openSvgIcon("ui/jadxecute");
private static final ImageIcon ICON_PREF = UiUtils.openSvgIcon("ui/settings");
private static final ImageIcon ICON_DEOBF = UiUtils.openSvgIcon("ui/helmChartLock");
+ private static final ImageIcon ICON_DECOMPILE_ALL = UiUtils.openSvgIcon("ui/runAll");
private static final ImageIcon ICON_LOG = UiUtils.openSvgIcon("ui/logVerbose");
private static final ImageIcon ICON_INFO = UiUtils.openSvgIcon("ui/showInfos");
private static final ImageIcon ICON_DEBUGGER = UiUtils.openSvgIcon("ui/startDebugger");
@@ -610,54 +608,17 @@ synchronized void runInitialBackgroundJobs() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
- waitDecompileTask();
+ requestFullDecompilation();
}
}, 1000);
}
}
- private static final Object DECOMPILER_TASK_SYNC = new Object();
-
- public void waitDecompileTask() {
- synchronized (DECOMPILER_TASK_SYNC) {
- try {
- DecompileTask decompileTask = new DecompileTask(wrapper);
- backgroundExecutor.executeAndWait(decompileTask);
- backgroundExecutor.execute(decompileTask.getTitle(), wrapper::unloadClasses).get();
- processDecompilationResults(decompileTask.getResult());
- System.gc();
- } catch (Exception e) {
- LOG.error("Decompile task execution failed", e);
- }
- }
- }
-
- private void processDecompilationResults(ProcessResult decompile) {
- int skippedCls = decompile.getSkipped();
- if (skippedCls == 0) {
+ public void requestFullDecompilation() {
+ if (cacheObject.isFullDecompilationFinished()) {
return;
}
- TaskStatus status = decompile.getStatus();
- LOG.warn("Decompile and indexing of some classes skipped: {}, status: {}", skippedCls, status);
- switch (status) {
- case CANCEL_BY_USER: {
- String reason = NLS.str("message.userCancelTask");
- String message = NLS.str("message.indexIncomplete", reason, skippedCls);
- JOptionPane.showMessageDialog(this, message);
- break;
- }
- case CANCEL_BY_TIMEOUT: {
- String reason = NLS.str("message.taskTimeout", decompile.getTimeLimit());
- String message = NLS.str("message.indexIncomplete", reason, skippedCls);
- JOptionPane.showMessageDialog(this, message);
- break;
- }
- case CANCEL_BY_MEMORY: {
- showHeapUsageBar();
- JOptionPane.showMessageDialog(this, NLS.str("message.indexingClassesSkipped", skippedCls));
- break;
- }
- }
+ backgroundExecutor.execute(new DecompileTask(this));
}
public void cancelBackgroundJobs() {
@@ -1043,6 +1004,10 @@ public void actionPerformed(ActionEvent e) {
commentSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_SEMICOLON,
UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
+ ActionHandler decompileAllAction = new ActionHandler(ev -> requestFullDecompilation());
+ decompileAllAction.setNameAndDesc(NLS.str("menu.decompile_all"));
+ decompileAllAction.setIcon(ICON_DECOMPILE_ALL);
+
Action deobfAction = new AbstractAction(NLS.str("menu.deobfuscation"), ICON_DEOBF) {
@Override
public void actionPerformed(ActionEvent e) {
@@ -1103,14 +1068,6 @@ public void actionPerformed(ActionEvent e) {
};
quarkAction.putValue(Action.SHORT_DESCRIPTION, "Quark Engine");
- Action jadxecuteAction = new AbstractAction("JADXecute", ICON_JADXECUTE) {
- @Override
- public void actionPerformed(ActionEvent e) {
- new JadxecuteDialog(MainWindow.this).setVisible(true);
- }
- };
- jadxecuteAction.putValue(Action.SHORT_DESCRIPTION, "JADXecute");
-
Action openDeviceAction = new AbstractAction(NLS.str("debugger.process_selector"), ICON_DEBUGGER) {
@Override
public void actionPerformed(ActionEvent e) {
@@ -1162,9 +1119,9 @@ public void actionPerformed(ActionEvent e) {
JMenu tools = new JMenu(NLS.str("menu.tools"));
tools.setMnemonic(KeyEvent.VK_T);
+ tools.add(decompileAllAction);
tools.add(deobfMenuItem);
tools.add(quarkAction);
- tools.add(jadxecuteAction);
tools.add(openDeviceAction);
JMenu help = new JMenu(NLS.str("menu.help"));
@@ -1220,7 +1177,6 @@ public void actionPerformed(ActionEvent e) {
toolbar.addSeparator();
toolbar.add(deobfToggleBtn);
toolbar.add(quarkAction);
- toolbar.add(jadxecuteAction);
toolbar.add(openDeviceAction);
toolbar.addSeparator();
toolbar.add(logAction);
@@ -1243,9 +1199,9 @@ public void actionPerformed(ActionEvent e) {
exportAction.setEnabled(loaded);
saveProjectAsAction.setEnabled(loaded);
reload.setEnabled(loaded);
+ decompileAllAction.setEnabled(loaded);
deobfAction.setEnabled(loaded);
quarkAction.setEnabled(loaded);
- jadxecuteAction.setEnabled(loaded);
return false;
});
}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java
index 991774d..c3eff17 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java
@@ -475,28 +475,28 @@ public void jdwpProcessOccurred(ADBDevice device, Set id) {
LOG.error("Failed to find device", e);
return;
}
- node.tNode.removeAllChildren();
- DefaultMutableTreeNode tempNode = null;
- for (String s : procList) {
- DefaultMutableTreeNode pnode = new DefaultMutableTreeNode(s);
- node.tNode.add(pnode);
- if (!debugSetter.expectPkg.isEmpty() && s.endsWith(debugSetter.expectPkg)) {
- if (debugSetter.autoAttachPkg && debugSetter.device.equals(node.device)) {
- debugSetter.set(node.device, debugSetter.ver, getPid(s), s);
- if (attachProcess(mainWindow)) {
- dispose();
- return;
+
+ SwingUtilities.invokeLater(() -> {
+ node.tNode.removeAllChildren();
+ DefaultMutableTreeNode foundNode = null;
+ for (String procStr : procList) {
+ DefaultMutableTreeNode pnode = new DefaultMutableTreeNode(procStr);
+ node.tNode.add(pnode);
+ if (!debugSetter.expectPkg.isEmpty() && procStr.endsWith(debugSetter.expectPkg)) {
+ if (debugSetter.autoAttachPkg && debugSetter.device.equals(node.device)) {
+ debugSetter.set(node.device, debugSetter.ver, getPid(procStr), procStr);
+ if (attachProcess(mainWindow)) {
+ dispose();
+ return;
+ }
}
+ foundNode = pnode;
}
- tempNode = pnode;
}
- }
- DefaultMutableTreeNode theNode = tempNode;
- SwingUtilities.invokeLater(() -> {
procTreeModel.reload(node.tNode);
procTree.expandPath(new TreePath(node.tNode.getPath()));
- if (theNode != null) {
- TreePath thePath = new TreePath(theNode.getPath());
+ if (foundNode != null) {
+ TreePath thePath = new TreePath(foundNode.getPath());
procTree.scrollPathToVisible(thePath);
procTree.setSelectionPath(thePath);
}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommentDialog.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommentDialog.java
index 963539b..88f0c2a 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommentDialog.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommentDialog.java
@@ -62,11 +62,6 @@ private static void updateCommentsData(CodeArea codeArea, Consumer list = new ArrayList<>(codeData.getComments());
updater.accept(list);
- for (ICodeComment comment : list) {
- LOG.info("Comment: " + comment.getComment());
- LOG.info("getNodeRef: " + comment.getNodeRef());
- LOG.info("getCodeRef: " + comment.getCodeRef());
- }
Collections.sort(list);
codeData.setComments(list);
project.setCodeData(codeData);
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java
index 5c685ea..32ae9e6 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java
@@ -17,6 +17,7 @@
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
+import java.util.Objects;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
@@ -44,6 +45,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import ch.qos.logback.classic.Level;
+
+import jadx.api.JavaClass;
+import jadx.api.metadata.ICodeAnnotation;
+import jadx.api.metadata.annotations.NodeDeclareRef;
+import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResSearchNode;
import jadx.gui.ui.MainWindow;
@@ -73,6 +80,7 @@ public abstract class CommonSearchDialog extends JFrame {
protected ResultsModel resultsModel;
protected ResultsTable resultsTable;
protected JLabel resultsInfoLabel;
+ protected JLabel progressInfoLabel;
protected JLabel warnLabel;
protected ProgressPanel progressPane;
@@ -142,13 +150,45 @@ protected void openItem(JNode node) {
JumpPosition jmpPos = new JumpPosition(((JResSearchNode) node).getResNode(), node.getPos());
tabbedPane.codeJump(jmpPos);
} else {
- tabbedPane.codeJump(node);
+ if (!checkForRedirects(node)) {
+ tabbedPane.codeJump(node);
+ }
}
if (!mainWindow.getSettings().getKeepCommonDialogOpen()) {
dispose();
}
}
+ // TODO: temp solution, move implementation into corresponding nodes
+ private boolean checkForRedirects(JNode node) {
+ if (node instanceof JClass) {
+ JavaClass cls = ((JClass) node).getCls();
+ JavaClass origTopCls = cls.getOriginalTopParentClass();
+ JavaClass codeParent = cls.getTopParentClass();
+ if (Objects.equals(codeParent, origTopCls)) {
+ return false;
+ }
+ JClass jumpCls = mainWindow.getCacheObject().getNodeCache().makeFrom(codeParent);
+ mainWindow.getBackgroundExecutor().execute(
+ NLS.str("progress.load"),
+ jumpCls::loadNode, // load code in background
+ status -> {
+ // search original node in jump class
+ codeParent.getCodeInfo().getCodeMetadata().searchDown(0, (pos, ann) -> {
+ if (ann.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
+ if (((NodeDeclareRef) ann).getNode().equals(cls.getClassNode())) {
+ tabbedPane.codeJump(new JumpPosition(jumpCls, pos));
+ return true;
+ }
+ }
+ return null;
+ });
+ });
+ return true;
+ }
+ return false;
+ }
+
@Nullable
private JNode getSelectedNode() {
try {
@@ -260,6 +300,15 @@ public void actionPerformed(ActionEvent e) {
resultsInfoLabel = new JLabel("");
resultsInfoLabel.setFont(mainWindow.getSettings().getFont());
+ progressInfoLabel = new JLabel("");
+ progressInfoLabel.setFont(mainWindow.getSettings().getFont());
+ progressInfoLabel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ LogViewerDialog.openWithLevel(mainWindow, Level.INFO);
+ }
+ });
+
JPanel resultsActionsPanel = new JPanel();
resultsActionsPanel.setLayout(new BoxLayout(resultsActionsPanel, BoxLayout.LINE_AXIS));
resultsActionsPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
@@ -276,6 +325,8 @@ public void actionPerformed(ActionEvent e) {
protected void addResultsActions(JPanel resultsActionsPanel) {
resultsActionsPanel.add(Box.createRigidArea(new Dimension(20, 0)));
resultsActionsPanel.add(resultsInfoLabel);
+ resultsActionsPanel.add(Box.createRigidArea(new Dimension(20, 0)));
+ resultsActionsPanel.add(progressInfoLabel);
resultsActionsPanel.add(Box.createHorizontalGlue());
}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java
index 3b384aa..dd78b2e 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java
@@ -55,6 +55,7 @@
import jadx.gui.search.providers.ResourceSearchProvider;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
+import jadx.gui.treemodel.JResource;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
@@ -452,9 +453,18 @@ private boolean buildSearch(SearchTask newSearchTask, String text, SearchSetting
resultsInfoLabel.setText("Can't search in current tab");
return false;
}
- JClass activeCls = currentPos.getNode().getRootClass();
- searchSettings.setActiveCls(activeCls);
- allClasses = Collections.singletonList(activeCls.getCls());
+ JNode currentNode = currentPos.getNode();
+ if (currentNode instanceof JClass) {
+ JClass activeCls = currentNode.getRootClass();
+ searchSettings.setActiveCls(activeCls);
+ allClasses = Collections.singletonList(activeCls.getCls());
+ } else if (currentNode instanceof JResource) {
+ searchSettings.setActiveResource((JResource) currentNode);
+ allClasses = Collections.emptyList();
+ } else {
+ resultsInfoLabel.setText("Can't search in current tab");
+ return false;
+ }
} else {
allClasses = mainWindow.getWrapper().getIncludedClassesWithInners();
}
@@ -475,9 +485,10 @@ private boolean buildSearch(SearchTask newSearchTask, String text, SearchSetting
merged.add(new FieldSearchProvider(mainWindow, searchSettings, allClasses));
}
if (options.contains(CODE)) {
- if (allClasses.size() == 1) {
+ int clsCount = allClasses.size();
+ if (clsCount == 1) {
newSearchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, allClasses));
- } else {
+ } else if (clsCount > 1) {
List> batches = mainWindow.getCacheObject().getDecompileBatches();
if (batches == null) {
List topClasses = ListUtils.filter(allClasses, c -> !c.isInner());
@@ -490,7 +501,7 @@ private boolean buildSearch(SearchTask newSearchTask, String text, SearchSetting
}
}
if (options.contains(RESOURCE)) {
- newSearchTask.addProviderJob(new ResourceSearchProvider(mainWindow, searchSettings));
+ newSearchTask.addProviderJob(new ResourceSearchProvider(mainWindow, searchSettings, this));
}
if (options.contains(COMMENT)) {
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
@@ -549,6 +560,7 @@ private void resetSearch() {
synchronized (pendingResults) {
pendingResults.clear();
}
+ updateProgressLabel("");
progressPane.setVisible(false);
warnLabel.setVisible(false);
loadAllButton.setEnabled(false);
@@ -598,6 +610,10 @@ private void updateProgress(ITaskProgress progress) {
});
}
+ public void updateProgressLabel(String text) {
+ UiUtils.uiRun(() -> progressInfoLabel.setText(text));
+ }
+
private void searchFinished(ITaskInfo status, Boolean complete) {
UiUtils.uiThreadGuard();
LOG.debug("Search complete: {}, complete: {}", status, complete);
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java
index 57014b2..084583d 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java
@@ -19,11 +19,14 @@
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.api.utils.CodeUtils;
+import jadx.core.dex.info.ConstStorage;
+import jadx.core.dex.nodes.FieldNode;
import jadx.gui.JadxWrapper;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JClass;
+import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
@@ -51,6 +54,7 @@ public UsageDialog(MainWindow mainWindow, JNode node) {
@Override
protected void openInit() {
progressStartCommon();
+ prepareUsageData();
mainWindow.getBackgroundExecutor().execute(NLS.str("progress.load"),
this::collectUsageData,
(status) -> {
@@ -63,26 +67,39 @@ protected void openInit() {
});
}
+ private void prepareUsageData() {
+ if (mainWindow.getSettings().isReplaceConsts() && node instanceof JField) {
+ FieldNode fld = ((JField) node).getJavaField().getFieldNode();
+ boolean constField = ConstStorage.getFieldConstValue(fld) != null;
+ if (constField && !fld.getAccessFlags().isPrivate()) {
+ // run full decompilation to prepare for full code scan
+ mainWindow.requestFullDecompilation();
+ }
+ }
+ }
+
private void collectUsageData() {
usageList = new ArrayList<>();
- Map> usageQuery = buildUsageQuery();
- usageQuery.forEach((searchNode, useNodes) -> useNodes.stream()
- .map(JavaNode::getTopParentClass)
- .distinct()
- .forEach(u -> processUsage(searchNode, u)));
+ buildUsageQuery().forEach(
+ (searchNode, useNodes) -> useNodes.stream()
+ .map(JavaNode::getTopParentClass)
+ .distinct()
+ .forEach(u -> processUsage(searchNode, u)));
}
/**
* Return mapping of 'node to search' to 'use places'
*/
- private Map> buildUsageQuery() {
- Map> map = new HashMap<>();
+ private Map> buildUsageQuery() {
+ Map> map = new HashMap<>();
if (node instanceof JMethod) {
JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
for (JavaMethod mth : getMethodWithOverrides(javaMethod)) {
map.put(mth, mth.getUseIn());
}
- } else if (node instanceof JClass) {
+ return map;
+ }
+ if (node instanceof JClass) {
JavaClass javaCls = ((JClass) node).getCls();
map.put(javaCls, javaCls.getUseIn());
// add constructors usage into class usage
@@ -91,10 +108,19 @@ private Map> buildUsageQuery() {
map.put(javaMth, javaMth.getUseIn());
}
}
- } else {
- JavaNode javaNode = node.getJavaNode();
- map.put(javaNode, javaNode.getUseIn());
+ return map;
}
+ if (node instanceof JField && mainWindow.getSettings().isReplaceConsts()) {
+ FieldNode fld = ((JField) node).getJavaField().getFieldNode();
+ boolean constField = ConstStorage.getFieldConstValue(fld) != null;
+ if (constField && !fld.getAccessFlags().isPrivate()) {
+ // search all classes to collect usage of replaced constants
+ map.put(fld.getJavaNode(), mainWindow.getWrapper().getIncludedClasses());
+ return map;
+ }
+ }
+ JavaNode javaNode = node.getJavaNode();
+ map.put(javaNode, javaNode.getUseIn());
return map;
}
@@ -108,9 +134,12 @@ private List getMethodWithOverrides(JavaMethod javaMethod) {
private void processUsage(JavaNode searchNode, JavaClass topUseClass) {
ICodeInfo codeInfo = topUseClass.getCodeInfo();
+ List usePositions = topUseClass.getUsePlacesFor(codeInfo, searchNode);
+ if (usePositions.isEmpty()) {
+ return;
+ }
String code = codeInfo.getCodeStr();
JadxWrapper wrapper = mainWindow.getWrapper();
- List usePositions = topUseClass.getUsePlacesFor(codeInfo, searchNode);
for (int pos : usePositions) {
String line = CodeUtils.getLineForPos(code, pos);
if (line.startsWith("import ")) {
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/panel/JDebuggerPanel.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/panel/JDebuggerPanel.java
index 5aef0fd..d98006f 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/panel/JDebuggerPanel.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/ui/panel/JDebuggerPanel.java
@@ -88,6 +88,7 @@ public class JDebuggerPanel extends JPanel {
private transient KeyEventDispatcher controllerShortCutDispatcher;
public JDebuggerPanel(MainWindow mainWindow) {
+ UiUtils.uiThreadGuard();
this.mainWindow = mainWindow;
controller = new DebugController();
this.setLayout(new BorderLayout());
@@ -287,24 +288,26 @@ public void actionPerformed(ActionEvent e) {
@Override
public void onStateChanged(boolean suspended, boolean stopped) {
- if (!stopped) {
- if (isGray) {
- stop.putValue(Action.SMALL_ICON, ICON_STOP);
+ UiUtils.uiRun(() -> {
+ if (!stopped) {
+ if (isGray) {
+ stop.putValue(Action.SMALL_ICON, ICON_STOP);
+ }
+ } else {
+ stop.putValue(Action.SMALL_ICON, ICON_STOP_GRAY);
+ run.putValue(Action.SMALL_ICON, ICON_RUN);
+ run.putValue(Action.SHORT_DESCRIPTION, NLS.str("debugger.run"));
+ isGray = true;
+ return;
}
- } else {
- stop.putValue(Action.SMALL_ICON, ICON_STOP_GRAY);
- run.putValue(Action.SMALL_ICON, ICON_RUN);
- run.putValue(Action.SHORT_DESCRIPTION, NLS.str("debugger.run"));
- isGray = true;
- return;
- }
- if (suspended) {
- run.putValue(Action.SMALL_ICON, ICON_RUN);
- run.putValue(Action.SHORT_DESCRIPTION, NLS.str("debugger.run"));
- } else {
- run.putValue(Action.SMALL_ICON, ICON_PAUSE);
- run.putValue(Action.SHORT_DESCRIPTION, NLS.str("debugger.pause"));
- }
+ if (suspended) {
+ run.putValue(Action.SMALL_ICON, ICON_RUN);
+ run.putValue(Action.SHORT_DESCRIPTION, NLS.str("debugger.run"));
+ } else {
+ run.putValue(Action.SMALL_ICON, ICON_PAUSE);
+ run.putValue(Action.SHORT_DESCRIPTION, NLS.str("debugger.pause"));
+ }
+ });
}
});
@@ -387,16 +390,18 @@ private void stackFrameSelected(Point p) {
public boolean showDebugger(String procName, String host, int port, int androidVer, ADBDevice device, String pid) {
boolean ok = controller.startDebugger(this, host, port, androidVer);
if (ok) {
- log(String.format("Attached %s %s:%d", procName, host, port));
- try {
- logcatPanel.init(device, pid);
- } catch (Exception e) {
- log(NLS.str("logcat.error_fail_start"));
- LOG.error("Logcat failed to start", e);
- }
- leftSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerStackFrameSplitterLoc());
- rightSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerVarTreeSplitterLoc());
- mainWindow.showDebuggerPanel();
+ UiUtils.uiRun(() -> {
+ log(String.format("Attached %s %s:%d", procName, host, port));
+ try {
+ logcatPanel.init(device, pid);
+ } catch (Exception e) {
+ log(NLS.str("logcat.error_fail_start"));
+ LOG.error("Logcat failed to start", e);
+ }
+ leftSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerStackFrameSplitterLoc());
+ rightSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerVarTreeSplitterLoc());
+ mainWindow.showDebuggerPanel();
+ });
}
return ok;
}
@@ -414,6 +419,8 @@ public int getRightSplitterLocation() {
}
public void loadSettings() {
+ UiUtils.uiThreadGuard();
+
Font font = mainWindow.getSettings().getFont();
variableTree.setFont(font.deriveFont(font.getSize() + 1.f));
variableTree.setRowHeight(-1);
@@ -423,6 +430,8 @@ public void loadSettings() {
}
public void resetUI() {
+ UiUtils.uiThreadGuard();
+
thisTreeNode.removeAllChildren();
regTreeNode.removeAllChildren();
@@ -464,12 +473,11 @@ public void updateThisFieldNodes(List extends ValueTreeNode> nodes) {
}
public void refreshThreadBox(List extends IListElement> elements) {
- if (elements.size() > 0) {
- DefaultComboBoxModel model =
- (DefaultComboBoxModel) threadBox.getModel();
- elements.forEach(model::addElement);
- }
- SwingUtilities.invokeLater(() -> {
+ UiUtils.uiRun(() -> {
+ if (!elements.isEmpty()) {
+ DefaultComboBoxModel model = (DefaultComboBoxModel) threadBox.getModel();
+ elements.forEach(model::addElement);
+ }
threadBox.updateUI();
stackFrameList.setFont(mainWindow.getSettings().getFont());
});
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java
index dabee1e..7eb5885 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java
+++ b/jadx-with-jadxecute/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java
@@ -18,6 +18,8 @@ public class CacheObject {
private List> decompileBatches;
+ private volatile boolean fullDecompilationFinished;
+
public CacheObject() {
reset();
}
@@ -27,6 +29,7 @@ public void reset() {
jNodeCache = new JNodeCache();
lastSearchOptions = new HashMap<>();
decompileBatches = null;
+ fullDecompilationFinished = false;
}
@Nullable
@@ -53,4 +56,12 @@ public Map> getLastSe
public void setDecompileBatches(List> decompileBatches) {
this.decompileBatches = decompileBatches;
}
+
+ public boolean isFullDecompilationFinished() {
+ return fullDecompilationFinished;
+ }
+
+ public void setFullDecompilationFinished(boolean fullDecompilationFinished) {
+ this.fullDecompilationFinished = fullDecompilationFinished;
+ }
}
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
index 9800ca4..425d7a1 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
+++ b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
@@ -14,6 +14,7 @@ menu.text_search=Textsuche
menu.class_search=Klassen-Suche
menu.comment_search=Kommentar suchen
menu.tools=Tools
+#menu.decompile_all=Decompile all classes
menu.deobfuscation=Deobfuskierung
menu.log=Log-Anzeige
menu.help=Hilfe
@@ -111,6 +112,9 @@ search_dialog.load_all=Alle laden
#search_dialog.stop=Stop
search_dialog.results_incomplete=%d+ gefunden
search_dialog.results_complete=%d gefunden (komplett)
+#search_dialog.resources_load_errors=Load errors: %d
+#search_dialog.resources_skip_by_size=Skipped by size: %d
+#search_dialog.resources_check_logs=(click to check logs)
search_dialog.col_node=Knoten
search_dialog.col_code=Code
#search_dialog.sort_results=Sort results
@@ -157,6 +161,7 @@ preferences.useImports=Import statements generieren
preferences.useDebugInfo=Debug-Infos verwenden
preferences.inlineAnonymous=Anonyme Inline-Klassen
preferences.inlineMethods=Inline-Methoden
+#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
#preferences.extractFinally=Extract finally block
preferences.fsCaseSensitive=Dateisystem unterscheidet zwischen Groß/Kleinschreibung
preferences.skipResourcesDecode=Keine Ressourcen dekodieren
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
index babdb80..dd3bb29 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
+++ b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
@@ -14,6 +14,7 @@ menu.text_search=Text search
menu.class_search=Class search
menu.comment_search=Comment searchF
menu.tools=Tools
+menu.decompile_all=Decompile all classes
menu.deobfuscation=Deobfuscation
menu.log=Log Viewer
menu.help=Help
@@ -111,6 +112,9 @@ search_dialog.load_all=Load all
search_dialog.stop=Stop
search_dialog.results_incomplete=Found %d+
search_dialog.results_complete=Found %d (complete)
+search_dialog.resources_load_errors=Load errors: %d
+search_dialog.resources_skip_by_size=Skipped by size: %d
+search_dialog.resources_check_logs=(click to check logs)
search_dialog.col_node=Node
search_dialog.col_code=Code
search_dialog.sort_results=Sort results
@@ -157,6 +161,7 @@ preferences.useImports=Use import statements
preferences.useDebugInfo=Use debug info
preferences.inlineAnonymous=Inline anonymous classes
preferences.inlineMethods=Inline methods
+preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=Extract finally block
preferences.fsCaseSensitive=File system is case-sensitive
preferences.skipResourcesDecode=Don't decode resources
@@ -198,7 +203,7 @@ preferences.rename_printable=To make printable
preferences.search_group_title=Search
preferences.search_results_per_page=Results per page (0 - no limit)
preferences.res_file_ext=Resource files extensions ('xml|html', * for all)
-preferences.res_skip_file=Skip resources files if larger (MB)
+preferences.res_skip_file=Skip resources files if larger (MB) (0 - disable)
msg.open_file=Please open file
msg.saving_sources=Saving sources
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
index 3772a46..aee3fad 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
+++ b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
@@ -14,6 +14,7 @@ menu.text_search=Buscar texto
menu.class_search=Buscar clase
#menu.comment_search=Comment search
menu.tools=Herramientas
+#menu.decompile_all=Decompile all classes
menu.deobfuscation=Desofuscación
menu.log=Visor log
menu.help=Ayuda
@@ -111,6 +112,9 @@ search_dialog.ignorecase=Ignorar minúsculas/mayúsculas
#search_dialog.stop=Stop
#search_dialog.results_incomplete=Found %d+
#search_dialog.results_complete=Found %d (complete)
+#search_dialog.resources_load_errors=Load errors: %d
+#search_dialog.resources_skip_by_size=Skipped by size: %d
+#search_dialog.resources_check_logs=(click to check logs)
search_dialog.col_node=Nodo
search_dialog.col_code=Código
#search_dialog.sort_results=Sort results
@@ -157,6 +161,7 @@ preferences.replaceConsts=Reemplazar constantes
#preferences.useDebugInfo=Use debug info
#preferences.inlineAnonymous=
#preferences.inlineMethods=Inline methods
+#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
#preferences.extractFinally=Extract finally block
#preferences.fsCaseSensitive=
preferences.skipResourcesDecode=No descodificar recursos
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
index 2fee02a..e774731 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
+++ b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
@@ -14,6 +14,7 @@ menu.text_search=텍스트 검색
menu.class_search=클래스 검색
menu.comment_search=주석 검색
menu.tools=도구
+#menu.decompile_all=Decompile all classes
menu.deobfuscation=난독화 해제
menu.log=로그 뷰어
menu.help=도움말
@@ -111,6 +112,9 @@ search_dialog.load_all=모두 로드
search_dialog.stop=정지
search_dialog.results_incomplete=%d+개 찾음
search_dialog.results_complete=%d개 찾음 (검색 완료)
+#search_dialog.resources_load_errors=Load errors: %d
+#search_dialog.resources_skip_by_size=Skipped by size: %d
+#search_dialog.resources_check_logs=(click to check logs)
search_dialog.col_node=노드
search_dialog.col_code=코드
search_dialog.sort_results=결과 정렬
@@ -157,6 +161,7 @@ preferences.useImports=import 문 사용
preferences.useDebugInfo=디버그 정보 사용
preferences.inlineAnonymous=인라인 익명 클래스
preferences.inlineMethods=인라인 메서드
+#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=finally 블록 추출
preferences.fsCaseSensitive=파일 시스템 대소문자 구별
preferences.skipResourcesDecode=리소스 디코딩 하지 않기
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
index 4d9301b..2cfe2c9 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
+++ b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
@@ -14,6 +14,7 @@ menu.text_search=Buscar por texto
menu.class_search=Buscar por classe
menu.comment_search=Busca por comentário
menu.tools=Ferramentas
+#menu.decompile_all=Decompile all classes
menu.deobfuscation=Desofuscar
menu.log=Visualizador de log
menu.help=Ajuda
@@ -111,6 +112,9 @@ search_dialog.load_all=Carregar todas
search_dialog.stop=Parar
search_dialog.results_incomplete=Encontradas %d+
search_dialog.results_complete=Encontradas %d (completos)
+#search_dialog.resources_load_errors=Load errors: %d
+#search_dialog.resources_skip_by_size=Skipped by size: %d
+#search_dialog.resources_check_logs=(click to check logs)
search_dialog.col_node=Nó
search_dialog.col_code=Código
search_dialog.sort_results=Ordenar resultados
@@ -157,6 +161,7 @@ preferences.useImports=Utilizar declaração de imports
preferences.useDebugInfo=Utilizar informação de depuração
preferences.inlineAnonymous=Classes anônimas de uma linha
preferences.inlineMethods=Métodos de uma linha
+#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=Extrair blocos finally
preferences.fsCaseSensitive=Sistema de arquivo diferencia maiúsculas de minúsculas
preferences.skipResourcesDecode=Não decodificar recursos
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties
index 4847c84..3fc4857 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties
+++ b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties
@@ -14,6 +14,7 @@ menu.text_search=Поиск строк
menu.class_search=Поиск классов
menu.comment_search=Поиск комментариев
menu.tools=Инструменты
+#menu.decompile_all=Decompile all classes
menu.deobfuscation=Деобфускация
menu.log=Просмотр логов
menu.help=Помощь
@@ -111,6 +112,9 @@ search_dialog.load_all=Загрузить все
search_dialog.stop=Стоп
search_dialog.results_incomplete=Найдено %d+
search_dialog.results_complete=Найдено %d (поиск завершен)
+#search_dialog.resources_load_errors=Load errors: %d
+#search_dialog.resources_skip_by_size=Skipped by size: %d
+#search_dialog.resources_check_logs=(click to check logs)
search_dialog.col_node=Вхождения
search_dialog.col_code=Код
search_dialog.sort_results=Сортировка результатов
@@ -157,6 +161,7 @@ preferences.useImports=Использовать импорты
preferences.useDebugInfo=Отладочная информация
preferences.inlineAnonymous=Объединять анонимные классы
preferences.inlineMethods=Объединять методы
+#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=Вычленять finally блоки
preferences.fsCaseSensitive=Учитывать регистр в файловой системе
preferences.skipResourcesDecode=Не декодировать ресурсы
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
index 71a16b8..2074a25 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
+++ b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
@@ -14,23 +14,24 @@ menu.text_search=文本搜索
menu.class_search=类名搜索
menu.comment_search=注释搜索
menu.tools=工具
+menu.decompile_all=反编译所有类
menu.deobfuscation=反混淆
menu.log=日志查看器
menu.help=帮助
menu.about=关于
menu.update_label=发现新版本 %s!
-file.open_action=打开文件...
+file.open_action=打开文件…
file.add_files_action=添加文件
file.open_title=打开文件
file.open_project=打开文件
file.new_project=新建项目
file.save_project=保存项目
-file.save_project_as=另存项目为...
+file.save_project_as=另存项目为…
file.reload=重新加载文件
file.live_reload=实时重加载
file.live_reload_desc=文件变动时自动重载
-#file.export_mappings_as=
+file.export_mappings_as=导出映射为…
file.save_all=全部保存
file.export_gradle=另存为 Gradle 项目
file.save_all_msg=请选择保存反编译资源的目录
@@ -42,10 +43,10 @@ start_page.recent=最近项目
tree.sources_title=源代码
tree.resources_title=资源文件
-tree.loading=加载中...
+tree.loading=加载中…
progress.load=正在加载
-#progress.export_mappings=
+progress.export_mappings=导出映射
progress.decompile=反编译中
progress.canceling=正在取消
@@ -111,6 +112,9 @@ search_dialog.load_all=加载所有
search_dialog.stop=停止
search_dialog.results_incomplete=已找到 %d+
search_dialog.results_complete=全部找到 %d
+search_dialog.resources_load_errors=加载错误:%d
+search_dialog.resources_skip_by_size=按大小跳过:%d
+search_dialog.resources_check_logs=(点击查看日志)
search_dialog.col_node=节点
search_dialog.col_code=代码
search_dialog.sort_results=结果分类
@@ -119,7 +123,7 @@ search_dialog.active_tab=只在当前页搜索
search_dialog.comments=注释
search_dialog.resource=资源
search_dialog.keep_open=保持窗口
-search_dialog.tip_searching=搜索中...
+search_dialog.tip_searching=搜索中…
usage_dialog.title=查找
usage_dialog.label=查找用例:
@@ -144,7 +148,7 @@ preferences.other=其他
preferences.language=语言
preferences.lineNumbersMode=编辑器行号模式
preferences.jumpOnDoubleClick=启用双击跳转
-#preferences.useAlternativeFileDialog=Use alternative file dialog
+preferences.useAlternativeFileDialog=使用选择文件对话框
preferences.check_for_updates=启动时检查更新
preferences.useDx=使用 dx/d8 来转换java字节码
preferences.decompilationMode=反编译模式
@@ -157,6 +161,7 @@ preferences.useImports=使用 import 语句
preferences.useDebugInfo=启用调试信息
preferences.inlineAnonymous=内联匿名类
preferences.inlineMethods=内联方法
+preferences.inlineKotlinLambdas=允许内联Kotlin Lambda
preferences.extractFinally=提取finally块
preferences.fsCaseSensitive=文件系统区分大小写
preferences.skipResourcesDecode=不反编译资源文件
@@ -168,7 +173,7 @@ preferences.excludedPackages=排除的包
preferences.excludedPackages.tooltip=排除于反编译或索引的以空格分隔的包名列表(节省 RAM)
preferences.excludedPackages.button=编辑
preferences.excludedPackages.editDialog=排除于反编译或索引的以空格分隔的包名列表(节省 RAM)
例如android.support
-preferences.cfg=生成方法的 CFG 图('.dot' 格式)
+preferences.cfg=生成方法的 CFG 图('.dot')
preferences.raw_cfg=生成原始的 CFG 图
preferences.font=编辑器字体
preferences.smali_font=Smali编辑器字体
@@ -192,13 +197,13 @@ preferences.reset_title=重置设置
preferences.copy=复制到剪切板
preferences.copy_message=所有设置都已复制
preferences.rename=重命名标识符
-preferences.rename_case=需要标识符能区分大小写
-preferences.rename_valid=需要标识符能符合规范
-preferences.rename_printable=需要标识符可正常显示
+preferences.rename_case=标识符要能够区分大小写
+preferences.rename_valid=标识符应该符合标准规范
+preferences.rename_printable=标识符必须要能正常显示
preferences.search_group_title=搜索资源
preferences.search_results_per_page=每页结果数(0 - 无限制)
-preferences.res_file_ext=文件扩展名 (e.g. .xml|.html),* 表示所有
-preferences.res_skip_file=跳过文件大小(MB)
+preferences.res_file_ext=文件扩展名(e.g. .xml|.html),* 表示所有
+preferences.res_skip_file=跳过文件大小(MB)
msg.open_file=请打开文件
msg.saving_sources=正在导出源代码
@@ -274,10 +279,10 @@ issues_panel.warnings=%d 警告
issues_panel.tooltip=点击查看日志
debugger.process_selector=选择要调试的进程
-debugger.step_into=步入 (F7)
-debugger.step_over=步过 (F8)
-debugger.step_out=步出 (Shift + F8)
-debugger.run=运行 (F9)
+debugger.step_into=步入(F7)
+debugger.step_over=步过(F8)
+debugger.step_out=步出(Shift + F8)
+debugger.run=运行(F9)
debugger.stop=停止调试并终止应用
debugger.pause=暂停
debugger.rerun=重新运行
@@ -324,8 +329,8 @@ adb_dialog.refresh=刷新
adb_dialog.tip_devices=%d 台设备
adb_dialog.device_node=设备
adb_dialog.missing_path=必须提供ADB路径才能启动ADB服务。
-adb_dialog.waiting=正在等待连接到ADB服务...
-adb_dialog.connecting=正在连接ADB服务,地址: %s:%s...
+adb_dialog.waiting=正在等待连接到ADB服务…
+adb_dialog.connecting=正在连接ADB服务,地址: %s:%s…
adb_dialog.connect_okay=已连接ADB服务,地址: %s:%s
adb_dialog.connect_fail=连接ADB服务失败。
adb_dialog.disconnected=ADB服务已断开连接。
@@ -340,4 +345,4 @@ adb_dialog.msg_read_mani_fail=解码AndroidManifest.xml失败
adb_dialog.no_devices=找不到任何设备用来启动APP。
adb_dialog.restart_while_debugging_title=调试时重新启动
adb_dialog.restart_while_debugging_msg=你正在调试一个APP,确定要重新启动一个会话吗?
-adb_dialog.starting_debugger=正在启动调试器...
+adb_dialog.starting_debugger=正在启动调试器…
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties
index ca81942..72676c8 100644
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties
+++ b/jadx-with-jadxecute/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties
@@ -14,6 +14,7 @@ menu.text_search=文字搜尋
menu.class_search=類別搜尋
menu.comment_search=註解搜尋
menu.tools=工具
+#menu.decompile_all=Decompile all classes
menu.deobfuscation=去模糊化
menu.log=日誌檢視器
menu.help=幫助
@@ -111,6 +112,9 @@ search_dialog.load_all=載入全部
search_dialog.stop=停止
search_dialog.results_incomplete=找到 %d+
search_dialog.results_complete=找到 %d (完整)
+#search_dialog.resources_load_errors=Load errors: %d
+#search_dialog.resources_skip_by_size=Skipped by size: %d
+#search_dialog.resources_check_logs=(click to check logs)
search_dialog.col_node=無
search_dialog.col_code=程式碼
search_dialog.sort_results=排序結果
@@ -157,6 +161,7 @@ preferences.useImports=使用 import 陳述式
preferences.useDebugInfo=使用除錯資訊
preferences.inlineAnonymous=內嵌匿名類別
preferences.inlineMethods=內嵌方式
+#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=擷取 finally 區塊
preferences.fsCaseSensitive=檔案系統區分大小寫
preferences.skipResourcesDecode=不要為資源解碼
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/icons/ui/jadxecute.svg b/jadx-with-jadxecute/jadx-gui/src/main/resources/icons/ui/jadxecute.svg
deleted file mode 100644
index e845f7b..0000000
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/icons/ui/jadxecute.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/icons/ui/runAll.svg b/jadx-with-jadxecute/jadx-gui/src/main/resources/icons/ui/runAll.svg
new file mode 100644
index 0000000..51c8bc7
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-gui/src/main/resources/icons/ui/runAll.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/jadxecute/codeexamples.properties b/jadx-with-jadxecute/jadx-gui/src/main/resources/jadxecute/codeexamples.properties
deleted file mode 100644
index b6f3c91..0000000
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/jadxecute/codeexamples.properties
+++ /dev/null
@@ -1,324 +0,0 @@
-### Blank Template
-```
-import jadx.gui.ui.MainWindow;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
-
- // Your code goes here!
-
- return jadxecuteOutput;
- }
-}
-```
-
-### JadxWrapper example
-```
-import jadx.gui.ui.MainWindow;
-import jadx.gui.JadxWrapper;
-import jadx.api.JavaClass;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "Example output...";
-
- JadxWrapper wrapper = mainWindow.getWrapper();
-
- // Here's an example of using the wrapper object. Update here!
- for (JavaClass cls : wrapper.getClasses()) {
- jadxecuteOutput += cls.getName() + "\n";
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print all class names
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- jadxecuteOutput += cls.getFullName() + "\n";
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print all method names
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.MethodNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- ClassNode selectedClassNode = null;
- String searchClassName = "exampleClassName"; // Update this
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- jadxecuteOutput += "Found class: " + cls.getFullName() + "\n";
- selectedClassNode = cls;
- }
- }
-
- if (selectedClassNode != null) {
- jadxecuteOutput += "Methods:\n";
-
- // Print all methods in the selected class
- for (MethodNode method : selectedClassNode.getMethods()) {
- jadxecuteOutput += method.getAlias() + "\n"; // Use the alias since this includes user updates
- }
- } else {
- jadxecuteOutput += "Could not find class " + searchClassName;
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print all field names
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.FieldNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- ClassNode selectedClassNode = null;
- String searchClassName = "exampleClassName"; // Update this
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- jadxecuteOutput += "Found class: " + cls.getFullName() + "\n";
- selectedClassNode = cls;
- }
- }
-
- if (selectedClassNode != null) {
- jadxecuteOutput += "Fields:\n";
-
- // Print all field in the selected class
- for (FieldNode field : selectedClassNode.getFields()) {
- jadxecuteOutput += field.getAlias() + "\n"; // Use the alias since this includes user updates
- }
- } else {
- jadxecuteOutput += "Could not find class " + searchClassName;
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print classes inheriting from class
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-import jadx.core.dex.instructions.args.ArgType;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- String searchClassName = "IntentService"; // Update this
- ArgType superClassType = null;
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
-
- jadxecuteOutput += "Classes extending " + searchClassName + ":\n";
-
- for (ClassNode cls : classes) {
- superClassType = cls.getSuperClass();
-
- if (superClassType != null && superClassType.toString().contains(searchClassName)) {
- jadxecuteOutput += cls.getFullName() + "\n";
- }
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print classes containing substring
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- String searchClassName = "exampleClassName"; // Update this
-
- // Loop through all classes and add desired name to return output
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
-
- // Example: finds all user-renamed classes renamed like "mw_MyClassA"
- if (cls.getFullName().contains(searchClassName)) {
- jadxecuteOutput += cls.getFullName() + "\n";
- }
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Rename a class
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-import jadx.gui.treemodel.JClass;
-import jadx.api.JavaClass;
-import jadx.gui.plugins.jadxecute.RenameObjectHelper;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- String searchClassName = "exampleClassName"; // Update this
-
- // Find desired class
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- RenameObjectHelper renameObjectHelper = new RenameObjectHelper();
- JClass jclass = new JClass(cls.getJavaNode());
-
- // Rename found class to desired name
- jadxecuteOutput += renameObjectHelper.renameObject(mainWindow, jclass, "newClassName");
-
- // Optionally return here or you could add functionality to change all
- // matched objects to different names and return out of the loop
- return jadxecuteOutput;
- }
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print Java code in a class
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-import jadx.gui.treemodel.JClass;
-import jadx.api.JavaClass;
-import jadx.gui.plugins.jadxecute.RenameObjectHelper;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- String searchClassName = "exampleClassName"; // Update this
-
- // Find desired class
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- RenameObjectHelper renameObjectHelper = new RenameObjectHelper();
-
- jadxecuteOutput += "Found class " + searchClassName + ":\n\n";
- jadxecuteOutput += cls.getJavaNode().getCodeInfo().toString();
-
- return jadxecuteOutput;
- }
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Insert a new instruction comment
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.MethodNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-import jadx.gui.plugins.jadxecute.AddCommentHelper;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- ClassNode selectedClassNode = null;
- MethodNode selectedMethodNode = null;
- String searchClassName = "exampleClassName"; // Update this
- String searchMethodName = "exampleMethodName"; // Update this
- String commentToAdd = "This is a new comment!"; // Update this
- int smaliInstructionIndex = 0; // Update this
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- jadxecuteOutput += "Found class: " + cls.getFullName() + "\n";
- selectedClassNode = cls;
- }
- }
-
- if (selectedClassNode != null) {
- for (MethodNode method : selectedClassNode.getMethods()) {
- if (method.getAlias().contains(searchMethodName)) {
- jadxecuteOutput += "Found method: " + method.getAlias() + "\n";
- selectedMethodNode = method;
- }
- }
-
- // Add the comment if the method was found
- if (selectedMethodNode != null) {
- AddCommentHelper addCommentHelper = new AddCommentHelper(mainWindow, selectedMethodNode.getJavaNode());
- jadxecuteOutput += addCommentHelper.addInstructionComment(commentToAdd, smaliInstructionIndex);
- }
-
- } else {
- jadxecuteOutput += "Could not find class " + searchClassName;
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
diff --git a/jadx-with-jadxecute/jadx-gui/src/main/resources/jadxecute/codeexamples.properties.bak b/jadx-with-jadxecute/jadx-gui/src/main/resources/jadxecute/codeexamples.properties.bak
deleted file mode 100644
index c6e9736..0000000
--- a/jadx-with-jadxecute/jadx-gui/src/main/resources/jadxecute/codeexamples.properties.bak
+++ /dev/null
@@ -1,325 +0,0 @@
-### Blank Template
-```
-import jadx.gui.ui.MainWindow;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
-
- // Your code goes here!
-
- return jadxecuteOutput;
- }
-}
-```
-
-### JadxWrapper example
-```
-import jadx.gui.ui.MainWindow;
-import jadx.gui.JadxWrapper;
-import jadx.api.JavaClass;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "Example output...";
-
- JadxWrapper wrapper = mainWindow.getWrapper();
-
- // Here's an example of using the wrapper object. Update here!
- for (JavaClass cls : wrapper.getClasses()) {
- jadxecuteOutput += cls.getName() + "\n";
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print all class names
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- jadxecuteOutput += cls.getFullName() + "\n";
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print all method names
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.MethodNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- ClassNode selectedClassNode = null;
- String searchClassName = "exampleClassName"; // Update this
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- jadxecuteOutput += "Found class: " + cls.getFullName() + "\n";
- selectedClassNode = cls;
- }
- }
-
- if (selectedClassNode != null) {
- jadxecuteOutput += "Methods:\n";
-
- // Print all methods in the selected class
- for (MethodNode method : selectedClassNode.getMethods()) {
- jadxecuteOutput += method.getAlias() + "\n"; // Use the alias since this includes user updates
- }
- } else {
- jadxecuteOutput += "Could not find class " + searchClassName;
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print all field names
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.FieldNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- ClassNode selectedClassNode = null;
- String searchClassName = "exampleClassName"; // Update this
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- jadxecuteOutput += "Found class: " + cls.getFullName() + "\n";
- selectedClassNode = cls;
- }
- }
-
- if (selectedClassNode != null) {
- jadxecuteOutput += "Fields:\n";
-
- // Print all field in the selected class
- for (FieldNode field : selectedClassNode.getFields()) {
- jadxecuteOutput += field.getAlias() + "\n"; // Use the alias since this includes user updates
- }
- } else {
- jadxecuteOutput += "Could not find class " + searchClassName;
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print classes inheriting from class
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-import jadx.core.dex.instructions.args.ArgType;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- String searchClassName = "IntentService"; // Update this
- ArgType superClassType = null;
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
-
- jadxecuteOutput += "Classes extending " + searchClassName + ":\n";
-
- for (ClassNode cls : classes) {
- superClassType = cls.getSuperClass();
-
- if (superClassType != null && superClassType.toString().contains(searchClassName)) {
- jadxecuteOutput += cls.getFullName() + "\n";
- }
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print classes containing substring
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- String searchClassName = "exampleClassName"; // Update this
-
- // Loop through all classes and add desired name to return output
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
-
- // Example: finds all user-renamed classes renamed like "mw_MyClassA"
- if (cls.getFullName().contains(searchClassName)) {
- jadxecuteOutput += cls.getFullName() + "\n";
- }
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Rename a class
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-import jadx.gui.treemodel.JClass;
-import jadx.api.JavaClass;
-import jadx.gui.plugins.jadxecute.RenameObjectHelper;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- String searchClassName = "exampleClassName"; // Update this
-
- // Find desired class
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- RenameObjectHelper renameObjectHelper = new RenameObjectHelper();
- JClass jclass = new JClass(cls.getJavaNode());
-
- // Rename found class to desired name
- jadxecuteOutput += renameObjectHelper.renameObject(mainWindow, jclass, "newClassName");
-
- // Optionally return here or you could add functionality to change all
- // matched objects to different names and return out of the loop
- return jadxecuteOutput;
- }
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Print Java code in a class
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-import jadx.gui.treemodel.JClass;
-import jadx.api.JavaClass;
-import jadx.gui.plugins.jadxecute.RenameObjectHelper;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- String searchClassName = "exampleClassName"; // Update this
-
- // Find desired class
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- RenameObjectHelper renameObjectHelper = new RenameObjectHelper();
-
- jadxecuteOutput += "Found class " + searchClassName + ":\n\n";
- jadxecuteOutput += cls.getJavaNode().getCodeInfo().toString();
-
- return jadxecuteOutput;
- }
- }
-
- return jadxecuteOutput;
- }
-}
-```
-
-### Insert a new instruction comment
-```
-import jadx.gui.ui.MainWindow;
-import jadx.core.dex.nodes.ClassNode;
-import jadx.core.dex.nodes.MethodNode;
-import jadx.core.dex.nodes.RootNode;
-import java.util.List;
-import jadx.gui.plugins.jadxecute.AddCommentHelper;
-
-public class UserCodeClass {
- public static String userCodeMain(MainWindow mainWindow) {
- String jadxecuteOutput = "";
- ClassNode selectedClassNode = null;
- MethodNode selectedMethodNode = null;
- String searchClassName = "exampleClassName"; // Update this
- String searchMethodName = "exampleMethodName"; // Update this
- String commentToAdd = "This is a new comment!"; // Update this
- int smaliInstructionIndex = 0; // Update this
-
- // Add all strings to the jadxecute output to be printed
- RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
- List classes = root.getClasses();
- for (ClassNode cls : classes) {
- if (cls.getFullName().contains(searchClassName)) {
- jadxecuteOutput += "Found class: " + cls.getFullName() + "\n";
- selectedClassNode = cls;
- }
- }
-
- if (selectedClassNode != null) {
- for (MethodNode method : selectedClassNode.getMethods()) {
- if (method.getAlias().contains(searchMethodName)) {
- jadxecuteOutput += "Found method: " + method.getAlias() + "\n";
- selectedMethodNode = method;
- }
- }
-
- // Add the comment if the method was found
- if (selectedMethodNode != null) {
- AddCommentHelper addCommentHelper = new AddCommentHelper(mainWindow, selectedMethodNode.getJavaNode());
- jadxecuteOutput += addCommentHelper.addInstructionComment(commentToAdd, smaliInstructionIndex);
- }
-
- } else {
- jadxecuteOutput += "Could not find class " + searchClassName;
- }
-
- return jadxecuteOutput;
- }
-}
-
-```
-
diff --git a/jadx-with-jadxecute/jadx-plugins/jadx-java-input/build.gradle b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/build.gradle
index 27d0872..0ac2fef 100644
--- a/jadx-with-jadxecute/jadx-plugins/jadx-java-input/build.gradle
+++ b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/build.gradle
@@ -7,4 +7,6 @@ dependencies {
// show bytecode disassemble
implementation 'io.github.skylot:raung-disasm:0.0.3'
+
+ testImplementation(project(":jadx-core"))
}
diff --git a/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputLoader.java
similarity index 90%
rename from jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java
rename to jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputLoader.java
index 073b079..bd7b22e 100644
--- a/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java
+++ b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputLoader.java
@@ -19,8 +19,8 @@
import jadx.api.plugins.utils.CommonFileUtils;
import jadx.api.plugins.utils.ZipSecurity;
-public class JavaFileLoader {
- private static final Logger LOG = LoggerFactory.getLogger(JavaFileLoader.class);
+public class JavaInputLoader {
+ private static final Logger LOG = LoggerFactory.getLogger(JavaInputLoader.class);
private static final int MAX_MAGIC_SIZE = 4;
private static final byte[] JAVA_CLASS_FILE_MAGIC = { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE };
@@ -37,6 +37,14 @@ public List collectFiles(List inputFiles) {
.collect(Collectors.toList());
}
+ public List loadInputStream(InputStream in, String name) throws IOException {
+ return loadReader(in, name, null, null);
+ }
+
+ public JavaClassReader loadClass(byte[] content, String fileName) {
+ return new JavaClassReader(getNextUniqId(), fileName, content);
+ }
+
private List loadFromFile(File file) {
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return loadReader(inputStream, file.getName(), file, null);
diff --git a/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java
index 7cc4429..57dc8a1 100644
--- a/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java
+++ b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java
@@ -1,8 +1,11 @@
package jadx.plugins.input.java;
import java.io.Closeable;
+import java.io.InputStream;
import java.nio.file.Path;
+import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
@@ -10,6 +13,7 @@
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.input.data.impl.EmptyLoadResult;
+import jadx.plugins.input.java.utils.JavaClassParseException;
public class JavaInputPlugin implements JadxInputPlugin {
@@ -29,10 +33,51 @@ public ILoadResult loadFiles(List inputFiles) {
}
public static ILoadResult loadClassFiles(List inputFiles, @Nullable Closeable closeable) {
- List readers = new JavaFileLoader().collectFiles(inputFiles);
+ List readers = new JavaInputLoader().collectFiles(inputFiles);
if (readers.isEmpty()) {
return EmptyLoadResult.INSTANCE;
}
return new JavaLoadResult(readers, closeable);
}
+
+ public static ILoadResult loadClassFiles(List inputFiles) {
+ return loadClassFiles(inputFiles, null);
+ }
+
+ /**
+ * Method for provide several inputs by using load methods from {@link JavaInputLoader} class.
+ */
+ public static ILoadResult load(Function> loader) {
+ return wrapClassReaders(loader.apply(new JavaInputLoader()));
+ }
+
+ /**
+ * Convenient method for load class file or jar from input stream.
+ * Should be used only once per JadxDecompiler instance.
+ * For load several times use {@link JavaInputPlugin#load(Function)} method.
+ */
+ public static ILoadResult loadFromInputStream(InputStream in, String fileName) {
+ try {
+ return wrapClassReaders(new JavaInputLoader().loadInputStream(in, fileName));
+ } catch (Exception e) {
+ throw new JavaClassParseException("Failed to read input stream", e);
+ }
+ }
+
+ /**
+ * Convenient method for load single class file by content.
+ * Should be used only once per JadxDecompiler instance.
+ * For load several times use {@link JavaInputPlugin#load(Function)} method.
+ */
+ public static ILoadResult loadSingleClass(byte[] content, String fileName) {
+ JavaClassReader reader = new JavaInputLoader().loadClass(content, fileName);
+ return new JavaLoadResult(Collections.singletonList(reader));
+ }
+
+ public static ILoadResult wrapClassReaders(List readers) {
+ if (readers.isEmpty()) {
+ return EmptyLoadResult.INSTANCE;
+ }
+ return new JavaLoadResult(readers);
+ }
}
diff --git a/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java
index e14cd70..add098f 100644
--- a/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java
+++ b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java
@@ -20,6 +20,10 @@ public class JavaLoadResult implements ILoadResult {
@Nullable
private final Closeable closeable;
+ public JavaLoadResult(List readers) {
+ this(readers, null);
+ }
+
public JavaLoadResult(List readers, @Nullable Closeable closeable) {
this.readers = readers;
this.closeable = closeable;
@@ -47,7 +51,6 @@ public boolean isEmpty() {
@Override
public void close() throws IOException {
- readers.clear();
if (closeable != null) {
closeable.close();
}
diff --git a/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/test/java/jadx/plugins/input/java/CustomLoadTest.java b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/test/java/jadx/plugins/input/java/CustomLoadTest.java
new file mode 100644
index 0000000..2069cf3
--- /dev/null
+++ b/jadx-with-jadxecute/jadx-plugins/jadx-java-input/src/test/java/jadx/plugins/input/java/CustomLoadTest.java
@@ -0,0 +1,129 @@
+package jadx.plugins.input.java;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import jadx.api.JadxArgs;
+import jadx.api.JadxDecompiler;
+import jadx.api.plugins.input.data.ILoadResult;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class CustomLoadTest {
+
+ private JadxDecompiler jadx;
+
+ @BeforeEach
+ void init() {
+ jadx = new JadxDecompiler(new JadxArgs());
+ }
+
+ @AfterEach
+ void close() {
+ jadx.close();
+ }
+
+ @Test
+ void loadFiles() {
+ List files = Stream.of("HelloWorld.class", "HelloWorld$HelloInner.class")
+ .map(this::getSample)
+ .collect(Collectors.toList());
+ ILoadResult loadResult = JavaInputPlugin.loadClassFiles(files);
+ loadDecompiler(loadResult);
+ assertThat(jadx.getClassesWithInners())
+ .hasSize(2)
+ .satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"))
+ .satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloInner"));
+ }
+
+ @Test
+ void loadFromInputStream() throws IOException {
+ String fileName = "HelloWorld$HelloInner.class";
+ try (InputStream in = Files.newInputStream(getSample(fileName))) {
+ ILoadResult loadResult = JavaInputPlugin.loadFromInputStream(in, fileName);
+ loadDecompiler(loadResult);
+ assertThat(jadx.getClassesWithInners())
+ .hasSize(1)
+ .satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld$HelloInner"));
+
+ System.out.println(jadx.getClassesWithInners().get(0).getCode());
+ }
+ }
+
+ @Test
+ void loadSingleClass() throws IOException {
+ String fileName = "HelloWorld.class";
+ byte[] content = Files.readAllBytes(getSample(fileName));
+ ILoadResult loadResult = JavaInputPlugin.loadSingleClass(content, fileName);
+ loadDecompiler(loadResult);
+ assertThat(jadx.getClassesWithInners())
+ .hasSize(1)
+ .satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"));
+
+ System.out.println(jadx.getClassesWithInners().get(0).getCode());
+ }
+
+ @Test
+ void load() {
+ ILoadResult loadResult = JavaInputPlugin.load(loader -> {
+ List inputs = new ArrayList<>(2);
+ try {
+ String hello = "HelloWorld.class";
+ byte[] content = Files.readAllBytes(getSample(hello));
+ inputs.add(loader.loadClass(content, hello));
+
+ String helloInner = "HelloWorld$HelloInner.class";
+ InputStream in = Files.newInputStream(getSample(helloInner));
+ inputs.addAll(loader.loadInputStream(in, helloInner));
+ } catch (Exception e) {
+ fail(e);
+ }
+ return inputs;
+ });
+ loadDecompiler(loadResult);
+ assertThat(jadx.getClassesWithInners())
+ .hasSize(2)
+ .satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"))
+ .satisfiesOnlyOnce(cls -> {
+ assertThat(cls.getName()).isEqualTo("HelloInner");
+ assertThat(cls.getCode()).isEqualTo(""); // no code for moved inner class
+ });
+
+ assertThat(jadx.getClasses())
+ .hasSize(1)
+ .satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"))
+ .satisfiesOnlyOnce(cls -> assertThat(cls.getInnerClasses()).hasSize(1)
+ .satisfiesOnlyOnce(inner -> assertThat(inner.getName()).isEqualTo("HelloInner")));
+
+ jadx.getClassesWithInners().forEach(cls -> System.out.println(cls.getCode()));
+ }
+
+ public void loadDecompiler(ILoadResult load) {
+ try {
+ jadx.addCustomLoad(load);
+ jadx.load();
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public Path getSample(String name) {
+ try {
+ return Paths.get(ClassLoader.getSystemResource("samples/" + name).toURI());
+ } catch (Exception e) {
+ return fail(e);
+ }
+ }
+}