Merge branch 'upstream'

# Conflicts:
#	brut.apktool/apktool-lib/build.gradle.kts
#	build.gradle.kts
This commit is contained in:
oSumAtrIX 2023-10-08 20:42:48 +02:00
commit 35e23a9ad7
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
77 changed files with 1136 additions and 239 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
./docker
./github
*.md

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,217 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 962.84482 136.337"
version="1.1"
id="svg3387"
width="150.84479"
height="136.33701"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<style
id="style3356">
.head, .body, .hindlimb-second, .foot {
fill: rgb(132, 189, 0);
}
.forelimb, .first-toe, .second-toe, .third-toe, .hindlimb-first {
fill: rgb(167, 213, 0);
}
.center-stripe, .hindlimb-third {
fill: rgb(84, 146, 0);
}
</style>
<defs
id="defs3383">
<g
id="left-half-def">
<path
class="head"
d="M 80.95,2.827 C 76.575,2.395 30.015,54.22 30.015,54.22 l 17.201,21.661 z"
id="path3358" />
<path
class="body"
d="M 34.499,109.709 77.779,14.768 V 134.516 Z"
id="path3360" />
<path
class="center-stripe"
d="m 80.402,9.112 h 0.548 v 127.225 l -3.171,-1.821 V 14.768 Z"
id="path3362" />
<path
class="center-stripe"
d="m 80.95,136.337 -3.171,-1.821 V 14.768 l 2.623,-5.656 h 0.548 z"
id="path3364" />
<path
class="forelimb"
d="M 28.473,51.545 47.75,31.06 30.837,28.98 Z"
id="path3366" />
<path
class="first-toe"
d="M 33.71,27.168 47.971,11.58 41.571,28.063 Z"
id="path3368" />
<path
class="second-toe"
d="M 37.563,19.132 31.993,13.184 34.644,0 l 6.61,15.132 z"
id="path3370" />
<path
class="third-toe"
d="M 31.463,26.207 22.135,5.884 35.95,20.859 Z"
id="path3372" />
<path
class="hindlimb-first"
d="M 33.153,106.835 21.269,86.614 41.38,89.218 Z"
id="path3374" />
<path
class="hindlimb-second"
d="m 19.005,87.743 -5.2,22.166 18.249,-0.076 z"
id="path3376" />
<path
class="hindlimb-third"
d="m 47.966,120.204 -24.08,-0.12 -9.971,-8.004 19.356,0.073 z"
id="path3378" />
<path
class="foot"
d="M 15.52,117.01 0,135.967 l 41.224,-1.406 6.106,-11.917 -24.445,0.22 z"
id="path3380" />
</g>
</defs>
<g
id="g11935">
<g
id="g11678">
<path
class="head"
d="M 80.95,2.827 C 76.575,2.395 30.015,54.22 30.015,54.22 l 17.201,21.661 z"
id="path10911" />
<path
class="body"
d="M 34.499,109.709 77.779,14.768 V 134.516 Z"
id="path10913" />
<path
class="center-stripe"
d="m 80.402,9.112 h 0.548 v 127.225 l -3.171,-1.821 V 14.768 Z"
id="path10915" />
<path
class="forelimb"
d="M 28.473,51.545 47.75,31.06 30.837,28.98 Z"
id="path10919" />
<path
class="first-toe"
d="M 33.71,27.168 47.971,11.58 41.571,28.063 Z"
id="path10921" />
<path
class="second-toe"
d="M 37.563,19.132 31.993,13.184 34.644,0 l 6.61,15.132 z"
id="path10923" />
<path
class="third-toe"
d="M 31.463,26.207 22.135,5.884 35.95,20.859 Z"
id="path10925" />
<path
class="hindlimb-first"
d="M 33.153,106.835 21.269,86.614 41.38,89.218 Z"
id="path10927" />
<path
class="hindlimb-second"
d="m 19.005,87.743 -5.2,22.166 18.249,-0.076 z"
id="path10929" />
<path
class="hindlimb-third"
d="m 47.966,120.204 -24.08,-0.12 -9.971,-8.004 19.356,0.073 z"
id="path10931" />
<path
class="foot"
d="M 15.52,117.01 0,135.967 l 41.224,-1.406 6.106,-11.917 -24.445,0.22 z"
id="path10933" />
<path
class="head"
d="M 80.95,2.827 C 85.325,2.395 131.885,54.22 131.885,54.22 L 114.684,75.881 Z"
id="path10937" />
<path
class="body"
d="M 127.401,109.709 84.121,14.768 v 119.748 z"
id="path10939" />
<path
class="center-stripe"
d="M 81.498,9.112 H 80.95 v 127.225 l 3.171,-1.821 V 14.768 Z"
id="path10941" />
<path
id="path10943"
class="center-stripe"
d="M 77.779,134.516 V 14.768 l 2.623,-5.656 h 1.096 l 2.623,5.656 v 119.748 l -3.171,1.821 z" />
<path
class="forelimb"
d="M 133.427,51.545 114.15,31.06 131.063,28.98 Z"
id="path10945" />
<path
class="first-toe"
d="m 128.19,27.168 -14.261,-15.588 6.4,16.483 z"
id="path10947" />
<path
class="second-toe"
d="m 124.337,19.132 5.57,-5.948 L 127.256,0 l -6.61,15.132 z"
id="path10949" />
<path
class="third-toe"
d="M 130.437,26.207 139.765,5.884 125.95,20.859 Z"
id="path10951" />
<path
class="hindlimb-first"
d="M 128.747,106.835 140.631,86.614 120.52,89.218 Z"
id="path10953" />
<path
class="hindlimb-second"
d="m 142.895,87.743 5.2,22.166 -18.249,-0.076 z"
id="path10955" />
<path
class="hindlimb-third"
d="m 113.934,120.204 24.08,-0.12 9.971,-8.004 -19.356,0.073 z"
id="path10957" />
<path
class="foot"
d="m 146.38,117.01 15.52,18.957 -41.224,-1.406 -6.106,-11.917 24.445,0.22 z"
id="path10959" />
</g>
<g
id="g11898">
<path
d="m 227.66966,44.130741 -6.35791,6.569851 q -7.91208,-7.700147 -15.47094,-7.700147 -4.80376,0 -8.2653,3.178959 -3.39089,3.17896 -3.39089,7.417573 0,3.744109 2.82574,7.134999 2.82575,3.461534 11.86812,8.124008 11.02039,5.722128 14.97643,11.020395 3.8854,5.368909 3.8854,12.080047 0,9.466234 -6.6405,16.036084 -6.64049,6.56985 -16.60123,6.56985 -6.64049,0 -12.71584,-2.89638 -6.0047,-2.89639 -9.96074,-7.98272 l 6.21663,-7.06436 q 7.55886,8.54787 16.03609,8.54787 5.93406,0 10.10203,-3.81475 4.16797,-3.814754 4.16797,-8.971733 0,-4.238613 -2.7551,-7.55886 -2.7551,-3.249603 -12.43327,-8.194652 -10.3846,-5.36891 -14.12871,-10.596533 -3.74411,-5.227622 -3.74411,-11.93876 0,-8.7598 5.93406,-14.552571 6.0047,-5.792772 15.11772,-5.792772 10.59653,0 21.33435,10.384602 z"
id="path11847"
style="font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Normal';fill:#84bd00;stroke-width:3.61692" />
<path
d="m 281.64134,33.746139 q 17.73153,0 29.38771,12.857127 10.59654,11.726829 10.59654,27.762916 0,16.106729 -11.23233,28.186778 -11.16168,12.0094 -28.75192,12.0094 -17.66089,0 -28.89322,-12.0094 -11.16168,-12.080049 -11.16168,-28.186778 0,-15.965443 10.59654,-27.692273 11.65618,-12.92777 29.45836,-12.92777 z m 0,9.678167 q -12.29198,0 -21.12243,9.113018 -8.83044,9.113018 -8.83044,22.040788 0,8.335939 4.02668,15.541582 4.02669,7.205642 10.87911,11.161676 6.85242,3.8854 15.04708,3.8854 8.19465,0 15.04707,-3.8854 6.85243,-3.956034 10.87911,-11.161676 4.02668,-7.205643 4.02668,-15.541582 0,-12.92777 -8.90109,-22.040788 -8.83044,-9.113018 -21.05177,-9.113018 z"
id="path11849"
style="font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Normal';fill:#84bd00;stroke-width:3.61692" />
<path
d="m 338.65068,35.724159 h 9.96074 v 35.886924 q 0,13.139701 1.41287,18.084749 2.11931,7.064355 8.05337,11.161678 6.0047,4.09733 14.26999,4.09733 8.2653,0 13.98743,-3.95604 5.79277,-4.026682 7.98272,-10.525889 1.48351,-4.450543 1.48351,-18.861828 V 35.724159 h 10.10203 v 37.723656 q 0,15.894799 -3.74411,23.948164 -3.67346,8.053361 -11.16168,12.645201 -7.41757,4.52118 -18.6499,4.52118 -11.23232,0 -18.79118,-4.52118 -7.48822,-4.59184 -11.23232,-12.715844 -3.67347,-8.194652 -3.67347,-24.583956 z"
id="path11851"
style="font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Normal';fill:#84bd00;stroke-width:3.61692" />
<path
d="m 424.05874,35.724159 h 10.10203 v 11.232324 q 4.52119,-6.640493 9.53688,-9.890097 5.01569,-3.320247 10.45524,-3.320247 4.09733,0 8.7598,2.613812 l -5.15697,8.335939 q -3.10832,-1.342228 -5.22763,-1.342228 -4.94505,0 -9.53688,4.097326 -4.59183,4.026683 -6.99371,12.574553 -1.83673,6.56985 -1.83673,26.561975 v 25.996824 h -10.10203 z"
id="path11853"
style="font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Normal';fill:#84bd00;stroke-width:3.61692" />
<path
d="m 544.8592,51.689601 -7.84144,4.874405 q -10.17267,-13.492918 -27.76291,-13.492918 -14.05807,0 -23.38302,9.042375 -9.2543,9.042374 -9.2543,21.970144 0,8.406583 4.23861,15.824156 4.30926,7.417573 11.72683,11.514897 7.48821,4.09733 16.74252,4.09733 16.95445,0 27.69227,-13.492921 l 7.84144,5.15698 q -5.5102,8.265291 -14.83515,12.857131 -9.2543,4.52118 -21.12242,4.52118 -18.22604,0 -30.23544,-11.58554 -12.00941,-11.585543 -12.00941,-28.186777 0,-11.161681 5.58085,-20.698561 5.65148,-9.607523 15.47093,-14.976433 9.81946,-5.36891 21.97015,-5.36891 7.6295,0 14.69386,2.331238 7.135,2.331237 12.08004,6.075345 4.94505,3.744108 8.40659,9.536879 z"
id="path11855"
style="font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Normal';fill:#84bd00;stroke-width:3.61692" />
<path
d="m 628.43053,87.082021 8.33593,4.3799 q -4.09732,8.053365 -9.46623,12.998409 -5.36891,4.94505 -12.08005,7.55886 -6.71114,2.54317 -15.18836,2.54317 -18.79119,0 -29.38772,-12.29198 -10.59653,-12.362617 -10.59653,-27.904198 0,-14.623216 8.97173,-26.067471 11.37361,-14.552572 30.44737,-14.552572 19.63891,0 31.36574,14.90579 8.33594,10.525889 8.47722,26.279401 h -69.08939 q 0.28257,13.422275 8.54787,22.040788 8.26529,8.547872 20.41598,8.547872 5.86342,0 11.37362,-2.04867 5.58084,-2.04866 9.46623,-5.439549 3.8854,-3.39089 8.40659,-10.94975 z m 0,-20.698561 q -1.97802,-7.912078 -5.79278,-12.645196 -3.7441,-4.733118 -9.96074,-7.629503 -6.21663,-2.896386 -13.06905,-2.896386 -11.30297,0 -19.42698,7.276286 -5.93406,5.298266 -8.97173,15.894799 z"
id="path11857"
style="font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Normal';fill:#84bd00;stroke-width:3.61692" />
<path
d="m 657.45273,7.3960946 h 19.21504 V 35.724159 h 11.44426 V 52.325393 H 676.66777 V 112.58434 H 657.45273 V 52.325393 h -9.8901 V 35.724159 h 9.8901 z"
id="path11838"
style="font-weight:bold;font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Bold';fill:#84bd00;stroke-width:3.61692" />
<path
d="m 734.73677,33.746139 q 10.87911,0 20.41599,5.439554 9.60752,5.439553 14.97643,14.764502 5.36891,9.324949 5.36891,20.133412 0,10.879107 -5.43955,20.345343 -5.36891,9.46624 -14.69386,14.83515 -9.32495,5.29826 -20.55727,5.29826 -16.5306,0 -28.25743,-11.72683 -11.65618,-11.79747 -11.65618,-28.610636 0,-18.014105 13.21034,-30.023509 11.58555,-10.455246 26.63262,-10.455246 z m 0.28258,18.155393 q -8.97174,0 -14.97644,6.287276 -5.93406,6.216633 -5.93406,15.965443 0,10.031384 5.86342,16.248017 5.93406,6.216632 14.97643,6.216632 9.04238,0 15.04708,-6.287276 6.0047,-6.287276 6.0047,-16.177373 0,-9.890097 -5.93406,-16.036086 -5.86341,-6.216633 -15.04707,-6.216633 z"
id="path11840"
style="font-weight:bold;font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Bold';fill:#84bd00;stroke-width:3.61692" />
<path
d="m 848.11968,35.724159 h 19.21505 v 76.860181 h -19.21505 v -8.12401 q -5.65148,5.36891 -11.37361,7.7708 -5.65148,2.33123 -12.29198,2.33123 -14.90579,0 -25.78489,-11.5149 -10.87911,-11.585539 -10.87911,-28.751922 0,-17.802175 10.52589,-29.175787 10.52589,-11.373612 25.57296,-11.373612 6.92307,0 12.99842,2.613812 6.07534,2.613811 11.23232,7.841434 z m -20.2747,15.824155 q -8.97173,0 -14.90579,6.35792 -5.93406,6.287276 -5.93406,16.177373 0,9.960741 6.00471,16.389304 6.07534,6.428563 14.90579,6.428563 9.11301,0 15.11772,-6.287276 6.0047,-6.357919 6.0047,-16.601234 0,-10.031385 -6.0047,-16.248017 -6.00471,-6.216633 -15.18837,-6.216633 z"
id="path11842"
style="font-weight:bold;font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Bold';fill:#84bd00;stroke-width:3.61692" />
<path
d="M 943.62975,6.0538671 H 962.8448 V 112.58434 h -19.21505 v -8.12401 q -5.65148,5.36891 -11.37361,7.7708 -5.65148,2.33123 -12.29198,2.33123 -14.90579,0 -25.78489,-11.5149 -10.87911,-11.585539 -10.87911,-28.751922 0,-17.802175 10.52589,-29.175787 10.52589,-11.373612 25.57296,-11.373612 6.92307,0 12.99842,2.613812 6.07534,2.613811 11.23232,7.841434 z m -20.2747,45.4944469 q -8.97173,0 -14.90579,6.35792 -5.93405,6.287276 -5.93405,16.177373 0,9.960741 6.0047,16.389304 6.07534,6.428563 14.90579,6.428563 9.11301,0 15.11772,-6.287276 6.0047,-6.357919 6.0047,-16.601234 0,-10.031385 -6.0047,-16.248017 -6.00471,-6.216633 -15.18837,-6.216633 z"
id="path11844"
style="font-weight:bold;font-size:144.678px;line-height:1.25;font-family:'Century Gothic';-inkscape-font-specification:'Century Gothic, Bold';fill:#84bd00;stroke-width:3.61692" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2

