Compare commits
528 Commits
issue-1973
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 03009b5159 | |||
| d25eda4839 | |||
| 59003bdb1f | |||
| 9d4babcdce | |||
| 8c28a8530e | |||
| 6775cc5a93 | |||
| e648e60af9 | |||
| e6b7db35c1 | |||
| c3f7027bdd | |||
| 2fe95da570 | |||
| 62fa2735dc | |||
| bce6611aaf | |||
| 21aa90c5d1 | |||
| 55e79fb70f | |||
| 044c75ab9f | |||
| 97fa8ff210 | |||
| 27c283fb11 | |||
| bd1c3fffde | |||
| 169ad2901f | |||
| 869422b424 | |||
| ccc4164d54 | |||
| b61642a646 | |||
| 189e4181de | |||
| 5b960db77e | |||
| 3119e8c893 | |||
| f95c5e8f39 | |||
| 1e908f7af3 | |||
| 7ce40baacb | |||
| 7d689a85ea | |||
| c7a162d827 | |||
| 325b3ac991 | |||
| 9a8a11619b | |||
| 7ae6bd737c | |||
| 57fd9b5bdb | |||
| cdd5bf536d | |||
| dcce3aaa39 | |||
| b3d86ae908 | |||
| 8b7d3f497e | |||
| 462e582bb8 | |||
| 14a7b63707 | |||
| 4051a50146 | |||
| b3db337abd | |||
| 15ea9a56b9 | |||
| 165ae24722 | |||
| 11b38ffed2 | |||
| 81dfac6d83 | |||
| a3bd3b09fd | |||
| 13d306024a | |||
| ff64da705c | |||
| 00196e412b | |||
| 06c4fea4d2 | |||
| 69fd88d883 | |||
| f2f145019d | |||
| 7b3563fb62 | |||
| 22ee9a216c | |||
| 06b4467fdc | |||
| ff778ab372 | |||
| ff4dde62ae | |||
| 468b52342d | |||
| 09d39de604 | |||
| eb079bd435 | |||
| d1b1fc0ab6 | |||
| 859d479569 | |||
| b0954e9620 | |||
| dfe1d0477d | |||
| a1aa6d7ecd | |||
| 6f01e9f76b | |||
| be8b96280e | |||
| 2a2806ebd7 | |||
| c7a0f7a092 | |||
| 9710ebe09a | |||
| f0da486703 | |||
| 925503ba04 | |||
| 039900a278 | |||
| d73455c689 | |||
| 07b3e5a7c0 | |||
| c1fc73a524 | |||
| 0d982e9709 | |||
| e6b38e172a | |||
| 331c4aaa5e | |||
| 4e82233cbc | |||
| ad267e1618 | |||
| 54265e34e5 | |||
| c677901aa5 | |||
| 220a2198a1 | |||
| b725dd18b6 | |||
| a0466d4494 | |||
| ae1a5e9277 | |||
| 018c20187d | |||
| add11dff1d | |||
| 005a59668c | |||
| 8f727325d6 | |||
| 7bbb58863b | |||
| 8629e4cf22 | |||
| bd5c6b6516 | |||
| 85a7aaa9fb | |||
| a3df83e60f | |||
| b3be2794a0 | |||
| 02ea3be8f2 | |||
| b78745a87b | |||
| 01af6481f6 | |||
| 0c3e6e77cd | |||
| 8b48219dc3 | |||
| 80187c7e29 | |||
| 22f7b40151 | |||
| 3e709d6693 | |||
| 1aa16c4664 | |||
| 9fba709687 | |||
| 6b61599114 | |||
| 2829e284f3 | |||
| 27b15aeb1c | |||
| 74c52a8884 | |||
| 39e7b82353 | |||
| 365e258180 | |||
| ea68024851 | |||
| 56ae4a6048 | |||
| 1d831d82d4 | |||
| 4c760f1029 | |||
| cf0101f13d | |||
| 748f45b386 | |||
| 9e278efc5c | |||
| cc1beab0b5 | |||
| 2fba204b33 | |||
| 9bf079aad4 | |||
| 6aeaf6aca9 | |||
| 7ea478e18a | |||
| ef99412de1 | |||
| cda1b1ad2c | |||
| 05863881f8 | |||
| 3e208509e0 | |||
| bd75baef56 | |||
| d191f62b8d | |||
| f17c46224d | |||
| e008e818b0 | |||
| 560b1a9ca7 | |||
| 06f113f0a6 | |||
| 48a0073002 | |||
| 88e90722ae | |||
| 1becfa9977 | |||
| 20ed13936a | |||
| 421de675a2 | |||
| 06a118c104 | |||
| 2e93656007 | |||
| c8c4adb60c | |||
| f2e78fe800 | |||
| 7172f8df2d | |||
| d7c6be5664 | |||
| 654cf5e4fb | |||
| bf2d5b5e2e | |||
| 0f495afc99 | |||
| 5f1985f281 | |||
| 2a574d45d5 | |||
| 3ee476c6b6 | |||
| fd3d488016 | |||
| 9898cbb4a1 | |||
| 104a0f0636 | |||
| e58b77267e | |||
| 73913651b4 | |||
| f01e6aa505 | |||
| d9da6a7f89 | |||
| 5726a52ab6 | |||
| f61d90ec2f | |||
| 2cd112cd3d | |||
| 43358643be | |||
| 1f0d3dac0f | |||
| da95a8ae17 | |||
| da3ac6bff0 | |||
| bdbeaff8f0 | |||
| b1f48f1db1 | |||
| 5b09378614 | |||
| b64c93160b | |||
| 58c4f56a71 | |||
| 3d11d1fa87 | |||
| 0d158592e4 | |||
| 0c253f9a1f | |||
| f565178c8c | |||
| f6d13f1860 | |||
| d58c9ac926 | |||
| 7f9d51b9b1 | |||
| 432e49df03 | |||
| b530c234f3 | |||
| 181dcf7b4f | |||
| dc4dcb2bd0 | |||
| 74c396448e | |||
| cb9693a9d1 | |||
| 5d13acc6f3 | |||
| c04dddfa81 | |||
| 1bb645d676 | |||
| ecb597a461 | |||
| 46cd3b5597 | |||
| d523f1b15e | |||
| 8030c2f84e | |||
| 47224dc599 | |||
| d492628bfe | |||
| 13e934ce4d | |||
| 7b54c3ae70 | |||
| b82791706a | |||
| e51a7fe417 | |||
| cf96fdec59 | |||
| 3374f9b64a | |||
| 6b54cde89c | |||
| 59b560b553 | |||
| 8b08ac3806 | |||
| d9d4180581 | |||
| 33f93ddc8a | |||
| bcd0c949dc | |||
| fc0f1f9a1c | |||
| abd64007e2 | |||
| fb02e32a6a | |||
| f33a2e4768 | |||
| 3d8e5e5851 | |||
| 646ee2d963 | |||
| b6f27d8a1a | |||
| 97d04edb01 | |||
| 2486c981a8 | |||
| b7a8a2879e | |||
| 092e897104 | |||
| a0a9f7fd41 | |||
| 00f0f5547b | |||
| 00608f8e51 | |||
| d0351a88ba | |||
| bee476895c | |||
| 580f25faae | |||
| 1d1ca7d0c0 | |||
| dbf4527ce6 | |||
| ec726d6ca1 | |||
| be1c02455f | |||
| aee1e86398 | |||
| c0d721bea1 | |||
| e19a456642 | |||
| acd57930cc | |||
| 73348e5183 | |||
| 32c92dd9a8 | |||
| 8bbdbfc563 | |||
| fd6cb2451b | |||
| 47647bbb9a | |||
| e31d697cd9 | |||
| a796d15289 | |||
| f56eb271a1 | |||
| fbebcb9845 | |||
| 79c91634ad | |||
| f6e12d71a0 | |||
| 62835fbade | |||
| 07c66b5c3c | |||
| e3aa49aaa9 | |||
| 9981949a2b | |||
| ea6492e5ba | |||
| 03d4cb134f | |||
| 37b0b09f25 | |||
| 1e75544636 | |||
| d4ce969fb7 | |||
| 9a692d6be4 | |||
| d3a8a43c74 | |||
| 518da3d8b5 | |||
| 61f5386fe5 | |||
| 20cb9c6a3b | |||
| 4a6784dc68 | |||
| bb6db25c9d | |||
| d0c496858e | |||
| b0ab702f9e | |||
| 5846e6d22e | |||
| 4daad2fc79 | |||
| cca6ca25d1 | |||
| dfa6a83f7c | |||
| d1a3935c9e | |||
| b4f1021885 | |||
| a163e5a1de | |||
| 6eeb303d73 | |||
| 3209dbb7a4 | |||
| d84f0389ec | |||
| 5d720dd29c | |||
| c9d650d186 | |||
| ce60aa8635 | |||
| 4a9276e904 | |||
| b78d3aa2f7 | |||
| 4644d1d8ac | |||
| 7b8fc01319 | |||
| ff66f95a8a | |||
| 4ef1f3b12b | |||
| 8873038b57 | |||
| bf58f03405 | |||
| acf1c8187e | |||
| 7bd1b14728 | |||
| 61f04d6b07 | |||
| 5d5bf325fe | |||
| afdd2d8b39 | |||
| b18604071a | |||
| 801890f0a8 | |||
| 3d36c93beb | |||
| 54fbbd9524 | |||
| 306547d499 | |||
| a43b475be7 | |||
| bc4bb0dc41 | |||
| ea5916452d | |||
| 19f3cdf501 | |||
| 45d320a596 | |||
| 6860a8be7e | |||
| 94915db739 | |||
| 29d114402d | |||
| 6889670b11 | |||
| fe7d527fcc | |||
| 84e211b809 | |||
| f4849d67cf | |||
| 60a8d8b9fd | |||
| 1449354c54 | |||
| 0df474f35a | |||
| de629544a3 | |||
| 7a2dad8ef2 | |||
| a46e35c15c | |||
| 73966fda89 | |||
| fe0ab5ebf8 | |||
| 8345edf76b | |||
| ff0fbba720 | |||
| fe41d6ed3d | |||
| ff95b9e999 | |||
| 87844d2193 | |||
| 2ac0cc62e6 | |||
| 2924bb259c | |||
| 17695babf4 | |||
| 0d105a5095 | |||
| 7eab3c8534 | |||
| 47f2e516e5 | |||
| fdad829c49 | |||
| 2fa7f84251 | |||
| 828cad3287 | |||
| cc0cb6a3d3 | |||
| a67dfcb7e1 | |||
| baa93bad63 | |||
| 45df80f036 | |||
| 792e0d6f3a | |||
| fe9d3bcab7 | |||
| 1e1036c049 | |||
| 60dcdc7096 | |||
| 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 |
@@ -8,56 +8,60 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 11
|
java-version: 25
|
||||||
|
|
||||||
- name: Set jadx version
|
- name: Set jadx version
|
||||||
run: |
|
run: |
|
||||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
JADX_REV=$(git rev-list --count HEAD)
|
||||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
|
||||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v6
|
||||||
with:
|
|
||||||
arguments: dist copyExe
|
- name: Build
|
||||||
|
run: ./gradlew dist distWin
|
||||||
|
env:
|
||||||
|
JADX_BUILD_JAVA_VERSION: 11
|
||||||
|
|
||||||
- name: Save bundle artifact
|
- name: Save bundle artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||||
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||||
# Upload unpacked files for now
|
# Upload unpacked files for now
|
||||||
path: build/jadx/**/*
|
path: build/jadx/**/*
|
||||||
if-no-files-found: error
|
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
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
name: ${{ format('jadx-gui-{0}-no-jre-win', env.JADX_VERSION) }}
|
||||||
path: build/*.exe
|
# Upload unpacked files for now
|
||||||
|
path: jadx-gui/build/jadx-gui-win/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 30
|
retention-days: 14
|
||||||
|
|
||||||
build-win-bundle:
|
build-win-bundle:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: oracle-actions/setup-java@v1
|
uses: oracle-actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
release: 17
|
release: 25
|
||||||
|
|
||||||
- name: Print Java version
|
- name: Print Java version
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -66,19 +70,21 @@ jobs:
|
|||||||
- name: Set jadx version
|
- name: Set jadx version
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
JADX_REV=$(git rev-list --count HEAD)
|
||||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
|
||||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v6
|
||||||
with:
|
|
||||||
arguments: dist -PbundleJRE=true
|
|
||||||
|
|
||||||
- name: Save exe bundle artifact
|
- name: Build
|
||||||
uses: actions/upload-artifact@v4
|
run: ./gradlew dist -PbundleJRE=true
|
||||||
|
|
||||||
|
- name: Save Windows with JRE bundle artifact
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
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
|
if-no-files-found: error
|
||||||
retention-days: 30
|
retention-days: 14
|
||||||
|
|||||||
@@ -14,15 +14,18 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 11
|
java-version: 25
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v6
|
||||||
with:
|
|
||||||
arguments: build dist copyExe
|
- name: Build
|
||||||
|
run: ./gradlew build dist distWin
|
||||||
|
env:
|
||||||
|
JADX_BUILD_JAVA_VERSION: 11
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [master]
|
|
||||||
schedule:
|
|
||||||
- cron: '0 9 * * 5'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: ['java']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v3
|
|
||||||
with:
|
|
||||||
queries: +security-extended
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
|
|
||||||
# Don't build tests in jadx-core also skip tests execution and checkstyle tasks
|
|
||||||
- run: |
|
|
||||||
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v3
|
|
||||||
@@ -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@v6
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: oracle-actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
release: 25
|
||||||
|
|
||||||
|
- name: Set jadx version
|
||||||
|
uses: actions/github-script@v9
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const jadxVersion = context.ref.split('/').pop().substring(1)
|
||||||
|
core.exportVariable('JADX_VERSION', jadxVersion);
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v6
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: ./gradlew dist -PbundleJRE=true
|
||||||
|
|
||||||
|
- name: Save JRE bundle artifact
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
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@v6
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 25
|
||||||
|
|
||||||
|
- name: Set jadx version and release name
|
||||||
|
uses: actions/github-script@v9
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const jadxVersion = context.ref.split('/').pop().substring(1)
|
||||||
|
core.exportVariable('JADX_VERSION', jadxVersion);
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v6
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: ./gradlew dist distWin
|
||||||
|
env:
|
||||||
|
JADX_BUILD_JAVA_VERSION: 11
|
||||||
|
|
||||||
|
- name: Download Windows JRE bundle
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
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@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.JADX_VERSION }}
|
||||||
|
draft: true
|
||||||
|
fail_on_unmatched_files: true
|
||||||
|
files: build/jadx-*.zip
|
||||||
+7
-1
@@ -21,6 +21,7 @@ build/
|
|||||||
classes/
|
classes/
|
||||||
idea/
|
idea/
|
||||||
.gradle/
|
.gradle/
|
||||||
|
.kotlin/
|
||||||
node_modules/
|
node_modules/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
@@ -28,13 +29,18 @@ jadx-output/
|
|||||||
*-tmp/
|
*-tmp/
|
||||||
**/tmp/
|
**/tmp/
|
||||||
*.jobf
|
*.jobf
|
||||||
|
*.jadx
|
||||||
|
|
||||||
*.class
|
*.class
|
||||||
|
*.jar
|
||||||
*.dump
|
*.dump
|
||||||
*.log
|
*.log
|
||||||
*.cfg
|
*.cfg
|
||||||
*.orig
|
*.orig
|
||||||
quark.json
|
*.json
|
||||||
|
*.dot
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
cliff.toml
|
cliff.toml
|
||||||
jadx-gui/src/main/resources/logback.xml
|
jadx-gui/src/main/resources/logback.xml
|
||||||
|
|||||||
+2
-12
@@ -8,17 +8,7 @@ before_script:
|
|||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
|
|
||||||
java-11:
|
build-test:
|
||||||
stage: test
|
|
||||||
image: eclipse-temurin:11
|
|
||||||
script: ./gradlew clean build dist copyExe
|
|
||||||
|
|
||||||
java-17:
|
|
||||||
stage: test
|
|
||||||
image: eclipse-temurin:17
|
|
||||||
script: ./gradlew clean build dist copyExe
|
|
||||||
|
|
||||||
java-21:
|
|
||||||
stage: test
|
stage: test
|
||||||
image: eclipse-temurin:21
|
image: eclipse-temurin:21
|
||||||
script: ./gradlew clean build dist copyExe
|
script: JADX_BUILD_JAVA_VERSION=11 JADX_TEST_JAVA_VERSION=11 ./gradlew clean build dist distWin
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<module name="jadx.jadx-gui.main"/>
|
<module name="jadx.jadx-gui.main"/>
|
||||||
<option name="PROGRAM_PARAMETERS" value="-v"/>
|
<option name="PROGRAM_PARAMETERS" value="-v"/>
|
||||||
<option name="VM_PARAMETERS"
|
<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 --enable-native-access=ALL-UNNAMED -Dsun.java2d.noddraw=true -Dsun.java2d.d3d=false -Dsun.java2d.ddforcevram=true -Dsun.java2d.ddblit=false -Dswing.useflipBufferStrategy=true"/>
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true"/>
|
<option name="Make" enabled="true"/>
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+10
-23
@@ -3,7 +3,7 @@
|
|||||||
## Our Pledge
|
## Our Pledge
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as
|
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
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||||
level of experience, education, socio-economic status, nationality, personal
|
level of experience, education, socio-economic status, nationality, personal
|
||||||
@@ -23,13 +23,13 @@ include:
|
|||||||
Examples of unacceptable behavior by participants include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
advances
|
advances
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
* Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic
|
* 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
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
## Our Responsibilities
|
## Our Responsibilities
|
||||||
|
|
||||||
@@ -45,25 +45,12 @@ threatening, offensive, or harmful.
|
|||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces
|
This Code of Conduct applies within all project spaces, and it also applies when
|
||||||
when an individual is representing the project or its community. Examples of
|
an individual is representing the project or its community in public spaces.
|
||||||
representing a project or community include using an official project e-mail
|
Examples of representing a project or community include using an official
|
||||||
address, posting via an official social media account, or acting as an appointed
|
project e-mail address, posting via an official social media account, or acting
|
||||||
representative at an online or offline event. Representation of a project may be
|
as an appointed representative at an online or offline event. Representation of
|
||||||
further defined and clarified by project maintainers.
|
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.
|
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
|
|||||||
@@ -8,16 +8,19 @@
|
|||||||

|

|
||||||

|

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

