Compare commits
195 Commits
issue-1973
...
v1.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
| c4c3d42d16 | |||
| 58c36de8c2 | |||
| b4ca566a19 | |||
| e644bad758 | |||
| dd86abcc5e | |||
| f0513a1a55 | |||
| be6cb573b1 | |||
| 5d064d3e50 | |||
| 15b6309e2c | |||
| 417bb7a7e9 | |||
| 4c74e8e300 | |||
| 57238de6ff | |||
| 313c4a121a | |||
| 39912398fc | |||
| 7544d1a113 | |||
| cfbe5ab672 | |||
| 2661b91a6f | |||
| 61578e8793 | |||
| cc6a893402 | |||
| 4d8a5d6671 | |||
| 982307b1ac | |||
| 37054dc84e | |||
| 343e2c531a | |||
| 6fa5d247f0 | |||
| 688dea0c50 | |||
| c0815b12bc | |||
| 233f8692b2 | |||
| e5be41b9cc | |||
| 3788e4ef3a | |||
| 32855f4511 | |||
| 3d5e225274 | |||
| 8a34d973ff | |||
| a43b3282ef | |||
| 249801880c | |||
| 742d30d770 | |||
| 267413332a | |||
| f9c94f27f1 | |||
| d2131d9640 | |||
| 548821c4df | |||
| 958ab245ae | |||
| 8f3cc3e8c1 | |||
| b872ffd1b9 | |||
| c5263f92fe | |||
| 1475e887c8 | |||
| c21cabcba7 | |||
| 063af8cd62 | |||
| 2d10537050 | |||
| 3814951408 | |||
| 964bd62d35 | |||
| 3ed2df828f | |||
| b26abdc851 | |||
| 90185fd947 | |||
| 681f8a98b5 | |||
| 0b225238fb | |||
| a7649dda7a | |||
| b5e3dcf70f | |||
| 7abbc81886 | |||
| 9c30aeacdb | |||
| 8f27de4d0e | |||
| 02b69d2d29 | |||
| e6fde48b69 | |||
| 109dea0857 | |||
| 1d34328dd3 | |||
| ef4f1d3ed4 | |||
| 23696d3971 | |||
| efa2f5d172 | |||
| 699ceb197e | |||
| 5c83c22501 | |||
| 7bb5c0a859 | |||
| 603863403f | |||
| 889a945cf1 | |||
| fd80e03809 | |||
| b5807082d9 | |||
| 5d1f0b8cae | |||
| 3f9aa34057 | |||
| e2c860f260 | |||
| 0938351d97 | |||
| 937dd20794 | |||
| ea5e87560a | |||
| 5fbbf2150e | |||
| 0e11bffe82 | |||
| ba9af5c288 | |||
| cca706c94f | |||
| 2df69bbfb4 | |||
| f5307636ef | |||
| 9a39b70a46 | |||
| e63808bc4b | |||
| 847225a6a9 | |||
| eee354a3ab | |||
| 1cc00a54f3 | |||
| ffdad1b652 | |||
| 9a8ec76989 | |||
| 0be5b2cea9 | |||
| c94201be4a | |||
| 1051dacb1e | |||
| a2bfe9bbe8 | |||
| 8c6ec3bccc | |||
| 015876b790 | |||
| 280f3870a9 | |||
| e9d770ae9e | |||
| 5f1bd1d9ba | |||
| 60fb458024 | |||
| 1b08779536 | |||
| c37e39a819 | |||
| 2d58fbd4b1 | |||
| ffbf800404 | |||
| 500aa8a68d | |||
| 58e8268126 | |||
| f9da6e00ed | |||
| 821cc668c7 | |||
| 287ba49008 | |||
| 115e563a2b | |||
| 1669200e62 | |||
| 6ab224ea0d | |||
| 61855a7ea1 | |||
| bda3119e86 | |||
| b26abed686 | |||
| c179beee95 | |||
| 04a454094b | |||
| 33bcbfec41 | |||
| ec645d80b1 | |||
| a8d889de3d | |||
| ec0bf701c8 | |||
| ad4dd116be | |||
| ef79f266c8 | |||
| 366225f9be | |||
| 730db0d24f | |||
| 05fb77e9bd | |||
| bbabfa0354 | |||
| 96bd9f0f17 | |||
| fd5b397b40 | |||
| f5e3a261b4 | |||
| 52a884608a | |||
| 74ddfde950 | |||
| 9aacb4f312 | |||
| 82e2104f3c | |||
| 09fa35f144 | |||
| f2a6a1e942 | |||
| b85900aa3d | |||
| 37a42d1418 | |||
| 07dde05337 | |||
| 8618214c7f | |||
| b80f32a36f | |||
| ce527ed753 | |||
| f2ea6415c9 | |||
| bc70f8eabb | |||
| be25cbf8c2 | |||
| f9c0cad146 | |||
| b356ff76e1 | |||
| ec9244a635 | |||
| a5bd64461d | |||
| 54bf79ccc5 | |||
| 6182332eef | |||
| 37b57096ec | |||
| 6aab8fabc9 | |||
| 665c1e57d2 | |||
| 6e8affcbdc | |||
| 41d6b0018e | |||
| dbadbb01fc | |||
| 0f52077c5c | |||
| ea861829c7 | |||
| c1de235289 | |||
| 8f969d4e89 | |||
| 0c1f830f94 | |||
| 43c082e4da | |||
| ecdc4e6757 | |||
| b865c9c687 | |||
| 6b4976c593 | |||
| 2807dc5090 | |||
| 463d2b90fa | |||
| bff00d101f | |||
| 1290ef63a2 | |||
| 49d2b34d84 | |||
| eecdfae73f | |||
| 8760b4ddde | |||
| 3599b248a4 | |||
| 2fdd496518 | |||
| 278e3c2d47 | |||
| 881a716b8e | |||
| a73c9e90fc | |||
| 56749b2afb | |||
| d7ec35791b | |||
| d51362ed50 | |||
| 5c0c1daa71 | |||
| 603ea3989a | |||
| 018ff98df7 | |||
| 5fbabdefca | |||
| 13607fc8b6 | |||
| 0c33d723c8 | |||
| 0143423dc9 | |||
| 21b1452485 | |||
| ecb8abb98e | |||
| a3a4fabd5a | |||
| edf6ce273c | |||
| 1bb956a8b0 |
@@ -16,18 +16,21 @@ jobs:
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 21
|
||||
|
||||
- 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}"
|
||||
JADX_REV=$(git rev-list --count HEAD)
|
||||
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: dist copyExe
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew dist distWin
|
||||
env:
|
||||
JADX_BUILD_JAVA_VERSION: 11
|
||||
|
||||
- name: Save bundle artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -37,15 +40,16 @@ jobs:
|
||||
# Upload unpacked files for now
|
||||
path: build/jadx/**/*
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
retention-days: 14
|
||||
|
||||
- name: Save exe artifact
|
||||
- name: Save Windows bundle artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||
path: build/*.exe
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win', env.JADX_VERSION) }}
|
||||
# Upload unpacked files for now
|
||||
path: jadx-gui/build/jadx-gui-win/*
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
retention-days: 14
|
||||
|
||||
build-win-bundle:
|
||||
runs-on: windows-latest
|
||||
@@ -57,7 +61,7 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: oracle-actions/setup-java@v1
|
||||
with:
|
||||
release: 17
|
||||
release: 21
|
||||
|
||||
- name: Print Java version
|
||||
shell: bash
|
||||
@@ -66,19 +70,21 @@ jobs:
|
||||
- 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}"
|
||||
JADX_REV=$(git rev-list --count HEAD)
|
||||
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: dist -PbundleJRE=true
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Save exe bundle artifact
|
||||
- name: Build
|
||||
run: ./gradlew dist -PbundleJRE=true
|
||||
|
||||
- name: Save Windows with JRE bundle artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
path: jadx-gui/build/*-with-jre-win/*
|
||||
# Upload unpacked files for now
|
||||
path: jadx-gui/build/jadx-gui-with-jre-win/*
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
retention-days: 14
|
||||
|
||||
@@ -20,9 +20,13 @@ jobs:
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew build dist distWin
|
||||
env:
|
||||
JADX_BUILD_JAVA_VERSION: 11
|
||||
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: build dist copyExe
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
name: Validate Gradle Wrapper
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, build-test ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: Validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
@@ -0,0 +1,92 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
# additional permissions for provided GitHub token to create new release
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-release-win-bundle:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK
|
||||
uses: oracle-actions/setup-java@v1
|
||||
with:
|
||||
release: 23
|
||||
|
||||
- name: Set jadx version
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const jadxVersion = context.ref.split('/').pop().substring(1)
|
||||
core.exportVariable('JADX_VERSION', jadxVersion);
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew dist -PbundleJRE=true
|
||||
|
||||
- name: Save JRE bundle artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
path: ${{ format('build/distWinWithJre/jadx-gui-{0}-with-jre-win.zip', env.JADX_VERSION) }}
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
release:
|
||||
needs: build-release-win-bundle
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Set jadx version and release name
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const jadxVersion = context.ref.split('/').pop().substring(1)
|
||||
core.exportVariable('JADX_VERSION', jadxVersion);
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew dist distWin
|
||||
env:
|
||||
JADX_BUILD_JAVA_VERSION: 11
|
||||
|
||||
- name: Download Windows JRE bundle
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
path: ${{ format('build/jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
|
||||
- run: |
|
||||
cd build
|
||||
pwd
|
||||
ls -l
|
||||
ls jadx-gui-*-with-jre-win
|
||||
mv jadx-gui-*-with-jre-win/jadx-gui-*-with-jre-win.zip .
|
||||
mv distWin/jadx-gui-*-win.zip .
|
||||
ls -l *.zip
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: ${{ env.JADX_VERSION }}
|
||||
draft: true
|
||||
fail_on_unmatched_files: true
|
||||
files: build/jadx-*.zip
|
||||
@@ -28,6 +28,7 @@ jadx-output/
|
||||
*-tmp/
|
||||
**/tmp/
|
||||
*.jobf
|
||||
*.jadx
|
||||
|
||||
*.class
|
||||
*.dump
|
||||
|
||||
+3
-3
@@ -11,14 +11,14 @@ stages:
|
||||
java-11:
|
||||
stage: test
|
||||
image: eclipse-temurin:11
|
||||
script: ./gradlew clean build dist copyExe
|
||||
script: ./gradlew clean build dist distWin
|
||||
|
||||
java-17:
|
||||
stage: test
|
||||
image: eclipse-temurin:17
|
||||
script: ./gradlew clean build dist copyExe
|
||||
script: ./gradlew clean build dist distWin
|
||||
|
||||
java-21:
|
||||
stage: test
|
||||
image: eclipse-temurin:21
|
||||
script: ./gradlew clean build dist copyExe
|
||||
script: ./gradlew clean build dist distWin
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<module name="jadx.jadx-gui.main"/>
|
||||
<option name="PROGRAM_PARAMETERS" value="-v"/>
|
||||
<option name="VM_PARAMETERS"
|
||||
value="-Xms128M -XX:MaxRAMPercentage=70.0 -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true -Djava.util.Arrays.useLegacyMergeSort=true -Djdk.util.zip.disableZip64ExtraFieldValidation=true -XX:+IgnoreUnrecognizedVMOptions --add-opens=java.base/java.lang=ALL-UNNAMED"/>
|
||||
value="-Xms128M -XX:MaxRAMPercentage=70.0 -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true -Djava.util.Arrays.useLegacyMergeSort=true -Djdk.util.zip.disableZip64ExtraFieldValidation=true -XX:+IgnoreUnrecognizedVMOptions --add-opens=java.base/java.lang=ALL-UNNAMED -Dsun.java2d.noddraw=true -Dsun.java2d.d3d=false -Dsun.java2d.ddforcevram=true -Dsun.java2d.ddblit=false -Dswing.useflipBufferStrategy=True"/>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true"/>
|
||||
</method>
|
||||
|
||||
+10
-23
@@ -3,7 +3,7 @@
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
@@ -23,13 +23,13 @@ include:
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
@@ -45,25 +45,12 @@ threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at skylot@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Attribution
|
||||
|
||||
|
||||
@@ -8,16 +8,19 @@
|
||||

|
||||