View File

@ -10,6 +10,7 @@ on:
- 'brut.apktool/apktool-lib/src/main/resources/**'
- 'brut.apktool/apktool-lib/src/test/**'
- '.github/workflows/**'
- 'gradle/libs.versions.toml'
- 'gradle/wrapper/**'
- 'gradlew'
- 'gradlew.bat'
@ -27,7 +28,7 @@ jobs:
matrix:
file: [ aapt_64, aapt2_64 ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Verify Executable
run: ${{ env.BINARY_PATH }}/macosx/${{ matrix.file }} version
- name: Output Static
@ -39,7 +40,7 @@ jobs:
matrix:
file: [ aapt, aapt_64, aapt2, aapt2_64 ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Verify Executable
run: ${{ env.BINARY_PATH }}/linux/${{ matrix.file }} version
- name: Output Static
@ -51,7 +52,7 @@ jobs:
matrix:
file: [ aapt.exe, aapt_64.exe, aapt2.exe, aapt2_64.exe ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Verify Executable
run: ${{ env.BINARY_PATH }}/windows/${{ matrix.file }} version
- name: Output Static
@ -70,13 +71,13 @@ jobs:
os: [ ubuntu-latest, macOS-latest, windows-latest ]
java: [ 8, 11, 17, 18, 19, 20 ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: ${{ matrix.java }}
- name: Build and test
uses: gradle/gradle-build-action@v2.7.1
uses: gradle/gradle-build-action@v2.9.0
with:
arguments: build shadowJar proguard
@ -87,7 +88,7 @@ jobs:
needs:
- build-and-test-with-Java-8-and-later
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@v3
@ -95,8 +96,9 @@ jobs:
distribution: 'zulu'
java-version: 17
- name: Build
uses: gradle/gradle-build-action@v2.7.1
uses: gradle/gradle-build-action@v2.9.0
with:
dependency-graph: generate-and-submit
arguments: build shadowJar proguard
- name: Upload
uses: actions/upload-artifact@v3

47
.github/workflows/docker-beta.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Build and push docker (beta)
on:
pull_request:
branches: [master]
types: [closed]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ibotpeaches/apktool
jobs:
build_and_push:
name: Build and push
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get hash
id: hash
run: echo "APKTOOL_HASH=$(echo $GITHUB_SHA | cut -c 1-6)" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64
file: ./docker/Dockerfile
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.APKTOOL_HASH }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:beta
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:master

50
.github/workflows/docker-release.yml vendored Normal file
View File

@ -0,0 +1,50 @@
name: Build and push docker (release)
on:
release:
types: [published]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ibotpeaches/apktool
jobs:
build_and_push:
name: Build and push
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get hash
id: hash
run: echo "APKTOOL_HASH=$(echo $GITHUB_SHA | cut -c 1-6)" >> $GITHUB_ENV
- name: Get release tag
id: tag
run: echo "APKTOOL_TAG=$(echo ${GITHUB_REF:-no-tag} |sed 's|refs/tags/||g')" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64
file: ./docker/Dockerfile
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.APKTOOL_HASH }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.APKTOOL_TAG }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

View File

@ -16,7 +16,7 @@ jobs:
validate-gradle-wrapper:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
distribution: 'zulu'

View File

@ -251,8 +251,8 @@ we aren't building the entire AOSP package, the initial build is to just see if
We check out a certain tag or branch. Currently we use
* aapt2 - `master`.
* aapt1 - `master`.
* aapt2 - `android-14.0.0_r2`.
* aapt1 - `android-14.0.0_r2`.
### Including our modified `frameworks/base` package.

View File

@ -12,14 +12,6 @@ It is NOT intended for piracy and other non-legal uses. It could be used for loc
- [Project Page](https://ibotpeaches.github.io/Apktool/)
- [#apktool on libera.chat](https://web.libera.chat/)
#### Sponsored by
* [Sourcetoad](https://www.sourcetoad.com/cool-tools/apktool/) - helping with a weekly sponsorship for continued improvement and maintenance of the project.
#### IDE of Choice
* [JetBrains IntelliJ](https://www.jetbrains.com/idea/)
#### Security Vulnerabilities
If you discover a security vulnerability within Apktool, please send an e-mail to Connor Tumbleson at connor.tumbleson(at)gmail.com. All security vulnerabilities will be promptly addressed.
@ -29,9 +21,35 @@ If you discover a security vulnerability within Apktool, please send an e-mail t
- [Downloads Mirror](https://connortumbleson.com/apktool/)
- [How to Build](https://ibotpeaches.github.io/Apktool/build/)
- [Documentation](https://ibotpeaches.github.io/Apktool/documentation/)
- [Use in Docker](./docker/README.md)
- [Bug Reports](https://github.com/iBotPeaches/Apktool/issues)
- [Changelog/Information](https://ibotpeaches.github.io/Apktool/changes/)
- [XDA Post](https://forum.xda-developers.com/t/util-dec-2-2020-apktool-tool-for-reverse-engineering-apk-files.1755243/)
- [Source (Github)](https://github.com/iBotPeaches/Apktool)
- [Source (Bitbucket)](https://bitbucket.org/iBotPeaches/apktool/)
## Sponsors
Special thanks goes to the following sponsors:
### Sourcetoad
[Sourcetoad](https://sourcetoad.com/) is an award-winning software and app development firm committed to the co-creation of technology solutions that solve complex business problems, delight users, and help our clients achieve their goals.
<a href="https://www.sourcetoad.com" alt="Sourcetoad">
<picture>
<img src="https://github.com/ibotpeaches/apktool/raw/master/.github/assets/sponsors/sourcetoad-horizontal.svg">
</picture>
</a>
### Emerge Tools
[Emerge Tools](https://www.emergetools.com) is a suite of revolutionary products designed to supercharge mobile apps and the teams that build them.
<a href="https://www.emergetools.com" alt="Emerge Tools">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/ibotpeaches/apktool/raw/master/.github/assets/sponsors/emerge-tools-vertical-white.svg">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/ibotpeaches/apktool/raw/master/.github/assets/sponsors/emerge-tools-vertical-black.svg">
<img src="https://github.com/ibotpeaches/apktool/raw/master/.github/assets/sponsors/emerge-tools-vertical-black.svg">
</picture>
</a>

View File

@ -1,10 +1,9 @@
import proguard.gradle.ProGuardTask
val commonsCliVersion: String by rootProject.extra
val apktoolVersion: String by rootProject.extra
plugins {
id("com.github.johnrengelman.shadow")
alias(libs.plugins.shadow)
application
}
@ -14,12 +13,12 @@ plugins {
buildscript {
dependencies {
// Proguard doesn't support plugin DSL - https://github.com/Guardsquare/proguard/issues/225
classpath("com.guardsquare:proguard-gradle:7.3.2")
classpath(libs.proguard)
}
}
dependencies {
implementation("commons-cli:commons-cli:$commonsCliVersion")
implementation(libs.commons.cli)
implementation(project(":brut.apktool:apktool-lib"))
}

View File

@ -55,7 +55,7 @@ public class Main {
CommandLine commandLine;
// load options
_Options();
_options();
try {
commandLine = parser.parse(allOptions, args, false);
@ -167,6 +167,34 @@ public class Main {
if (cli.hasOption("m") || cli.hasOption("match-original")) {
config.analysisMode = true;
}
if (cli.hasOption("resm") || cli.hasOption("res-mode") || cli.hasOption("resolve-resources-mode")) {
String mode = cli.getOptionValue("resm");
if (mode == null) {
mode = cli.getOptionValue("res-mode");
}
if (mode == null) {
mode = cli.getOptionValue("resolve-resources-mode");
}
switch (mode) {
case "remove":
case "delete":
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_REMOVE);
break;
case "dummy":
case "dummies":
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_DUMMY);
break;
case "keep":
case "preserve":
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_RETAIN);
break;
default:
System.err.println("Unknown resolve resources mode: " + mode);
System.err.println("Expect: 'remove', 'dummy' or 'keep'.");
System.exit(1);
}
}
File outDir;
if (cli.hasOption("o") || cli.hasOption("output")) {
@ -239,11 +267,13 @@ public class Main {
if (cli.hasOption("nc") || cli.hasOption("no-crunch")) {
config.noCrunch = true;
}
if (cli.hasOption("use-aapt1")) {
config.useAapt2 = false;
}
// Temporary flag to enable the use of aapt2. This will transform in time to a use-aapt1 flag, which will be
// legacy and eventually removed.
if (cli.hasOption("use-aapt2")) {
config.useAapt2 = true;
if (cli.hasOption("use-aapt1") && cli.hasOption("use-aapt2")) {
System.err.println("You can only use one of --use-aapt1 or --use-aapt2.");
System.exit(1);
}
File outFile;
@ -300,9 +330,7 @@ public class Main {
System.out.println(ApktoolProperties.getVersion());
}
private static void _Options() {
// create options
private static void _options() {
Option versionOption = Option.builder("version")
.longOpt("version")
.desc("Print the version.")
@ -409,6 +437,13 @@ public class Main {
.desc("Skip changes detection and build all files.")
.build();
Option resolveResModeOption = Option.builder("resm")
.longOpt("resource-mode")
.desc("Sets the resolve resources mode. Possible values are: 'remove' (default), 'dummy' or 'keep'.")
.hasArg(true)
.argName("mode")
.build();
Option aaptOption = Option.builder("a")
.longOpt("aapt")
.hasArg(true)
@ -416,9 +451,14 @@ public class Main {
.desc("Load aapt from specified location.")
.build();
Option aapt1Option = Option.builder()
.longOpt("use-aapt1")
.desc("Use aapt binary instead of aapt2 during the build step.")
.build();
Option aapt2Option = Option.builder()
.longOpt("use-aapt2")
.desc("Use aapt2 binary instead of aapt1 during the build step.")
.desc("Use aapt2 binary instead of aapt during the build step.")
.build();
Option originalOption = Option.builder("c")
@ -469,13 +509,14 @@ public class Main {
decodeOptions.addOption(apiLevelOption);
decodeOptions.addOption(noAssetOption);
decodeOptions.addOption(forceManOption);
decodeOptions.addOption(resolveResModeOption);
buildOptions.addOption(apiLevelOption);
buildOptions.addOption(debugBuiOption);
buildOptions.addOption(netSecConfOption);
buildOptions.addOption(aaptOption);
buildOptions.addOption(originalOption);
buildOptions.addOption(aapt2Option);
buildOptions.addOption(aapt1Option);
buildOptions.addOption(noCrunchOption);
}
@ -525,6 +566,7 @@ public class Main {
allOptions.addOption(debugDecOption);
allOptions.addOption(noDbgOption);
allOptions.addOption(forceManOption);
allOptions.addOption(resolveResModeOption);
allOptions.addOption(noAssetOption);
allOptions.addOption(keepResOption);
allOptions.addOption(debugBuiOption);
@ -533,6 +575,7 @@ public class Main {
allOptions.addOption(originalOption);
allOptions.addOption(verboseOption);
allOptions.addOption(quietOption);
allOptions.addOption(aapt1Option);
allOptions.addOption(aapt2Option);
allOptions.addOption(noCrunchOption);
allOptions.addOption(onlyMainClassesOption);
@ -547,7 +590,7 @@ public class Main {
}
private static void usage() {
_Options();
_options();
HelpFormatter formatter = new HelpFormatter();
formatter.setWidth(120);
@ -596,7 +639,14 @@ public class Main {
@Override
public void publish(LogRecord record) {
if (getFormatter() == null) {
setFormatter(new SimpleFormatter());
setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
return record.getLevel().toString().charAt(0) + ": "
+ record.getMessage()
+ System.getProperty("line.separator");
}
});
}
try {
@ -616,6 +666,7 @@ public class Main {
reportError(null, exception, ErrorManager.FORMAT_FAILURE);
}
}
@Override
public void close() throws SecurityException {}
@Override
@ -627,15 +678,6 @@ public class Main {
if (verbosity == Verbosity.VERBOSE) {
handler.setLevel(Level.ALL);
logger.setLevel(Level.ALL);
} else {
handler.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
return record.getLevel().toString().charAt(0) + ": "
+ record.getMessage()
+ System.getProperty("line.separator");
}
});
}
}

View File

@ -1,13 +1,3 @@
val baksmaliVersion: String by rootProject.extra
val smaliVersion: String by rootProject.extra
val xmlpullVersion: String by rootProject.extra
val guavaVersion: String by rootProject.extra
val commonsLangVersion: String by rootProject.extra
val commonsIoVersion: String by rootProject.extra
val commonsTextVersion: String by rootProject.extra
val junitVersion: String by rootProject.extra
val xmlunitVersion: String by rootProject.extra
val gitRevision: String by rootProject.extra
val apktoolVersion: String by rootProject.extra
@ -53,16 +43,16 @@ dependencies {
api(project(":brut.j.util"))
api(project(":brut.j.common"))
implementation("com.android.tools.smali:smali-baksmali:$baksmaliVersion")
implementation("com.android.tools.smali:smali:$smaliVersion")
implementation("xpp3:xpp3:$xmlpullVersion")
implementation("com.google.guava:guava:$guavaVersion")
implementation("org.apache.commons:commons-lang3:$commonsLangVersion")
implementation("commons-io:commons-io:$commonsIoVersion")
implementation("org.apache.commons:commons-text:$commonsTextVersion")
implementation(libs.baksmali)
implementation(libs.smali)
implementation(libs.xmlpull)
implementation(libs.guava)
implementation(libs.commons.lang3)
implementation(libs.commons.io)
implementation(libs.commons.text)
testImplementation("junit:junit:$junitVersion")
testImplementation("org.xmlunit:xmlunit-legacy:$xmlunitVersion")
testImplementation(libs.junit)
testImplementation(libs.xmlunit)
compileOnly(files(androidJarPath))
}

View File

@ -92,6 +92,7 @@ public class ApkDecoder {
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
//noinspection ResultOfMethodCallIgnored
outDir.mkdirs();
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " on " + mApkInfo.apkFileName);

View File

@ -23,6 +23,7 @@ import java.io.File;
import java.util.logging.Logger;
public class Config {
private static Config instance = null;
private final static Logger LOGGER = Logger.getLogger(Config.class.getName());
public final static short DECODE_SOURCES_NONE = 0x0000;
@ -38,6 +39,10 @@ public class Config {
public final static short DECODE_ASSETS_NONE = 0x0000;
public final static short DECODE_ASSETS_FULL = 0x0001;
public final static short DECODE_RES_RESOLVE_REMOVE = 0x0000;
public final static short DECODE_RES_RESOLVE_DUMMY = 0x0001;
public final static short DECODE_RES_RESOLVE_RETAIN = 0x0002;
// Build options
public boolean forceBuildAll = false;
public boolean forceDeleteFramework = false;
@ -46,7 +51,7 @@ public class Config {
public boolean verbose = false;
public boolean copyOriginalFiles = false;
public boolean updateFiles = false;
public boolean useAapt2 = false;
public boolean useAapt2 = true;
public boolean noCrunch = false;
public int forceApi = 0;
@ -55,6 +60,7 @@ public class Config {
public short decodeResources = DECODE_RESOURCES_FULL;
public short forceDecodeManifest = FORCE_DECODE_MANIFEST_NONE;
public short decodeAssets = DECODE_ASSETS_FULL;
public short decodeResolveMode = DECODE_RES_RESOLVE_REMOVE;
public int apiLevel = 0;
public boolean analysisMode = false;
public boolean forceDelete = false;
@ -72,8 +78,23 @@ public class Config {
return this.useAapt2 || this.aaptVersion == 2;
}
private Config() {
public boolean isDecodeResolveModeUsingDummies() {
return decodeResolveMode == DECODE_RES_RESOLVE_DUMMY;
}
public boolean isDecodeResolveModeRemoving() {
return decodeResolveMode == DECODE_RES_RESOLVE_REMOVE;
}
private Config() {
instance = this;
}
public static Config getInstance() {
if (instance == null) {
instance = new Config();
}
return instance;
}
private void setDefaultFrameworkDirectory() {
@ -105,6 +126,13 @@ public class Config {
decodeSources = mode;
}
public void setDecodeResolveMode(short mode) throws AndrolibException {
if (mode != DECODE_RES_RESOLVE_REMOVE && mode != DECODE_RES_RESOLVE_DUMMY && mode != DECODE_RES_RESOLVE_RETAIN) {
throw new AndrolibException("Invalid decode resources mode");
}
decodeResolveMode = mode;
}
public void setDecodeResources(short mode) throws AndrolibException {
if (mode != DECODE_RESOURCES_NONE && mode != DECODE_RESOURCES_FULL) {
throw new AndrolibException("Invalid decode resources mode");

View File

@ -191,6 +191,7 @@ public class ApkInfo implements YamlSerializable {
return ResConfigFlags.SDK_TIRAMISU;
case "UPSIDEDOWNCAKE":
case "UPSIDE_DOWN_CAKE":
return ResConfigFlags.SDK_UPSIDEDOWN_CAKE;
case "VANILLAICECREAM":
case "VANILLA_ICE_CREAM":
return ResConfigFlags.SDK_DEVELOPMENT;

View File

@ -32,6 +32,7 @@ public class ResConfigFlags {
public final byte keyboard;
public final byte navigation;
public final byte inputFlags;
public final byte grammaticalInflection;
public final short screenWidth;
public final short screenHeight;
@ -70,6 +71,7 @@ public class ResConfigFlags {
keyboard = KEYBOARD_ANY;
navigation = NAVIGATION_ANY;
inputFlags = KEYSHIDDEN_ANY | NAVHIDDEN_ANY;
grammaticalInflection = GRAMMATICAL_GENDER_ANY;
screenWidth = 0;
screenHeight = 0;
sdkVersion = 0;
@ -91,7 +93,7 @@ public class ResConfigFlags {
public ResConfigFlags(short mcc, short mnc, char[] language,
char[] region, byte orientation,
byte touchscreen, int density, byte keyboard, byte navigation,
byte inputFlags, short screenWidth, short screenHeight,
byte inputFlags, byte grammaticalInflection, short screenWidth, short screenHeight,
short sdkVersion, byte screenLayout, byte uiMode,
short smallestScreenWidthDp, short screenWidthDp,
short screenHeightDp, char[] localeScript, char[] localeVariant,
@ -149,6 +151,7 @@ public class ResConfigFlags {
this.keyboard = keyboard;
this.navigation = navigation;
this.inputFlags = inputFlags;
this.grammaticalInflection = grammaticalInflection;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.sdkVersion = sdkVersion;
@ -198,6 +201,18 @@ public class ResConfigFlags {
}
ret.append(getLocaleString());
switch (grammaticalInflection) {
case GRAMMATICAL_GENDER_NEUTER:
ret.append("-neuter");
break;
case GRAMMATICAL_GENDER_FEMININE:
ret.append("-feminine");
break;
case GRAMMATICAL_GENDER_MASCULINE:
ret.append("-masculine");
break;
}
switch (screenLayout & MASK_LAYOUTDIR) {
case SCREENLAYOUT_LAYOUTDIR_RTL:
ret.append("-ldrtl");
@ -421,6 +436,9 @@ public class ResConfigFlags {
}
private short getNaturalSdkVersionRequirement() {
if (grammaticalInflection != 0) {
return SDK_UPSIDEDOWN_CAKE;
}
if ((uiMode & MASK_UI_MODE_TYPE) == UI_MODE_TYPE_VR_HEADSET || (colorMode & COLOR_WIDE_MASK) != 0 || ((colorMode & COLOR_HDR_MASK) != 0)) {
return SDK_OREO;
}
@ -550,6 +568,7 @@ public class ResConfigFlags {
public final static byte SDK_S = 31;
public final static byte SDK_S_V2 = 32;
public final static byte SDK_TIRAMISU = 33;
public final static byte SDK_UPSIDEDOWN_CAKE = 34;
// AOSP has this as 10,000 for dev purposes.
// platform_frameworks_base/commit/c7a1109a1fe0771d4c9b572dcf178e2779fc4f2d
@ -590,6 +609,11 @@ public class ResConfigFlags {
public final static short SCREENLAYOUT_ROUND_NO = 0x1;
public final static short SCREENLAYOUT_ROUND_YES = 0x2;
public final static byte GRAMMATICAL_GENDER_ANY = 0;
public final static byte GRAMMATICAL_GENDER_NEUTER = 1;
public final static byte GRAMMATICAL_GENDER_FEMININE = 2;
public final static byte GRAMMATICAL_GENDER_MASCULINE = 3;
public final static byte KEYBOARD_ANY = 0;
public final static byte KEYBOARD_NOKEYS = 1;
public final static byte KEYBOARD_QWERTY = 2;

View File

@ -105,6 +105,10 @@ public class ResResSpec {
return mType;
}
public boolean isDummyResSpec() {
return getName().startsWith("APKTOOL_DUMMY_");
}
public void addResource(ResResource res) throws AndrolibException {
addResource(res, false);
}

View File

@ -70,6 +70,10 @@ public class ResTable {
return mConfig.analysisMode;
}
public Config getConfig() {
return mConfig;
}
public boolean isMainPkgLoaded() {
return mMainPkgLoaded;
}

View File

@ -23,9 +23,11 @@ import java.util.*;
public final class ResTypeSpec {
public static final String RES_TYPE_NAME_ARRAY = "array";
public static final String RES_TYPE_NAME_PLURALS = "plurals";
public static final String RES_TYPE_NAME_STYLES = "style";
public static final String RES_TYPE_NAME_ATTR = "attr";
public static final String RES_TYPE_NAME_ATTR_PRIVATE = "^attr-private";
public static final String RES_TYPE_NAME_PLURALS = "plurals";
public static final String RES_TYPE_NAME_STRING = "string";
public static final String RES_TYPE_NAME_STYLES = "style";
private final String mName;
private final Map<String, ResResSpec> mResSpecs = new LinkedHashMap<>();
@ -46,7 +48,7 @@ public final class ResTypeSpec {
}
public boolean isString() {
return mName.equalsIgnoreCase("string");
return mName.equalsIgnoreCase(RES_TYPE_NAME_STRING);
}
public ResResSpec getResSpec(String name) throws AndrolibException {

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package brut.androlib.res.data.arsc;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.value.ResReferenceValue;
public class FlagItem {
public final ResReferenceValue ref;
public final int flag;
public String value;
public FlagItem(ResReferenceValue ref, int flag) {
this.ref = ref;
this.flag = flag;
}
public String getValue() throws AndrolibException {
if (value == null) {
if (ref.referentIsNull()) {
return String.format("APKTOOL_MISSING_0x%08x", ref.getRawIntValue());
}
value = ref.getReferent().getName();
}
return value;
}
}

View File

@ -91,6 +91,4 @@ public class ResArrayValue extends ResBagValue implements ResValuesXmlSerializab
private final ResScalarValue[] mItems;
private final String[] AllowedArrayTypes = {"string", "integer"};
public static final int BAG_KEY_ARRAY_START = 0x02000000;
}

View File

@ -26,8 +26,7 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
ResAttr(ResReferenceValue parentVal, int type, Integer min, Integer max,
Boolean l10n) {
ResAttr(ResReferenceValue parentVal, int type, Integer min, Integer max, Boolean l10n) {
super(parentVal);
mType = type;
mMin = min;
@ -64,38 +63,39 @@ public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
public static ResAttr factory(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items, ResValueFactory factory,
ResPackage pkg) throws AndrolibException {
int type = ((ResIntValue) items[0].m2).getValue();
int scalarType = type & 0xffff;
Integer min = null, max = null;
Boolean l10n = null;
int i;
for (i = 1; i < items.length; i++) {
switch (items[i].m1) {
case BAG_KEY_ATTR_MIN:
min = ((ResIntValue) items[i].m2).getValue();
min = (items[i].m2).getRawIntValue();
continue;
case BAG_KEY_ATTR_MAX:
max = ((ResIntValue) items[i].m2).getValue();
max = (items[i].m2).getRawIntValue();
continue;
case BAG_KEY_ATTR_L10N:
l10n = ((ResIntValue) items[i].m2).getValue() != 0;
l10n = (items[i].m2).getRawIntValue() != 0;
continue;
}
break;
}
// #2806 - Make sure we handle int-based values and not just ResIntValue
int rawValue = items[0].m2.getRawIntValue();
int scalarType = rawValue & 0xffff;
if (i == items.length) {
return new ResAttr(parent, scalarType, min, max, l10n);
}
Duo<ResReferenceValue, ResIntValue>[] attrItems = new Duo[items.length - i];
Duo<ResReferenceValue, ResScalarValue>[] attrItems = new Duo[items.length - i];
int j = 0;
for (; i < items.length; i++) {
int resId = items[i].m1;
pkg.addSynthesizedRes(resId);
attrItems[j++] = new Duo<>(factory.newReference(resId, null), (ResIntValue) items[i].m2);
attrItems[j++] = new Duo<>(factory.newReference(resId, null), items[i].m2);
}
switch (type & 0xff0000) {
switch (rawValue & 0xff0000) {
case TYPE_ENUM:
return new ResEnumAttr(parent, scalarType, min, max, l10n, attrItems);
case TYPE_FLAGS:
@ -145,7 +145,6 @@ public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
private final Integer mMax;
private final Boolean mL10n;
public static final int BAG_KEY_ATTR_TYPE = 0x01000000;
private static final int BAG_KEY_ATTR_MIN = 0x01000001;
private static final int BAG_KEY_ATTR_MAX = 0x01000002;
private static final int BAG_KEY_ATTR_L10N = 0x01000003;

View File

@ -36,18 +36,15 @@ public class ResBagValue extends ResValue implements ResValuesXmlSerializable {
ResResource res) throws IOException, AndrolibException {
String type = res.getResSpec().getType().getName();
if ("style".equals(type)) {
new ResStyleValue(mParent, new Duo[0], null)
.serializeToResValuesXml(serializer, res);
new ResStyleValue(mParent, new Duo[0], null).serializeToResValuesXml(serializer, res);
return;
}
if ("array".equals(type)) {
new ResArrayValue(mParent, new Duo[0]).serializeToResValuesXml(
serializer, res);
new ResArrayValue(mParent, new Duo[0]).serializeToResValuesXml(serializer, res);
return;
}
if ("plurals".equals(type)) {
new ResPluralsValue(mParent, new Duo[0]).serializeToResValuesXml(
serializer, res);
new ResPluralsValue(mParent, new Duo[0]).serializeToResValuesXml(serializer, res);
return;
}

View File

@ -25,10 +25,11 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
public class ResEnumAttr extends ResAttr {
ResEnumAttr(ResReferenceValue parent, int type, Integer min, Integer max,
Boolean l10n, Duo<ResReferenceValue, ResIntValue>[] items) {
Boolean l10n, Duo<ResReferenceValue, ResScalarValue>[] items) {
super(parent, type, min, max, l10n);
mItems = items;
}
@ -46,15 +47,21 @@ public class ResEnumAttr extends ResAttr {
}
@Override
protected void serializeBody(XmlSerializer serializer, ResResource res)
throws AndrolibException, IOException {
for (Duo<ResReferenceValue, ResIntValue> duo : mItems) {
int intVal = duo.m2.getValue();
protected void serializeBody(XmlSerializer serializer, ResResource res) throws AndrolibException, IOException {
for (Duo<ResReferenceValue, ResScalarValue> duo : mItems) {
int intVal = duo.m2.getRawIntValue();
// #2836 - Support skipping items if the resource cannot be identified.
ResResSpec m1Referent = duo.m1.getReferent();
if (m1Referent == null && shouldRemoveUnknownRes()) {
LOGGER.fine(String.format("null enum reference: m1=0x%08x(%s), m2=0x%08x(%s)",
duo.m1.getRawIntValue(), duo.m1.getType(), duo.m2.getRawIntValue(), duo.m2.getType()));
continue;
}
serializer.startTag(null, "enum");
serializer.attribute(null, "name",
m1Referent != null ? m1Referent.getName() : "@null"
m1Referent != null ? m1Referent.getName() : String.format("APKTOOL_MISSING_0x%08x", duo.m1.getRawIntValue())
);
serializer.attribute(null, "value", String.valueOf(intVal));
serializer.endTag(null, "enum");
@ -65,8 +72,8 @@ public class ResEnumAttr extends ResAttr {
String value2 = mItemsCache.get(value);
if (value2 == null) {
ResReferenceValue ref = null;
for (Duo<ResReferenceValue, ResIntValue> duo : mItems) {
if (duo.m2.getValue() == value) {
for (Duo<ResReferenceValue, ResScalarValue> duo : mItems) {
if (duo.m2.getRawIntValue() == value) {
ref = duo.m1;
break;
}
@ -79,6 +86,8 @@ public class ResEnumAttr extends ResAttr {
return value2;
}
private final Duo<ResReferenceValue, ResIntValue>[] mItems;
private final Duo<ResReferenceValue, ResScalarValue>[] mItems;
private final Map<Integer, String> mItemsCache = new HashMap<>();
private static final Logger LOGGER = Logger.getLogger(ResEnumAttr.class.getName());
}

View File

@ -17,21 +17,24 @@
package brut.androlib.res.data.value;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.ResResSpec;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.data.arsc.FlagItem;
import brut.util.Duo;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Logger;
public class ResFlagsAttr extends ResAttr {
ResFlagsAttr(ResReferenceValue parent, int type, Integer min, Integer max,
Boolean l10n, Duo<ResReferenceValue, ResIntValue>[] items) {
Boolean l10n, Duo<ResReferenceValue, ResScalarValue>[] items) {
super(parent, type, min, max, l10n);
mItems = new FlagItem[items.length];
for (int i = 0; i < items.length; i++) {
mItems[i] = new FlagItem(items[i].m1, items[i].m2.getValue());
mItems[i] = new FlagItem(items[i].m1, items[i].m2.getRawIntValue());
}
}
@ -70,13 +73,19 @@ public class ResFlagsAttr extends ResAttr {
}
@Override
protected void serializeBody(XmlSerializer serializer, ResResource res)
throws AndrolibException, IOException {
protected void serializeBody(XmlSerializer serializer, ResResource res) throws AndrolibException, IOException {
for (FlagItem item : mItems) {
ResResSpec referent = item.ref.getReferent();
// #2836 - Support skipping items if the resource cannot be identified.
if (referent == null && shouldRemoveUnknownRes()) {
LOGGER.fine(String.format("null flag reference: 0x%08x(%s)", item.ref.getValue(), item.ref.getType()));
continue;
}
serializer.startTag(null, "flag");
serializer.attribute(null, "name", item.getValue());
serializer.attribute(null, "value",
String.format("0x%08x", item.flag));
serializer.attribute(null, "value", String.format("0x%08x", item.flag));
serializer.endTag(null, "flag");
}
}
@ -130,24 +139,5 @@ public class ResFlagsAttr extends ResAttr {
private FlagItem[] mZeroFlags;
private FlagItem[] mFlags;
private static class FlagItem {
public final ResReferenceValue ref;
public final int flag;
public String value;
public FlagItem(ResReferenceValue ref, int flag) {
this.ref = ref;
this.flag = flag;
}
public String getValue() throws AndrolibException {
if (value == null) {
if (ref.referentIsNull()) {
return "@null";
}
value = ref.getReferent().getName();
}
return value;
}
}
private static final Logger LOGGER = Logger.getLogger(ResFlagsAttr.class.getName());
}

View File

@ -25,10 +25,8 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
public class ResPluralsValue extends ResBagValue implements
ResValuesXmlSerializable {
ResPluralsValue(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items) {
public class ResPluralsValue extends ResBagValue implements ResValuesXmlSerializable {
ResPluralsValue(ResReferenceValue parent, Duo<Integer, ResScalarValue>[] items) {
super(parent);
mItems = new ResScalarValue[6];
@ -59,6 +57,5 @@ public class ResPluralsValue extends ResBagValue implements
private final ResScalarValue[] mItems;
public static final int BAG_KEY_PLURALS_START = 0x01000004;
public static final int BAG_KEY_PLURALS_END = 0x01000009;
private static final String[] QUANTITY_MAP = new String[] { "other", "zero", "one", "two", "few", "many" };
}

View File

@ -81,6 +81,11 @@ public abstract class ResScalarValue extends ResIntBasedValue implements
}
}
// Dummy attributes should be <item> with type attribute
if (res.getResSpec().isDummyResSpec()) {
item = true;
}
// Android does not allow values (false) for ids.xml anymore
// https://issuetracker.google.com/issues/80475496
// But it decodes as a ResBoolean, which makes no sense. So force it to empty

View File

@ -25,7 +25,6 @@ import java.io.IOException;
import java.util.regex.Pattern;
public class ResStringValue extends ResScalarValue {
public ResStringValue(String value, int rawValue) {
this(value, rawValue, "string");
}

View File

@ -26,10 +26,8 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.logging.Logger;
public class ResStyleValue extends ResBagValue implements
ResValuesXmlSerializable {
ResStyleValue(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items, ResValueFactory factory) {
public class ResStyleValue extends ResBagValue implements ResValuesXmlSerializable {
ResStyleValue(ResReferenceValue parent, Duo<Integer, ResScalarValue>[] items, ResValueFactory factory) {
super(parent);
mItems = new Duo[items.length];
@ -53,7 +51,7 @@ public class ResStyleValue extends ResBagValue implements
ResResSpec spec = mItem.m1.getReferent();
if (spec == null) {
LOGGER.fine(String.format("null reference: m1=0x%08x(%s), m2=0x%08x(%s)",
LOGGER.fine(String.format("null style reference: m1=0x%08x(%s), m2=0x%08x(%s)",
mItem.m1.getRawIntValue(), mItem.m1.getType(), mItem.m2.getRawIntValue(), mItem.m2.getType()));
continue;
}

View File

@ -16,6 +16,10 @@
*/
package brut.androlib.res.data.value;
public class ResValue {
import brut.androlib.Config;
public class ResValue {
public boolean shouldRemoveUnknownRes() {
return Config.getInstance().isDecodeResolveModeRemoving();
}
}

View File

@ -80,40 +80,30 @@ public class ResValueFactory {
return new ResStringValue(value, rawValue);
}
public ResBagValue bagFactory(int parent, Duo<Integer, ResScalarValue>[] items, ResTypeSpec resTypeSpec) throws AndrolibException {
public ResBagValue bagFactory(int parent, Duo<Integer, ResScalarValue>[] items, ResTypeSpec resTypeSpec)
throws AndrolibException {
ResReferenceValue parentVal = newReference(parent, null);
if (items.length == 0) {
return new ResBagValue(parentVal);
}
int key = items[0].m1;
if (key == ResAttr.BAG_KEY_ATTR_TYPE) {
return ResAttr.factory(parentVal, items, this, mPackage);
}
String resTypeName = resTypeSpec.getName();
// Android O Preview added an unknown enum for c. This is hardcoded as 0 for now.
if (ResTypeSpec.RES_TYPE_NAME_ARRAY.equals(resTypeName)
|| key == ResArrayValue.BAG_KEY_ARRAY_START || key == 0) {
switch (resTypeName) {
case ResTypeSpec.RES_TYPE_NAME_ATTR:
case ResTypeSpec.RES_TYPE_NAME_ATTR_PRIVATE:
return ResAttr.factory(parentVal, items, this, mPackage);
case ResTypeSpec.RES_TYPE_NAME_ARRAY:
return new ResArrayValue(parentVal, items);
}
if (ResTypeSpec.RES_TYPE_NAME_PLURALS.equals(resTypeName) ||
(key >= ResPluralsValue.BAG_KEY_PLURALS_START && key <= ResPluralsValue.BAG_KEY_PLURALS_END)) {
case ResTypeSpec.RES_TYPE_NAME_PLURALS:
return new ResPluralsValue(parentVal, items);
}
if (ResTypeSpec.RES_TYPE_NAME_ATTR.equals(resTypeName)) {
return new ResAttr(parentVal, 0, null, null, null);
}
default:
if (resTypeName.startsWith(ResTypeSpec.RES_TYPE_NAME_STYLES)) {
return new ResStyleValue(parentVal, items, this);
}
throw new AndrolibException("unsupported res type name for bags. Found: " + resTypeName);
}
}
public ResReferenceValue newReference(int resID, String rawValue) {
return newReference(resID, rawValue, false);

View File

@ -59,11 +59,11 @@ public class ARSCDecoder {
mIn = new ExtCountingDataInput(new LittleEndianDataInputStream(arscStream));
mResTable = resTable;
mKeepBroken = keepBroken;
mMissingResSpecMap = new LinkedHashMap<>();
}
private ResPackage[] readResourceTable() throws IOException, AndrolibException {
Set<ResPackage> pkgs = new LinkedHashSet<>();
ResTypeSpec typeSpec;
int chunkNumber = 1;
@ -118,6 +118,10 @@ public class ARSCDecoder {
}
}
if (mResTable.getConfig().isDecodeResolveModeUsingDummies() && mPkg != null && mPkg.getResSpecCount() > 0) {
addMissingResSpecs();
}
return pkgs.toArray(new ResPackage[0]);
}
@ -207,7 +211,7 @@ public class ARSCDecoder {
mHeader.checkForUnreadHeader(mIn);
for (int i = 0; i < count; i++) {
LOGGER.fine(String.format("Skipping staged alias stagedId (%h) finalId: %h", mIn.readInt(), mIn.readInt()));
LOGGER.fine(String.format("Staged alias: 0x%08x -> 0x%08x", mIn.readInt(), mIn.readInt()));
}
}
@ -256,10 +260,16 @@ public class ARSCDecoder {
private ResType readTableType() throws IOException, AndrolibException {
checkChunkType(ARSCHeader.XML_TYPE_TYPE);
int typeId = mIn.readUnsignedByte() - mTypeIdOffset;
// #3311 - Some older applications have no TYPE_SPEC chunks, but still define TYPE chunks.
if (mResTypeSpecs.containsKey(typeId)) {
mResId = (0xff000000 & mResId) | mResTypeSpecs.get(typeId).getId() << 16;
mTypeSpec = mResTypeSpecs.get(typeId);
} else {
mTypeSpec = new ResTypeSpec(mTypeNames.getString(typeId - 1), typeId);
addTypeSpec(mTypeSpec);
mPkg.addType(mTypeSpec);
}
mResId = (0xff000000 & mResId) | mTypeSpec.getId() << 16;
int typeFlags = mIn.readByte();
mIn.skipBytes(2); // reserved
@ -270,16 +280,21 @@ public class ARSCDecoder {
mHeader.checkForUnreadHeader(mIn);
boolean isOffset16 = (typeFlags & TABLE_TYPE_FLAG_OFFSET16) != 0;
boolean isSparse = (typeFlags & TABLE_TYPE_FLAG_SPARSE) != 0;
// Be sure we don't poison mResTable by marking the application as sparse
// Only flag the ResTable as sparse if the main package is not loaded.
if ((typeFlags & 0x01) != 0 && !mResTable.isMainPkgLoaded()) {
if (isSparse && !mResTable.isMainPkgLoaded()) {
mResTable.setSparseResources(true);
}
HashMap<Integer, Integer> entryOffsetMap = new LinkedHashMap<>();
for (int i = 0; i < entryCount; i++) {
if ((typeFlags & 0x01) != 0) {
if (isSparse) {
entryOffsetMap.put(mIn.readUnsignedShort(), mIn.readUnsignedShort());
} else if (isOffset16) {
entryOffsetMap.put(i, mIn.readUnsignedShort());
} else {
entryOffsetMap.put(i, mIn.readInt());
}
@ -295,11 +310,13 @@ public class ARSCDecoder {
}
mType = flags.isInvalid && !mKeepBroken ? null : mPkg.getOrCreateConfig(flags);
int noEntry = isOffset16 ? NO_ENTRY_OFFSET16 : NO_ENTRY;
for (int i : entryOffsetMap.keySet()) {
mResId = (mResId & 0xffff0000) | i;
int offset = entryOffsetMap.get(i);
if (offset == NO_ENTRY) {
if (offset == noEntry) {
mMissingResSpecMap.put(mResId, typeId);
continue;
}
@ -315,6 +332,8 @@ public class ARSCDecoder {
EntryData entryData = readEntryData();
if (entryData != null) {
readEntry(entryData);
} else {
mMissingResSpecMap.put(mResId, typeId);
}
}
@ -329,17 +348,35 @@ public class ARSCDecoder {
private EntryData readEntryData() throws IOException, AndrolibException {
short size = mIn.readShort();
if (size < 0) {
throw new AndrolibException("Entry size is under 0 bytes.");
short flags = mIn.readShort();
boolean isComplex = (flags & ENTRY_FLAG_COMPLEX) != 0;
boolean isCompact = (flags & ENTRY_FLAG_COMPACT) != 0;
if (size < 0 && !isCompact) {
throw new AndrolibException("Entry size is under 0 bytes and not compactly packed.");
}
short flags = mIn.readShort();
int specNamesId = mIn.readInt();
if (specNamesId == NO_ENTRY) {
if (specNamesId == NO_ENTRY && !isCompact) {
return null;
}
ResValue value = (flags & ENTRY_FLAG_COMPLEX) == 0 ? readValue() : readComplexEntry();
// #3366 - In a compactly packed entry, the key index is the size & type is higher 8 bits on flags.
// We assume a size of 8 bytes for compact entries and the specNamesId is the data itself encoded.
ResValue value;
if (isCompact) {
byte type = (byte) ((flags >> 8) & 0xFF);
value = readCompactValue(type, specNamesId);
// To keep code below happy - we know if compact that the size has the key index encoded.
specNamesId = size;
} else if (isComplex) {
value = readComplexEntry();
} else {
value = readValue();
}
// #2824 - In some applications the res entries are duplicated with the 2nd being malformed.
// AOSP skips this, so we will do the same.
if (value == null) {
@ -402,6 +439,12 @@ public class ARSCDecoder {
resId = mIn.readInt();
resValue = readValue();
// #2824 - In some applications the res entries are duplicated with the 2nd being malformed.
// AOSP skips this, so we will do the same.
if (resValue == null) {
continue;
}
if (!(resValue instanceof ResScalarValue)) {
resValue = new ResStringValue(resValue.toString(), resValue.getRawIntValue());
}
@ -411,6 +454,12 @@ public class ARSCDecoder {
return factory.bagFactory(parent, items, mTypeSpec);
}
private ResIntBasedValue readCompactValue(byte type, int data) throws AndrolibException {
return type == TypedValue.TYPE_STRING
? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)
: mPkg.getValueFactory().factory(type, data, null);
}
private ResIntBasedValue readValue() throws IOException, AndrolibException {
int size = mIn.readShort();
if (size < 8) {
@ -464,11 +513,12 @@ public class ARSCDecoder {
byte keyboard = 0;
byte navigation = 0;
byte inputFlags = 0;
byte grammaticalInflection = 0;
if (size >= 20) {
keyboard = mIn.readByte();
navigation = mIn.readByte();
inputFlags = mIn.readByte();
mIn.skipBytes(1); // inputPad0
grammaticalInflection = mIn.readByte();
read = 20;
}
@ -526,6 +576,7 @@ public class ARSCDecoder {
}
int exceedingKnownSize = size - KNOWN_CONFIG_BYTES;
if (exceedingKnownSize > 0) {
byte[] buf = new byte[exceedingKnownSize];
read += exceedingKnownSize;
@ -550,7 +601,7 @@ public class ARSCDecoder {
return new ResConfigFlags(mcc, mnc, language, country,
orientation, touchscreen, density, keyboard, navigation,
inputFlags, screenWidth, screenHeight, sdkVersion,
inputFlags, grammaticalInflection, screenWidth, screenHeight, sdkVersion,
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
screenHeightDp, localeScript, localeVariant, screenLayout2,
colorMode, localeNumberingSystem, isInvalid, size);
@ -589,6 +640,30 @@ public class ARSCDecoder {
mResTypeSpecs.put(resTypeSpec.getId(), resTypeSpec);
}
private void addMissingResSpecs() throws AndrolibException {
for (int resId : mMissingResSpecMap.keySet()) {
int typeId = mMissingResSpecMap.get(resId);
String resName = "APKTOOL_DUMMY_" + Integer.toHexString(resId);
ResID id = new ResID(resId);
ResResSpec spec = new ResResSpec(id, resName, mPkg, mResTypeSpecs.get(typeId));
// If we already have this resID don't add it again.
if (! mPkg.hasResSpec(id)) {
mPkg.addResSpec(spec);
spec.getType().addResSpec(spec);
ResType resType = mPkg.getOrCreateConfig(new ResConfigFlags());
// We are going to make dummy attributes a null reference (@null) now instead of a boolean false.
// This is because aapt2 is stricter when it comes to what we can put in an application.
ResValue value = new ResReferenceValue(mPkg, 0, "");
ResResource res = new ResResource(resType, spec, value);
resType.addResource(res);
spec.addResource(res);
}
}
}
private ARSCHeader nextChunk() throws IOException {
return mHeader = ARSCHeader.read(mIn);
}
@ -614,15 +689,21 @@ public class ARSCDecoder {
private ResType mType;
private int mResId;
private int mTypeIdOffset = 0;
private final HashMap<Integer, Integer> mMissingResSpecMap;
private final HashMap<Integer, ResTypeSpec> mResTypeSpecs = new HashMap<>();
private final static short ENTRY_FLAG_COMPLEX = 0x0001;
private final static short ENTRY_FLAG_PUBLIC = 0x0002;
private final static short ENTRY_FLAG_WEAK = 0x0004;
private final static short ENTRY_FLAG_COMPACT = 0x0008;
private final static short TABLE_TYPE_FLAG_SPARSE = 0x01;
private final static short TABLE_TYPE_FLAG_OFFSET16 = 0x02;
private static final int KNOWN_CONFIG_BYTES = 64;
private static final int NO_ENTRY = 0xFFFFFFFF;
private static final int NO_ENTRY_OFFSET16 = 0xFFFF;
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
}

View File

@ -365,7 +365,7 @@ public final class ResXmlPatcher {
* @throws SAXException
* @throws ParserConfigurationException
*/
private static Document loadDocument(File file)
public static Document loadDocument(File file)
throws IOException, SAXException, ParserConfigurationException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();

View File

@ -22,6 +22,7 @@ import brut.directory.ExtFile;
import brut.directory.FileDirectory;
import org.custommonkey.xmlunit.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
@ -155,6 +156,17 @@ public class BaseTest {
}
}
protected static int getStringEntryCount(Document doc, String key) {
int count = 0;
Element resources = doc.getDocumentElement();
for (int i = 0; i < resources.getChildNodes().getLength(); i++) {
if (resources.getChildNodes().item(i).getNodeName().equals(key)) {
count++;
}
}
return count;
}
protected static ExtFile sTmpDir;
protected static ExtFile sTestOrigDir;
protected static ExtFile sTestNewDir;

View File

@ -18,15 +18,19 @@ package brut.androlib;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.Framework;
import brut.androlib.res.xml.ResXmlPatcher;
import brut.common.BrutException;
import brut.directory.DirUtil;
import brut.directory.Directory;
import brut.directory.FileDirectory;
import brut.util.OS;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
@ -77,6 +81,14 @@ public abstract class TestUtils {
}
}
public static Document getDocumentFromFile(File file) throws BrutException {
try {
return ResXmlPatcher.loadDocument(file);
} catch (ParserConfigurationException | SAXException | IOException ex) {
throw new BrutException(ex);
}
}
public static void copyResourceDir(Class<?> class_, String dirPath, File out) throws BrutException {
if (!out.exists()) {
out.mkdirs();

View File

@ -47,6 +47,7 @@ public class AndroidOreoNotSparseTest extends BaseTest {
LOGGER.info("Building not_sparse.apk...");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestNewDir).build(testApk);
}

View File

@ -47,6 +47,7 @@ public class AndroidOreoSparseTest extends BaseTest {
LOGGER.info("Building sparse.apk...");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestNewDir).build(testApk);
}

View File

@ -16,10 +16,7 @@
*/
package brut.androlib.aapt1;
import brut.androlib.ApkBuilder;
import brut.androlib.ApkDecoder;
import brut.androlib.BaseTest;
import brut.androlib.TestUtils;
import brut.androlib.*;
import brut.directory.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
@ -44,7 +41,9 @@ public class BuildAndDecodeJarTest extends BaseTest {
LOGGER.info("Building testjar.jar...");
File testJar = new File(sTmpDir, "testjar.jar");
new ApkBuilder(sTestOrigDir).build(testJar);
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestOrigDir).build(testJar);
LOGGER.info("Decoding testjar.jar...");
ApkDecoder apkDecoder = new ApkDecoder(testJar);

View File

@ -16,10 +16,7 @@
*/
package brut.androlib.aapt1;
import brut.androlib.ApkBuilder;
import brut.androlib.ApkDecoder;
import brut.androlib.BaseTest;
import brut.androlib.TestUtils;
import brut.androlib.*;
import brut.androlib.apk.ApkInfo;
import brut.common.BrutException;
import brut.directory.ExtFile;
@ -52,7 +49,9 @@ public class BuildAndDecodeTest extends BaseTest {
LOGGER.info("Building testapp.apk...");
File testApk = new File(sTmpDir, "testapp.apk");
new ApkBuilder(sTestOrigDir).build(testApk);
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestOrigDir).build(testApk);
LOGGER.info("Decoding testapp.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);

View File

@ -48,6 +48,7 @@ public class DebugTagRetainedTest extends BaseTest {
LOGGER.info("Building issue1235.apk...");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
config.debugMode = true;
File testApk = new File(sTmpDir, "issue1235.apk");

View File

@ -16,10 +16,7 @@
*/
package brut.androlib.aapt1;
import brut.androlib.ApkBuilder;
import brut.androlib.ApkDecoder;
import brut.androlib.BaseTest;
import brut.androlib.TestUtils;
import brut.androlib.*;
import brut.common.BrutException;
import brut.directory.ExtFile;
import brut.util.OS;
@ -46,7 +43,9 @@ public class DefaultBaksmaliVariableTest extends BaseTest {
LOGGER.info("Building issue1481.jar...");
File testJar = new File(sTmpDir, "issue1481.jar");
new ApkBuilder(sTestOrigDir).build(testJar);
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestOrigDir).build(testJar);
LOGGER.info("Decoding issue1481.jar...");
ApkDecoder apkDecoder = new ApkDecoder(testJar);

View File

@ -50,6 +50,7 @@ public class EmptyResourcesArscTest {
LOGGER.info("Building issue1730.apk...");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestNewDir).build(testApk);
}

View File

@ -14,12 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package brut.androlib.decode;
package brut.androlib.aapt1;
import brut.androlib.ApkBuilder;
import brut.androlib.ApkDecoder;
import brut.androlib.BaseTest;
import brut.androlib.TestUtils;
import brut.androlib.*;
import brut.directory.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
@ -43,7 +40,9 @@ public class ExternalEntityTest extends BaseTest {
LOGGER.info("Building doctype.apk...");
File testApk = new File(sTestOrigDir, "doctype.apk");
new ApkBuilder(sTestOrigDir).build(testApk);
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestOrigDir).build(testApk);
LOGGER.info("Decoding doctype.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);

View File

@ -16,10 +16,7 @@
*/
package brut.androlib.aapt1;
import brut.androlib.ApkBuilder;
import brut.androlib.ApkDecoder;
import brut.androlib.BaseTest;
import brut.androlib.TestUtils;
import brut.androlib.*;
import brut.directory.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
@ -54,8 +51,10 @@ public class LargeIntsInManifestTest extends BaseTest {
apkDecoder.decode(outDir);
// build issue767
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
ExtFile testApk = new ExtFile(sTmpDir, apk + ".out");
new ApkBuilder(testApk).build(null);
new ApkBuilder(config, testApk).build(null);
String newApk = apk + ".out" + File.separator + "dist" + File.separator + apk;
// decode issue767 again

View File

@ -16,10 +16,7 @@
*/
package brut.androlib.aapt1;
import brut.androlib.ApkBuilder;
import brut.androlib.ApkDecoder;
import brut.androlib.BaseTest;
import brut.androlib.TestUtils;
import brut.androlib.*;
import brut.directory.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
@ -62,7 +59,9 @@ public class ProviderAttributeTest extends BaseTest {
// build issue636
ExtFile testApk = new ExtFile(sTmpDir, apk + ".out");
new ApkBuilder(testApk).build(null);
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, testApk).build(null);
String newApk = apk + ".out" + File.separator + "dist" + File.separator + apk;
assertTrue(fileExists(newApk));

View File

@ -81,6 +81,7 @@ public class SharedLibraryTest extends BaseTest {
Config config = Config.getDefaultConfig();
config.frameworkDirectory = sTmpDir.getAbsolutePath();
config.frameworkTag = "shared";
config.useAapt2 = false;
// install library/framework
new Framework(config).installFramework(new File(sTmpDir + File.separator + library));

View File

@ -42,6 +42,7 @@ public class UnknownCompressionTest extends BaseTest {
String apk = "deflated_unknowns.apk";
Config config = Config.getDefaultConfig();
config.frameworkDirectory = sTmpDir.getAbsolutePath();
config.useAapt2 = false;
sTestOrigDir = new ExtFile(sTmpDir, apk);

View File

@ -86,6 +86,12 @@ public class BuildAndDecodeTest extends BaseTest {
compareValuesFiles("values-b+iw+660/strings.xml");
}
@Test
public void valuesGrammaticalGenderTest() throws BrutException {
compareValuesFiles("values-neuter/strings.xml");
compareValuesFiles("values-feminine/strings.xml");
}
@Test
public void valuesBcp47LanguageScriptRegionVariantTest() throws BrutException {
compareValuesFiles("values-b+ast+Latn+IT+AREVELA/strings.xml");

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package brut.androlib.decode;
import brut.androlib.*;
import brut.directory.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
import java.io.File;
import java.io.IOException;
import org.junit.*;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import static org.junit.Assert.*;
public class CompactResourceTest extends BaseTest {
@BeforeClass
public static void beforeClass() throws Exception {
TestUtils.cleanFrameworkFile();
sTmpDir = new ExtFile(OS.createTempDirectory());
TestUtils.copyResourceDir(CompactResourceTest.class, "decode/issue3366/", sTmpDir);
}
@AfterClass
public static void afterClass() throws BrutException {
OS.rmdir(sTmpDir);
}
@Test
public void checkIfDecodeSucceeds() throws BrutException, IOException, ParserConfigurationException, SAXException {
String apk = "issue3366.apk";
File testApk = new File(sTmpDir, apk);
// decode issue3366.apk
ApkDecoder apkDecoder = new ApkDecoder(testApk);
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(outDir);
Document doc = loadDocument(new File(sTestOrigDir + "/res/values/strings.xml"));
assertEquals(1002, getStringEntryCount(doc, "string"));
Config config = Config.getDefaultConfig();
LOGGER.info("Building duplicatedex.apk...");
new ApkBuilder(config, sTestOrigDir).build(testApk);
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package brut.androlib.decode;
import brut.androlib.ApkDecoder;
import brut.androlib.BaseTest;
import brut.androlib.Config;
import brut.androlib.TestUtils;
import brut.directory.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
import java.io.File;
import java.io.IOException;
import org.junit.*;
import org.w3c.dom.Document;
import static org.junit.Assert.*;
public class ResourceModeTest extends BaseTest {
@BeforeClass
public static void beforeClass() throws Exception {
TestUtils.cleanFrameworkFile();
sTmpDir = new ExtFile(OS.createTempDirectory());
TestUtils.copyResourceDir(ResourceModeTest.class, "decode/issue2836/", sTmpDir);
}
@AfterClass
public static void afterClass() throws BrutException {
OS.rmdir(sTmpDir);
}
@Test
public void checkDecodingModeAsRemove() throws BrutException, IOException {
String apk = "issue2836.apk";
Config config = Config.getDefaultConfig();
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_REMOVE);
// decode issue2836.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + "remove.out");
File outDir = new File(sTmpDir + File.separator + apk + "remove.out");
apkDecoder.decode(outDir);
File stringsXml = new File(sTestOrigDir,"res/values/strings.xml");
assertTrue(stringsXml.isFile());
File attrXml = new File(sTestOrigDir,"res/values/attrs.xml");
Document attrDocument = TestUtils.getDocumentFromFile(attrXml);
assertEquals(3, attrDocument.getElementsByTagName("enum").getLength());
File colorXml = new File(sTestOrigDir,"res/values/colors.xml");
Document colorDocument = TestUtils.getDocumentFromFile(colorXml);
assertEquals(8, colorDocument.getElementsByTagName("color").getLength());
assertEquals(0, colorDocument.getElementsByTagName("item").getLength());
File publicXml = new File(sTestOrigDir,"res/values/public.xml");
Document publicDocument = TestUtils.getDocumentFromFile(publicXml);
assertEquals(21, publicDocument.getElementsByTagName("public").getLength());
}
@Test
public void checkDecodingModeAsDummies() throws BrutException, IOException {
String apk = "issue2836.apk";
Config config = Config.getDefaultConfig();
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_DUMMY);
// decode issue2836.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + "dummies.out");
File outDir = new File(sTmpDir + File.separator + apk + "dummies.out");
apkDecoder.decode(outDir);
File stringsXml = new File(sTestOrigDir,"res/values/strings.xml");
assertTrue(stringsXml.isFile());
File attrXml = new File(sTestOrigDir,"res/values/attrs.xml");
Document attrDocument = TestUtils.getDocumentFromFile(attrXml);
assertEquals(4, attrDocument.getElementsByTagName("enum").getLength());
File colorXml = new File(sTestOrigDir,"res/values/colors.xml");
Document colorDocument = TestUtils.getDocumentFromFile(colorXml);
assertEquals(8, colorDocument.getElementsByTagName("color").getLength());
assertEquals(1, colorDocument.getElementsByTagName("item").getLength());
File publicXml = new File(sTestOrigDir,"res/values/public.xml");
Document publicDocument = TestUtils.getDocumentFromFile(publicXml);
assertEquals(22, publicDocument.getElementsByTagName("public").getLength());
}
@Test
public void checkDecodingModeAsLeave() throws BrutException, IOException {
String apk = "issue2836.apk";
Config config = Config.getDefaultConfig();
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_RETAIN);
// decode issue2836.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + "leave.out");
File outDir = new File(sTmpDir + File.separator + apk + "leave.out");
apkDecoder.decode(outDir);
File stringsXml = new File(sTestOrigDir,"res/values/strings.xml");
assertTrue(stringsXml.isFile());
File attrXml = new File(sTestOrigDir,"res/values/attrs.xml");
Document attrDocument = TestUtils.getDocumentFromFile(attrXml);
assertEquals(4, attrDocument.getElementsByTagName("enum").getLength());
File colorXml = new File(sTestOrigDir,"res/values/colors.xml");
Document colorDocument = TestUtils.getDocumentFromFile(colorXml);
assertEquals(8, colorDocument.getElementsByTagName("color").getLength());
assertEquals(0, colorDocument.getElementsByTagName("item").getLength());
File publicXml = new File(sTestOrigDir,"res/values/public.xml");
Document publicDocument = TestUtils.getDocumentFromFile(publicXml);
assertEquals(21, publicDocument.getElementsByTagName("public").getLength());
}
}

View File

@ -35,4 +35,7 @@
<item>res/</item>
<item>view/</item>
</string-array>
<string-array name="issue_2806">
<item>MIICXAIBAAKBgQCjcGqTkOq0CR3rTx0ZSQSIdTrDrFAYl29611xN8aVgMQIWtDB/lD0W5TpKPuU9iaiG/sSn/VYt6EzN7Sr332jj7cyl2WrrHI6ujRswNy4HojMuqtfab5FFDpRmCuvl35fge18OvoQTJELhhJ1EvJ5KUeZiuJ3u3YyMnxxXzLuKbQIDAQABAoGAPrNDz7TKtaLBvaIuMaMXgBopHyQd3jFKbT/tg2Fu5kYm3PrnmCoQfZYXFKCoZUFIS/G1FBVWWGpD/MQ9tbYZkKpwuH+t2rGndMnLXiTC296/s9uix7gsjnT4Naci5N6EN9pVUBwQmGrYUTHFc58ThtelSiPARX7LSU2ibtJSv8ECQQDWBRrrAYmbCUN7ra0DFT6SppaDtvvuKtb+mUeKbg0B8U4y4wCIK5GH8EyQSwUWcXnNBO05rlUPbifsDLv/u82lAkEAw39sTJ0KmJJyaChqvqAJ8guulKlgucQJ0Et9ppZyet9iVwNKX/aW9UlwGBMQdafQ36nd1QMEA8AbAw4D+hw/KQJBANJbHDUGQtk2hrSmZNoV5HXB9Uiq7v4N71k5ER8XwgM5yVGs2tX8dMM3RhnBEtQXXs9LW1uJZSOQcv7JGXNnhN0CQBZenzrJAWxh3XtznHtBfsHWelyCYRIAj4rpCHCmaGUM6IjCVKFUawOYKp5mmAyObkUZf8ue87emJLEdynC1CLkCQHduNjP1hemAGWrd6v8BHhE3kKtcK6KHsPvJR5dOfzbdHAqVePERhISfN6cwZt5p8B3/JUwSR8el66DF7Jm57BM=</item>
</string-array>
</resources>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,5 @@
val commonsIoVersion: String by rootProject.extra
dependencies {
implementation(project(":brut.j.common"))
implementation(project(":brut.j.util"))
implementation("commons-io:commons-io:$commonsIoVersion")
implementation(libs.commons.io)
}

View File

@ -267,8 +267,7 @@ public abstract class AbstractDirectory implements Directory {
protected abstract AbstractDirectory createDirLocal(String name) throws DirectoryException;
protected abstract void removeFileLocal(String name);
private class ParsedPath {
private static class ParsedPath {
public final String dir;
public final String subpath;
public ParsedPath(String dir, String subpath) {
@ -277,7 +276,7 @@ public abstract class AbstractDirectory implements Directory {
}
}
private class SubPath {
private static class SubPath {
public final AbstractDirectory dir;
public final String path;

View File

@ -1,8 +1,5 @@
val commonsIoVersion: String by rootProject.extra
val guavaVersion: String by rootProject.extra
dependencies {
implementation(project(":brut.j.common"))
implementation("commons-io:commons-io:$commonsIoVersion")
implementation("com.google.guava:guava:$guavaVersion")
implementation(libs.commons.io)
implementation(libs.guava)
}

View File

@ -1,18 +1,7 @@
import java.io.ByteArrayOutputStream
val baksmaliVersion by extra("3.0.3")
val commonsCliVersion by extra("1.5.0")
val commonsIoVersion by extra("2.13.0")
val commonsLangVersion by extra("3.13.0")
val commonsTextVersion by extra("1.10.0")
val guavaVersion by extra("32.0.1-jre")
val junitVersion by extra("4.13.2")
val smaliVersion by extra("3.0.3")
val xmlpullVersion by extra("1.1.4c")
val xmlunitVersion by extra("2.9.1")
val version = "2.8.2"
val suffix = "6"
val version = "2.9.1"
val suffix = "SNAPSHOT"
// Strings embedded into the build.
var gitRevision by extra("")
@ -66,17 +55,12 @@ if ("release" !in gradle.startParameter.taskNames) {
}
plugins {
id("com.github.johnrengelman.shadow") version "8.1.1"
alias(libs.plugins.shadow)
`java-library`
`maven-publish`
signing
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType<JavaCompile> {
options.compilerArgs.add("-Xlint:-options")
options.compilerArgs.add("--release 8")
@ -95,6 +79,11 @@ subprojects {
apply(plugin = "java")
apply(plugin = "java-library")
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
val mavenProjects = arrayOf("apktool-lib", "apktool-cli", "brut.j.common", "brut.j.util", "brut.j.dir")
if (project.name in mavenProjects) {

62
docker/Dockerfile Normal file
View File

@ -0,0 +1,62 @@
# BUILDER
# =====================================================
FROM ibm-semeru-runtimes:open-17-jdk-jammy AS builder
COPY . /build
WORKDIR /build
RUN \
# Apktool
./gradlew build shadowJar proguard
RUN \
# Relocate for easier copying
JAR=$(find /build/brut.apktool/apktool-cli/build/libs/apktool-*.jar |grep -v cli-all) &&\
mv ${JAR} /build/apktool.jar
# BASE
# =====================================================
FROM ibm-semeru-runtimes:open-17-jre-jammy AS base
# Latest version as of 2023.10.01
# Ref: https://developer.android.com/studio/index.html#command-line-tools-only
ARG COMMAND_LINE_TOOLS_VERSION=10406996
ARG ANDROID_SDK_ROOT=/opt/android-sdk
COPY --from=builder /build/apktool.jar /usr/local/bin/apktool.jar
COPY --from=builder /build/scripts/linux/apktool /usr/local/bin/apktool
RUN \
# Update
apt-get update &&\
\
# Apktool final setup
chmod +x /usr/local/bin/apktool /usr/local/bin/apktool.jar &&\
\
# Android SDK
apt-get install -y --no-install-recommends \
ca-certificates \
curl \
zipalign \
git \
openssl \
wget \
unzip \
sdkmanager &&\
curl -o /tmp/tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${COMMAND_LINE_TOOLS_VERSION}_latest.zip &&\
mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools &&\
unzip -q /tmp/tools.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools &&\
mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest &&\
rm -v /tmp/tools.zip &&\
mkdir -p /root/.android/ && touch /root/.android/repositories.cfg &&\
yes | sdkmanager --licenses &&\
export BUILD_TOOLS_VERSION=$(sdkmanager --list |grep build-tools |grep -v rc |awk '{print $1}' |sed 's/build-tools;//g' |sort |tail -n1) &&\
sdkmanager --install "build-tools;${BUILD_TOOLS_VERSION}" &&\
ln -s ${ANDROID_SDK_ROOT}/build-tools/${BUILD_TOOLS_VERSION} /opt/bin &&\
\
# Cleanup
rm -rf /var/lib/apt/lists/*
ENV PATH ${PATH}:/opt/bin
CMD ["/usr/local/bin/apktool"]

27
docker/README.md Normal file
View File

@ -0,0 +1,27 @@
# Apktool in Docker
We provide an easy way to leverage `apktool`, along with common Android tools such as `zipalign` and `apksigner`, all from within Docker.
## Building the Docker image
To use the image, pull or build with the included Dockerfile:
```bash
docker pull ghcr.io/ibotpeaches/apktool:latest
# OR
docker build -t apktool:local .
```
## Using the Docker image
The best way to use the image is to create aliases to run the internal commands. Replace `ghcr.io/ibotpeaches/apktool:latest` with `apktool:local` if you have built the image locally.
```bash
alias apktool="docker run --rm -ti --name=apktool -v \"${PWD}:${PWD}\" -w \"${PWD}\" ghcr.io/ibotpeaches/apktool:latest apktool"
alias zipalign="docker run --rm -ti --name=zipalign -v \"${PWD}:${PWD}\" -w \"${PWD}\" ghcr.io/ibotpeaches/apktool:latest zipalign"
alias apksigner="docker run --rm -ti --name=apksigner -v \"${PWD}:${PWD}\" -w \"${PWD}\" ghcr.io/ibotpeaches/apktool:latest apksigner"
```
## Running the commands
You can then utilize these commands as you would if they were natively installed:
```bash
apktool d My.apk -o MyFolder
apktool b MyFolder -o MyNew.apk
zipalign -p -f 4 MyNew.apk MyNewAligned.apk
apksigner sign --ks My.keystore MyNewAligned.apk
```

29
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,29 @@
[versions]
baksmali = "3.0.3"
commons_io = "2.14.0"
commons_cli = "1.5.0"
commons_lang3 = "3.13.0"
commons_text = "1.10.0"
guava = "32.0.1-jre"
junit = "4.13.2"
proguard = "7.3.2"
shadow = "8.1.1"
smali = "3.0.3"
xmlpull = "1.1.4c"
xmlunit = "2.9.1"
[libraries]
baksmali = { module = "com.android.tools.smali:smali-baksmali", version.ref = "baksmali" }
commons_cli = { module = "commons-cli:commons-cli", version.ref = "commons_cli"}
commons_io = { module = "commons-io:commons-io", version.ref = "commons_io" }
commons_lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commons_lang3" }
commons_text = { module = "org.apache.commons:commons-text", version.ref = "commons_text" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit = { module = "junit:junit", version.ref = "junit" }
proguard = { module = "com.guardsquare:proguard-gradle", version.ref = "proguard" }
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
xmlpull = { module = "xpp3:xpp3", version.ref = "xmlpull" }
xmlunit = { module = "org.xmlunit:xmlunit-legacy", version.ref = "xmlunit" }
[plugins]
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }

View File

@ -39,4 +39,4 @@ if "%ATTR:~0,1%"=="-" if "%~x1"==".apk" (
"%java_exe%" -jar -Xmx1024M -Duser.language=en -Dfile.encoding=UTF8 -Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djdk.nio.zipfs.allowDotZipEntry=true "%~dp0%BASENAME%%max%.jar" %fastCommand% %*
rem Pause when ran non interactively
for /f "tokens=2" %%# in ("%cmdcmdline%") do if /i "%%#" equ "/c" pause
for %%i in (%cmdcmdline%) do if /i "%%~i"=="/c" pause & exit /b

View File

@ -1,2 +1,8 @@
rootProject.name = "apktool-cli"
include("brut.j.common", "brut.j.util", "brut.j.dir", "brut.apktool:apktool-lib", "brut.apktool:apktool-cli")
dependencyResolutionManagement {
versionCatalogs {
create("libs") {}
}
}