|
||||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
|
||||||
**jadx** - Dex to Java decompiler
|
**jadx** - Dex to Java decompiler
|
||||||
|
|
||||||
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
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:**
|
**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`
|
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
||||||
- deobfuscator included
|
- deobfuscator included
|
||||||
|
|
||||||
@@ -48,24 +51,28 @@ 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).
|
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
1. Arch linux 
|
- Arch Linux
|
||||||
```bash
|
[](https://archlinux.org/packages/extra/any/jadx/)
|
||||||
sudo pacman -S jadx
|
[](https://aur.archlinux.org/packages/jadx-git)
|
||||||
```
|
```bash
|
||||||
2. macOS 
|
sudo pacman -S jadx
|
||||||
```bash
|
```
|
||||||
brew install jadx
|
- macOS
|
||||||
```
|
[](https://formulae.brew.sh/formula/jadx)
|
||||||
3. [Flathub ](https://flathub.org/apps/details/com.github.skylot.jadx)
|
```bash
|
||||||
```bash
|
brew install jadx
|
||||||
flatpak install flathub com.github.skylot.jadx
|
```
|
||||||
```
|
- Flathub
|
||||||
|
[](https://flathub.org/apps/com.github.skylot.jadx)
|
||||||
|
```bash
|
||||||
|
flatpak install flathub com.github.skylot.jadx
|
||||||
|
```
|
||||||
|
|
||||||
### Use jadx as a library
|
### 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)
|
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
|
||||||
|
|
||||||
### Build from source
|
### Build from source
|
||||||
JDK 11 or higher must be installed:
|
JDK 17 or higher must be installed:
|
||||||
```
|
```
|
||||||
git clone https://github.com/skylot/jadx.git
|
git clone https://github.com/skylot/jadx.git
|
||||||
cd jadx
|
cd jadx
|
||||||
@@ -79,104 +86,134 @@ and also packed to `build/jadx-<version>.zip`
|
|||||||
|
|
||||||
### Usage
|
### 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, .apkm, .jadx.kts)
|
||||||
commands (use '<command> --help' for command options):
|
commands (use '<command> --help' for command options):
|
||||||
plugins - manage jadx plugins
|
plugins - manage jadx plugins
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-d, --output-dir - output directory
|
-d, --output-dir - output directory
|
||||||
-ds, --output-dir-src - output directory for sources
|
-ds, --output-dir-src - output directory for sources
|
||||||
-dr, --output-dir-res - output directory for resources
|
-dr, --output-dir-res - output directory for resources
|
||||||
-r, --no-res - do not decode resources
|
-r, --no-res - do not decode resources
|
||||||
-s, --no-src - do not decompile source code
|
-s, --no-src - do not decompile source code
|
||||||
--single-class - decompile a single class, full name, raw or alias
|
-j, --threads-count - processing threads count, default: 16
|
||||||
--single-class-output - file or dir for write if decompile a single class
|
--single-class - decompile a single class, full name, raw or alias
|
||||||
--output-format - can be 'java' or 'json', default: java
|
--single-class-output - file or dir for write if decompile a single class
|
||||||
-e, --export-gradle - save as android gradle project
|
--output-format - can be 'java' or 'json', default: java
|
||||||
-j, --threads-count - processing threads count, default: 4
|
-e, --export-gradle - save as gradle project (set '--export-gradle-type' to 'auto')
|
||||||
-m, --decompilation-mode - code output mode:
|
--export-gradle-type - Gradle project template for export:
|
||||||
'auto' - trying best options (default)
|
'auto' - detect automatically
|
||||||
'restructure' - restore code structure (normal java code)
|
'android-app' - Android Application (apk)
|
||||||
'simple' - simplified instructions (linear, with goto's)
|
'android-library' - Android Library (aar)
|
||||||
'fallback' - raw instructions without modifications
|
'simple-java' - simple Java
|
||||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
-m, --decompilation-mode - code output mode:
|
||||||
--no-xml-pretty-print - do not prettify XML
|
'auto' - trying best options (default)
|
||||||
--no-imports - disable use of imports, always write entire package name
|
'restructure' - restore code structure (normal java code)
|
||||||
--no-debug-info - disable debug info parsing and processing
|
'simple' - simplified instructions (linear, with goto's)
|
||||||
--add-debug-lines - add comments with debug line numbers if available
|
'fallback' - raw instructions without modifications
|
||||||
--no-inline-anonymous - disable anonymous classes inline
|
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||||
--no-inline-methods - disable methods inline
|
--no-xml-pretty-print - do not prettify XML
|
||||||
--no-move-inner-classes - disable move inner classes into parent
|
--no-imports - disable use of imports, always write entire package name
|
||||||
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
|
--no-debug-info - disable debug info parsing and processing
|
||||||
--no-finally - don't extract finally block
|
--add-debug-lines - add comments with debug line numbers if available
|
||||||
--no-replace-consts - don't replace constant value with matching constant field
|
--no-inline-anonymous - disable anonymous classes inline
|
||||||
--escape-unicode - escape non latin characters in strings (with \u)
|
--no-inline-methods - disable methods inline
|
||||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
--no-move-inner-classes - disable move inner classes into parent
|
||||||
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
|
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
|
||||||
--mappings-mode - set mode for handling the deobfuscation mapping file:
|
--no-finally - don't extract finally block
|
||||||
'read' - just read, user can always save manually (default)
|
--no-restore-switch-over-string - don't restore switch over string
|
||||||
'read-and-autosave-every-change' - read and autosave after every change
|
--no-replace-consts - don't replace constant value with matching constant field
|
||||||
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
|
--escape-unicode - escape non latin characters in strings (with \u)
|
||||||
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
|
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||||
--deobf - activate deobfuscation
|
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
|
||||||
--deobf-min - min length of name, renamed if shorter, default: 3
|
--mappings-mode - set mode for handling the deobfuscation mapping file:
|
||||||
--deobf-max - max length of name, renamed if longer, default: 64
|
'read' - just read, user can always save manually (default)
|
||||||
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
|
'read-and-autosave-every-change' - read and autosave after every change
|
||||||
--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
|
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
|
||||||
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
|
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
|
||||||
'read' - read if found, don't save (default)
|
--deobf - activate deobfuscation
|
||||||
'read-or-save' - read if found, save otherwise (don't overwrite)
|
--deobf-min - min length of name, renamed if shorter, default: 3
|
||||||
'overwrite' - don't read, always save
|
--deobf-max - max length of name, renamed if longer, default: 64
|
||||||
'ignore' - don't read and don't save
|
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
|
||||||
--deobf-use-sourcename - use source file name as class name alias
|
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
|
||||||
--deobf-res-name-source - better name source for resources:
|
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
|
||||||
'auto' - automatically select best name (default)
|
'read' - read if found, don't save (default)
|
||||||
'resources' - use resources names
|
'read-or-save' - read if found, save otherwise (don't overwrite)
|
||||||
'code' - use R class fields names
|
'overwrite' - don't read, always save
|
||||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
'ignore' - don't read and don't save
|
||||||
--rename-flags - fix options (comma-separated list of):
|
--deobf-res-name-source - better name source for resources:
|
||||||
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
'auto' - automatically select best name (default)
|
||||||
'valid' - rename java identifiers to make them valid,
|
'resources' - use resources names
|
||||||
'printable' - remove non-printable chars from identifiers,
|
'code' - use R class fields names
|
||||||
or single 'none' - to disable all renames
|
--use-source-name-as-class-name-alias - use source name as class name alias:
|
||||||
or single 'all' - to enable all (default)
|
'always' - always use source name if it's available
|
||||||
--integer-format - how integers are displayed:
|
'if-better' - use source name if it seems better than the current one
|
||||||
'auto' - automatically select (default)
|
'never' - never use source name, even if it's available
|
||||||
'decimal' - use decimal
|
--source-name-repeat-limit - allow using source name if it appears less than a limit number, default: 10
|
||||||
'hexadecimal' - use hexadecimal
|
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||||
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
--use-headers-for-detect-resource-extensions - Use headers for detect resource extensions if resource obfuscated
|
||||||
--cfg - save methods control flow graph to dot file
|
--rename-flags - fix options (comma-separated list of):
|
||||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||||
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
|
'valid' - rename java identifiers to make them valid,
|
||||||
--use-dx - use dx/d8 to convert java bytecode
|
'printable' - remove non-printable chars from identifiers,
|
||||||
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
or single 'none' - to disable all renames
|
||||||
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
or single 'all' - to enable all (default)
|
||||||
-v, --verbose - verbose output (set --log-level to DEBUG)
|
--integer-format - how integers are displayed:
|
||||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
'auto' - automatically select (default)
|
||||||
--version - print jadx version
|
'decimal' - use decimal
|
||||||
-h, --help - print this help
|
'hexadecimal' - use hexadecimal
|
||||||
|
--type-update-limit - type update limit count (per one instruction), default: 10
|
||||||
|
--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)
|
||||||
|
--call-graph - save app call graph in format: 'dot' or 'json', default: none
|
||||||
|
-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)
|
||||||
|
--disable-plugins - comma separated list of plugin ids to disable
|
||||||
|
--config <config-ref> - load configuration from file, <config-ref> can be:
|
||||||
|
path to '.json' file
|
||||||
|
short name - uses file with this name from config directory
|
||||||
|
'none' - to disable config loading
|
||||||
|
--save-config <config-ref> - save current options into configuration file and exit, <config-ref> can be:
|
||||||
|
empty - for default config
|
||||||
|
path to '.json' file
|
||||||
|
short name - file will be saved in config directory
|
||||||
|
--print-files - print files and directories used by jadx (config, cache, temp)
|
||||||
|
--version - print jadx version
|
||||||
|
-h, --help - print this help
|
||||||
|
|
||||||
Plugin options (-P<name>=<value>):
|
Plugin options (-P<name>=<value>):
|
||||||
1) dex-input: Load .dex and .apk files
|
dex-input: Load .dex and .apk files
|
||||||
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
|
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
|
||||||
2) java-convert: Convert .class, .jar and .aar files to dex
|
java-convert: Convert .class, .jar and .aar files to dex
|
||||||
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
||||||
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
||||||
3) kotlin-metadata: Use kotlin.Metadata annotation for code generation
|
kotlin-metadata: Use kotlin.Metadata annotation for code generation
|
||||||
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
|
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
|
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
|
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
|
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
|
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
|
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
||||||
4) rename-mappings: various mappings support
|
kotlin-smap: Use kotlin.SourceDebugExtension annotation for rename class alias
|
||||||
- rename-mappings.format - mapping format, values: [auto, TINY, TINY_2, ENIGMA, ENIGMA_DIR, MCP, SRG, TSRG, TSRG2, PROGUARD], default: auto
|
- kotlin-smap.class-alias-source-dbg - rename class alias from SourceDebugExtension, values: [yes, no], default: no
|
||||||
- rename-mappings.invert - invert mapping, values: [yes, no], default: no
|
rename-mappings: various mappings support
|
||||||
|
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, PROGUARD_FILE, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, INTELLIJ_MIGRATION_MAP_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:
|
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_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_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
|
JADX_TMP_DIR - custom temp directory, using system by default
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -186,7 +223,25 @@ Examples:
|
|||||||
jadx --log-level ERROR app.apk
|
jadx --log-level ERROR app.apk
|
||||||
jadx -Pdex-input.verify-checksum=no 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
|
||||||
|
|
||||||
|
Usage for `plugins` command
|
||||||
|
```
|
||||||
|
usage: plugins [options]
|
||||||
|
options:
|
||||||
|
-i, --install <locationId> - install plugin with locationId
|
||||||
|
-j, --install-jar <path-to.jar> - install plugin from jar file
|
||||||
|
-l, --list - list installed plugins
|
||||||
|
-a, --available - list available plugins from jadx-plugins-list (aka marketplace)
|
||||||
|
-u, --update - update installed plugins
|
||||||
|
--uninstall <pluginId> - uninstall plugin with pluginId
|
||||||
|
--disable <pluginId> - disable plugin with pluginId
|
||||||
|
--enable <pluginId> - enable plugin with pluginId
|
||||||
|
--list-all - list all plugins including bundled and dropins
|
||||||
|
--list-versions <locationId> - fetch latest versions of plugin from locationId (will download all artefacts, limited to 10)
|
||||||
|
-h, --help - print this help
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
|
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
|
## 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.
|
To report a security issue, please open a [new security advisory](https://github.com/skylot/jadx/security/advisories/new).
|
||||||
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.
|
Please fill the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
|
||||||
This project follows a 90 day disclosure timeline.
|
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.
|
||||||
|
|||||||
+98
-29
@@ -6,15 +6,47 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.github.ben-manes.versions") version "0.51.0"
|
id("com.github.ben-manes.versions") version "0.54.0"
|
||||||
id("se.patrikerdes.use-latest-versions") version "0.2.18"
|
id("se.patrikerdes.use-latest-versions") version "0.2.19"
|
||||||
id("com.diffplug.spotless") version "6.25.0"
|
id("com.diffplug.spotless") version "8.7.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
val jadxVersion by extra { System.getenv("JADX_VERSION") ?: "dev" }
|
val jadxEnv = loadEnv(file("$rootDir/.env"))
|
||||||
|
|
||||||
|
val jadxVersion by extra { jadxEnv["JADX_VERSION"] ?: "dev" }
|
||||||
println("jadx version: $jadxVersion")
|
println("jadx version: $jadxVersion")
|
||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
|
val jadxBuildJavaVersion by extra { getBuildJavaVersion() }
|
||||||
|
|
||||||
|
fun getBuildJavaVersion(): Int? {
|
||||||
|
val envVarName = "JADX_BUILD_JAVA_VERSION"
|
||||||
|
val buildJavaVer = jadxEnv[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
|
||||||
|
}
|
||||||
|
|
||||||
|
// control ErrorProne checks level, can be: off, warn, error
|
||||||
|
val jadxBuildChecksMode: String by extra { getBuildChecksMode() }
|
||||||
|
|
||||||
|
fun getBuildChecksMode(): String {
|
||||||
|
val buildChecksMode = jadxEnv["JADX_BUILD_CHECKS_MODE"]?.lowercase() ?: "off"
|
||||||
|
val expectedValues = listOf("off", "warn", "error")
|
||||||
|
if (!expectedValues.contains(buildChecksMode)) {
|
||||||
|
throw GradleException("Unknown check mode: '$buildChecksMode', should be one of $expectedValues")
|
||||||
|
}
|
||||||
|
if (buildChecksMode != "off") {
|
||||||
|
val javaVersion = jadxBuildJavaVersion?.let { JavaVersion.toVersion(it) } ?: JavaVersion.current()
|
||||||
|
if (!javaVersion.isCompatibleWith(JavaVersion.VERSION_21)) {
|
||||||
|
throw GradleException("Error Prone requires Java 21")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildChecksMode
|
||||||
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
apply(plugin = "java")
|
apply(plugin = "java")
|
||||||
apply(plugin = "checkstyle")
|
apply(plugin = "checkstyle")
|
||||||
@@ -70,12 +102,49 @@ fun isNonStable(version: String): Boolean {
|
|||||||
return isStable.not()
|
return isStable.not()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadEnv(file: File): Map<String, String> {
|
||||||
|
val envMap = HashMap<String, String>()
|
||||||
|
System
|
||||||
|
.getenv()
|
||||||
|
.filter { it.key.startsWith("JADX_") }
|
||||||
|
.forEach { envMap[it.key] = it.value }
|
||||||
|
if (file.exists()) {
|
||||||
|
file
|
||||||
|
.readLines()
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||||
|
.forEach {
|
||||||
|
val (k, v) = it.split("=", limit = 2)
|
||||||
|
envMap[k.trim()] = v.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println(
|
||||||
|
"Loaded env vars (${envMap.size}):\n${
|
||||||
|
envMap.toList().sortedBy { it.first }.joinToString(separator = "\n") { "${it.first}=${it.second}" }
|
||||||
|
}\n",
|
||||||
|
)
|
||||||
|
return envMap
|
||||||
|
}
|
||||||
|
|
||||||
|
val distWinConfiguration: Configuration by configurations.creating {
|
||||||
|
isCanBeConsumed = false
|
||||||
|
}
|
||||||
|
val distWinWithJreConfiguration: Configuration by configurations.creating {
|
||||||
|
isCanBeConsumed = false
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
distWinConfiguration(project(":jadx-gui", "distWinConfiguration"))
|
||||||
|
distWinWithJreConfiguration(project(":jadx-gui", "distWinWithJreConfiguration"))
|
||||||
|
}
|
||||||
|
|
||||||
val copyArtifacts by tasks.registering(Copy::class) {
|
val copyArtifacts by tasks.registering(Copy::class) {
|
||||||
val jarCliPattern = "jadx-cli-(.*)-all.jar".toPattern()
|
val jarCliPattern = "jadx-cli-(.*)-all.jar".toPattern()
|
||||||
from(tasks.getByPath(":jadx-cli:installShadowDist")) {
|
from(tasks.getByPath(":jadx-cli:installShadowDist")) {
|
||||||
exclude("**/*.jar")
|
exclude("**/*.jar")
|
||||||
filter { line ->
|
filter { line ->
|
||||||
jarCliPattern.matcher(line).replaceAll("jadx-$1-all.jar")
|
jarCliPattern
|
||||||
|
.matcher(line)
|
||||||
|
.replaceAll("jadx-$1-all.jar")
|
||||||
.replace("-jar \"\\\"\$CLASSPATH\\\"\"", "-cp \"\\\"\$CLASSPATH\\\"\" jadx.cli.JadxCLI")
|
.replace("-jar \"\\\"\$CLASSPATH\\\"\"", "-cp \"\\\"\$CLASSPATH\\\"\" jadx.cli.JadxCLI")
|
||||||
.replace("-jar \"%CLASSPATH%\"", "-cp \"%CLASSPATH%\" jadx.cli.JadxCLI")
|
.replace("-jar \"%CLASSPATH%\"", "-cp \"%CLASSPATH%\" jadx.cli.JadxCLI")
|
||||||
}
|
}
|
||||||
@@ -89,6 +158,10 @@ val copyArtifacts by tasks.registering(Copy::class) {
|
|||||||
include("**/*.jar")
|
include("**/*.jar")
|
||||||
rename("jadx-gui-(.*)-all.jar", "jadx-$1-all.jar")
|
rename("jadx-gui-(.*)-all.jar", "jadx-$1-all.jar")
|
||||||
}
|
}
|
||||||
|
from(layout.projectDirectory) {
|
||||||
|
include("README.md")
|
||||||
|
include("LICENSE")
|
||||||
|
}
|
||||||
into(layout.buildDirectory.dir("jadx"))
|
into(layout.buildDirectory.dir("jadx"))
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
@@ -97,42 +170,39 @@ val pack by tasks.registering(Zip::class) {
|
|||||||
from(copyArtifacts)
|
from(copyArtifacts)
|
||||||
archiveFileName.set("jadx-$jadxVersion.zip")
|
archiveFileName.set("jadx-$jadxVersion.zip")
|
||||||
destinationDirectory.set(layout.buildDirectory)
|
destinationDirectory.set(layout.buildDirectory)
|
||||||
|
eachFile {
|
||||||
|
if (path == "bin/jadx" || path == "bin/jadx-gui") {
|
||||||
|
permissions {
|
||||||
|
unix("rwxr-xr-x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val copyExe by tasks.registering(Copy::class) {
|
val distWin by tasks.registering(Zip::class) {
|
||||||
group = "jadx"
|
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
|
from(distWinConfiguration)
|
||||||
mustRunAfter("jar")
|
|
||||||
mustRunAfter(pack)
|
|
||||||
|
|
||||||
from(tasks.getByPath("jadx-gui:createExe"))
|
destinationDirectory.set(layout.buildDirectory.dir("distWin"))
|
||||||
include("*.exe")
|
archiveFileName.set("jadx-gui-$jadxVersion-win.zip")
|
||||||
into(layout.buildDirectory)
|
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
val distWinBundle by tasks.registering(Copy::class) {
|
val distWinWithJre by tasks.registering(Zip::class) {
|
||||||
group = "jadx"
|
description = "Build Windows with JRE bundle"
|
||||||
description = "Copy bundle to build dir"
|
|
||||||
|
|
||||||
dependsOn(tasks.getByPath(":jadx-gui:distWinWithJre"))
|
from(distWinWithJreConfiguration)
|
||||||
|
|
||||||
// next task dependencies not needed, but gradle throws warning because of same output dir
|
destinationDirectory.set(layout.buildDirectory.dir("distWinWithJre"))
|
||||||
mustRunAfter("jar")
|
archiveFileName.set("jadx-gui-$jadxVersion-with-jre-win.zip")
|
||||||
mustRunAfter(pack)
|
|
||||||
|
|
||||||
from(tasks.getByPath("jadx-gui:distWinWithJre").outputs) {
|
|
||||||
include("*.zip")
|
|
||||||
}
|
|
||||||
into(layout.buildDirectory)
|
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
val dist by tasks.registering {
|
val dist by tasks.registering {
|
||||||
group = "jadx"
|
group = "jadx"
|
||||||
description = "Build jadx distribution zip"
|
description = "Build jadx distribution zip bundles"
|
||||||
|
|
||||||
dependsOn(pack)
|
dependsOn(pack)
|
||||||
|
|
||||||
@@ -140,15 +210,14 @@ val dist by tasks.registering {
|
|||||||
if (os.isWindows) {
|
if (os.isWindows) {
|
||||||
if (project.hasProperty("bundleJRE")) {
|
if (project.hasProperty("bundleJRE")) {
|
||||||
println("Build win bundle with JRE")
|
println("Build win bundle with JRE")
|
||||||
dependsOn(distWinBundle)
|
dependsOn(distWinWithJre)
|
||||||
} else {
|
} else {
|
||||||
dependsOn(copyExe)
|
dependsOn(distWin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val cleanBuildDir by tasks.registering(Delete::class) {
|
val cleanBuildDir by tasks.registering(Delete::class) {
|
||||||
group = "jadx"
|
|
||||||
delete(layout.buildDirectory)
|
delete(layout.buildDirectory)
|
||||||
}
|
}
|
||||||
tasks.getByName("clean").dependsOn(cleanBuildDir)
|
tasks.getByName("clean").dependsOn(cleanBuildDir)
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
|
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.10")
|
||||||
|
|
||||||
|
implementation("org.openrewrite:plugin:6.19.1")
|
||||||
|
implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:4.2.0")
|
||||||
|
implementation("net.ltgt.nullaway:net.ltgt.nullaway.gradle.plugin:2.3.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -1,28 +1,38 @@
|
|||||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||||
|
import net.ltgt.gradle.errorprone.CheckSeverity
|
||||||
|
import net.ltgt.gradle.errorprone.errorprone
|
||||||
|
import net.ltgt.gradle.nullaway.nullaway
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
java
|
java
|
||||||
checkstyle
|
checkstyle
|
||||||
|
|
||||||
|
id("jadx-rewrite")
|
||||||
|
id("net.ltgt.errorprone")
|
||||||
|
id("net.ltgt.nullaway")
|
||||||
}
|
}
|
||||||
|
|
||||||
val jadxVersion: String by rootProject.extra
|
val jadxVersion: String by rootProject.extra
|
||||||
|
val jadxBuildJavaVersion: Int? by rootProject.extra
|
||||||
|
val jadxBuildChecksMode: String by rootProject.extra
|
||||||
|
|
||||||
group = "io.github.skylot"
|
group = "io.github.skylot"
|
||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.slf4j:slf4j-api:2.0.11")
|
implementation("org.slf4j:slf4j-api:2.0.18")
|
||||||
compileOnly("org.jetbrains:annotations:24.1.0")
|
compileOnly("org.jetbrains:annotations:26.1.0")
|
||||||
|
|
||||||
testImplementation("ch.qos.logback:logback-classic:1.4.14")
|
testImplementation("ch.qos.logback:logback-classic:1.5.34")
|
||||||
testImplementation("org.hamcrest:hamcrest-library:2.2")
|
testImplementation("org.assertj:assertj-core:3.27.7")
|
||||||
testImplementation("org.mockito:mockito-core:5.10.0")
|
|
||||||
testImplementation("org.assertj:assertj-core:3.25.2")
|
|
||||||
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.13.3")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|
||||||
testCompileOnly("org.jetbrains:annotations:24.1.0")
|
testCompileOnly("org.jetbrains:annotations:26.1.0")
|
||||||
|
|
||||||
|
errorprone("com.google.errorprone:error_prone_core:2.50.0")
|
||||||
|
errorprone("com.uber.nullaway:nullaway:0.13.7")
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -32,6 +42,11 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
jadxBuildJavaVersion?.let { buildJavaVer ->
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(buildJavaVer)
|
||||||
|
}
|
||||||
|
}
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
@@ -39,6 +54,7 @@ java {
|
|||||||
tasks {
|
tasks {
|
||||||
compileJava {
|
compileJava {
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
|
// options.compilerArgs = listOf("-Xlint:deprecation")
|
||||||
}
|
}
|
||||||
jar {
|
jar {
|
||||||
manifest {
|
manifest {
|
||||||
@@ -55,3 +71,27 @@ tasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile>().configureEach {
|
||||||
|
val checkEnabled = jadxBuildChecksMode != "off"
|
||||||
|
if (checkEnabled) {
|
||||||
|
options.compilerArgs.add("-XDaddTypeAnnotationsToSymbol=true")
|
||||||
|
}
|
||||||
|
options.errorprone {
|
||||||
|
isEnabled = checkEnabled
|
||||||
|
allErrorsAsWarnings = jadxBuildChecksMode == "warn"
|
||||||
|
excludedPaths = ".*/test/.*"
|
||||||
|
nullaway {
|
||||||
|
if (jadxBuildChecksMode == "error") {
|
||||||
|
error()
|
||||||
|
}
|
||||||
|
annotatedPackages.add("jadx")
|
||||||
|
}
|
||||||
|
// TODO: fix and enable all checks
|
||||||
|
disable("MixedMutabilityReturnType")
|
||||||
|
disable("EqualsGetClass")
|
||||||
|
disable("OperatorPrecedence")
|
||||||
|
disable("UnusedVariable")
|
||||||
|
disable("ImmutableEnumChecker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ plugins {
|
|||||||
id("org.jetbrains.kotlin.jvm")
|
id("org.jetbrains.kotlin.jvm")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("stdlib"))
|
||||||
|
implementation(kotlin("reflect")) // don't work from plugin classloader
|
||||||
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget.set(JvmTarget.JVM_11)
|
jvmTarget.set(JvmTarget.JVM_11)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ publishing {
|
|||||||
}
|
}
|
||||||
pom {
|
pom {
|
||||||
name.set(project.name)
|
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")
|
url.set("https://github.com/skylot/jadx")
|
||||||
licenses {
|
licenses {
|
||||||
license {
|
license {
|
||||||
@@ -42,14 +42,14 @@ publishing {
|
|||||||
developer {
|
developer {
|
||||||
id.set("skylot")
|
id.set("skylot")
|
||||||
name.set("Skylot")
|
name.set("Skylot")
|
||||||
email.set("skylot@gmail.com")
|
email.set(project.properties["libEmail"].toString())
|
||||||
url.set("https://github.com/skylot")
|
url.set("https://github.com/skylot")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scm {
|
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")
|
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,35 @@
|
|||||||
|
plugins {
|
||||||
|
id("org.openrewrite.rewrite")
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:3.38.0")
|
||||||
|
rewrite("org.openrewrite.recipe:rewrite-logging-frameworks:3.29.1")
|
||||||
|
rewrite("org.openrewrite.recipe:rewrite-migrate-java:3.37.0")
|
||||||
|
rewrite("org.openrewrite.recipe:rewrite-static-analysis:2.37.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,15 +120,22 @@
|
|||||||
|
|
||||||
<module name="SuppressWarningsHolder"/>
|
<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">
|
<module name="IllegalImport">
|
||||||
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
|
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
|
||||||
|
<!-- don't use nullable annotations from RxJava -->
|
||||||
|
<property name="illegalClasses" value="io.reactivex.rxjava3.annotations.NonNull"/>
|
||||||
|
<property name="illegalClasses" value="io.reactivex.rxjava3.annotations.Nullable"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="RegexpSinglelineJava">
|
<module name="RegexpSinglelineJava">
|
||||||
<property name="id" value="printstacktrace"/>
|
<property name="id" value="printstacktrace"/>
|
||||||
<property name="format" value="\.printStackTrace\(\)"/>
|
<property name="format" value="\.printStackTrace\(\)"/>
|
||||||
<property name="ignoreComments" value="true"/>
|
<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>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Generated on 11/22/21, 8:58 PM
|
* Generated on 11/22/21, 8:58 PM
|
||||||
*/
|
*/
|
||||||
package jadx.gui.ui.codeearea;
|
package jadx.gui.ui.codearea;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import javax.swing.text.Segment;
|
import javax.swing.text.Segment;
|
||||||
@@ -9,7 +9,7 @@ import javax.swing.text.Segment;
|
|||||||
import org.fife.ui.rsyntaxtextarea.*;
|
import org.fife.ui.rsyntaxtextarea.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* 用于Smali代码高亮
|
* 用于Smali代码高亮
|
||||||
* MartinKay@qq.com
|
* MartinKay@qq.com
|
||||||
*/
|
*/
|
||||||
@@ -173,7 +173,6 @@ import org.fife.ui.rsyntaxtextarea.*;
|
|||||||
zzAtEOF = false;
|
zzAtEOF = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
%}
|
%}
|
||||||
|
|
||||||
Letter = [A-Za-z]
|
Letter = [A-Za-z]
|
||||||
@@ -678,4 +677,3 @@ FLAG_ARRAY = (":array_"{SimpleName})
|
|||||||
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
|
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
|
||||||
<<EOF>> { 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;
|
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
|
* matched text
|
||||||
*/
|
*/
|
||||||
private int yycolumn;
|
private int yycolumn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
|
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
|
||||||
*/
|
*/
|
||||||
private boolean zzAtBOL = true;
|
private boolean zzAtBOL = true;
|
||||||
@@ -102,6 +102,9 @@
|
|||||||
zzLexicalState = newState;
|
zzLexicalState = newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final int yystate() {
|
||||||
|
return zzLexicalState;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text matched by the current regular expression.
|
* Returns the text matched by the current regular expression.
|
||||||
@@ -112,12 +115,12 @@
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the character at position <tt>pos</tt> from the
|
* Returns the character at position <tt>pos</tt> from the
|
||||||
* matched text.
|
* matched text.
|
||||||
*
|
*
|
||||||
* It is equivalent to yytext().charAt(pos), but faster
|
* 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.
|
* A value from 0 to yylength()-1.
|
||||||
*
|
*
|
||||||
* @return the character at position pos
|
* @return the character at position pos
|
||||||
@@ -138,8 +141,8 @@
|
|||||||
/**
|
/**
|
||||||
* Reports an error that occured while scanning.
|
* Reports an error that occured while scanning.
|
||||||
*
|
*
|
||||||
* In a wellformed scanner (no or only correct usage of
|
* In a wellformed scanner (no or only correct usage of
|
||||||
* yypushback(int) and a match-all fallback rule) this method
|
* yypushback(int) and a match-all fallback rule) this method
|
||||||
* will only be called with things that "Can't Possibly Happen".
|
* will only be called with things that "Can't Possibly Happen".
|
||||||
* If this method is called, something is seriously wrong
|
* If this method is called, something is seriously wrong
|
||||||
* (e.g. a JFlex bug producing a faulty scanner etc.).
|
* (e.g. a JFlex bug producing a faulty scanner etc.).
|
||||||
@@ -159,7 +162,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
--- throws clause
|
--- throws clause
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,12 +209,12 @@
|
|||||||
zzAction = -1;
|
zzAction = -1;
|
||||||
|
|
||||||
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
|
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
|
||||||
|
|
||||||
--- start admin (lexstate etc)
|
--- start admin (lexstate etc)
|
||||||
|
|
||||||
zzForAction: {
|
zzForAction: {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
--- next input, line, col, char count, next transition, isFinal action
|
--- next input, line, col, char count, next transition, isFinal action
|
||||||
zzAction = zzState;
|
zzAction = zzState;
|
||||||
zzMarkedPosL = zzCurrentPosL;
|
zzMarkedPosL = zzCurrentPosL;
|
||||||
@@ -226,11 +229,11 @@
|
|||||||
--- char count update
|
--- char count update
|
||||||
|
|
||||||
--- actions
|
--- actions
|
||||||
default:
|
default:
|
||||||
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
|
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
|
||||||
zzAtEOF = true;
|
zzAtEOF = true;
|
||||||
--- eofvalue
|
--- eofvalue
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
--- no match
|
--- no match
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=JADX GUI
|
||||||
|
Comment=Dex to Java decompiler
|
||||||
|
Icon=jadx
|
||||||
|
Exec=jadx-gui %f
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Development;Java;
|
||||||
|
Keywords=Java;Decompiler;
|
||||||
|
StartupWMClass=jadx-gui-JadxGUI
|
||||||
@@ -2,6 +2,10 @@ org.gradle.warning.mode=all
|
|||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
|
||||||
|
### Disable configuration cache for now: causing issues with spotless and version plugins
|
||||||
|
# org.gradle.configuration-cache=true
|
||||||
|
# org.gradle.configuration-cache.problems=warn
|
||||||
|
|
||||||
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
|
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
|
||||||
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
|
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
|
||||||
org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions \
|
org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions \
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026
|
distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright © 2015 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (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/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -84,7 +86,7 @@ done
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# 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\n' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -112,7 +114,6 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -170,7 +171,6 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -203,15 +203,14 @@ fi
|
|||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
# Collect all arguments for the java command:
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# and any embedded shellness will be escaped.
|
# and any embedded shellness will be escaped.
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# treated as '${Hostname}' itself on the command line.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|||||||
Vendored
+13
-12
@@ -13,6 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("jadx-java")
|
id("jadx-java")
|
||||||
|
id("jadx-library")
|
||||||
id("application")
|
id("application")
|
||||||
|
|
||||||
// use shadow only for application scripts, jar will be copied from jadx-gui
|
// 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.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":jadx-core"))
|
implementation(project(":jadx-core"))
|
||||||
implementation(project(":jadx-plugins-tools"))
|
implementation(project(":jadx-plugins-tools"))
|
||||||
|
implementation(project(":jadx-commons:jadx-app-commons"))
|
||||||
|
implementation(project(":jadx-commons:jadx-analysis"))
|
||||||
|
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-dex-input"))
|
runtimeOnly(project(":jadx-plugins:jadx-dex-input"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-java-input"))
|
runtimeOnly(project(":jadx-plugins:jadx-java-input"))
|
||||||
@@ -16,11 +19,15 @@ dependencies {
|
|||||||
runtimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
runtimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-rename-mappings"))
|
runtimeOnly(project(":jadx-plugins:jadx-rename-mappings"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata"))
|
runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-script:jadx-script-plugin"))
|
runtimeOnly(project(":jadx-plugins:jadx-kotlin-source-debug-extension"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
|
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
|
||||||
|
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
|
||||||
|
runtimeOnly(project(":jadx-plugins:jadx-apkm-input"))
|
||||||
|
runtimeOnly(project(":jadx-plugins:jadx-apks-input"))
|
||||||
|
|
||||||
implementation("org.jcommander:jcommander:1.83")
|
implementation("org.jcommander:jcommander:2.0")
|
||||||
implementation("ch.qos.logback:logback-classic:1.4.14")
|
implementation("ch.qos.logback:logback-classic:1.5.34")
|
||||||
|
implementation("com.google.code.gson:gson:2.14.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
@@ -28,10 +35,14 @@ application {
|
|||||||
mainClass.set("jadx.cli.JadxCLI")
|
mainClass.set("jadx.cli.JadxCLI")
|
||||||
applicationDefaultJvmArgs =
|
applicationDefaultJvmArgs =
|
||||||
listOf(
|
listOf(
|
||||||
|
"-XX:+IgnoreUnrecognizedVMOptions",
|
||||||
"-Xms256M",
|
"-Xms256M",
|
||||||
"-XX:MaxRAMPercentage=70.0",
|
"-XX:MaxRAMPercentage=70.0",
|
||||||
|
"-XX:ParallelGCThreads=3",
|
||||||
// disable zip checks (#1962)
|
// disable zip checks (#1962)
|
||||||
"-Djdk.util.zip.disableZip64ExtraFieldValidation=true",
|
"-Djdk.util.zip.disableZip64ExtraFieldValidation=true",
|
||||||
|
// Foreign API access for 'directories' library (Windows only)
|
||||||
|
"--enable-native-access=ALL-UNNAMED",
|
||||||
)
|
)
|
||||||
applicationDistribution.from("$rootDir") {
|
applicationDistribution.from("$rootDir") {
|
||||||
include("README.md")
|
include("README.md")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.io.PrintStream;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -13,11 +13,11 @@ import java.util.function.Supplier;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import com.beust.jcommander.JCommander;
|
import com.beust.jcommander.JCommander;
|
||||||
|
import com.beust.jcommander.Parameter;
|
||||||
import com.beust.jcommander.ParameterDescription;
|
import com.beust.jcommander.ParameterDescription;
|
||||||
import com.beust.jcommander.ParameterException;
|
import com.beust.jcommander.ParameterException;
|
||||||
import com.beust.jcommander.Parameterized;
|
import com.beust.jcommander.Parameterized;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.plugins.JadxPluginInfo;
|
import jadx.api.plugins.JadxPluginInfo;
|
||||||
import jadx.api.plugins.options.JadxPluginOptions;
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
@@ -25,9 +25,8 @@ import jadx.api.plugins.options.OptionDescription;
|
|||||||
import jadx.core.plugins.JadxPluginManager;
|
import jadx.core.plugins.JadxPluginManager;
|
||||||
import jadx.core.plugins.PluginContext;
|
import jadx.core.plugins.PluginContext;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
|
||||||
|
|
||||||
public class JCommanderWrapper<T> {
|
public class JCommanderWrapper {
|
||||||
private final JCommander jc;
|
private final JCommander jc;
|
||||||
private final JadxCLIArgs argsObj;
|
private final JadxCLIArgs argsObj;
|
||||||
|
|
||||||
@@ -41,15 +40,25 @@ public class JCommanderWrapper<T> {
|
|||||||
|
|
||||||
public boolean parse(String[] args) {
|
public boolean parse(String[] args) {
|
||||||
try {
|
try {
|
||||||
jc.parse(args);
|
String[] fixedArgs = fixArgsForEmptySaveConfig(args);
|
||||||
|
jc.parse(fixedArgs);
|
||||||
|
applyFiles(argsObj);
|
||||||
return true;
|
return true;
|
||||||
} catch (ParameterException e) {
|
} catch (ParameterException e) {
|
||||||
System.err.println("Arguments parse error: " + e.getMessage());
|
System.err.println("Arguments parse error: " + e.getMessage());
|
||||||
printUsage();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void overrideProvided(JadxCLIArgs obj) {
|
||||||
|
applyFiles(obj);
|
||||||
|
for (ParameterDescription parameter : jc.getParameters()) {
|
||||||
|
if (parameter.isAssigned()) {
|
||||||
|
overrideProperty(obj, parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean processCommands() {
|
public boolean processCommands() {
|
||||||
String parsedCommand = jc.getParsedCommand();
|
String parsedCommand = jc.getParsedCommand();
|
||||||
if (parsedCommand == null) {
|
if (parsedCommand == null) {
|
||||||
@@ -58,20 +67,21 @@ public class JCommanderWrapper<T> {
|
|||||||
return JadxCLICommands.process(this, jc, parsedCommand);
|
return JadxCLICommands.process(this, jc, parsedCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void overrideProvided(JadxCLIArgs obj) {
|
/**
|
||||||
List<ParameterDescription> fieldsParams = jc.getParameters();
|
* The main parameter parsing doesn't work if accepting unknown options
|
||||||
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
|
*/
|
||||||
parameters.add(jc.getMainParameterValue());
|
private void applyFiles(JadxCLIArgs argsObj) {
|
||||||
parameters.addAll(fieldsParams);
|
argsObj.setFiles(jc.getUnknownOptions());
|
||||||
for (ParameterDescription parameter : parameters) {
|
}
|
||||||
if (parameter.isAssigned()) {
|
|
||||||
// copy assigned field value to obj
|
/**
|
||||||
Parameterized parameterized = parameter.getParameterized();
|
* Override assigned field value to obj
|
||||||
Object providedValue = parameterized.get(parameter.getObject());
|
*/
|
||||||
Object newValue = mergeValues(parameterized.getType(), providedValue, () -> parameterized.get(obj));
|
private static void overrideProperty(JadxCLIArgs obj, ParameterDescription parameter) {
|
||||||
parameterized.set(obj, newValue);
|
Parameterized parameterized = parameter.getParameterized();
|
||||||
}
|
Object providedValue = parameterized.get(parameter.getObject());
|
||||||
}
|
Object newValue = mergeValues(parameterized.getType(), providedValue, () -> parameterized.get(obj));
|
||||||
|
parameterized.set(obj, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
@@ -85,8 +95,39 @@ public class JCommanderWrapper<T> {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getUnknownOptions() {
|
/**
|
||||||
return jc.getUnknownOptions();
|
* Workaround to allow empty value (i.e. zero arity) for '--save-config' option
|
||||||
|
* Insert empty string arg if another option start right after this one, or it is a last one.
|
||||||
|
*/
|
||||||
|
private String[] fixArgsForEmptySaveConfig(String[] args) {
|
||||||
|
int len = args.length;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
String arg = args[i];
|
||||||
|
if (arg.equals("--save-config")) {
|
||||||
|
int next = i + 1;
|
||||||
|
if (next == len) {
|
||||||
|
return insertEmptyArg(args, next, true);
|
||||||
|
}
|
||||||
|
if (next < len) {
|
||||||
|
String nextArg = args[next];
|
||||||
|
if (nextArg.startsWith("-")) {
|
||||||
|
return insertEmptyArg(args, next, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] insertEmptyArg(String[] args, int i, boolean add) {
|
||||||
|
List<String> strings = new ArrayList<>(Arrays.asList(args));
|
||||||
|
if (add) {
|
||||||
|
strings.add("");
|
||||||
|
} else {
|
||||||
|
strings.add(i, "");
|
||||||
|
}
|
||||||
|
return strings.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void printUsage() {
|
public void printUsage() {
|
||||||
@@ -109,8 +150,11 @@ public class JCommanderWrapper<T> {
|
|||||||
out.println(appendPluginOptions(maxNamesLen));
|
out.println(appendPluginOptions(maxNamesLen));
|
||||||
out.println();
|
out.println();
|
||||||
out.println("Environment variables:");
|
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_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_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(" JADX_TMP_DIR - custom temp directory, using system by default");
|
||||||
out.println();
|
out.println();
|
||||||
out.println("Examples:");
|
out.println("Examples:");
|
||||||
@@ -131,14 +175,16 @@ public class JCommanderWrapper<T> {
|
|||||||
out.println("options:");
|
out.println("options:");
|
||||||
|
|
||||||
List<ParameterDescription> params = jc.getParameters();
|
List<ParameterDescription> params = jc.getParameters();
|
||||||
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
|
Map<String, ParameterDescription> paramsMap = new HashMap<>(params.size());
|
||||||
int maxNamesLen = 0;
|
int maxNamesLen = 0;
|
||||||
for (ParameterDescription p : params) {
|
for (ParameterDescription p : params) {
|
||||||
paramsMap.put(p.getParameterized().getName(), p);
|
paramsMap.put(p.getParameterized().getName(), p);
|
||||||
int len = p.getNames().length();
|
int len = p.getNames().length();
|
||||||
if (len > maxNamesLen) {
|
String valueDesc = getValueDesc(p);
|
||||||
maxNamesLen = len;
|
if (valueDesc != null) {
|
||||||
|
len += 1 + valueDesc.length();
|
||||||
}
|
}
|
||||||
|
maxNamesLen = Math.max(maxNamesLen, len);
|
||||||
}
|
}
|
||||||
maxNamesLen += 3;
|
maxNamesLen += 3;
|
||||||
|
|
||||||
@@ -151,8 +197,12 @@ public class JCommanderWrapper<T> {
|
|||||||
}
|
}
|
||||||
StringBuilder opt = new StringBuilder();
|
StringBuilder opt = new StringBuilder();
|
||||||
opt.append(" ").append(p.getNames());
|
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());
|
addSpaces(opt, maxNamesLen - opt.length());
|
||||||
|
String description = p.getDescription();
|
||||||
if (description.contains("\n")) {
|
if (description.contains("\n")) {
|
||||||
String[] lines = description.split("\n");
|
String[] lines = description.split("\n");
|
||||||
opt.append("- ").append(lines[0]);
|
opt.append("- ").append(lines[0]);
|
||||||
@@ -165,8 +215,10 @@ public class JCommanderWrapper<T> {
|
|||||||
opt.append("- ").append(description);
|
opt.append("- ").append(description);
|
||||||
}
|
}
|
||||||
if (addDefaults) {
|
if (addDefaults) {
|
||||||
String defaultValue = getDefaultValue(args, f, opt);
|
String defaultValue = getDefaultValue(args, f);
|
||||||
if (defaultValue != null && !description.contains("(default)")) {
|
if (defaultValue != null
|
||||||
|
&& !defaultValue.isEmpty()
|
||||||
|
&& !description.contains("(default)")) {
|
||||||
opt.append(", default: ").append(defaultValue);
|
opt.append(", default: ").append(defaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,6 +227,11 @@ public class JCommanderWrapper<T> {
|
|||||||
return maxNamesLen;
|
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
|
* Get all declared fields of the specified class and all super classes
|
||||||
*/
|
*/
|
||||||
@@ -188,7 +245,7 @@ public class JCommanderWrapper<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String getDefaultValue(Object args, Field f, StringBuilder opt) {
|
private static String getDefaultValue(Object args, Field f) {
|
||||||
try {
|
try {
|
||||||
Class<?> fieldType = f.getType();
|
Class<?> fieldType = f.getType();
|
||||||
if (fieldType == int.class) {
|
if (fieldType == int.class) {
|
||||||
@@ -217,19 +274,20 @@ public class JCommanderWrapper<T> {
|
|||||||
|
|
||||||
private String appendPluginOptions(int maxNamesLen) {
|
private String appendPluginOptions(int maxNamesLen) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
int k = 1;
|
|
||||||
// load and init all options plugins to print all options
|
// 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();
|
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
||||||
pluginManager.load(new JadxExternalPluginsLoader());
|
pluginManager.load(decompiler.getArgs().getPluginLoader());
|
||||||
pluginManager.initAll();
|
pluginManager.initAll();
|
||||||
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
try {
|
||||||
JadxPluginOptions options = context.getOptions();
|
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
||||||
if (options != null) {
|
JadxPluginOptions options = context.getOptions();
|
||||||
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen, k)) {
|
if (options != null) {
|
||||||
k++;
|
appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
pluginManager.unloadAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sb.length() == 0) {
|
if (sb.length() == 0) {
|
||||||
@@ -238,12 +296,12 @@ public class JCommanderWrapper<T> {
|
|||||||
return "\nPlugin options (-P<name>=<value>):" + sb;
|
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();
|
List<OptionDescription> descs = options.getOptionsDescriptions();
|
||||||
if (descs.isEmpty()) {
|
if (descs.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
out.append("\n ").append(k).append(") ");
|
out.append("\n ");
|
||||||
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
|
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
|
||||||
for (OptionDescription desc : descs) {
|
for (OptionDescription desc : descs) {
|
||||||
StringBuilder opt = new StringBuilder();
|
StringBuilder opt = new StringBuilder();
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.security.JadxSecurityFlag;
|
||||||
|
import jadx.api.security.impl.JadxSecurity;
|
||||||
|
import jadx.commons.app.JadxCommonEnv;
|
||||||
|
import jadx.zip.security.DisabledZipSecurity;
|
||||||
|
import jadx.zip.security.IJadxZipSecurity;
|
||||||
|
import jadx.zip.security.JadxZipSecurity;
|
||||||
|
|
||||||
|
public class JadxAppCommon {
|
||||||
|
|
||||||
|
public static void applyEnvVars(JadxArgs jadxArgs) {
|
||||||
|
Set<JadxSecurityFlag> flags = JadxSecurityFlag.all();
|
||||||
|
IJadxZipSecurity zipSecurity;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean disableZipSecurity = JadxCommonEnv.getBool("JADX_DISABLE_ZIP_SECURITY", false);
|
||||||
|
if (disableZipSecurity) {
|
||||||
|
flags.remove(JadxSecurityFlag.SECURE_ZIP_READER);
|
||||||
|
zipSecurity = DisabledZipSecurity.INSTANCE;
|
||||||
|
} else {
|
||||||
|
JadxZipSecurity jadxZipSecurity = new JadxZipSecurity();
|
||||||
|
int maxZipEntriesCount = JadxCommonEnv.getInt("JADX_ZIP_MAX_ENTRIES_COUNT", -2);
|
||||||
|
if (maxZipEntriesCount != -2) {
|
||||||
|
jadxZipSecurity.setMaxEntriesCount(maxZipEntriesCount);
|
||||||
|
}
|
||||||
|
int zipBombMinUncompressedSize = JadxCommonEnv.getInt("JADX_ZIP_BOMB_MIN_UNCOMPRESSED_SIZE", -2);
|
||||||
|
if (zipBombMinUncompressedSize != -2) {
|
||||||
|
jadxZipSecurity.setZipBombMinUncompressedSize(zipBombMinUncompressedSize);
|
||||||
|
}
|
||||||
|
int setZipBombDetectionFactor = JadxCommonEnv.getInt("JADX_ZIP_BOMB_DETECTION_FACTOR", -2);
|
||||||
|
if (setZipBombDetectionFactor != -2) {
|
||||||
|
jadxZipSecurity.setZipBombDetectionFactor(setZipBombDetectionFactor);
|
||||||
|
}
|
||||||
|
zipSecurity = jadxZipSecurity;
|
||||||
|
}
|
||||||
|
jadxArgs.setSecurity(new JadxSecurity(flags, zipSecurity));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,58 +1,83 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.analysis.callgraph.JadxCallGraph;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraph;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.NoOpCodeCache;
|
import jadx.api.impl.NoOpCodeCache;
|
||||||
import jadx.api.impl.SimpleCodeWriter;
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
|
import jadx.api.usage.impl.EmptyUsageInfoCache;
|
||||||
import jadx.cli.LogHelper.LogLevelEnum;
|
import jadx.cli.LogHelper.LogLevelEnum;
|
||||||
|
import jadx.cli.config.JadxConfigAdapter;
|
||||||
|
import jadx.cli.plugins.JadxFilesGetter;
|
||||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||||
|
|
||||||
public class JadxCLI {
|
public class JadxCLI {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
int result = 0;
|
int result = 1;
|
||||||
try {
|
try {
|
||||||
result = execute(args);
|
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 {
|
} finally {
|
||||||
FileUtils.deleteTempRootDir();
|
|
||||||
System.exit(result);
|
System.exit(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int execute(String[] args) {
|
public static int execute(String[] args) {
|
||||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
return execute(args, null);
|
||||||
if (jadxArgs.processArgs(args)) {
|
|
||||||
return processAndSave(jadxArgs);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int processAndSave(JadxCLIArgs cliArgs) {
|
public static int execute(String[] args, @Nullable Consumer<JadxArgs> argsMod) {
|
||||||
LogHelper.initLogLevel(cliArgs);
|
try {
|
||||||
LogHelper.setLogLevelsForLoadingStage();
|
JadxCLIArgs cliArgs = JadxCLIArgs.processArgs(args,
|
||||||
|
new JadxCLIArgs(),
|
||||||
|
new JadxConfigAdapter<>(JadxCLIArgs.class, "cli"));
|
||||||
|
if (cliArgs == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
JadxArgs jadxArgs = buildArgs(cliArgs);
|
||||||
|
if (argsMod != null) {
|
||||||
|
argsMod.accept(jadxArgs);
|
||||||
|
}
|
||||||
|
return runSave(jadxArgs, cliArgs);
|
||||||
|
} catch (JadxArgsValidateException e) {
|
||||||
|
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||||
|
return 1;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.error("Process error:", e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JadxArgs buildArgs(JadxCLIArgs cliArgs) {
|
||||||
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||||
|
jadxArgs.setUsageInfoCache(new EmptyUsageInfoCache());
|
||||||
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
|
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
|
||||||
|
jadxArgs.setFilesGetter(JadxFilesGetter.INSTANCE);
|
||||||
initCodeWriterProvider(jadxArgs);
|
initCodeWriterProvider(jadxArgs);
|
||||||
|
JadxAppCommon.applyEnvVars(jadxArgs);
|
||||||
|
return jadxArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int runSave(JadxArgs jadxArgs, JadxCLIArgs cliArgs) {
|
||||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||||
jadx.load();
|
jadx.load();
|
||||||
if (checkForErrors(jadx)) {
|
if (checkForErrors(jadx)) {
|
||||||
return 1;
|
return 2;
|
||||||
}
|
}
|
||||||
LogHelper.setLogLevelsForDecompileStage();
|
writeCallGraph(jadx, cliArgs);
|
||||||
if (!SingleClassMode.process(jadx, cliArgs)) {
|
if (!SingleClassMode.process(jadx, cliArgs)) {
|
||||||
save(jadx);
|
save(jadx);
|
||||||
}
|
}
|
||||||
@@ -60,11 +85,11 @@ public class JadxCLI {
|
|||||||
if (errorsCount != 0) {
|
if (errorsCount != 0) {
|
||||||
jadx.printErrorsReport();
|
jadx.printErrorsReport();
|
||||||
LOG.error("finished with errors, count: {}", errorsCount);
|
LOG.error("finished with errors, count: {}", errorsCount);
|
||||||
} else {
|
return 3;
|
||||||
LOG.info("done");
|
|
||||||
}
|
}
|
||||||
|
LOG.info("done");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initCodeWriterProvider(JadxArgs jadxArgs) {
|
private static void initCodeWriterProvider(JadxArgs jadxArgs) {
|
||||||
@@ -90,10 +115,10 @@ public class JadxCLI {
|
|||||||
jadx.getArgs().setSkipSources(true);
|
jadx.getArgs().setSkipSources(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (jadx.getErrorsCount() > 0) {
|
int errorsCount = jadx.getErrorsCount();
|
||||||
LOG.error("Load with errors! Check log for details");
|
if (errorsCount > 0) {
|
||||||
|
LOG.error("Loading finished with errors! Count: {}", errorsCount);
|
||||||
// continue processing
|
// continue processing
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -111,4 +136,29 @@ public class JadxCLI {
|
|||||||
System.out.print(" \r");
|
System.out.print(" \r");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void writeCallGraph(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
|
||||||
|
JadxCLIArgs.CallGraphSaveMode mode = cliArgs.callGraphSaveMode;
|
||||||
|
if (mode == null || mode == JadxCLIArgs.CallGraphSaveMode.NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Path outPath = jadx.getArgs().getOutDir().toPath();
|
||||||
|
ICallGraph callGraph = JadxCallGraph.builder(jadx)
|
||||||
|
.resolvedOnly(true)
|
||||||
|
.build();
|
||||||
|
Path cgPath;
|
||||||
|
switch (mode) {
|
||||||
|
case JSON:
|
||||||
|
cgPath = outPath.resolve("callgraph.json");
|
||||||
|
callGraph.writeJson(cgPath);
|
||||||
|
break;
|
||||||
|
case DOT:
|
||||||
|
cgPath = outPath.resolve("callgraph.dot");
|
||||||
|
callGraph.writeDot(cgPath);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unexpected call graph save mode: " + mode);
|
||||||
|
}
|
||||||
|
LOG.info("Call graph saved: {}", cgPath.toAbsolutePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -14,6 +14,10 @@ import java.util.function.Supplier;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.beust.jcommander.DynamicParameter;
|
import com.beust.jcommander.DynamicParameter;
|
||||||
import com.beust.jcommander.IStringConverter;
|
import com.beust.jcommander.IStringConverter;
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
@@ -27,22 +31,35 @@ import jadx.api.JadxDecompiler;
|
|||||||
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
||||||
import jadx.api.args.IntegerFormat;
|
import jadx.api.args.IntegerFormat;
|
||||||
import jadx.api.args.ResourceNameSource;
|
import jadx.api.args.ResourceNameSource;
|
||||||
|
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||||
import jadx.api.args.UserRenamesMappingsMode;
|
import jadx.api.args.UserRenamesMappingsMode;
|
||||||
|
import jadx.cli.config.IJadxConfig;
|
||||||
|
import jadx.cli.config.JadxConfigAdapter;
|
||||||
|
import jadx.cli.config.JadxConfigExclude;
|
||||||
|
import jadx.commons.app.JadxCommonFiles;
|
||||||
|
import jadx.commons.app.JadxTempFiles;
|
||||||
import jadx.core.deobf.conditions.DeobfWhitelist;
|
import jadx.core.deobf.conditions.DeobfWhitelist;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.export.ExportGradleType;
|
||||||
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxCLIArgs {
|
public class JadxCLIArgs implements IJadxConfig {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JadxCLIArgs.class);
|
||||||
|
|
||||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk)")
|
@JadxConfigExclude
|
||||||
protected List<String> files = new ArrayList<>(1);
|
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .apkm, .jadx.kts)")
|
||||||
|
protected List<String> files = Collections.emptyList();
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||||
protected String outDir;
|
protected String outDir;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
|
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
|
||||||
protected String outDirSrc;
|
protected String outDirSrc;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
|
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
|
||||||
protected String outDirRes;
|
protected String outDirRes;
|
||||||
|
|
||||||
@@ -52,20 +69,33 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
||||||
protected boolean skipSources = false;
|
protected boolean skipSources = false;
|
||||||
|
|
||||||
|
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||||
|
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
||||||
protected String singleClass = null;
|
protected String singleClass = null;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
|
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
|
||||||
protected String singleClassOutput = null;
|
protected String singleClassOutput = null;
|
||||||
|
|
||||||
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
||||||
protected String outputFormat = "java";
|
protected String outputFormat = "java";
|
||||||
|
|
||||||
@Parameter(names = { "-e", "--export-gradle" }, description = "save as android gradle project")
|
@Parameter(names = { "-e", "--export-gradle" }, description = "save as gradle project (set '--export-gradle-type' to 'auto')")
|
||||||
protected boolean exportAsGradleProject = false;
|
protected boolean exportAsGradleProject = false;
|
||||||
|
|
||||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
@Parameter(
|
||||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
names = { "--export-gradle-type" },
|
||||||
|
description = "Gradle project template for export:"
|
||||||
|
+ "\n 'auto' - detect automatically"
|
||||||
|
+ "\n 'android-app' - Android Application (apk)"
|
||||||
|
+ "\n 'android-library' - Android Library (aar)"
|
||||||
|
+ "\n 'simple-java' - simple Java",
|
||||||
|
converter = ExportGradleTypeConverter.class
|
||||||
|
)
|
||||||
|
protected @Nullable ExportGradleType exportGradleType = null;
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "-m", "--decompilation-mode" },
|
names = { "-m", "--decompilation-mode" },
|
||||||
@@ -108,6 +138,9 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = "--no-finally", description = "don't extract finally block")
|
@Parameter(names = "--no-finally", description = "don't extract finally block")
|
||||||
protected boolean extractFinally = true;
|
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")
|
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
||||||
protected boolean replaceConsts = true;
|
protected boolean replaceConsts = true;
|
||||||
|
|
||||||
@@ -148,6 +181,7 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected String deobfuscationWhitelistStr = DeobfWhitelist.DEFAULT_STR;
|
protected String deobfuscationWhitelistStr = DeobfWhitelist.DEFAULT_STR;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--deobf-cfg-file" },
|
names = { "--deobf-cfg-file" },
|
||||||
description = "deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format),"
|
description = "deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format),"
|
||||||
@@ -166,8 +200,15 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
protected GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
@SuppressWarnings("DeprecatedIsStillUsed")
|
||||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
@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(
|
@Parameter(
|
||||||
names = { "--deobf-res-name-source" },
|
names = { "--deobf-res-name-source" },
|
||||||
@@ -179,6 +220,22 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
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 = { "--source-name-repeat-limit" },
|
||||||
|
description = "allow using source name if it appears less than a limit number"
|
||||||
|
)
|
||||||
|
protected int sourceNameRepeatLimit = 10;
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--use-kotlin-methods-for-var-names" },
|
names = { "--use-kotlin-methods-for-var-names" },
|
||||||
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
||||||
@@ -186,6 +243,12 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--use-headers-for-detect-resource-extensions" },
|
||||||
|
description = "Use headers for detect resource extensions if resource obfuscated"
|
||||||
|
)
|
||||||
|
protected boolean useHeadersForDetectResourceExtensions = false;
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--rename-flags" },
|
names = { "--rename-flags" },
|
||||||
description = "fix options (comma-separated list of):"
|
description = "fix options (comma-separated list of):"
|
||||||
@@ -194,7 +257,7 @@ public class JadxCLIArgs {
|
|||||||
+ "\n 'printable' - remove non-printable chars from identifiers,"
|
+ "\n 'printable' - remove non-printable chars from identifiers,"
|
||||||
+ "\nor single 'none' - to disable all renames"
|
+ "\nor single 'none' - to disable all renames"
|
||||||
+ "\nor single 'all' - to enable all (default)",
|
+ "\nor single 'all' - to enable all (default)",
|
||||||
converter = RenameConverter.class
|
listConverter = RenameConverter.class
|
||||||
)
|
)
|
||||||
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
||||||
|
|
||||||
@@ -208,6 +271,9 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected IntegerFormat integerFormat = IntegerFormat.AUTO;
|
protected IntegerFormat integerFormat = IntegerFormat.AUTO;
|
||||||
|
|
||||||
|
@Parameter(names = { "--type-update-limit" }, description = "type update limit count (per one instruction)")
|
||||||
|
protected int typeUpdatesLimitCount = 10;
|
||||||
|
|
||||||
@Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
|
@Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
|
||||||
protected boolean fsCaseSensitive = false;
|
protected boolean fsCaseSensitive = false;
|
||||||
|
|
||||||
@@ -217,6 +283,13 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
||||||
protected boolean rawCfgOutput = false;
|
protected boolean rawCfgOutput = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--call-graph" },
|
||||||
|
description = "save app call graph in format: 'dot' or 'json'",
|
||||||
|
converter = CallGraphSaveModeConverter.class
|
||||||
|
)
|
||||||
|
protected CallGraphSaveMode callGraphSaveMode = CallGraphSaveMode.NONE;
|
||||||
|
|
||||||
@Parameter(names = { "-f", "--fallback" }, description = "set '--decompilation-mode' to 'fallback' (deprecated)")
|
@Parameter(names = { "-f", "--fallback" }, description = "set '--decompilation-mode' to 'fallback' (deprecated)")
|
||||||
protected boolean fallbackMode = false;
|
protected boolean fallbackMode = false;
|
||||||
|
|
||||||
@@ -237,45 +310,113 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
|
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
|
||||||
protected boolean verbose = false;
|
protected boolean verbose = false;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
||||||
protected boolean quiet = false;
|
protected boolean quiet = false;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
|
@Parameter(names = { "--disable-plugins" }, description = "comma separated list of plugin ids to disable")
|
||||||
|
protected String disablePlugins = "";
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
|
@Parameter(
|
||||||
|
names = { "--config" },
|
||||||
|
defaultValueDescription = "<config-ref>",
|
||||||
|
description = "load configuration from file, <config-ref> can be:"
|
||||||
|
+ "\n path to '.json' file"
|
||||||
|
+ "\n short name - uses file with this name from config directory"
|
||||||
|
+ "\n 'none' - to disable config loading"
|
||||||
|
)
|
||||||
|
protected String config = "";
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
|
@Parameter(
|
||||||
|
names = { "--save-config" },
|
||||||
|
defaultValueDescription = "<config-ref>",
|
||||||
|
description = "save current options into configuration file and exit, <config-ref> can be:"
|
||||||
|
+ "\n empty - for default config"
|
||||||
|
+ "\n path to '.json' file"
|
||||||
|
+ "\n short name - file will be saved in config directory"
|
||||||
|
)
|
||||||
|
protected String saveConfig = null;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
|
@Parameter(names = { "--print-files" }, description = "print files and directories used by jadx (config, cache, temp)")
|
||||||
|
protected boolean printFiles = false;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "--version" }, description = "print jadx version")
|
@Parameter(names = { "--version" }, description = "print jadx version")
|
||||||
protected boolean printVersion = false;
|
protected boolean printVersion = false;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||||
protected boolean printHelp = false;
|
protected boolean printHelp = false;
|
||||||
|
|
||||||
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
|
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
|
||||||
protected Map<String, String> pluginOptions = new HashMap<>();
|
protected Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
public boolean processArgs(String[] args) {
|
|
||||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
|
||||||
return jcw.parse(args) && process(jcw);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set values only for options provided in cmd.
|
* Obsolete method without config support,
|
||||||
* Used to merge saved options and options passed in command line.
|
* prefer {@link #processArgs(String[], JadxCLIArgs, JadxConfigAdapter)}
|
||||||
*/
|
*/
|
||||||
public boolean overrideProvided(String[] args) {
|
public boolean processArgs(String[] args) {
|
||||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(newInstance());
|
return processArgs(args, this, null) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends JadxCLIArgs> @Nullable T processArgs(
|
||||||
|
String[] args, T argsObj, @Nullable JadxConfigAdapter<T> configAdapter) {
|
||||||
|
JCommanderWrapper jcw = new JCommanderWrapper(argsObj);
|
||||||
if (!jcw.parse(args)) {
|
if (!jcw.parse(args)) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
jcw.overrideProvided(this);
|
applyArgs(argsObj);
|
||||||
return process(jcw);
|
|
||||||
|
// process commands and early exit flags
|
||||||
|
if (!argsObj.process(jcw)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (configAdapter != null) {
|
||||||
|
if (argsObj.printFiles) {
|
||||||
|
printFilesAndDirs(configAdapter.getDefaultConfigFileName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!argsObj.config.equalsIgnoreCase("none")) {
|
||||||
|
// load config file and merge with command line args
|
||||||
|
try {
|
||||||
|
configAdapter.useConfigRef(argsObj.config);
|
||||||
|
T configObj = configAdapter.load();
|
||||||
|
if (configObj != null) {
|
||||||
|
jcw.overrideProvided(configObj);
|
||||||
|
argsObj = configObj;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Config load failed, continue with default values", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// verify result object
|
||||||
|
argsObj.verify();
|
||||||
|
applyArgs(argsObj);
|
||||||
|
|
||||||
|
// save config if requested
|
||||||
|
if (argsObj.saveConfig != null) {
|
||||||
|
saveConfig(argsObj, configAdapter);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return argsObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JadxCLIArgs newInstance() {
|
private static <T extends JadxCLIArgs> void applyArgs(T argsObj) {
|
||||||
return new JadxCLIArgs();
|
// apply log levels
|
||||||
|
LogHelper.initLogLevel(argsObj);
|
||||||
|
LogHelper.applyLogLevels();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
|
public boolean process(JCommanderWrapper jcw) {
|
||||||
files.addAll(jcw.getUnknownOptions());
|
|
||||||
if (jcw.processCommands()) {
|
if (jcw.processCommands()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -287,18 +428,38 @@ public class JadxCLIArgs {
|
|||||||
System.out.println(JadxDecompiler.getVersion());
|
System.out.println(JadxDecompiler.getVersion());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
// unknown options added to 'files', run checks
|
||||||
if (threadsCount <= 0) {
|
for (String fileName : files) {
|
||||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
if (fileName.startsWith("-")) {
|
||||||
|
throw new JadxArgsValidateException("Unknown option: " + fileName);
|
||||||
}
|
}
|
||||||
} catch (JadxException e) {
|
|
||||||
System.err.println("ERROR: " + e.getMessage());
|
|
||||||
jcw.printUsage();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void printFilesAndDirs(String defaultConfigFileName) {
|
||||||
|
System.out.println("Files and directories used by jadx:");
|
||||||
|
System.out.println(" - default config file: " + JadxCommonFiles.getConfigDir().resolve(defaultConfigFileName).toAbsolutePath());
|
||||||
|
System.out.println(" - config directory: " + JadxCommonFiles.getConfigDir().toAbsolutePath());
|
||||||
|
System.out.println(" - cache directory: " + JadxCommonFiles.getCacheDir().toAbsolutePath());
|
||||||
|
System.out.println(" - temp directory: " + JadxTempFiles.getTempRootDir().getParent().toAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verify() {
|
||||||
|
if (threadsCount <= 0) {
|
||||||
|
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends JadxCLIArgs> void saveConfig(T argsObj, @Nullable JadxConfigAdapter<T> configAdapter) {
|
||||||
|
if (configAdapter == null) {
|
||||||
|
throw new JadxRuntimeException("Config adapter set to null, can't save config");
|
||||||
|
}
|
||||||
|
configAdapter.useConfigRef(argsObj.saveConfig);
|
||||||
|
configAdapter.save(argsObj);
|
||||||
|
System.out.println("Config saved to " + configAdapter.getConfigPath().toAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
public JadxArgs toJadxArgs() {
|
public JadxArgs toJadxArgs() {
|
||||||
JadxArgs args = new JadxArgs();
|
JadxArgs args = new JadxArgs();
|
||||||
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
|
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
|
||||||
@@ -328,12 +489,17 @@ public class JadxCLIArgs {
|
|||||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||||
args.setDeobfuscationWhitelist(Arrays.asList(deobfuscationWhitelistStr.split(" ")));
|
args.setDeobfuscationWhitelist(Arrays.asList(deobfuscationWhitelistStr.split(" ")));
|
||||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
args.setUseSourceNameAsClassNameAlias(getUseSourceNameAsClassNameAlias());
|
||||||
|
args.setUseHeadersForDetectResourceExtensions(useHeadersForDetectResourceExtensions);
|
||||||
|
args.setSourceNameRepeatLimit(sourceNameRepeatLimit);
|
||||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||||
args.setResourceNameSource(resourceNameSource);
|
args.setResourceNameSource(resourceNameSource);
|
||||||
args.setEscapeUnicode(escapeUnicode);
|
args.setEscapeUnicode(escapeUnicode);
|
||||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||||
args.setExportAsGradleProject(exportAsGradleProject);
|
args.setExportGradleType(exportGradleType);
|
||||||
|
if (exportAsGradleProject && exportGradleType == null) {
|
||||||
|
args.setExportGradleType(ExportGradleType.AUTO);
|
||||||
|
}
|
||||||
args.setSkipXmlPrettyPrint(skipXmlPrettyPrint);
|
args.setSkipXmlPrettyPrint(skipXmlPrettyPrint);
|
||||||
args.setUseImports(useImports);
|
args.setUseImports(useImports);
|
||||||
args.setDebugInfo(debugInfo);
|
args.setDebugInfo(debugInfo);
|
||||||
@@ -343,19 +509,32 @@ public class JadxCLIArgs {
|
|||||||
args.setMoveInnerClasses(moveInnerClasses);
|
args.setMoveInnerClasses(moveInnerClasses);
|
||||||
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
|
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
|
||||||
args.setExtractFinally(extractFinally);
|
args.setExtractFinally(extractFinally);
|
||||||
args.setRenameFlags(renameFlags);
|
args.setRestoreSwitchOverString(restoreSwitchOverString);
|
||||||
|
args.setRenameFlags(buildEnumSetForRenameFlags());
|
||||||
args.setFsCaseSensitive(fsCaseSensitive);
|
args.setFsCaseSensitive(fsCaseSensitive);
|
||||||
args.setCommentsLevel(commentsLevel);
|
args.setCommentsLevel(commentsLevel);
|
||||||
args.setIntegerFormat(integerFormat);
|
args.setIntegerFormat(integerFormat);
|
||||||
|
args.setTypeUpdatesLimitCount(typeUpdatesLimitCount);
|
||||||
args.setUseDxInput(useDx);
|
args.setUseDxInput(useDx);
|
||||||
args.setPluginOptions(pluginOptions);
|
args.setPluginOptions(pluginOptions);
|
||||||
|
args.setDisabledPlugins(Arrays.stream(disablePlugins.split(",")).map(String::trim).collect(Collectors.toSet()));
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EnumSet<RenameEnum> buildEnumSetForRenameFlags() {
|
||||||
|
EnumSet<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
|
||||||
|
set.addAll(renameFlags);
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getFiles() {
|
public List<String> getFiles() {
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFiles(List<String> files) {
|
||||||
|
this.files = files;
|
||||||
|
}
|
||||||
|
|
||||||
public String getOutDir() {
|
public String getOutDir() {
|
||||||
return outDir;
|
return outDir;
|
||||||
}
|
}
|
||||||
@@ -380,14 +559,26 @@ public class JadxCLIArgs {
|
|||||||
return skipResources;
|
return skipResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSkipResources(boolean skipResources) {
|
||||||
|
this.skipResources = skipResources;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSkipSources() {
|
public boolean isSkipSources() {
|
||||||
return skipSources;
|
return skipSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSkipSources(boolean skipSources) {
|
||||||
|
this.skipSources = skipSources;
|
||||||
|
}
|
||||||
|
|
||||||
public int getThreadsCount() {
|
public int getThreadsCount() {
|
||||||
return threadsCount;
|
return threadsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setThreadsCount(int threadsCount) {
|
||||||
|
this.threadsCount = threadsCount;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isFallbackMode() {
|
public boolean isFallbackMode() {
|
||||||
return fallbackMode;
|
return fallbackMode;
|
||||||
}
|
}
|
||||||
@@ -396,122 +587,293 @@ public class JadxCLIArgs {
|
|||||||
return useDx;
|
return useDx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseDx(boolean useDx) {
|
||||||
|
this.useDx = useDx;
|
||||||
|
}
|
||||||
|
|
||||||
public DecompilationMode getDecompilationMode() {
|
public DecompilationMode getDecompilationMode() {
|
||||||
return decompilationMode;
|
return decompilationMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDecompilationMode(DecompilationMode decompilationMode) {
|
||||||
|
this.decompilationMode = decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isShowInconsistentCode() {
|
public boolean isShowInconsistentCode() {
|
||||||
return showInconsistentCode;
|
return showInconsistentCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setShowInconsistentCode(boolean showInconsistentCode) {
|
||||||
|
this.showInconsistentCode = showInconsistentCode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUseImports() {
|
public boolean isUseImports() {
|
||||||
return useImports;
|
return useImports;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseImports(boolean useImports) {
|
||||||
|
this.useImports = useImports;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDebugInfo() {
|
public boolean isDebugInfo() {
|
||||||
return debugInfo;
|
return debugInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDebugInfo(boolean debugInfo) {
|
||||||
|
this.debugInfo = debugInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAddDebugLines() {
|
public boolean isAddDebugLines() {
|
||||||
return addDebugLines;
|
return addDebugLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAddDebugLines(boolean addDebugLines) {
|
||||||
|
this.addDebugLines = addDebugLines;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInlineAnonymousClasses() {
|
public boolean isInlineAnonymousClasses() {
|
||||||
return inlineAnonymousClasses;
|
return inlineAnonymousClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setInlineAnonymousClasses(boolean inlineAnonymousClasses) {
|
||||||
|
this.inlineAnonymousClasses = inlineAnonymousClasses;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInlineMethods() {
|
public boolean isInlineMethods() {
|
||||||
return inlineMethods;
|
return inlineMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setInlineMethods(boolean inlineMethods) {
|
||||||
|
this.inlineMethods = inlineMethods;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isMoveInnerClasses() {
|
public boolean isMoveInnerClasses() {
|
||||||
return moveInnerClasses;
|
return moveInnerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMoveInnerClasses(boolean moveInnerClasses) {
|
||||||
|
this.moveInnerClasses = moveInnerClasses;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAllowInlineKotlinLambda() {
|
public boolean isAllowInlineKotlinLambda() {
|
||||||
return allowInlineKotlinLambda;
|
return allowInlineKotlinLambda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
|
||||||
|
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isExtractFinally() {
|
public boolean isExtractFinally() {
|
||||||
return extractFinally;
|
return extractFinally;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExtractFinally(boolean extractFinally) {
|
||||||
|
this.extractFinally = extractFinally;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRestoreSwitchOverString() {
|
||||||
|
return restoreSwitchOverString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRestoreSwitchOverString(boolean restoreSwitchOverString) {
|
||||||
|
this.restoreSwitchOverString = restoreSwitchOverString;
|
||||||
|
}
|
||||||
|
|
||||||
public Path getUserRenamesMappingsPath() {
|
public Path getUserRenamesMappingsPath() {
|
||||||
return userRenamesMappingsPath;
|
return userRenamesMappingsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUserRenamesMappingsPath(Path userRenamesMappingsPath) {
|
||||||
|
this.userRenamesMappingsPath = userRenamesMappingsPath;
|
||||||
|
}
|
||||||
|
|
||||||
public UserRenamesMappingsMode getUserRenamesMappingsMode() {
|
public UserRenamesMappingsMode getUserRenamesMappingsMode() {
|
||||||
return userRenamesMappingsMode;
|
return userRenamesMappingsMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUserRenamesMappingsMode(UserRenamesMappingsMode userRenamesMappingsMode) {
|
||||||
|
this.userRenamesMappingsMode = userRenamesMappingsMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationOn() {
|
public boolean isDeobfuscationOn() {
|
||||||
return deobfuscationOn;
|
return deobfuscationOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationOn(boolean deobfuscationOn) {
|
||||||
|
this.deobfuscationOn = deobfuscationOn;
|
||||||
|
}
|
||||||
|
|
||||||
public int getDeobfuscationMinLength() {
|
public int getDeobfuscationMinLength() {
|
||||||
return deobfuscationMinLength;
|
return deobfuscationMinLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
|
||||||
|
this.deobfuscationMinLength = deobfuscationMinLength;
|
||||||
|
}
|
||||||
|
|
||||||
public int getDeobfuscationMaxLength() {
|
public int getDeobfuscationMaxLength() {
|
||||||
return deobfuscationMaxLength;
|
return deobfuscationMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
|
||||||
|
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDeobfuscationWhitelistStr() {
|
public String getDeobfuscationWhitelistStr() {
|
||||||
return deobfuscationWhitelistStr;
|
return deobfuscationWhitelistStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationWhitelistStr(String deobfuscationWhitelistStr) {
|
||||||
|
this.deobfuscationWhitelistStr = deobfuscationWhitelistStr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getGeneratedRenamesMappingFile() {
|
public String getGeneratedRenamesMappingFile() {
|
||||||
return generatedRenamesMappingFile;
|
return generatedRenamesMappingFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGeneratedRenamesMappingFile(String generatedRenamesMappingFile) {
|
||||||
|
this.generatedRenamesMappingFile = generatedRenamesMappingFile;
|
||||||
|
}
|
||||||
|
|
||||||
public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() {
|
public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() {
|
||||||
return generatedRenamesMappingFileMode;
|
return generatedRenamesMappingFileMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode) {
|
||||||
|
this.generatedRenamesMappingFileMode = generatedRenamesMappingFileMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceNameRepeatLimit() {
|
||||||
|
return sourceNameRepeatLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceNameRepeatLimit(int sourceNameRepeatLimit) {
|
||||||
|
this.sourceNameRepeatLimit = sourceNameRepeatLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
|
||||||
|
if (useSourceNameAsClassNameAlias != null) {
|
||||||
|
return useSourceNameAsClassNameAlias;
|
||||||
|
} else if (deobfuscationUseSourceNameAsAlias != null) {
|
||||||
|
// noinspection deprecation
|
||||||
|
return UseSourceNameAsClassNameAlias.create(deobfuscationUseSourceNameAsAlias);
|
||||||
|
} else {
|
||||||
|
return UseSourceNameAsClassNameAlias.getDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias) {
|
||||||
|
this.useSourceNameAsClassNameAlias = useSourceNameAsClassNameAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #getUseSourceNameAsClassNameAlias()} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
||||||
return deobfuscationUseSourceNameAsAlias;
|
return getUseSourceNameAsClassNameAlias().toBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationUseSourceNameAsAlias(Boolean deobfuscationUseSourceNameAsAlias) {
|
||||||
|
this.deobfuscationUseSourceNameAsAlias = deobfuscationUseSourceNameAsAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceNameSource getResourceNameSource() {
|
public ResourceNameSource getResourceNameSource() {
|
||||||
return resourceNameSource;
|
return resourceNameSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
|
||||||
|
this.resourceNameSource = resourceNameSource;
|
||||||
|
}
|
||||||
|
|
||||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||||
return useKotlinMethodsForVarNames;
|
return useKotlinMethodsForVarNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
|
||||||
|
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||||
|
}
|
||||||
|
|
||||||
public IntegerFormat getIntegerFormat() {
|
public IntegerFormat getIntegerFormat() {
|
||||||
return integerFormat;
|
return integerFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIntegerFormat(IntegerFormat integerFormat) {
|
||||||
|
this.integerFormat = integerFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTypeUpdatesLimitCount() {
|
||||||
|
return typeUpdatesLimitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTypeUpdatesLimitCount(int typeUpdatesLimitCount) {
|
||||||
|
this.typeUpdatesLimitCount = typeUpdatesLimitCount;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEscapeUnicode() {
|
public boolean isEscapeUnicode() {
|
||||||
return escapeUnicode;
|
return escapeUnicode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setEscapeUnicode(boolean escapeUnicode) {
|
||||||
|
this.escapeUnicode = escapeUnicode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCfgOutput() {
|
public boolean isCfgOutput() {
|
||||||
return cfgOutput;
|
return cfgOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCfgOutput(boolean cfgOutput) {
|
||||||
|
this.cfgOutput = cfgOutput;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRawCfgOutput() {
|
public boolean isRawCfgOutput() {
|
||||||
return rawCfgOutput;
|
return rawCfgOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRawCfgOutput(boolean rawCfgOutput) {
|
||||||
|
this.rawCfgOutput = rawCfgOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallGraphSaveMode getCallGraphSaveMode() {
|
||||||
|
return callGraphSaveMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallGraphSaveMode(CallGraphSaveMode callGraphSaveMode) {
|
||||||
|
this.callGraphSaveMode = callGraphSaveMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isReplaceConsts() {
|
public boolean isReplaceConsts() {
|
||||||
return replaceConsts;
|
return replaceConsts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setReplaceConsts(boolean replaceConsts) {
|
||||||
|
this.replaceConsts = replaceConsts;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRespectBytecodeAccessModifiers() {
|
public boolean isRespectBytecodeAccessModifiers() {
|
||||||
return respectBytecodeAccessModifiers;
|
return respectBytecodeAccessModifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRespectBytecodeAccessModifiers(boolean respectBytecodeAccessModifiers) {
|
||||||
|
this.respectBytecodeAccessModifiers = respectBytecodeAccessModifiers;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isExportAsGradleProject() {
|
public boolean isExportAsGradleProject() {
|
||||||
return exportAsGradleProject;
|
return exportAsGradleProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||||
|
this.exportAsGradleProject = exportAsGradleProject;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSkipXmlPrettyPrint() {
|
public boolean isSkipXmlPrettyPrint() {
|
||||||
return skipXmlPrettyPrint;
|
return skipXmlPrettyPrint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSkipXmlPrettyPrint(boolean skipXmlPrettyPrint) {
|
||||||
|
this.skipXmlPrettyPrint = skipXmlPrettyPrint;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRenameCaseSensitive() {
|
public boolean isRenameCaseSensitive() {
|
||||||
return renameFlags.contains(RenameEnum.CASE);
|
return renameFlags.contains(RenameEnum.CASE);
|
||||||
}
|
}
|
||||||
@@ -528,18 +890,70 @@ public class JadxCLIArgs {
|
|||||||
return fsCaseSensitive;
|
return fsCaseSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFsCaseSensitive(boolean fsCaseSensitive) {
|
||||||
|
this.fsCaseSensitive = fsCaseSensitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUseHeadersForDetectResourceExtensions() {
|
||||||
|
return useHeadersForDetectResourceExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseHeadersForDetectResourceExtensions(boolean useHeadersForDetectResourceExtensions) {
|
||||||
|
this.useHeadersForDetectResourceExtensions = useHeadersForDetectResourceExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
public CommentsLevel getCommentsLevel() {
|
public CommentsLevel getCommentsLevel() {
|
||||||
return commentsLevel;
|
return commentsLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCommentsLevel(CommentsLevel commentsLevel) {
|
||||||
|
this.commentsLevel = commentsLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public LogHelper.LogLevelEnum getLogLevel() {
|
public LogHelper.LogLevelEnum getLogLevel() {
|
||||||
return logLevel;
|
return logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLogLevel(LogHelper.LogLevelEnum logLevel) {
|
||||||
|
this.logLevel = logLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getPluginOptions() {
|
public Map<String, String> getPluginOptions() {
|
||||||
return pluginOptions;
|
return pluginOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPluginOptions(Map<String, String> pluginOptions) {
|
||||||
|
this.pluginOptions = pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisablePlugins() {
|
||||||
|
return disablePlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisablePlugins(String disablePlugins) {
|
||||||
|
this.disablePlugins = disablePlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExportGradleType(@Nullable ExportGradleType exportGradleType) {
|
||||||
|
this.exportGradleType = exportGradleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputFormat(String outputFormat) {
|
||||||
|
this.outputFormat = outputFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<RenameEnum> getRenameFlags() {
|
||||||
|
return renameFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRenameFlags(Set<RenameEnum> renameFlags) {
|
||||||
|
this.renameFlags = renameFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||||
private final String paramName;
|
private final String paramName;
|
||||||
|
|
||||||
@@ -559,8 +973,8 @@ public class JadxCLIArgs {
|
|||||||
for (String s : value.split(",")) {
|
for (String s : value.split(",")) {
|
||||||
try {
|
try {
|
||||||
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
|
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException(
|
throw new JadxArgsValidateException(
|
||||||
'\'' + s + "' is unknown for parameter " + paramName
|
'\'' + s + "' is unknown for parameter " + paramName
|
||||||
+ ", possible values are " + enumValuesString(RenameEnum.values()));
|
+ ", possible values are " + enumValuesString(RenameEnum.values()));
|
||||||
}
|
}
|
||||||
@@ -593,12 +1007,24 @@ 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 static class DecompilationModeConverter extends BaseEnumConverter<DecompilationMode> {
|
||||||
public DecompilationModeConverter() {
|
public DecompilationModeConverter() {
|
||||||
super(DecompilationMode::valueOf, DecompilationMode::values);
|
super(DecompilationMode::valueOf, DecompilationMode::values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ExportGradleTypeConverter extends BaseEnumConverter<ExportGradleType> {
|
||||||
|
public ExportGradleTypeConverter() {
|
||||||
|
super(ExportGradleType::valueOf, ExportGradleType::values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class LogLevelConverter extends BaseEnumConverter<LogHelper.LogLevelEnum> {
|
public static class LogLevelConverter extends BaseEnumConverter<LogHelper.LogLevelEnum> {
|
||||||
public LogLevelConverter() {
|
public LogLevelConverter() {
|
||||||
super(LogHelper.LogLevelEnum::valueOf, LogHelper.LogLevelEnum::values);
|
super(LogHelper.LogLevelEnum::valueOf, LogHelper.LogLevelEnum::values);
|
||||||
@@ -611,6 +1037,18 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum CallGraphSaveMode {
|
||||||
|
NONE,
|
||||||
|
DOT,
|
||||||
|
JSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CallGraphSaveModeConverter extends BaseEnumConverter<CallGraphSaveMode> {
|
||||||
|
public CallGraphSaveModeConverter() {
|
||||||
|
super(CallGraphSaveMode::valueOf, CallGraphSaveMode::values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract static class BaseEnumConverter<E extends Enum<E>> implements IStringConverter<E> {
|
public abstract static class BaseEnumConverter<E extends Enum<E>> implements IStringConverter<E> {
|
||||||
private final Function<String, E> parse;
|
private final Function<String, E> parse;
|
||||||
private final Supplier<E[]> values;
|
private final Supplier<E[]> values;
|
||||||
@@ -625,7 +1063,7 @@ public class JadxCLIArgs {
|
|||||||
try {
|
try {
|
||||||
return parse.apply(stringAsEnumName(value));
|
return parse.apply(stringAsEnumName(value));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException(
|
throw new JadxArgsValidateException(
|
||||||
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
|
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
import com.beust.jcommander.JCommander;
|
import com.beust.jcommander.JCommander;
|
||||||
|
|
||||||
import jadx.cli.commands.CommandPlugins;
|
import jadx.cli.commands.CommandPlugins;
|
||||||
import jadx.cli.commands.ICommand;
|
import jadx.cli.commands.ICommand;
|
||||||
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
|
|
||||||
public class JadxCLICommands {
|
public class JadxCLICommands {
|
||||||
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
|
private static final Map<String, ICommand> COMMANDS_MAP = new LinkedHashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
JadxCLICommands.register(new CommandPlugins());
|
JadxCLICommands.register(new CommandPlugins());
|
||||||
@@ -23,10 +24,11 @@ public class JadxCLICommands {
|
|||||||
COMMANDS_MAP.forEach(builder::addCommand);
|
COMMANDS_MAP.forEach(builder::addCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean process(JCommanderWrapper<?> jcw, JCommander jc, String parsedCommand) {
|
public static boolean process(JCommanderWrapper jcw, JCommander jc, String parsedCommand) {
|
||||||
ICommand command = COMMANDS_MAP.get(parsedCommand);
|
ICommand command = COMMANDS_MAP.get(parsedCommand);
|
||||||
if (command == null) {
|
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);
|
JCommander subCommander = jc.getCommands().get(parsedCommand);
|
||||||
command.process(jcw, subCommander);
|
command.process(jcw, subCommander);
|
||||||
|
|||||||
@@ -43,10 +43,9 @@ public class LogHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (args.quiet) {
|
if (args.quiet) {
|
||||||
return LogLevelEnum.QUIET;
|
args.logLevel = LogLevelEnum.QUIET;
|
||||||
}
|
} else if (args.verbose) {
|
||||||
if (args.verbose) {
|
args.logLevel = LogLevelEnum.DEBUG;
|
||||||
return LogLevelEnum.DEBUG;
|
|
||||||
}
|
}
|
||||||
return args.logLevel;
|
return args.logLevel;
|
||||||
}
|
}
|
||||||
@@ -56,20 +55,7 @@ public class LogHelper {
|
|||||||
applyLogLevel(logLevelValue);
|
applyLogLevel(logLevelValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setLogLevelsForLoadingStage() {
|
public static void applyLogLevels() {
|
||||||
if (logLevelValue == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (logLevelValue == LogLevelEnum.PROGRESS) {
|
|
||||||
// show load errors
|
|
||||||
LogHelper.applyLogLevel(LogLevelEnum.ERROR);
|
|
||||||
fixForShowProgress();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
applyLogLevel(logLevelValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setLogLevelsForDecompileStage() {
|
|
||||||
if (logLevelValue == null) {
|
if (logLevelValue == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,6 +72,9 @@ public class LogHelper {
|
|||||||
setLevelForClass(JadxCLI.class, Level.INFO);
|
setLevelForClass(JadxCLI.class, Level.INFO);
|
||||||
setLevelForClass(JadxDecompiler.class, Level.INFO);
|
setLevelForClass(JadxDecompiler.class, Level.INFO);
|
||||||
setLevelForClass(SingleClassMode.class, Level.INFO);
|
setLevelForClass(SingleClassMode.class, Level.INFO);
|
||||||
|
|
||||||
|
// show warnings and errors from input plugins
|
||||||
|
setLevelForPackage("jadx.plugins.input", Level.WARN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyLogLevel(@NotNull LogLevelEnum logLevel) {
|
private static void applyLogLevel(@NotNull LogLevelEnum logLevel) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import jadx.api.JadxDecompiler;
|
|||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.visitors.SaveCode;
|
import jadx.core.dex.visitors.SaveCode;
|
||||||
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -33,10 +34,10 @@ public class SingleClassMode {
|
|||||||
.findFirst().orElse(null);
|
.findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
if (clsForProcess == 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)) {
|
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()) {
|
if (clsForProcess.isInner()) {
|
||||||
clsForProcess = clsForProcess.getTopParentClass();
|
clsForProcess = clsForProcess.getTopParentClass();
|
||||||
@@ -52,7 +53,7 @@ public class SingleClassMode {
|
|||||||
if (size == 1) {
|
if (size == 1) {
|
||||||
clsForProcess = classes.get(0);
|
clsForProcess = classes.get(0);
|
||||||
} else {
|
} 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;
|
ICodeInfo codeInfo;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||||
import jadx.core.clsp.ClsSet;
|
import jadx.core.clsp.ClsSet;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
@@ -23,8 +24,9 @@ public class ConvertToClsSet {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
||||||
|
|
||||||
public static void usage() {
|
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: "
|
LOG.info("Arguments to update core.jcst: "
|
||||||
|
+ "<android API level (number)> "
|
||||||
+ "<jadx root>/jadx-core/src/main/resources/clst/core.jcst "
|
+ "<jadx root>/jadx-core/src/main/resources/clst/core.jcst "
|
||||||
+ "<sdk_root>/platforms/android-<api level>/android.jar"
|
+ "<sdk_root>/platforms/android-<api level>/android.jar"
|
||||||
+ "<sdk_root>/platforms/android-<api level>/optional/android.car.jar "
|
+ "<sdk_root>/platforms/android-<api level>/optional/android.car.jar "
|
||||||
@@ -32,11 +34,12 @@ public class ConvertToClsSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (args.length < 2) {
|
if (args.length != 5) {
|
||||||
usage();
|
usage();
|
||||||
System.exit(1);
|
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);
|
Path output = inputPaths.remove(0);
|
||||||
|
|
||||||
JadxArgs jadxArgs = new JadxArgs();
|
JadxArgs jadxArgs = new JadxArgs();
|
||||||
@@ -45,7 +48,7 @@ public class ConvertToClsSet {
|
|||||||
// disable not needed passes executed at prepare stage
|
// disable not needed passes executed at prepare stage
|
||||||
jadxArgs.setDeobfuscationOn(false);
|
jadxArgs.setDeobfuscationOn(false);
|
||||||
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||||
jadxArgs.setUseSourceNameAsClassAlias(false);
|
jadxArgs.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.NEVER);
|
||||||
jadxArgs.setMoveInnerClasses(false);
|
jadxArgs.setMoveInnerClasses(false);
|
||||||
jadxArgs.setInlineAnonymousClasses(false);
|
jadxArgs.setInlineAnonymousClasses(false);
|
||||||
jadxArgs.setInlineMethods(false);
|
jadxArgs.setInlineMethods(false);
|
||||||
@@ -57,6 +60,7 @@ public class ConvertToClsSet {
|
|||||||
decompiler.load();
|
decompiler.load();
|
||||||
RootNode root = decompiler.getRoot();
|
RootNode root = decompiler.getRoot();
|
||||||
ClsSet set = new ClsSet(root);
|
ClsSet set = new ClsSet(root);
|
||||||
|
set.setAndroidApiLevel(androidApiLevel);
|
||||||
set.loadFrom(root);
|
set.loadFrom(root);
|
||||||
set.save(output);
|
set.save(output);
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,61 @@
|
|||||||
package jadx.cli.commands;
|
package jadx.cli.commands;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.beust.jcommander.JCommander;
|
import com.beust.jcommander.JCommander;
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
import com.beust.jcommander.Parameters;
|
import com.beust.jcommander.Parameters;
|
||||||
|
|
||||||
|
import jadx.api.plugins.JadxPluginInfo;
|
||||||
import jadx.cli.JCommanderWrapper;
|
import jadx.cli.JCommanderWrapper;
|
||||||
|
import jadx.cli.LogHelper;
|
||||||
|
import jadx.core.utils.StringUtils;
|
||||||
import jadx.plugins.tools.JadxPluginsList;
|
import jadx.plugins.tools.JadxPluginsList;
|
||||||
import jadx.plugins.tools.JadxPluginsTools;
|
import jadx.plugins.tools.JadxPluginsTools;
|
||||||
|
import jadx.plugins.tools.data.JadxPluginListEntry;
|
||||||
import jadx.plugins.tools.data.JadxPluginMetadata;
|
import jadx.plugins.tools.data.JadxPluginMetadata;
|
||||||
import jadx.plugins.tools.data.JadxPluginUpdate;
|
import jadx.plugins.tools.data.JadxPluginUpdate;
|
||||||
|
|
||||||
@Parameters(commandDescription = "manage jadx plugins")
|
@Parameters(commandDescription = "manage jadx plugins")
|
||||||
public class CommandPlugins implements ICommand {
|
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;
|
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;
|
protected String installJar;
|
||||||
|
|
||||||
@Parameter(names = { "-l", "--list" }, description = "list installed plugins")
|
@Parameter(names = { "-l", "--list" }, description = "list installed plugins")
|
||||||
protected boolean list;
|
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;
|
protected boolean available;
|
||||||
|
|
||||||
@Parameter(names = { "-u", "--update" }, description = "update installed plugins")
|
@Parameter(names = { "-u", "--update" }, description = "update installed plugins")
|
||||||
protected boolean update;
|
protected boolean update;
|
||||||
|
|
||||||
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId")
|
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId", defaultValueDescription = "<pluginId>")
|
||||||
protected String uninstall;
|
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)
|
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||||
protected boolean printHelp = false;
|
protected boolean printHelp = false;
|
||||||
|
|
||||||
@@ -41,21 +64,33 @@ public class CommandPlugins implements ICommand {
|
|||||||
return "plugins";
|
return "plugins";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnnecessaryReturnStatement")
|
||||||
@Override
|
@Override
|
||||||
public void process(JCommanderWrapper<?> jcw, JCommander subCommander) {
|
public void process(JCommanderWrapper jcw, JCommander subCommander) {
|
||||||
if (printHelp) {
|
if (printHelp) {
|
||||||
jcw.printUsage(subCommander);
|
jcw.printUsage(subCommander);
|
||||||
return;
|
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) {
|
if (install != null) {
|
||||||
installPlugin(install);
|
installPlugin(install);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (installJar != null) {
|
if (installJar != null) {
|
||||||
installPlugin("file:" + installJar);
|
installPlugin("file:" + installJar);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (uninstall != null) {
|
if (uninstall != null) {
|
||||||
boolean uninstalled = JadxPluginsTools.getInstance().uninstall(uninstall);
|
boolean uninstalled = JadxPluginsTools.getInstance().uninstall(uninstall);
|
||||||
System.out.println(uninstalled ? "Uninstalled" : "Plugin not found");
|
System.out.println(uninstalled ? "Uninstalled" : "Plugin not found");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (update) {
|
if (update) {
|
||||||
List<JadxPluginUpdate> updates = JadxPluginsTools.getInstance().updateAll();
|
List<JadxPluginUpdate> updates = JadxPluginsTools.getInstance().updateAll();
|
||||||
@@ -67,28 +102,113 @@ public class CommandPlugins implements ICommand {
|
|||||||
System.out.println(" " + update.getPluginId() + ": " + update.getOldVersion() + " -> " + update.getNewVersion());
|
System.out.println(" " + update.getPluginId() + ": " + update.getOldVersion() + " -> " + update.getNewVersion());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (list) {
|
if (list) {
|
||||||
List<JadxPluginMetadata> installed = JadxPluginsTools.getInstance().getInstalled();
|
printPlugins(JadxPluginsTools.getInstance().getInstalled());
|
||||||
System.out.println("Installed plugins: " + installed.size());
|
return;
|
||||||
int i = 1;
|
}
|
||||||
for (JadxPluginMetadata plugin : installed) {
|
if (listAll) {
|
||||||
System.out.println(" " + (i++) + ") "
|
printAllPlugins();
|
||||||
+ plugin.getPluginId() + " (" + plugin.getVersion() + ") - "
|
return;
|
||||||
+ plugin.getName() + ": " + plugin.getDescription());
|
}
|
||||||
}
|
if (listVersions != null) {
|
||||||
|
printVersions(listVersions, 10);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (available) {
|
if (available) {
|
||||||
List<JadxPluginMetadata> availableList = JadxPluginsList.getInstance().get();
|
List<JadxPluginListEntry> availableList = JadxPluginsList.getInstance().get();
|
||||||
System.out.println("Available plugins: " + availableList.size());
|
System.out.println("Available plugins: " + availableList.size());
|
||||||
int i = 1;
|
for (JadxPluginListEntry plugin : availableList) {
|
||||||
for (JadxPluginMetadata plugin : availableList) {
|
System.out.println(" - " + plugin.getName() + ": " + plugin.getDescription()
|
||||||
System.out.println(" " + (i++) + ") "
|
|
||||||
+ plugin.getName() + ": " + plugin.getDescription()
|
|
||||||
+ " (" + plugin.getLocationId() + ")");
|
+ " (" + 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(formatDescription(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()
|
||||||
|
+ ": " + formatDescription(plugin.getDescription()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatDescription(String desc) {
|
||||||
|
if (desc.contains("\n")) {
|
||||||
|
// remove new lines
|
||||||
|
desc = desc.replaceAll("\\R+", " ");
|
||||||
|
}
|
||||||
|
int maxLen = 512;
|
||||||
|
if (desc.length() > maxLen) {
|
||||||
|
// truncate very long descriptions
|
||||||
|
desc = desc.substring(0, maxLen) + " ...";
|
||||||
|
}
|
||||||
|
return desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void installPlugin(String locationId) {
|
private void installPlugin(String locationId) {
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ import jadx.cli.JCommanderWrapper;
|
|||||||
public interface ICommand {
|
public interface ICommand {
|
||||||
String name();
|
String name();
|
||||||
|
|
||||||
void process(JCommanderWrapper<?> jcw, JCommander subCommander);
|
void process(JCommanderWrapper jcw, JCommander subCommander);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package jadx.cli.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for jadx config objects
|
||||||
|
*/
|
||||||
|
public interface IJadxConfig {
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package jadx.cli.config;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.ExclusionStrategy;
|
||||||
|
import com.google.gson.FieldAttributes;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
|
||||||
|
import jadx.commons.app.JadxCommonFiles;
|
||||||
|
import jadx.core.utils.GsonUtils;
|
||||||
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
public class JadxConfigAdapter<T extends IJadxConfig> {
|
||||||
|
private static final ExclusionStrategy GSON_EXCLUSION_STRATEGY = new ExclusionStrategy() {
|
||||||
|
@Override
|
||||||
|
public boolean shouldSkipField(FieldAttributes f) {
|
||||||
|
return f.getAnnotation(JadxConfigExclude.class) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldSkipClass(Class<?> clazz) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Class<T> configCls;
|
||||||
|
private final String defaultConfigFileName;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
private Path configPath;
|
||||||
|
|
||||||
|
public JadxConfigAdapter(Class<T> configCls, String defaultConfigName) {
|
||||||
|
this(configCls, defaultConfigName, gsonBuilder -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxConfigAdapter(Class<T> configCls, String defaultConfigName, Consumer<GsonBuilder> applyGsonOptions) {
|
||||||
|
this.configCls = configCls;
|
||||||
|
this.defaultConfigFileName = defaultConfigName + ".json";
|
||||||
|
GsonBuilder gsonBuilder = GsonUtils.defaultGsonBuilder();
|
||||||
|
gsonBuilder.setExclusionStrategies(GSON_EXCLUSION_STRATEGY);
|
||||||
|
applyGsonOptions.accept(gsonBuilder);
|
||||||
|
this.gson = gsonBuilder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useConfigRef(String configRef) {
|
||||||
|
this.configPath = resolveConfigRef(configRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getConfigPath() {
|
||||||
|
return configPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultConfigFileName() {
|
||||||
|
return defaultConfigFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable T load() {
|
||||||
|
if (!Files.isRegularFile(configPath)) {
|
||||||
|
// file not found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try (JsonReader reader = gson.newJsonReader(Files.newBufferedReader(configPath))) {
|
||||||
|
return gson.fromJson(reader, configCls);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to load config file: " + configPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(T configObject) {
|
||||||
|
try {
|
||||||
|
String jsonStr = gson.toJson(configObject, configCls);
|
||||||
|
// don't use stream writer here because serialization errors will corrupt config
|
||||||
|
Files.writeString(configPath, jsonStr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to save config file: " + configPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String objectToJsonString(T configObject) {
|
||||||
|
return gson.toJson(configObject, configCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T jsonStringToObject(String jsonStr) {
|
||||||
|
return gson.fromJson(jsonStr, configCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path resolveConfigRef(String configRef) {
|
||||||
|
if (configRef == null || configRef.isEmpty()) {
|
||||||
|
// use default config file
|
||||||
|
return JadxCommonFiles.getConfigDir().resolve(defaultConfigFileName);
|
||||||
|
}
|
||||||
|
if (configRef.contains("/") || configRef.contains("\\")) {
|
||||||
|
if (!configRef.toLowerCase().endsWith(".json")) {
|
||||||
|
throw new JadxArgsValidateException("Config file extension should be '.json'");
|
||||||
|
}
|
||||||
|
return Path.of(configRef);
|
||||||
|
}
|
||||||
|
// treat as a short name
|
||||||
|
return JadxCommonFiles.getConfigDir().resolve(configRef + ".json");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package jadx.cli.config;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public @interface JadxConfigExclude {
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package jadx.cli.tools;
|
package jadx.cli.tools;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -11,8 +10,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -20,7 +17,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.android.TextResMapFile;
|
import jadx.core.utils.android.TextResMapFile;
|
||||||
import jadx.core.xmlgen.ResTableParser;
|
import jadx.core.xmlgen.ResTableBinaryParser;
|
||||||
|
import jadx.zip.IZipEntry;
|
||||||
|
import jadx.zip.ZipContent;
|
||||||
|
import jadx.zip.ZipReader;
|
||||||
|
|
||||||
|
import static jadx.core.utils.files.FileUtils.expandDirs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for convert '.arsc' to simple text file with mapping id to resource name
|
* 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;
|
private static int rewritesCount;
|
||||||
|
|
||||||
public static void usage() {
|
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("");
|
||||||
LOG.info("Note: If res-map already exists - it will be merged and updated");
|
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());
|
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
|
||||||
Path resMapFile = inputPaths.remove(0);
|
Path resMapFile = inputPaths.remove(0);
|
||||||
|
List<Path> inputResFiles = filterAndSort(expandDirs(inputPaths));
|
||||||
Map<Integer, String> resMap;
|
Map<Integer, String> resMap;
|
||||||
if (Files.isReadable(resMapFile)) {
|
if (Files.isReadable(resMapFile)) {
|
||||||
resMap = TextResMapFile.read(resMapFile);
|
resMap = TextResMapFile.read(resMapFile);
|
||||||
@@ -51,25 +54,25 @@ public class ConvertArscFile {
|
|||||||
LOG.info("Input entries count: {}", resMap.size());
|
LOG.info("Input entries count: {}", resMap.size());
|
||||||
|
|
||||||
RootNode root = new RootNode(new JadxArgs()); // not really needed
|
RootNode root = new RootNode(new JadxArgs()); // not really needed
|
||||||
|
ZipReader zipReader = new ZipReader();
|
||||||
rewritesCount = 0;
|
rewritesCount = 0;
|
||||||
for (Path resFile : inputPaths) {
|
for (Path resFile : inputResFiles) {
|
||||||
LOG.info("Processing {}", resFile);
|
ResTableBinaryParser resTableParser = new ResTableBinaryParser(root, true);
|
||||||
ResTableParser resTableParser = new ResTableParser(root, true);
|
|
||||||
if (resFile.getFileName().toString().endsWith(".jar")) {
|
if (resFile.getFileName().toString().endsWith(".jar")) {
|
||||||
// Load resources.arsc from android.jar
|
// Load resources.arsc from android.jar
|
||||||
try (ZipFile zip = new ZipFile(resFile.toFile())) {
|
try (ZipContent zip = zipReader.open(resFile.toFile())) {
|
||||||
ZipEntry entry = zip.getEntry("resources.arsc");
|
IZipEntry entry = zip.searchEntry("resources.arsc");
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
LOG.error("Failed to load \"resources.arsc\" from {}", resFile);
|
LOG.error("Failed to load \"resources.arsc\" from {}", resFile);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try (InputStream inputStream = zip.getInputStream(entry)) {
|
try (InputStream inputStream = entry.getInputStream()) {
|
||||||
resTableParser.decode(inputStream);
|
resTableParser.decode(inputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Load resources.arsc from extracted file
|
// Load resources.arsc from extracted file
|
||||||
try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(resFile))) {
|
try (InputStream inputStream = Files.newInputStream(resFile)) {
|
||||||
resTableParser.decode(inputStream);
|
resTableParser.decode(inputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,6 +87,16 @@ public class ConvertArscFile {
|
|||||||
LOG.info("done");
|
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) {
|
private static void mergeResMaps(Map<Integer, String> mainResMap, Map<Integer, String> newResMap) {
|
||||||
for (Map.Entry<Integer, String> entry : newResMap.entrySet()) {
|
for (Map.Entry<Integer, String> entry : newResMap.entrySet()) {
|
||||||
Integer id = entry.getKey();
|
Integer id = entry.getKey();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<!-- jadx-gui -->
|
<!-- jadx-gui -->
|
||||||
<logger name="com.pinterest.ktlint" level="INFO"/>
|
<logger name="com.pinterest.ktlint" level="INFO"/>
|
||||||
|
<logger name="guru.nidi.graphviz" level="WARN"/>
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.plugins.loader.JadxBasePluginLoader;
|
||||||
|
import jadx.core.plugins.files.SingleDirFilesGetter;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
|
||||||
|
public class BaseCliIntegrationTest {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(BaseCliIntegrationTest.class);
|
||||||
|
|
||||||
|
static final PathMatcher LOG_ALL_FILES = path -> {
|
||||||
|
LOG.debug("File in result dir: {}", path);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path testDir;
|
||||||
|
|
||||||
|
Path outputDir;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
outputDir = testDir.resolve("output");
|
||||||
|
}
|
||||||
|
|
||||||
|
int execJadxCli(String sampleName, String... options) {
|
||||||
|
return execJadxCli(buildArgs(List.of(options), sampleName));
|
||||||
|
}
|
||||||
|
|
||||||
|
int execJadxCli(String[] args) {
|
||||||
|
return JadxCLI.execute(args, jadxArgs -> {
|
||||||
|
// don't use global config and plugins
|
||||||
|
jadxArgs.setFilesGetter(new SingleDirFilesGetter(testDir));
|
||||||
|
jadxArgs.setPluginLoader(new JadxBasePluginLoader());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] buildArgs(List<String> options, String... inputSamples) {
|
||||||
|
List<String> args = new ArrayList<>(options);
|
||||||
|
args.add("-v");
|
||||||
|
args.add("-d");
|
||||||
|
args.add(outputDir.toAbsolutePath().toString());
|
||||||
|
|
||||||
|
for (String inputSample : inputSamples) {
|
||||||
|
try {
|
||||||
|
URL resource = getClass().getClassLoader().getResource(inputSample);
|
||||||
|
assertThat(resource).isNotNull();
|
||||||
|
String sampleFile = resource.toURI().getRawPath();
|
||||||
|
args.add(sampleFile);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
fail("Failed to load sample: " + inputSample, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void decompile(String... inputSamples) throws IOException {
|
||||||
|
int result = execJadxCli(buildArgs(List.of(), inputSamples));
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
List<Path> resultJavaFiles = collectJavaFilesInDir(outputDir);
|
||||||
|
assertThat(resultJavaFiles).isNotEmpty();
|
||||||
|
|
||||||
|
// do not copy input files as resources
|
||||||
|
for (Path path : collectFilesInDir(outputDir, LOG_ALL_FILES)) {
|
||||||
|
for (String inputSample : inputSamples) {
|
||||||
|
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printFiles(List<Path> files) {
|
||||||
|
LOG.info("Output files (count: {}):", files.size());
|
||||||
|
for (Path file : files) {
|
||||||
|
LOG.info(" {}", file);
|
||||||
|
}
|
||||||
|
LOG.info("");
|
||||||
|
}
|
||||||
|
|
||||||
|
String pathToUniformString(Path path) {
|
||||||
|
return path.toString().replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
Path printFileContent(Path file) {
|
||||||
|
try {
|
||||||
|
String content = Files.readString(outputDir.resolve(file));
|
||||||
|
String spacer = Utils.strRepeat("=", 70);
|
||||||
|
LOG.info("File content: {}\n{}\n{}\n{}", file, spacer, content, spacer);
|
||||||
|
return file;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to load file: " + file, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
|
||||||
|
PathMatcher javaMatcher = dir.getFileSystem().getPathMatcher("glob:**.java");
|
||||||
|
return collectFilesInDir(dir, javaMatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Path> collectAllFilesInDir(Path dir) throws IOException {
|
||||||
|
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||||
|
List<Path> files = pathStream
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.map(dir::relativize)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
printFiles(files);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
|
||||||
|
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||||
|
List<Path> files = pathStream
|
||||||
|
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
|
||||||
|
.filter(matcher::matches)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
printFiles(files);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,12 @@ package jadx.cli;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.hamcrest.Matchers;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import static jadx.core.utils.Utils.newConstStringMap;
|
import static jadx.core.utils.Utils.newConstStringMap;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
|
|
||||||
public class JadxCLIArgsTest {
|
public class JadxCLIArgsTest {
|
||||||
|
|
||||||
@@ -18,38 +16,38 @@ public class JadxCLIArgsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvertedBooleanOption() {
|
public void testInvertedBooleanOption() {
|
||||||
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
|
assertThat(parse("--no-replace-consts").isReplaceConsts()).isFalse();
|
||||||
assertThat(parse("").isReplaceConsts(), is(true));
|
assertThat(parse("").isReplaceConsts()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEscapeUnicodeOption() {
|
public void testEscapeUnicodeOption() {
|
||||||
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
|
assertThat(parse("--escape-unicode").isEscapeUnicode()).isTrue();
|
||||||
assertThat(parse("").isEscapeUnicode(), is(false));
|
assertThat(parse("").isEscapeUnicode()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSrcOption() {
|
public void testSrcOption() {
|
||||||
assertThat(parse("--no-src").isSkipSources(), is(true));
|
assertThat(parse("--no-src").isSkipSources()).isTrue();
|
||||||
assertThat(parse("-s").isSkipSources(), is(true));
|
assertThat(parse("-s").isSkipSources()).isTrue();
|
||||||
assertThat(parse("").isSkipSources(), is(false));
|
assertThat(parse("").isSkipSources()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOptionsOverride() {
|
public void testOptionsOverride() {
|
||||||
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
|
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports()).isFalse();
|
||||||
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
|
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo()).isFalse();
|
||||||
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
|
assertThat(override(new JadxCLIArgs(), "").isUseImports()).isTrue();
|
||||||
|
|
||||||
JadxCLIArgs args = new JadxCLIArgs();
|
JadxCLIArgs args = new JadxCLIArgs();
|
||||||
args.useImports = false;
|
args.useImports = false;
|
||||||
assertThat(override(args, "--no-imports").isUseImports(), is(false));
|
assertThat(override(args, "--no-imports").isUseImports()).isFalse();
|
||||||
args.debugInfo = false;
|
args.debugInfo = false;
|
||||||
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
|
assertThat(override(args, "--no-debug-info").isDebugInfo()).isFalse();
|
||||||
|
|
||||||
args = new JadxCLIArgs();
|
args = new JadxCLIArgs();
|
||||||
args.useImports = false;
|
args.useImports = false;
|
||||||
assertThat(override(args, "").isUseImports(), is(false));
|
assertThat(override(args, "").isUseImports()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -83,7 +81,7 @@ public class JadxCLIArgsTest {
|
|||||||
JadxCLIArgs args = new JadxCLIArgs();
|
JadxCLIArgs args = new JadxCLIArgs();
|
||||||
args.pluginOptions = baseMap;
|
args.pluginOptions = baseMap;
|
||||||
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
|
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
|
||||||
assertThat(resultMap, Matchers.equalTo(expectedMap));
|
assertThat(resultMap).isEqualTo(expectedMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JadxCLIArgs parse(String... args) {
|
private JadxCLIArgs parse(String... args) {
|
||||||
@@ -91,15 +89,24 @@ public class JadxCLIArgsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
|
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
|
||||||
boolean res = jadxArgs.processArgs(args);
|
return check(jadxArgs, jadxArgs.processArgs(args));
|
||||||
assertThat(res, is(true));
|
|
||||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
|
||||||
return jadxArgs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
|
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
|
||||||
boolean res = jadxArgs.overrideProvided(args);
|
return check(jadxArgs, overrideProvided(jadxArgs, args));
|
||||||
assertThat(res, is(true));
|
}
|
||||||
|
|
||||||
|
private static boolean overrideProvided(JadxCLIArgs jadxArgs, String[] args) {
|
||||||
|
JCommanderWrapper jcw = new JCommanderWrapper(new JadxCLIArgs());
|
||||||
|
if (!jcw.parse(args)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
jcw.overrideProvided(jadxArgs);
|
||||||
|
return jadxArgs.process(jcw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JadxCLIArgs check(JadxCLIArgs jadxArgs, boolean res) {
|
||||||
|
assertThat(res).isTrue();
|
||||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||||
return jadxArgs;
|
return jadxArgs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import org.junit.jupiter.api.Test;
|
|||||||
|
|
||||||
import jadx.api.JadxArgs.RenameEnum;
|
import jadx.api.JadxArgs.RenameEnum;
|
||||||
import jadx.cli.JadxCLIArgs.RenameConverter;
|
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.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
public class RenameConverterTest {
|
public class RenameConverterTest {
|
||||||
|
|
||||||
@@ -24,25 +24,24 @@ public class RenameConverterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void all() {
|
public void all() {
|
||||||
Set<RenameEnum> set = converter.convert("all");
|
Set<RenameEnum> set = converter.convert("all");
|
||||||
assertEquals(3, set.size());
|
assertThat(set).hasSize(3);
|
||||||
assertTrue(set.contains(RenameEnum.CASE));
|
assertThat(set).contains(RenameEnum.CASE);
|
||||||
assertTrue(set.contains(RenameEnum.VALID));
|
assertThat(set).contains(RenameEnum.VALID);
|
||||||
assertTrue(set.contains(RenameEnum.PRINTABLE));
|
assertThat(set).contains(RenameEnum.PRINTABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void none() {
|
public void none() {
|
||||||
Set<RenameEnum> set = converter.convert("none");
|
Set<RenameEnum> set = converter.convert("none");
|
||||||
assertTrue(set.isEmpty());
|
assertThat(set).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void wrong() {
|
public void wrong() {
|
||||||
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
|
JadxArgsValidateException thrown = assertThrows(JadxArgsValidateException.class,
|
||||||
() -> converter.convert("wrong"),
|
() -> converter.convert("wrong"),
|
||||||
"Expected convert() to throw, but it didn't");
|
"Expected convert() to throw, but it didn't");
|
||||||
|
|
||||||
assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
|
assertThat(thrown.getMessage()).isEqualTo("'wrong' is unknown for parameter someParam, possible values are case, valid, printable");
|
||||||
thrown.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import org.assertj.core.api.Condition;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class TestExport extends BaseCliIntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicExport() throws Exception {
|
||||||
|
int result = execJadxCli("samples/small.apk");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.map(this::pathToUniformString)
|
||||||
|
.haveExactly(2, new Condition<>(f -> f.startsWith("sources/") && f.endsWith(".java"), "sources"))
|
||||||
|
.haveExactly(10, new Condition<>(f -> f.startsWith("resources/"), "resources"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("resources/AndroidManifest.xml"), "manifest"))
|
||||||
|
.hasSize(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradleExportApk() throws Exception {
|
||||||
|
int result = execJadxCli("samples/small.apk", "--export-gradle");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.describedAs("check output files")
|
||||||
|
.map(this::pathToUniformString)
|
||||||
|
.haveExactly(2, new Condition<>(f -> f.endsWith(".java"), "java classes"))
|
||||||
|
.haveExactly(0, new Condition<>(f -> f.endsWith("classes.dex"), "dex files"))
|
||||||
|
.hasSize(15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradleExportAAR() throws Exception {
|
||||||
|
int result = execJadxCli("samples/test-lib.aar", "--export-gradle");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.describedAs("check output files")
|
||||||
|
.map(this::printFileContent)
|
||||||
|
.map(this::pathToUniformString)
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.startsWith("lib/src/main/java/") && f.endsWith(".java"), "java"))
|
||||||
|
.haveExactly(0, new Condition<>(f -> f.endsWith(".jar"), "jar files"))
|
||||||
|
.hasSize(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradleExportSimpleJava() throws Exception {
|
||||||
|
int result = execJadxCli("samples/HelloWorld.class", "--export-gradle");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.describedAs("check output files")
|
||||||
|
.map(this::printFileContent)
|
||||||
|
.map(this::pathToUniformString)
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.endsWith(".java") && f.startsWith("app/src/main/java/"), "java"))
|
||||||
|
.haveExactly(0, new Condition<>(f -> f.endsWith(".class"), "class files"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("settings.gradle.kts"), "settings"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("app/build.gradle.kts"), "build"))
|
||||||
|
.hasSize(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradleExportInvalidType() throws Exception {
|
||||||
|
int result = execJadxCli("samples/HelloWorld.class", "--export-gradle-type", "android-app");
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
// expect output in 'android-app' template, but most fields will be set to UNKNOWN.
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.describedAs("check output files")
|
||||||
|
.map(this::printFileContent)
|
||||||
|
.map(this::pathToUniformString)
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.endsWith(".java") && f.startsWith("app/src/main/java/"), "java"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("settings.gradle"), "settings"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("build.gradle"), "build"))
|
||||||
|
.haveExactly(1, new Condition<>(f -> f.equals("app/build.gradle"), "app build"))
|
||||||
|
.hasSize(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,123 +1,84 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.PathMatcher;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.assertj.core.api.Condition;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.core.utils.files.FileUtils;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public class TestInput {
|
public class TestInput extends BaseCliIntegrationTest {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
|
|
||||||
|
@Test
|
||||||
|
public void testHelp() {
|
||||||
|
int result = execJadxCli(new String[] { "--help" });
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testApkInput() throws Exception {
|
||||||
|
int result = execJadxCli(buildArgs(List.of(), "samples/small.apk"));
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
assertThat(collectAllFilesInDir(outputDir))
|
||||||
|
.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("AndroidManifest.xml"), "manifest"))
|
||||||
|
.hasSize(12);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDexInput() throws Exception {
|
public void testDexInput() throws Exception {
|
||||||
decompile("dex", "samples/hello.dex");
|
decompile("samples/hello.dex");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSmaliInput() throws Exception {
|
public void testSmaliInput() throws Exception {
|
||||||
decompile("smali", "samples/HelloWorld.smali");
|
decompile("samples/HelloWorld.smali");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClassInput() throws Exception {
|
public void testClassInput() throws Exception {
|
||||||
decompile("class", "samples/HelloWorld.class");
|
decompile("samples/HelloWorld.class");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultipleInput() throws Exception {
|
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 = execJadxCli(buildArgs(List.of("-f"), "samples/hello.dex"));
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
List<Path> files = collectJavaFilesInDir(outputDir);
|
||||||
|
assertThat(files).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleMode() throws Exception {
|
||||||
|
int result = execJadxCli(buildArgs(List.of("--decompilation-mode", "simple"), "samples/hello.dex"));
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
List<Path> files = collectJavaFilesInDir(outputDir);
|
||||||
|
assertThat(files).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResourceOnly() throws Exception {
|
public void testResourceOnly() throws Exception {
|
||||||
decode("resourceOnly", "samples/resources-only.apk");
|
int result = execJadxCli(buildArgs(List.of(), "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]));
|
|
||||||
assertThat(result).isEqualTo(0);
|
assertThat(result).isEqualTo(0);
|
||||||
List<Path> files = Files.find(
|
List<Path> files = collectFilesInDir(outputDir,
|
||||||
tempDir,
|
path -> path.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"));
|
||||||
3,
|
assertThat(files).isNotEmpty();
|
||||||
(file, attr) -> file.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
assertThat(files.isEmpty()).isFalse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
@Test
|
||||||
List<String> args = new ArrayList<>();
|
public void testNoRenameForDefPkg() throws Exception {
|
||||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
int result = execJadxCli(buildArgs(List.of("--rename-flags", "none"), "samples/defpkg.smali"));
|
||||||
args.add("-v");
|
|
||||||
args.add("-d");
|
|
||||||
args.add(tempDir.toAbsolutePath().toString());
|
|
||||||
|
|
||||||
for (String inputSample : inputSamples) {
|
|
||||||
URL resource = getClass().getClassLoader().getResource(inputSample);
|
|
||||||
assertThat(resource).isNotNull();
|
|
||||||
String sampleFile = resource.toURI().getRawPath();
|
|
||||||
args.add(sampleFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
int result = JadxCLI.execute(args.toArray(new String[0]));
|
|
||||||
assertThat(result).isEqualTo(0);
|
assertThat(result).isEqualTo(0);
|
||||||
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
|
List<Path> files = collectJavaFilesInDir(outputDir);
|
||||||
assertThat(resultJavaFiles).isNotEmpty();
|
assertThat(files).hasSize(1);
|
||||||
|
|
||||||
// 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 static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
|
|
||||||
PathMatcher javaMatcher = dir.getFileSystem().getPathMatcher("glob:**.java");
|
|
||||||
return collectFilesInDir(dir, javaMatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
|
|
||||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
|
||||||
return pathStream
|
|
||||||
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
|
|
||||||
.filter(matcher::matches)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
public static void cleanup() {
|
|
||||||
FileUtils.clearTempRootDir();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package jadx.plugins.tools.utils;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static jadx.plugins.tools.utils.PluginUtils.extractVersion;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class PluginUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractVersion() {
|
||||||
|
assertThat(extractVersion("plugin-name-v1.2.3.jar")).isEqualTo("1.2.3");
|
||||||
|
assertThat(extractVersion("plugin-name-v1.2.jar")).isEqualTo("1.2");
|
||||||
|
assertThat(extractVersion("1.2.3.jar")).isEqualTo("1.2.3");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.class public LA;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,29 @@
|
|||||||
|
## jadx analysis
|
||||||
|
|
||||||
|
Various utilities for analyze and process code and related information.
|
||||||
|
|
||||||
|
|
||||||
|
### Call graph
|
||||||
|
|
||||||
|
Full app code usage/call graph.
|
||||||
|
Usage:
|
||||||
|
```java
|
||||||
|
JadxArgs args = new JadxArgs();
|
||||||
|
args.addInputFile(new File("input.apk"));
|
||||||
|
try (JadxDecompiler jadx = new JadxDecompiler(args)) {
|
||||||
|
jadx.load();
|
||||||
|
|
||||||
|
ICallGraph callGraph = JadxCallGraph.builder(jadx)
|
||||||
|
.includePackages("com.example") // filter nodes by package
|
||||||
|
.resolvedOnly(true) // add nodes only from app (exclude framework/lib calls)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (ICallGraphEdge edge : callGraph.edges()) {
|
||||||
|
if (edge.isResolved()) {
|
||||||
|
System.out.printf("Edge from '%s' to '%s'%n", edge.from(), edge.to());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callGraph.writeDot(Path.of("test.dot")); // export to '.dot'
|
||||||
|
callGraph.writeJson(Path.of("test.json")); // export to JSON
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
plugins {
|
||||||
|
id("jadx-library")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":jadx-core"))
|
||||||
|
|
||||||
|
implementation("com.google.code.gson:gson:2.14.0")
|
||||||
|
|
||||||
|
testRuntimeOnly(project(":jadx-plugins:jadx-dex-input"))
|
||||||
|
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package jadx.analysis.callgraph;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraph;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphEdge;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
|
||||||
|
class CallGraph implements ICallGraph {
|
||||||
|
|
||||||
|
private final JadxArgs args;
|
||||||
|
private final List<ICallGraphEdge> edges;
|
||||||
|
|
||||||
|
public CallGraph(JadxArgs args, List<ICallGraphEdge> edges) {
|
||||||
|
this.args = args;
|
||||||
|
this.edges = edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ICallGraphEdge> edges() {
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeDot(Path path) {
|
||||||
|
new CallGraphExportDot(args, this).writeTo(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeJson(Path path) {
|
||||||
|
new CallGraphExportJson(this).writeTo(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package jadx.analysis.callgraph;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AttrNode;
|
||||||
|
|
||||||
|
class CallGraphAttrNode extends AttrNode {
|
||||||
|
}
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
package jadx.analysis.callgraph;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraph;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphBuilder;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphEdge;
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
class CallGraphBuilder implements ICallGraphBuilder {
|
||||||
|
private final JadxDecompiler decompiler;
|
||||||
|
private boolean resolvedOnly = false;
|
||||||
|
private @Nullable String pkgFilter;
|
||||||
|
|
||||||
|
public CallGraphBuilder(JadxDecompiler decompiler) {
|
||||||
|
this.decompiler = decompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICallGraphBuilder resolvedOnly(boolean resolved) {
|
||||||
|
this.resolvedOnly = resolved;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICallGraphBuilder includePackages(String pkgFilter) {
|
||||||
|
this.pkgFilter = pkgFilter.endsWith(".") ? pkgFilter : pkgFilter + '.';
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICallGraph build() {
|
||||||
|
return new CallGraph(decompiler.getArgs(), collectEdges());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ICallGraphEdge> collectEdges() {
|
||||||
|
AtomicInteger nodeId = new AtomicInteger();
|
||||||
|
Map<MethodInfo, CallGraphNode> nodes = new HashMap<>();
|
||||||
|
List<ICallGraphEdge> edges = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ClassNode cls : decompiler.getRoot().getClasses(true)) {
|
||||||
|
if (ignorePkg(cls.getClassInfo())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
CallGraphNode thisNode = getCallGraphNode(mth, nodes, nodeId);
|
||||||
|
for (MethodNode use : mth.getUseIn()) {
|
||||||
|
if (ignorePkg(use.getDeclaringClass().getClassInfo())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CallGraphNode useInNode = getCallGraphNode(use, nodes, nodeId);
|
||||||
|
edges.add(new CallGraphEdge(useInNode, thisNode));
|
||||||
|
}
|
||||||
|
if (!resolvedOnly) {
|
||||||
|
for (MethodInfo used : mth.getUnresolvedUsed()) {
|
||||||
|
if (ignorePkg(used.getDeclClass())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CallGraphNode usedNode = getCallGraphNode(used, nodes, nodeId);
|
||||||
|
edges.add(new CallGraphEdge(thisNode, usedNode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean ignorePkg(ClassInfo cls) {
|
||||||
|
if (pkgFilter == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !cls.getFullName().startsWith(pkgFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CallGraphNode getCallGraphNode(MethodNode mth, Map<MethodInfo, CallGraphNode> nodes, AtomicInteger nodeId) {
|
||||||
|
return nodes.computeIfAbsent(mth.getMethodInfo(), i -> new CallGraphNode(nodeId.incrementAndGet(), mth));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CallGraphNode getCallGraphNode(MethodInfo mth, Map<MethodInfo, CallGraphNode> nodes, AtomicInteger nodeId) {
|
||||||
|
return nodes.computeIfAbsent(mth, i -> new CallGraphNode(nodeId.incrementAndGet(), mth));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package jadx.analysis.callgraph;
|
||||||
|
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphEdge;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphNode;
|
||||||
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
|
|
||||||
|
class CallGraphEdge implements ICallGraphEdge {
|
||||||
|
private final ICallGraphNode from;
|
||||||
|
private final ICallGraphNode to;
|
||||||
|
private final CallGraphAttrNode attrNode = new CallGraphAttrNode();
|
||||||
|
|
||||||
|
public CallGraphEdge(ICallGraphNode from, ICallGraphNode to) {
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICallGraphNode from() {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICallGraphNode to() {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isResolved() {
|
||||||
|
return to.isResolved();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAttributeNode attributes() {
|
||||||
|
return attrNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CallGraphEdge{from=" + from + ", to=" + to + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
package jadx.analysis.callgraph;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraph;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphEdge;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphNode;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
|
import jadx.core.utils.DotGraphUtils;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
import static java.nio.file.StandardOpenOption.CREATE;
|
||||||
|
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
|
||||||
|
import static java.nio.file.StandardOpenOption.WRITE;
|
||||||
|
|
||||||
|
public class CallGraphExportDot {
|
||||||
|
private final JadxArgs args;
|
||||||
|
private final ICallGraph callGraph;
|
||||||
|
|
||||||
|
public CallGraphExportDot(JadxArgs args, ICallGraph callGraph) {
|
||||||
|
this.args = args;
|
||||||
|
this.callGraph = callGraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeTo(Path path) {
|
||||||
|
try {
|
||||||
|
FileUtils.makeDirsForFile(path);
|
||||||
|
Files.writeString(path, writeToString(), StandardCharsets.UTF_8,
|
||||||
|
WRITE, TRUNCATE_EXISTING, CREATE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to save JSON file: " + path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String writeToString() {
|
||||||
|
// collect nodes
|
||||||
|
Map<Integer, Node> nodeMap = new HashMap<>();
|
||||||
|
for (ICallGraphEdge edge : callGraph.edges()) {
|
||||||
|
addNode(edge.from(), nodeMap);
|
||||||
|
addNode(edge.to(), nodeMap);
|
||||||
|
}
|
||||||
|
List<Node> nodes = new ArrayList<>(nodeMap.values());
|
||||||
|
nodes.sort(Comparator.comparingInt(o -> o.id));
|
||||||
|
|
||||||
|
SimpleCodeWriter cw = new SimpleCodeWriter(args);
|
||||||
|
cw.add("digraph CallGraph {");
|
||||||
|
for (Node node : nodes) {
|
||||||
|
cw.startLine();
|
||||||
|
addNodeName(cw, node.id);
|
||||||
|
cw.add("[shape=record,label=\"{");
|
||||||
|
cw.add(DotGraphUtils.escape(node.method));
|
||||||
|
cw.add("}\"];");
|
||||||
|
}
|
||||||
|
for (ICallGraphEdge edge : callGraph.edges()) {
|
||||||
|
cw.startLine();
|
||||||
|
addNodeName(cw, edge.from().getId());
|
||||||
|
cw.add(" -> ");
|
||||||
|
addNodeName(cw, edge.to().getId());
|
||||||
|
cw.add(';');
|
||||||
|
}
|
||||||
|
cw.startLine('}');
|
||||||
|
return cw.getCodeStr();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNodeName(ICodeWriter cw, int id) {
|
||||||
|
cw.add('N').add(Integer.toString(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNode(ICallGraphNode cgNode, Map<Integer, Node> nodeMap) {
|
||||||
|
nodeMap.computeIfAbsent(cgNode.getId(), id -> {
|
||||||
|
Node node = new Node();
|
||||||
|
node.id = id;
|
||||||
|
node.method = cgNode.getMethodInfo().getRawFullId();
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Node {
|
||||||
|
int id;
|
||||||
|
String method;
|
||||||
|
}
|
||||||
|
}
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
package jadx.analysis.callgraph;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.Strictness;
|
||||||
|
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraph;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphEdge;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphNode;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
import static java.nio.file.StandardOpenOption.CREATE;
|
||||||
|
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
|
||||||
|
import static java.nio.file.StandardOpenOption.WRITE;
|
||||||
|
|
||||||
|
public class CallGraphExportJson {
|
||||||
|
private final ICallGraph callGraph;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
public CallGraphExportJson(ICallGraph callGraph) {
|
||||||
|
this.callGraph = callGraph;
|
||||||
|
this.gson = new GsonBuilder()
|
||||||
|
.disableJdkUnsafe()
|
||||||
|
.disableInnerClassSerialization()
|
||||||
|
.setStrictness(Strictness.STRICT)
|
||||||
|
// .setPrettyPrinting() // TODO: add option for pretty print?
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeTo(Path path) {
|
||||||
|
try {
|
||||||
|
FileUtils.makeDirsForFile(path);
|
||||||
|
Files.writeString(path, writeToString(), StandardCharsets.UTF_8,
|
||||||
|
WRITE, TRUNCATE_EXISTING, CREATE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to save JSON file: " + path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String writeToString() {
|
||||||
|
List<Edge> edges = new ArrayList<>();
|
||||||
|
Map<Integer, Node> nodeMap = new HashMap<>();
|
||||||
|
for (ICallGraphEdge edge : callGraph.edges()) {
|
||||||
|
ICallGraphNode from = edge.from();
|
||||||
|
ICallGraphNode to = edge.to();
|
||||||
|
addNode(from, nodeMap);
|
||||||
|
addNode(to, nodeMap);
|
||||||
|
Edge jsonEdge = new Edge();
|
||||||
|
jsonEdge.from = from.getId();
|
||||||
|
jsonEdge.to = to.getId();
|
||||||
|
jsonEdge.resolved = edge.isResolved();
|
||||||
|
edges.add(jsonEdge);
|
||||||
|
}
|
||||||
|
List<Node> nodes = new ArrayList<>(nodeMap.values());
|
||||||
|
nodes.sort(Comparator.comparingInt(o -> o.id));
|
||||||
|
|
||||||
|
RootNode rootNode = new RootNode();
|
||||||
|
rootNode.nodes = nodes;
|
||||||
|
rootNode.edges = edges;
|
||||||
|
return gson.toJson(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNode(ICallGraphNode cgNode, Map<Integer, Node> nodeMap) {
|
||||||
|
nodeMap.computeIfAbsent(cgNode.getId(), id -> {
|
||||||
|
Node node = new Node();
|
||||||
|
node.id = id;
|
||||||
|
node.method = cgNode.getMethodInfo().getRawFullId();
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class RootNode {
|
||||||
|
List<Node> nodes;
|
||||||
|
List<Edge> edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Node {
|
||||||
|
int id;
|
||||||
|
String method;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Edge {
|
||||||
|
int from;
|
||||||
|
int to;
|
||||||
|
boolean resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package jadx.analysis.callgraph;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphNode;
|
||||||
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
class CallGraphNode implements ICallGraphNode {
|
||||||
|
private final int id;
|
||||||
|
private final MethodInfo mthInfo;
|
||||||
|
private final @Nullable MethodNode mthNode;
|
||||||
|
private final CallGraphAttrNode attrNode;
|
||||||
|
|
||||||
|
public CallGraphNode(int id, MethodInfo mthInfo) {
|
||||||
|
this(id, mthInfo, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallGraphNode(int id, MethodNode mthNode) {
|
||||||
|
this(id, mthNode.getMethodInfo(), mthNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallGraphNode(int id, MethodInfo mthInfo, @Nullable MethodNode mthNode) {
|
||||||
|
this.id = id;
|
||||||
|
this.mthInfo = mthInfo;
|
||||||
|
this.mthNode = mthNode;
|
||||||
|
this.attrNode = new CallGraphAttrNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodInfo getMethodInfo() {
|
||||||
|
return mthInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable MethodNode getMethodNode() {
|
||||||
|
return mthNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isResolved() {
|
||||||
|
return mthNode != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAttributeNode attributes() {
|
||||||
|
return attrNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return mthInfo.getFullId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package jadx.analysis.callgraph;
|
||||||
|
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphBuilder;
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
|
||||||
|
public class JadxCallGraph {
|
||||||
|
|
||||||
|
public static ICallGraphBuilder builder(JadxDecompiler decompiler) {
|
||||||
|
return new CallGraphBuilder(decompiler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package jadx.analysis.callgraph.api;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ICallGraph {
|
||||||
|
|
||||||
|
List<ICallGraphEdge> edges();
|
||||||
|
|
||||||
|
void writeDot(Path path);
|
||||||
|
|
||||||
|
void writeJson(Path path);
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package jadx.analysis.callgraph.api;
|
||||||
|
|
||||||
|
public interface ICallGraphBuilder {
|
||||||
|
|
||||||
|
ICallGraphBuilder includePackages(String pkgFilter);
|
||||||
|
|
||||||
|
ICallGraphBuilder resolvedOnly(boolean resolved);
|
||||||
|
|
||||||
|
ICallGraph build();
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package jadx.analysis.callgraph.api;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
|
|
||||||
|
public interface ICallGraphEdge {
|
||||||
|
|
||||||
|
ICallGraphNode from();
|
||||||
|
|
||||||
|
ICallGraphNode to();
|
||||||
|
|
||||||
|
boolean isResolved();
|
||||||
|
|
||||||
|
IAttributeNode attributes();
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package jadx.analysis.callgraph.api;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
public interface ICallGraphNode {
|
||||||
|
|
||||||
|
int getId();
|
||||||
|
|
||||||
|
MethodInfo getMethodInfo();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
MethodNode getMethodNode();
|
||||||
|
|
||||||
|
boolean isResolved();
|
||||||
|
|
||||||
|
IAttributeNode attributes();
|
||||||
|
}
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
package jadx.analysis.callgraph.test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import jadx.analysis.callgraph.CallGraphExportDot;
|
||||||
|
import jadx.analysis.callgraph.CallGraphExportJson;
|
||||||
|
import jadx.analysis.callgraph.JadxCallGraph;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraph;
|
||||||
|
import jadx.analysis.callgraph.api.ICallGraphEdge;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class JadxCallGraphTest {
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
void usageExample() {
|
||||||
|
JadxArgs args = new JadxArgs();
|
||||||
|
args.addInputFile(new File("input.apk"));
|
||||||
|
try (JadxDecompiler jadx = new JadxDecompiler(args)) {
|
||||||
|
jadx.load();
|
||||||
|
|
||||||
|
ICallGraph callGraph = JadxCallGraph.builder(jadx)
|
||||||
|
.includePackages("com.example")
|
||||||
|
.resolvedOnly(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (ICallGraphEdge edge : callGraph.edges()) {
|
||||||
|
if (edge.isResolved()) {
|
||||||
|
System.out.printf("Edge from '%s' to '%s'%n", edge.from(), edge.to());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callGraph.writeDot(Path.of("test.dot"));
|
||||||
|
callGraph.writeJson(Path.of("test.json"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void simpleTest() {
|
||||||
|
JadxArgs args = new JadxArgs();
|
||||||
|
args.addInputFile(getSampleFile("simple.smali"));
|
||||||
|
try (JadxDecompiler jadx = new JadxDecompiler(args)) {
|
||||||
|
jadx.load();
|
||||||
|
|
||||||
|
ICallGraph callGraph = JadxCallGraph.builder(jadx)
|
||||||
|
.includePackages("test.pkg")
|
||||||
|
.resolvedOnly(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(callGraph.edges()).hasSize(1);
|
||||||
|
|
||||||
|
for (ICallGraphEdge edge : callGraph.edges()) {
|
||||||
|
System.out.println("Edge from " + edge.from() + " to " + edge.to());
|
||||||
|
}
|
||||||
|
|
||||||
|
String dotStr = new CallGraphExportDot(jadx.getArgs(), callGraph).writeToString();
|
||||||
|
System.out.println("dot: " + dotStr);
|
||||||
|
|
||||||
|
String jsonStr = new CallGraphExportJson(callGraph).writeToString();
|
||||||
|
System.out.println("json: " + jsonStr);
|
||||||
|
|
||||||
|
callGraph.writeDot(tempDir.resolve("test.dot"));
|
||||||
|
callGraph.writeJson(tempDir.resolve("test.json"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getSampleFile(String sampleName) {
|
||||||
|
try {
|
||||||
|
URL resource = getClass().getResource("/samples/" + sampleName);
|
||||||
|
assertThat(resource).describedAs("Sample not found: %s", sampleName).isNotNull();
|
||||||
|
return new File(resource.toURI().toURL().getFile());
|
||||||
|
} catch (MalformedURLException | URISyntaxException e) {
|
||||||
|
throw new RuntimeException("Failed to load sample file: " + sampleName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.class Ltest/pkg/HelloWorld;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.source "HelloWorld.java"
|
||||||
|
|
||||||
|
.method public static main([Ljava/lang/String;)V
|
||||||
|
.registers 2
|
||||||
|
|
||||||
|
const-string v0, "Hello, World"
|
||||||
|
invoke-static {p0, v0}, Ltest/pkg/HelloWorld;->hello(Ljava/lang/String;)V
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static hello(Ljava/lang/String;)V
|
||||||
|
.registers 2
|
||||||
|
|
||||||
|
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||||
|
invoke-virtual {v0, p0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -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("io.get-coursier.util:directories-jni:0.1.4")
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package jadx.commons.app;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class JadxCommonEnv {
|
||||||
|
|
||||||
|
public static @Nullable String get(String varName, @Nullable 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(@Nullable String value) {
|
||||||
|
return value == null || value.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package jadx.commons.app;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import dev.dirs.ProjectDirectories;
|
||||||
|
import dev.dirs.impl.Windows;
|
||||||
|
import dev.dirs.impl.WindowsPowerShell;
|
||||||
|
import dev.dirs.jni.WindowsJni;
|
||||||
|
|
||||||
|
public class JadxCommonFiles {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JadxCommonFiles.class);
|
||||||
|
|
||||||
|
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();
|
||||||
|
CONFIG_DIR = loader.getConfigDir();
|
||||||
|
CACHE_DIR = loader.getCacheDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DirsLoader {
|
||||||
|
private final Path configDir;
|
||||||
|
private final Path cacheDir;
|
||||||
|
|
||||||
|
DirsLoader() {
|
||||||
|
try {
|
||||||
|
AtomicReference<@Nullable ProjectDirectories> pdRef = new AtomicReference<>();
|
||||||
|
configDir = loadEnvDir("JADX_CONFIG_DIR", () -> loadDirs(pdRef).configDir);
|
||||||
|
cacheDir = loadEnvDir("JADX_CACHE_DIR", () -> loadDirs(pdRef).cacheDir);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to init common directories", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path loadEnvDir(String envVar, Supplier<String> dirFunc) throws IOException {
|
||||||
|
String envDir = JadxCommonEnv.get(envVar, null);
|
||||||
|
String dirStr;
|
||||||
|
if (envDir != null) {
|
||||||
|
dirStr = envDir;
|
||||||
|
} else {
|
||||||
|
dirStr = dirFunc.get();
|
||||||
|
}
|
||||||
|
Path path = Path.of(dirStr).toAbsolutePath();
|
||||||
|
Files.createDirectories(path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProjectDirectories loadDirs(AtomicReference<@Nullable ProjectDirectories> pdRef) {
|
||||||
|
ProjectDirectories currentDirs = pdRef.get();
|
||||||
|
if (currentDirs != null) {
|
||||||
|
return currentDirs;
|
||||||
|
}
|
||||||
|
LOG.debug("Loading system dirs ...");
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
ProjectDirectories loadedDirs = ProjectDirectories.from("io.github", "skylot", "jadx", DirsLoader::getWinDirs);
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Loaded system dirs ({}ms): config: {}, cache: {}",
|
||||||
|
System.currentTimeMillis() - start, loadedDirs.configDir, loadedDirs.cacheDir);
|
||||||
|
}
|
||||||
|
pdRef.set(loadedDirs);
|
||||||
|
return loadedDirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return JNI, Foreign or PowerShell implementation
|
||||||
|
*/
|
||||||
|
private static Windows getWinDirs() {
|
||||||
|
Windows impl = Windows.getDefaultSupplier().get();
|
||||||
|
if (impl instanceof WindowsPowerShell) {
|
||||||
|
if (JadxSystemInfo.IS_AMD64) {
|
||||||
|
// JNI library compiled only for x86-64
|
||||||
|
impl = new WindowsJni();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("Using win dirs implementation: {}", impl.getClass().getSimpleName());
|
||||||
|
return impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path getCacheDir() {
|
||||||
|
return cacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path getConfigDir() {
|
||||||
|
return configDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package jadx.commons.app;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class JadxSystemInfo {
|
||||||
|
public static final String JAVA_VM = System.getProperty("java.vm.name", "?");
|
||||||
|
public static final String JAVA_VER = System.getProperty("java.version", "?");
|
||||||
|
|
||||||
|
public static final String OS_NAME = System.getProperty("os.name", "?");
|
||||||
|
public static final String OS_ARCH = System.getProperty("os.arch", "?");
|
||||||
|
public static final String OS_VERSION = System.getProperty("os.version", "?");
|
||||||
|
|
||||||
|
private static final String OS_NAME_LOWER = OS_NAME.toLowerCase(Locale.ENGLISH);
|
||||||
|
public static final boolean IS_WINDOWS = OS_NAME_LOWER.startsWith("windows");
|
||||||
|
public static final boolean IS_MAC = OS_NAME_LOWER.startsWith("mac");
|
||||||
|
public static final boolean IS_LINUX = !IS_WINDOWS && !IS_MAC;
|
||||||
|
public static final boolean IS_UNIX = !IS_WINDOWS;
|
||||||
|
|
||||||
|
private static final String OS_ARCH_LOWER = OS_ARCH.toLowerCase(Locale.ENGLISH);
|
||||||
|
public static final boolean IS_AMD64 = OS_ARCH_LOWER.equals("amd64");
|
||||||
|
public static final boolean IS_ARM64 = OS_ARCH_LOWER.equals("aarch64");
|
||||||
|
|
||||||
|
private JadxSystemInfo() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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) {
|
||||||
|
Path customTmpRootDir = Paths.get(jadxTmpDir);
|
||||||
|
Files.createDirectories(customTmpRootDir);
|
||||||
|
dir = Files.createTempDirectory(customTmpRootDir, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
## jadx zip
|
||||||
|
|
||||||
|
Custom zip reader implementation to fight tampering and provide additional security checks
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
plugins {
|
||||||
|
id("jadx-library")
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package jadx.zip;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public interface IZipEntry {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zip entry name
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncompressed bytes
|
||||||
|
*/
|
||||||
|
byte[] getBytes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream of uncompressed bytes.
|
||||||
|
*/
|
||||||
|
InputStream getInputStream();
|
||||||
|
|
||||||
|
long getCompressedSize();
|
||||||
|
|
||||||
|
long getUncompressedSize();
|
||||||
|
|
||||||
|
boolean isDirectory();
|
||||||
|
|
||||||
|
File getZipFile();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if {@link #getBytes()} method is more optimal to use other than
|
||||||
|
* {@link #getInputStream()}
|
||||||
|
*/
|
||||||
|
boolean preferBytes();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package jadx.zip;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface IZipParser extends Closeable {
|
||||||
|
|
||||||
|
ZipContent open() throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package jadx.zip;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ZipContent implements Closeable {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ZipContent.class);
|
||||||
|
|
||||||
|
private final IZipParser zipParser;
|
||||||
|
private final List<IZipEntry> entries;
|
||||||
|
private final Map<String, IZipEntry> entriesMap;
|
||||||
|
|
||||||
|
public ZipContent(IZipParser zipParser, List<IZipEntry> entries) {
|
||||||
|
this.zipParser = zipParser;
|
||||||
|
this.entries = entries;
|
||||||
|
this.entriesMap = buildNameMap(zipParser, entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, IZipEntry> buildNameMap(IZipParser zipParser, List<IZipEntry> entries) {
|
||||||
|
Map<String, IZipEntry> map = new HashMap<>(entries.size());
|
||||||
|
for (IZipEntry entry : entries) {
|
||||||
|
String name = entry.getName();
|
||||||
|
IZipEntry prevEntry = map.put(name, entry);
|
||||||
|
if (prevEntry != null) {
|
||||||
|
LOG.warn("Found duplicate entry: {} in {}", name, zipParser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IZipEntry> getEntries() {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable IZipEntry searchEntry(String fileName) {
|
||||||
|
return entriesMap.get(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
zipParser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package jadx.zip;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.zip.fallback.FallbackException;
|
||||||
|
import jadx.zip.fallback.FallbackZipParser;
|
||||||
|
import jadx.zip.parser.JadxZipParser;
|
||||||
|
import jadx.zip.security.IJadxZipSecurity;
|
||||||
|
import jadx.zip.security.JadxZipSecurity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jadx wrapper to provide custom zip parser ({@link JadxZipParser})
|
||||||
|
* with fallback to default Java implementation.
|
||||||
|
*/
|
||||||
|
public class ZipReader {
|
||||||
|
private final ZipReaderOptions options;
|
||||||
|
|
||||||
|
public ZipReader() {
|
||||||
|
this(ZipReaderOptions.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZipReader(Set<ZipReaderFlags> flags) {
|
||||||
|
this(new ZipReaderOptions(new JadxZipSecurity(), flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZipReader(IJadxZipSecurity security) {
|
||||||
|
this(new ZipReaderOptions(security, ZipReaderFlags.none()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZipReader(ZipReaderOptions options) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
public ZipContent open(File zipFile) throws IOException {
|
||||||
|
if (!zipFile.exists()) {
|
||||||
|
throw new FileNotFoundException(zipFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JadxZipParser jadxParser = new JadxZipParser(zipFile, options);
|
||||||
|
IZipParser detectedParser = detectParser(zipFile, jadxParser);
|
||||||
|
return detectedParser.open();
|
||||||
|
} catch (FallbackException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (options.getFlags().contains(ZipReaderFlags.DONT_USE_FALLBACK)) {
|
||||||
|
throw new IOException("Failed to open zip: " + zipFile, e);
|
||||||
|
}
|
||||||
|
// switch to fallback parser
|
||||||
|
return buildFallbackParser(zipFile).open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visit valid entries in a zip file.
|
||||||
|
* Return not null value from visitor to stop iteration.
|
||||||
|
*/
|
||||||
|
public <R> @Nullable R visitEntries(File file, Function<IZipEntry, R> visitor) {
|
||||||
|
try (ZipContent content = open(file)) {
|
||||||
|
for (IZipEntry entry : content.getEntries()) {
|
||||||
|
R result = visitor.apply(entry);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to process zip file: " + file.getAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readEntries(File file, BiConsumer<IZipEntry, InputStream> visitor) {
|
||||||
|
visitEntries(file, entry -> {
|
||||||
|
if (!entry.isDirectory()) {
|
||||||
|
try (InputStream in = entry.getInputStream()) {
|
||||||
|
visitor.accept(entry, in);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to process zip entry: " + entry, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZipReaderOptions getOptions() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IZipParser detectParser(File zipFile, JadxZipParser jadxParser) throws IOException {
|
||||||
|
if (zipFile.getName().endsWith(".apk")
|
||||||
|
|| options.getFlags().contains(ZipReaderFlags.DONT_USE_FALLBACK)) {
|
||||||
|
return jadxParser;
|
||||||
|
}
|
||||||
|
if (!jadxParser.canOpen()) {
|
||||||
|
return buildFallbackParser(zipFile);
|
||||||
|
}
|
||||||
|
// default
|
||||||
|
if (options.getFlags().contains(ZipReaderFlags.FALLBACK_AS_DEFAULT)) {
|
||||||
|
return buildFallbackParser(zipFile);
|
||||||
|
}
|
||||||
|
return jadxParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FallbackZipParser buildFallbackParser(File zipFile) throws IOException {
|
||||||
|
return new FallbackZipParser(zipFile, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package jadx.zip;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public enum ZipReaderFlags {
|
||||||
|
/**
|
||||||
|
* Search all local file headers by signature without reading
|
||||||
|
* 'central directory' and 'end of central directory' entries
|
||||||
|
*/
|
||||||
|
IGNORE_CENTRAL_DIR_ENTRIES,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable additional checks to verify zip data and report possible tampering
|
||||||
|
*/
|
||||||
|
REPORT_TAMPERING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use fallback (java built-in implementation) parser as default.
|
||||||
|
* Custom implementation will be used for '*.apk' files only.
|
||||||
|
*/
|
||||||
|
FALLBACK_AS_DEFAULT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use only jadx custom parser and do not switch to fallback on errors.
|
||||||
|
*/
|
||||||
|
DONT_USE_FALLBACK;
|
||||||
|
|
||||||
|
public static Set<ZipReaderFlags> none() {
|
||||||
|
return EnumSet.noneOf(ZipReaderFlags.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package jadx.zip;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import jadx.zip.security.IJadxZipSecurity;
|
||||||
|
import jadx.zip.security.JadxZipSecurity;
|
||||||
|
|
||||||
|
public class ZipReaderOptions {
|
||||||
|
|
||||||
|
public static ZipReaderOptions getDefault() {
|
||||||
|
return new ZipReaderOptions(new JadxZipSecurity(), ZipReaderFlags.none());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final IJadxZipSecurity zipSecurity;
|
||||||
|
private final Set<ZipReaderFlags> flags;
|
||||||
|
|
||||||
|
public ZipReaderOptions(IJadxZipSecurity zipSecurity, Set<ZipReaderFlags> flags) {
|
||||||
|
this.zipSecurity = zipSecurity;
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IJadxZipSecurity getZipSecurity() {
|
||||||
|
return zipSecurity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ZipReaderFlags> getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package jadx.zip.fallback;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class FallbackException extends IOException {
|
||||||
|
public FallbackException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package jadx.zip.fallback;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
|
||||||
|
import jadx.zip.IZipEntry;
|
||||||
|
|
||||||
|
public class FallbackZipEntry implements IZipEntry {
|
||||||
|
private final FallbackZipParser parser;
|
||||||
|
private final ZipEntry zipEntry;
|
||||||
|
|
||||||
|
public FallbackZipEntry(FallbackZipParser parser, ZipEntry zipEntry) {
|
||||||
|
this.parser = parser;
|
||||||
|
this.zipEntry = zipEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZipEntry getZipEntry() {
|
||||||
|
return zipEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return zipEntry.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preferBytes() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBytes() {
|
||||||
|
return parser.getBytes(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() {
|
||||||
|
return parser.getInputStream(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCompressedSize() {
|
||||||
|
return zipEntry.getCompressedSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getUncompressedSize() {
|
||||||
|
return zipEntry.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDirectory() {
|
||||||
|
return zipEntry.isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getZipFile() {
|
||||||
|
return parser.getZipFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package jadx.zip.fallback;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.zip.IZipEntry;
|
||||||
|
import jadx.zip.IZipParser;
|
||||||
|
import jadx.zip.ZipContent;
|
||||||
|
import jadx.zip.ZipReaderOptions;
|
||||||
|
import jadx.zip.io.LimitedInputStream;
|
||||||
|
import jadx.zip.security.IJadxZipSecurity;
|
||||||
|
|
||||||
|
public class FallbackZipParser implements IZipParser {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FallbackZipParser.class);
|
||||||
|
|
||||||
|
private final File file;
|
||||||
|
private final ZipFile zipFile;
|
||||||
|
private final IJadxZipSecurity zipSecurity;
|
||||||
|
private final boolean useLimitedDataStream;
|
||||||
|
|
||||||
|
public FallbackZipParser(File file, ZipReaderOptions options) throws FallbackException {
|
||||||
|
try {
|
||||||
|
this.file = file;
|
||||||
|
this.zipFile = new ZipFile(file);
|
||||||
|
this.zipSecurity = options.getZipSecurity();
|
||||||
|
this.useLimitedDataStream = zipSecurity.useLimitedDataStream();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FallbackException("Error opening zip file: " + file.getAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ZipContent open() throws IOException {
|
||||||
|
try {
|
||||||
|
int maxEntriesCount = zipSecurity.getMaxEntriesCount();
|
||||||
|
if (maxEntriesCount == -1) {
|
||||||
|
maxEntriesCount = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
List<IZipEntry> list = new ArrayList<>();
|
||||||
|
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
FallbackZipEntry zipEntry = new FallbackZipEntry(this, entries.nextElement());
|
||||||
|
if (isValidEntry(zipEntry)) {
|
||||||
|
list.add(zipEntry);
|
||||||
|
if (list.size() > maxEntriesCount) {
|
||||||
|
throw new IllegalStateException("Max entries count limit exceeded: " + list.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ZipContent(this, list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FallbackException("Error opening zip file: " + file.getAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidEntry(IZipEntry zipEntry) {
|
||||||
|
boolean validEntry = zipSecurity.isValidEntry(zipEntry);
|
||||||
|
if (!validEntry) {
|
||||||
|
LOG.warn("Zip entry '{}' is invalid and excluded from processing", zipEntry);
|
||||||
|
}
|
||||||
|
return validEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes(FallbackZipEntry entry) {
|
||||||
|
try (InputStream is = getEntryStream(entry)) {
|
||||||
|
return is.readAllBytes();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to read bytes for entry: " + entry.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getInputStream(FallbackZipEntry entry) {
|
||||||
|
try {
|
||||||
|
return getEntryStream(entry);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to open input stream for entry: " + entry.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getEntryStream(FallbackZipEntry entry) throws IOException {
|
||||||
|
InputStream entryStream = zipFile.getInputStream(entry.getZipEntry());
|
||||||
|
InputStream stream;
|
||||||
|
if (useLimitedDataStream) {
|
||||||
|
stream = new LimitedInputStream(entryStream, entry.getUncompressedSize());
|
||||||
|
} else {
|
||||||
|
stream = entryStream;
|
||||||
|
}
|
||||||
|
return new BufferedInputStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getZipFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (zipFile != null) {
|
||||||
|
zipFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package jadx.zip.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class ByteBufferBackedInputStream extends InputStream {
|
||||||
|
private final ByteBuffer buf;
|
||||||
|
private int markedPosition = 0;
|
||||||
|
|
||||||
|
public ByteBufferBackedInputStream(ByteBuffer buf) {
|
||||||
|
this.buf = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (!buf.hasRemaining()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return buf.get() & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
|
public int read(byte[] bytes, int off, int len) throws IOException {
|
||||||
|
if (!buf.hasRemaining()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int readLen = Math.min(len, buf.remaining());
|
||||||
|
buf.get(bytes, off, readLen);
|
||||||
|
return readLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean markSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void mark(int unused) {
|
||||||
|
markedPosition = buf.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() {
|
||||||
|
buf.position(markedPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-11
@@ -1,21 +1,22 @@
|
|||||||
package jadx.api.plugins.utils;
|
package jadx.zip.io;
|
||||||
|
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class LimitedInputStream extends FilterInputStream {
|
public class LimitedInputStream extends FilterInputStream {
|
||||||
|
|
||||||
private final long maxSize;
|
private final long maxSize;
|
||||||
|
|
||||||
private long currentPos;
|
private long currentPos;
|
||||||
|
private long markPos;
|
||||||
|
|
||||||
protected LimitedInputStream(InputStream in, long maxSize) {
|
public LimitedInputStream(InputStream in, long maxSize) {
|
||||||
super(in);
|
super(in);
|
||||||
this.maxSize = maxSize;
|
this.maxSize = maxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPos() {
|
private void addAndCheckPos(long count) {
|
||||||
|
currentPos += count;
|
||||||
if (currentPos > maxSize) {
|
if (currentPos > maxSize) {
|
||||||
throw new IllegalStateException("Read limit exceeded");
|
throw new IllegalStateException("Read limit exceeded");
|
||||||
}
|
}
|
||||||
@@ -25,18 +26,17 @@ public class LimitedInputStream extends FilterInputStream {
|
|||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
int data = super.read();
|
int data = super.read();
|
||||||
if (data != -1) {
|
if (data != -1) {
|
||||||
currentPos++;
|
addAndCheckPos(1);
|
||||||
checkPos();
|
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
int count = super.read(b, off, len);
|
int count = super.read(b, off, len);
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
currentPos += count;
|
addAndCheckPos(count);
|
||||||
checkPos();
|
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
@@ -44,10 +44,21 @@ public class LimitedInputStream extends FilterInputStream {
|
|||||||
@Override
|
@Override
|
||||||
public long skip(long n) throws IOException {
|
public long skip(long n) throws IOException {
|
||||||
long skipped = super.skip(n);
|
long skipped = super.skip(n);
|
||||||
if (skipped != 0) {
|
if (skipped > 0) {
|
||||||
currentPos += skipped;
|
addAndCheckPos(skipped);
|
||||||
checkPos();
|
|
||||||
}
|
}
|
||||||
return skipped;
|
return skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mark(int readLimit) {
|
||||||
|
super.mark(readLimit);
|
||||||
|
markPos = currentPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() throws IOException {
|
||||||
|
super.reset();
|
||||||
|
currentPos = markPos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package jadx.zip.parser;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import jadx.zip.IZipEntry;
|
||||||
|
|
||||||
|
public final class JadxZipEntry implements IZipEntry {
|
||||||
|
private final JadxZipParser parser;
|
||||||
|
private final String fileName;
|
||||||
|
private final int compressMethod;
|
||||||
|
private final int entryStart;
|
||||||
|
private final int dataStart;
|
||||||
|
private final long compressedSize;
|
||||||
|
private final long uncompressedSize;
|
||||||
|
|
||||||
|
JadxZipEntry(JadxZipParser parser, String fileName, int entryStart, int dataStart,
|
||||||
|
int compressMethod, long compressedSize, long uncompressedSize) {
|
||||||
|
this.parser = parser;
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.entryStart = entryStart;
|
||||||
|
this.dataStart = dataStart;
|
||||||
|
this.compressMethod = compressMethod;
|
||||||
|
this.compressedSize = compressedSize;
|
||||||
|
this.uncompressedSize = uncompressedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSizesValid() {
|
||||||
|
if (compressedSize <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (uncompressedSize <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return compressedSize <= uncompressedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCompressedSize() {
|
||||||
|
return compressedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getUncompressedSize() {
|
||||||
|
return uncompressedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDirectory() {
|
||||||
|
return fileName.endsWith("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preferBytes() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBytes() {
|
||||||
|
return parser.getBytes(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() {
|
||||||
|
return parser.getInputStream(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEntryStart() {
|
||||||
|
return entryStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDataStart() {
|
||||||
|
return dataStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCompressMethod() {
|
||||||
|
return compressMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getZipFile() {
|
||||||
|
return parser.getZipFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return parser.getZipFile().getName() + ':' + fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
package jadx.zip.parser;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.zip.IZipEntry;
|
||||||
|
import jadx.zip.IZipParser;
|
||||||
|
import jadx.zip.ZipContent;
|
||||||
|
import jadx.zip.ZipReaderFlags;
|
||||||
|
import jadx.zip.ZipReaderOptions;
|
||||||
|
import jadx.zip.fallback.FallbackZipParser;
|
||||||
|
import jadx.zip.io.ByteBufferBackedInputStream;
|
||||||
|
import jadx.zip.io.LimitedInputStream;
|
||||||
|
import jadx.zip.security.IJadxZipSecurity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom and simple zip parser to fight tampering.
|
||||||
|
* Many zip features aren't supported:
|
||||||
|
* - Compression methods other than STORE or DEFLATE
|
||||||
|
* - Zip64
|
||||||
|
* - Checksum verification
|
||||||
|
* - Multi file archives
|
||||||
|
*/
|
||||||
|
public final class JadxZipParser implements IZipParser {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JadxZipParser.class);
|
||||||
|
|
||||||
|
private static final byte LOCAL_FILE_HEADER_START = 0x50;
|
||||||
|
private static final int LOCAL_FILE_HEADER_SIGN = 0x04034b50;
|
||||||
|
private static final int CD_SIGN = 0x02014b50;
|
||||||
|
private static final int END_OF_CD_SIGN = 0x06054b50;
|
||||||
|
|
||||||
|
private final File zipFile;
|
||||||
|
private final ZipReaderOptions options;
|
||||||
|
private final IJadxZipSecurity zipSecurity;
|
||||||
|
private final Set<ZipReaderFlags> flags;
|
||||||
|
private final boolean verify;
|
||||||
|
private final boolean useLimitedDataStream;
|
||||||
|
|
||||||
|
private @Nullable RandomAccessFile file;
|
||||||
|
private @Nullable FileChannel fileChannel;
|
||||||
|
private @Nullable ByteBuffer byteBuffer;
|
||||||
|
|
||||||
|
private int endOfCDStart = -2;
|
||||||
|
|
||||||
|
private @Nullable ZipContent fallbackZipContent;
|
||||||
|
|
||||||
|
public JadxZipParser(File zipFile, ZipReaderOptions options) {
|
||||||
|
this.zipFile = zipFile;
|
||||||
|
this.options = options;
|
||||||
|
this.zipSecurity = options.getZipSecurity();
|
||||||
|
this.flags = options.getFlags();
|
||||||
|
this.verify = options.getFlags().contains(ZipReaderFlags.REPORT_TAMPERING);
|
||||||
|
this.useLimitedDataStream = zipSecurity.useLimitedDataStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ZipContent open() throws IOException {
|
||||||
|
load();
|
||||||
|
try {
|
||||||
|
int maxEntriesCount = zipSecurity.getMaxEntriesCount();
|
||||||
|
if (maxEntriesCount == -1) {
|
||||||
|
maxEntriesCount = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
List<IZipEntry> entries;
|
||||||
|
if (flags.contains(ZipReaderFlags.IGNORE_CENTRAL_DIR_ENTRIES)) {
|
||||||
|
entries = searchLocalFileHeaders(maxEntriesCount);
|
||||||
|
} else {
|
||||||
|
entries = loadFromCentralDirs(maxEntriesCount);
|
||||||
|
}
|
||||||
|
return new ZipContent(this, entries);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (flags.contains(ZipReaderFlags.DONT_USE_FALLBACK)) {
|
||||||
|
throw new IOException("Failed to open zip: " + zipFile + ", error: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
LOG.warn("Zip open failed, switching to fallback parser, zip: {}", zipFile, e);
|
||||||
|
return initFallbackParser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canOpen() {
|
||||||
|
try {
|
||||||
|
load();
|
||||||
|
int eocdStart = searchEndOfCDStart();
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
buf.position(eocdStart + 4);
|
||||||
|
int diskNum = readU2(buf);
|
||||||
|
if (diskNum != 0xFFFF) { // Zip64 not supported
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Jadx parser can't open zip file: {}", zipFile, e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Failed to close jadx parser, zip file: {}", zipFile, e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidEntry(JadxZipEntry zipEntry) {
|
||||||
|
boolean validEntry = zipSecurity.isValidEntry(zipEntry);
|
||||||
|
if (!validEntry) {
|
||||||
|
LOG.warn("Zip entry '{}' is invalid and excluded from processing", zipEntry);
|
||||||
|
}
|
||||||
|
return validEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ByteBuffer getBuffer() {
|
||||||
|
ByteBuffer buf = byteBuffer;
|
||||||
|
if (buf == null) {
|
||||||
|
throw new RuntimeException("File not opened: " + zipFile);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load() throws IOException {
|
||||||
|
if (byteBuffer != null) {
|
||||||
|
// already loaded
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RandomAccessFile raFile = new RandomAccessFile(zipFile, "r");
|
||||||
|
long size = raFile.length();
|
||||||
|
if (size >= Integer.MAX_VALUE) {
|
||||||
|
throw new IOException("Zip file is too big");
|
||||||
|
}
|
||||||
|
int fileLen = (int) size;
|
||||||
|
if (fileLen < 100 * 1024 * 1024) {
|
||||||
|
// load files smaller than 100MB directly into memory
|
||||||
|
byte[] bytes = new byte[fileLen];
|
||||||
|
raFile.readFully(bytes);
|
||||||
|
byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
raFile.close();
|
||||||
|
} else {
|
||||||
|
// for big files - use a memory mapped file
|
||||||
|
file = raFile;
|
||||||
|
fileChannel = raFile.getChannel();
|
||||||
|
byteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
|
||||||
|
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IZipEntry> searchLocalFileHeaders(int maxEntriesCount) {
|
||||||
|
List<IZipEntry> entries = new ArrayList<>();
|
||||||
|
while (true) {
|
||||||
|
int start = searchEntryStart();
|
||||||
|
if (start == -1) {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
JadxZipEntry zipEntry = loadFileEntry(start);
|
||||||
|
if (isValidEntry(zipEntry)) {
|
||||||
|
entries.add(zipEntry);
|
||||||
|
if (entries.size() > maxEntriesCount) {
|
||||||
|
throw new IllegalStateException("Max entries count limit exceeded: " + entries.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IZipEntry> loadFromCentralDirs(int maxEntriesCount) throws IOException {
|
||||||
|
int eocdStart = searchEndOfCDStart();
|
||||||
|
if (eocdStart < 0) {
|
||||||
|
throw new RuntimeException("End of central directory not found");
|
||||||
|
}
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
buf.position(eocdStart + 10);
|
||||||
|
int entriesCount = readU2(buf);
|
||||||
|
buf.position(eocdStart + 16);
|
||||||
|
int cdOffset = buf.getInt();
|
||||||
|
|
||||||
|
if (entriesCount > maxEntriesCount) {
|
||||||
|
throw new IllegalStateException("Max entries count limit exceeded: " + entriesCount);
|
||||||
|
}
|
||||||
|
List<IZipEntry> entries = new ArrayList<>(entriesCount);
|
||||||
|
buf.position(cdOffset);
|
||||||
|
for (int i = 0; i < entriesCount; i++) {
|
||||||
|
JadxZipEntry zipEntry = loadCDEntry();
|
||||||
|
if (isValidEntry(zipEntry)) {
|
||||||
|
entries.add(zipEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JadxZipEntry loadCDEntry() {
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
int start = buf.position();
|
||||||
|
buf.position(start + 28);
|
||||||
|
int fileNameLen = readU2(buf);
|
||||||
|
int extraFieldLen = readU2(buf);
|
||||||
|
int commentLen = readU2(buf);
|
||||||
|
buf.position(start + 42);
|
||||||
|
int fileEntryStart = buf.getInt();
|
||||||
|
int entryEnd = start + 46 + fileNameLen + extraFieldLen + commentLen;
|
||||||
|
JadxZipEntry entry = loadFileEntry(fileEntryStart);
|
||||||
|
if (verify) {
|
||||||
|
compareCDAndLFH(buf, start, entry);
|
||||||
|
}
|
||||||
|
if (!entry.isSizesValid()) {
|
||||||
|
entry = fixEntryFromCD(entry, start);
|
||||||
|
}
|
||||||
|
buf.position(entryEnd);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JadxZipEntry fixEntryFromCD(JadxZipEntry entry, int start) {
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
buf.position(start + 10);
|
||||||
|
int comprMethod = readU2(buf);
|
||||||
|
buf.position(start + 20);
|
||||||
|
int comprSize = buf.getInt();
|
||||||
|
int unComprSize = buf.getInt();
|
||||||
|
return new JadxZipEntry(this, entry.getName(), start, entry.getDataStart(), comprMethod, comprSize, unComprSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void compareCDAndLFH(ByteBuffer buf, int start, JadxZipEntry entry) {
|
||||||
|
buf.position(start + 10);
|
||||||
|
int comprMethod = readU2(buf);
|
||||||
|
if (comprMethod != entry.getCompressMethod()) {
|
||||||
|
LOG.warn("Compression method differ in CD {} and LFH {} for {}",
|
||||||
|
comprMethod, entry.getCompressMethod(), entry);
|
||||||
|
}
|
||||||
|
buf.position(start + 20);
|
||||||
|
int comprSize = buf.getInt();
|
||||||
|
int unComprSize = buf.getInt();
|
||||||
|
if (comprSize != entry.getCompressedSize()) {
|
||||||
|
LOG.warn("Compressed size differ in CD {} and LFH {} for {}",
|
||||||
|
comprSize, entry.getCompressedSize(), entry);
|
||||||
|
}
|
||||||
|
if (unComprSize != entry.getUncompressedSize()) {
|
||||||
|
LOG.warn("Uncompressed size differ in CD {} and LFH {} for {}",
|
||||||
|
unComprSize, entry.getUncompressedSize(), entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JadxZipEntry loadFileEntry(int start) {
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
buf.position(start + 8);
|
||||||
|
int comprMethod = readU2(buf);
|
||||||
|
buf.position(start + 18);
|
||||||
|
int comprSize = buf.getInt();
|
||||||
|
int unComprSize = buf.getInt();
|
||||||
|
int fileNameLen = readU2(buf);
|
||||||
|
int extraFieldLen = readU2(buf);
|
||||||
|
String fileName = readString(buf, fileNameLen);
|
||||||
|
int dataStart = start + 30 + fileNameLen + extraFieldLen;
|
||||||
|
buf.position(dataStart + comprSize);
|
||||||
|
return new JadxZipEntry(this, fileName, start, dataStart, comprMethod, comprSize, unComprSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int searchEndOfCDStart() throws IOException {
|
||||||
|
if (endOfCDStart != -2) {
|
||||||
|
return endOfCDStart;
|
||||||
|
}
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
int pos = buf.limit() - 22;
|
||||||
|
int minPos = Math.max(0, pos - 0xffff);
|
||||||
|
while (true) {
|
||||||
|
buf.position(pos);
|
||||||
|
int sign = buf.getInt();
|
||||||
|
if (sign == END_OF_CD_SIGN) {
|
||||||
|
endOfCDStart = pos;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
pos--;
|
||||||
|
if (pos < minPos) {
|
||||||
|
throw new IOException("End of central directory record not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int searchEntryStart() {
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
while (true) {
|
||||||
|
int start = buf.position();
|
||||||
|
if (start + 4 > buf.limit()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
byte b = buf.get();
|
||||||
|
if (b == LOCAL_FILE_HEADER_START) {
|
||||||
|
buf.position(start);
|
||||||
|
int sign = buf.getInt();
|
||||||
|
if (sign == LOCAL_FILE_HEADER_SIGN) {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized InputStream getInputStream(JadxZipEntry entry) {
|
||||||
|
if (verify) {
|
||||||
|
verifyEntry(entry);
|
||||||
|
}
|
||||||
|
InputStream stream;
|
||||||
|
if (entry.getCompressMethod() == 8) {
|
||||||
|
try {
|
||||||
|
stream = ZipDeflate.decompressEntryToStream(getBuffer(), entry);
|
||||||
|
} catch (Exception e) {
|
||||||
|
entryParseFailed(entry, e);
|
||||||
|
return useFallbackParser(entry).getInputStream();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// treat any other compression methods values as UNCOMPRESSED
|
||||||
|
stream = bufferToStream(getBuffer(), entry.getDataStart(), (int) entry.getUncompressedSize());
|
||||||
|
}
|
||||||
|
if (useLimitedDataStream) {
|
||||||
|
return new LimitedInputStream(stream, entry.getUncompressedSize());
|
||||||
|
}
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized byte[] getBytes(JadxZipEntry entry) {
|
||||||
|
if (verify) {
|
||||||
|
verifyEntry(entry);
|
||||||
|
}
|
||||||
|
if (entry.getCompressMethod() == 8) {
|
||||||
|
try {
|
||||||
|
return ZipDeflate.decompressEntryToBytes(getBuffer(), entry);
|
||||||
|
} catch (Exception e) {
|
||||||
|
entryParseFailed(entry, e);
|
||||||
|
return useFallbackParser(entry).getBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// treat any other compression methods values as UNCOMPRESSED
|
||||||
|
return bufferToBytes(getBuffer(), entry.getDataStart(), (int) entry.getUncompressedSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyEntry(JadxZipEntry entry) {
|
||||||
|
int compressMethod = entry.getCompressMethod();
|
||||||
|
if (compressMethod == 0) {
|
||||||
|
if (entry.getCompressedSize() != entry.getUncompressedSize()) {
|
||||||
|
LOG.warn("Not equal sizes for STORE method: compressed: {}, uncompressed: {}, entry: {}",
|
||||||
|
entry.getCompressedSize(), entry.getUncompressedSize(), entry);
|
||||||
|
}
|
||||||
|
} else if (compressMethod != 8) {
|
||||||
|
LOG.warn("Unknown compress method: {} in entry: {}", compressMethod, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void entryParseFailed(JadxZipEntry entry, Exception e) {
|
||||||
|
if (isEncrypted(entry)) {
|
||||||
|
throw new RuntimeException("Entry is encrypted, failed to decompress: " + entry, e);
|
||||||
|
}
|
||||||
|
if (flags.contains(ZipReaderFlags.DONT_USE_FALLBACK)) {
|
||||||
|
throw new RuntimeException("Failed to decompress zip entry: " + entry + ", error: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
LOG.warn("Entry '{}' parse failed, switching to fallback parser", entry, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
private IZipEntry useFallbackParser(JadxZipEntry entry) {
|
||||||
|
LOG.debug("useFallbackParser used for {}", entry);
|
||||||
|
IZipEntry zipEntry = initFallbackParser().searchEntry(entry.getName());
|
||||||
|
if (zipEntry == null) {
|
||||||
|
throw new RuntimeException("Fallback parser can't find entry: " + entry);
|
||||||
|
}
|
||||||
|
return zipEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
private synchronized ZipContent initFallbackParser() {
|
||||||
|
if (fallbackZipContent == null) {
|
||||||
|
try {
|
||||||
|
fallbackZipContent = new FallbackZipParser(zipFile, options).open();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Fallback parser failed to open file: " + zipFile, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallbackZipContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEncrypted(JadxZipEntry entry) {
|
||||||
|
int flags = readFlags(entry);
|
||||||
|
return (flags & 1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readFlags(JadxZipEntry entry) {
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
buf.position(entry.getEntryStart() + 6);
|
||||||
|
return readU2(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] bufferToBytes(ByteBuffer buf, int start, int size) {
|
||||||
|
byte[] data = new byte[size];
|
||||||
|
buf.position(start);
|
||||||
|
buf.get(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static InputStream bufferToStream(ByteBuffer buf, int start, int size) {
|
||||||
|
buf.position(start);
|
||||||
|
ByteBuffer streamBuf = buf.slice();
|
||||||
|
streamBuf.limit(size);
|
||||||
|
return new ByteBufferBackedInputStream(streamBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readU2(ByteBuffer buf) {
|
||||||
|
return buf.getShort() & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readString(ByteBuffer buf, int fileNameLen) {
|
||||||
|
byte[] bytes = new byte[fileNameLen];
|
||||||
|
buf.get(bytes);
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("DataFlowIssue")
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
try {
|
||||||
|
if (fileChannel != null) {
|
||||||
|
fileChannel.close();
|
||||||
|
}
|
||||||
|
if (file != null) {
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
if (fallbackZipContent != null) {
|
||||||
|
fallbackZipContent.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fileChannel = null;
|
||||||
|
file = null;
|
||||||
|
byteBuffer = null;
|
||||||
|
endOfCDStart = -2;
|
||||||
|
fallbackZipContent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getZipFile() {
|
||||||
|
return zipFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JadxZipParser{" + zipFile + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package jadx.zip.parser;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.zip.DataFormatException;
|
||||||
|
import java.util.zip.Inflater;
|
||||||
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
|
import static jadx.zip.parser.JadxZipParser.bufferToStream;
|
||||||
|
|
||||||
|
final class ZipDeflate {
|
||||||
|
private static final int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
|
static byte[] decompressEntryToBytes(ByteBuffer buf, JadxZipEntry entry) throws DataFormatException {
|
||||||
|
buf.position(entry.getDataStart());
|
||||||
|
ByteBuffer entryBuf = buf.slice();
|
||||||
|
entryBuf.limit((int) entry.getCompressedSize());
|
||||||
|
if (entry.getUncompressedSize() > Integer.MAX_VALUE) {
|
||||||
|
throw new DataFormatException("Entry too large: " + entry.getUncompressedSize());
|
||||||
|
}
|
||||||
|
byte[] out = new byte[(int) entry.getUncompressedSize()];
|
||||||
|
Inflater inflater = new Inflater(true);
|
||||||
|
inflater.setInput(entryBuf);
|
||||||
|
int written = inflater.inflate(out);
|
||||||
|
inflater.end();
|
||||||
|
if (written != out.length) {
|
||||||
|
throw new DataFormatException("Unexpected size of decompressed entry: " + entry
|
||||||
|
+ ", got: " + written + ", expected: " + out.length);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static InputStream decompressEntryToStream(ByteBuffer buf, JadxZipEntry entry) {
|
||||||
|
InputStream stream = bufferToStream(buf, entry.getDataStart(), (int) entry.getCompressedSize());
|
||||||
|
Inflater inflater = new Inflater(true);
|
||||||
|
return new InflaterInputStream(stream, inflater, BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package jadx.zip.security;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import jadx.zip.IZipEntry;
|
||||||
|
|
||||||
|
public class DisabledZipSecurity implements IJadxZipSecurity {
|
||||||
|
|
||||||
|
public static final DisabledZipSecurity INSTANCE = new DisabledZipSecurity();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidEntry(IZipEntry entry) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidEntryName(String entryName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInSubDirectory(File baseDir, File file) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useLimitedDataStream() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxEntriesCount() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package jadx.zip.security;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import jadx.zip.IZipEntry;
|
||||||
|
|
||||||
|
public interface IJadxZipSecurity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if zip entry is valid and safe to process
|
||||||
|
*/
|
||||||
|
boolean isValidEntry(IZipEntry entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the zip entry name is valid.
|
||||||
|
* This check should be part of {@link #isValidEntry(IZipEntry)} method.
|
||||||
|
*/
|
||||||
|
boolean isValidEntryName(String entryName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use limited InputStream for entry uncompressed data
|
||||||
|
*/
|
||||||
|
boolean useLimitedDataStream();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max entries count expected in a zip file, fail zip open if the limit exceeds.
|
||||||
|
* Return -1 to disable entries count check.
|
||||||
|
*/
|
||||||
|
int getMaxEntriesCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file will be inside baseDir after a system resolves its path
|
||||||
|
*/
|
||||||
|
boolean isInSubDirectory(File baseDir, File file);
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package jadx.zip.security;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.zip.IZipEntry;
|
||||||
|
|
||||||
|
public class JadxZipSecurity implements IJadxZipSecurity {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JadxZipSecurity.class);
|
||||||
|
|
||||||
|
private static final Path CWD = Paths.get(".").toAbsolutePath().normalize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of uncompressed zip entry shouldn't be bigger of compressed in zipBombDetectionFactor
|
||||||
|
* times
|
||||||
|
*/
|
||||||
|
private int zipBombDetectionFactor = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zip entries that have an uncompressed size of less than zipBombMinUncompressedSize are considered
|
||||||
|
* safe
|
||||||
|
*/
|
||||||
|
private int zipBombMinUncompressedSize = 25 * 1024 * 1024;
|
||||||
|
|
||||||
|
private int maxEntriesCount = 100_000;
|
||||||
|
|
||||||
|
private boolean useLimitedDataStream = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidEntry(IZipEntry entry) {
|
||||||
|
return isValidEntryName(entry.getName()) && !isZipBomb(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useLimitedDataStream() {
|
||||||
|
return useLimitedDataStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxEntriesCount() {
|
||||||
|
return maxEntriesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that entry name contains no any traversals and prevents cases like "../classes.dex",
|
||||||
|
* to limit output only to the specified directory
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isValidEntryName(String entryName) {
|
||||||
|
if (entryName.contains("..")) { // quick pre-check
|
||||||
|
if (entryName.contains("../") || entryName.contains("..\\")) {
|
||||||
|
LOG.error("Path traversal attack detected in entry: '{}'", entryName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Path traversal check as presented on
|
||||||
|
// https://www.heise.de/en/background/Secure-Coding-Best-practices-for-using-Java-NIO-against-path-traversal-9996787.html
|
||||||
|
try {
|
||||||
|
Path entryPath = CWD.resolve(entryName).normalize();
|
||||||
|
if (entryPath.startsWith(CWD)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// check failed
|
||||||
|
LOG.error("Invalid file name or path traversal attack detected: {} - error: {}", entryName, e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG.error("Invalid file name or path traversal attack detected: {}", entryName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInSubDirectory(File baseDir, File file) {
|
||||||
|
try {
|
||||||
|
return isInSubDirectoryInternal(baseDir.getCanonicalFile(), file.getCanonicalFile());
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isZipBomb(IZipEntry entry) {
|
||||||
|
long compressedSize = entry.getCompressedSize();
|
||||||
|
long uncompressedSize = entry.getUncompressedSize();
|
||||||
|
boolean invalidSize = compressedSize < 0 || uncompressedSize < 0;
|
||||||
|
boolean possibleZipBomb = uncompressedSize >= zipBombMinUncompressedSize
|
||||||
|
&& compressedSize * zipBombDetectionFactor < uncompressedSize;
|
||||||
|
if (invalidSize || possibleZipBomb) {
|
||||||
|
LOG.error("Potential zip bomb attack detected, invalid sizes: compressed {}, uncompressed {}, name {}",
|
||||||
|
compressedSize, uncompressedSize, entry.getName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isInSubDirectoryInternal(File baseDir, File file) {
|
||||||
|
File current = file;
|
||||||
|
while (true) {
|
||||||
|
if (current == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (current.equals(baseDir)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
current = current.getParentFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxEntriesCount(int maxEntriesCount) {
|
||||||
|
this.maxEntriesCount = maxEntriesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZipBombDetectionFactor(int zipBombDetectionFactor) {
|
||||||
|
this.zipBombDetectionFactor = zipBombDetectionFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZipBombMinUncompressedSize(int zipBombMinUncompressedSize) {
|
||||||
|
this.zipBombMinUncompressedSize = zipBombMinUncompressedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseLimitedDataStream(boolean useLimitedDataStream) {
|
||||||
|
this.useLimitedDataStream = useLimitedDataStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+42
-13
@@ -4,20 +4,19 @@ plugins {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":jadx-plugins:jadx-input-api"))
|
api(project(":jadx-plugins:jadx-input-api"))
|
||||||
|
api(project(":jadx-commons:jadx-zip"))
|
||||||
|
|
||||||
implementation("com.google.code.gson:gson:2.10.1")
|
implementation("com.google.code.gson:gson:2.14.0")
|
||||||
|
|
||||||
// TODO: move resources decoding to separate plugin module
|
testImplementation("org.apache.commons:commons-lang3:3.20.0")
|
||||||
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(project(":jadx-plugins:jadx-dex-input"))
|
testImplementation(project(":jadx-plugins:jadx-dex-input"))
|
||||||
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
// 'ClassNotFound' error is raised if set as 'testRuntime'
|
||||||
testRuntimeOnly(project(":jadx-plugins:jadx-java-convert"))
|
// for the plugins below when running the tests from vscode.
|
||||||
testRuntimeOnly(project(":jadx-plugins:jadx-java-input"))
|
testImplementation(project(":jadx-plugins:jadx-smali-input"))
|
||||||
testRuntimeOnly(project(":jadx-plugins:jadx-raung-input"))
|
testImplementation(project(":jadx-plugins:jadx-java-convert"))
|
||||||
|
testImplementation(project(":jadx-plugins:jadx-java-input"))
|
||||||
|
testImplementation(project(":jadx-plugins:jadx-raung-input"))
|
||||||
|
|
||||||
testImplementation("org.eclipse.jdt:ecj") {
|
testImplementation("org.eclipse.jdt:ecj") {
|
||||||
version {
|
version {
|
||||||
@@ -25,9 +24,39 @@ dependencies {
|
|||||||
strictly("[3.33, 3.34[") // from 3.34 compiled with Java 17
|
strictly("[3.33, 3.34[") // from 3.34 compiled with Java 17
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
testImplementation("tools.profiler:async-profiler:3.0")
|
testImplementation("tools.profiler:async-profiler:4.4")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.test {
|
val jadxTestJavaVersion = getTestJavaVersion()
|
||||||
exclude("**/tmp/*")
|
|
||||||
|
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/*")
|
||||||
|
|
||||||
|
// maxHeapSize = "4g"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,18 @@ public enum DecompilationMode {
|
|||||||
/**
|
/**
|
||||||
* Raw instructions without modifications
|
* Raw instructions without modifications
|
||||||
*/
|
*/
|
||||||
FALLBACK
|
FALLBACK;
|
||||||
|
|
||||||
|
public boolean isSpecial() {
|
||||||
|
switch (this) {
|
||||||
|
case AUTO:
|
||||||
|
case RESTRUCTURE:
|
||||||
|
return false;
|
||||||
|
case SIMPLE:
|
||||||
|
case FALLBACK:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Unexpected decompilation mode: " + this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import jadx.api.metadata.ICodeAnnotation;
|
|||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
|
||||||
public interface ICodeWriter {
|
public interface ICodeWriter {
|
||||||
String NL = System.getProperty("line.separator");
|
|
||||||
String INDENT_STR = " ";
|
|
||||||
|
|
||||||
boolean isMetadataSupported();
|
boolean isMetadataSupported();
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import java.io.Closeable;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
||||||
import jadx.api.args.IntegerFormat;
|
import jadx.api.args.IntegerFormat;
|
||||||
import jadx.api.args.ResourceNameSource;
|
import jadx.api.args.ResourceNameSource;
|
||||||
|
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||||
import jadx.api.args.UserRenamesMappingsMode;
|
import jadx.api.args.UserRenamesMappingsMode;
|
||||||
import jadx.api.data.ICodeData;
|
import jadx.api.data.ICodeData;
|
||||||
import jadx.api.deobf.IAliasProvider;
|
import jadx.api.deobf.IAliasProvider;
|
||||||
@@ -29,12 +30,18 @@ import jadx.api.impl.AnnotatedCodeWriter;
|
|||||||
import jadx.api.impl.InMemoryCodeCache;
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
import jadx.api.plugins.loader.JadxBasePluginLoader;
|
import jadx.api.plugins.loader.JadxBasePluginLoader;
|
||||||
import jadx.api.plugins.loader.JadxPluginLoader;
|
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.IUsageInfoCache;
|
||||||
import jadx.api.usage.impl.InMemoryUsageInfoCache;
|
import jadx.api.usage.impl.InMemoryUsageInfoCache;
|
||||||
import jadx.core.deobf.DeobfAliasProvider;
|
import jadx.core.deobf.DeobfAliasProvider;
|
||||||
import jadx.core.deobf.conditions.DeobfWhitelist;
|
import jadx.core.deobf.conditions.DeobfWhitelist;
|
||||||
import jadx.core.deobf.conditions.JadxRenameConditions;
|
import jadx.core.deobf.conditions.JadxRenameConditions;
|
||||||
|
import jadx.core.export.ExportGradleType;
|
||||||
import jadx.core.plugins.PluginContext;
|
import jadx.core.plugins.PluginContext;
|
||||||
|
import jadx.core.plugins.files.IJadxFilesGetter;
|
||||||
|
import jadx.core.plugins.files.TempFilesGetter;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxArgs implements Closeable {
|
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 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_OUT_DIR = "jadx-output";
|
||||||
public static final String DEFAULT_SRC_DIR = "sources";
|
public static final String DEFAULT_SRC_DIR = "sources";
|
||||||
public static final String DEFAULT_RES_DIR = "resources";
|
public static final String DEFAULT_RES_DIR = "resources";
|
||||||
@@ -80,11 +90,12 @@ public class JadxArgs implements Closeable {
|
|||||||
|
|
||||||
private boolean skipResources = false;
|
private boolean skipResources = false;
|
||||||
private boolean skipSources = false;
|
private boolean skipSources = false;
|
||||||
|
private boolean useHeadersForDetectResourceExtensions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Predicate that allows to filter the classes to be process based on their full name
|
* Predicate that allows to filter the classes to be process based on their full name
|
||||||
*/
|
*/
|
||||||
private Predicate<String> classFilter = null;
|
private @Nullable Predicate<String> classFilter = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save dependencies for classes accepted by {@code classFilter}
|
* Save dependencies for classes accepted by {@code classFilter}
|
||||||
@@ -95,7 +106,8 @@ public class JadxArgs implements Closeable {
|
|||||||
private UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault();
|
private UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault();
|
||||||
|
|
||||||
private boolean deobfuscationOn = false;
|
private boolean deobfuscationOn = false;
|
||||||
private boolean useSourceNameAsClassAlias = false;
|
private UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.getDefault();
|
||||||
|
private int sourceNameRepeatLimit = 10;
|
||||||
|
|
||||||
private File generatedRenamesMappingFile = null;
|
private File generatedRenamesMappingFile = null;
|
||||||
private GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
private GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
||||||
@@ -122,7 +134,9 @@ public class JadxArgs implements Closeable {
|
|||||||
private boolean escapeUnicode = false;
|
private boolean escapeUnicode = false;
|
||||||
private boolean replaceConsts = true;
|
private boolean replaceConsts = true;
|
||||||
private boolean respectBytecodeAccModifiers = false;
|
private boolean respectBytecodeAccModifiers = false;
|
||||||
private boolean exportAsGradleProject = false;
|
private @Nullable ExportGradleType exportGradleType = null;
|
||||||
|
|
||||||
|
private boolean restoreSwitchOverString = true;
|
||||||
|
|
||||||
private boolean skipXmlPrettyPrint = false;
|
private boolean skipXmlPrettyPrint = false;
|
||||||
|
|
||||||
@@ -144,10 +158,20 @@ public class JadxArgs implements Closeable {
|
|||||||
|
|
||||||
private ICodeData codeData;
|
private ICodeData codeData;
|
||||||
|
|
||||||
|
private String codeNewLineStr = DEFAULT_NEW_LINE_STR;
|
||||||
|
|
||||||
|
private String codeIndentStr = DEFAULT_INDENT_STR;
|
||||||
|
|
||||||
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||||
|
|
||||||
private IntegerFormat integerFormat = IntegerFormat.AUTO;
|
private IntegerFormat integerFormat = IntegerFormat.AUTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum updates allowed total in method per one instruction.
|
||||||
|
* Should be more or equal 1, default value is 10.
|
||||||
|
*/
|
||||||
|
private int typeUpdatesLimitCount = 10;
|
||||||
|
|
||||||
private boolean useDxInput = false;
|
private boolean useDxInput = false;
|
||||||
|
|
||||||
public enum UseKotlinMethodsForVarNames {
|
public enum UseKotlinMethodsForVarNames {
|
||||||
@@ -156,13 +180,36 @@ public class JadxArgs implements Closeable {
|
|||||||
|
|
||||||
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
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)
|
* Don't save files (can be using for performance testing)
|
||||||
*/
|
*/
|
||||||
private boolean skipFilesSave = false;
|
private boolean skipFilesSave = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run additional expensive checks to verify internal invariants and info integrity
|
||||||
|
*/
|
||||||
|
private boolean runDebugChecks = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passes to exclude from processing.
|
||||||
|
*/
|
||||||
|
private final List<String> disabledPasses = new ArrayList<>();
|
||||||
|
|
||||||
private Map<String, String> pluginOptions = new HashMap<>();
|
private Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
|
private Set<String> disabledPlugins = new HashSet<>();
|
||||||
|
|
||||||
private JadxPluginLoader pluginLoader = new JadxBasePluginLoader();
|
private JadxPluginLoader pluginLoader = new JadxBasePluginLoader();
|
||||||
|
|
||||||
private boolean loadJadxClsSetFile = true;
|
private boolean loadJadxClsSetFile = true;
|
||||||
@@ -180,7 +227,6 @@ public class JadxArgs implements Closeable {
|
|||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
try {
|
try {
|
||||||
inputFiles = null;
|
|
||||||
if (codeCache != null) {
|
if (codeCache != null) {
|
||||||
codeCache.close();
|
codeCache.close();
|
||||||
}
|
}
|
||||||
@@ -192,9 +238,6 @@ public class JadxArgs implements Closeable {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to close JadxArgs", e);
|
LOG.error("Failed to close JadxArgs", e);
|
||||||
} finally {
|
|
||||||
codeCache = null;
|
|
||||||
usageInfoCache = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,8 +245,12 @@ public class JadxArgs implements Closeable {
|
|||||||
return inputFiles;
|
return inputFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addInputFile(File inputFile) {
|
||||||
|
this.inputFiles.add(inputFile);
|
||||||
|
}
|
||||||
|
|
||||||
public void setInputFile(File inputFile) {
|
public void setInputFile(File inputFile) {
|
||||||
this.inputFiles = Collections.singletonList(inputFile);
|
addInputFile(inputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInputFiles(List<File> inputFiles) {
|
public void setInputFiles(List<File> inputFiles) {
|
||||||
@@ -418,12 +465,37 @@ public class JadxArgs implements Closeable {
|
|||||||
this.generatedRenamesMappingFileMode = mode;
|
this.generatedRenamesMappingFileMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUseSourceNameAsClassAlias() {
|
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
|
||||||
return useSourceNameAsClassAlias;
|
return useSourceNameAsClassNameAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias) {
|
||||||
|
this.useSourceNameAsClassNameAlias = useSourceNameAsClassNameAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceNameRepeatLimit() {
|
||||||
|
return sourceNameRepeatLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceNameRepeatLimit(int sourceNameRepeatLimit) {
|
||||||
|
this.sourceNameRepeatLimit = sourceNameRepeatLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #getUseSourceNameAsClassNameAlias()} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean isUseSourceNameAsClassAlias() {
|
||||||
|
return getUseSourceNameAsClassNameAlias().toBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
|
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
|
||||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
var useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.create(useSourceNameAsClassAlias);
|
||||||
|
setUseSourceNameAsClassNameAlias(useSourceNameAsClassNameAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDeobfuscationMinLength() {
|
public int getDeobfuscationMinLength() {
|
||||||
@@ -507,11 +579,33 @@ public class JadxArgs implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExportAsGradleProject() {
|
public boolean isExportAsGradleProject() {
|
||||||
return exportAsGradleProject;
|
return exportGradleType != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||||
this.exportAsGradleProject = exportAsGradleProject;
|
if (exportAsGradleProject) {
|
||||||
|
if (exportGradleType == null) {
|
||||||
|
exportGradleType = ExportGradleType.AUTO;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exportGradleType = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ExportGradleType getExportGradleType() {
|
||||||
|
return exportGradleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExportGradleType(@Nullable ExportGradleType exportGradleType) {
|
||||||
|
this.exportGradleType = exportGradleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRestoreSwitchOverString() {
|
||||||
|
return restoreSwitchOverString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRestoreSwitchOverString(boolean restoreSwitchOverString) {
|
||||||
|
this.restoreSwitchOverString = restoreSwitchOverString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSkipXmlPrettyPrint() {
|
public boolean isSkipXmlPrettyPrint() {
|
||||||
@@ -622,6 +716,22 @@ public class JadxArgs implements Closeable {
|
|||||||
this.codeData = codeData;
|
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() {
|
public CommentsLevel getCommentsLevel() {
|
||||||
return commentsLevel;
|
return commentsLevel;
|
||||||
}
|
}
|
||||||
@@ -638,6 +748,14 @@ public class JadxArgs implements Closeable {
|
|||||||
this.integerFormat = format;
|
this.integerFormat = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTypeUpdatesLimitCount() {
|
||||||
|
return typeUpdatesLimitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTypeUpdatesLimitCount(int typeUpdatesLimitCount) {
|
||||||
|
this.typeUpdatesLimitCount = Math.max(1, typeUpdatesLimitCount);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUseDxInput() {
|
public boolean isUseDxInput() {
|
||||||
return useDxInput;
|
return useDxInput;
|
||||||
}
|
}
|
||||||
@@ -654,6 +772,22 @@ public class JadxArgs implements Closeable {
|
|||||||
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
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() {
|
public boolean isSkipFilesSave() {
|
||||||
return skipFilesSave;
|
return skipFilesSave;
|
||||||
}
|
}
|
||||||
@@ -662,6 +796,18 @@ public class JadxArgs implements Closeable {
|
|||||||
this.skipFilesSave = skipFilesSave;
|
this.skipFilesSave = skipFilesSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRunDebugChecks() {
|
||||||
|
return runDebugChecks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRunDebugChecks(boolean runDebugChecks) {
|
||||||
|
this.runDebugChecks = runDebugChecks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getDisabledPasses() {
|
||||||
|
return disabledPasses;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getPluginOptions() {
|
public Map<String, String> getPluginOptions() {
|
||||||
return pluginOptions;
|
return pluginOptions;
|
||||||
}
|
}
|
||||||
@@ -670,6 +816,14 @@ public class JadxArgs implements Closeable {
|
|||||||
this.pluginOptions = pluginOptions;
|
this.pluginOptions = pluginOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<String> getDisabledPlugins() {
|
||||||
|
return disabledPlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisabledPlugins(Set<String> disabledPlugins) {
|
||||||
|
this.disabledPlugins = disabledPlugins;
|
||||||
|
}
|
||||||
|
|
||||||
public JadxPluginLoader getPluginLoader() {
|
public JadxPluginLoader getPluginLoader() {
|
||||||
return pluginLoader;
|
return pluginLoader;
|
||||||
}
|
}
|
||||||
@@ -686,6 +840,14 @@ public class JadxArgs implements Closeable {
|
|||||||
this.loadJadxClsSetFile = loadJadxClsSetFile;
|
this.loadJadxClsSetFile = loadJadxClsSetFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseHeadersForDetectResourceExtensions(boolean useHeadersForDetectResourceExtensions) {
|
||||||
|
this.useHeadersForDetectResourceExtensions = useHeadersForDetectResourceExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUseHeadersForDetectResourceExtensions() {
|
||||||
|
return useHeadersForDetectResourceExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash of all options that can change result code
|
* Hash of all options that can change result code
|
||||||
*/
|
*/
|
||||||
@@ -693,12 +855,13 @@ public class JadxArgs implements Closeable {
|
|||||||
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
||||||
+ inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda
|
+ inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda
|
||||||
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationWhitelist
|
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationWhitelist
|
||||||
+ resourceNameSource
|
+ useSourceNameAsClassNameAlias + sourceNameRepeatLimit
|
||||||
|
+ resourceNameSource + useHeadersForDetectResourceExtensions
|
||||||
+ useKotlinMethodsForVarNames
|
+ useKotlinMethodsForVarNames
|
||||||
+ insertDebugLines + extractFinally
|
+ insertDebugLines + extractFinally
|
||||||
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
+ debugInfo + escapeUnicode + replaceConsts + restoreSwitchOverString
|
||||||
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
||||||
+ commentsLevel + useDxInput + integerFormat
|
+ commentsLevel + useDxInput + integerFormat + typeUpdatesLimitCount
|
||||||
+ "|" + buildPluginsHash(decompiler);
|
+ "|" + buildPluginsHash(decompiler);
|
||||||
return FileUtils.md5Sum(argStr);
|
return FileUtils.md5Sum(argStr);
|
||||||
}
|
}
|
||||||
@@ -732,7 +895,8 @@ public class JadxArgs implements Closeable {
|
|||||||
+ ", generatedRenamesMappingFile=" + generatedRenamesMappingFile
|
+ ", generatedRenamesMappingFile=" + generatedRenamesMappingFile
|
||||||
+ ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode
|
+ ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode
|
||||||
+ ", resourceNameSource=" + resourceNameSource
|
+ ", resourceNameSource=" + resourceNameSource
|
||||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
+ ", useSourceNameAsClassNameAlias=" + useSourceNameAsClassNameAlias
|
||||||
|
+ ", sourceNameRepeatLimit=" + sourceNameRepeatLimit
|
||||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||||
+ ", insertDebugLines=" + insertDebugLines
|
+ ", insertDebugLines=" + insertDebugLines
|
||||||
+ ", extractFinally=" + extractFinally
|
+ ", extractFinally=" + extractFinally
|
||||||
@@ -741,8 +905,9 @@ public class JadxArgs implements Closeable {
|
|||||||
+ ", deobfuscationWhitelist=" + deobfuscationWhitelist
|
+ ", deobfuscationWhitelist=" + deobfuscationWhitelist
|
||||||
+ ", escapeUnicode=" + escapeUnicode
|
+ ", escapeUnicode=" + escapeUnicode
|
||||||
+ ", replaceConsts=" + replaceConsts
|
+ ", replaceConsts=" + replaceConsts
|
||||||
|
+ ", restoreSwitchOverString=" + restoreSwitchOverString
|
||||||
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
|
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
|
||||||
+ ", exportAsGradleProject=" + exportAsGradleProject
|
+ ", exportGradleType=" + exportGradleType
|
||||||
+ ", skipXmlPrettyPrint=" + skipXmlPrettyPrint
|
+ ", skipXmlPrettyPrint=" + skipXmlPrettyPrint
|
||||||
+ ", fsCaseSensitive=" + fsCaseSensitive
|
+ ", fsCaseSensitive=" + fsCaseSensitive
|
||||||
+ ", renameFlags=" + renameFlags
|
+ ", renameFlags=" + renameFlags
|
||||||
@@ -754,6 +919,8 @@ public class JadxArgs implements Closeable {
|
|||||||
+ ", pluginOptions=" + pluginOptions
|
+ ", pluginOptions=" + pluginOptions
|
||||||
+ ", cfgOutput=" + cfgOutput
|
+ ", cfgOutput=" + cfgOutput
|
||||||
+ ", rawCFGOutput=" + rawCFGOutput
|
+ ", rawCFGOutput=" + rawCFGOutput
|
||||||
|
+ ", useHeadersForDetectResourceExtensions=" + useHeadersForDetectResourceExtensions
|
||||||
|
+ ", typeUpdatesLimitCount=" + typeUpdatesLimitCount
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,6 @@ public class JadxArgsValidator {
|
|||||||
if (inputFiles.isEmpty() && jadx.getCustomCodeLoaders().isEmpty()) {
|
if (inputFiles.isEmpty() && jadx.getCustomCodeLoaders().isEmpty()) {
|
||||||
throw new JadxArgsValidateException("Please specify input file");
|
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) {
|
for (File file : inputFiles) {
|
||||||
checkFile(file);
|
checkFile(file);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -42,7 +43,8 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.dex.nodes.PackageNode;
|
import jadx.core.dex.nodes.PackageNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.SaveCode;
|
import jadx.core.dex.visitors.SaveCode;
|
||||||
import jadx.core.export.ExportGradleTask;
|
import jadx.core.export.ExportGradle;
|
||||||
|
import jadx.core.export.OutDirs;
|
||||||
import jadx.core.plugins.JadxPluginManager;
|
import jadx.core.plugins.JadxPluginManager;
|
||||||
import jadx.core.plugins.PluginContext;
|
import jadx.core.plugins.PluginContext;
|
||||||
import jadx.core.plugins.events.JadxEventsImpl;
|
import jadx.core.plugins.events.JadxEventsImpl;
|
||||||
@@ -51,9 +53,8 @@ import jadx.core.utils.Utils;
|
|||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
import jadx.core.utils.tasks.TaskExecutor;
|
import jadx.core.utils.tasks.TaskExecutor;
|
||||||
import jadx.core.xmlgen.BinaryXMLParser;
|
|
||||||
import jadx.core.xmlgen.ProtoXMLParser;
|
|
||||||
import jadx.core.xmlgen.ResourcesSaver;
|
import jadx.core.xmlgen.ResourcesSaver;
|
||||||
|
import jadx.zip.ZipReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jadx API usage example:
|
* Jadx API usage example:
|
||||||
@@ -86,55 +87,67 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||||
|
|
||||||
private final JadxArgs args;
|
private final JadxArgs args;
|
||||||
private final JadxPluginManager pluginManager = new JadxPluginManager(this);
|
private final JadxPluginManager pluginManager;
|
||||||
private final List<ICodeLoader> loadedInputs = new ArrayList<>();
|
private final List<ICodeLoader> loadedInputs = new ArrayList<>();
|
||||||
|
private final ZipReader zipReader;
|
||||||
|
|
||||||
private RootNode root;
|
private RootNode root;
|
||||||
private List<JavaClass> classes;
|
private List<JavaClass> classes;
|
||||||
private List<ResourceFile> resources;
|
private List<ResourceFile> resources;
|
||||||
|
|
||||||
private BinaryXMLParser binaryXmlParser;
|
|
||||||
private ProtoXMLParser protoXmlParser;
|
|
||||||
|
|
||||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
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<ICodeLoader> customCodeLoaders = new ArrayList<>();
|
||||||
private final List<CustomResourcesLoader> customResourcesLoaders = new ArrayList<>();
|
private final List<CustomResourcesLoader> customResourcesLoaders = new ArrayList<>();
|
||||||
private final Map<JadxPassType, List<JadxPass>> customPasses = new HashMap<>();
|
private final Map<JadxPassType, List<JadxPass>> customPasses = new HashMap<>();
|
||||||
|
private final List<Closeable> closeableList = new ArrayList<>();
|
||||||
|
|
||||||
|
private IJadxEvents events = new JadxEventsImpl();
|
||||||
|
|
||||||
public JadxDecompiler() {
|
public JadxDecompiler() {
|
||||||
this(new JadxArgs());
|
this(new JadxArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
public JadxDecompiler(JadxArgs args) {
|
public JadxDecompiler(JadxArgs args) {
|
||||||
this.args = args;
|
this.args = Objects.requireNonNull(args);
|
||||||
|
this.pluginManager = new JadxPluginManager(this);
|
||||||
|
this.resourcesLoader = new ResourcesLoader(this);
|
||||||
|
this.zipReader = new ZipReader(args.getSecurity());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load() {
|
public void load() {
|
||||||
reset();
|
reset();
|
||||||
JadxArgsValidator.validate(this);
|
JadxArgsValidator.validate(this);
|
||||||
LOG.info("loading ...");
|
LOG.info("loading ...");
|
||||||
|
FileUtils.updateTempRootDir(args.getFilesGetter().getTempDir());
|
||||||
loadPlugins();
|
loadPlugins();
|
||||||
loadInputFiles();
|
loadInputFiles();
|
||||||
|
|
||||||
root = new RootNode(args);
|
root = new RootNode(this);
|
||||||
root.init();
|
root.init();
|
||||||
root.setDecompilerRef(this);
|
// load classes and resources
|
||||||
root.mergePasses(customPasses);
|
|
||||||
root.loadClasses(loadedInputs);
|
root.loadClasses(loadedInputs);
|
||||||
|
root.loadResources(resourcesLoader, getResources());
|
||||||
|
root.finishClassLoad();
|
||||||
root.initClassPath();
|
root.initClassPath();
|
||||||
root.loadResources(getResources());
|
// init passes
|
||||||
|
root.mergePasses(customPasses);
|
||||||
root.runPreDecompileStage();
|
root.runPreDecompileStage();
|
||||||
root.initPasses();
|
root.initPasses();
|
||||||
loadFinished();
|
loadFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload passes and plugins without processing classes and inputs
|
||||||
|
*/
|
||||||
public void reloadPasses() {
|
public void reloadPasses() {
|
||||||
LOG.info("reloading (passes only) ...");
|
LOG.info("reloading (passes only) ...");
|
||||||
customPasses.clear();
|
customPasses.clear();
|
||||||
root.resetPasses();
|
root.resetPasses();
|
||||||
events.reset();
|
events.reset();
|
||||||
|
unloadPlugins();
|
||||||
|
|
||||||
loadPlugins();
|
loadPlugins();
|
||||||
root.mergePasses(customPasses);
|
root.mergePasses(customPasses);
|
||||||
root.restartVisitors();
|
root.restartVisitors();
|
||||||
@@ -155,7 +168,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
loadedInputs.add(loader);
|
loadedInputs.add(loader);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new JadxRuntimeException("Failed to load code for plugin: " + plugin, e);
|
LOG.warn("Failed to load code for plugin: {}", plugin, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,42 +179,37 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
|
unloadPlugins();
|
||||||
root = null;
|
root = null;
|
||||||
classes = null;
|
classes = null;
|
||||||
resources = null;
|
resources = null;
|
||||||
binaryXmlParser = null;
|
|
||||||
protoXmlParser = null;
|
|
||||||
events.reset();
|
events.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
reset();
|
reset();
|
||||||
closeInputs();
|
closeAll(loadedInputs);
|
||||||
closeLoaders();
|
closeAll(customCodeLoaders);
|
||||||
|
closeAll(customResourcesLoaders);
|
||||||
|
closeAll(closeableList);
|
||||||
|
FileUtils.deleteDirIfExists(args.getFilesGetter().getTempDir());
|
||||||
args.close();
|
args.close();
|
||||||
|
FileUtils.clearTempRootDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeInputs() {
|
private void closeAll(List<? extends Closeable> list) {
|
||||||
loadedInputs.forEach(load -> {
|
try {
|
||||||
try {
|
for (Closeable closeable : list) {
|
||||||
load.close();
|
try {
|
||||||
} catch (Exception e) {
|
closeable.close();
|
||||||
LOG.error("Failed to close input", e);
|
} catch (Exception e) {
|
||||||
}
|
LOG.warn("Fail to close '{}'", closeable, e);
|
||||||
});
|
}
|
||||||
loadedInputs.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeLoaders() {
|
|
||||||
for (CustomResourcesLoader resourcesLoader : customResourcesLoaders) {
|
|
||||||
try {
|
|
||||||
resourcesLoader.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Failed to close resource loader: " + resourcesLoader, e);
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
list.clear();
|
||||||
}
|
}
|
||||||
customResourcesLoaders.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadPlugins() {
|
private void loadPlugins() {
|
||||||
@@ -218,6 +226,10 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void unloadPlugins() {
|
||||||
|
pluginManager.unloadResolved();
|
||||||
|
}
|
||||||
|
|
||||||
private void loadFinished() {
|
private void loadFinished() {
|
||||||
LOG.debug("Load finished");
|
LOG.debug("Load finished");
|
||||||
List<JadxPass> list = customPasses.get(JadxAfterLoadPass.TYPE);
|
List<JadxPass> list = customPasses.get(JadxAfterLoadPass.TYPE);
|
||||||
@@ -295,31 +307,28 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
if (root == null) {
|
if (root == null) {
|
||||||
throw new JadxRuntimeException("No loaded files");
|
throw new JadxRuntimeException("No loaded files");
|
||||||
}
|
}
|
||||||
File sourcesOutDir;
|
OutDirs outDirs;
|
||||||
File resOutDir;
|
ExportGradle gradleExport;
|
||||||
ExportGradleTask gradleExportTask;
|
if (args.getExportGradleType() != null) {
|
||||||
if (args.isExportAsGradleProject()) {
|
gradleExport = new ExportGradle(root, args.getOutDir(), getResources());
|
||||||
gradleExportTask = new ExportGradleTask(resources, root, args.getOutDir());
|
outDirs = gradleExport.init();
|
||||||
gradleExportTask.init();
|
|
||||||
sourcesOutDir = gradleExportTask.getSrcOutDir();
|
|
||||||
resOutDir = gradleExportTask.getResOutDir();
|
|
||||||
} else {
|
} else {
|
||||||
sourcesOutDir = args.getOutDirSrc();
|
gradleExport = null;
|
||||||
resOutDir = args.getOutDirRes();
|
outDirs = new OutDirs(args.getOutDirSrc(), args.getOutDirRes());
|
||||||
gradleExportTask = null;
|
outDirs.makeDirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskExecutor executor = new TaskExecutor();
|
TaskExecutor executor = new TaskExecutor();
|
||||||
executor.setThreadsCount(args.getThreadsCount());
|
executor.setThreadsCount(args.getThreadsCount());
|
||||||
if (saveResources) {
|
if (saveResources) {
|
||||||
// save resources first because decompilation can stop or fail
|
// save resources first because decompilation can stop or fail
|
||||||
appendResourcesSaveTasks(executor, resOutDir);
|
appendResourcesSaveTasks(executor, outDirs.getResOutDir());
|
||||||
}
|
}
|
||||||
if (saveSources) {
|
if (saveSources) {
|
||||||
appendSourcesSave(executor, sourcesOutDir);
|
appendSourcesSave(executor, outDirs.getSrcOutDir());
|
||||||
}
|
}
|
||||||
if (gradleExportTask != null) {
|
if (gradleExport != null) {
|
||||||
executor.addSequentialTask(gradleExportTask);
|
executor.addSequentialTask(gradleExport::generateGradleFiles);
|
||||||
}
|
}
|
||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
@@ -331,13 +340,15 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
// process AndroidManifest.xml first to load complete resource ids table
|
// process AndroidManifest.xml first to load complete resource ids table
|
||||||
for (ResourceFile resourceFile : getResources()) {
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
if (resourceFile.getType() == ResourceType.MANIFEST) {
|
if (resourceFile.getType() == ResourceType.MANIFEST) {
|
||||||
new ResourcesSaver(outDir, resourceFile).run();
|
new ResourcesSaver(this, outDir, resourceFile).run();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Set<String> inputFileNames = args.getInputFiles().stream()
|
Set<String> inputFileNames = args.getInputFiles().stream()
|
||||||
.map(File::getAbsolutePath)
|
.map(File::getAbsolutePath)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
Set<String> codeSources = collectCodeSources();
|
||||||
|
|
||||||
List<Runnable> tasks = new ArrayList<>();
|
List<Runnable> tasks = new ArrayList<>();
|
||||||
for (ResourceFile resourceFile : getResources()) {
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
ResourceType resType = resourceFile.getType();
|
ResourceType resType = resourceFile.getType();
|
||||||
@@ -345,16 +356,44 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
// already processed
|
// already processed
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (resType != ResourceType.ARSC
|
String resOriginalName = resourceFile.getOriginalName();
|
||||||
&& inputFileNames.contains(resourceFile.getOriginalName())) {
|
if (resType != ResourceType.ARSC && inputFileNames.contains(resOriginalName)) {
|
||||||
// ignore resource made from input file
|
// ignore resource made from an input file
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tasks.add(new ResourcesSaver(outDir, resourceFile));
|
if (codeSources.contains(resOriginalName)) {
|
||||||
|
// don't output code source resources (.dex, .class, etc)
|
||||||
|
// do not trust file extensions, use only sources set as class inputs
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tasks.add(new ResourcesSaver(this, outDir, resourceFile));
|
||||||
}
|
}
|
||||||
executor.addParallelTasks(tasks);
|
executor.addParallelTasks(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<String> collectCodeSources() {
|
||||||
|
Set<String> set = new HashSet<>();
|
||||||
|
for (ClassNode cls : root.getClasses(true)) {
|
||||||
|
if (cls.getClsData() == null) {
|
||||||
|
// exclude synthetic classes
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String inputFileName = cls.getInputFileName();
|
||||||
|
if (inputFileName.endsWith(".class")) {
|
||||||
|
// cut .class name to get source .jar file
|
||||||
|
// current template: "<optional input files>:<.jar>:<full class name>"
|
||||||
|
// TODO: add property to set file name or reference to resource name
|
||||||
|
int endIdx = inputFileName.lastIndexOf(':');
|
||||||
|
if (endIdx != -1) {
|
||||||
|
int startIdx = inputFileName.lastIndexOf(':', endIdx - 1) + 1;
|
||||||
|
inputFileName = inputFileName.substring(startIdx, endIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set.add(inputFileName);
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
private void appendSourcesSave(ITaskExecutor executor, File outDir) {
|
private void appendSourcesSave(ITaskExecutor executor, File outDir) {
|
||||||
List<JavaClass> classes = getClasses();
|
List<JavaClass> classes = getClasses();
|
||||||
List<JavaClass> processQueue = filterClasses(classes);
|
List<JavaClass> processQueue = filterClasses(classes);
|
||||||
@@ -401,7 +440,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaClass> getClasses() {
|
public synchronized List<JavaClass> getClasses() {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -409,10 +448,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
List<ClassNode> classNodeList = root.getClasses();
|
List<ClassNode> classNodeList = root.getClasses();
|
||||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||||
for (ClassNode classNode : classNodeList) {
|
for (ClassNode classNode : classNodeList) {
|
||||||
if (classNode.contains(AFlag.DONT_GENERATE)) {
|
if (!classNode.contains(AFlag.DONT_GENERATE) && !classNode.isInner()) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!classNode.getClassInfo().isInner()) {
|
|
||||||
clsList.add(convertClassNode(classNode));
|
clsList.add(convertClassNode(classNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,7 +466,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
if (root == null) {
|
if (root == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
resources = new ResourcesLoader(this).load();
|
resources = resourcesLoader.load(root);
|
||||||
}
|
}
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
@@ -469,20 +505,6 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return root;
|
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
|
* Get JavaClass by ClassNode without loading and decompilation
|
||||||
*/
|
*/
|
||||||
@@ -526,9 +548,10 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return foundPkg;
|
return foundPkg;
|
||||||
}
|
}
|
||||||
List<JavaClass> clsList = Utils.collectionMap(pkg.getClasses(), this::convertClassNode);
|
List<JavaClass> clsList = Utils.collectionMap(pkg.getClasses(), this::convertClassNode);
|
||||||
|
List<JavaClass> clsListNoDup = Utils.collectionMap(pkg.getClassesNoDup(), this::convertClassNode);
|
||||||
int subPkgsCount = pkg.getSubPackages().size();
|
int subPkgsCount = pkg.getSubPackages().size();
|
||||||
List<JavaPackage> subPkgs = subPkgsCount == 0 ? Collections.emptyList() : new ArrayList<>(subPkgsCount);
|
List<JavaPackage> subPkgs = subPkgsCount == 0 ? Collections.emptyList() : new ArrayList<>(subPkgsCount);
|
||||||
JavaPackage javaPkg = new JavaPackage(pkg, clsList, subPkgs);
|
JavaPackage javaPkg = new JavaPackage(pkg, clsList, clsListNoDup, subPkgs);
|
||||||
if (subPkgsCount != 0) {
|
if (subPkgsCount != 0) {
|
||||||
// add subpackages after parent to avoid endless recursion
|
// add subpackages after parent to avoid endless recursion
|
||||||
for (PackageNode subPackage : pkg.getSubPackages()) {
|
for (PackageNode subPackage : pkg.getSubPackages()) {
|
||||||
@@ -599,6 +622,8 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return convertMethodNode((MethodNode) ann);
|
return convertMethodNode((MethodNode) ann);
|
||||||
case FIELD:
|
case FIELD:
|
||||||
return convertFieldNode((FieldNode) ann);
|
return convertFieldNode((FieldNode) ann);
|
||||||
|
case PKG:
|
||||||
|
return convertPackageNode((PackageNode) ann);
|
||||||
case DECLARATION:
|
case DECLARATION:
|
||||||
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
|
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
|
||||||
case VAR:
|
case VAR:
|
||||||
@@ -681,6 +706,10 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setEventsImpl(IJadxEvents eventsImpl) {
|
||||||
|
this.events = eventsImpl;
|
||||||
|
}
|
||||||
|
|
||||||
public void addCustomCodeLoader(ICodeLoader customCodeLoader) {
|
public void addCustomCodeLoader(ICodeLoader customCodeLoader) {
|
||||||
customCodeLoaders.add(customCodeLoader);
|
customCodeLoaders.add(customCodeLoader);
|
||||||
}
|
}
|
||||||
@@ -704,6 +733,18 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass);
|
customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourcesLoader getResourcesLoader() {
|
||||||
|
return resourcesLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZipReader getZipReader() {
|
||||||
|
return zipReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCloseable(Closeable closeable) {
|
||||||
|
closeableList.add(closeable);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "jadx decompiler " + getVersion();
|
return "jadx decompiler " + getVersion();
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import java.util.Comparator;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
@@ -26,11 +25,9 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.utils.ListUtils;
|
import jadx.core.utils.ListUtils;
|
||||||
|
|
||||||
public final class JavaClass implements JavaNode {
|
public final class JavaClass implements JavaNode {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
|
private final @Nullable JadxDecompiler decompiler;
|
||||||
|
|
||||||
private final JadxDecompiler decompiler;
|
|
||||||
private final ClassNode cls;
|
private final ClassNode cls;
|
||||||
private final JavaClass parent;
|
private final @Nullable JavaClass parent;
|
||||||
|
|
||||||
private List<JavaClass> innerClasses = Collections.emptyList();
|
private List<JavaClass> innerClasses = Collections.emptyList();
|
||||||
private List<JavaClass> inlinedClasses = Collections.emptyList();
|
private List<JavaClass> inlinedClasses = Collections.emptyList();
|
||||||
@@ -38,7 +35,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
private List<JavaMethod> methods = Collections.emptyList();
|
private List<JavaMethod> methods = Collections.emptyList();
|
||||||
private boolean listsLoaded;
|
private boolean listsLoaded;
|
||||||
|
|
||||||
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
|
JavaClass(ClassNode classNode, @NotNull JadxDecompiler decompiler) {
|
||||||
this.decompiler = decompiler;
|
this.decompiler = decompiler;
|
||||||
this.cls = classNode;
|
this.cls = classNode;
|
||||||
this.parent = null;
|
this.parent = null;
|
||||||
@@ -47,7 +44,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
/**
|
/**
|
||||||
* Inner classes constructor
|
* Inner classes constructor
|
||||||
*/
|
*/
|
||||||
JavaClass(ClassNode classNode, JavaClass parent) {
|
JavaClass(ClassNode classNode, @NotNull JavaClass parent) {
|
||||||
this.decompiler = null;
|
this.decompiler = null;
|
||||||
this.cls = classNode;
|
this.cls = classNode;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@@ -69,6 +66,21 @@ public final class JavaClass implements JavaNode {
|
|||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if calling load() would trigger a potentially expensive decompilation operation.
|
||||||
|
*/
|
||||||
|
public boolean loadingWouldRequireDecompilation() {
|
||||||
|
if (listsLoaded) {
|
||||||
|
// lists are already populated, so it's safe regardless of the state of the class itself
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cls.getState().isProcessComplete()) {
|
||||||
|
// decompilation has already finished
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized ICodeInfo reload() {
|
public synchronized ICodeInfo reload() {
|
||||||
listsLoaded = false;
|
listsLoaded = false;
|
||||||
return cls.reloadCode();
|
return cls.reloadCode();
|
||||||
@@ -122,8 +134,6 @@ public final class JavaClass implements JavaNode {
|
|||||||
if (listsLoaded) {
|
if (listsLoaded) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
listsLoaded = true;
|
|
||||||
|
|
||||||
ICodeInfo code;
|
ICodeInfo code;
|
||||||
if (cls.getState().isProcessComplete()) {
|
if (cls.getState().isProcessComplete()) {
|
||||||
// already decompiled -> class internals loaded
|
// already decompiled -> class internals loaded
|
||||||
@@ -131,7 +141,12 @@ public final class JavaClass implements JavaNode {
|
|||||||
} else {
|
} else {
|
||||||
code = cls.decompile();
|
code = cls.decompile();
|
||||||
}
|
}
|
||||||
|
loadLists();
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadLists() {
|
||||||
|
listsLoaded = true;
|
||||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||||
int inClsCount = cls.getInnerClasses().size();
|
int inClsCount = cls.getInnerClasses().size();
|
||||||
if (inClsCount != 0) {
|
if (inClsCount != 0) {
|
||||||
@@ -139,7 +154,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
for (ClassNode inner : cls.getInnerClasses()) {
|
for (ClassNode inner : cls.getInnerClasses()) {
|
||||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
javaClass.load();
|
javaClass.loadLists();
|
||||||
list.add(javaClass);
|
list.add(javaClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +165,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
||||||
for (ClassNode inner : cls.getInlinedClasses()) {
|
for (ClassNode inner : cls.getInlinedClasses()) {
|
||||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
javaClass.load();
|
javaClass.loadLists();
|
||||||
list.add(javaClass);
|
list.add(javaClass);
|
||||||
}
|
}
|
||||||
this.inlinedClasses = Collections.unmodifiableList(list);
|
this.inlinedClasses = Collections.unmodifiableList(list);
|
||||||
@@ -178,17 +193,16 @@ public final class JavaClass implements JavaNode {
|
|||||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||||
this.methods = Collections.unmodifiableList(mths);
|
this.methods = Collections.unmodifiableList(mths);
|
||||||
}
|
}
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JadxDecompiler getRootDecompiler() {
|
JadxDecompiler getRootDecompiler() {
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
return parent.getRootDecompiler();
|
return parent.getRootDecompiler();
|
||||||
}
|
}
|
||||||
return decompiler;
|
return Objects.requireNonNull(decompiler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICodeAnnotation getAnnotationAt(int pos) {
|
public @Nullable ICodeAnnotation getAnnotationAt(int pos) {
|
||||||
return getCodeInfo().getCodeMetadata().getAt(pos);
|
return getCodeInfo().getCodeMetadata().getAt(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,8 +266,12 @@ public final class JavaClass implements JavaNode {
|
|||||||
return cls.getPackage();
|
return cls.getPackage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaPackage getJavaPackage() {
|
||||||
|
return cls.getPackageNode().getJavaNode();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getDeclaringClass() {
|
public @Nullable JavaClass getDeclaringClass() {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user