|
||||
[](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
|
||||

|
||||
[](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
|
||||
> [!WARNING]
|
||||
> Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur.<br />
|
||||
> 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
|
||||
- decompile Dalvik bytecode to Java code from APK, dex, aar, aab and zip files
|
||||
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
||||
- deobfuscator included
|
||||
|
||||
@@ -48,18 +51,22 @@ On Windows run `.bat` files with double-click\
|
||||
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 
|
||||
```bash
|
||||
sudo pacman -S jadx
|
||||
```
|
||||
2. macOS 
|
||||
```bash
|
||||
brew install jadx
|
||||
```
|
||||
3. [Flathub ](https://flathub.org/apps/details/com.github.skylot.jadx)
|
||||
```bash
|
||||
flatpak install flathub com.github.skylot.jadx
|
||||
```
|
||||
- Arch Linux
|
||||
[](https://archlinux.org/packages/extra/any/jadx/)
|
||||
[](https://aur.archlinux.org/packages/jadx-git)
|
||||
```bash
|
||||
sudo pacman -S jadx
|
||||
```
|
||||
- macOS
|
||||
[](https://formulae.brew.sh/formula/jadx)
|
||||
```bash
|
||||
brew install jadx
|
||||
```
|
||||
- Flathub
|
||||
[](https://flathub.org/apps/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)
|
||||
@@ -79,104 +86,113 @@ and also packed to `build/jadx-<version>.zip`
|
||||
|
||||
### Usage
|
||||
```
|
||||
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk)
|
||||
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)
|
||||
commands (use '<command> --help' for command options):
|
||||
plugins - manage jadx plugins
|
||||
|
||||
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-xml-pretty-print - do not prettify XML
|
||||
--no-imports - disable use of imports, always write entire package name
|
||||
--no-debug-info - disable debug info parsing and processing
|
||||
--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-move-inner-classes - disable move inner classes into parent
|
||||
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
|
||||
--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
|
||||
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
|
||||
--mappings-mode - set mode for handling the deobfuscation mapping file:
|
||||
'read' - just read, user can always save manually (default)
|
||||
'read-and-autosave-every-change' - read and autosave after every change
|
||||
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
|
||||
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
|
||||
--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-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
|
||||
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
|
||||
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' 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-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)
|
||||
--integer-format - how integers are displayed:
|
||||
'auto' - automatically select (default)
|
||||
'decimal' - use decimal
|
||||
'hexadecimal' - use hexadecimal
|
||||
--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
|
||||
-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-xml-pretty-print - do not prettify XML
|
||||
--no-imports - disable use of imports, always write entire package name
|
||||
--no-debug-info - disable debug info parsing and processing
|
||||
--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-move-inner-classes - disable move inner classes into parent
|
||||
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
|
||||
--no-finally - don't extract finally block
|
||||
--no-restore-switch-over-string - don't restore switch over string
|
||||
--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
|
||||
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
|
||||
--mappings-mode - set mode for handling the deobfuscation mapping file:
|
||||
'read' - just read, user can always save manually (default)
|
||||
'read-and-autosave-every-change' - read and autosave after every change
|
||||
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
|
||||
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
|
||||
--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-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
|
||||
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
|
||||
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' 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-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-source-name-as-class-name-alias - use source name as class name alias:
|
||||
'always' - always use source name if it's available
|
||||
'if-better' - use source name if it seems better than the current one
|
||||
'never' - never use source name, even if it's available
|
||||
--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)
|
||||
--integer-format - how integers are displayed:
|
||||
'auto' - automatically select (default)
|
||||
'decimal' - use decimal
|
||||
'hexadecimal' - use hexadecimal
|
||||
--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<name>=<value>):
|
||||
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
|
||||
3) kotlin-metadata: Use kotlin.Metadata annotation for code generation
|
||||
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
|
||||
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
|
||||
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
|
||||
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
|
||||
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
|
||||
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
|
||||
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
||||
4) rename-mappings: various mappings support
|
||||
- rename-mappings.format - mapping format, values: [auto, TINY, TINY_2, ENIGMA, ENIGMA_DIR, MCP, SRG, TSRG, TSRG2, PROGUARD], default: auto
|
||||
- rename-mappings.invert - invert mapping, values: [yes, no], default: no
|
||||
dex-input: Load .dex and .apk files
|
||||
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
|
||||
java-convert: Convert .class, .jar and .aar files to dex
|
||||
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
||||
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
||||
kotlin-metadata: Use kotlin.Metadata annotation for code generation
|
||||
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
|
||||
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
|
||||
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
|
||||
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
|
||||
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
|
||||
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
|
||||
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
||||
rename-mappings: various mappings support
|
||||
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
|
||||
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
|
||||
smali-input: Load .smali files
|
||||
- smali-input.api-level - Android API level, default: 27
|
||||
|
||||
Environment variables:
|
||||
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
|
||||
JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files
|
||||
JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)
|
||||
JADX_CONFIG_DIR - custom config directory, using system by default
|
||||
JADX_CACHE_DIR - custom cache directory, using system by default
|
||||
JADX_TMP_DIR - custom temp directory, using system by default
|
||||
|
||||
Examples:
|
||||
@@ -186,7 +202,7 @@ Examples:
|
||||
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
|
||||
These options also work in 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)
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security issue, please email `skylot@gmail.com` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
|
||||
We will check and respond within 3 working days. If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
|
||||
This project follows a 90 day disclosure timeline.
|
||||
To report a security issue, please open a [new security advisory](https://github.com/skylot/jadx/security/advisories/new).
|
||||
Please fill the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
|
||||
We will check and respond within 3 working days.
|
||||
If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
|
||||
|
||||
+33
-24
@@ -15,6 +15,18 @@ val jadxVersion by extra { System.getenv("JADX_VERSION") ?: "dev" }
|
||||
println("jadx version: $jadxVersion")
|
||||
version = jadxVersion
|
||||
|
||||
val jadxBuildJavaVersion by extra { getBuildJavaVersion() }
|
||||
|
||||
fun getBuildJavaVersion(): Int? {
|
||||
val envVarName = "JADX_BUILD_JAVA_VERSION"
|
||||
val buildJavaVer = System.getenv(envVarName)?.toInt() ?: return null
|
||||
if (buildJavaVer < 11) {
|
||||
throw GradleException("'$envVarName' can't be set to lower than 11")
|
||||
}
|
||||
println("Set Java toolchain for jadx build to version '$buildJavaVer'")
|
||||
return buildJavaVer
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply(plugin = "java")
|
||||
apply(plugin = "checkstyle")
|
||||
@@ -89,6 +101,10 @@ val copyArtifacts by tasks.registering(Copy::class) {
|
||||
include("**/*.jar")
|
||||
rename("jadx-gui-(.*)-all.jar", "jadx-$1-all.jar")
|
||||
}
|
||||
from(layout.projectDirectory) {
|
||||
include("README.md")
|
||||
include("LICENSE")
|
||||
}
|
||||
into(layout.buildDirectory.dir("jadx"))
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
@@ -99,40 +115,34 @@ val pack by tasks.registering(Zip::class) {
|
||||
destinationDirectory.set(layout.buildDirectory)
|
||||
}
|
||||
|
||||
val copyExe by tasks.registering(Copy::class) {
|
||||
val distWin by tasks.registering(Zip::class) {
|
||||
group = "jadx"
|
||||
description = "Copy exe to build dir"
|
||||
description = "Build Windows bundle"
|
||||
|
||||
// next task dependencies not needed, but gradle throws warning because of same output dir
|
||||
mustRunAfter("jar")
|
||||
mustRunAfter(pack)
|
||||
val guiTask = tasks.getByPath("jadx-gui:copyDistWin")
|
||||
dependsOn(guiTask)
|
||||
from(guiTask.outputs)
|
||||
|
||||
from(tasks.getByPath("jadx-gui:createExe"))
|
||||
include("*.exe")
|
||||
into(layout.buildDirectory)
|
||||
destinationDirectory.set(layout.buildDirectory.dir("distWin"))
|
||||
archiveFileName.set("jadx-gui-$jadxVersion-win.zip")
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
val distWinBundle by tasks.registering(Copy::class) {
|
||||
group = "jadx"
|
||||
description = "Copy bundle to build dir"
|
||||
val distWinWithJre by tasks.registering(Zip::class) {
|
||||
description = "Build Windows with JRE bundle"
|
||||
|
||||
dependsOn(tasks.getByPath(":jadx-gui:distWinWithJre"))
|
||||
val guiTask = tasks.getByPath(":jadx-gui:copyDistWinWithJre")
|
||||
dependsOn(guiTask)
|
||||
from(guiTask.outputs)
|
||||
|
||||
// next task dependencies not needed, but gradle throws warning because of same output dir
|
||||
mustRunAfter("jar")
|
||||
mustRunAfter(pack)
|
||||
|
||||
from(tasks.getByPath("jadx-gui:distWinWithJre").outputs) {
|
||||
include("*.zip")
|
||||
}
|
||||
into(layout.buildDirectory)
|
||||
destinationDirectory.set(layout.buildDirectory.dir("distWinWithJre"))
|
||||
archiveFileName.set("jadx-gui-$jadxVersion-with-jre-win.zip")
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
val dist by tasks.registering {
|
||||
group = "jadx"
|
||||
description = "Build jadx distribution zip"
|
||||
description = "Build jadx distribution zip bundles"
|
||||
|
||||
dependsOn(pack)
|
||||
|
||||
@@ -140,15 +150,14 @@ val dist by tasks.registering {
|
||||
if (os.isWindows) {
|
||||
if (project.hasProperty("bundleJRE")) {
|
||||
println("Build win bundle with JRE")
|
||||
dependsOn(distWinBundle)
|
||||
dependsOn(distWinWithJre)
|
||||
} else {
|
||||
dependsOn(copyExe)
|
||||
dependsOn(distWin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val cleanBuildDir by tasks.registering(Delete::class) {
|
||||
group = "jadx"
|
||||
delete(layout.buildDirectory)
|
||||
}
|
||||
tasks.getByName("clean").dependsOn(cleanBuildDir)
|
||||
|
||||
@@ -3,7 +3,9 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
|
||||
|
||||
implementation("org.openrewrite:plugin:6.19.1")
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -3,26 +3,27 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
plugins {
|
||||
java
|
||||
checkstyle
|
||||
|
||||
id("jadx-rewrite")
|
||||
}
|
||||
|
||||
val jadxVersion: String by rootProject.extra
|
||||
val jadxBuildJavaVersion: Int? by rootProject.extra
|
||||
|
||||
group = "io.github.skylot"
|
||||
version = jadxVersion
|
||||
|
||||
dependencies {
|
||||
implementation("org.slf4j:slf4j-api:2.0.11")
|
||||
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||
implementation("org.slf4j:slf4j-api:2.0.16")
|
||||
compileOnly("org.jetbrains:annotations:26.0.1")
|
||||
|
||||
testImplementation("ch.qos.logback:logback-classic:1.4.14")
|
||||
testImplementation("org.hamcrest:hamcrest-library:2.2")
|
||||
testImplementation("org.mockito:mockito-core:5.10.0")
|
||||
testImplementation("org.assertj:assertj-core:3.25.2")
|
||||
testImplementation("ch.qos.logback:logback-classic:1.5.12")
|
||||
testImplementation("org.assertj:assertj-core:3.26.3")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.11.3")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
|
||||
testCompileOnly("org.jetbrains:annotations:24.1.0")
|
||||
testCompileOnly("org.jetbrains:annotations:26.0.1")
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -32,6 +33,11 @@ repositories {
|
||||
}
|
||||
|
||||
java {
|
||||
jadxBuildJavaVersion?.let { buildJavaVer ->
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(buildJavaVer)
|
||||
}
|
||||
}
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ publishing {
|
||||
}
|
||||
pom {
|
||||
name.set(project.name)
|
||||
description.set("Dex to Java decompiler")
|
||||
description.set(project.description ?: "Dex to Java decompiler")
|
||||
url.set("https://github.com/skylot/jadx")
|
||||
licenses {
|
||||
license {
|
||||
@@ -42,14 +42,14 @@ publishing {
|
||||
developer {
|
||||
id.set("skylot")
|
||||
name.set("Skylot")
|
||||
email.set("skylot@gmail.com")
|
||||
email.set(project.properties["libEmail"].toString())
|
||||
url.set("https://github.com/skylot")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection .set("scm:git:git://github.com/skylot/jadx.git")
|
||||
connection.set("scm:git:git://github.com/skylot/jadx.git")
|
||||
developerConnection.set("scm:git:ssh://github.com:skylot/jadx.git")
|
||||
url .set("https://github.com/skylot/jadx")
|
||||
url.set("https://github.com/skylot/jadx")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
|
||||
plugins {
|
||||
id("org.openrewrite.rewrite")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:2.21.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-logging-frameworks:2.15.1")
|
||||
rewrite("org.openrewrite.recipe:rewrite-migrate-java:2.28.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-static-analysis:1.19.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
rewrite {
|
||||
// exclusion("src/test/java/jadx/tests/integration")
|
||||
|
||||
// activeRecipe("org.openrewrite.java.migrate.Java8toJava11")
|
||||
|
||||
// checkstyle auto fix
|
||||
// activeRecipe("org.openrewrite.staticanalysis.CodeCleanup")
|
||||
// setCheckstyleConfigFile(file("$rootDir/config/checkstyle/checkstyle.xml"))
|
||||
|
||||
// logging
|
||||
// activeRecipe("org.openrewrite.java.logging.slf4j.Slf4jBestPractices")
|
||||
// activeRecipe("org.openrewrite.java.logging.slf4j.LoggersNamedForEnclosingClass")
|
||||
// activeRecipe("org.openrewrite.java.logging.slf4j.ParameterizedLogging")
|
||||
// activeRecipe("org.openrewrite.java.logging.PrintStackTraceToLogError")
|
||||
|
||||
// testing
|
||||
activeRecipe("org.openrewrite.java.testing.assertj.Assertj")
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,10 @@
|
||||
|
||||
<module name="SuppressWarningsHolder"/>
|
||||
|
||||
<module name="IllegalType"/>
|
||||
<module name="IllegalType">
|
||||
<property name="illegalClassNames" value="java.util.ArrayList, java.util.HashMap, java.util.HashSet,
|
||||
java.util.LinkedHashMap, java.util.LinkedHashSet, java.util.TreeMap, java.util.TreeSet"/>
|
||||
</module>
|
||||
<module name="IllegalImport">
|
||||
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
|
||||
</module>
|
||||
@@ -128,7 +131,8 @@
|
||||
<property name="id" value="printstacktrace"/>
|
||||
<property name="format" value="\.printStackTrace\(\)"/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
<property name="message" value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
|
||||
<property name="message"
|
||||
value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Generated on 11/22/21, 8:58 PM
|
||||
*/
|
||||
package jadx.gui.ui.codeearea;
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.io.*;
|
||||
import javax.swing.text.Segment;
|
||||
@@ -9,7 +9,7 @@ import javax.swing.text.Segment;
|
||||
import org.fife.ui.rsyntaxtextarea.*;
|
||||
|
||||
|
||||
/**
|
||||
/*
|
||||
* 用于Smali代码高亮
|
||||
* MartinKay@qq.com
|
||||
*/
|
||||
@@ -173,7 +173,6 @@ import org.fife.ui.rsyntaxtextarea.*;
|
||||
zzAtEOF = false;
|
||||
}
|
||||
|
||||
|
||||
%}
|
||||
|
||||
Letter = [A-Za-z]
|
||||
@@ -678,4 +677,3 @@ FLAG_ARRAY = (":array_"{SimpleName})
|
||||
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
|
||||
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
|
||||
}
|
||||
|
||||
|
||||
@@ -57,12 +57,12 @@
|
||||
private int yychar;
|
||||
|
||||
/**
|
||||
* the number of characters from the last newline up to the start of the
|
||||
* the number of characters from the last newline up to the start of the
|
||||
* matched text
|
||||
*/
|
||||
private int yycolumn;
|
||||
|
||||
/**
|
||||
/**
|
||||
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
|
||||
*/
|
||||
private boolean zzAtBOL = true;
|
||||
@@ -102,6 +102,9 @@
|
||||
zzLexicalState = newState;
|
||||
}
|
||||
|
||||
public final int yystate() {
|
||||
return zzLexicalState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text matched by the current regular expression.
|
||||
@@ -112,12 +115,12 @@
|
||||
|
||||
|
||||
/**
|
||||
* Returns the character at position <tt>pos</tt> from the
|
||||
* matched text.
|
||||
*
|
||||
* Returns the character at position <tt>pos</tt> from the
|
||||
* matched text.
|
||||
*
|
||||
* It is equivalent to yytext().charAt(pos), but faster
|
||||
*
|
||||
* @param pos the position of the character to fetch.
|
||||
* @param pos the position of the character to fetch.
|
||||
* A value from 0 to yylength()-1.
|
||||
*
|
||||
* @return the character at position pos
|
||||
@@ -138,8 +141,8 @@
|
||||
/**
|
||||
* Reports an error that occured while scanning.
|
||||
*
|
||||
* In a wellformed scanner (no or only correct usage of
|
||||
* yypushback(int) and a match-all fallback rule) this method
|
||||
* In a wellformed scanner (no or only correct usage of
|
||||
* yypushback(int) and a match-all fallback rule) this method
|
||||
* will only be called with things that "Can't Possibly Happen".
|
||||
* If this method is called, something is seriously wrong
|
||||
* (e.g. a JFlex bug producing a faulty scanner etc.).
|
||||
@@ -159,7 +162,7 @@
|
||||
}
|
||||
|
||||
--- throws clause
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -206,12 +209,12 @@
|
||||
zzAction = -1;
|
||||
|
||||
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
|
||||
|
||||
|
||||
--- start admin (lexstate etc)
|
||||
|
||||
zzForAction: {
|
||||
while (true) {
|
||||
|
||||
|
||||
--- next input, line, col, char count, next transition, isFinal action
|
||||
zzAction = zzState;
|
||||
zzMarkedPosL = zzCurrentPosL;
|
||||
@@ -226,11 +229,11 @@
|
||||
--- char count update
|
||||
|
||||
--- actions
|
||||
default:
|
||||
default:
|
||||
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
|
||||
zzAtEOF = true;
|
||||
--- eofvalue
|
||||
}
|
||||
}
|
||||
else {
|
||||
--- no match
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,7 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,8 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
Vendored
+12
-10
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
plugins {
|
||||
id("jadx-java")
|
||||
id("jadx-library")
|
||||
id("application")
|
||||
|
||||
// use shadow only for application scripts, jar will be copied from jadx-gui
|
||||
id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||
id("com.gradleup.shadow") version "8.3.5"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":jadx-core"))
|
||||
implementation(project(":jadx-plugins-tools"))
|
||||
implementation(project(":jadx-commons:jadx-app-commons"))
|
||||
|
||||
runtimeOnly(project(":jadx-plugins:jadx-dex-input"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-java-input"))
|
||||
@@ -18,9 +20,10 @@ dependencies {
|
||||
runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-script:jadx-script-plugin"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
|
||||
|
||||
implementation("org.jcommander:jcommander:1.83")
|
||||
implementation("ch.qos.logback:logback-classic:1.4.14")
|
||||
implementation("org.jcommander:jcommander:2.0")
|
||||
implementation("ch.qos.logback:logback-classic:1.5.12")
|
||||
}
|
||||
|
||||
application {
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -13,11 +13,11 @@ import java.util.function.Supplier;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParameterDescription;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.beust.jcommander.Parameterized;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
@@ -109,8 +109,11 @@ public class JCommanderWrapper<T> {
|
||||
out.println(appendPluginOptions(maxNamesLen));
|
||||
out.println();
|
||||
out.println("Environment variables:");
|
||||
out.println(" JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files");
|
||||
out.println(" JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files");
|
||||
out.println(" JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)");
|
||||
out.println(" JADX_CONFIG_DIR - custom config directory, using system by default");
|
||||
out.println(" JADX_CACHE_DIR - custom cache directory, using system by default");
|
||||
out.println(" JADX_TMP_DIR - custom temp directory, using system by default");
|
||||
out.println();
|
||||
out.println("Examples:");
|
||||
@@ -131,14 +134,16 @@ public class JCommanderWrapper<T> {
|
||||
out.println("options:");
|
||||
|
||||
List<ParameterDescription> params = jc.getParameters();
|
||||
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
|
||||
Map<String, ParameterDescription> paramsMap = new HashMap<>(params.size());
|
||||
int maxNamesLen = 0;
|
||||
for (ParameterDescription p : params) {
|
||||
paramsMap.put(p.getParameterized().getName(), p);
|
||||
int len = p.getNames().length();
|
||||
if (len > maxNamesLen) {
|
||||
maxNamesLen = len;
|
||||
String valueDesc = getValueDesc(p);
|
||||
if (valueDesc != null) {
|
||||
len += 1 + valueDesc.length();
|
||||
}
|
||||
maxNamesLen = Math.max(maxNamesLen, len);
|
||||
}
|
||||
maxNamesLen += 3;
|
||||
|
||||
@@ -151,8 +156,12 @@ public class JCommanderWrapper<T> {
|
||||
}
|
||||
StringBuilder opt = new StringBuilder();
|
||||
opt.append(" ").append(p.getNames());
|
||||
String description = p.getDescription();
|
||||
String valueDesc = getValueDesc(p);
|
||||
if (valueDesc != null) {
|
||||
opt.append(' ').append(valueDesc);
|
||||
}
|
||||
addSpaces(opt, maxNamesLen - opt.length());
|
||||
String description = p.getDescription();
|
||||
if (description.contains("\n")) {
|
||||
String[] lines = description.split("\n");
|
||||
opt.append("- ").append(lines[0]);
|
||||
@@ -165,7 +174,7 @@ public class JCommanderWrapper<T> {
|
||||
opt.append("- ").append(description);
|
||||
}
|
||||
if (addDefaults) {
|
||||
String defaultValue = getDefaultValue(args, f, opt);
|
||||
String defaultValue = getDefaultValue(args, f);
|
||||
if (defaultValue != null && !description.contains("(default)")) {
|
||||
opt.append(", default: ").append(defaultValue);
|
||||
}
|
||||
@@ -175,6 +184,11 @@ public class JCommanderWrapper<T> {
|
||||
return maxNamesLen;
|
||||
}
|
||||
|
||||
private static @Nullable String getValueDesc(ParameterDescription p) {
|
||||
Parameter parameterAnnotation = p.getParameterAnnotation();
|
||||
return parameterAnnotation == null ? null : parameterAnnotation.defaultValueDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all declared fields of the specified class and all super classes
|
||||
*/
|
||||
@@ -188,7 +202,7 @@ public class JCommanderWrapper<T> {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getDefaultValue(Object args, Field f, StringBuilder opt) {
|
||||
private static String getDefaultValue(Object args, Field f) {
|
||||
try {
|
||||
Class<?> fieldType = f.getType();
|
||||
if (fieldType == int.class) {
|
||||
@@ -219,14 +233,14 @@ public class JCommanderWrapper<T> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int k = 1;
|
||||
// load and init all options plugins to print all options
|
||||
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
|
||||
try (JadxDecompiler decompiler = new JadxDecompiler(argsObj.toJadxArgs())) {
|
||||
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
||||
pluginManager.load(new JadxExternalPluginsLoader());
|
||||
pluginManager.initAll();
|
||||
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
||||
JadxPluginOptions options = context.getOptions();
|
||||
if (options != null) {
|
||||
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen, k)) {
|
||||
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen)) {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
@@ -238,12 +252,12 @@ public class JCommanderWrapper<T> {
|
||||
return "\nPlugin options (-P<name>=<value>):" + sb;
|
||||
}
|
||||
|
||||
private boolean appendPlugin(JadxPluginInfo pluginInfo, JadxPluginOptions options, StringBuilder out, int maxNamesLen, int k) {
|
||||
private boolean appendPlugin(JadxPluginInfo pluginInfo, JadxPluginOptions options, StringBuilder out, int maxNamesLen) {
|
||||
List<OptionDescription> descs = options.getOptionsDescriptions();
|
||||
if (descs.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
out.append("\n ").append(k).append(") ");
|
||||
out.append("\n ");
|
||||
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
|
||||
for (OptionDescription desc : descs) {
|
||||
StringBuilder opt = new StringBuilder();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -8,36 +10,40 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.NoOpCodeCache;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.api.security.JadxSecurityFlag;
|
||||
import jadx.api.security.impl.JadxSecurity;
|
||||
import jadx.cli.LogHelper.LogLevelEnum;
|
||||
import jadx.cli.plugins.JadxFilesGetter;
|
||||
import jadx.commons.app.JadxCommonEnv;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||
|
||||
public class JadxCLI {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
int result = 0;
|
||||
int result = 1;
|
||||
try {
|
||||
result = execute(args);
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
result = 1;
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Process error:", e);
|
||||
result = 1;
|
||||
} finally {
|
||||
FileUtils.deleteTempRootDir();
|
||||
System.exit(result);
|
||||
}
|
||||
}
|
||||
|
||||
public static int execute(String[] args) {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
return processAndSave(jadxArgs);
|
||||
try {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
return processAndSave(jadxArgs);
|
||||
}
|
||||
return 0;
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
return 1;
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Process error:", e);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int processAndSave(JadxCLIArgs cliArgs) {
|
||||
@@ -46,7 +52,9 @@ public class JadxCLI {
|
||||
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
|
||||
jadxArgs.setFilesGetter(JadxFilesGetter.INSTANCE);
|
||||
initCodeWriterProvider(jadxArgs);
|
||||
applyEnvVars(jadxArgs);
|
||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||
jadx.load();
|
||||
if (checkForErrors(jadx)) {
|
||||
@@ -60,11 +68,11 @@ public class JadxCLI {
|
||||
if (errorsCount != 0) {
|
||||
jadx.printErrorsReport();
|
||||
LOG.error("finished with errors, count: {}", errorsCount);
|
||||
} else {
|
||||
LOG.info("done");
|
||||
return 1;
|
||||
}
|
||||
LOG.info("done");
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void initCodeWriterProvider(JadxArgs jadxArgs) {
|
||||
@@ -79,6 +87,22 @@ public class JadxCLI {
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyEnvVars(JadxArgs jadxArgs) {
|
||||
Set<JadxSecurityFlag> flags = JadxSecurityFlag.all();
|
||||
boolean modified = false;
|
||||
boolean disableXmlSecurity = JadxCommonEnv.getBool("JADX_DISABLE_XML_SECURITY", false);
|
||||
if (disableXmlSecurity) {
|
||||
flags.remove(JadxSecurityFlag.SECURE_XML_PARSER);
|
||||
// TODO: not related to 'xml security', but kept for compatibility
|
||||
flags.remove(JadxSecurityFlag.VERIFY_APP_PACKAGE);
|
||||
modified = true;
|
||||
}
|
||||
// TODO: migrate 'ZipSecurity'
|
||||
if (modified) {
|
||||
jadxArgs.setSecurity(new JadxSecurity(flags));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkForErrors(JadxDecompiler jadx) {
|
||||
if (jadx.getRoot().getClasses().isEmpty()) {
|
||||
if (jadx.getArgs().isSkipResources()) {
|
||||
|
||||
@@ -27,14 +27,15 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
||||
import jadx.api.args.IntegerFormat;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||
import jadx.api.args.UserRenamesMappingsMode;
|
||||
import jadx.core.deobf.conditions.DeobfWhitelist;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxCLIArgs {
|
||||
|
||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk)")
|
||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)")
|
||||
protected List<String> files = new ArrayList<>(1);
|
||||
|
||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||
@@ -108,6 +109,9 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = "--no-finally", description = "don't extract finally block")
|
||||
protected boolean extractFinally = true;
|
||||
|
||||
@Parameter(names = "--no-restore-switch-over-string", description = "don't restore switch over string")
|
||||
protected boolean restoreSwitchOverString = true;
|
||||
|
||||
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
||||
protected boolean replaceConsts = true;
|
||||
|
||||
@@ -166,8 +170,15 @@ public class JadxCLIArgs {
|
||||
)
|
||||
protected GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
||||
|
||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
@SuppressWarnings("DeprecatedIsStillUsed")
|
||||
@Parameter(
|
||||
names = { "--deobf-use-sourcename" },
|
||||
description = "use source file name as class name alias."
|
||||
+ "\nDEPRECATED, use \"--use-source-name-as-class-name-alias\" instead",
|
||||
hidden = true
|
||||
)
|
||||
@Deprecated
|
||||
protected Boolean deobfuscationUseSourceNameAsAlias = null;
|
||||
|
||||
@Parameter(
|
||||
names = { "--deobf-res-name-source" },
|
||||
@@ -179,6 +190,16 @@ public class JadxCLIArgs {
|
||||
)
|
||||
protected ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
||||
|
||||
@Parameter(
|
||||
names = { "--use-source-name-as-class-name-alias" },
|
||||
description = "use source name as class name alias:"
|
||||
+ "\n 'always' - always use source name if it's available"
|
||||
+ "\n 'if-better' - use source name if it seems better than the current one"
|
||||
+ "\n 'never' - never use source name, even if it's available",
|
||||
converter = UseSourceNameAsClassNameConverter.class
|
||||
)
|
||||
protected UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias = null;
|
||||
|
||||
@Parameter(
|
||||
names = { "--use-kotlin-methods-for-var-names" },
|
||||
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
||||
@@ -243,6 +264,9 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
||||
protected boolean quiet = false;
|
||||
|
||||
@Parameter(names = { "--disable-plugins" }, description = "comma separated list of plugin ids to disable")
|
||||
protected String disablePlugins = "";
|
||||
|
||||
@Parameter(names = { "--version" }, description = "print jadx version")
|
||||
protected boolean printVersion = false;
|
||||
|
||||
@@ -287,14 +311,13 @@ public class JadxCLIArgs {
|
||||
System.out.println(JadxDecompiler.getVersion());
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (threadsCount <= 0) {
|
||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
||||
if (threadsCount <= 0) {
|
||||
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
|
||||
}
|
||||
for (String fileName : files) {
|
||||
if (fileName.startsWith("-")) {
|
||||
throw new JadxArgsValidateException("Unknown option: " + fileName);
|
||||
}
|
||||
} catch (JadxException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
jcw.printUsage();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -328,7 +351,7 @@ public class JadxCLIArgs {
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setDeobfuscationWhitelist(Arrays.asList(deobfuscationWhitelistStr.split(" ")));
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setUseSourceNameAsClassNameAlias(getUseSourceNameAsClassNameAlias());
|
||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||
args.setResourceNameSource(resourceNameSource);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
@@ -343,12 +366,14 @@ public class JadxCLIArgs {
|
||||
args.setMoveInnerClasses(moveInnerClasses);
|
||||
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
|
||||
args.setExtractFinally(extractFinally);
|
||||
args.setRestoreSwitchOverString(restoreSwitchOverString);
|
||||
args.setRenameFlags(renameFlags);
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
args.setCommentsLevel(commentsLevel);
|
||||
args.setIntegerFormat(integerFormat);
|
||||
args.setUseDxInput(useDx);
|
||||
args.setPluginOptions(pluginOptions);
|
||||
args.setDisabledPlugins(Arrays.stream(disablePlugins.split(",")).map(String::trim).collect(Collectors.toSet()));
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -436,6 +461,10 @@ public class JadxCLIArgs {
|
||||
return extractFinally;
|
||||
}
|
||||
|
||||
public boolean isRestoreSwitchOverString() {
|
||||
return restoreSwitchOverString;
|
||||
}
|
||||
|
||||
public Path getUserRenamesMappingsPath() {
|
||||
return userRenamesMappingsPath;
|
||||
}
|
||||
@@ -468,8 +497,23 @@ public class JadxCLIArgs {
|
||||
return generatedRenamesMappingFileMode;
|
||||
}
|
||||
|
||||
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
|
||||
if (useSourceNameAsClassNameAlias != null) {
|
||||
return useSourceNameAsClassNameAlias;
|
||||
} else if (deobfuscationUseSourceNameAsAlias != null) {
|
||||
// noinspection deprecation
|
||||
return UseSourceNameAsClassNameAlias.create(deobfuscationUseSourceNameAsAlias);
|
||||
} else {
|
||||
return UseSourceNameAsClassNameAlias.getDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getUseSourceNameAsClassNameAlias()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
return getUseSourceNameAsClassNameAlias().toBoolean();
|
||||
}
|
||||
|
||||
public ResourceNameSource getResourceNameSource() {
|
||||
@@ -540,6 +584,10 @@ public class JadxCLIArgs {
|
||||
return pluginOptions;
|
||||
}
|
||||
|
||||
public String getDisablePlugins() {
|
||||
return disablePlugins;
|
||||
}
|
||||
|
||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||
private final String paramName;
|
||||
|
||||
@@ -559,8 +607,8 @@ public class JadxCLIArgs {
|
||||
for (String s : value.split(",")) {
|
||||
try {
|
||||
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(
|
||||
} catch (Exception e) {
|
||||
throw new JadxArgsValidateException(
|
||||
'\'' + s + "' is unknown for parameter " + paramName
|
||||
+ ", possible values are " + enumValuesString(RenameEnum.values()));
|
||||
}
|
||||
@@ -593,6 +641,12 @@ public class JadxCLIArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public static class UseSourceNameAsClassNameConverter extends BaseEnumConverter<UseSourceNameAsClassNameAlias> {
|
||||
public UseSourceNameAsClassNameConverter() {
|
||||
super(UseSourceNameAsClassNameAlias::valueOf, UseSourceNameAsClassNameAlias::values);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DecompilationModeConverter extends BaseEnumConverter<DecompilationMode> {
|
||||
public DecompilationModeConverter() {
|
||||
super(DecompilationMode::valueOf, DecompilationMode::values);
|
||||
@@ -625,7 +679,7 @@ public class JadxCLIArgs {
|
||||
try {
|
||||
return parse.apply(stringAsEnumName(value));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(
|
||||
throw new JadxArgsValidateException(
|
||||
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
|
||||
import jadx.cli.commands.CommandPlugins;
|
||||
import jadx.cli.commands.ICommand;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
|
||||
public class JadxCLICommands {
|
||||
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
|
||||
private static final Map<String, ICommand> COMMANDS_MAP = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
JadxCLICommands.register(new CommandPlugins());
|
||||
@@ -26,7 +27,8 @@ public class JadxCLICommands {
|
||||
public static boolean process(JCommanderWrapper<?> jcw, JCommander jc, String parsedCommand) {
|
||||
ICommand command = COMMANDS_MAP.get(parsedCommand);
|
||||
if (command == null) {
|
||||
throw new IllegalArgumentException("Unknown command: " + parsedCommand);
|
||||
throw new JadxArgsValidateException("Unknown command: " + parsedCommand
|
||||
+ ". Expected one of: " + COMMANDS_MAP.keySet());
|
||||
}
|
||||
JCommander subCommander = jc.getCommands().get(parsedCommand);
|
||||
command.process(jcw, subCommander);
|
||||
|
||||
@@ -12,6 +12,7 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
@@ -33,10 +34,10 @@ public class SingleClassMode {
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
if (clsForProcess == null) {
|
||||
throw new JadxRuntimeException("Input class not found: " + singleClass);
|
||||
throw new JadxArgsValidateException("Input class not found: " + singleClass);
|
||||
}
|
||||
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
|
||||
throw new JadxRuntimeException("Input class can't be saved by current jadx settings (marked as DONT_GENERATE)");
|
||||
throw new JadxArgsValidateException("Input class can't be saved by current jadx settings (marked as DONT_GENERATE)");
|
||||
}
|
||||
if (clsForProcess.isInner()) {
|
||||
clsForProcess = clsForProcess.getTopParentClass();
|
||||
@@ -52,7 +53,7 @@ public class SingleClassMode {
|
||||
if (size == 1) {
|
||||
clsForProcess = classes.get(0);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
|
||||
throw new JadxArgsValidateException("Found " + size + " classes, single class output can't be used");
|
||||
}
|
||||
}
|
||||
ICodeInfo codeInfo;
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||
import jadx.core.clsp.ClsSet;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
@@ -23,8 +24,9 @@ public class ConvertToClsSet {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
||||
|
||||
public static void usage() {
|
||||
LOG.info("<output .jcst file> <several input dex or jar files> ");
|
||||
LOG.info("<android API level (number)> <output .jcst file> <several input dex or jar files> ");
|
||||
LOG.info("Arguments to update core.jcst: "
|
||||
+ "<android API level (number)> "
|
||||
+ "<jadx root>/jadx-core/src/main/resources/clst/core.jcst "
|
||||
+ "<sdk_root>/platforms/android-<api level>/android.jar"
|
||||
+ "<sdk_root>/platforms/android-<api level>/optional/android.car.jar "
|
||||
@@ -32,11 +34,12 @@ public class ConvertToClsSet {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 2) {
|
||||
if (args.length != 5) {
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
|
||||
int androidApiLevel = Integer.parseInt(args[0]);
|
||||
List<Path> inputPaths = Stream.of(args).skip(1).map(Paths::get).collect(Collectors.toList());
|
||||
Path output = inputPaths.remove(0);
|
||||
|
||||
JadxArgs jadxArgs = new JadxArgs();
|
||||
@@ -45,7 +48,7 @@ public class ConvertToClsSet {
|
||||
// disable not needed passes executed at prepare stage
|
||||
jadxArgs.setDeobfuscationOn(false);
|
||||
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||
jadxArgs.setUseSourceNameAsClassAlias(false);
|
||||
jadxArgs.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.NEVER);
|
||||
jadxArgs.setMoveInnerClasses(false);
|
||||
jadxArgs.setInlineAnonymousClasses(false);
|
||||
jadxArgs.setInlineMethods(false);
|
||||
@@ -57,6 +60,7 @@ public class ConvertToClsSet {
|
||||
decompiler.load();
|
||||
RootNode root = decompiler.getRoot();
|
||||
ClsSet set = new ClsSet(root);
|
||||
set.setAndroidApiLevel(androidApiLevel);
|
||||
set.loadFrom(root);
|
||||
set.save(output);
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package jadx.cli.commands;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
|
||||
import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.cli.JCommanderWrapper;
|
||||
import jadx.cli.LogHelper;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.plugins.tools.JadxPluginsList;
|
||||
import jadx.plugins.tools.JadxPluginsTools;
|
||||
import jadx.plugins.tools.data.JadxPluginMetadata;
|
||||
@@ -15,24 +21,40 @@ import jadx.plugins.tools.data.JadxPluginUpdate;
|
||||
@Parameters(commandDescription = "manage jadx plugins")
|
||||
public class CommandPlugins implements ICommand {
|
||||
|
||||
@Parameter(names = { "-i", "--install" }, description = "install plugin with locationId")
|
||||
@Parameter(names = { "-i", "--install" }, description = "install plugin with locationId", defaultValueDescription = "<locationId>")
|
||||
protected String install;
|
||||
|
||||
@Parameter(names = { "-j", "--install-jar" }, description = "install plugin from jar file")
|
||||
@Parameter(names = { "-j", "--install-jar" }, description = "install plugin from jar file", defaultValueDescription = "<path-to.jar>")
|
||||
protected String installJar;
|
||||
|
||||
@Parameter(names = { "-l", "--list" }, description = "list installed plugins")
|
||||
protected boolean list;
|
||||
|
||||
@Parameter(names = { "-a", "--available" }, description = "list available plugins")
|
||||
@Parameter(names = { "-a", "--available" }, description = "list available plugins from jadx-plugins-list (aka marketplace)")
|
||||
protected boolean available;
|
||||
|
||||
@Parameter(names = { "-u", "--update" }, description = "update installed plugins")
|
||||
protected boolean update;
|
||||
|
||||
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId")
|
||||
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId", defaultValueDescription = "<pluginId>")
|
||||
protected String uninstall;
|
||||
|
||||
@Parameter(names = { "--disable" }, description = "disable plugin with pluginId", defaultValueDescription = "<pluginId>")
|
||||
protected String disable;
|
||||
|
||||
@Parameter(names = { "--enable" }, description = "enable plugin with pluginId", defaultValueDescription = "<pluginId>")
|
||||
protected String enable;
|
||||
|
||||
@Parameter(names = { "--list-all" }, description = "list all plugins including bundled and dropins")
|
||||
protected boolean listAll;
|
||||
|
||||
@Parameter(
|
||||
names = { "--list-versions" },
|
||||
description = "fetch latest versions of plugin from locationId (will download all artefacts, limited to 10)",
|
||||
defaultValueDescription = "<locationId>"
|
||||
)
|
||||
protected String listVersions;
|
||||
|
||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
@@ -41,21 +63,33 @@ public class CommandPlugins implements ICommand {
|
||||
return "plugins";
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessaryReturnStatement")
|
||||
@Override
|
||||
public void process(JCommanderWrapper<?> jcw, JCommander subCommander) {
|
||||
if (printHelp) {
|
||||
jcw.printUsage(subCommander);
|
||||
return;
|
||||
}
|
||||
Set<String> unknownOptions = new HashSet<>(subCommander.getUnknownOptions());
|
||||
boolean verbose = unknownOptions.remove("-v") || unknownOptions.remove("--verbose");
|
||||
LogHelper.setLogLevel(verbose ? LogHelper.LogLevelEnum.DEBUG : LogHelper.LogLevelEnum.INFO);
|
||||
|
||||
if (!unknownOptions.isEmpty()) {
|
||||
System.out.println("Error: found unknown options: " + unknownOptions);
|
||||
}
|
||||
|
||||
if (install != null) {
|
||||
installPlugin(install);
|
||||
return;
|
||||
}
|
||||
if (installJar != null) {
|
||||
installPlugin("file:" + installJar);
|
||||
return;
|
||||
}
|
||||
if (uninstall != null) {
|
||||
boolean uninstalled = JadxPluginsTools.getInstance().uninstall(uninstall);
|
||||
System.out.println(uninstalled ? "Uninstalled" : "Plugin not found");
|
||||
return;
|
||||
}
|
||||
if (update) {
|
||||
List<JadxPluginUpdate> updates = JadxPluginsTools.getInstance().updateAll();
|
||||
@@ -67,27 +101,99 @@ public class CommandPlugins implements ICommand {
|
||||
System.out.println(" " + update.getPluginId() + ": " + update.getOldVersion() + " -> " + update.getNewVersion());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (list) {
|
||||
List<JadxPluginMetadata> installed = JadxPluginsTools.getInstance().getInstalled();
|
||||
System.out.println("Installed plugins: " + installed.size());
|
||||
int i = 1;
|
||||
for (JadxPluginMetadata plugin : installed) {
|
||||
System.out.println(" " + (i++) + ") "
|
||||
+ plugin.getPluginId() + " (" + plugin.getVersion() + ") - "
|
||||
+ plugin.getName() + ": " + plugin.getDescription());
|
||||
}
|
||||
printPlugins(JadxPluginsTools.getInstance().getInstalled());
|
||||
return;
|
||||
}
|
||||
if (listAll) {
|
||||
printAllPlugins();
|
||||
return;
|
||||
}
|
||||
if (listVersions != null) {
|
||||
printVersions(listVersions, 10);
|
||||
return;
|
||||
}
|
||||
|
||||
if (available) {
|
||||
List<JadxPluginMetadata> availableList = JadxPluginsList.getInstance().get();
|
||||
System.out.println("Available plugins: " + availableList.size());
|
||||
int i = 1;
|
||||
for (JadxPluginMetadata plugin : availableList) {
|
||||
System.out.println(" " + (i++) + ") "
|
||||
+ plugin.getName() + ": " + plugin.getDescription()
|
||||
System.out.println(" - " + plugin.getName() + ": " + plugin.getDescription()
|
||||
+ " (" + plugin.getLocationId() + ")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (disable != null) {
|
||||
if (JadxPluginsTools.getInstance().changeDisabledStatus(disable, true)) {
|
||||
System.out.println("Plugin '" + disable + "' disabled.");
|
||||
} else {
|
||||
System.out.println("Plugin '" + disable + "' already disabled.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (enable != null) {
|
||||
if (JadxPluginsTools.getInstance().changeDisabledStatus(enable, false)) {
|
||||
System.out.println("Plugin '" + enable + "' enabled.");
|
||||
} else {
|
||||
System.out.println("Plugin '" + enable + "' already enabled.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void printPlugins(List<JadxPluginMetadata> installed) {
|
||||
System.out.println("Installed plugins: " + installed.size());
|
||||
for (JadxPluginMetadata plugin : installed) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(" - ").append(plugin.getPluginId());
|
||||
String version = plugin.getVersion();
|
||||
if (version != null) {
|
||||
sb.append(" (").append(version).append(')');
|
||||
}
|
||||
if (plugin.isDisabled()) {
|
||||
sb.append(" (disabled)");
|
||||
}
|
||||
sb.append(" - ").append(plugin.getName());
|
||||
sb.append(": ").append(plugin.getDescription());
|
||||
System.out.println(sb);
|
||||
}
|
||||
}
|
||||
|
||||
private void printVersions(String locationId, int limit) {
|
||||
System.out.println("Loading ...");
|
||||
List<JadxPluginMetadata> versions = JadxPluginsTools.getInstance().getVersionsByLocation(locationId, 1, limit);
|
||||
if (versions.isEmpty()) {
|
||||
System.out.println("No versions found");
|
||||
return;
|
||||
}
|
||||
JadxPluginMetadata plugin = versions.get(0);
|
||||
System.out.println("Versions for plugin id: " + plugin.getPluginId());
|
||||
for (JadxPluginMetadata version : versions) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(" - ").append(version.getVersion());
|
||||
String reqVer = version.getRequiredJadxVersion();
|
||||
if (StringUtils.notBlank(reqVer)) {
|
||||
sb.append(", require jadx: ").append(reqVer);
|
||||
}
|
||||
System.out.println(sb);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printAllPlugins() {
|
||||
List<JadxPluginMetadata> installed = JadxPluginsTools.getInstance().getInstalled();
|
||||
printPlugins(installed);
|
||||
Set<String> installedSet = installed.stream().map(JadxPluginMetadata::getPluginId).collect(Collectors.toSet());
|
||||
|
||||
List<JadxPluginInfo> plugins = JadxPluginsTools.getInstance().getAllPluginsInfo();
|
||||
System.out.println("Other plugins: " + plugins.size());
|
||||
for (JadxPluginInfo plugin : plugins) {
|
||||
if (!installedSet.contains(plugin.getPluginId())) {
|
||||
System.out.println(" - " + plugin.getPluginId()
|
||||
+ " - " + plugin.getName()
|
||||
+ ": " + plugin.getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package jadx.cli.plugins;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import jadx.commons.app.JadxCommonFiles;
|
||||
import jadx.commons.app.JadxTempFiles;
|
||||
import jadx.core.plugins.files.IJadxFilesGetter;
|
||||
|
||||
public class JadxFilesGetter implements IJadxFilesGetter {
|
||||
|
||||
public static final JadxFilesGetter INSTANCE = new JadxFilesGetter();
|
||||
|
||||
@Override
|
||||
public Path getConfigDir() {
|
||||
return JadxCommonFiles.getConfigDir();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getCacheDir() {
|
||||
return JadxCommonFiles.getCacheDir();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getTempDir() {
|
||||
return JadxTempFiles.getTempRootDir();
|
||||
}
|
||||
|
||||
private JadxFilesGetter() {
|
||||
// singleton
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -20,7 +19,10 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.android.TextResMapFile;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
import jadx.core.utils.files.ZipFile;
|
||||
import jadx.core.xmlgen.ResTableBinaryParser;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.expandDirs;
|
||||
|
||||
/**
|
||||
* Utility class for convert '.arsc' to simple text file with mapping id to resource name
|
||||
@@ -30,7 +32,7 @@ public class ConvertArscFile {
|
||||
private static int rewritesCount;
|
||||
|
||||
public static void usage() {
|
||||
LOG.info("<res-map file> <input .arsc files>");
|
||||
LOG.info("<res-map file> <input .arsc/android.jar files or dir>");
|
||||
LOG.info("");
|
||||
LOG.info("Note: If res-map already exists - it will be merged and updated");
|
||||
}
|
||||
@@ -42,6 +44,7 @@ public class ConvertArscFile {
|
||||
}
|
||||
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
|
||||
Path resMapFile = inputPaths.remove(0);
|
||||
List<Path> inputResFiles = filterAndSort(expandDirs(inputPaths));
|
||||
Map<Integer, String> resMap;
|
||||
if (Files.isReadable(resMapFile)) {
|
||||
resMap = TextResMapFile.read(resMapFile);
|
||||
@@ -52,9 +55,8 @@ public class ConvertArscFile {
|
||||
|
||||
RootNode root = new RootNode(new JadxArgs()); // not really needed
|
||||
rewritesCount = 0;
|
||||
for (Path resFile : inputPaths) {
|
||||
LOG.info("Processing {}", resFile);
|
||||
ResTableParser resTableParser = new ResTableParser(root, true);
|
||||
for (Path resFile : inputResFiles) {
|
||||
ResTableBinaryParser resTableParser = new ResTableBinaryParser(root, true);
|
||||
if (resFile.getFileName().toString().endsWith(".jar")) {
|
||||
// Load resources.arsc from android.jar
|
||||
try (ZipFile zip = new ZipFile(resFile.toFile())) {
|
||||
@@ -84,6 +86,16 @@ public class ConvertArscFile {
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
private static List<Path> filterAndSort(List<Path> inputPaths) {
|
||||
return inputPaths.stream()
|
||||
.filter(p -> {
|
||||
String fileName = p.getFileName().toString();
|
||||
return fileName.endsWith(".arsc") || fileName.endsWith(".jar");
|
||||
})
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static void mergeResMaps(Map<Integer, String> mainResMap, Map<Integer, String> newResMap) {
|
||||
for (Map.Entry<Integer, String> entry : newResMap.entrySet()) {
|
||||
Integer id = entry.getKey();
|
||||
|
||||
@@ -3,14 +3,12 @@ package jadx.cli;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.Utils.newConstStringMap;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class JadxCLIArgsTest {
|
||||
|
||||
@@ -18,38 +16,38 @@ public class JadxCLIArgsTest {
|
||||
|
||||
@Test
|
||||
public void testInvertedBooleanOption() {
|
||||
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
|
||||
assertThat(parse("").isReplaceConsts(), is(true));
|
||||
assertThat(parse("--no-replace-consts").isReplaceConsts()).isFalse();
|
||||
assertThat(parse("").isReplaceConsts()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeUnicodeOption() {
|
||||
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
|
||||
assertThat(parse("").isEscapeUnicode(), is(false));
|
||||
assertThat(parse("--escape-unicode").isEscapeUnicode()).isTrue();
|
||||
assertThat(parse("").isEscapeUnicode()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSrcOption() {
|
||||
assertThat(parse("--no-src").isSkipSources(), is(true));
|
||||
assertThat(parse("-s").isSkipSources(), is(true));
|
||||
assertThat(parse("").isSkipSources(), is(false));
|
||||
assertThat(parse("--no-src").isSkipSources()).isTrue();
|
||||
assertThat(parse("-s").isSkipSources()).isTrue();
|
||||
assertThat(parse("").isSkipSources()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionsOverride() {
|
||||
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
|
||||
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
|
||||
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
|
||||
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports()).isFalse();
|
||||
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo()).isFalse();
|
||||
assertThat(override(new JadxCLIArgs(), "").isUseImports()).isTrue();
|
||||
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "--no-imports").isUseImports(), is(false));
|
||||
assertThat(override(args, "--no-imports").isUseImports()).isFalse();
|
||||
args.debugInfo = false;
|
||||
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
|
||||
assertThat(override(args, "--no-debug-info").isDebugInfo()).isFalse();
|
||||
|
||||
args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "").isUseImports(), is(false));
|
||||
assertThat(override(args, "").isUseImports()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,7 +81,7 @@ public class JadxCLIArgsTest {
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.pluginOptions = baseMap;
|
||||
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
|
||||
assertThat(resultMap, Matchers.equalTo(expectedMap));
|
||||
assertThat(resultMap).isEqualTo(expectedMap);
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
@@ -91,15 +89,15 @@ public class JadxCLIArgsTest {
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
|
||||
boolean res = jadxArgs.processArgs(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
return check(jadxArgs, jadxArgs.processArgs(args));
|
||||
}
|
||||
|
||||
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
|
||||
boolean res = jadxArgs.overrideProvided(args);
|
||||
assertThat(res, is(true));
|
||||
return check(jadxArgs, jadxArgs.overrideProvided(args));
|
||||
}
|
||||
|
||||
private static JadxCLIArgs check(JadxCLIArgs jadxArgs, boolean res) {
|
||||
assertThat(res).isTrue();
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.cli.JadxCLIArgs.RenameConverter;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class RenameConverterTest {
|
||||
|
||||
@@ -24,25 +24,24 @@ public class RenameConverterTest {
|
||||
@Test
|
||||
public void all() {
|
||||
Set<RenameEnum> set = converter.convert("all");
|
||||
assertEquals(3, set.size());
|
||||
assertTrue(set.contains(RenameEnum.CASE));
|
||||
assertTrue(set.contains(RenameEnum.VALID));
|
||||
assertTrue(set.contains(RenameEnum.PRINTABLE));
|
||||
assertThat(set).hasSize(3);
|
||||
assertThat(set).contains(RenameEnum.CASE);
|
||||
assertThat(set).contains(RenameEnum.VALID);
|
||||
assertThat(set).contains(RenameEnum.PRINTABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void none() {
|
||||
Set<RenameEnum> set = converter.convert("none");
|
||||
assertTrue(set.isEmpty());
|
||||
assertThat(set).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrong() {
|
||||
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
|
||||
JadxArgsValidateException thrown = assertThrows(JadxArgsValidateException.class,
|
||||
() -> converter.convert("wrong"),
|
||||
"Expected convert() to throw, but it didn't");
|
||||
|
||||
assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
|
||||
thrown.getMessage());
|
||||
assertThat(thrown.getMessage()).isEqualTo("'wrong' is unknown for parameter someParam, possible values are case, valid, printable");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,71 +12,111 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestInput {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
|
||||
|
||||
private static final PathMatcher LOG_ALL_FILES = path -> {
|
||||
LOG.debug("File in result dir: {}", path);
|
||||
return true;
|
||||
};
|
||||
|
||||
@TempDir
|
||||
Path testDir;
|
||||
|
||||
@Test
|
||||
public void testHelp() {
|
||||
int result = JadxCLI.execute(new String[] { "--help" });
|
||||
assertThat(result).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApkInput() throws Exception {
|
||||
int result = JadxCLI.execute(buildArgs(List.of(), "samples/small.apk"));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> resultFiles = collectAllFilesInDir(testDir);
|
||||
printFiles(resultFiles);
|
||||
assertThat(resultFiles)
|
||||
.describedAs("check output files")
|
||||
.map(p -> p.getFileName().toString())
|
||||
.haveExactly(2, new Condition<>(f -> f.endsWith(".java"), "java classes"))
|
||||
.haveExactly(9, new Condition<>(f -> f.endsWith(".xml"), "xml resources"))
|
||||
.haveExactly(1, new Condition<>(f -> f.equals("classes.dex"), "dex"))
|
||||
.haveExactly(1, new Condition<>(f -> f.equals("AndroidManifest.xml"), "manifest"))
|
||||
.hasSize(13);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDexInput() throws Exception {
|
||||
decompile("dex", "samples/hello.dex");
|
||||
decompile("samples/hello.dex");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmaliInput() throws Exception {
|
||||
decompile("smali", "samples/HelloWorld.smali");
|
||||
decompile("samples/HelloWorld.smali");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassInput() throws Exception {
|
||||
decompile("class", "samples/HelloWorld.class");
|
||||
decompile("samples/HelloWorld.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleInput() throws Exception {
|
||||
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
||||
decompile("samples/hello.dex", "samples/HelloWorld.smali");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFallbackMode() throws Exception {
|
||||
int result = JadxCLI.execute(buildArgs(List.of("-f"), "samples/hello.dex"));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> files = collectJavaFilesInDir(testDir);
|
||||
assertThat(files).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleMode() throws Exception {
|
||||
int result = JadxCLI.execute(buildArgs(List.of("--decompilation-mode", "simple"), "samples/hello.dex"));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> files = collectJavaFilesInDir(testDir);
|
||||
assertThat(files).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceOnly() throws Exception {
|
||||
decode("resourceOnly", "samples/resources-only.apk");
|
||||
}
|
||||
|
||||
private void decode(String tmpDirName, String apkSample) throws URISyntaxException, IOException {
|
||||
List<String> args = new ArrayList<>();
|
||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||
args.add("-v");
|
||||
args.add("-d");
|
||||
args.add(tempDir.toAbsolutePath().toString());
|
||||
|
||||
URL resource = getClass().getClassLoader().getResource(apkSample);
|
||||
assertThat(resource).isNotNull();
|
||||
String sampleFile = resource.toURI().getRawPath();
|
||||
args.add(sampleFile);
|
||||
|
||||
int result = JadxCLI.execute(args.toArray(new String[0]));
|
||||
int result = JadxCLI.execute(buildArgs(List.of(), "samples/resources-only.apk"));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> files = Files.find(
|
||||
tempDir,
|
||||
3,
|
||||
(file, attr) -> file.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"))
|
||||
.collect(Collectors.toList());
|
||||
assertThat(files.isEmpty()).isFalse();
|
||||
List<Path> files = collectFilesInDir(testDir,
|
||||
path -> path.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"));
|
||||
assertThat(files).isNotEmpty();
|
||||
}
|
||||
|
||||
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
||||
List<String> args = new ArrayList<>();
|
||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||
private void decompile(String... inputSamples) throws URISyntaxException, IOException {
|
||||
int result = JadxCLI.execute(buildArgs(List.of(), inputSamples));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> resultJavaFiles = collectJavaFilesInDir(testDir);
|
||||
assertThat(resultJavaFiles).isNotEmpty();
|
||||
|
||||
// do not copy input files as resources
|
||||
for (Path path : collectFilesInDir(testDir, LOG_ALL_FILES)) {
|
||||
for (String inputSample : inputSamples) {
|
||||
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String[] buildArgs(List<String> options, String... inputSamples) throws URISyntaxException {
|
||||
List<String> args = new ArrayList<>(options);
|
||||
args.add("-v");
|
||||
args.add("-d");
|
||||
args.add(tempDir.toAbsolutePath().toString());
|
||||
args.add(testDir.toAbsolutePath().toString());
|
||||
|
||||
for (String inputSample : inputSamples) {
|
||||
URL resource = getClass().getClassLoader().getResource(inputSample);
|
||||
@@ -84,21 +124,13 @@ public class TestInput {
|
||||
String sampleFile = resource.toURI().getRawPath();
|
||||
args.add(sampleFile);
|
||||
}
|
||||
return args.toArray(new String[0]);
|
||||
}
|
||||
|
||||
int result = JadxCLI.execute(args.toArray(new String[0]));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
|
||||
assertThat(resultJavaFiles).isNotEmpty();
|
||||
|
||||
// do not copy input files as resources
|
||||
PathMatcher logAllFiles = path -> {
|
||||
LOG.debug("File in result dir: {}", path);
|
||||
return true;
|
||||
};
|
||||
for (Path path : collectFilesInDir(tempDir, logAllFiles)) {
|
||||
for (String inputSample : inputSamples) {
|
||||
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
|
||||
}
|
||||
private void printFiles(List<Path> files) {
|
||||
LOG.info("Output files (count: {}):", files.size());
|
||||
for (Path file : files) {
|
||||
LOG.info(" {}", testDir.relativize(file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +139,14 @@ public class TestInput {
|
||||
return collectFilesInDir(dir, javaMatcher);
|
||||
}
|
||||
|
||||
private static List<Path> collectAllFilesInDir(Path dir) throws IOException {
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
return pathStream
|
||||
.filter(Files::isRegularFile)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
return pathStream
|
||||
@@ -115,9 +155,4 @@ public class TestInput {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
FileUtils.clearTempRootDir();
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
## jadx app commons
|
||||
|
||||
This module contains common utilities used in jadx apps (cli and gui) and not needed in jadx-code module:
|
||||
- `JadxCommonFiles` - wrapper for `dev.dirs:directories` lib to get
|
||||
'config' and 'cache' directories in cross-platform way
|
||||
- `JadxCommonEnv` - utils for work with environment variables
|
||||
@@ -0,0 +1,7 @@
|
||||
plugins {
|
||||
id("jadx-library")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("dev.dirs:directories:26")
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package jadx.commons.app;
|
||||
|
||||
public class JadxCommonEnv {
|
||||
|
||||
public static String get(String varName, String defValue) {
|
||||
String strValue = System.getenv(varName);
|
||||
return isNullOrEmpty(strValue) ? defValue : strValue;
|
||||
}
|
||||
|
||||
public static boolean getBool(String varName, boolean defValue) {
|
||||
String strValue = System.getenv(varName);
|
||||
if (isNullOrEmpty(strValue)) {
|
||||
return defValue;
|
||||
}
|
||||
return strValue.equalsIgnoreCase("true");
|
||||
}
|
||||
|
||||
public static int getInt(String varName, int defValue) {
|
||||
String strValue = System.getenv(varName);
|
||||
if (isNullOrEmpty(strValue)) {
|
||||
return defValue;
|
||||
}
|
||||
return Integer.parseInt(strValue);
|
||||
}
|
||||
|
||||
private static boolean isNullOrEmpty(String value) {
|
||||
return value == null || value.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package jadx.commons.app;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import dev.dirs.ProjectDirectories;
|
||||
|
||||
public class JadxCommonFiles {
|
||||
|
||||
private static final Path CONFIG_DIR;
|
||||
private static final Path CACHE_DIR;
|
||||
|
||||
public static Path getConfigDir() {
|
||||
return CONFIG_DIR;
|
||||
}
|
||||
|
||||
public static Path getCacheDir() {
|
||||
return CACHE_DIR;
|
||||
}
|
||||
|
||||
static {
|
||||
DirsLoader loader = new DirsLoader();
|
||||
loader.init();
|
||||
CONFIG_DIR = loader.getConfigDir();
|
||||
CACHE_DIR = loader.getCacheDir();
|
||||
}
|
||||
|
||||
private static final class DirsLoader {
|
||||
private @Nullable ProjectDirectories dirs;
|
||||
private Path configDir;
|
||||
private Path cacheDir;
|
||||
|
||||
public void init() {
|
||||
try {
|
||||
configDir = loadEnvDir("JADX_CONFIG_DIR", pd -> pd.configDir);
|
||||
cacheDir = loadEnvDir("JADX_CACHE_DIR", pd -> pd.cacheDir);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to init common directories", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path loadEnvDir(String envVar, Function<ProjectDirectories, String> dirFunc) throws IOException {
|
||||
String envDir = JadxCommonEnv.get(envVar, null);
|
||||
String dirStr;
|
||||
if (envDir != null) {
|
||||
dirStr = envDir;
|
||||
} else {
|
||||
dirStr = dirFunc.apply(loadDirs());
|
||||
}
|
||||
Path path = Path.of(dirStr).toAbsolutePath();
|
||||
Files.createDirectories(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private synchronized ProjectDirectories loadDirs() {
|
||||
if (dirs == null) {
|
||||
dirs = ProjectDirectories.from("io.github", "skylot", "jadx");
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
public Path getCacheDir() {
|
||||
return cacheDir;
|
||||
}
|
||||
|
||||
public Path getConfigDir() {
|
||||
return configDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package jadx.commons.app;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class JadxTempFiles {
|
||||
private static final String JADX_TMP_INSTANCE_PREFIX = "jadx-instance-";
|
||||
|
||||
private static final Path TEMP_ROOT_DIR = createTempRootDir();
|
||||
|
||||
public static Path getTempRootDir() {
|
||||
return TEMP_ROOT_DIR;
|
||||
}
|
||||
|
||||
private static Path createTempRootDir() {
|
||||
try {
|
||||
String jadxTmpDir = System.getenv("JADX_TMP_DIR");
|
||||
Path dir;
|
||||
if (jadxTmpDir != null) {
|
||||
dir = Files.createTempDirectory(Paths.get(jadxTmpDir), JADX_TMP_INSTANCE_PREFIX);
|
||||
} else {
|
||||
dir = Files.createTempDirectory(JADX_TMP_INSTANCE_PREFIX);
|
||||
}
|
||||
dir.toFile().deleteOnExit();
|
||||
return dir;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create temp root directory", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,9 @@ plugins {
|
||||
dependencies {
|
||||
api(project(":jadx-plugins:jadx-input-api"))
|
||||
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
implementation("com.google.code.gson:gson:2.11.0")
|
||||
|
||||
// TODO: move resources decoding to separate plugin module
|
||||
implementation("com.android.tools.build:aapt2-proto:8.2.2-10154469")
|
||||
implementation("com.google.protobuf:protobuf-java:3.25.2") // forcing latest version
|
||||
|
||||
testImplementation("org.apache.commons:commons-lang3:3.14.0")
|
||||
testImplementation("org.apache.commons:commons-lang3:3.17.0")
|
||||
|
||||
testImplementation(project(":jadx-plugins:jadx-dex-input"))
|
||||
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||
@@ -28,6 +24,31 @@ dependencies {
|
||||
testImplementation("tools.profiler:async-profiler:3.0")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
val jadxTestJavaVersion = getTestJavaVersion()
|
||||
|
||||
fun getTestJavaVersion(): Int? {
|
||||
val envVarName = "JADX_TEST_JAVA_VERSION"
|
||||
val testJavaVer = System.getenv(envVarName)?.toInt() ?: return null
|
||||
val currentJavaVer = java.toolchain.languageVersion.get().asInt()
|
||||
if (testJavaVer < currentJavaVer) {
|
||||
throw GradleException("'$envVarName' can't be set to lower version than $currentJavaVer")
|
||||
}
|
||||
println("Set Java toolchain for core tests to version '$testJavaVer'")
|
||||
return testJavaVer
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
jadxTestJavaVersion?.let { testJavaVer ->
|
||||
javaLauncher =
|
||||
javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(testJavaVer)
|
||||
}
|
||||
}
|
||||
|
||||
// disable cache to allow test's rerun,
|
||||
// because most tests are integration and depends on plugins and environment
|
||||
outputs.cacheIf { false }
|
||||
|
||||
// exclude temp tests
|
||||
exclude("**/tmp/*")
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
|
||||
public interface ICodeWriter {
|
||||
String NL = System.getProperty("line.separator");
|
||||
String INDENT_STR = " ";
|
||||
|
||||
boolean isMetadataSupported();
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -21,6 +22,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
||||
import jadx.api.args.IntegerFormat;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||
import jadx.api.args.UserRenamesMappingsMode;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.deobf.IAliasProvider;
|
||||
@@ -29,12 +31,17 @@ import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
import jadx.api.plugins.loader.JadxBasePluginLoader;
|
||||
import jadx.api.plugins.loader.JadxPluginLoader;
|
||||
import jadx.api.security.IJadxSecurity;
|
||||
import jadx.api.security.JadxSecurityFlag;
|
||||
import jadx.api.security.impl.JadxSecurity;
|
||||
import jadx.api.usage.IUsageInfoCache;
|
||||
import jadx.api.usage.impl.InMemoryUsageInfoCache;
|
||||
import jadx.core.deobf.DeobfAliasProvider;
|
||||
import jadx.core.deobf.conditions.DeobfWhitelist;
|
||||
import jadx.core.deobf.conditions.JadxRenameConditions;
|
||||
import jadx.core.plugins.PluginContext;
|
||||
import jadx.core.plugins.files.IJadxFilesGetter;
|
||||
import jadx.core.plugins.files.TempFilesGetter;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxArgs implements Closeable {
|
||||
@@ -42,6 +49,9 @@ public class JadxArgs implements Closeable {
|
||||
|
||||
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||
|
||||
public static final String DEFAULT_NEW_LINE_STR = System.lineSeparator();
|
||||
public static final String DEFAULT_INDENT_STR = " ";
|
||||
|
||||
public static final String DEFAULT_OUT_DIR = "jadx-output";
|
||||
public static final String DEFAULT_SRC_DIR = "sources";
|
||||
public static final String DEFAULT_RES_DIR = "resources";
|
||||
@@ -95,7 +105,7 @@ public class JadxArgs implements Closeable {
|
||||
private UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault();
|
||||
|
||||
private boolean deobfuscationOn = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
private UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.getDefault();
|
||||
|
||||
private File generatedRenamesMappingFile = null;
|
||||
private GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
||||
@@ -124,6 +134,8 @@ public class JadxArgs implements Closeable {
|
||||
private boolean respectBytecodeAccModifiers = false;
|
||||
private boolean exportAsGradleProject = false;
|
||||
|
||||
private boolean restoreSwitchOverString = true;
|
||||
|
||||
private boolean skipXmlPrettyPrint = false;
|
||||
|
||||
private boolean fsCaseSensitive;
|
||||
@@ -144,6 +156,10 @@ public class JadxArgs implements Closeable {
|
||||
|
||||
private ICodeData codeData;
|
||||
|
||||
private String codeNewLineStr = DEFAULT_NEW_LINE_STR;
|
||||
|
||||
private String codeIndentStr = DEFAULT_INDENT_STR;
|
||||
|
||||
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||
|
||||
private IntegerFormat integerFormat = IntegerFormat.AUTO;
|
||||
@@ -156,13 +172,31 @@ public class JadxArgs implements Closeable {
|
||||
|
||||
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||
|
||||
/**
|
||||
* Additional files structure info.
|
||||
* Defaults to tmp dirs.
|
||||
*/
|
||||
private IJadxFilesGetter filesGetter = TempFilesGetter.INSTANCE;
|
||||
|
||||
/**
|
||||
* Additional data validation and security checks
|
||||
*/
|
||||
private IJadxSecurity security = new JadxSecurity(JadxSecurityFlag.all());
|
||||
|
||||
/**
|
||||
* Don't save files (can be using for performance testing)
|
||||
*/
|
||||
private boolean skipFilesSave = false;
|
||||
|
||||
/**
|
||||
* Run additional expensive checks to verify internal invariants and info integrity
|
||||
*/
|
||||
private boolean runDebugChecks = false;
|
||||
|
||||
private Map<String, String> pluginOptions = new HashMap<>();
|
||||
|
||||
private Set<String> disabledPlugins = new HashSet<>();
|
||||
|
||||
private JadxPluginLoader pluginLoader = new JadxBasePluginLoader();
|
||||
|
||||
private boolean loadJadxClsSetFile = true;
|
||||
@@ -418,12 +452,29 @@ public class JadxArgs implements Closeable {
|
||||
this.generatedRenamesMappingFileMode = mode;
|
||||
}
|
||||
|
||||
public boolean isUseSourceNameAsClassAlias() {
|
||||
return useSourceNameAsClassAlias;
|
||||
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
|
||||
return useSourceNameAsClassNameAlias;
|
||||
}
|
||||
|
||||
public void setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias) {
|
||||
this.useSourceNameAsClassNameAlias = useSourceNameAsClassNameAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getUseSourceNameAsClassNameAlias()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isUseSourceNameAsClassAlias() {
|
||||
return getUseSourceNameAsClassNameAlias().toBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
|
||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||
final var useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.create(useSourceNameAsClassAlias);
|
||||
setUseSourceNameAsClassNameAlias(useSourceNameAsClassNameAlias);
|
||||
}
|
||||
|
||||
public int getDeobfuscationMinLength() {
|
||||
@@ -514,6 +565,14 @@ public class JadxArgs implements Closeable {
|
||||
this.exportAsGradleProject = exportAsGradleProject;
|
||||
}
|
||||
|
||||
public boolean isRestoreSwitchOverString() {
|
||||
return restoreSwitchOverString;
|
||||
}
|
||||
|
||||
public void setRestoreSwitchOverString(boolean restoreSwitchOverString) {
|
||||
this.restoreSwitchOverString = restoreSwitchOverString;
|
||||
}
|
||||
|
||||
public boolean isSkipXmlPrettyPrint() {
|
||||
return skipXmlPrettyPrint;
|
||||
}
|
||||
@@ -622,6 +681,22 @@ public class JadxArgs implements Closeable {
|
||||
this.codeData = codeData;
|
||||
}
|
||||
|
||||
public String getCodeIndentStr() {
|
||||
return codeIndentStr;
|
||||
}
|
||||
|
||||
public void setCodeIndentStr(String codeIndentStr) {
|
||||
this.codeIndentStr = codeIndentStr;
|
||||
}
|
||||
|
||||
public String getCodeNewLineStr() {
|
||||
return codeNewLineStr;
|
||||
}
|
||||
|
||||
public void setCodeNewLineStr(String codeNewLineStr) {
|
||||
this.codeNewLineStr = codeNewLineStr;
|
||||
}
|
||||
|
||||
public CommentsLevel getCommentsLevel() {
|
||||
return commentsLevel;
|
||||
}
|
||||
@@ -654,6 +729,22 @@ public class JadxArgs implements Closeable {
|
||||
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||
}
|
||||
|
||||
public IJadxFilesGetter getFilesGetter() {
|
||||
return filesGetter;
|
||||
}
|
||||
|
||||
public void setFilesGetter(IJadxFilesGetter filesGetter) {
|
||||
this.filesGetter = filesGetter;
|
||||
}
|
||||
|
||||
public IJadxSecurity getSecurity() {
|
||||
return security;
|
||||
}
|
||||
|
||||
public void setSecurity(IJadxSecurity security) {
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public boolean isSkipFilesSave() {
|
||||
return skipFilesSave;
|
||||
}
|
||||
@@ -662,6 +753,14 @@ public class JadxArgs implements Closeable {
|
||||
this.skipFilesSave = skipFilesSave;
|
||||
}
|
||||
|
||||
public boolean isRunDebugChecks() {
|
||||
return runDebugChecks;
|
||||
}
|
||||
|
||||
public void setRunDebugChecks(boolean runDebugChecks) {
|
||||
this.runDebugChecks = runDebugChecks;
|
||||
}
|
||||
|
||||
public Map<String, String> getPluginOptions() {
|
||||
return pluginOptions;
|
||||
}
|
||||
@@ -670,6 +769,14 @@ public class JadxArgs implements Closeable {
|
||||
this.pluginOptions = pluginOptions;
|
||||
}
|
||||
|
||||
public Set<String> getDisabledPlugins() {
|
||||
return disabledPlugins;
|
||||
}
|
||||
|
||||
public void setDisabledPlugins(Set<String> disabledPlugins) {
|
||||
this.disabledPlugins = disabledPlugins;
|
||||
}
|
||||
|
||||
public JadxPluginLoader getPluginLoader() {
|
||||
return pluginLoader;
|
||||
}
|
||||
@@ -693,10 +800,11 @@ public class JadxArgs implements Closeable {
|
||||
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
||||
+ inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda
|
||||
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationWhitelist
|
||||
+ useSourceNameAsClassNameAlias
|
||||
+ resourceNameSource
|
||||
+ useKotlinMethodsForVarNames
|
||||
+ insertDebugLines + extractFinally
|
||||
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||
+ debugInfo + escapeUnicode + replaceConsts + restoreSwitchOverString
|
||||
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
||||
+ commentsLevel + useDxInput + integerFormat
|
||||
+ "|" + buildPluginsHash(decompiler);
|
||||
@@ -732,7 +840,7 @@ public class JadxArgs implements Closeable {
|
||||
+ ", generatedRenamesMappingFile=" + generatedRenamesMappingFile
|
||||
+ ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode
|
||||
+ ", resourceNameSource=" + resourceNameSource
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", useSourceNameAsClassNameAlias=" + useSourceNameAsClassNameAlias
|
||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||
+ ", insertDebugLines=" + insertDebugLines
|
||||
+ ", extractFinally=" + extractFinally
|
||||
@@ -741,6 +849,7 @@ public class JadxArgs implements Closeable {
|
||||
+ ", deobfuscationWhitelist=" + deobfuscationWhitelist
|
||||
+ ", escapeUnicode=" + escapeUnicode
|
||||
+ ", replaceConsts=" + replaceConsts
|
||||
+ ", restoreSwitchOverString=" + restoreSwitchOverString
|
||||
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
|
||||
+ ", exportAsGradleProject=" + exportAsGradleProject
|
||||
+ ", skipXmlPrettyPrint=" + skipXmlPrettyPrint
|
||||
|
||||
@@ -28,12 +28,6 @@ public class JadxArgsValidator {
|
||||
if (inputFiles.isEmpty() && jadx.getCustomCodeLoaders().isEmpty()) {
|
||||
throw new JadxArgsValidateException("Please specify input file");
|
||||
}
|
||||
for (File inputFile : inputFiles) {
|
||||
String fileName = inputFile.getName();
|
||||
if (fileName.startsWith("--")) {
|
||||
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
||||
}
|
||||
}
|
||||
for (File file : inputFiles) {
|
||||
checkFile(file);
|
||||
}
|
||||
|
||||
@@ -50,9 +50,8 @@ import jadx.core.utils.DecompilerScheduler;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.ZipPatch;
|
||||
import jadx.core.utils.tasks.TaskExecutor;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
import jadx.core.xmlgen.ProtoXMLParser;
|
||||
import jadx.core.xmlgen.ResourcesSaver;
|
||||
|
||||
/**
|
||||
@@ -86,35 +85,37 @@ public final class JadxDecompiler implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||
|
||||
private final JadxArgs args;
|
||||
private final JadxPluginManager pluginManager = new JadxPluginManager(this);
|
||||
private final JadxPluginManager pluginManager;
|
||||
private final List<ICodeLoader> loadedInputs = new ArrayList<>();
|
||||
|
||||
private RootNode root;
|
||||
private List<JavaClass> classes;
|
||||
private List<ResourceFile> resources;
|
||||
|
||||
private BinaryXMLParser binaryXmlParser;
|
||||
private ProtoXMLParser protoXmlParser;
|
||||
|
||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
||||
private final JadxEventsImpl events = new JadxEventsImpl();
|
||||
private final ResourcesLoader resourcesLoader;
|
||||
|
||||
private final List<ICodeLoader> customCodeLoaders = new ArrayList<>();
|
||||
private final List<CustomResourcesLoader> customResourcesLoaders = new ArrayList<>();
|
||||
private final Map<JadxPassType, List<JadxPass>> customPasses = new HashMap<>();
|
||||
|
||||
private IJadxEvents events = new JadxEventsImpl();
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new JadxArgs());
|
||||
}
|
||||
|
||||
public JadxDecompiler(JadxArgs args) {
|
||||
this.args = args;
|
||||
this.args = Objects.requireNonNull(args);
|
||||
this.pluginManager = new JadxPluginManager(this);
|
||||
this.resourcesLoader = new ResourcesLoader(this);
|
||||
}
|
||||
|
||||
public void load() {
|
||||
reset();
|
||||
JadxArgsValidator.validate(this);
|
||||
LOG.info("loading ...");
|
||||
FileUtils.updateTempRootDir(args.getFilesGetter().getTempDir());
|
||||
loadPlugins();
|
||||
loadInputFiles();
|
||||
|
||||
@@ -124,7 +125,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
root.mergePasses(customPasses);
|
||||
root.loadClasses(loadedInputs);
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.loadResources(resourcesLoader, getResources());
|
||||
root.runPreDecompileStage();
|
||||
root.initPasses();
|
||||
loadFinished();
|
||||
@@ -144,7 +145,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
|
||||
private void loadInputFiles() {
|
||||
loadedInputs.clear();
|
||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||
List<File> inputs = ZipPatch.patchZipFiles(args.getInputFiles());
|
||||
args.setInputFiles(inputs);
|
||||
List<Path> inputPaths = Utils.collectionMap(inputs, File::toPath);
|
||||
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||
long start = System.currentTimeMillis();
|
||||
for (PluginContext plugin : pluginManager.getResolvedPluginContexts()) {
|
||||
@@ -169,8 +172,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
root = null;
|
||||
classes = null;
|
||||
resources = null;
|
||||
binaryXmlParser = null;
|
||||
protoXmlParser = null;
|
||||
events.reset();
|
||||
}
|
||||
|
||||
@@ -180,6 +181,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
closeInputs();
|
||||
closeLoaders();
|
||||
args.close();
|
||||
FileUtils.clearTempRootDir();
|
||||
}
|
||||
|
||||
private void closeInputs() {
|
||||
@@ -198,7 +200,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
try {
|
||||
resourcesLoader.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to close resource loader: " + resourcesLoader, e);
|
||||
LOG.error("Failed to close resource loader: {}", resourcesLoader, e);
|
||||
}
|
||||
}
|
||||
customResourcesLoaders.clear();
|
||||
@@ -430,7 +432,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
resources = new ResourcesLoader(this).load();
|
||||
resources = resourcesLoader.load(root);
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
@@ -469,20 +471,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
return root;
|
||||
}
|
||||
|
||||
synchronized BinaryXMLParser getBinaryXmlParser() {
|
||||
if (binaryXmlParser == null) {
|
||||
binaryXmlParser = new BinaryXMLParser(root);
|
||||
}
|
||||
return binaryXmlParser;
|
||||
}
|
||||
|
||||
synchronized ProtoXMLParser getProtoXmlParser() {
|
||||
if (protoXmlParser == null) {
|
||||
protoXmlParser = new ProtoXMLParser(root);
|
||||
}
|
||||
return protoXmlParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JavaClass by ClassNode without loading and decompilation
|
||||
*/
|
||||
@@ -681,6 +669,10 @@ public final class JadxDecompiler implements Closeable {
|
||||
return events;
|
||||
}
|
||||
|
||||
public void setEventsImpl(IJadxEvents eventsImpl) {
|
||||
this.events = eventsImpl;
|
||||
}
|
||||
|
||||
public void addCustomCodeLoader(ICodeLoader customCodeLoader) {
|
||||
customCodeLoaders.add(customCodeLoader);
|
||||
}
|
||||
@@ -704,6 +696,10 @@ public final class JadxDecompiler implements Closeable {
|
||||
customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass);
|
||||
}
|
||||
|
||||
public ResourcesLoader getResourcesLoader() {
|
||||
return resourcesLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "jadx decompiler " + getVersion();
|
||||
|
||||
@@ -252,6 +252,10 @@ public final class JavaClass implements JavaNode {
|
||||
return cls.getPackage();
|
||||
}
|
||||
|
||||
public JavaPackage getJavaPackage() {
|
||||
return cls.getPackageNode().getJavaNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return parent;
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -79,15 +78,9 @@ public final class JavaMethod implements JavaNode {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||
return ovrdAttr.getRelatedMthNodes().stream()
|
||||
.map(m -> {
|
||||
JavaMethod javaMth = decompiler.convertMethodNode(m);
|
||||
if (javaMth == null) {
|
||||
LOG.warn("Failed convert to java method: {}", m);
|
||||
}
|
||||
return javaMth;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
return ovrdAttr.getRelatedMthNodes()
|
||||
.stream()
|
||||
.map(decompiler::convertMethodNode)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -104,6 +97,10 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getDefPosition();
|
||||
}
|
||||
|
||||
public String getCodeStr() {
|
||||
return mth.getCodeStr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
this.mth.getMethodInfo().removeAlias();
|
||||
|
||||
@@ -76,6 +76,22 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return !Objects.equals(parent, aliasParent);
|
||||
}
|
||||
|
||||
public boolean isDescendantOf(JavaPackage ancestor) {
|
||||
JavaPackage current = this;
|
||||
while (current != null) {
|
||||
if (ancestor.equals(current)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (current.getPkgNode().getParentPkg() == null) {
|
||||
current = null;
|
||||
} else {
|
||||
current = current.getPkgNode().getParentPkg().getJavaNode();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getCodeNodeRef() {
|
||||
return pkgNode;
|
||||
|
||||
@@ -62,6 +62,10 @@ public class ResourceFile {
|
||||
return deobfName != null ? deobfName : name;
|
||||
}
|
||||
|
||||
public void setDeobfName(String resFullName) {
|
||||
this.deobfName = resFullName;
|
||||
}
|
||||
|
||||
public ResourceType getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -84,7 +88,7 @@ public class ResourceFile {
|
||||
}
|
||||
String alias = sb.toString();
|
||||
if (!alias.equals(name)) {
|
||||
deobfName = alias;
|
||||
setDeobfName(alias);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -17,30 +16,43 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.plugins.CustomResourcesLoader;
|
||||
import jadx.api.plugins.resources.IResContainerFactory;
|
||||
import jadx.api.plugins.resources.IResTableParserProvider;
|
||||
import jadx.api.plugins.resources.IResourcesLoader;
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.android.Res9patchStreamDecoder;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.ZipFile;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
import jadx.core.xmlgen.IResTableParser;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResProtoParser;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
import jadx.core.xmlgen.ResTableBinaryParserProvider;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
|
||||
import static jadx.core.utils.files.FileUtils.copyStream;
|
||||
|
||||
// TODO: move to core package
|
||||
public final class ResourcesLoader {
|
||||
public final class ResourcesLoader implements IResourcesLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||
|
||||
private final JadxDecompiler jadxRef;
|
||||
|
||||
private final List<IResTableParserProvider> resTableParserProviders = new ArrayList<>();
|
||||
private final List<IResContainerFactory> resContainerFactories = new ArrayList<>();
|
||||
|
||||
private BinaryXMLParser binaryXmlParser;
|
||||
|
||||
ResourcesLoader(JadxDecompiler jadxRef) {
|
||||
this.jadxRef = jadxRef;
|
||||
this.resTableParserProviders.add(new ResTableBinaryParserProvider());
|
||||
}
|
||||
|
||||
List<ResourceFile> load() {
|
||||
List<ResourceFile> load(RootNode root) {
|
||||
init(root);
|
||||
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
|
||||
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
||||
for (File file : inputFiles) {
|
||||
@@ -49,10 +61,37 @@ public final class ResourcesLoader {
|
||||
return list;
|
||||
}
|
||||
|
||||
private void init(RootNode root) {
|
||||
for (IResTableParserProvider resTableParserProvider : resTableParserProviders) {
|
||||
try {
|
||||
resTableParserProvider.init(root);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to init res table provider: " + resTableParserProvider);
|
||||
}
|
||||
}
|
||||
for (IResContainerFactory resContainerFactory : resContainerFactories) {
|
||||
try {
|
||||
resContainerFactory.init(root);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to init res container factory: " + resContainerFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ResourceDecoder<T> {
|
||||
T decode(long size, InputStream is) throws IOException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResContainerFactory(IResContainerFactory resContainerFactory) {
|
||||
resContainerFactories.add(resContainerFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResTableParserProvider(IResTableParserProvider resTableParserProvider) {
|
||||
resTableParserProviders.add(resTableParserProvider);
|
||||
}
|
||||
|
||||
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
|
||||
try {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
@@ -82,7 +121,8 @@ public final class ResourcesLoader {
|
||||
|
||||
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
|
||||
try {
|
||||
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
|
||||
ResourcesLoader resLoader = jadxRef.getResourcesLoader();
|
||||
return decodeStream(rf, (size, is) -> resLoader.loadContent(rf, is));
|
||||
} catch (JadxException e) {
|
||||
LOG.error("Decode error", e);
|
||||
ICodeWriter cw = jadxRef.getRoot().makeCodeWriter();
|
||||
@@ -92,36 +132,48 @@ public final class ResourcesLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream) throws IOException {
|
||||
RootNode root = jadxRef.getRoot();
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML: {
|
||||
ICodeInfo content;
|
||||
if (root.isProto()) {
|
||||
content = jadxRef.getProtoXmlParser().parse(inputStream);
|
||||
} else {
|
||||
content = jadxRef.getBinaryXmlParser().parse(inputStream);
|
||||
}
|
||||
return ResContainer.textResource(rf.getDeobfName(), content);
|
||||
private ResContainer loadContent(ResourceFile resFile, InputStream inputStream) throws IOException {
|
||||
for (IResContainerFactory customFactory : resContainerFactories) {
|
||||
ResContainer resContainer = customFactory.create(resFile, inputStream);
|
||||
if (resContainer != null) {
|
||||
return resContainer;
|
||||
}
|
||||
}
|
||||
switch (resFile.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
ICodeInfo content = loadBinaryXmlParser().parse(inputStream);
|
||||
return ResContainer.textResource(resFile.getDeobfName(), content);
|
||||
|
||||
case ARSC:
|
||||
if (root.isProto()) {
|
||||
return new ResProtoParser(root).decodeFiles(inputStream);
|
||||
} else {
|
||||
return new ResTableParser(root).decodeFiles(inputStream);
|
||||
}
|
||||
return decodeTable(resFile, inputStream).decodeFiles();
|
||||
|
||||
case IMG:
|
||||
return decodeImage(rf, inputStream);
|
||||
return decodeImage(resFile, inputStream);
|
||||
|
||||
default:
|
||||
return ResContainer.resourceFileLink(rf);
|
||||
return ResContainer.resourceFileLink(resFile);
|
||||
}
|
||||
}
|
||||
|
||||
public IResTableParser decodeTable(ResourceFile resFile, InputStream is) throws IOException {
|
||||
if (resFile.getType() != ResourceType.ARSC) {
|
||||
throw new IllegalArgumentException("Unexpected resource type for decode: " + resFile.getType() + ", expect '.pb'/'.arsc'");
|
||||
}
|
||||
IResTableParser parser = null;
|
||||
for (IResTableParserProvider provider : resTableParserProviders) {
|
||||
parser = provider.getParser(resFile);
|
||||
if (parser != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parser == null) {
|
||||
throw new JadxRuntimeException("Unknown type of resource file: " + resFile.getOriginalName());
|
||||
}
|
||||
parser.decode(is);
|
||||
return parser;
|
||||
}
|
||||
|
||||
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
|
||||
String name = rf.getOriginalName();
|
||||
if (name.endsWith(".9.png")) {
|
||||
@@ -184,4 +236,11 @@ public final class ResourcesLoader {
|
||||
copyStream(is, baos);
|
||||
return new SimpleCodeInfo(baos.toString("UTF-8"));
|
||||
}
|
||||
|
||||
private synchronized BinaryXMLParser loadBinaryXmlParser() {
|
||||
if (binaryXmlParser == null) {
|
||||
binaryXmlParser = new BinaryXMLParser(jadxRef.getRoot());
|
||||
}
|
||||
return binaryXmlParser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package jadx.api.args;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public enum UseSourceNameAsClassNameAlias {
|
||||
ALWAYS,
|
||||
IF_BETTER,
|
||||
NEVER;
|
||||
|
||||
public static UseSourceNameAsClassNameAlias getDefault() {
|
||||
return NEVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link UseSourceNameAsClassNameAlias} directly.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean toBoolean() {
|
||||
switch (this) {
|
||||
case IF_BETTER:
|
||||
return true;
|
||||
case NEVER:
|
||||
return false;
|
||||
case ALWAYS:
|
||||
throw new JadxRuntimeException("No match between " + this + " and boolean");
|
||||
default:
|
||||
throw new JadxRuntimeException("Unhandled strategy: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link UseSourceNameAsClassNameAlias} directly.
|
||||
*/
|
||||
@Deprecated
|
||||
public static UseSourceNameAsClassNameAlias create(boolean useSourceNameAsAlias) {
|
||||
return useSourceNameAsAlias ? IF_BETTER : NEVER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package jadx.api.data;
|
||||
|
||||
public enum CommentStyle {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* // comment
|
||||
* </pre>
|
||||
*/
|
||||
LINE("// ", "// ", ""),
|
||||
|
||||
// @formatter:off
|
||||
/**
|
||||
* <pre>
|
||||
* /*
|
||||
* * comment
|
||||
* */
|
||||
* </pre>
|
||||
*/
|
||||
// @formatter:on
|
||||
BLOCK("/*\n * ", " * ", "\n */"),
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* /* comment */
|
||||
* </pre>
|
||||
*/
|
||||
BLOCK_CONDENSED("/* ", " * ", " */"),
|
||||
|
||||
// @formatter:off
|
||||
/**
|
||||
* <pre>
|
||||
* /**
|
||||
* * comment
|
||||
* */
|
||||
* </pre>
|
||||
*/
|
||||
// @formatter:on
|
||||
JAVADOC("/**\n * ", " * ", "\n */"),
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* /** comment */
|
||||
* </pre>
|
||||
*/
|
||||
JAVADOC_CONDENSED("/** ", " * ", " */");
|
||||
|
||||
private final String start;
|
||||
private final String onNewLine;
|
||||
private final String end;
|
||||
|
||||
CommentStyle(String start, String onNewLine, String end) {
|
||||
this.start = start;
|
||||
this.onNewLine = onNewLine;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public String getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public String getOnNewLine() {
|
||||
return onNewLine;
|
||||
}
|
||||
|
||||
public String getEnd() {
|
||||
return end;
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,6 @@ public interface ICodeComment extends Comparable<ICodeComment> {
|
||||
IJavaCodeRef getCodeRef();
|
||||
|
||||
String getComment();
|
||||
|
||||
CommentStyle getStyle();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.api.data.impl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.data.CommentStyle;
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
@@ -13,15 +14,25 @@ public class JadxCodeComment implements ICodeComment {
|
||||
@Nullable
|
||||
private IJavaCodeRef codeRef;
|
||||
private String comment;
|
||||
private CommentStyle style = CommentStyle.LINE;
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
|
||||
this(nodeRef, null, comment);
|
||||
}
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, CommentStyle style) {
|
||||
this(nodeRef, null, comment, style);
|
||||
}
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment) {
|
||||
this(nodeRef, codeRef, comment, CommentStyle.LINE);
|
||||
}
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment, CommentStyle style) {
|
||||
this.nodeRef = nodeRef;
|
||||
this.codeRef = codeRef;
|
||||
this.comment = comment;
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
public JadxCodeComment() {
|
||||
@@ -56,6 +67,15 @@ public class JadxCodeComment implements ICodeComment {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentStyle getStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
public void setStyle(CommentStyle style) {
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ICodeComment other) {
|
||||
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
|
||||
@@ -73,6 +93,7 @@ public class JadxCodeComment implements ICodeComment {
|
||||
return "JadxCodeComment{" + nodeRef
|
||||
+ ", ref=" + codeRef
|
||||
+ ", comment='" + comment + '\''
|
||||
+ ", style=" + style
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
|
||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||
|
||||
public AnnotatedCodeWriter() {
|
||||
}
|
||||
|
||||
public AnnotatedCodeWriter(JadxArgs args) {
|
||||
super(args);
|
||||
}
|
||||
@@ -35,9 +32,9 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
|
||||
@Override
|
||||
public AnnotatedCodeWriter addMultiLine(String str) {
|
||||
if (str.contains(NL)) {
|
||||
buf.append(str.replace(NL, NL + indentStr));
|
||||
line += StringUtils.countMatches(str, NL);
|
||||
if (str.contains(newLineStr)) {
|
||||
buf.append(str.replace(newLineStr, newLineStr + indentStr));
|
||||
line += StringUtils.countMatches(str, newLineStr);
|
||||
offset = 0;
|
||||
} else {
|
||||
buf.append(str);
|
||||
@@ -65,7 +62,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
buf.append(cw.getCodeStr());
|
||||
return this;
|
||||
}
|
||||
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
||||
AnnotatedCodeWriter code = (AnnotatedCodeWriter) cw;
|
||||
line--;
|
||||
int startPos = getLength();
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
|
||||
@@ -84,7 +81,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
|
||||
@Override
|
||||
protected void addLine() {
|
||||
buf.append(NL);
|
||||
buf.append(newLineStr);
|
||||
line++;
|
||||
offset = 0;
|
||||
}
|
||||
@@ -154,7 +151,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
|
||||
@Override
|
||||
public ICodeInfo finish() {
|
||||
processDefinitionAnnotations();
|
||||
validateAnnotations();
|
||||
String code = buf.toString();
|
||||
buf = null;
|
||||
@@ -166,18 +162,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private void processDefinitionAnnotations() {
|
||||
if (!annotations.isEmpty()) {
|
||||
annotations.forEach((k, v) -> {
|
||||
if (v instanceof NodeDeclareRef) {
|
||||
NodeDeclareRef declareRef = (NodeDeclareRef) v;
|
||||
declareRef.setDefPos(k);
|
||||
declareRef.getNode().setDefPosition(k);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAnnotations() {
|
||||
if (annotations.isEmpty()) {
|
||||
return;
|
||||
|
||||
@@ -14,38 +14,39 @@ import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
* CodeWriter implementation without meta information support (only strings builder)
|
||||
* CodeWriter implementation without meta information support
|
||||
*/
|
||||
public class SimpleCodeWriter implements ICodeWriter {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SimpleCodeWriter.class);
|
||||
|
||||
private static final String[] INDENT_CACHE = {
|
||||
"",
|
||||
INDENT_STR,
|
||||
INDENT_STR + INDENT_STR,
|
||||
INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
};
|
||||
|
||||
protected StringBuilder buf = new StringBuilder();
|
||||
protected String indentStr = "";
|
||||
protected int indent = 0;
|
||||
|
||||
private final boolean insertLineNumbers;
|
||||
|
||||
public SimpleCodeWriter() {
|
||||
this.insertLineNumbers = false;
|
||||
}
|
||||
protected final boolean insertLineNumbers;
|
||||
protected final String singleIndentStr;
|
||||
protected final String newLineStr;
|
||||
|
||||
public SimpleCodeWriter(JadxArgs args) {
|
||||
this.insertLineNumbers = args.isInsertDebugLines();
|
||||
this.singleIndentStr = args.getCodeIndentStr();
|
||||
this.newLineStr = args.getCodeNewLineStr();
|
||||
if (insertLineNumbers) {
|
||||
incIndent(3);
|
||||
add(indentStr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with JadxArgs should be used.
|
||||
*/
|
||||
@Deprecated
|
||||
public SimpleCodeWriter() {
|
||||
this.insertLineNumbers = false;
|
||||
this.singleIndentStr = JadxArgs.DEFAULT_INDENT_STR;
|
||||
this.newLineStr = JadxArgs.DEFAULT_NEW_LINE_STR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMetadataSupported() {
|
||||
return false;
|
||||
@@ -96,8 +97,8 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
|
||||
@Override
|
||||
public SimpleCodeWriter addMultiLine(String str) {
|
||||
if (str.contains(NL)) {
|
||||
buf.append(str.replace(NL, NL + indentStr));
|
||||
if (str.contains(newLineStr)) {
|
||||
buf.append(str.replace(newLineStr, newLineStr + indentStr));
|
||||
} else {
|
||||
buf.append(str);
|
||||
}
|
||||
@@ -130,12 +131,12 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
|
||||
@Override
|
||||
public SimpleCodeWriter addIndent() {
|
||||
add(INDENT_STR);
|
||||
add(singleIndentStr);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void addLine() {
|
||||
buf.append(NL);
|
||||
buf.append(newLineStr);
|
||||
}
|
||||
|
||||
protected SimpleCodeWriter addLineIndent() {
|
||||
@@ -144,12 +145,7 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
}
|
||||
|
||||
private void updateIndent() {
|
||||
int curIndent = indent;
|
||||
if (curIndent < INDENT_CACHE.length) {
|
||||
this.indentStr = INDENT_CACHE[curIndent];
|
||||
} else {
|
||||
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
|
||||
}
|
||||
this.indentStr = Utils.strRepeat(singleIndentStr, indent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -219,17 +215,17 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
|
||||
@Override
|
||||
public ICodeInfo finish() {
|
||||
removeFirstEmptyLine();
|
||||
String code = buf.toString();
|
||||
String code = getStringWithoutFirstEmptyLine();
|
||||
buf = null;
|
||||
return new SimpleCodeInfo(code);
|
||||
}
|
||||
|
||||
protected void removeFirstEmptyLine() {
|
||||
int len = NL.length();
|
||||
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
||||
buf.delete(0, len);
|
||||
private String getStringWithoutFirstEmptyLine() {
|
||||
int len = newLineStr.length();
|
||||
if (buf.length() > len && buf.substring(0, len).equals(newLineStr)) {
|
||||
return buf.substring(len);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,12 +6,14 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.data.IJadxFiles;
|
||||
import jadx.api.plugins.data.IJadxPlugins;
|
||||
import jadx.api.plugins.events.IJadxEvents;
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
import jadx.api.plugins.resources.IResourcesLoader;
|
||||
|
||||
public interface JadxPluginContext {
|
||||
|
||||
@@ -32,6 +34,11 @@ public interface JadxPluginContext {
|
||||
*/
|
||||
void registerInputsHashSupplier(Supplier<String> supplier);
|
||||
|
||||
/**
|
||||
* Customize resource loading
|
||||
*/
|
||||
IResourcesLoader getResourcesLoader();
|
||||
|
||||
/**
|
||||
* Access to jadx-gui specific methods
|
||||
*/
|
||||
@@ -47,4 +54,9 @@ public interface JadxPluginContext {
|
||||
* Access to registered plugins and runtime data
|
||||
*/
|
||||
IJadxPlugins plugins();
|
||||
|
||||
/**
|
||||
* Access to plugin specific files and directories
|
||||
*/
|
||||
IJadxFiles files();
|
||||
}
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
package jadx.api.plugins;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class JadxPluginInfo {
|
||||
private final String pluginId;
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final String homepage;
|
||||
private String homepage;
|
||||
|
||||
/**
|
||||
* Conflicting plugins should have the same 'provides' property; only one will be loaded
|
||||
*/
|
||||
private final String provides;
|
||||
private String provides;
|
||||
|
||||
/**
|
||||
* Minimum required jadx version to run this plugin.
|
||||
* <br>
|
||||
* Format: "<stable version>, r<revision number of unstable version>".
|
||||
* Example: "1.5.1, r2305"
|
||||
*
|
||||
* @see <a href="https://github.com/skylot/jadx/wiki/Jadx-plugins-guide#required-jadx-version">wiki
|
||||
* page</a>
|
||||
* for details.
|
||||
*/
|
||||
private @Nullable String requiredJadxVersion;
|
||||
|
||||
public JadxPluginInfo(String id, String name, String description) {
|
||||
this(id, name, description, "", id);
|
||||
@@ -43,10 +57,26 @@ public class JadxPluginInfo {
|
||||
return homepage;
|
||||
}
|
||||
|
||||
public void setHomepage(String homepage) {
|
||||
this.homepage = homepage;
|
||||
}
|
||||
|
||||
public String getProvides() {
|
||||
return provides;
|
||||
}
|
||||
|
||||
public void setProvides(String provides) {
|
||||
this.provides = provides;
|
||||
}
|
||||
|
||||
public @Nullable String getRequiredJadxVersion() {
|
||||
return requiredJadxVersion;
|
||||
}
|
||||
|
||||
public void setRequiredJadxVersion(@Nullable String requiredJadxVersion) {
|
||||
this.requiredJadxVersion = requiredJadxVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pluginId + ": " + name + " - '" + description + '\'';
|
||||
|
||||
@@ -4,11 +4,14 @@ import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.plugins.versions.VerifyRequiredVersion;
|
||||
|
||||
public class JadxPluginInfoBuilder {
|
||||
private String pluginId;
|
||||
private String name;
|
||||
private String description;
|
||||
private String homepage = "";
|
||||
private @Nullable String requiredJadxVersion;
|
||||
private @Nullable String provides;
|
||||
|
||||
/**
|
||||
@@ -43,6 +46,11 @@ public class JadxPluginInfoBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public JadxPluginInfoBuilder requiredJadxVersion(String versions) {
|
||||
this.requiredJadxVersion = versions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JadxPluginInfo build() {
|
||||
Objects.requireNonNull(pluginId, "PluginId is required");
|
||||
Objects.requireNonNull(name, "Name is required");
|
||||
@@ -50,6 +58,11 @@ public class JadxPluginInfoBuilder {
|
||||
if (provides == null) {
|
||||
provides = pluginId;
|
||||
}
|
||||
return new JadxPluginInfo(pluginId, name, description, homepage, provides);
|
||||
if (requiredJadxVersion != null) {
|
||||
VerifyRequiredVersion.verify(requiredJadxVersion);
|
||||
}
|
||||
JadxPluginInfo pluginInfo = new JadxPluginInfo(pluginId, name, description, homepage, provides);
|
||||
pluginInfo.setRequiredJadxVersion(requiredJadxVersion);
|
||||
return pluginInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package jadx.api.plugins.data;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface IJadxFiles {
|
||||
|
||||
/**
|
||||
* Plugin cache directory.
|
||||
*/
|
||||
Path getPluginCacheDir();
|
||||
|
||||
/**
|
||||
* Plugin config directory.
|
||||
*/
|
||||
Path getPluginConfigDir();
|
||||
|
||||
/**
|
||||
* Plugin temp directory.
|
||||
*/
|
||||
Path getPluginTempDir();
|
||||
}
|
||||
@@ -15,4 +15,15 @@ public interface IJadxEvents {
|
||||
* For public event types check {@link JadxEvents} class.
|
||||
*/
|
||||
<E extends IJadxEvent> void addListener(JadxEventType<E> eventType, Consumer<E> listener);
|
||||
|
||||
/**
|
||||
* Remove listener for specific event.
|
||||
* Listener should be same or equal object.
|
||||
*/
|
||||
<E extends IJadxEvent> void removeListener(JadxEventType<E> eventType, Consumer<E> listener);
|
||||
|
||||
/**
|
||||
* Clear all listeners.
|
||||
*/
|
||||
void reset();
|
||||
}
|
||||
|
||||
@@ -6,4 +6,13 @@ public abstract class JadxEventType<T extends IJadxEvent> {
|
||||
return new JadxEventType<>() {
|
||||
};
|
||||
}
|
||||
|
||||
public static <E extends IJadxEvent> JadxEventType<E> create(String name) {
|
||||
return new JadxEventType<>() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,17 +16,17 @@ public class JadxEvents {
|
||||
/**
|
||||
* Notify about renaming done by user (GUI only).
|
||||
*/
|
||||
public static final JadxEventType<NodeRenamedByUser> NODE_RENAMED_BY_USER = create();
|
||||
public static final JadxEventType<NodeRenamedByUser> NODE_RENAMED_BY_USER = create("NODE_RENAMED_BY_USER");
|
||||
|
||||
/**
|
||||
* Request reload of a current project (GUI only).
|
||||
*/
|
||||
public static final JadxEventType<ReloadProject> RELOAD_PROJECT = create();
|
||||
public static final JadxEventType<ReloadProject> RELOAD_PROJECT = create("RELOAD_PROJECT");
|
||||
|
||||
/**
|
||||
* Request reload of a settings window (GUI only).
|
||||
* Useful for a reload custom settings group which was set with
|
||||
* {@link JadxGuiSettings#setCustomSettingsGroup(ISettingsGroup)}.
|
||||
*/
|
||||
public static final JadxEventType<ReloadSettingsWindow> RELOAD_SETTINGS_WINDOW = create();
|
||||
public static final JadxEventType<ReloadSettingsWindow> RELOAD_SETTINGS_WINDOW = create("RELOAD_SETTINGS_WINDOW");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.api.plugins.gui;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -50,6 +51,12 @@ public interface JadxGuiContext {
|
||||
*/
|
||||
JadxGuiSettings settings();
|
||||
|
||||
/**
|
||||
* Main window component.
|
||||
* Can be used as a parent for creating new windows or dialogs.
|
||||
*/
|
||||
JFrame getMainFrame();
|
||||
|
||||
ICodeNodeRef getNodeUnderCaret();
|
||||
|
||||
ICodeNodeRef getNodeUnderMouse();
|
||||
|
||||
@@ -12,12 +12,12 @@ public enum OptionFlag {
|
||||
HIDE_IN_GUI,
|
||||
|
||||
/**
|
||||
* Do not show this option in jadx-gui (useful if option is configured with custom ui)
|
||||
* Option will be read-only in jadx-gui (can be used for calculated properties)
|
||||
*/
|
||||
DISABLE_IN_GUI,
|
||||
|
||||
/**
|
||||
* Add this flag only if option do not affect generated code.
|
||||
* Add this flag only if the option does not affect generated code.
|
||||
* If added, option value change will not cause code cache reset.
|
||||
*/
|
||||
NOT_CHANGING_CODE,
|
||||
|
||||
@@ -64,6 +64,14 @@ public abstract class BasePluginOptionsBuilder implements JadxPluginOptions {
|
||||
.parser(v -> v));
|
||||
}
|
||||
|
||||
public OptionBuilder<Integer> intOption(String name) {
|
||||
return addOption(
|
||||
new OptionData<Integer>(name)
|
||||
.type(OptionType.NUMBER)
|
||||
.formatter(Object::toString)
|
||||
.parser(Integer::parseInt));
|
||||
}
|
||||
|
||||
public <E extends Enum<?>> OptionBuilder<E> enumOption(String name, E[] values, Function<String, E> valueOf) {
|
||||
return addOption(
|
||||
new OptionData<E>(name)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package jadx.api.plugins.resources;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
/**
|
||||
* Factory for {@link ResContainer}. Can be used in plugins via
|
||||
* {@code context.getResourcesLoader().addResContainerFactory()} to implement content parsing in
|
||||
* files with
|
||||
* different formats.
|
||||
*/
|
||||
public interface IResContainerFactory {
|
||||
|
||||
/**
|
||||
* Optional init method
|
||||
*/
|
||||
default void init(RootNode root) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if resource file is of expected format and tries to parse its content.
|
||||
*
|
||||
* @return {@link ResContainer} if file is of expected format, {@code null} otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package jadx.api.plugins.resources;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.xmlgen.IResTableParser;
|
||||
|
||||
/**
|
||||
* Provides the resource table parser instance for specific resource table file format. Can be used
|
||||
* in plugins via {@code context.getResourcesLoader().addResTableParserProvider()} to parse
|
||||
* resources from tables
|
||||
* in different formats.
|
||||
*/
|
||||
public interface IResTableParserProvider {
|
||||
|
||||
/**
|
||||
* Optional init method
|
||||
*/
|
||||
default void init(RootNode root) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a file format and provides the instance if the format is expected.
|
||||
*
|
||||
* @return {@link IResTableParser} if resource table is of expected format, {@code null} otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
IResTableParser getParser(ResourceFile resFile);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package jadx.api.plugins.resources;
|
||||
|
||||
public interface IResourcesLoader {
|
||||
|
||||
void addResContainerFactory(IResContainerFactory resContainerFactory);
|
||||
|
||||
void addResTableParserProvider(IResTableParserProvider resTableParserProvider);
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import java.util.Enumeration;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
@@ -16,6 +15,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.ZipFile;
|
||||
|
||||
public class ZipSecurity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ZipSecurity.class);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package jadx.api.security;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
public interface IJadxSecurity {
|
||||
|
||||
/**
|
||||
* Check if application package is safe
|
||||
*
|
||||
* @return normalized/sanitized string or same string if safe
|
||||
*/
|
||||
String verifyAppPackage(String appPackage);
|
||||
|
||||
/**
|
||||
* XML document parser
|
||||
*/
|
||||
Document parseXml(InputStream in);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package jadx.api.security;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
public enum JadxSecurityFlag {
|
||||
|
||||
VERIFY_APP_PACKAGE,
|
||||
SECURE_XML_PARSER;
|
||||
|
||||
public static Set<JadxSecurityFlag> all() {
|
||||
return EnumSet.allOf(JadxSecurityFlag.class);
|
||||
}
|
||||
|
||||
public static Set<JadxSecurityFlag> none() {
|
||||
return EnumSet.noneOf(JadxSecurityFlag.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package jadx.api.security.impl;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import jadx.api.security.IJadxSecurity;
|
||||
import jadx.api.security.JadxSecurityFlag;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
|
||||
public class JadxSecurity implements IJadxSecurity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxSecurity.class);
|
||||
|
||||
private final Set<JadxSecurityFlag> flags;
|
||||
|
||||
public JadxSecurity(Set<JadxSecurityFlag> flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String verifyAppPackage(String appPackage) {
|
||||
if (flags.contains(JadxSecurityFlag.VERIFY_APP_PACKAGE)
|
||||
&& !NameMapper.isValidFullIdentifier(appPackage)) {
|
||||
LOG.warn("App package '{}' has invalid format and will be ignored", appPackage);
|
||||
return "INVALID_PACKAGE";
|
||||
}
|
||||
return appPackage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document parseXml(InputStream in) {
|
||||
DocumentBuilderFactory dbf;
|
||||
if (flags.contains(JadxSecurityFlag.SECURE_XML_PARSER)) {
|
||||
dbf = SecureDBFHolder.INSTANCE;
|
||||
} else {
|
||||
dbf = SimpleDBFHolder.INSTANCE;
|
||||
}
|
||||
try {
|
||||
return dbf.newDocumentBuilder().parse(in);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to parse xml", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SimpleDBFHolder {
|
||||
private static final DocumentBuilderFactory INSTANCE = DocumentBuilderFactory.newInstance();
|
||||
}
|
||||
|
||||
private static final class SecureDBFHolder {
|
||||
private static final DocumentBuilderFactory INSTANCE = buildSecureDBF();
|
||||
|
||||
private static DocumentBuilderFactory buildSecureDBF() {
|
||||
try {
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
dbf.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes", false);
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
return dbf;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Fail to build secure XML DocumentBuilderFactory", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
package jadx.api.utils;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class CodeUtils {
|
||||
|
||||
@@ -11,18 +17,32 @@ public class CodeUtils {
|
||||
}
|
||||
|
||||
public static int getLineStartForPos(String code, int pos) {
|
||||
String newLine = ICodeWriter.NL;
|
||||
int start = code.lastIndexOf(newLine, pos);
|
||||
return start == -1 ? 0 : start + newLine.length();
|
||||
int start = getNewLinePosBefore(code, pos);
|
||||
return start == -1 ? 0 : start + 1;
|
||||
}
|
||||
|
||||
public static int getLineEndForPos(String code, int pos) {
|
||||
int end = code.indexOf(ICodeWriter.NL, pos);
|
||||
int end = getNewLinePosAfter(code, pos);
|
||||
return end == -1 ? code.length() : end;
|
||||
}
|
||||
|
||||
public static int getLineNumForPos(String code, int pos) {
|
||||
String newLine = ICodeWriter.NL;
|
||||
public static int getNewLinePosAfter(String code, int startPos) {
|
||||
int pos = code.indexOf('\n', startPos);
|
||||
if (pos != -1) {
|
||||
// check for '\r\n'
|
||||
int prev = pos - 1;
|
||||
if (code.charAt(prev) == '\r') {
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
public static int getNewLinePosBefore(String code, int startPos) {
|
||||
return code.lastIndexOf('\n', startPos);
|
||||
}
|
||||
|
||||
public static int getLineNumForPos(String code, int pos, String newLine) {
|
||||
int newLineLen = newLine.length();
|
||||
int line = 1;
|
||||
int prev = 0;
|
||||
@@ -35,4 +55,71 @@ public class CodeUtils {
|
||||
line++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cut method code (including comments and annotations) from class code.
|
||||
*
|
||||
* @return method code or empty string if metadata is not available
|
||||
*/
|
||||
public static String extractMethodCode(MethodNode mth, ICodeInfo codeInfo) {
|
||||
int end = getMethodEnd(mth, codeInfo);
|
||||
if (end == -1) {
|
||||
return "";
|
||||
}
|
||||
int start = getMethodStart(mth, codeInfo);
|
||||
if (end < start) {
|
||||
return "";
|
||||
}
|
||||
return codeInfo.getCodeStr().substring(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search first empty line before method definition to include comments and annotations
|
||||
*/
|
||||
private static int getMethodStart(MethodNode mth, ICodeInfo codeInfo) {
|
||||
int pos = mth.getDefPosition();
|
||||
String newLineStr = mth.root().getArgs().getCodeNewLineStr();
|
||||
String emptyLine = newLineStr + newLineStr;
|
||||
int emptyLinePos = codeInfo.getCodeStr().lastIndexOf(emptyLine, pos);
|
||||
return emptyLinePos == -1 ? pos : emptyLinePos + emptyLine.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search method end position in provided class code info.
|
||||
*
|
||||
* @return end pos or -1 if metadata not available
|
||||
*/
|
||||
public static int getMethodEnd(MethodNode mth, ICodeInfo codeInfo) {
|
||||
if (!codeInfo.hasMetadata()) {
|
||||
return -1;
|
||||
}
|
||||
// skip nested nodes DEF/END until first unpaired END annotation (end of this method)
|
||||
Integer end = codeInfo.getCodeMetadata().searchDown(mth.getDefPosition() + 1, new BiFunction<>() {
|
||||
int nested = 0;
|
||||
|
||||
@Override
|
||||
public Integer apply(Integer pos, ICodeAnnotation ann) {
|
||||
switch (ann.getAnnType()) {
|
||||
case DECLARATION:
|
||||
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||
switch (node.getAnnType()) {
|
||||
case CLASS:
|
||||
case METHOD:
|
||||
nested++;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case END:
|
||||
if (nested == 0) {
|
||||
return pos;
|
||||
}
|
||||
nested--;
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return end == null ? -1 : end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public class Consts {
|
||||
public static final boolean DEBUG_FINALLY = false;
|
||||
public static final boolean DEBUG_ATTRIBUTES = false;
|
||||
public static final boolean DEBUG_RESTRUCTURE = false;
|
||||
public static final boolean DEBUG_EVENTS = true;
|
||||
public static final boolean DEBUG_EVENTS = Jadx.isDevVersion();
|
||||
|
||||
public static final String CLASS_OBJECT = "java.lang.Object";
|
||||
public static final String CLASS_STRING = "java.lang.String";
|
||||
|
||||
@@ -28,7 +28,6 @@ import jadx.core.dex.visitors.DotGraphVisitor;
|
||||
import jadx.core.dex.visitors.EnumVisitor;
|
||||
import jadx.core.dex.visitors.ExtractFieldInit;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.dex.visitors.FixAccessModifiers;
|
||||
import jadx.core.dex.visitors.FixSwitchOverEnum;
|
||||
import jadx.core.dex.visitors.GenericTypesVisitor;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
@@ -48,18 +47,23 @@ import jadx.core.dex.visitors.ReplaceNewArray;
|
||||
import jadx.core.dex.visitors.ShadowFieldVisitor;
|
||||
import jadx.core.dex.visitors.SignatureProcessor;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.dex.visitors.blocks.BlockFinisher;
|
||||
import jadx.core.dex.visitors.blocks.BlockProcessor;
|
||||
import jadx.core.dex.visitors.blocks.BlockSplitter;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.fixaccessmodifiers.FixAccessModifiers;
|
||||
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
|
||||
import jadx.core.dex.visitors.prepare.AddAndroidConstants;
|
||||
import jadx.core.dex.visitors.prepare.CollectConstValues;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||
import jadx.core.dex.visitors.regions.SwitchOverStringVisitor;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
|
||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||
@@ -67,6 +71,7 @@ import jadx.core.dex.visitors.rename.SourceFileRename;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||
import jadx.core.dex.visitors.typeinference.FixTypesVisitor;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -95,6 +100,8 @@ public class Jadx {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new SignatureProcessor());
|
||||
passes.add(new OverrideMethodVisitor());
|
||||
passes.add(new AddAndroidConstants());
|
||||
passes.add(new CollectConstValues());
|
||||
|
||||
// rename and deobfuscation
|
||||
passes.add(new DeobfuscatorVisitor());
|
||||
@@ -125,6 +132,7 @@ public class Jadx {
|
||||
// blocks IR
|
||||
passes.add(new BlockSplitter());
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockFinisher());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
@@ -141,7 +149,9 @@ public class Jadx {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
passes.add(new FixTypesVisitor());
|
||||
passes.add(new FinishTypeInference());
|
||||
|
||||
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
|
||||
passes.add(new ProcessKotlinInternals());
|
||||
}
|
||||
@@ -163,6 +173,9 @@ public class Jadx {
|
||||
// regions IR
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
if (args.isRestoreSwitchOverString()) {
|
||||
passes.add(new SwitchOverStringVisitor());
|
||||
}
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CleanRegions());
|
||||
|
||||
@@ -216,6 +229,7 @@ public class Jadx {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
passes.add(new FixTypesVisitor());
|
||||
passes.add(new FinishTypeInference());
|
||||
passes.add(new CodeRenameVisitor());
|
||||
passes.add(new DeboxingVisitor());
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -32,8 +31,8 @@ public class ProcessClass {
|
||||
|
||||
private final List<IDexTreeVisitor> passes;
|
||||
|
||||
public ProcessClass(JadxArgs args) {
|
||||
this.passes = Jadx.getPassesList(args);
|
||||
public ProcessClass(List<IDexTreeVisitor> passesList) {
|
||||
this.passes = passesList;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -125,6 +124,33 @@ public class ProcessClass {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and process class without its deps
|
||||
*/
|
||||
public void forceProcess(ClassNode cls) {
|
||||
ClassNode topParentClass = cls.getTopParentClass();
|
||||
if (topParentClass != cls) {
|
||||
forceProcess(topParentClass);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
process(cls, false);
|
||||
} catch (Throwable e) {
|
||||
throw new JadxRuntimeException("Failed to process class: " + cls.getFullName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate code for class without processing its deps
|
||||
*/
|
||||
public @Nullable ICodeInfo forceGenerateCode(ClassNode cls) {
|
||||
try {
|
||||
return process(cls, true);
|
||||
} catch (Throwable e) {
|
||||
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void initPasses(RootNode root) {
|
||||
for (IDexTreeVisitor pass : passes) {
|
||||
try {
|
||||
|
||||
@@ -44,14 +44,17 @@ public class ClsSet {
|
||||
private static final String CLST_PATH = "/clst/" + CLST_FILENAME;
|
||||
|
||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||
private static final int VERSION = 4;
|
||||
private static final int VERSION = 5;
|
||||
|
||||
private static final String STRING_CHARSET = "US-ASCII";
|
||||
|
||||
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
|
||||
private static final ArgType[] OBJECT_ARGTYPE_ARRAY = new ArgType[] { ArgType.OBJECT };
|
||||
|
||||
private final RootNode root;
|
||||
|
||||
private int androidApiLevel;
|
||||
|
||||
public ClsSet(RootNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
@@ -79,7 +82,8 @@ public class ClsSet {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
||||
LOG.debug("Clst file loaded in {}ms, android api: {}, classes: {}, methods: {}",
|
||||
time, androidApiLevel, classes.length, methodsCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +97,7 @@ public class ClsSet {
|
||||
cls.load();
|
||||
|
||||
ClspClassSource source = getClspClassSource(cls);
|
||||
ClspClass nClass = new ClspClass(clsType, k, source);
|
||||
ClspClass nClass = new ClspClass(clsType, k, cls.getAccessFlags().rawValue(), source);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
@@ -151,7 +155,11 @@ public class ClsSet {
|
||||
// cls is java.lang.Object
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
|
||||
int interfacesCount = cls.getInterfaces().size();
|
||||
if (interfacesCount == 0 && superClass == ArgType.OBJECT) {
|
||||
return OBJECT_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] parents = new ArgType[1 + interfacesCount];
|
||||
parents[0] = superClass;
|
||||
int k = 1;
|
||||
for (ArgType iface : cls.getInterfaces()) {
|
||||
@@ -193,10 +201,12 @@ public class ClsSet {
|
||||
DataOutputStream out = new DataOutputStream(output);
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
out.writeInt(androidApiLevel);
|
||||
|
||||
Map<String, ClspClass> names = new HashMap<>(classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (ClspClass cls : classes) {
|
||||
out.writeInt(cls.getAccFlags());
|
||||
writeUnsignedByte(out, cls.getSource().ordinal());
|
||||
String clsName = cls.getName();
|
||||
writeString(out, clsName);
|
||||
@@ -243,6 +253,10 @@ public class ClsSet {
|
||||
out.writeByte(-1);
|
||||
return;
|
||||
}
|
||||
if (arr == OBJECT_ARGTYPE_ARRAY) {
|
||||
out.writeByte(-2);
|
||||
return;
|
||||
}
|
||||
int size = arr.length;
|
||||
out.writeByte(size);
|
||||
if (size != 0) {
|
||||
@@ -294,22 +308,22 @@ public class ClsSet {
|
||||
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
|
||||
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||
int readHeaderLength = in.read(header);
|
||||
int version = in.readByte();
|
||||
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|
||||
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|
||||
|| version != VERSION) {
|
||||
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))) {
|
||||
throw new DecodeException("Wrong jadx class set header");
|
||||
}
|
||||
int version = in.readByte();
|
||||
if (version != VERSION) {
|
||||
throw new DecodeException("Wrong jadx class set version, got: " + version + ", expect: " + VERSION);
|
||||
}
|
||||
androidApiLevel = in.readInt();
|
||||
int clsCount = in.readInt();
|
||||
classes = new ClspClass[clsCount];
|
||||
ClspClassSource[] clspClassSources = ClspClassSource.values();
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
int source = readUnsignedByte(in);
|
||||
if (source < 0 || source > clspClassSources.length) {
|
||||
throw new DecodeException("Wrong jadx source identifier");
|
||||
}
|
||||
int accFlags = in.readInt();
|
||||
ClspClassSource clsSource = readClsSource(in);
|
||||
String name = readString(in);
|
||||
classes[i] = new ClspClass(ArgType.object(name), i, clspClassSources[source]);
|
||||
classes[i] = new ClspClass(ArgType.object(name), i, accFlags, clsSource);
|
||||
}
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
ClspClass nClass = classes[i];
|
||||
@@ -321,6 +335,15 @@ public class ClsSet {
|
||||
}
|
||||
}
|
||||
|
||||
private static ClspClassSource readClsSource(DataInputStream in) throws IOException, DecodeException {
|
||||
int source = readUnsignedByte(in);
|
||||
ClspClassSource[] clspClassSources = ClspClassSource.values();
|
||||
if (source < 0 || source > clspClassSources.length) {
|
||||
throw new DecodeException("Wrong jadx source identifier: " + source);
|
||||
}
|
||||
return clspClassSources[source];
|
||||
}
|
||||
|
||||
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||
int mCount = in.readShort();
|
||||
List<ClspMethod> methods = new ArrayList<>(mCount);
|
||||
@@ -366,17 +389,20 @@ public class ClsSet {
|
||||
@Nullable
|
||||
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == -1) {
|
||||
return null;
|
||||
switch (count) {
|
||||
case -1:
|
||||
return null;
|
||||
case -2:
|
||||
return OBJECT_ARGTYPE_ARRAY;
|
||||
case 0:
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
default:
|
||||
ArgType[] arr = new ArgType[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
arr[i] = readArgType(in);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
if (count == 0) {
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] arr = new ArgType[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
arr[i] = readArgType(in);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private ArgType readArgType(DataInputStream in) throws IOException {
|
||||
@@ -384,9 +410,6 @@ public class ClsSet {
|
||||
if (ordinal == -1) {
|
||||
return null;
|
||||
}
|
||||
if (ordinal >= TypeEnum.values().length) {
|
||||
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
|
||||
}
|
||||
switch (TypeEnum.values()[ordinal]) {
|
||||
case WILDCARD:
|
||||
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
|
||||
@@ -414,7 +437,7 @@ public class ClsSet {
|
||||
return classes[in.readInt()].getClsType();
|
||||
|
||||
case ARRAY:
|
||||
return ArgType.array(readArgType(in));
|
||||
return ArgType.array(Objects.requireNonNull(readArgType(in)));
|
||||
|
||||
case PRIMITIVE:
|
||||
char shortName = (char) in.readByte();
|
||||
@@ -474,4 +497,12 @@ public class ClsSet {
|
||||
nameMap.put(cls.getName(), cls);
|
||||
}
|
||||
}
|
||||
|
||||
public int getAndroidApiLevel() {
|
||||
return androidApiLevel;
|
||||
}
|
||||
|
||||
public void setAndroidApiLevel(int androidApiLevel) {
|
||||
this.androidApiLevel = androidApiLevel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
/**
|
||||
@@ -16,21 +19,17 @@ public class ClspClass {
|
||||
|
||||
private final ArgType clsType;
|
||||
private final int id;
|
||||
private final int accFlags;
|
||||
private ArgType[] parents;
|
||||
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
|
||||
private List<ArgType> typeParameters = Collections.emptyList();
|
||||
|
||||
private ClspClassSource source;
|
||||
private final ClspClassSource source;
|
||||
|
||||
public ClspClass(ArgType clsType, int id) {
|
||||
this.clsType = clsType;
|
||||
this.id = id;
|
||||
this.source = ClspClassSource.APP;
|
||||
}
|
||||
|
||||
public ClspClass(ArgType clsType, int id, ClspClassSource source) {
|
||||
public ClspClass(ArgType clsType, int id, int accFlags, ClspClassSource source) {
|
||||
this.clsType = clsType;
|
||||
this.id = id;
|
||||
this.accFlags = accFlags;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@@ -46,6 +45,18 @@ public class ClspClass {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getAccFlags() {
|
||||
return accFlags;
|
||||
}
|
||||
|
||||
public boolean isInterface() {
|
||||
return AccessFlags.hasFlag(accFlags, AccessFlags.INTERFACE);
|
||||
}
|
||||
|
||||
public boolean hasAccFlag(@MagicConstant(flagsFromClass = AccessFlags.class) int flags) {
|
||||
return AccessFlags.hasFlag(accFlags, flags);
|
||||
}
|
||||
|
||||
public ArgType[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -106,7 +107,7 @@ public class ClspGraph {
|
||||
private void addClass(ClassNode cls) {
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String rawName = clsType.getObject();
|
||||
ClspClass clspClass = new ClspClass(clsType, -1);
|
||||
ClspClass clspClass = new ClspClass(clsType, -1, cls.getAccessFlags().rawValue(), ClspClassSource.APP);
|
||||
clspClass.setParents(ClsSet.makeParentsArray(cls));
|
||||
nameMap.put(rawName, clspClass);
|
||||
}
|
||||
@@ -174,6 +175,8 @@ public class ClspGraph {
|
||||
return result == null ? Collections.emptySet() : result;
|
||||
}
|
||||
|
||||
private static final Set<String> OBJECT_SINGLE_SET = Collections.singleton(Consts.CLASS_OBJECT);
|
||||
|
||||
private void fillSuperTypesCache() {
|
||||
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
|
||||
Set<String> tmpSet = new HashSet<>();
|
||||
@@ -182,10 +185,25 @@ public class ClspGraph {
|
||||
tmpSet.clear();
|
||||
addSuperTypes(cls, tmpSet);
|
||||
Set<String> result;
|
||||
if (tmpSet.isEmpty()) {
|
||||
result = Collections.emptySet();
|
||||
} else {
|
||||
result = new HashSet<>(tmpSet);
|
||||
int size = tmpSet.size();
|
||||
switch (size) {
|
||||
case 0: {
|
||||
result = Collections.emptySet();
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
String supCls = tmpSet.iterator().next();
|
||||
if (supCls.equals(Consts.CLASS_OBJECT)) {
|
||||
result = OBJECT_SINGLE_SET;
|
||||
} else {
|
||||
result = Collections.singleton(supCls);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
result = new HashSet<>(tmpSet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
map.put(cls.getName(), result);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.codegen.utils.CodeGenUtils;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.FieldInitInsnAttr;
|
||||
@@ -45,7 +46,6 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.EncodedValueUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||
@@ -421,18 +421,22 @@ public class ClassGen {
|
||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
if (f.contains(JadxAttrType.ANNOTATION_LIST)
|
||||
|| f.contains(AType.JADX_COMMENTS)
|
||||
|| f.contains(AType.CODE_COMMENTS)
|
||||
|| f.getFieldInfo().hasAlias()) {
|
||||
code.newLine();
|
||||
}
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
addFieldUsageInfo(code, f);
|
||||
}
|
||||
if (f.getFieldInfo().hasAlias()) {
|
||||
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
||||
}
|
||||
CodeGenUtils.addComments(code, f);
|
||||
annotationGen.addForField(code, f);
|
||||
|
||||
boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
|
||||
if (f.getFieldInfo().hasAlias() && addInfoComments) {
|
||||
code.newLine();
|
||||
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
||||
}
|
||||
code.startLine(f.getAccessFlags().makeString(addInfoComments));
|
||||
code.startLine(f.getAccessFlags().makeString(f.checkCommentsLevel(CommentsLevel.INFO)));
|
||||
useType(code, f.getType());
|
||||
code.add(' ');
|
||||
code.attachDefinition(f);
|
||||
@@ -648,6 +652,13 @@ public class ClassGen {
|
||||
code.add(clsName);
|
||||
}
|
||||
|
||||
public void addClsShortNameForced(ICodeWriter code, ClassInfo classInfo) {
|
||||
code.add(classInfo.getAliasShortName());
|
||||
if (!isBothClassesInOneTopClass(cls.getClassInfo(), classInfo)) {
|
||||
addImport(classInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||
String fullName = extClsInfo.getAliasFullName();
|
||||
if (fallback || !useImports) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.codegen.utils.CodeGenUtils;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.FieldInitInsnAttr;
|
||||
@@ -60,7 +61,6 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -434,7 +434,7 @@ public class InsnGen {
|
||||
code.add(']');
|
||||
}
|
||||
int dim = arrayType.getArrayDimension();
|
||||
for (; k < dim - 1; k++) {
|
||||
for (; k < dim; k++) {
|
||||
code.add("[]");
|
||||
}
|
||||
break;
|
||||
@@ -749,6 +749,7 @@ public class InsnGen {
|
||||
code.attachAnnotation(refMth);
|
||||
code.add("this");
|
||||
} else {
|
||||
boolean forceShortName = addOuterClassInstance(insn, code, callMth);
|
||||
code.add("new ");
|
||||
if (refMth == null || refMth.contains(AFlag.DONT_GENERATE)) {
|
||||
// use class reference if constructor method is missing (default constructor)
|
||||
@@ -756,7 +757,11 @@ public class InsnGen {
|
||||
} else {
|
||||
code.attachAnnotation(refMth);
|
||||
}
|
||||
mgen.getClassGen().addClsName(code, insn.getClassType());
|
||||
if (forceShortName) {
|
||||
mgen.getClassGen().addClsShortNameForced(code, insn.getClassType());
|
||||
} else {
|
||||
mgen.getClassGen().addClsName(code, insn.getClassType());
|
||||
}
|
||||
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||
if (genericInfoAttr != null) {
|
||||
code.add('<');
|
||||
@@ -777,6 +782,27 @@ public class InsnGen {
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
}
|
||||
|
||||
private boolean addOuterClassInstance(ConstructorInsn insn, ICodeWriter code, MethodNode callMth) throws CodegenException {
|
||||
if (callMth == null || !callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
return false;
|
||||
}
|
||||
ClassNode ctrCls = callMth.getDeclaringClass();
|
||||
if (!ctrCls.isInner() || insn.getArgsCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
InsnArg instArg = insn.getArg(0);
|
||||
if (instArg.isThis()) {
|
||||
return false;
|
||||
}
|
||||
// instance arg should be of an outer class type
|
||||
if (!instArg.getType().equals(ctrCls.getDeclaringClass().getType())) {
|
||||
return false;
|
||||
}
|
||||
addArgDot(code, instArg);
|
||||
// can't use another dot, force short name of class
|
||||
return true;
|
||||
}
|
||||
|
||||
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
cls.ensureProcessed();
|
||||
if (this.mth.getParentClass() == cls) {
|
||||
@@ -936,7 +962,7 @@ public class InsnGen {
|
||||
makeInlinedLambdaMethod(code, customNode, callMth);
|
||||
}
|
||||
|
||||
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) {
|
||||
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
|
||||
InsnNode callInsn = customNode.getCallInsn();
|
||||
if (callInsn instanceof ConstructorInsn) {
|
||||
MethodInfo callMth = ((ConstructorInsn) callInsn).getCallMth();
|
||||
@@ -950,7 +976,7 @@ public class InsnGen {
|
||||
if (customNode.getHandleType() == MethodHandleType.INVOKE_STATIC) {
|
||||
useClass(code, callMth.getDeclClass());
|
||||
} else {
|
||||
code.add("this");
|
||||
addArg(code, customNode.getArg(0));
|
||||
}
|
||||
code.add("::").add(callMth.getAlias());
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.codegen.utils.CodeGenUtils;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
@@ -43,7 +44,6 @@ import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
@@ -269,7 +269,7 @@ public class MethodGen {
|
||||
JadxArgs args = mth.root().getArgs();
|
||||
switch (args.getDecompilationMode()) {
|
||||
case AUTO:
|
||||
if (classGen.isFallbackMode()) {
|
||||
if (classGen.isFallbackMode() || mth.getRegion() == null) {
|
||||
// TODO: try simple mode first
|
||||
dumpInstructions(code);
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -12,14 +13,18 @@ import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.codegen.utils.CodeGenUtils;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.SwitchInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
@@ -44,7 +49,6 @@ import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
@@ -268,26 +272,41 @@ public class RegionGen extends InsnGen {
|
||||
|
||||
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
code.add(fn.getAlias());
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||
// print original value, sometimes replaced with incorrect field
|
||||
EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
|
||||
if (constVal != null && constVal.getValue() != null) {
|
||||
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
|
||||
}
|
||||
}
|
||||
}
|
||||
FieldNode fld = (FieldNode) k;
|
||||
useField(code, fld.getFieldInfo(), fld);
|
||||
} else if (k instanceof FieldInfo) {
|
||||
useField(code, (FieldInfo) k, null);
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
||||
} else if (k instanceof String) {
|
||||
code.add('\"').add((String) k).add('\"');
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
}
|
||||
|
||||
private void useField(ICodeWriter code, FieldInfo fldInfo, @Nullable FieldNode fld) throws CodegenException {
|
||||
boolean isEnum;
|
||||
if (fld != null) {
|
||||
isEnum = fld.getParentClass().isEnum();
|
||||
} else {
|
||||
ClspClass clsDetails = root.getClsp().getClsDetails(fldInfo.getDeclClass().getType());
|
||||
isEnum = clsDetails != null && clsDetails.hasAccFlag(AccessFlags.ENUM);
|
||||
}
|
||||
if (isEnum) {
|
||||
code.add(fldInfo.getAlias());
|
||||
return;
|
||||
}
|
||||
staticField(code, fldInfo);
|
||||
if (fld != null && mth.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||
// print original value, sometimes replaced with incorrect field
|
||||
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
|
||||
if (constVal != null && constVal.getValue() != null) {
|
||||
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
|
||||
code.startLine("try {");
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class SimpleModeHelper {
|
||||
if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
|
||||
startLabel.set(block.getId());
|
||||
}
|
||||
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
|
||||
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlock(prev)) {
|
||||
endGoto.set(prev.getId());
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public class SimpleModeHelper {
|
||||
if (block.contains(AType.EXC_HANDLER)) {
|
||||
startLabel.set(block.getId());
|
||||
}
|
||||
if (nextBlock == null && !mth.isPreExitBlocks(block)) {
|
||||
if (nextBlock == null && !mth.isPreExitBlock(block)) {
|
||||
endGoto.set(block.getId());
|
||||
}
|
||||
prev = block;
|
||||
@@ -145,7 +145,7 @@ public class SimpleModeHelper {
|
||||
// DFS sort blocks to reduce goto count
|
||||
private List<BlockNode> getSortedBlocks() {
|
||||
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
|
||||
BlockUtils.dfsVisit(mth, list::add);
|
||||
BlockUtils.visitDFS(mth, list::add);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import jadx.core.codegen.json.cls.JsonClass;
|
||||
import jadx.core.codegen.json.cls.JsonCodeLine;
|
||||
import jadx.core.codegen.json.cls.JsonField;
|
||||
import jadx.core.codegen.json.cls.JsonMethod;
|
||||
import jadx.core.codegen.utils.CodeGenUtils;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -31,7 +32,6 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -86,7 +86,7 @@ public class JsonCodeGen {
|
||||
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), clsType -> getTypeAlias(classGen, clsType)));
|
||||
}
|
||||
|
||||
ICodeWriter cw = new SimpleCodeWriter();
|
||||
ICodeWriter cw = new SimpleCodeWriter(args);
|
||||
CodeGenUtils.addErrorsAndComments(cw, cls);
|
||||
classGen.addClassDeclaration(cw);
|
||||
jsonCls.setDeclaration(cw.getCodeStr());
|
||||
@@ -130,7 +130,7 @@ public class JsonCodeGen {
|
||||
jsonField.setAlias(field.getAlias());
|
||||
}
|
||||
|
||||
ICodeWriter cw = new SimpleCodeWriter();
|
||||
ICodeWriter cw = new SimpleCodeWriter(args);
|
||||
classGen.addField(cw, field);
|
||||
jsonField.setDeclaration(cw.getCodeStr());
|
||||
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
|
||||
@@ -154,7 +154,7 @@ public class JsonCodeGen {
|
||||
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), clsType -> getTypeAlias(classGen, clsType)));
|
||||
|
||||
MethodGen mthGen = new MethodGen(classGen, mth);
|
||||
ICodeWriter cw = new AnnotatedCodeWriter();
|
||||
ICodeWriter cw = new AnnotatedCodeWriter(args);
|
||||
mthGen.addDefinition(cw);
|
||||
jsonMth.setDeclaration(cw.getCodeStr());
|
||||
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
|
||||
@@ -181,7 +181,7 @@ public class JsonCodeGen {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
||||
String[] lines = codeStr.split(args.getCodeNewLineStr());
|
||||
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
|
||||
ICodeMetadata metadata = code.getCodeMetadata();
|
||||
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
||||
@@ -189,7 +189,7 @@ public class JsonCodeGen {
|
||||
int linesCount = lines.length;
|
||||
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
||||
int lineStartPos = 0;
|
||||
int newLineLen = ICodeWriter.NL.length();
|
||||
int newLineLen = args.getCodeNewLineStr().length();
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
String codeLine = lines[i];
|
||||
int line = i + 2;
|
||||
@@ -208,7 +208,7 @@ public class JsonCodeGen {
|
||||
}
|
||||
|
||||
private String getTypeAlias(ClassGen classGen, ArgType clsType) {
|
||||
ICodeWriter code = new SimpleCodeWriter();
|
||||
ICodeWriter code = new SimpleCodeWriter(args);
|
||||
classGen.useType(code, clsType);
|
||||
return code.getCodeStr();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package jadx.core.codegen.utils;
|
||||
|
||||
import jadx.api.data.CommentStyle;
|
||||
import jadx.api.data.ICodeComment;
|
||||
|
||||
public class CodeComment {
|
||||
|
||||
private final String comment;
|
||||
private final CommentStyle style;
|
||||
|
||||
public CodeComment(String comment, CommentStyle style) {
|
||||
this.comment = comment;
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
public CodeComment(ICodeComment comment) {
|
||||
this(comment.getComment(), comment.getStyle());
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public CommentStyle getStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CodeComment{" + style + ": '" + comment + "'}";
|
||||
}
|
||||
}
|
||||
+38
-42
@@ -1,12 +1,13 @@
|
||||
package jadx.core.utils;
|
||||
package jadx.core.codegen.utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.data.CommentStyle;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
@@ -20,6 +21,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class CodeGenUtils {
|
||||
|
||||
@@ -72,48 +74,36 @@ public class CodeGenUtils {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
List<String> comments = node.getAll(AType.CODE_COMMENTS);
|
||||
if (comments.isEmpty()) {
|
||||
return;
|
||||
boolean startNewLine = node instanceof ICodeNode; // add on same line for instructions
|
||||
for (CodeComment comment : node.getAll(AType.CODE_COMMENTS)) {
|
||||
addCodeComment(code, comment, startNewLine);
|
||||
}
|
||||
if (node instanceof ICodeNode) {
|
||||
// for classes, fields and methods add one line before node declaration
|
||||
}
|
||||
|
||||
private static void addCodeComment(ICodeWriter code, CodeComment comment, boolean startNewLine) {
|
||||
if (startNewLine) {
|
||||
code.startLine();
|
||||
} else {
|
||||
code.add(' ');
|
||||
}
|
||||
if (comments.size() == 1) {
|
||||
String comment = comments.get(0);
|
||||
if (!comment.contains("\n")) {
|
||||
code.add("// ").add(comment);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addMultiLineComment(code, comments);
|
||||
CommentStyle style = comment.getStyle();
|
||||
appendMultiLineString(code, "", style.getStart());
|
||||
appendMultiLineString(code, style.getOnNewLine(), comment.getComment());
|
||||
appendMultiLineString(code, "", style.getEnd());
|
||||
}
|
||||
|
||||
private static void addMultiLineComment(ICodeWriter code, List<String> comments) {
|
||||
boolean first = true;
|
||||
String indent = "";
|
||||
ICodeAnnotation lineAnn = null;
|
||||
for (String comment : comments) {
|
||||
for (String line : comment.split("\n")) {
|
||||
if (first) {
|
||||
first = false;
|
||||
StringBuilder buf = code.getRawBuf();
|
||||
int startLinePos = buf.lastIndexOf(ICodeWriter.NL) + 1;
|
||||
indent = Utils.strRepeat(" ", buf.length() - startLinePos);
|
||||
if (code.isMetadataSupported()) {
|
||||
lineAnn = code.getRawAnnotations().get(startLinePos);
|
||||
}
|
||||
} else {
|
||||
code.newLine().add(indent);
|
||||
if (lineAnn != null) {
|
||||
code.attachLineAnnotation(lineAnn);
|
||||
}
|
||||
}
|
||||
code.add("// ").add(line);
|
||||
}
|
||||
private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\\R");
|
||||
|
||||
private static void appendMultiLineString(ICodeWriter code, String onNewLine, String str) {
|
||||
String[] lines = NEW_LINE_PATTERN.split(str);
|
||||
int linesCount = lines.length;
|
||||
if (linesCount == 0) {
|
||||
return;
|
||||
}
|
||||
code.add(lines[0]);
|
||||
for (int i = 1; i < linesCount; i++) {
|
||||
code.startLine(onNewLine);
|
||||
code.add(lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +114,7 @@ public class CodeGenUtils {
|
||||
code.startLine("/* renamed from: ").add(origName);
|
||||
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
||||
if (renameReasonAttr != null) {
|
||||
code.add(" reason: ");
|
||||
code.add(renameReasonAttr.getDescription());
|
||||
code.add(", reason: ").add(renameReasonAttr.getDescription());
|
||||
}
|
||||
code.add(" */");
|
||||
}
|
||||
@@ -146,10 +135,17 @@ public class CodeGenUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void addInputFileInfo(ICodeWriter code, ClassNode node) {
|
||||
if (node.getClsData() != null && node.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||
String inputFileName = node.getClsData().getInputFileName();
|
||||
public static void addInputFileInfo(ICodeWriter code, ClassNode cls) {
|
||||
if (cls.checkCommentsLevel(CommentsLevel.INFO) && cls.getClsData() != null) {
|
||||
String inputFileName = cls.getClsData().getInputFileName();
|
||||
if (inputFileName != null) {
|
||||
ClassNode declCls = cls.getDeclaringClass();
|
||||
if (declCls != null
|
||||
&& declCls.getClsData() != null
|
||||
&& inputFileName.equals(declCls.getClsData().getInputFileName())) {
|
||||
// don't add same comment for inner classes
|
||||
return;
|
||||
}
|
||||
code.startLine("/* loaded from: ").add(inputFileName).add(" */");
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public class NameMapper {
|
||||
|
||||
private static final Set<String> RESERVED_NAMES = new HashSet<>(
|
||||
Arrays.asList(
|
||||
"_",
|
||||
"abstract",
|
||||
"assert",
|
||||
"boolean",
|
||||
|
||||
@@ -9,8 +9,7 @@ import jadx.core.dex.nodes.PackageNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
* Provides a list of all top level domains with 3 characters and less,
|
||||
* so we can exclude them from deobfuscation.
|
||||
* Provides a list of all top level domains, so we can exclude them from deobfuscation.
|
||||
*/
|
||||
public class ExcludePackageWithTLDNames extends AbstractDeobfCondition {
|
||||
|
||||
@@ -18,23 +17,22 @@ public class ExcludePackageWithTLDNames extends AbstractDeobfCondition {
|
||||
* Lazy load TLD set
|
||||
*/
|
||||
private static class TldHolder {
|
||||
private static final Set<String> TLD_SET = loadTldFile();
|
||||
private static final Set<String> TLD_SET = loadTldSet();
|
||||
}
|
||||
|
||||
private static Set<String> loadTldFile() {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHolder.class.getResourceAsStream("tld_3.txt")))) {
|
||||
private static Set<String> loadTldSet() {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHolder.class.getResourceAsStream("tlds.txt")))) {
|
||||
return reader.lines()
|
||||
.map(String::trim)
|
||||
.filter(line -> !line.startsWith("#") && !line.isEmpty())
|
||||
.collect(Collectors.toSet());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to load top level domain list file: tld_3.txt", e);
|
||||
throw new JadxRuntimeException("Failed to load top level domain list file: tlds.txt", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action check(PackageNode pkg) {
|
||||
if (TldHolder.TLD_SET.contains(pkg.getName())) {
|
||||
if (pkg.isRoot() && TldHolder.TLD_SET.contains(pkg.getName())) {
|
||||
return Action.FORBID_RENAME;
|
||||
}
|
||||
return Action.NO_ACTION;
|
||||
|
||||
@@ -90,7 +90,6 @@ public enum AFlag {
|
||||
|
||||
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
|
||||
REQUEST_CODE_SHRINK,
|
||||
RERUN_SSA_TRANSFORM,
|
||||
|
||||
METHOD_CANDIDATE_FOR_INLINE,
|
||||
USE_LINES_HINTS, // source lines info in methods can be trusted
|
||||
@@ -106,4 +105,5 @@ public enum AFlag {
|
||||
DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)
|
||||
|
||||
RESOLVE_JAVA_JSR,
|
||||
COMPUTE_POST_DOM,
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
import jadx.core.codegen.utils.CodeComment;
|
||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
|
||||
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
@@ -44,7 +46,7 @@ import jadx.core.dex.trycatch.TryCatchBlockAttr;
|
||||
public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
||||
|
||||
// class, method, field, insn
|
||||
public static final AType<AttrList<String>> CODE_COMMENTS = new AType<>();
|
||||
public static final AType<AttrList<CodeComment>> CODE_COMMENTS = new AType<>();
|
||||
|
||||
// class, method, field
|
||||
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
|
||||
@@ -73,6 +75,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
||||
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
|
||||
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
||||
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
|
||||
public static final AType<CodeFeaturesAttr> METHOD_CODE_FEATURES = new AType<>();
|
||||
|
||||
// region
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
|
||||
@@ -139,14 +139,12 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
storage = EMPTY_ATTR_STORAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all attribute
|
||||
*/
|
||||
public void unloadAttributes() {
|
||||
if (storage == EMPTY_ATTR_STORAGE) {
|
||||
return;
|
||||
}
|
||||
storage.unloadAttributes();
|
||||
storage.clearFlags();
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,10 @@ public class AttributeStorage {
|
||||
flags.remove(flag);
|
||||
}
|
||||
|
||||
public void clearFlags() {
|
||||
flags.clear();
|
||||
}
|
||||
|
||||
public <T extends IJadxAttribute> void remove(IJadxAttrType<T> type) {
|
||||
if (!attributes.isEmpty()) {
|
||||
writeAttributes(map -> map.remove(type));
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class CodeFeaturesAttr implements IJadxAttribute {
|
||||
|
||||
public enum CodeFeature {
|
||||
/**
|
||||
* Code contains switch instruction
|
||||
*/
|
||||
SWITCH,
|
||||
|
||||
/**
|
||||
* Code contains new-array instruction
|
||||
*/
|
||||
NEW_ARRAY,
|
||||
}
|
||||
|
||||
public static boolean contains(MethodNode mth, CodeFeature feature) {
|
||||
CodeFeaturesAttr codeFeaturesAttr = mth.get(AType.METHOD_CODE_FEATURES);
|
||||
if (codeFeaturesAttr == null) {
|
||||
return false;
|
||||
}
|
||||
return codeFeaturesAttr.getCodeFeatures().contains(feature);
|
||||
}
|
||||
|
||||
public static void add(MethodNode mth, CodeFeature feature) {
|
||||
CodeFeaturesAttr codeFeaturesAttr = mth.get(AType.METHOD_CODE_FEATURES);
|
||||
if (codeFeaturesAttr == null) {
|
||||
codeFeaturesAttr = new CodeFeaturesAttr();
|
||||
mth.addAttr(codeFeaturesAttr);
|
||||
}
|
||||
codeFeaturesAttr.getCodeFeatures().add(feature);
|
||||
}
|
||||
|
||||
private final Set<CodeFeature> codeFeatures = EnumSet.noneOf(CodeFeature.class);
|
||||
|
||||
public Set<CodeFeature> getCodeFeatures() {
|
||||
return codeFeatures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<CodeFeaturesAttr> getAttrType() {
|
||||
return AType.METHOD_CODE_FEATURES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toAttrString() {
|
||||
return "CodeFeatures{" + codeFeatures + '}';
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class JadxCommentsAttr implements IJadxAttribute {
|
||||
private final Map<CommentsLevel, List<String>> comments = new EnumMap<>(CommentsLevel.class);
|
||||
|
||||
public void add(CommentsLevel level, String comment) {
|
||||
comments.computeIfAbsent(level, (l) -> new ArrayList<>()).add(comment);
|
||||
comments.computeIfAbsent(level, l -> new ArrayList<>()).add(comment);
|
||||
}
|
||||
|
||||
public List<String> formatAndFilter(CommentsLevel level) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class JadxError implements Comparable<JadxError> {
|
||||
@@ -55,7 +54,7 @@ public class JadxError implements Comparable<JadxError> {
|
||||
str.append(cause.getClass());
|
||||
str.append(':');
|
||||
str.append(cause.getMessage());
|
||||
str.append(ICodeWriter.NL);
|
||||
str.append('\n');
|
||||
str.append(Utils.getStackTrace(cause));
|
||||
}
|
||||
return str.toString();
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.plugins.input.data.ILocalVar;
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
@@ -26,6 +25,6 @@ public class LocalVarsDebugInfoAttr implements IJadxAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Debug Info:" + ICodeWriter.NL + " " + Utils.listToString(localVars, ICodeWriter.NL + " ");
|
||||
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ public class MethodOverrideAttr extends PinnedAttribute {
|
||||
/**
|
||||
* All methods overridden by current method. Current method excluded, empty for base method.
|
||||
*/
|
||||
private List<IMethodDetails> overrideList;
|
||||
private final List<IMethodDetails> overrideList;
|
||||
|
||||
/**
|
||||
* All method nodes from override hierarchy. Current method included.
|
||||
*/
|
||||
private SortedSet<MethodNode> relatedMthNodes;
|
||||
|
||||
private Set<IMethodDetails> baseMethods;
|
||||
private final Set<IMethodDetails> baseMethods;
|
||||
|
||||
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes, Set<IMethodDetails> baseMethods) {
|
||||
this.overrideList = overrideList;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user