mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-12 22:42:36 +00:00
Compare commits
338 Commits
canary-270
...
canary-281
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
427a1ca4e5 | ||
|
|
22884e173a | ||
|
|
d1829308e9 | ||
|
|
73840f8721 | ||
|
|
c7d1af9805 | ||
|
|
4ad26d3dfb | ||
|
|
0c70b7670c | ||
|
|
f44d044095 | ||
|
|
5c1cb13472 | ||
|
|
3327fc668e | ||
|
|
610945ac54 | ||
|
|
ddf5474917 | ||
|
|
6ba1685ade | ||
|
|
e02b5f7868 | ||
|
|
ab2e5d1e7e | ||
|
|
f3fef7bfe4 | ||
|
|
c34c7838bb | ||
|
|
c8a16b0e0c | ||
|
|
14f9ed91a1 | ||
|
|
7a207d4ccf | ||
|
|
92a42d901f | ||
|
|
084d89fcce | ||
|
|
55b036c071 | ||
|
|
30e79310ab | ||
|
|
f063fa5054 | ||
|
|
7bd901273c | ||
|
|
c1e061603b | ||
|
|
cb08504fe5 | ||
|
|
c0a1fb77be | ||
|
|
4864c1112a | ||
|
|
9ddeab034b | ||
|
|
c4847ed288 | ||
|
|
b8f1523fb2 | ||
|
|
fb7fa8a6b3 | ||
|
|
9c7d359093 | ||
|
|
eb54bc1fd7 | ||
|
|
d4a0286e13 | ||
|
|
83e66767ff | ||
|
|
7dc010749b | ||
|
|
8e8d013b1b | ||
|
|
bba0373808 | ||
|
|
1fa318dc8c | ||
|
|
6edc5e2037 | ||
|
|
1523ed9f78 | ||
|
|
8e604d2ab8 | ||
|
|
2aba7247a9 | ||
|
|
e66fe8533e | ||
|
|
b03fbb3917 | ||
|
|
c2ece62e4c | ||
|
|
8c972dcf34 | ||
|
|
50af14f2a3 | ||
|
|
e0a356b319 | ||
|
|
c09a792958 | ||
|
|
0bbfe7f44d | ||
|
|
a396abf565 | ||
|
|
1e3edb8883 | ||
|
|
3b8b61bf35 | ||
|
|
6f90456036 | ||
|
|
f56fd4e215 | ||
|
|
aa35aac5d5 | ||
|
|
1f162b819d | ||
|
|
52ef1d1cb2 | ||
|
|
f14e3a89cc | ||
|
|
95d3eac2e0 | ||
|
|
8e73536e02 | ||
|
|
12a0870bc9 | ||
|
|
6ff82c4e86 | ||
|
|
c64de35375 | ||
|
|
ee5283f4e8 | ||
|
|
bd0e954fea | ||
|
|
675471a49e | ||
|
|
c90e73ccec | ||
|
|
a43c1267d8 | ||
|
|
e8958c6b5c | ||
|
|
e8a3bf82c6 | ||
|
|
27fd79176a | ||
|
|
28d86a3454 | ||
|
|
c6c1a17ae6 | ||
|
|
2b47d47215 | ||
|
|
0e82df9e10 | ||
|
|
893821ad88 | ||
|
|
6b80fbfa99 | ||
|
|
8c3c7d0194 | ||
|
|
b94a3d9f2f | ||
|
|
442d0b5ddc | ||
|
|
494615d9a0 | ||
|
|
afbfb81837 | ||
|
|
3ed4e258a3 | ||
|
|
dddd41c95b | ||
|
|
5f2ca81e86 | ||
|
|
c9eac0c438 | ||
|
|
b6b34f7612 | ||
|
|
e55c413261 | ||
|
|
0399cde50a | ||
|
|
019eb03823 | ||
|
|
363410e1c0 | ||
|
|
fc2ef21660 | ||
|
|
18cb659ff3 | ||
|
|
63231d97ce | ||
|
|
9ac81a8a25 | ||
|
|
79af2787ae | ||
|
|
f5f9b285c0 | ||
|
|
6c05f2ae85 | ||
|
|
29043e1684 | ||
|
|
b73d4a7022 | ||
|
|
ad95e8951b | ||
|
|
bf591fca12 | ||
|
|
dcf027884d | ||
|
|
584f3820fe | ||
|
|
3c7c46307a | ||
|
|
4d80361805 | ||
|
|
9a74e19117 | ||
|
|
b1e17706a4 | ||
|
|
caad129d69 | ||
|
|
da58571ce5 | ||
|
|
2aa7f1c094 | ||
|
|
823e31a91b | ||
|
|
fb926ae302 | ||
|
|
e0489eeffd | ||
|
|
dc9d5a4cac | ||
|
|
143743d0b0 | ||
|
|
563f0d5ad5 | ||
|
|
c99f4a591b | ||
|
|
449204e380 | ||
|
|
a85c4c6528 | ||
|
|
d203a6fff6 | ||
|
|
6c612d66d7 | ||
|
|
540253a55b | ||
|
|
15b7c4ccd1 | ||
|
|
442d5335ea | ||
|
|
8a80eea597 | ||
|
|
5e35703091 | ||
|
|
b7ca73f431 | ||
|
|
a14fc90f07 | ||
|
|
c913f7ec74 | ||
|
|
7f6c9e8411 | ||
|
|
bb02ea3a20 | ||
|
|
3981c9665e | ||
|
|
88628fdf3c | ||
|
|
0469817781 | ||
|
|
a786801141 | ||
|
|
ab86732c89 | ||
|
|
59622d1688 | ||
|
|
58a25a3e2b | ||
|
|
15dca29a87 | ||
|
|
46980819c0 | ||
|
|
4fb6a7268c | ||
|
|
c05e963f37 | ||
|
|
7f7f625864 | ||
|
|
b25aa8295a | ||
|
|
15a605765c | ||
|
|
b575c95710 | ||
|
|
a48a9c858a | ||
|
|
0d8d6290a3 | ||
|
|
4dcd733ddd | ||
|
|
b62835cbeb | ||
|
|
6ea740b5ab | ||
|
|
7ab98dd5ac | ||
|
|
fc8b3400fc | ||
|
|
54428ba415 | ||
|
|
95d1e69d8e | ||
|
|
a0f13ab49f | ||
|
|
c3e8405020 | ||
|
|
a93593ea66 | ||
|
|
23eff70883 | ||
|
|
110dd4a8b9 | ||
|
|
d9c2bffc9f | ||
|
|
049db49dc8 | ||
|
|
7c1d2ec61e | ||
|
|
a1b2830c06 | ||
|
|
82d1d19267 | ||
|
|
4d4195c02d | ||
|
|
5637a258fc | ||
|
|
ee6810f417 | ||
|
|
7098248c64 | ||
|
|
0d31d356ef | ||
|
|
b782e7dcb7 | ||
|
|
a4671b4698 | ||
|
|
7edd8be169 | ||
|
|
24650eefe4 | ||
|
|
8e1a44e7eb | ||
|
|
2722875190 | ||
|
|
3ca6d06f69 | ||
|
|
10e47248de | ||
|
|
e73ff679ac | ||
|
|
53e401fa2d | ||
|
|
d2768357da | ||
|
|
a6c2ba7c1e | ||
|
|
aae5b466fb | ||
|
|
2b7be8b949 | ||
|
|
b6511a510d | ||
|
|
704541aef2 | ||
|
|
005560a4c5 | ||
|
|
231a5d1853 | ||
|
|
9e2b59060d | ||
|
|
08ea937f7c | ||
|
|
2baedf74d1 | ||
|
|
32faa4ced6 | ||
|
|
ccdb0b5d13 | ||
|
|
8506b672ad | ||
|
|
ce2e33bb20 | ||
|
|
6707b72260 | ||
|
|
5885b8c20d | ||
|
|
820710c086 | ||
|
|
51cf196bf7 | ||
|
|
dadba44cf9 | ||
|
|
2ce4a5543b | ||
|
|
9112a3a4f5 | ||
|
|
24615afda1 | ||
|
|
c5778f398b | ||
|
|
4eb4097b9b | ||
|
|
c512496847 | ||
|
|
506961a10d | ||
|
|
3414415907 | ||
|
|
dc2ae7cfd1 | ||
|
|
2e86d21c29 | ||
|
|
2654382c43 | ||
|
|
9e26b73813 | ||
|
|
10cd13bf80 | ||
|
|
f10ee5f887 | ||
|
|
47cc532d96 | ||
|
|
218327f92b | ||
|
|
4eae66a1a7 | ||
|
|
b09ceeb43c | ||
|
|
4fb539c110 | ||
|
|
849b284da5 | ||
|
|
895b5f6cbf | ||
|
|
cb3d4ea514 | ||
|
|
0d89a2a97d | ||
|
|
3ca5913055 | ||
|
|
df6b808f49 | ||
|
|
09c7ac754b | ||
|
|
805da67c23 | ||
|
|
3c6889505b | ||
|
|
c8e9ce7627 | ||
|
|
837c679a31 | ||
|
|
06616659b8 | ||
|
|
a34c04f999 | ||
|
|
da43ac89a0 | ||
|
|
830fc758b9 | ||
|
|
0f3cfef278 | ||
|
|
b32d7bfafd | ||
|
|
c0899f2939 | ||
|
|
082330808f | ||
|
|
024da05888 | ||
|
|
377b6d0cc2 | ||
|
|
c661009b31 | ||
|
|
613f2d31c5 | ||
|
|
7dbb973db5 | ||
|
|
f4502f8be8 | ||
|
|
455b13b83c | ||
|
|
8b98709743 | ||
|
|
1b12f45f39 | ||
|
|
a5cad532ff | ||
|
|
070719db50 | ||
|
|
28cccdf7aa | ||
|
|
b7e0986a5c | ||
|
|
da709745dd | ||
|
|
8b6771d487 | ||
|
|
e1b847fbc5 | ||
|
|
7188de1205 | ||
|
|
44fb7dbcbe | ||
|
|
8086b5933c | ||
|
|
7f675f4bf7 | ||
|
|
5e6b53e0da | ||
|
|
5b29fefc65 | ||
|
|
16a168535d | ||
|
|
33f70f8f6d | ||
|
|
4f18a66d73 | ||
|
|
250dc16007 | ||
|
|
7af273e047 | ||
|
|
e381aebaa0 | ||
|
|
45d91c9658 | ||
|
|
4a9158f667 | ||
|
|
0d9ee89e7f | ||
|
|
abaff72304 | ||
|
|
b828e2d0b2 | ||
|
|
53d7cbc11b | ||
|
|
310be7ab47 | ||
|
|
60894e458f | ||
|
|
fbebb6ac10 | ||
|
|
a9f8c20703 | ||
|
|
ae0b15d197 | ||
|
|
869aa62328 | ||
|
|
dcd3bc58a3 | ||
|
|
a82f17c594 | ||
|
|
b38fd1ca5f | ||
|
|
8e82113bce | ||
|
|
f723ef153b | ||
|
|
1dc723fb6d | ||
|
|
8f271c2575 | ||
|
|
7cf56b4406 | ||
|
|
c2eb603957 | ||
|
|
e6bd2ff60f | ||
|
|
5604074eba | ||
|
|
3f061c1a1e | ||
|
|
55e78a7b1a | ||
|
|
000f1d6041 | ||
|
|
2cbec20238 | ||
|
|
4b724c7257 | ||
|
|
ab04c6ab39 | ||
|
|
821a6c6954 | ||
|
|
5f27a62221 | ||
|
|
c76cc4c6bd | ||
|
|
52b75c53b6 | ||
|
|
9db2e99086 | ||
|
|
e9e2ecf2dd | ||
|
|
9a9e617c35 | ||
|
|
3a0becc783 | ||
|
|
1f974cb220 | ||
|
|
1db80228e8 | ||
|
|
838e1e254d | ||
|
|
554eda8fe1 | ||
|
|
2bdc047c4d | ||
|
|
e64f59ce5b | ||
|
|
b8140ad4e6 | ||
|
|
5a55483698 | ||
|
|
2d341863f5 | ||
|
|
278046becb | ||
|
|
5c0497354f | ||
|
|
98c258df93 | ||
|
|
c578cccfd5 | ||
|
|
07835a3e0e | ||
|
|
09131aca89 | ||
|
|
9ce998a6df | ||
|
|
ca36b42d79 | ||
|
|
37df39ec37 | ||
|
|
1701361a73 | ||
|
|
4c14ae33f5 | ||
|
|
d4a9ef7b7f | ||
|
|
1539cfe888 | ||
|
|
9093be1329 | ||
|
|
606d076251 | ||
|
|
46a34e19bc | ||
|
|
5ac7dc0b37 | ||
|
|
3b27de3715 | ||
|
|
939bfac920 | ||
|
|
f601bf12d5 |
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -12,13 +12,11 @@
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
tools/** binary
|
||||
tools/rustup-wrapper/** -binary
|
||||
tools/elf-cleaner/** -binary
|
||||
*.jar binary
|
||||
*.exe binary
|
||||
*.apk binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.ttf binary
|
||||
|
||||
# Help GitHub detect languages
|
||||
native/jni/external/** linguist-vendored
|
||||
native/jni/systemproperties/** linguist-language=C++
|
||||
|
||||
101
.github/actions/setup/action.yml
vendored
101
.github/actions/setup/action.yml
vendored
@@ -1,44 +1,107 @@
|
||||
name: Magisk Setup
|
||||
inputs:
|
||||
is-asset-build:
|
||||
required: false
|
||||
default: false
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up JDK 17
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
java-version: "21"
|
||||
|
||||
- name: Set up Python 3
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Set up sccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
- name: Install GNU make
|
||||
if: runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
brew install make
|
||||
echo 'GNUMAKE=gmake' >> "$GITHUB_ENV"
|
||||
|
||||
- name: Cache sccache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
variant: sccache
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}
|
||||
max-size: 10000M
|
||||
path: .sccache
|
||||
key: sccache-${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: sccache-${{ runner.os }}-
|
||||
|
||||
- name: Restore sccache
|
||||
uses: actions/cache/restore@v4
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
with:
|
||||
path: .sccache
|
||||
key: sccache-${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: sccache-${{ runner.os }}-
|
||||
|
||||
- name: Set up sccache
|
||||
shell: bash
|
||||
env:
|
||||
SCCACHE_DIRECT: false
|
||||
SCCACHE_DIR: ${{ github.workspace }}/.sccache
|
||||
SCCACHE_CACHE_SIZE: 2G
|
||||
SCCACHE_IDLE_TIMEOUT: 0
|
||||
run: |
|
||||
bash $GITHUB_ACTION_PATH/sccache.sh
|
||||
sccache --start-server
|
||||
sccache -z
|
||||
|
||||
- name: Show sccache stats
|
||||
uses: gacts/run-and-post-run@v1
|
||||
with:
|
||||
run: sccache -s
|
||||
post: sccache -s
|
||||
|
||||
- name: Set GRADLE_USER_HOME
|
||||
shell: bash
|
||||
run: echo "GRADLE_USER_HOME=$GITHUB_WORKSPACE/.gradle" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v4
|
||||
if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
|
||||
restore-keys: ${{ runner.os }}-gradle-
|
||||
.gradle/caches
|
||||
.gradle/wrapper
|
||||
!.gradle/caches/build-cache-*
|
||||
key: gradle-cache-${{ hashFiles('gradle/**') }}
|
||||
restore-keys: gradle-cache-
|
||||
|
||||
- name: Cache build cache
|
||||
uses: actions/cache@v4
|
||||
- name: Restore Gradle dependencies
|
||||
uses: actions/cache/restore@v4
|
||||
if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}-build-cache-
|
||||
.gradle/caches
|
||||
.gradle/wrapper
|
||||
!.gradle/caches/build-cache-*
|
||||
key: gradle-cache-${{ hashFiles('gradle/**') }}
|
||||
restore-keys: gradle-cache-
|
||||
enableCrossOsArchive: true
|
||||
|
||||
- name: Cache Gradle build cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}
|
||||
with:
|
||||
path: .gradle/caches/build-cache-*
|
||||
key: gradle-build-cache-${{ github.sha }}
|
||||
restore-keys: gradle-build-cache-
|
||||
|
||||
- name: Restore Gradle build cache
|
||||
uses: actions/cache/restore@v4
|
||||
if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}
|
||||
with:
|
||||
path: .gradle/caches/build-cache-*
|
||||
key: gradle-build-cache-${{ github.sha }}
|
||||
restore-keys: gradle-build-cache-
|
||||
enableCrossOsArchive: true
|
||||
|
||||
- name: Set up NDK
|
||||
run: python build.py -v ndk
|
||||
shell: bash
|
||||
run: python build.py -v ndk
|
||||
|
||||
25
.github/actions/setup/sccache.sh
vendored
Executable file
25
.github/actions/setup/sccache.sh
vendored
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Get latest sccache version
|
||||
get_sccache_ver() {
|
||||
curl -sL 'https://api.github.com/repos/mozilla/sccache/releases/latest' | jq -r .name
|
||||
}
|
||||
|
||||
# $1=variant
|
||||
# $2=install_dir
|
||||
# $3=exe
|
||||
install_from_gh() {
|
||||
local ver=$(curl -sL 'https://api.github.com/repos/mozilla/sccache/releases/latest' | jq -r .name)
|
||||
local url="https://github.com/mozilla/sccache/releases/download/${ver}/sccache-${ver}-$1.tar.gz"
|
||||
local dest="$2/$3"
|
||||
curl -L "$url" | tar xz -O --wildcards "*/$3" > $dest
|
||||
chmod +x $dest
|
||||
}
|
||||
|
||||
if [ $RUNNER_OS = "macOS" ]; then
|
||||
brew install sccache
|
||||
elif [ $RUNNER_OS = "Linux" ]; then
|
||||
install_from_gh x86_64-unknown-linux-musl /usr/local/bin sccache
|
||||
elif [ $RUNNER_OS = "Windows" ]; then
|
||||
install_from_gh x86_64-pc-windows-msvc $USERPROFILE/.cargo/bin sccache.exe
|
||||
fi
|
||||
1
.github/ci.prop
vendored
Normal file
1
.github/ci.prop
vendored
Normal file
@@ -0,0 +1 @@
|
||||
abiList=arm64-v8a
|
||||
70
.github/workflows/build.yml
vendored
70
.github/workflows/build.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
- "buildSrc/**"
|
||||
- "build.py"
|
||||
- "gradle.properties"
|
||||
- "gradle/libs.versions.toml"
|
||||
- ".github/workflows/build.yml"
|
||||
pull_request:
|
||||
branches: [master]
|
||||
@@ -17,9 +18,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build Magisk artifacts
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SCCACHE_DIRECT: false
|
||||
runs-on: macos-15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
@@ -27,10 +26,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup environment
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
is-asset-build: true
|
||||
|
||||
- name: Build release
|
||||
run: ./build.py -vr all
|
||||
@@ -58,46 +58,44 @@ jobs:
|
||||
test-build:
|
||||
name: Test building on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
SCCACHE_DIRECT: false
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, macos-14]
|
||||
os: [windows-2025, ubuntu-24.04]
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup environment
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build debug
|
||||
run: python build.py -v all
|
||||
- name: Test build
|
||||
run: python build.py -v -c .github/ci.prop all
|
||||
|
||||
- name: Stop gradle daemon
|
||||
run: ./gradlew --stop
|
||||
|
||||
avd-test:
|
||||
name: Test API ${{ matrix.version }} (x86_64)
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
|
||||
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
|
||||
type: [""]
|
||||
include:
|
||||
- version: 35
|
||||
- version: 36
|
||||
type: "google_apis"
|
||||
- version: 36
|
||||
type: "google_apis_ps16k"
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -114,13 +112,23 @@ jobs:
|
||||
- name: Run AVD test
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
AVD_TEST_VERBOSE: 1
|
||||
AVD_TEST_LOG: 1
|
||||
run: scripts/avd_test.sh ${{ matrix.version }} ${{ matrix.type }}
|
||||
|
||||
- name: Upload logs on error
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "avd-logs-${{ matrix.version }}"
|
||||
path: |
|
||||
kernel.log
|
||||
logcat.log
|
||||
|
||||
avd-test-32:
|
||||
name: Test API ${{ matrix.version }} (x86)
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -129,8 +137,6 @@ jobs:
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -148,27 +154,35 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
FORCE_32_BIT: 1
|
||||
AVD_TEST_VERBOSE: 1
|
||||
AVD_TEST_LOG: 1
|
||||
run: scripts/avd_test.sh ${{ matrix.version }}
|
||||
|
||||
cf_test:
|
||||
name: Test ${{ matrix.branch }} (${{ matrix.target }})
|
||||
- name: Upload logs on error
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "avd32-logs-${{ matrix.version }}"
|
||||
path: |
|
||||
kernel.log
|
||||
logcat.log
|
||||
|
||||
cf-test:
|
||||
name: Test ${{ matrix.device }}
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
env:
|
||||
CF_HOME: /home/runner/aosp_cf_phone
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- branch: "aosp-main"
|
||||
target: "aosp_cf_x86_64_phone-trunk_staging-userdebug"
|
||||
- branch: "aosp-android-latest-release"
|
||||
device: "aosp_cf_x86_64_only_phone"
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -179,7 +193,7 @@ jobs:
|
||||
- name: Setup Cuttlefish environment
|
||||
run: |
|
||||
scripts/cuttlefish.sh setup
|
||||
scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.target }}
|
||||
scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}
|
||||
|
||||
- name: Run Cuttlefish test
|
||||
timeout-minutes: 10
|
||||
@@ -189,7 +203,7 @@ jobs:
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "cvd-logs"
|
||||
name: "cvd-logs-${{ matrix.device }}"
|
||||
path: |
|
||||
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/logs
|
||||
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/cuttlefish_config.json
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,7 +13,8 @@ native/out
|
||||
# Android Studio / Gradle
|
||||
*.iml
|
||||
.gradle
|
||||
.idea
|
||||
.kotlin
|
||||
/local.properties
|
||||
/.idea
|
||||
/build
|
||||
/captures
|
||||
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -4,18 +4,12 @@
|
||||
[submodule "lz4"]
|
||||
path = native/src/external/lz4
|
||||
url = https://github.com/lz4/lz4.git
|
||||
[submodule "bzip2"]
|
||||
path = native/src/external/bzip2
|
||||
url = https://github.com/nemequ/bzip2.git
|
||||
[submodule "xz"]
|
||||
path = native/src/external/xz
|
||||
url = https://github.com/xz-mirror/xz.git
|
||||
[submodule "libcxx"]
|
||||
path = native/src/external/libcxx
|
||||
url = https://github.com/topjohnwu/libcxx.git
|
||||
[submodule "zlib"]
|
||||
path = native/src/external/zlib
|
||||
url = https://android.googlesource.com/platform/external/zlib
|
||||
[submodule "zopfli"]
|
||||
path = native/src/external/zopfli
|
||||
url = https://github.com/google/zopfli.git
|
||||
@@ -31,6 +25,3 @@
|
||||
[submodule "crt0"]
|
||||
path = native/src/external/crt0
|
||||
url = https://github.com/topjohnwu/crt0.git
|
||||
[submodule "termux-elf-cleaner"]
|
||||
path = tools/termux-elf-cleaner
|
||||
url = https://github.com/termux/termux-elf-cleaner.git
|
||||
|
||||
@@ -20,9 +20,9 @@ Some highlight features:
|
||||
|
||||
Click the icon below to download Magisk apk.
|
||||
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-27006)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-28104)
|
||||
|
||||
## Useful Links
|
||||
|
||||
|
||||
@@ -6,63 +6,53 @@ plugins {
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
}
|
||||
|
||||
setupAppCommon()
|
||||
setupMainApk()
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
useBuildCache = true
|
||||
mapDiagnosticLocations = true
|
||||
javacOptions {
|
||||
option("-Xmaxerrs", 1000)
|
||||
option("-Xmaxerrs", "1000")
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk"
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
versionName = Config.version
|
||||
versionCode = Config.versionCode
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
|
||||
debugSymbolLevel = "FULL"
|
||||
}
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":app:core"))
|
||||
coreLibraryDesugaring(libs.jdk.libs)
|
||||
|
||||
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
|
||||
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
|
||||
implementation("dev.rikka.rikkax.insets:insets:1.3.0")
|
||||
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2")
|
||||
implementation(libs.indeterminate.checkbox)
|
||||
implementation(libs.rikka.layoutinflater)
|
||||
implementation(libs.rikka.insets)
|
||||
implementation(libs.rikka.recyclerview)
|
||||
|
||||
val vNav = "2.7.7"
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:${vNav}")
|
||||
implementation(libs.navigation.fragment.ktx)
|
||||
implementation(libs.navigation.ui.ktx)
|
||||
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
implementation("androidx.transition:transition:1.5.1")
|
||||
implementation("androidx.fragment:fragment-ktx:1.8.2")
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
implementation(libs.constraintlayout)
|
||||
implementation(libs.swiperefreshlayout)
|
||||
implementation(libs.recyclerview)
|
||||
implementation(libs.transition)
|
||||
implementation(libs.fragment.ktx)
|
||||
implementation(libs.appcompat)
|
||||
implementation(libs.material)
|
||||
|
||||
// Make sure kapt runs with a proper kotlin-stdlib
|
||||
kapt(kotlin("stdlib"))
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.topjohnwu.magisk.arch
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.view.KeyEvent
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDirections
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.navOptions
|
||||
import com.topjohnwu.magisk.utils.AccessibilityUtils
|
||||
|
||||
abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Binding>() {
|
||||
|
||||
@@ -31,7 +34,17 @@ abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Bindin
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun navigate(directions: NavDirections, navigation: NavController, cr: ContentResolver) {
|
||||
if (AccessibilityUtils.isAnimationEnabled(cr)) {
|
||||
navigation.navigate(directions)
|
||||
} else {
|
||||
navigation.navigate(directions, navOptions {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun NavDirections.navigate() {
|
||||
navigation.navigate(this)
|
||||
navigate(this, navigation, contentResolver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package com.topjohnwu.magisk.dialog
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.topjohnwu.magisk.core.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.ktx.reboot
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.events.DialogBuilder
|
||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : DialogBuilder {
|
||||
@@ -27,9 +32,15 @@ class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : Dialo
|
||||
setCancelable(false)
|
||||
}
|
||||
dialog.activity.lifecycleScope.launch {
|
||||
MagiskInstaller.FixEnv {
|
||||
MagiskInstaller.FixEnv().exec { success ->
|
||||
dialog.dismiss()
|
||||
}.exec()
|
||||
context.toast(
|
||||
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed(5000) { reboot() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.topjohnwu.magisk.dialog
|
||||
|
||||
import android.content.Context
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
@@ -7,6 +8,8 @@ import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
|
||||
|
||||
@@ -17,14 +20,21 @@ class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog
|
||||
return if (str.length > 1000) str.substring(0, 1000) else str
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class Module(
|
||||
override val module: OnlineModule,
|
||||
override val autoLaunch: Boolean,
|
||||
override val notifyId: Int = Notifications.nextId()
|
||||
) : Subject.Module() {
|
||||
override fun pendingIntent(context: Context) = FlashFragment.installIntent(context, file)
|
||||
}
|
||||
|
||||
override fun build(dialog: MagiskDialog) {
|
||||
super.build(dialog)
|
||||
dialog.apply {
|
||||
|
||||
fun download(install: Boolean) {
|
||||
val module = Subject.Module(item, install)
|
||||
module.piCreator = FlashFragment::installIntent
|
||||
DownloadEngine.startWithActivity(activity, module)
|
||||
DownloadEngine.startWithActivity(activity, Module(item, install))
|
||||
}
|
||||
|
||||
val title = context.getString(R.string.repo_install_title,
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package com.topjohnwu.magisk.dialog
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.topjohnwu.magisk.arch.NavigationActivity
|
||||
import com.topjohnwu.magisk.arch.UIActivity
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.events.DialogBuilder
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class UninstallDialog : DialogBuilder {
|
||||
|
||||
@@ -19,7 +21,7 @@ class UninstallDialog : DialogBuilder {
|
||||
setMessage(R.string.uninstall_magisk_msg)
|
||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||
text = R.string.restore_img
|
||||
onClick { restore(dialog.context) }
|
||||
onClick { restore(dialog.activity) }
|
||||
}
|
||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||
text = R.string.complete_uninstall
|
||||
@@ -29,18 +31,20 @@ class UninstallDialog : DialogBuilder {
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun restore(context: Context) {
|
||||
val dialog = ProgressDialog(context).apply {
|
||||
setMessage(context.getString(R.string.restore_img_msg))
|
||||
private fun restore(activity: UIActivity<*>) {
|
||||
val dialog = ProgressDialog(activity).apply {
|
||||
setMessage(activity.getString(R.string.restore_img_msg))
|
||||
show()
|
||||
}
|
||||
|
||||
Shell.cmd("restore_imgs").submit { result ->
|
||||
dialog.dismiss()
|
||||
if (result.isSuccess) {
|
||||
context.toast(R.string.restore_done, Toast.LENGTH_SHORT)
|
||||
} else {
|
||||
context.toast(R.string.restore_fail, Toast.LENGTH_LONG)
|
||||
activity.lifecycleScope.launch {
|
||||
MagiskInstaller.Restore().exec { success ->
|
||||
dialog.dismiss()
|
||||
if (success) {
|
||||
activity.toast(R.string.restore_done, Toast.LENGTH_SHORT)
|
||||
} else {
|
||||
activity.toast(R.string.restore_fail, Toast.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>(), MenuProvider {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
defaultOrientation = activity?.requestedOrientation ?: -1
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
if (savedInstanceState == null) {
|
||||
viewModel.startFlashing()
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
|
||||
import com.topjohnwu.magisk.core.R as CoreR
|
||||
import androidx.navigation.findNavController
|
||||
import com.topjohnwu.magisk.arch.NavigationActivity
|
||||
|
||||
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
|
||||
|
||||
@@ -68,7 +70,13 @@ class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
|
||||
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_settings ->
|
||||
HomeFragmentDirections.actionHomeFragmentToSettingsFragment().navigate()
|
||||
activity?.let {
|
||||
NavigationActivity.navigate(
|
||||
HomeFragmentDirections.actionHomeFragmentToSettingsFragment(),
|
||||
it.findNavController(R.id.main_nav_host),
|
||||
it.contentResolver,
|
||||
)
|
||||
}
|
||||
R.id.action_reboot -> activity?.let { RebootMenu.inflate(it).show() }
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ object RebootMenu {
|
||||
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) {
|
||||
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
|
||||
}
|
||||
if (Const.Version.isCanary()) {
|
||||
if (Const.Version.atLeast_28_0()) {
|
||||
menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2
|
||||
} else {
|
||||
menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.HorizontalScrollView
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import com.topjohnwu.magisk.R
|
||||
@@ -12,6 +13,7 @@ import com.topjohnwu.magisk.arch.BaseFragment
|
||||
import com.topjohnwu.magisk.arch.viewModel
|
||||
import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding
|
||||
import com.topjohnwu.magisk.ui.MainActivity
|
||||
import com.topjohnwu.magisk.utils.AccessibilityUtils
|
||||
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
||||
import rikka.recyclerview.addEdgeSpacing
|
||||
import rikka.recyclerview.addItemSpacing
|
||||
@@ -56,6 +58,11 @@ class LogFragment : BaseFragment<FragmentLogMd2Binding>(), MenuProvider {
|
||||
addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)
|
||||
fixEdgeEffect()
|
||||
}
|
||||
|
||||
if (!AccessibilityUtils.isAnimationEnabled(requireContext().contentResolver)) {
|
||||
val scrollView = view.findViewById<HorizontalScrollView>(R.id.log_scroll_magisk)
|
||||
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseFragment
|
||||
import com.topjohnwu.magisk.arch.viewModel
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.databinding.FragmentActionMd2Binding
|
||||
import com.topjohnwu.magisk.core.R as CoreR
|
||||
|
||||
class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
|
||||
|
||||
override val layoutRes = R.layout.fragment_action_md2
|
||||
override val viewModel by viewModel<ActionViewModel>()
|
||||
override val snackbarView: View get() = binding.snackbarContainer
|
||||
|
||||
private var defaultOrientation = -1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel.args = ActionFragmentArgs.fromBundle(requireArguments())
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
activity?.setTitle(viewModel.args.name)
|
||||
binding.closeBtn.setOnClickListener {
|
||||
activity?.onBackPressed()
|
||||
}
|
||||
|
||||
viewModel.state.observe(this) {
|
||||
if (it != ActionViewModel.State.RUNNING) {
|
||||
binding.closeBtn.apply {
|
||||
if (!this.isVisible) this.show()
|
||||
if (!this.isFocused) this.requestFocus()
|
||||
}
|
||||
}
|
||||
if (it != ActionViewModel.State.SUCCESS) return@observe
|
||||
view?.viewTreeObserver?.addOnWindowFocusChangeListener(
|
||||
object : ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
if (hasFocus) return
|
||||
view?.viewTreeObserver?.removeOnWindowFocusChangeListener(this)
|
||||
view?.context?.apply {
|
||||
toast(
|
||||
getString(CoreR.string.done_action, viewModel.args.name),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
}
|
||||
viewModel.back()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_flash, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||
return viewModel.onMenuItemClicked(item)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
defaultOrientation = activity?.requestedOrientation ?: -1
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
if (savedInstanceState == null) {
|
||||
viewModel.startRunAction()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
override fun onDestroyView() {
|
||||
if (defaultOrientation != -1) {
|
||||
activity?.requestedOrientation = defaultOrientation
|
||||
}
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onKeyEvent(event: KeyEvent): Boolean {
|
||||
return when (event.keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
if (viewModel.state.value == ActionViewModel.State.RUNNING) return true
|
||||
return super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onPreBind(binding: FragmentActionMd2Binding) = Unit
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.view.MenuItem
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.ktx.synchronized
|
||||
import com.topjohnwu.magisk.core.ktx.timeFormatStandard
|
||||
import com.topjohnwu.magisk.core.ktx.toTime
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.ui.flash.ConsoleItem
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
class ActionViewModel : BaseViewModel() {
|
||||
|
||||
enum class State {
|
||||
RUNNING, SUCCESS, FAILED
|
||||
}
|
||||
|
||||
private val _state = MutableLiveData(State.RUNNING)
|
||||
val state: LiveData<State> get() = _state
|
||||
|
||||
val items = ObservableArrayList<ConsoleItem>()
|
||||
lateinit var args: ActionFragmentArgs
|
||||
|
||||
private val logItems = mutableListOf<String>().synchronized()
|
||||
private val outItems = object : CallbackList<String>() {
|
||||
override fun onAddElement(e: String?) {
|
||||
e ?: return
|
||||
items.add(ConsoleItem(e))
|
||||
logItems.add(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun startRunAction() = viewModelScope.launch {
|
||||
onResult(withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Shell.cmd("run_action \'${args.id}\'")
|
||||
.to(outItems, logItems)
|
||||
.exec().isSuccess
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun onResult(success: Boolean) {
|
||||
_state.value = if (success) State.SUCCESS else State.FAILED
|
||||
}
|
||||
|
||||
fun onMenuItemClicked(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_save -> savePressed()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun savePressed() = withExternalRW {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val name = "%s_action_log_%s.log".format(
|
||||
args.name,
|
||||
System.currentTimeMillis().toTime(timeFormatStandard)
|
||||
)
|
||||
val file = MediaStoreUtils.getFile(name)
|
||||
file.uri.outputStream().bufferedWriter().use { writer ->
|
||||
synchronized(logItems) {
|
||||
logItems.forEach {
|
||||
writer.write(it)
|
||||
writer.newLine()
|
||||
}
|
||||
}
|
||||
}
|
||||
SnackbarEvent(file.toString()).publish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ class LocalModuleRvItem(
|
||||
override val layoutRes = R.layout.item_module_md2
|
||||
|
||||
val showNotice: Boolean
|
||||
val showAction: Boolean
|
||||
val noticeText: TextHolder
|
||||
|
||||
init {
|
||||
@@ -35,6 +36,7 @@ class LocalModuleRvItem(
|
||||
showNotice = zygiskUnloaded ||
|
||||
(Info.isZygiskEnabled && isRiru) ||
|
||||
(!Info.isZygiskEnabled && isZygisk)
|
||||
showAction = item.hasAction && !showNotice
|
||||
noticeText =
|
||||
when {
|
||||
zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText()
|
||||
|
||||
@@ -4,8 +4,10 @@ import android.net.Uri
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.MainDirections
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
||||
@@ -96,6 +98,10 @@ class ModuleViewModel : AsyncLoadViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun runAction(id: String, name: String) {
|
||||
MainDirections.actionActionFragment(id, name).navigate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val uri = MutableLiveData<Uri?>()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.R
|
||||
@@ -92,7 +93,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
||||
DownloadPath -> withExternalRW(doAction)
|
||||
UpdateChecker -> withPostNotificationPermission(doAction)
|
||||
Authentication -> AuthEvent(doAction).publish()
|
||||
Hide, Restore -> withInstallPermission(doAction)
|
||||
AutomaticResponse -> if (Config.suAuth) AuthEvent(doAction).publish() else doAction()
|
||||
else -> doAction()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ class SuperuserViewModel(
|
||||
this@SuperuserViewModel, policy,
|
||||
info.packageName,
|
||||
info.sharedUserId != null,
|
||||
info.applicationInfo.loadIcon(pm),
|
||||
info.applicationInfo.getLabel(pm)
|
||||
info.applicationInfo?.loadIcon(pm) ?: pm.defaultActivityIcon,
|
||||
info.applicationInfo?.getLabel(pm) ?: info.packageName
|
||||
)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
|
||||
@@ -111,7 +111,7 @@ class SuRequestViewModel(
|
||||
// shared UID. We have no way to know where this request comes from.
|
||||
icon = pm.defaultActivityIcon
|
||||
title = "[SharedUID] ${info.sharedUserId}"
|
||||
packageName = info.sharedUserId
|
||||
packageName = info.sharedUserId.toString()
|
||||
} else {
|
||||
val prefix = if (info.sharedUserId == null) "" else "[SharedUID] "
|
||||
icon = app.loadIcon(pm)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.provider.Settings
|
||||
|
||||
class AccessibilityUtils {
|
||||
companion object {
|
||||
fun isAnimationEnabled(cr: ContentResolver): Boolean {
|
||||
return !(Settings.Global.getFloat(cr, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) == 0.0f
|
||||
&& Settings.Global.getFloat(cr, Settings.Global.TRANSITION_ANIMATION_SCALE, 1.0f) == 0.0f
|
||||
&& Settings.Global.getFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE, 1.0f) == 0.0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
5
app/apk/src/main/res/drawable/ic_action_md2.xml
Normal file
5
app/apk/src/main/res/drawable/ic_action_md2.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M8,5v14l11,-7z"/>
|
||||
|
||||
</vector>
|
||||
68
app/apk/src/main/res/layout/fragment_action_md2.xml
Normal file
68
app/apk/src/main/res/layout/fragment_action_md2.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.module.ActionViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/internal_action_bar_size"
|
||||
app:layout_fitsSystemWindowsInsets="top"
|
||||
tools:layout_marginTop="@dimen/internal_action_bar_size">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/flash_content"
|
||||
scrollToLast="@{true}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
app:fitsSystemWindowsInsets="start|end|bottom"
|
||||
app:items="@{viewModel.items}"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:listitem="@layout/item_console_md2" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/close_btn"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l1"
|
||||
android:clickable="true"
|
||||
android:enabled="true"
|
||||
android:focusable="true"
|
||||
android:text="@string/close"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="?colorOnPrimary"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="?colorPrimary"
|
||||
app:icon="@drawable/ic_close_md2"
|
||||
app:iconTint="?colorOnPrimary"
|
||||
app:layout_fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/snackbar_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:fitsSystemWindowsInsets="top|bottom" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
@@ -16,6 +16,7 @@
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/log_scroll_magisk"
|
||||
gone="@{viewModel.loading}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -189,12 +189,32 @@
|
||||
android:textColor="?colorError"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/module_remove"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottom_bar_barrier"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/module_config"
|
||||
app:layout_constraintTop_toTopOf="@+id/module_remove"
|
||||
tools:lines="2"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/module_config"
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
goneUnless="@{item.showAction}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:enabled="@{item.enabled}"
|
||||
android:focusable="true"
|
||||
android:onClick="@{() -> viewModel.runAction(item.item.id, item.item.name)}"
|
||||
android:text="@string/module_action"
|
||||
android:textAllCaps="false"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_action_md2"
|
||||
app:iconGravity="textStart"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/module_remove"
|
||||
app:layout_constraintTop_toTopOf="@+id/module_remove"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:srcCompat="@drawable/ic_download_md2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
@@ -53,6 +53,21 @@
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/actionFragment"
|
||||
android:name="com.topjohnwu.magisk.ui.module.ActionFragment"
|
||||
android:label="ActionFragment"
|
||||
tools:layout="@layout/fragment_action_md2" >
|
||||
|
||||
<argument
|
||||
android:name="id"
|
||||
app:argType="string" />
|
||||
|
||||
<argument
|
||||
android:name="name"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/installFragment"
|
||||
android:name="com.topjohnwu.magisk.ui.install.InstallFragment"
|
||||
@@ -152,4 +167,12 @@
|
||||
app:popEnterAnim="@anim/fragment_enter_pop"
|
||||
app:popExitAnim="@anim/fragment_exit_pop" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_actionFragment"
|
||||
app:destination="@id/actionFragment"
|
||||
app:enterAnim="@anim/fragment_enter"
|
||||
app:exitAnim="@anim/fragment_exit"
|
||||
app:popEnterAnim="@anim/fragment_enter_pop"
|
||||
app:popExitAnim="@anim/fragment_exit_pop" />
|
||||
|
||||
</navigation>
|
||||
|
||||
@@ -19,6 +19,7 @@ android {
|
||||
buildConfigField("int", "APP_VERSION_CODE", "${Config.versionCode}")
|
||||
buildConfigField("String", "APP_VERSION_NAME", "\"${Config.version}\"")
|
||||
buildConfigField("int", "STUB_VERSION", Config.stubVersion)
|
||||
consumerProguardFile("proguard-rules.pro")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
@@ -30,39 +31,39 @@ android {
|
||||
dependencies {
|
||||
api(project(":app:shared"))
|
||||
|
||||
api("com.jakewharton.timber:timber:5.0.1")
|
||||
api("io.noties.markwon:core:4.6.2")
|
||||
implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1")
|
||||
implementation("org.apache.commons:commons-compress:1.26.2")
|
||||
api(libs.timber)
|
||||
api(libs.markwon.core)
|
||||
implementation(libs.bcpkix)
|
||||
implementation(libs.commons.compress)
|
||||
|
||||
val vLibsu = "6.0.0"
|
||||
api("com.github.topjohnwu.libsu:core:${vLibsu}")
|
||||
api("com.github.topjohnwu.libsu:service:${vLibsu}")
|
||||
api("com.github.topjohnwu.libsu:nio:${vLibsu}")
|
||||
api(libs.libsu.core)
|
||||
api(libs.libsu.service)
|
||||
api(libs.libsu.nio)
|
||||
|
||||
val vRetrofit = "2.11.0"
|
||||
implementation("com.squareup.retrofit2:retrofit:${vRetrofit}")
|
||||
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
|
||||
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
|
||||
implementation(libs.retrofit)
|
||||
implementation(libs.retrofit.moshi)
|
||||
implementation(libs.retrofit.scalars)
|
||||
|
||||
val vOkHttp = "4.12.0"
|
||||
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.okhttp.logging)
|
||||
implementation(libs.okhttp.dnsoverhttps)
|
||||
|
||||
val vMoshi = "1.15.1"
|
||||
implementation("com.squareup.moshi:moshi:${vMoshi}")
|
||||
ksp("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
|
||||
implementation(libs.moshi)
|
||||
ksp(libs.moshi.codegen)
|
||||
|
||||
val vRoom = "2.6.1"
|
||||
implementation("androidx.room:room-runtime:${vRoom}")
|
||||
implementation("androidx.room:room-ktx:${vRoom}")
|
||||
ksp("androidx.room:room-compiler:${vRoom}")
|
||||
implementation(libs.room.runtime)
|
||||
implementation(libs.room.ktx)
|
||||
ksp(libs.room.compiler)
|
||||
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.core:core-ktx:1.13.1")
|
||||
implementation("androidx.activity:activity:1.9.1")
|
||||
implementation("androidx.collection:collection-ktx:1.4.2")
|
||||
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
|
||||
implementation("androidx.lifecycle:lifecycle-process:2.8.4")
|
||||
implementation(libs.core.splashscreen)
|
||||
implementation(libs.core.ktx)
|
||||
implementation(libs.activity)
|
||||
implementation(libs.collection.ktx)
|
||||
implementation(libs.profileinstaller)
|
||||
|
||||
// We also implement all our tests in this module.
|
||||
// However, we don't want to bundle test dependencies.
|
||||
// That's why we make it compileOnly.
|
||||
compileOnly(libs.test.junit)
|
||||
compileOnly(libs.test.uiautomator)
|
||||
}
|
||||
|
||||
@@ -22,42 +22,19 @@
|
||||
int mActivityHandlesConfigFlags;
|
||||
}
|
||||
|
||||
# main
|
||||
-keep,allowoptimization public class com.topjohnwu.magisk.signing.SignBoot {
|
||||
public static void main(java.lang.String[]);
|
||||
}
|
||||
|
||||
# Strip Timber verbose and debug logging
|
||||
-assumenosideeffects class timber.log.Timber$Tree {
|
||||
public void v(**);
|
||||
public void d(**);
|
||||
}
|
||||
|
||||
# https://github.com/square/retrofit/issues/3751#issuecomment-1192043644
|
||||
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
|
||||
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
|
||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
||||
|
||||
# With R8 full mode generic signatures are stripped for classes that are not
|
||||
# kept. Suspend functions are wrapped in continuations where the type argument
|
||||
# is used.
|
||||
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
|
||||
|
||||
|
||||
# Excessive obfuscation
|
||||
-repackageclasses 'a'
|
||||
-flattenpackagehierarchy
|
||||
-allowaccessmodification
|
||||
|
||||
-obfuscationdictionary ../dict.txt
|
||||
-classobfuscationdictionary ../dict.txt
|
||||
-packageobfuscationdictionary ../dict.txt
|
||||
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
-dontwarn org.commonmark.ext.gfm.strikethrough.Strikethrough
|
||||
-dontwarn org.conscrypt.Conscrypt*
|
||||
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||
-dontwarn org.junit.**
|
||||
@@ -16,7 +16,6 @@ import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.base.UntrackedActivity
|
||||
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||
import com.topjohnwu.magisk.core.utils.NetworkObserver
|
||||
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.core.utils.ShellInit
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@@ -40,6 +39,7 @@ object AppContext : ContextWrapper(null),
|
||||
|
||||
private var ref = WeakReference<Activity>(null)
|
||||
private lateinit var application: Application
|
||||
private lateinit var networkObserver: NetworkObserver
|
||||
|
||||
init {
|
||||
// Always log full stack trace with Timber
|
||||
@@ -56,6 +56,10 @@ object AppContext : ContextWrapper(null),
|
||||
LocaleSetting.instance.updateResource(resources)
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
networkObserver.postCurrentState()
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
if (activity is UntrackedActivity) return
|
||||
ref = WeakReference(activity)
|
||||
@@ -102,8 +106,7 @@ object AppContext : ContextWrapper(null),
|
||||
val lm = getSystemService(LocaleManager::class.java)
|
||||
lm.overrideLocaleConfig = LocaleSetting.localeConfig
|
||||
}
|
||||
ProcessLifecycle.init(this)
|
||||
NetworkObserver.init(this)
|
||||
networkObserver = NetworkObserver.init(this)
|
||||
if (!BuildConfig.DEBUG && !isRunningAsStub) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
ProfileInstaller.writeProfile(this@AppContext)
|
||||
@@ -120,7 +123,6 @@ object AppContext : ContextWrapper(null),
|
||||
}
|
||||
|
||||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
||||
override fun onActivityStarted(activity: Activity) {}
|
||||
override fun onActivityStopped(activity: Activity) {}
|
||||
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
||||
override fun onActivityDestroyed(activity: Activity) {}
|
||||
|
||||
@@ -83,7 +83,7 @@ object Config : PreferenceConfig, DBConfig {
|
||||
const val SU_AUTO_ALLOW = 2
|
||||
|
||||
// su timeout
|
||||
val TIMEOUT_LIST = intArrayOf(0, -1, 10, 20, 30, 60)
|
||||
val TIMEOUT_LIST = longArrayOf(0, -1, 10, 20, 30, 60)
|
||||
}
|
||||
|
||||
private val defaultChannel =
|
||||
|
||||
@@ -14,7 +14,7 @@ object Const {
|
||||
else Build.SUPPORTED_32_BIT_ABIS.firstOrNull()
|
||||
|
||||
// Paths
|
||||
const val MAGISK_PATH = "/data/adb/modules"
|
||||
const val MODULE_PATH = "/data/adb/modules"
|
||||
const val TMPDIR = "/dev/tmp"
|
||||
const val MAGISK_LOG = "/cache/magisk.log"
|
||||
|
||||
@@ -28,6 +28,7 @@ object Const {
|
||||
|
||||
fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary()
|
||||
fun atLeast_25_0() = Info.env.versionCode >= 25000 || isCanary()
|
||||
fun atLeast_28_0() = Info.env.versionCode >= 28000 || isCanary()
|
||||
fun isCanary() = isCanary(Info.env.versionCode)
|
||||
|
||||
fun isCanary(ver: Int) = ver > 0 && ver % 100 != 0
|
||||
|
||||
@@ -47,7 +47,6 @@ object Info {
|
||||
private set
|
||||
private var crypto = ""
|
||||
|
||||
var hasGMS = true
|
||||
val isEmulator =
|
||||
Build.DEVICE.contains("vsoc")
|
||||
|| getProperty("ro.kernel.qemu", "0") == "1"
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.core.content.getSystemService
|
||||
import com.topjohnwu.magisk.core.base.BaseJobService
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.DownloadSession
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -25,7 +26,7 @@ class JobService : BaseJobService() {
|
||||
@TargetApi(value = 34)
|
||||
inner class Session(
|
||||
private var params: JobParameters
|
||||
) : DownloadEngine.Session {
|
||||
) : DownloadSession {
|
||||
|
||||
override val context get() = this@JobService
|
||||
val engine = DownloadEngine(this)
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core
|
||||
import android.os.Bundle
|
||||
import com.topjohnwu.magisk.core.base.BaseProvider
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import com.topjohnwu.magisk.core.su.TestHandler
|
||||
|
||||
class Provider : BaseProvider() {
|
||||
|
||||
@@ -13,7 +12,7 @@ class Provider : BaseProvider() {
|
||||
SuCallbackHandler.run(context!!, method, extras)
|
||||
Bundle.EMPTY
|
||||
}
|
||||
else -> TestHandler.run(method)
|
||||
else -> Bundle.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.DownloadSession
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
|
||||
class Service : BaseService(), DownloadEngine.Session {
|
||||
class Service : BaseService(), DownloadSession {
|
||||
|
||||
private var mEngine: DownloadEngine? = null
|
||||
override val context get() = this
|
||||
|
||||
@@ -7,9 +7,13 @@ import kotlinx.coroutines.withContext
|
||||
|
||||
open class MagiskDB {
|
||||
|
||||
suspend fun <R> exec(
|
||||
class Literal(
|
||||
val str: String
|
||||
)
|
||||
|
||||
suspend inline fun <R> exec(
|
||||
query: String,
|
||||
mapper: suspend (Map<String, String>) -> R
|
||||
crossinline mapper: (Map<String, String>) -> R
|
||||
): List<R> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val out = Shell.cmd("magisk --sqlite '$query'").await().out
|
||||
@@ -18,13 +22,15 @@ open class MagiskDB {
|
||||
.map { it.split("=", limit = 2) }
|
||||
.filter { it.size == 2 }
|
||||
.associate { it[0] to it[1] }
|
||||
.let { mapper(it) }
|
||||
.let(mapper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun exec(query: String) {
|
||||
exec(query) {}
|
||||
suspend fun exec(query: String) {
|
||||
withContext(Dispatchers.IO) {
|
||||
Shell.cmd("magisk --sqlite '$query'").await()
|
||||
}
|
||||
}
|
||||
|
||||
fun Map<String, Any>.toQuery(): String {
|
||||
@@ -33,6 +39,7 @@ open class MagiskDB {
|
||||
when (it) {
|
||||
is Boolean -> if (it) "1" else "0"
|
||||
is Number -> it.toString()
|
||||
is Literal -> it.str
|
||||
else -> "\"$it\""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,24 +3,24 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val SELECT_QUERY = "SELECT (until - strftime(\"%s\", \"now\")) AS remain, *"
|
||||
|
||||
class PolicyDao : MagiskDB() {
|
||||
|
||||
suspend fun deleteOutdated() {
|
||||
val nowSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
|
||||
val query = "DELETE FROM ${Table.POLICY} WHERE " +
|
||||
"(until > 0 AND until < $nowSeconds) OR until < 0"
|
||||
"(until > 0 AND until < strftime(\"%s\", \"now\")) OR until < 0"
|
||||
exec(query)
|
||||
}
|
||||
|
||||
suspend fun delete(uid: Int) {
|
||||
val query = "DELETE FROM ${Table.POLICY} WHERE uid == $uid"
|
||||
val query = "DELETE FROM ${Table.POLICY} WHERE uid=$uid"
|
||||
exec(query)
|
||||
}
|
||||
|
||||
suspend fun fetch(uid: Int): SuPolicy? {
|
||||
val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT = 1"
|
||||
val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid=$uid LIMIT 1"
|
||||
return exec(query, ::toPolicy).firstOrNull()
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class PolicyDao : MagiskDB() {
|
||||
}
|
||||
|
||||
suspend fun fetchAll(): List<SuPolicy> {
|
||||
val query = "SELECT * FROM ${Table.POLICY} WHERE uid/100000 == ${Const.USER_ID}"
|
||||
val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid/100000=${Const.USER_ID}"
|
||||
return exec(query, ::toPolicy).filterNotNull()
|
||||
}
|
||||
|
||||
@@ -43,8 +43,15 @@ class PolicyDao : MagiskDB() {
|
||||
val uid = map["uid"]?.toInt() ?: return null
|
||||
val policy = SuPolicy(uid)
|
||||
|
||||
map["until"]?.toLong()?.let { until ->
|
||||
if (until <= 0) {
|
||||
policy.remain = until
|
||||
} else {
|
||||
map["remain"]?.toLong()?.let { policy.remain = it }
|
||||
}
|
||||
}
|
||||
|
||||
map["policy"]?.toInt()?.let { policy.policy = it }
|
||||
map["until"]?.toLong()?.let { policy.until = it }
|
||||
map["logging"]?.toInt()?.let { policy.logging = it != 0 }
|
||||
map["notification"]?.toInt()?.let { policy.notification = it != 0 }
|
||||
return policy
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
||||
class SettingsDao : MagiskDB() {
|
||||
|
||||
suspend fun delete(key: String) {
|
||||
val query = "DELETE FROM ${Table.SETTINGS} WHERE key == \"$key\""
|
||||
val query = "DELETE FROM ${Table.SETTINGS} WHERE key=\"$key\""
|
||||
exec(query)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class SettingsDao : MagiskDB() {
|
||||
}
|
||||
|
||||
suspend fun fetch(key: String, default: Int = -1): Int {
|
||||
val query = "SELECT value FROM ${Table.SETTINGS} WHERE key == \"$key\" LIMIT 1"
|
||||
val query = "SELECT value FROM ${Table.SETTINGS} WHERE key=\"$key\" LIMIT 1"
|
||||
return exec(query) { it["value"]?.toInt() }.firstOrNull() ?: default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
||||
class StringDao : MagiskDB() {
|
||||
|
||||
suspend fun delete(key: String) {
|
||||
val query = "DELETE FROM ${Table.STRINGS} WHERE key == \"$key\""
|
||||
val query = "DELETE FROM ${Table.STRINGS} WHERE key=\"$key\""
|
||||
exec(query)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class StringDao : MagiskDB() {
|
||||
}
|
||||
|
||||
suspend fun fetch(key: String, default: String = ""): String {
|
||||
val query = "SELECT value FROM ${Table.STRINGS} WHERE key == \"$key\" LIMIT 1"
|
||||
val query = "SELECT value FROM ${Table.STRINGS} WHERE key=\"$key\" LIMIT 1"
|
||||
return exec(query) { it["value"] }.firstOrNull() ?: default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.squareup.moshi.Moshi
|
||||
import com.topjohnwu.magisk.ProviderInstaller
|
||||
import com.topjohnwu.magisk.core.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||
import okhttp3.Cache
|
||||
import okhttp3.ConnectionSpec
|
||||
@@ -72,9 +71,7 @@ fun createOkHttpClient(context: Context): OkHttpClient {
|
||||
chain.proceed(request.build())
|
||||
}
|
||||
|
||||
if (!ProviderInstaller.install(context)) {
|
||||
Info.hasGMS = false
|
||||
}
|
||||
ProviderInstaller.install(context)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.app.PendingIntent
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobScheduler
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -16,7 +15,6 @@ import androidx.collection.isNotEmpty
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.JobService
|
||||
@@ -25,18 +23,8 @@ import com.topjohnwu.magisk.core.base.IActivityExtension
|
||||
import com.topjohnwu.magisk.core.cmp
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.forEach
|
||||
import com.topjohnwu.magisk.core.ktx.set
|
||||
import com.topjohnwu.magisk.core.ktx.withStreams
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -44,13 +32,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.ResponseBody
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* This class drives the execution of file downloads and notification management.
|
||||
@@ -69,16 +51,7 @@ import java.util.zip.ZipOutputStream
|
||||
* For API 23 - 33, we use a foreground service as a session.
|
||||
* For API 34 and higher, we use user-initiated job services as a session.
|
||||
*/
|
||||
class DownloadEngine(
|
||||
private val session: Session
|
||||
) {
|
||||
|
||||
interface Session {
|
||||
val context: Context
|
||||
|
||||
fun attachNotification(id: Int, builder: Notification.Builder)
|
||||
fun onDownloadComplete()
|
||||
}
|
||||
class DownloadEngine(session: DownloadSession) : DownloadSession by session, DownloadNotifier {
|
||||
|
||||
companion object {
|
||||
const val ACTION = "com.topjohnwu.magisk.DOWNLOAD"
|
||||
@@ -99,33 +72,35 @@ class DownloadEngine(
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIntent(context: Context, subject: Subject) =
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
context.intent<com.topjohnwu.magisk.core.Receiver>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
} else {
|
||||
context.intent<com.topjohnwu.magisk.core.Service>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
}
|
||||
private fun createBroadcastIntent(context: Context, subject: Subject) =
|
||||
context.intent<com.topjohnwu.magisk.core.Receiver>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
|
||||
private fun createServiceIntent(context: Context, subject: Subject) =
|
||||
context.intent<com.topjohnwu.magisk.core.Service>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
|
||||
val flag = PendingIntent.FLAG_IMMUTABLE or
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or
|
||||
PendingIntent.FLAG_ONE_SHOT
|
||||
val intent = createIntent(context, subject)
|
||||
return if (Build.VERSION.SDK_INT >= 34) {
|
||||
// On API 34+, download tasks are handled with a user-initiated job.
|
||||
// However, there is no way to schedule a new job directly with a pending intent.
|
||||
// As a workaround, we send the subject to a broadcast receiver and have it
|
||||
// schedule the job for us.
|
||||
val intent = createBroadcastIntent(context, subject)
|
||||
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)
|
||||
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
|
||||
val intent = createServiceIntent(context, subject)
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +115,7 @@ class DownloadEngine(
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun start(context: Context, subject: Subject) {
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
val scheduler = context.getSystemService<JobScheduler>()!!
|
||||
@@ -152,24 +128,29 @@ class DownloadEngine(
|
||||
.setTransientExtras(extras)
|
||||
.build()
|
||||
scheduler.schedule(info)
|
||||
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.startForegroundService(createIntent(context, subject))
|
||||
} else {
|
||||
context.startService(createIntent(context, subject))
|
||||
val intent = createServiceIntent(context, subject)
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val notifications = SparseArrayCompat<Notification.Builder>()
|
||||
private var attachedId = -1
|
||||
private val job = Job()
|
||||
private val processor = DownloadProcessor(this)
|
||||
private val network get() = ServiceLocator.networkService
|
||||
|
||||
fun download(subject: Subject) {
|
||||
notifyUpdate(subject.notifyId)
|
||||
CoroutineScope(job + Dispatchers.IO).launch {
|
||||
try {
|
||||
val stream = network.fetchFile(subject.url).toProgressStream(subject)
|
||||
when (subject) {
|
||||
is Subject.App -> handleApp(stream, subject)
|
||||
is Subject.Module -> handleModule(stream, subject.file)
|
||||
else -> stream.copyAndClose(subject.file.outputStream())
|
||||
}
|
||||
processor.handle(stream, subject)
|
||||
val activity = AppContext.foregroundActivity
|
||||
if (activity != null && subject.autoLaunch) {
|
||||
notifyRemove(subject.notifyId)
|
||||
@@ -187,16 +168,13 @@ class DownloadEngine(
|
||||
@Synchronized
|
||||
fun reattach() {
|
||||
val builder = notifications[attachedId] ?: return
|
||||
session.attachNotification(attachedId, builder)
|
||||
attachNotification(attachedId, builder)
|
||||
}
|
||||
|
||||
private val notifications = SparseArrayCompat<Notification.Builder>()
|
||||
private var attachedId = -1
|
||||
|
||||
private val job = Job()
|
||||
|
||||
private val context get() = session.context
|
||||
private val network get() = ServiceLocator.networkService
|
||||
private fun attach(id: Int, notification: Notification.Builder) {
|
||||
attachedId = id
|
||||
attachNotification(id, notification)
|
||||
}
|
||||
|
||||
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
|
||||
val notification = notifyRemove(id)?.also(editor) ?: return -1
|
||||
@@ -223,19 +201,14 @@ class DownloadEngine(
|
||||
subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) }
|
||||
}
|
||||
|
||||
private fun attachNotification(id: Int, notification: Notification.Builder) {
|
||||
attachedId = id
|
||||
session.attachNotification(id, notification)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
|
||||
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {
|
||||
val notification = (notifications[id] ?: Notifications.startProgress("").also {
|
||||
notifications[id] = it
|
||||
}).apply(editor)
|
||||
|
||||
if (attachedId < 0)
|
||||
attachNotification(id, notification)
|
||||
attach(id, notification)
|
||||
else
|
||||
Notifications.mgr.notify(id, notification.build())
|
||||
}
|
||||
@@ -255,11 +228,11 @@ class DownloadEngine(
|
||||
// There are still remaining notifications, pick one and attach to the session
|
||||
val anotherId = notifications.keyAt(0)
|
||||
val notification = notifications.valueAt(0)
|
||||
attachNotification(anotherId, notification)
|
||||
attach(anotherId, notification)
|
||||
} else {
|
||||
// No more notifications left, terminate the session
|
||||
attachedId = -1
|
||||
session.onDownloadComplete()
|
||||
onDownloadComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,90 +241,6 @@ class DownloadEngine(
|
||||
return n
|
||||
}
|
||||
|
||||
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
val external = subject.file.outputStream()
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val updateApk = StubApk.update(context)
|
||||
try {
|
||||
// Download full APK to stub update path
|
||||
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
|
||||
|
||||
// Also upgrade stub
|
||||
notifyUpdate(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(context.getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
// Extract stub
|
||||
val zf = ZipFile(updateApk)
|
||||
val apk = context.cachedFile("stub.apk")
|
||||
apk.delete()
|
||||
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
||||
zf.close()
|
||||
|
||||
// Patch and install
|
||||
subject.intent = AppMigration.upgradeStub(context, apk)
|
||||
?: throw IOException("HideAPK patch error")
|
||||
apk.delete()
|
||||
} catch (e: Exception) {
|
||||
// If any error occurred, do not let stub load the new APK
|
||||
updateApk.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val session = APKInstall.startSession(context)
|
||||
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleModule(src: InputStream, file: Uri) {
|
||||
val input = ZipInputStream(src)
|
||||
val output = ZipOutputStream(file.outputStream())
|
||||
|
||||
withStreams(input, output) { zin, zout ->
|
||||
zout.putNextEntry(ZipEntry("META-INF/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
|
||||
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
|
||||
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray())
|
||||
|
||||
zin.forEach { entry ->
|
||||
val path = entry.name
|
||||
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
|
||||
zout.putNextEntry(ZipEntry(path))
|
||||
if (!entry.isDirectory) {
|
||||
zin.copyAll(zout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TeeOutputStream(
|
||||
private val o1: OutputStream,
|
||||
private val o2: OutputStream
|
||||
) : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
o1.write(b)
|
||||
o2.write(b)
|
||||
}
|
||||
override fun write(b: ByteArray?, off: Int, len: Int) {
|
||||
o1.write(b, off, len)
|
||||
o2.write(b, off, len)
|
||||
}
|
||||
override fun close() {
|
||||
o1.close()
|
||||
o2.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
|
||||
val max = contentLength()
|
||||
val total = max.toFloat() / 1048576
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.net.Uri
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.withInOut
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class DownloadProcessor(notifier: DownloadNotifier) : DownloadNotifier by notifier {
|
||||
|
||||
suspend fun handle(stream: InputStream, subject: Subject) {
|
||||
when (subject) {
|
||||
is Subject.App -> handleApp(stream, subject)
|
||||
is Subject.Module -> handleModule(stream, subject.file)
|
||||
else -> stream.copyAndClose(subject.file.outputStream())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
val external = subject.file.outputStream()
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val updateApk = StubApk.update(context)
|
||||
try {
|
||||
// Download full APK to stub update path
|
||||
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
|
||||
|
||||
// Also upgrade stub
|
||||
notifyUpdate(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(context.getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
// Extract stub
|
||||
val apk = context.cachedFile("stub.apk")
|
||||
ZipFile.Builder().setFile(updateApk).get().use { zf ->
|
||||
apk.delete()
|
||||
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
||||
}
|
||||
|
||||
// Patch and install
|
||||
subject.intent = AppMigration.upgradeStub(context, apk)
|
||||
?: throw IOException("HideAPK patch error")
|
||||
apk.delete()
|
||||
} catch (e: Exception) {
|
||||
// If any error occurred, do not let stub load the new APK
|
||||
updateApk.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val session = APKInstall.startSession(context)
|
||||
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleModule(src: InputStream, file: Uri) {
|
||||
val tmp = context.cachedFile("module.zip")
|
||||
try {
|
||||
// First download the entire zip into cache so we can process it
|
||||
src.writeTo(tmp)
|
||||
|
||||
val input = ZipFile.Builder().setFile(tmp).get()
|
||||
val output = ZipArchiveOutputStream(file.outputStream())
|
||||
withInOut(input, output) { zin, zout ->
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/"))
|
||||
zout.closeArchiveEntry()
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/"))
|
||||
zout.closeArchiveEntry()
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/"))
|
||||
zout.closeArchiveEntry()
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/"))
|
||||
zout.closeArchiveEntry()
|
||||
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/update-binary"))
|
||||
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
|
||||
zout.closeArchiveEntry()
|
||||
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray())
|
||||
zout.closeArchiveEntry()
|
||||
|
||||
// Then simply copy all entries to output
|
||||
zin.copyRawEntries(zout) { entry -> !entry.name.startsWith("META-INF") }
|
||||
}
|
||||
} finally {
|
||||
tmp.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private class TeeOutputStream(
|
||||
private val o1: OutputStream,
|
||||
private val o2: OutputStream
|
||||
) : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
o1.write(b)
|
||||
o2.write(b)
|
||||
}
|
||||
override fun write(b: ByteArray?, off: Int, len: Int) {
|
||||
o1.write(b, off, len)
|
||||
o2.write(b, off, len)
|
||||
}
|
||||
override fun close() {
|
||||
o1.close()
|
||||
o2.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
|
||||
interface DownloadSession {
|
||||
val context: Context
|
||||
fun attachNotification(id: Int, builder: Notification.Builder)
|
||||
fun onDownloadComplete()
|
||||
}
|
||||
|
||||
interface DownloadNotifier {
|
||||
val context: Context
|
||||
fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {})
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import kotlinx.parcelize.Parcelize
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
sealed class Subject : Parcelable {
|
||||
abstract class Subject : Parcelable {
|
||||
|
||||
abstract val url: String
|
||||
abstract val file: Uri
|
||||
@@ -27,24 +27,13 @@ sealed class Subject : Parcelable {
|
||||
|
||||
open fun pendingIntent(context: Context): PendingIntent? = null
|
||||
|
||||
@Parcelize
|
||||
class Module(
|
||||
private val module: OnlineModule,
|
||||
override val autoLaunch: Boolean,
|
||||
override val notifyId: Int = Notifications.nextId()
|
||||
) : Subject() {
|
||||
override val url: String get() = module.zipUrl
|
||||
override val title: String get() = module.downloadFilename
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val file by lazy {
|
||||
abstract class Module : Subject() {
|
||||
abstract val module: OnlineModule
|
||||
final override val url: String get() = module.zipUrl
|
||||
final override val title: String get() = module.downloadFilename
|
||||
final override val file by lazy {
|
||||
MediaStoreUtils.getFile(title).uri
|
||||
}
|
||||
|
||||
@IgnoredOnParcel
|
||||
var piCreator: ((Context, Uri) -> PendingIntent)? = null
|
||||
|
||||
override fun pendingIntent(context: Context) = piCreator?.invoke(context, file)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -2,7 +2,11 @@ package com.topjohnwu.magisk.core.ktx
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
@@ -23,7 +27,6 @@ import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import java.io.File
|
||||
import kotlin.String
|
||||
|
||||
fun Context.getBitmap(id: Int): Bitmap {
|
||||
var drawable = getDrawable(id)!!
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@@ -17,24 +18,14 @@ import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
inline fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
|
||||
var entry: ZipEntry? = nextEntry
|
||||
while (entry != null) {
|
||||
callback(entry)
|
||||
entry = nextEntry
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <In : InputStream, Out : OutputStream> withStreams(
|
||||
inStream: In,
|
||||
outStream: Out,
|
||||
inline fun <In : Closeable, Out : Closeable> withInOut(
|
||||
input: In,
|
||||
output: Out,
|
||||
withBoth: (In, Out) -> Unit
|
||||
) {
|
||||
inStream.use { reader ->
|
||||
outStream.use { writer ->
|
||||
input.use { reader ->
|
||||
output.use { writer ->
|
||||
withBoth(reader, writer)
|
||||
}
|
||||
}
|
||||
@@ -64,7 +55,7 @@ suspend inline fun InputStream.copyAndClose(
|
||||
out: OutputStream,
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = withStreams(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
|
||||
) = withInOut(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
|
||||
|
||||
@Throws(IOException::class)
|
||||
suspend inline fun InputStream.writeTo(
|
||||
|
||||
@@ -5,14 +5,15 @@ import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.nio.ExtendedFile
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
data class LocalModule(
|
||||
private val path: String,
|
||||
private val base: ExtendedFile,
|
||||
) : Module() {
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
|
||||
@@ -24,19 +25,18 @@ data class LocalModule(
|
||||
var description: String = ""
|
||||
var updateInfo: OnlineModule? = null
|
||||
var outdated = false
|
||||
|
||||
private var updateUrl: String = ""
|
||||
private val removeFile = RootUtils.fs.getFile(path, "remove")
|
||||
private val disableFile = RootUtils.fs.getFile(path, "disable")
|
||||
private val updateFile = RootUtils.fs.getFile(path, "update")
|
||||
private val riruFolder = RootUtils.fs.getFile(path, "riru")
|
||||
private val zygiskFolder = RootUtils.fs.getFile(path, "zygisk")
|
||||
private val unloaded = RootUtils.fs.getFile(zygiskFolder, "unloaded")
|
||||
|
||||
val updated: Boolean get() = updateFile.exists()
|
||||
val isRiru: Boolean get() = (id == "riru-core") || riruFolder.exists()
|
||||
val isZygisk: Boolean get() = zygiskFolder.exists()
|
||||
val zygiskUnloaded: Boolean get() = unloaded.exists()
|
||||
private val removeFile = base.getChildFile("remove")
|
||||
private val disableFile = base.getChildFile("disable")
|
||||
private val updateFile = base.getChildFile("update")
|
||||
val zygiskFolder = base.getChildFile("zygisk")
|
||||
|
||||
val updated get() = updateFile.exists()
|
||||
val isRiru = (id == "riru-core") || base.getChildFile("riru").exists()
|
||||
val isZygisk = zygiskFolder.exists()
|
||||
val zygiskUnloaded = zygiskFolder.getChildFile("unloaded").exists()
|
||||
val hasAction = base.getChildFile("action.sh").exists()
|
||||
|
||||
var enable: Boolean
|
||||
get() = !disableFile.exists()
|
||||
@@ -89,12 +89,11 @@ data class LocalModule(
|
||||
|
||||
init {
|
||||
runCatching {
|
||||
parseProps(Shell.cmd("dos2unix < $path/module.prop").exec().out)
|
||||
parseProps(Shell.cmd("dos2unix < $base/module.prop").exec().out)
|
||||
}
|
||||
|
||||
if (id.isEmpty()) {
|
||||
val sep = path.lastIndexOf('/')
|
||||
id = path.substring(sep + 1)
|
||||
id = base.name
|
||||
}
|
||||
|
||||
if (name.isEmpty()) {
|
||||
@@ -122,14 +121,14 @@ data class LocalModule(
|
||||
|
||||
companion object {
|
||||
|
||||
fun loaded() = RootUtils.fs.getFile(Const.MAGISK_PATH).exists()
|
||||
fun loaded() = RootUtils.fs.getFile(Const.MODULE_PATH).exists()
|
||||
|
||||
suspend fun installed() = withContext(Dispatchers.IO) {
|
||||
RootUtils.fs.getFile(Const.MAGISK_PATH)
|
||||
RootUtils.fs.getFile(Const.MODULE_PATH)
|
||||
.listFiles()
|
||||
.orEmpty()
|
||||
.filter { !it.isFile && !it.isHidden }
|
||||
.map { LocalModule("${Const.MAGISK_PATH}/${it.name}") }
|
||||
.map { LocalModule(it) }
|
||||
.sortedBy { it.name.lowercase(Locale.ROOT) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.topjohnwu.magisk.core.model.su
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
@@ -24,7 +24,7 @@ class SuLog(
|
||||
}
|
||||
|
||||
fun PackageManager.createSuLog(
|
||||
info: PackageInfo,
|
||||
info: ApplicationInfo,
|
||||
toUid: Int,
|
||||
fromPid: Int,
|
||||
command: String,
|
||||
@@ -33,13 +33,12 @@ fun PackageManager.createSuLog(
|
||||
context: String,
|
||||
gids: String,
|
||||
): SuLog {
|
||||
val appInfo = info.applicationInfo
|
||||
return SuLog(
|
||||
fromUid = appInfo.uid,
|
||||
fromUid = info.uid,
|
||||
toUid = toUid,
|
||||
fromPid = fromPid,
|
||||
packageName = getNameForUid(appInfo.uid)!!,
|
||||
appName = appInfo.getLabel(this),
|
||||
packageName = getNameForUid(info.uid)!!,
|
||||
appName = info.getLabel(this),
|
||||
command = command,
|
||||
action = policy,
|
||||
target = target,
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
package com.topjohnwu.magisk.core.model.su
|
||||
|
||||
class SuPolicy(val uid: Int) {
|
||||
import com.topjohnwu.magisk.core.data.magiskdb.MagiskDB
|
||||
|
||||
class SuPolicy(
|
||||
val uid: Int,
|
||||
var policy: Int = INTERACTIVE,
|
||||
var remain: Long = -1L,
|
||||
var logging: Boolean = true,
|
||||
var notification: Boolean = true,
|
||||
) {
|
||||
companion object {
|
||||
const val INTERACTIVE = 0
|
||||
const val DENY = 1
|
||||
const val ALLOW = 2
|
||||
}
|
||||
|
||||
var policy: Int = INTERACTIVE
|
||||
var until: Long = -1L
|
||||
var logging: Boolean = true
|
||||
var notification: Boolean = true
|
||||
|
||||
fun toMap(): MutableMap<String, Any> = mutableMapOf(
|
||||
"uid" to uid,
|
||||
"policy" to policy,
|
||||
"until" to until,
|
||||
"logging" to logging,
|
||||
"notification" to notification
|
||||
)
|
||||
fun toMap(): MutableMap<String, Any> {
|
||||
val until = if (remain <= 0) {
|
||||
remain
|
||||
} else {
|
||||
MagiskDB.Literal("(strftime(\"%s\", \"now\") + $remain)")
|
||||
}
|
||||
return mutableMapOf(
|
||||
"uid" to uid,
|
||||
"policy" to policy,
|
||||
"until" to until,
|
||||
"logging" to logging,
|
||||
"notification" to notification
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ByteArrayStream extends ByteArrayOutputStream {
|
||||
|
||||
@@ -27,4 +28,8 @@ public class ByteArrayStream extends ByteArrayOutputStream {
|
||||
public ByteArrayInputStream getInputStream() {
|
||||
return new ByteArrayInputStream(buf, 0, count);
|
||||
}
|
||||
|
||||
public ByteBuffer toByteBuffer() {
|
||||
return ByteBuffer.wrap(buf, 0, count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,7 +510,7 @@ public class SignApk {
|
||||
privateKey[0] = key;
|
||||
|
||||
// Generate, in memory, an APK signed using standard JAR Signature Scheme.
|
||||
ByteArrayOutputStream v1SignedApkBuf = new ByteArrayOutputStream();
|
||||
ByteArrayStream v1SignedApkBuf = new ByteArrayStream();
|
||||
JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf);
|
||||
// Use maximum compression for compressed entries because the APK lives forever on
|
||||
// the system partition.
|
||||
@@ -519,8 +519,7 @@ public class SignApk {
|
||||
copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
|
||||
signFile(manifest, publicKey, privateKey, timestamp, outputJar);
|
||||
outputJar.close();
|
||||
ByteBuffer v1SignedApk = ByteBuffer.wrap(v1SignedApkBuf.toByteArray());
|
||||
v1SignedApkBuf.reset();
|
||||
ByteBuffer v1SignedApk = v1SignedApkBuf.toByteBuffer();
|
||||
|
||||
ByteBuffer[] outputChunks;
|
||||
List<ApkSignerV2.SignerConfig> signerConfigs = createV2SignerConfigs(privateKey, publicKey,
|
||||
|
||||
@@ -64,7 +64,7 @@ object SuCallbackHandler {
|
||||
val pm = context.packageManager
|
||||
|
||||
val log = runCatching {
|
||||
pm.getPackageInfo(fromUid, pid)?.let {
|
||||
pm.getPackageInfo(fromUid, pid)?.applicationInfo?.let {
|
||||
pm.createSuLog(it, toUid, pid, command, policy, target, seContext, gids)
|
||||
}
|
||||
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
|
||||
|
||||
@@ -62,7 +62,7 @@ class SuRequestHandler(
|
||||
return false
|
||||
}
|
||||
output = File(fifo)
|
||||
policy = SuPolicy(uid)
|
||||
policy = policyDB.fetch(uid) ?: SuPolicy(uid)
|
||||
try {
|
||||
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
|
||||
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
||||
@@ -81,15 +81,13 @@ class SuRequestHandler(
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun respond(action: Int, time: Int) {
|
||||
val until = if (time > 0)
|
||||
TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) +
|
||||
TimeUnit.MINUTES.toSeconds(time.toLong())
|
||||
else
|
||||
time.toLong()
|
||||
|
||||
suspend fun respond(action: Int, time: Long) {
|
||||
policy.policy = action
|
||||
policy.until = until
|
||||
if (time >= 0) {
|
||||
policy.remain = TimeUnit.MINUTES.toSeconds(time)
|
||||
} else {
|
||||
policy.remain = time
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -100,7 +98,7 @@ class SuRequestHandler(
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
}
|
||||
if (until >= 0) {
|
||||
if (time >= 0) {
|
||||
policyDB.update(policy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.su
|
||||
|
||||
import android.os.Bundle
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
|
||||
object TestHandler {
|
||||
|
||||
object LogList : CallbackList<String>(Runnable::run) {
|
||||
override fun onAddElement(e: String) {
|
||||
Timber.i(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun run(method: String): Bundle {
|
||||
var reason: String? = null
|
||||
|
||||
fun prerequisite(): Boolean {
|
||||
// Make sure the Magisk app can get root
|
||||
val shell = Shell.getShell()
|
||||
if (!shell.isRoot) {
|
||||
reason = "shell not root"
|
||||
return false
|
||||
}
|
||||
|
||||
// Make sure the root service is running
|
||||
RootUtils.Connection.await()
|
||||
return true
|
||||
}
|
||||
|
||||
fun setup(): Boolean {
|
||||
return runBlocking {
|
||||
MagiskInstaller.Emulator(LogList, LogList).exec()
|
||||
}
|
||||
}
|
||||
|
||||
fun test(): Boolean {
|
||||
// Make sure Zygisk works correctly
|
||||
if (!Info.isZygiskEnabled) {
|
||||
reason = "zygisk not enabled"
|
||||
return false
|
||||
}
|
||||
|
||||
// Clear existing grant for ADB shell
|
||||
runBlocking {
|
||||
ServiceLocator.policyDB.delete(2000)
|
||||
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||
Config.prefs.edit().commit()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val result = prerequisite() && runCatching {
|
||||
when (method) {
|
||||
"setup" -> setup()
|
||||
"test" -> test()
|
||||
else -> {
|
||||
reason = "unknown method"
|
||||
false
|
||||
}
|
||||
}
|
||||
}.getOrElse {
|
||||
reason = it.stackTraceToString()
|
||||
false
|
||||
}
|
||||
|
||||
return Bundle().apply {
|
||||
putBoolean("result", result)
|
||||
if (reason != null) putString("reason", reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.app.Activity
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
@@ -13,7 +14,6 @@ import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.ktx.await
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.signing.JarMap
|
||||
@@ -23,11 +23,9 @@ import com.topjohnwu.magisk.core.utils.Keygen
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.security.SecureRandom
|
||||
@@ -38,6 +36,7 @@ object AppMigration {
|
||||
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
|
||||
private const val ALPHADOTS = "$ALPHA....."
|
||||
private const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
||||
private const val TEST_PKG_NAME = "$APP_PACKAGE_NAME.test"
|
||||
|
||||
// Some arbitrary limit
|
||||
const val MAX_LABEL_LENGTH = 32
|
||||
@@ -125,28 +124,23 @@ object AppMigration {
|
||||
apk: File, out: OutputStream,
|
||||
pkg: String, label: CharSequence
|
||||
): Boolean {
|
||||
val info = context.packageManager.getPackageArchiveInfo(apk.path, 0) ?: return false
|
||||
val origLabel = info.applicationInfo.nonLocalizedLabel.toString()
|
||||
val pm = context.packageManager
|
||||
val info = pm.getPackageArchiveInfo(apk.path, 0)?.applicationInfo ?: return false
|
||||
val origLabel = info.nonLocalizedLabel.toString()
|
||||
try {
|
||||
JarMap.open(apk, true).use { jar ->
|
||||
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
||||
val xml = AXML(jar.getRawData(je))
|
||||
val generator = classNameGenerator()
|
||||
|
||||
if (!xml.patchStrings {
|
||||
for (i in it.indices) {
|
||||
val s = it[i]
|
||||
if (s.contains(APP_PACKAGE_NAME)) {
|
||||
it[i] = s.replace(APP_PACKAGE_NAME, pkg)
|
||||
} else if (s.contains(PLACEHOLDER)) {
|
||||
it[i] = generator.next()
|
||||
} else if (s == origLabel) {
|
||||
it[i] = label.toString()
|
||||
}
|
||||
val p = xml.patchStrings {
|
||||
when {
|
||||
it.contains(APP_PACKAGE_NAME) -> it.replace(APP_PACKAGE_NAME, pkg)
|
||||
it.contains(PLACEHOLDER) -> generator.next()
|
||||
it == origLabel -> label.toString()
|
||||
else -> it
|
||||
}
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
if (!p) return false
|
||||
|
||||
// Write apk changes
|
||||
jar.getOutputStream(je).use { it.write(xml.bytes) }
|
||||
@@ -160,51 +154,87 @@ object AppMigration {
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchApp(activity: Activity, pkg: String) {
|
||||
val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ?: return
|
||||
private fun patchTest(apk: File, out: File, pkg: String): Boolean {
|
||||
try {
|
||||
JarMap.open(apk, true).use { jar ->
|
||||
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
||||
val xml = AXML(jar.getRawData(je))
|
||||
val p = xml.patchStrings {
|
||||
when (it) {
|
||||
APP_PACKAGE_NAME -> pkg
|
||||
TEST_PKG_NAME -> "$pkg.test"
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
if (!p) return false
|
||||
|
||||
// Write apk changes
|
||||
jar.getOutputStream(je).use { it.write(xml.bytes) }
|
||||
val keys = Keygen()
|
||||
out.outputStream().use { SignApk.sign(keys.cert, keys.key, jar, it) }
|
||||
return true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchApp(context: Context, pkg: String) {
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(pkg) ?: return
|
||||
intent.putExtra(Const.Key.PREV_CONFIG, Config.toBundle())
|
||||
val options = ActivityOptions.makeBasic()
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
options.setShareIdentityEnabled(true)
|
||||
}
|
||||
activity.startActivity(intent, options.toBundle())
|
||||
activity.finish()
|
||||
context.startActivity(intent, options.toBundle())
|
||||
if (context is Activity) {
|
||||
context.finish()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
|
||||
val stub = File(activity.cacheDir, "stub.apk")
|
||||
suspend fun patchAndHide(context: Context, label: String, pkg: String? = null): Boolean {
|
||||
val stub = File(context.cacheDir, "stub.apk")
|
||||
try {
|
||||
activity.assets.open("stub.apk").writeTo(stub)
|
||||
context.assets.open("stub.apk").writeTo(stub)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
|
||||
// Generate a new random package name and signature
|
||||
val repack = File(activity.cacheDir, "patched.apk")
|
||||
val pkg = genPackageName()
|
||||
// Generate a new random signature and package name if needed
|
||||
val pkg = pkg ?: genPackageName()
|
||||
Config.keyStoreRaw = ""
|
||||
|
||||
if (!patch(activity, stub, FileOutputStream(repack), pkg, label))
|
||||
return false
|
||||
// Check and patch the test APK
|
||||
try {
|
||||
val info = context.packageManager.getApplicationInfo(TEST_PKG_NAME, 0)
|
||||
val testApk = File(info.sourceDir)
|
||||
val testRepack = File(context.cacheDir, "test.apk")
|
||||
if (!patchTest(testApk, testRepack, pkg))
|
||||
return false
|
||||
val cmd = "adb_pm_install $testRepack $pkg.test"
|
||||
if (!Shell.cmd(cmd).exec().isSuccess)
|
||||
return false
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
}
|
||||
|
||||
val repack = File(context.cacheDir, "patched.apk")
|
||||
repack.outputStream().use {
|
||||
if (!patch(context, stub, it, pkg, label))
|
||||
return false
|
||||
}
|
||||
|
||||
// Install and auto launch app
|
||||
val session = APKInstall.startSession(activity, pkg, onFailure) {
|
||||
launchApp(activity, pkg)
|
||||
}
|
||||
|
||||
Config.suManager = pkg
|
||||
val cmd = "touch $AppApkPath; adb_pm_install $repack $pkg"
|
||||
if (Shell.cmd(cmd).exec().isSuccess) return true
|
||||
|
||||
try {
|
||||
repack.inputStream().copyAndClose(session.openStream(activity))
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
val cmd = "adb_pm_install $repack $pkg"
|
||||
if (Shell.cmd(cmd).exec().isSuccess) {
|
||||
Config.suManager = pkg
|
||||
Shell.cmd("touch $AppApkPath").exec()
|
||||
launchApp(context, pkg)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
session.waitIntent()?.let { activity.startActivity(it) } ?: return false
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -215,14 +245,25 @@ object AppMigration {
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
val onFailure = Runnable {
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
patchAndHide(activity, label)
|
||||
}
|
||||
if (!success) {
|
||||
dialog.dismiss()
|
||||
activity.toast(R.string.failure, Toast.LENGTH_LONG)
|
||||
}
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
patchAndHide(activity, label, onFailure)
|
||||
}
|
||||
|
||||
suspend fun restoreApp(context: Context): Boolean {
|
||||
val apk = StubApk.current(context)
|
||||
val cmd = "adb_pm_install $apk $APP_PACKAGE_NAME"
|
||||
if (Shell.cmd(cmd).await().isSuccess) {
|
||||
Config.suManager = ""
|
||||
Shell.cmd("touch $AppApkPath").exec()
|
||||
launchApp(context, APP_PACKAGE_NAME)
|
||||
return true
|
||||
}
|
||||
if (!success) onFailure.run()
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -233,29 +274,10 @@ object AppMigration {
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
val onFailure = Runnable {
|
||||
dialog.dismiss()
|
||||
if (!restoreApp(activity)) {
|
||||
activity.toast(R.string.failure, Toast.LENGTH_LONG)
|
||||
}
|
||||
val apk = StubApk.current(activity)
|
||||
val session = APKInstall.startSession(activity, APP_PACKAGE_NAME, onFailure) {
|
||||
launchApp(activity, APP_PACKAGE_NAME)
|
||||
dialog.dismiss()
|
||||
}
|
||||
Config.suManager = ""
|
||||
val cmd = "touch $AppApkPath; adb_pm_install $apk $APP_PACKAGE_NAME"
|
||||
if (Shell.cmd(cmd).await().isSuccess) return
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
apk.inputStream().copyAndClose(session.openStream(activity))
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return@withContext false
|
||||
}
|
||||
session.waitIntent()?.let { activity.startActivity(it) } ?: return@withContext false
|
||||
return@withContext true
|
||||
}
|
||||
if (!success) onFailure.run()
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
suspend fun upgradeStub(context: Context, apk: File): Intent? {
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.unzip
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -47,20 +46,14 @@ open class FlashZip(
|
||||
}
|
||||
}
|
||||
|
||||
val isValid = try {
|
||||
zipFile.unzip(installDir, "META-INF/com/google/android", true)
|
||||
val script = File(installDir, "updater-script")
|
||||
script.readText().contains("#MAGISK")
|
||||
try {
|
||||
val binary = File(installDir, "update-binary")
|
||||
AppContext.assets.open("module_installer.sh").use { it.writeTo(binary) }
|
||||
} catch (e: IOException) {
|
||||
console.add("! Unzip error")
|
||||
throw e
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
console.add("! This zip is not a Magisk module!")
|
||||
return false
|
||||
}
|
||||
|
||||
console.add("- Installing ${mUri.displayName}")
|
||||
|
||||
return Shell.cmd("sh $installDir/update-binary dummy 1 \'$zipFile\'")
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.system.ErrnoException
|
||||
import android.system.Os
|
||||
import android.system.OsConstants
|
||||
import android.system.OsConstants.O_WRONLY
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.os.postDelayed
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
@@ -15,13 +14,9 @@ import com.topjohnwu.magisk.core.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.reboot
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.utils.DummyList
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
@@ -51,7 +46,6 @@ import java.io.OutputStream
|
||||
import java.io.PushbackInputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.security.SecureRandom
|
||||
import java.util.Arrays
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@@ -134,10 +128,12 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
|
||||
val abi32 = Const.CPU_ABI_32
|
||||
if (Process.is64Bit() && abi32 != null) {
|
||||
val magisk32 = File(installDir, "magisk32")
|
||||
val entry = zf.getEntry("lib/$abi32/libmagisk.so")
|
||||
zf.getInputStream(entry).writeTo(magisk32)
|
||||
magisk32.setExecutable(true)
|
||||
if (entry != null) {
|
||||
val magisk32 = File(installDir, "magisk32")
|
||||
zf.getInputStream(entry).writeTo(magisk32)
|
||||
magisk32.setExecutable(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -193,7 +189,8 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyAll(it) }
|
||||
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) =
|
||||
out.use { copyAll(it, 1024 * 1024) }
|
||||
|
||||
private class NoAvailableStream(s: InputStream) : FilterInputStream(s) {
|
||||
// Make sure available is never called on the actual stream and always return 0
|
||||
@@ -227,8 +224,8 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
console.add("- Processing tar file")
|
||||
var entry: TarArchiveEntry? = tarIn.nextEntry
|
||||
|
||||
fun TarArchiveEntry.decompressedStream(): InputStream {
|
||||
val stream = if (name.endsWith(".lz4"))
|
||||
fun decompressedStream(): InputStream {
|
||||
val stream = if (tarIn.currentEntry.name.endsWith(".lz4"))
|
||||
FramedLZ4CompressorInputStream(tarIn, true) else tarIn
|
||||
return NoAvailableStream(stream)
|
||||
}
|
||||
@@ -254,9 +251,9 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
|
||||
if (bootItem != null) {
|
||||
console.add("-- Extracting: ${bootItem.name}")
|
||||
entry.decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream())
|
||||
decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream())
|
||||
} else if (entry.name.contains("vbmeta.img")) {
|
||||
val rawData = entry.decompressedStream().readBytes()
|
||||
val rawData = decompressedStream().readBytes()
|
||||
// Valid vbmeta.img should be at least 256 bytes
|
||||
if (rawData.size < 256)
|
||||
continue
|
||||
@@ -289,7 +286,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
} else {
|
||||
console.add("-- Copying : ${entry.name}")
|
||||
tarOut.putArchiveEntry(entry)
|
||||
tarIn.copyAll(tarOut, bufferSize = 1024 * 1024)
|
||||
tarIn.copyAll(tarOut)
|
||||
tarOut.closeArchiveEntry()
|
||||
}
|
||||
entry = tarIn.nextEntry ?: break
|
||||
@@ -431,7 +428,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
|
||||
// Process input file
|
||||
try {
|
||||
PushbackInputStream(uri.inputStream(), 512).use { src ->
|
||||
PushbackInputStream(uri.inputStream().buffered(1024 * 1024), 512).use { src ->
|
||||
val head = ByteArray(512)
|
||||
if (src.read(head) != head.size) {
|
||||
console.add("! Invalid input file")
|
||||
@@ -440,12 +437,13 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
src.unread(head)
|
||||
|
||||
val magic = head.copyOf(4)
|
||||
val tarMagic = Arrays.copyOfRange(head, 257, 262)
|
||||
val tarMagic = head.copyOfRange(257, 262)
|
||||
|
||||
srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) {
|
||||
// tar file
|
||||
outFile = MediaStoreUtils.getFile("$destName.tar")
|
||||
outStream = TarArchiveOutputStream(outFile.uri.outputStream()).also {
|
||||
val os = outFile.uri.outputStream().buffered(1024 * 1024)
|
||||
outStream = TarArchiveOutputStream(os).also {
|
||||
it.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)
|
||||
it.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)
|
||||
}
|
||||
@@ -502,7 +500,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
bootItem.file = newBoot
|
||||
bootItem.copyTo(outStream as TarArchiveOutputStream)
|
||||
} else {
|
||||
newBoot.newInputStream().copyAndClose(outStream)
|
||||
newBoot.newInputStream().use { it.copyAll(outStream, 1024 * 1024) }
|
||||
}
|
||||
newBoot.delete()
|
||||
|
||||
@@ -516,6 +514,8 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
outFile.delete()
|
||||
Timber.e(e)
|
||||
return false
|
||||
} finally {
|
||||
outStream.close()
|
||||
}
|
||||
|
||||
// Fix up binaries
|
||||
@@ -583,6 +583,8 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
|
||||
protected suspend fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
|
||||
|
||||
protected fun restore() = findImage() && "restore_imgs $srcBoot".sh().isSuccess
|
||||
|
||||
protected fun uninstall() = "run_uninstaller $AppApkPath".sh().isSuccess
|
||||
|
||||
@WorkerThread
|
||||
@@ -597,7 +599,9 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
if (result)
|
||||
return true
|
||||
|
||||
Shell.cmd("rm -rf $installDir").submit()
|
||||
// Not every operation initializes installDir
|
||||
if (::installDir.isInitialized)
|
||||
Shell.cmd("rm -rf $installDir").submit()
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -606,11 +610,10 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MagiskInstaller(
|
||||
abstract class ConsoleInstaller(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstallImpl(console, logs) {
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
if (success) {
|
||||
@@ -620,40 +623,51 @@ abstract class MagiskInstaller(
|
||||
}
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CallBackInstaller : MagiskInstallImpl(DummyList, DummyList) {
|
||||
suspend fun exec(callback: (Boolean) -> Unit): Boolean {
|
||||
val success = exec()
|
||||
callback(success)
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
class MagiskInstaller {
|
||||
|
||||
class Patch(
|
||||
private val uri: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = patchFile(uri)
|
||||
}
|
||||
|
||||
class SecondSlot(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = secondSlot()
|
||||
}
|
||||
|
||||
class Direct(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = direct()
|
||||
}
|
||||
|
||||
class Emulator(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = fixEnv()
|
||||
}
|
||||
|
||||
class Uninstall(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstallImpl(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = uninstall()
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
@@ -667,19 +681,11 @@ abstract class MagiskInstaller(
|
||||
}
|
||||
}
|
||||
|
||||
class FixEnv(private val callback: () -> Unit) : MagiskInstallImpl(DummyList, DummyList) {
|
||||
override suspend fun operations() = fixEnv()
|
||||
class Restore : CallBackInstaller() {
|
||||
override suspend fun operations() = restore()
|
||||
}
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
callback()
|
||||
context.toast(
|
||||
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed(5000) { reboot() }
|
||||
return success
|
||||
}
|
||||
class FixEnv : CallBackInstaller() {
|
||||
override suspend fun operations() = fixEnv()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class AXML(b: ByteArray) {
|
||||
* Followed by an array of uint32_t with size = number of strings
|
||||
* Each entry points to an offset into the string data
|
||||
*/
|
||||
fun patchStrings(patchFn: (Array<String>) -> Unit): Boolean {
|
||||
fun patchStrings(mapFn: (String) -> String): Boolean {
|
||||
val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN)
|
||||
|
||||
fun findStringPool(): Int {
|
||||
@@ -65,7 +65,9 @@ class AXML(b: ByteArray) {
|
||||
}
|
||||
|
||||
val strArr = strList.toTypedArray()
|
||||
patchFn(strArr)
|
||||
for (i in strArr.indices) {
|
||||
strArr[i] = mapFn(strArr[i])
|
||||
}
|
||||
|
||||
// Write everything before string data, will patch values later
|
||||
val baos = RawByteStream()
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.topjohnwu.magisk.core.utils;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||
import org.apache.commons.compress.archivers.zip.ZipUtil;
|
||||
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
public class Desugar {
|
||||
public static FileTime getLastModifiedTime(ZipEntry entry) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return entry.getLastModifiedTime();
|
||||
} else {
|
||||
return FileTime.fromMillis(entry.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
public static FileTime getLastAccessTime(ZipEntry entry) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return entry.getLastAccessTime();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static FileTime getCreationTime(ZipEntry entry) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return entry.getCreationTime();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Within {@link ZipArchiveOutputStream#copyFromZipInputStream}, we redirect the method call
|
||||
* {@link ZipUtil#checkRequestedFeatures} to this method. This is safe because the only usage
|
||||
* of copyFromZipInputStream is in {@link ZipArchiveOutputStream#addRawArchiveEntry},
|
||||
* which does not need to actually understand the content of the zip entry. By removing
|
||||
* this feature check, we can modify zip files using unsupported compression methods.
|
||||
*/
|
||||
public static void checkRequestedFeatures(final ZipArchiveEntry ze) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,10 @@ import android.net.NetworkRequest
|
||||
import android.os.PowerManager
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.ktx.registerRuntimeReceiver
|
||||
|
||||
class NetworkObserver(context: Context): DefaultLifecycleObserver {
|
||||
class NetworkObserver(context: Context) {
|
||||
private val manager = context.getSystemService<ConnectivityManager>()!!
|
||||
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
@@ -55,16 +52,13 @@ class NetworkObserver(context: Context): DefaultLifecycleObserver {
|
||||
manager.registerNetworkCallback(request, networkCallback)
|
||||
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
|
||||
context.applicationContext.registerRuntimeReceiver(receiver, filter)
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
postCurrentState()
|
||||
}
|
||||
|
||||
private fun postCurrentState() {
|
||||
postValue(manager.getNetworkCapabilities(manager.activeNetwork)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ?: false)
|
||||
fun postCurrentState() {
|
||||
postValue(
|
||||
manager.getNetworkCapabilities(manager.activeNetwork)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == true
|
||||
)
|
||||
}
|
||||
|
||||
private fun postValue(b: Boolean) {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LifecycleDispatcher;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
// Use Java to bypass Kotlin internal visibility modifier
|
||||
public class ProcessLifecycle {
|
||||
public static void init(@NonNull Context context) {
|
||||
LifecycleDispatcher.init(context);
|
||||
ProcessLifecycleOwner.init$lifecycle_process_release(context);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.utils
|
||||
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
suspend fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
|
||||
ZipFile.Builder().setFile(this).get().use { zip ->
|
||||
for (entry in zip.entries) {
|
||||
if (!entry.name.startsWith(path) || entry.isDirectory) {
|
||||
// Ignore directories, only create files
|
||||
continue
|
||||
}
|
||||
val name = if (junkPath)
|
||||
entry.name.substring(entry.name.lastIndexOf('/') + 1)
|
||||
else
|
||||
entry.name
|
||||
val dest = File(folder, name)
|
||||
dest.parentFile?.mkdirs()
|
||||
dest.outputStream().use { out -> zip.getInputStream(entry).copyAll(out) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
suspend fun InputStream.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
|
||||
ZipArchiveInputStream(this).use { zin ->
|
||||
var entry: ZipArchiveEntry
|
||||
while (true) {
|
||||
entry = zin.nextEntry ?: break
|
||||
if (!entry.name.startsWith(path) || entry.isDirectory) {
|
||||
// Ignore directories, only create files
|
||||
continue
|
||||
}
|
||||
val name = if (junkPath)
|
||||
entry.name.substring(entry.name.lastIndexOf('/') + 1)
|
||||
else
|
||||
entry.name
|
||||
|
||||
val dest = File(folder, name)
|
||||
dest.parentFile?.mkdirs()
|
||||
dest.outputStream().use { out -> zin.copyAll(out) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import androidx.annotation.Keep
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.Until
|
||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AdditionalTest : BaseTest {
|
||||
|
||||
companion object {
|
||||
private const val SHELL_PKG = "com.android.shell"
|
||||
private const val LSPOSED_CATEGORY = "org.lsposed.manager.LAUNCH_MANAGER"
|
||||
private const val LSPOSED_PKG = "org.lsposed.manager"
|
||||
|
||||
private lateinit var modules: List<LocalModule>
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() {
|
||||
BaseTest.prerequisite()
|
||||
runBlocking {
|
||||
modules = LocalModule.installed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
device.pressHome()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModuleCount() {
|
||||
var expected = 0
|
||||
if (Environment.testModules()) expected +=2
|
||||
if (Environment.lsposed()) expected++
|
||||
if (Environment.shamiko()) expected++
|
||||
assertEquals("Module count incorrect", expected, modules.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLsposed() {
|
||||
assumeTrue(Environment.lsposed())
|
||||
|
||||
val module = modules.find { it.id == "zygisk_lsposed" }
|
||||
assertNotNull("zygisk_lsposed is not installed", module)
|
||||
module!!
|
||||
assertFalse("zygisk_lsposed is not enabled", module.zygiskUnloaded)
|
||||
|
||||
// Launch lsposed manager to ensure the module is active
|
||||
uiAutomation.executeShellCommand(
|
||||
"am start -c $LSPOSED_CATEGORY $SHELL_PKG/.BugreportWarningActivity"
|
||||
).let { pfd -> AutoCloseInputStream(pfd).use { it.readBytes() } }
|
||||
|
||||
val pattern = Pattern.compile("$LSPOSED_PKG:id/.*")
|
||||
assertNotNull(
|
||||
"LSPosed manager launch failed",
|
||||
device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModule01() {
|
||||
assumeTrue(Environment.testModules())
|
||||
|
||||
val module = modules.find { it.id == "test_01" }
|
||||
assertNotNull("test_01 is not installed", module)
|
||||
assertTrue(
|
||||
"/system/etc/newfile should exist",
|
||||
RootUtils.fs.getFile("/system/etc/newfile").exists()
|
||||
)
|
||||
assertFalse(
|
||||
"/system/bin/screenrecord should not exist",
|
||||
RootUtils.fs.getFile("/system/bin/screenrecord").exists()
|
||||
)
|
||||
module!!
|
||||
assertTrue("test_01 should be zygisk unloaded", module.zygiskUnloaded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModule02() {
|
||||
assumeTrue(Environment.testModules())
|
||||
|
||||
val module = modules.find { it.id == "test_02" }
|
||||
assertNotNull("test_02 is not installed", module)
|
||||
module!!
|
||||
assertTrue("test_02 should be zygisk unloaded", module.zygiskUnloaded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModule03() {
|
||||
assumeTrue(Environment.testModules())
|
||||
|
||||
assertNull("test_03 should be removed", modules.find { it.id == "test_03" })
|
||||
}
|
||||
}
|
||||
27
app/core/src/main/java/com/topjohnwu/magisk/test/BaseTest.kt
Normal file
27
app/core/src/main/java/com/topjohnwu/magisk/test/BaseTest.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.app.Instrumentation
|
||||
import android.app.UiAutomation
|
||||
import android.content.Context
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
interface BaseTest {
|
||||
val instrumentation: Instrumentation
|
||||
get() = InstrumentationRegistry.getInstrumentation()
|
||||
val appContext: Context get() = instrumentation.targetContext
|
||||
val testContext: Context get() = instrumentation.context
|
||||
val uiAutomation: UiAutomation get() = instrumentation.uiAutomation
|
||||
val device: UiDevice get() = UiDevice.getInstance(instrumentation)
|
||||
|
||||
companion object {
|
||||
fun prerequisite() {
|
||||
assertTrue("Should have root access", Shell.getShell().isRoot)
|
||||
// Make sure the root service is running
|
||||
RootUtils.Connection.await()
|
||||
}
|
||||
}
|
||||
}
|
||||
209
app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt
Normal file
209
app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt
Normal file
@@ -0,0 +1,209 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.app.Notification
|
||||
import android.os.Build
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.net.toUri
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.download.DownloadNotifier
|
||||
import com.topjohnwu.magisk.core.download.DownloadProcessor
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.tasks.FlashZip
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.nio.ExtendedFile
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Environment : BaseTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() = BaseTest.prerequisite()
|
||||
|
||||
// The kernel running on emulators < API 26 does not play well with
|
||||
// magic mount. Skip module tests on those legacy platforms.
|
||||
fun testModules(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= 26
|
||||
}
|
||||
|
||||
fun lsposed(): Boolean {
|
||||
return Build.VERSION.SDK_INT in 27..34
|
||||
}
|
||||
|
||||
fun shamiko(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= 27
|
||||
}
|
||||
|
||||
private const val MODULE_ERROR = "Module zip processing incorrect"
|
||||
}
|
||||
|
||||
object TimberLog : CallbackList<String>(Runnable::run) {
|
||||
override fun onAddElement(e: String) {
|
||||
Timber.i(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkModuleZip(file: File) {
|
||||
// Make sure module processing is correct
|
||||
ZipFile.Builder().setFile(file).get().use { zip ->
|
||||
val meta = zip.entries
|
||||
.asSequence()
|
||||
.filter { it.name.startsWith("META-INF") }
|
||||
.toMutableList()
|
||||
assertEquals(MODULE_ERROR, 6, meta.size)
|
||||
|
||||
val binary = zip.getInputStream(
|
||||
zip.getEntry("META-INF/com/google/android/update-binary")
|
||||
).use { it.readBytes() }
|
||||
val ref = appContext.assets.open("module_installer.sh").use { it.readBytes() }
|
||||
assertArrayEquals(MODULE_ERROR, ref, binary)
|
||||
|
||||
val script = zip.getInputStream(
|
||||
zip.getEntry("META-INF/com/google/android/updater-script")
|
||||
).use { it.readBytes() }
|
||||
assertArrayEquals(MODULE_ERROR, "#MAGISK\n".toByteArray(), script)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupModule01(root: ExtendedFile) {
|
||||
val error = "test_01 setup failed"
|
||||
val path = root.getChildFile("test_01")
|
||||
|
||||
// Create /system/etc/newfile
|
||||
val etc = path.getChildFile("system").getChildFile("etc")
|
||||
assertTrue(error, etc.mkdirs())
|
||||
assertTrue(error, etc.getChildFile("newfile").createNewFile())
|
||||
|
||||
// Delete /system/bin/screenrecord
|
||||
val bin = path.getChildFile("system").getChildFile("bin")
|
||||
assertTrue(error, bin.mkdirs())
|
||||
assertTrue(error, Shell.cmd("mknod $bin/screenrecord c 0 0").exec().isSuccess)
|
||||
|
||||
// Create an empty zygisk folder
|
||||
val module = LocalModule(path)
|
||||
assertTrue(error, module.zygiskFolder.mkdir())
|
||||
|
||||
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
|
||||
}
|
||||
|
||||
private fun setupModule02(root: ExtendedFile) {
|
||||
val error = "test_02 setup failed"
|
||||
val path = root.getChildFile("test_02")
|
||||
|
||||
// Create invalid zygisk libraries
|
||||
val module = LocalModule(path)
|
||||
assertTrue(error, module.zygiskFolder.mkdirs())
|
||||
assertTrue(error, module.zygiskFolder.getChildFile("armeabi-v7a.so").createNewFile())
|
||||
assertTrue(error, module.zygiskFolder.getChildFile("arm64-v8a.so").createNewFile())
|
||||
assertTrue(error, module.zygiskFolder.getChildFile("x86.so").createNewFile())
|
||||
assertTrue(error, module.zygiskFolder.getChildFile("x86_64.so").createNewFile())
|
||||
|
||||
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
|
||||
}
|
||||
|
||||
private fun setupModule03(root: ExtendedFile) {
|
||||
val error = "test_03 setup failed"
|
||||
val path = root.getChildFile("test_03")
|
||||
|
||||
// Create a new module but mark is as "remove"
|
||||
val module = LocalModule(path)
|
||||
assertTrue(error, path.mkdirs())
|
||||
assertTrue(error, path.getChildFile("service.sh").createNewFile())
|
||||
module.remove = true
|
||||
|
||||
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupEnvironment() {
|
||||
runBlocking {
|
||||
assertTrue(
|
||||
"Magisk setup failed",
|
||||
MagiskInstaller.Emulator(TimberLog, TimberLog).exec()
|
||||
)
|
||||
}
|
||||
|
||||
val notify = object : DownloadNotifier {
|
||||
override val context = appContext
|
||||
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {}
|
||||
}
|
||||
val processor = DownloadProcessor(notify)
|
||||
|
||||
val shamiko = appContext.cachedFile("shamiko.zip")
|
||||
runBlocking {
|
||||
testContext.assets.open("shamiko.zip").use {
|
||||
processor.handleModule(it, shamiko.toUri())
|
||||
}
|
||||
checkModuleZip(shamiko)
|
||||
if (shamiko()) {
|
||||
assertTrue(
|
||||
"Shamiko installation failed",
|
||||
FlashZip(shamiko.toUri(), TimberLog, TimberLog).exec()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val lsp = appContext.cachedFile("lsposed.zip")
|
||||
runBlocking {
|
||||
testContext.assets.open("lsposed.zip").use {
|
||||
processor.handleModule(it, lsp.toUri())
|
||||
}
|
||||
checkModuleZip(lsp)
|
||||
if (lsposed()) {
|
||||
assertTrue(
|
||||
"LSPosed installation failed",
|
||||
FlashZip(lsp.toUri(), TimberLog, TimberLog).exec()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (testModules()) {
|
||||
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
|
||||
setupModule01(root)
|
||||
setupModule02(root)
|
||||
setupModule03(root)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupAppHide() {
|
||||
runBlocking {
|
||||
assertTrue(
|
||||
"App hiding failed",
|
||||
AppMigration.patchAndHide(
|
||||
context = appContext,
|
||||
label = "Settings",
|
||||
pkg = "repackaged.$APP_PACKAGE_NAME"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupAppRestore() {
|
||||
runBlocking {
|
||||
assertTrue(
|
||||
"App restoration failed",
|
||||
AppMigration.restoreApp(appContext)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import androidx.annotation.Keep
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MagiskAppTest : BaseTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() = BaseTest.prerequisite()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZygisk() {
|
||||
assertTrue("Zygisk should be enabled", Info.isZygiskEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSuRequest() {
|
||||
// Bypass the need to actually show a dialog
|
||||
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||
Config.prefs.edit().commit()
|
||||
|
||||
// Inject an undetermined + mute logging policy for ADB shell
|
||||
val policy = SuPolicy(
|
||||
uid = 2000,
|
||||
logging = false,
|
||||
notification = false,
|
||||
remain = 0L
|
||||
)
|
||||
runBlocking {
|
||||
ServiceLocator.policyDB.update(policy)
|
||||
}
|
||||
|
||||
val filter = IntentFilter(Intent.ACTION_VIEW)
|
||||
filter.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
val monitor = instrumentation.addMonitor(filter, null, false)
|
||||
|
||||
// Try to call su from ADB shell
|
||||
val cmd = if (Build.VERSION.SDK_INT < 24) {
|
||||
// API 23 runs executeShellCommand as root
|
||||
"/system/xbin/su 2000 su -c id"
|
||||
} else {
|
||||
"su -c id"
|
||||
}
|
||||
val pfd = uiAutomation.executeShellCommand(cmd)
|
||||
|
||||
// Make sure SuRequestActivity is launched
|
||||
val suRequest = monitor.waitForActivityWithTimeout(TimeUnit.SECONDS.toMillis(10))
|
||||
assertNotNull("SuRequestActivity is not launched", suRequest)
|
||||
|
||||
// Check that the request went through
|
||||
AutoCloseInputStream(pfd).reader().use {
|
||||
assertTrue(
|
||||
"Cannot grant root permission from shell",
|
||||
it.readText().contains("uid=0")
|
||||
)
|
||||
}
|
||||
|
||||
// Check that the database is updated
|
||||
runBlocking {
|
||||
val policy = ServiceLocator.policyDB.fetch(2000)
|
||||
?: throw AssertionError("PolicyDB is invalid")
|
||||
assertEquals("Policy for shell is incorrect", SuPolicy.ALLOW, policy.policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<string name="modules">Módulos</string>
|
||||
<string name="superuser">Superusuariu</string>
|
||||
<string name="logs">Rexistru</string>
|
||||
<string name="settings">Axustes</string>
|
||||
<string name="settings">Configuración</string>
|
||||
<string name="install">Instalar</string>
|
||||
<string name="section_home">Aniciu</string>
|
||||
<string name="section_theme">Estilos</string>
|
||||
@@ -15,7 +15,7 @@
|
||||
<string name="loading">Cargando…</string>
|
||||
<string name="update">Anovar</string>
|
||||
<string name="not_available">N/D</string>
|
||||
<string name="hide">Anubrir</string>
|
||||
<string name="hide">Esconder</string>
|
||||
<string name="home_package">Paquete</string>
|
||||
<string name="home_app_title">Aplicación</string>
|
||||
<string name="home_notice_content">Baxa Magisk NAMÁS dende la páxina oficial de GitHub. ¡Los ficheros de fontes desconocíes puen ser maliciosos!</string>
|
||||
@@ -39,10 +39,10 @@
|
||||
<string name="manager_download_install">Primi equí pa baxalu ya instalalu</string>
|
||||
<string name="direct_install">Instalación direuta (aconséyase)</string>
|
||||
<string name="install_inactive_slot">Instalar na ralura inactiva (darréu del OTA)</string>
|
||||
<string name="install_inactive_slot_msg">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva darréu de reaniciar!\nUsa esta opción namás dempués d\'acabar l\'anovamientu per OTA.\n¿Quies siguir?</string>
|
||||
<string name="install_inactive_slot_msg">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva dempués de reaniciar!\nUsa esta opción namás dempués d\'acabar l\'anovamientu per OTA.\n¿Quies siguir?</string>
|
||||
<string name="setup_title">Configuración adicional</string>
|
||||
<string name="select_patch_file">Seleicionar y parchiar un ficheru</string>
|
||||
<string name="patch_file_msg">Seleiciona una imaxe en bruto (*.img) o un archivu d\'ODIN (*.tar)</string>
|
||||
<string name="patch_file_msg">Seleiciona una imaxe en bruto (*.img), un archivu d\'ODIN (*.tar) o un ficheru payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Reaniciando en 5 segundos…</string>
|
||||
<string name="flash_screen_title">Instalación</string>
|
||||
<!--Superuser-->
|
||||
@@ -81,6 +81,9 @@
|
||||
<string name="logs_cleared">El rexistru borróse correutamente</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">UID de destín: %1$d</string>
|
||||
<string name="target_pid">Mount ns target PID: %s</string>
|
||||
<string name="selinux_context">Contestu de SELinux: %s</string>
|
||||
<string name="supp_group">Grupu suplementariu: %s</string>
|
||||
<!--SafetyNet-->
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Aplicaciones del sistema</string>
|
||||
@@ -94,15 +97,19 @@
|
||||
<string name="reboot_bootloader">Reaniciar al cargador d\'arrinque</string>
|
||||
<string name="reboot_download">Reaniciar al mou de descarga</string>
|
||||
<string name="reboot_edl">Reaniciar al mou EDL</string>
|
||||
<string name="reboot_safe_mode">Mou seguru</string>
|
||||
<string name="module_version_author">%1$s por %2$s</string>
|
||||
<string name="module_state_remove">Quitar</string>
|
||||
<string name="module_action">Aición</string>
|
||||
<string name="module_state_restore">Restaurar</string>
|
||||
<string name="module_action_install_external">Instalar dende l\'almacenamientu</string>
|
||||
<string name="update_available">Hai un anovamientu disponible</string>
|
||||
<string name="suspend_text_riru">Suspendióse\'l módulu porque s\'activó «%1$s»</string>
|
||||
<string name="suspend_text_zygisk">Suspendióse\'l módulu porque nun s\'activó «%1$s»</string>
|
||||
<string name="zygisk_module_unloaded">El módulu de Zygisk nun cargó pola mor d\'haber incompatibilidaes</string>
|
||||
<string name="zygisk_module_unloaded">El módulu de Zygisk nun cargó por haber incompatibilidaes</string>
|
||||
<string name="module_empty">Nun hai nengún módulu instaláu</string>
|
||||
<string name="confirm_install">¿Quies instalar el módulu «%1$s»?</string>
|
||||
<string name="confirm_install_title">Confirmación de la instalación</string>
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Mou del estilu</string>
|
||||
<string name="settings_dark_mode_message">¡Seleiciona\'l mou que meyor s\'adaute al to estilu!</string>
|
||||
@@ -111,7 +118,7 @@
|
||||
<string name="settings_dark_mode_dark">Escuridá</string>
|
||||
<string name="settings_download_path_title">Camín de les descargues</string>
|
||||
<string name="settings_download_path_message">Los ficheros van guardase en «%1$s»</string>
|
||||
<string name="settings_hide_app_title">Anubrir Magisk</string>
|
||||
<string name="settings_hide_app_title">Esconder Magisk</string>
|
||||
<string name="settings_hide_app_summary">Instala una aplicación intermedia con una ID y una etiqueta al debalu</string>
|
||||
<string name="settings_restore_app_title">Restaurar el mou visible</string>
|
||||
<string name="settings_restore_app_summary">Fai que l\'aplicación orixinal vuelva ser visible</string>
|
||||
@@ -149,14 +156,19 @@
|
||||
<string name="auto_response">Rempuesta automática</string>
|
||||
<string name="request_timeout">Tiempu d\'espera de les solicitúes</string>
|
||||
<string name="superuser_notification">Avisu de superusuariu</string>
|
||||
<string name="settings_su_reauth_title">Volver autenticar darréu d\'anovar</string>
|
||||
<string name="settings_su_reauth_title">Volver autenticar dempués d\'anovar</string>
|
||||
<string name="settings_su_reauth_summary">Vuelve pidir los permisos de superusuariu dempués d\'anovar les aplicaciones</string>
|
||||
<string name="settings_su_tapjack_title">Proteición escontra\'l tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">El diálogu de concesión de permisos de superusuariu nun respuende a la entrada mentanto lu torgue otra ventana o superposición</string>
|
||||
<string name="settings_su_auth_title">Autenticación d\'usuariu</string>
|
||||
<string name="settings_su_auth_summary">Pide l\'autenticación demientres les solicitúes de superusuariu</string>
|
||||
<string name="settings_su_auth_insecure">Nun se configuró nengún métodu d\'autenticación nel preséu</string>
|
||||
<string name="settings_customization">Personalización</string>
|
||||
<string name="setting_add_shortcut_summary">Amiesta un atayu a la pantalla d\'aniciu en casu de que\'l nome y l\'iconu seyan difíciles de reconocer darréu d\'anubrir l\'aplicación</string>
|
||||
<string name="setting_add_shortcut_summary">Amiesta un atayu a la pantalla d\'aniciu en casu de que\'l nome y l\'iconu seyan difíciles de reconocer dempués d\'esconder l\'aplicación</string>
|
||||
<string name="settings_doh_title">DNS per HTTPS</string>
|
||||
<string name="settings_doh_description">Una igua alternativa pal envelenamientu de DNS en dalgunos países</string>
|
||||
<string name="settings_random_name_title">Nome de la salida aleatoriu</string>
|
||||
<string name="settings_random_name_description">Fai que\'l nome de ficheru de la salida de les imáxenes parchiaes y los ficheros tar seya aleatoriu pa impidir la deteición</string>
|
||||
<string name="multiuser_mode">Mou de multiusuariu</string>
|
||||
<string name="settings_owner_only">Namás el propietariu del preséu</string>
|
||||
<string name="settings_owner_manage">El propietariu xestionáu del preséu</string>
|
||||
@@ -186,11 +198,14 @@
|
||||
<string name="repo_install_title">Instalación de: %1$s %2$s (%3$d)</string>
|
||||
<string name="download">Baxar</string>
|
||||
<string name="reboot">Reaniciar</string>
|
||||
<string name="close">Zarrar</string>
|
||||
<string name="release_notes">Notes de la versión</string>
|
||||
<string name="flashing">Flaxando…</string>
|
||||
<string name="running">Executando…</string>
|
||||
<string name="done">¡Fecho!</string>
|
||||
<string name="done_action">Completóse l\'aición de: %1$s</string>
|
||||
<string name="failure">¡Falló!</string>
|
||||
<string name="hide_app_title">Anubriendo l\'aplicación Magisk…</string>
|
||||
<string name="hide_app_title">Escondiendo l\'aplicación Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nun s\'atopó nenguna aplicación p\'abrir l\'enllaz</string>
|
||||
<string name="complete_uninstall">Desinstalar dafechu</string>
|
||||
<string name="restore_img">Restaurar les imáxenes</string>
|
||||
@@ -200,6 +215,7 @@
|
||||
<string name="setup_fail">La configuración falló</string>
|
||||
<string name="env_fix_title">Configuración adicional</string>
|
||||
<string name="env_fix_msg">El preséu precisa una configuración adicional pa que Magisk funcione afayadizamente. ¿Quies siguir y reaniciar?</string>
|
||||
<string name="env_full_fix_msg">El preséu precisa volver flaxar Magisk pa que funcione afayadizamente. Volvi instalar Magisk dientro de l\'aplicación porque\'l mou de recuperación nun pue consiguir la información correuta del preséu.</string>
|
||||
<string name="setup_msg">Executando la configuración del entornu…</string>
|
||||
<string name="unsupport_magisk_title">Versión non compatible</string>
|
||||
<string name="unsupport_magisk_msg">Esta versión de l\'aplicación nun ye compatible coles versiones de Magisk anteriores a la %1$s.\n\nL\'aplicación va comportase como si Magisk nun tuviere instaláu, anueva Magisk namás que puedas.</string>
|
||||
@@ -207,12 +223,13 @@
|
||||
<string name="unsupport_system_app_msg">Esta aplicación nun se pue executar nel espaciu del sistema. Volvi instalala mas nel espaciu del usuariu.</string>
|
||||
<string name="unsupport_other_su_msg">Detectóse un binariu «su» que nun ye de Magisk. Quita cualesquier solución de root y/o volvi instalar Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk ta instaláu nel almacenamientu esternu. Movi l\'aplicación al almacenamientu internu, por favor.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Magisk nun pue siguir funcionando nel mou anubríu darréu que se perdió\'l root. Restaura\'l mou visible de l\'aplicación.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Magisk nun pue siguir funcionando nel mou escondíu darréu que se perdió\'l root. Restaura\'l mou visible de l\'aplicación.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Concede\'l permisu d\'almacenamientu p\'activar esta funcionalidá</string>
|
||||
<string name="post_notifications_denied">Concede\'l permisu de los avisos p\'activar esta función</string>
|
||||
<string name="install_unknown_denied">Permite la instalación d\'aplicaciones desconocíes p\'activar esta funcionalidá</string>
|
||||
<string name="add_shortcut_title">Amestar un atayu a la pantalla d\'aniciu</string>
|
||||
<string name="add_shortcut_msg">Darréu d\'anubrir esta aplicación, el so nome ya iconu van ser difíciles de reconocer. ¿Quies amestar un atayu a la pantalla d\'aniciu?</string>
|
||||
<string name="add_shortcut_msg">Dempués d\'esconder esta aplicación, el so nome ya iconu van ser difíciles de reconocer. ¿Quies amestar un atayu a la pantalla d\'aniciu?</string>
|
||||
<string name="app_not_found">Nun s\'atopó nenguna aplicación pa remanar esta aición</string>
|
||||
<string name="reboot_apply_change">Reanicia p\'aplicar los cambeos</string>
|
||||
<string name="restore_app_confirmation">Esta aición va restaurar l\'aplicación orixinal y desanicia la intermedia. ¿De xuru que quies facelo?</string>
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
<string name="section_theme">עיצוב</string>
|
||||
<string name="denylist">רשימת דחייה</string>
|
||||
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">אין חיבור זמין</string>
|
||||
<string name="app_changelog">רשימת שינויים</string>
|
||||
<string name="app_changelog">יומן שינויים</string>
|
||||
<string name="loading">טוען…</string>
|
||||
<string name="update">עדכון</string>
|
||||
<string name="not_available">ל/ז</string>
|
||||
@@ -45,16 +44,16 @@
|
||||
<string name="install_inactive_slot_msg">ההתקן שלך ייאלץ אתחול לחריץ הלא פעיל הנוכחי שלך לאחר הפעלה מחדש!\nיש להשתמש באפשרות זו רק לאחר ביצוע OTA בלבד.\nלהמשיך?</string>
|
||||
<string name="setup_title">התקנה נוספת</string>
|
||||
<string name="select_patch_file">בחירה והתקנת קובץ</string>
|
||||
<string name="patch_file_msg">בחירת תמונה גולמית (*.img) או ODIN קובץ tar (*.tar)</string>
|
||||
<string name="patch_file_msg">בחירת תמונה גולמית (*.img) או ODIN tarfile (*.tar) או payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">מאתחל בעוד 5 שניות…</string>
|
||||
<string name="flash_screen_title">התקנה</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">בקשות משתמש על</string>
|
||||
<string name="touch_filtered_warning">מכיוון שיישום מסתיר בקשה של משתמש על, Magisk לא יכול לאמת את תגובתך</string>
|
||||
<string name="deny">דחה</string>
|
||||
<string name="deny">דחייה</string>
|
||||
<string name="prompt">מיידי</string>
|
||||
<string name="grant">הענק</string>
|
||||
<string name="grant">הענקה</string>
|
||||
<string name="su_warning">מעניק גישה מלאה להתקן שלך.\nיש לדחות באי וודאות!</string>
|
||||
<string name="forever">לצמיתות</string>
|
||||
<string name="once">פעם אחת</string>
|
||||
@@ -62,24 +61,24 @@
|
||||
<string name="twentymin">20 דקות</string>
|
||||
<string name="thirtymin">חצי שעה</string>
|
||||
<string name="sixtymin">שעה</string>
|
||||
<string name="su_allow_toast">%1$s הוענקו הרשאות משתמש עבור</string>
|
||||
<string name="su_deny_toast">%1$s נשללו הרשאות משתמש עבור</string>
|
||||
<string name="su_allow_toast">%1$s קיבל הרשאות משתמש על</string>
|
||||
<string name="su_deny_toast">%1$s נשללו הרשאות משתמש על</string>
|
||||
<string name="su_snack_grant">הרשאות משתמש על עבור %1$s הוענקו</string>
|
||||
<string name="su_snack_deny">הרשאות משתמש על עבור %1$s נשללו</string>
|
||||
<string name="su_snack_notif_on">התראות עבור %1$s פועלות</string>
|
||||
<string name="su_snack_notif_off">התראות עבור %1$s כבויות</string>
|
||||
<string name="su_snack_notif_on">התראות של %1$s מופעלות</string>
|
||||
<string name="su_snack_notif_off">התראות של %1$s מושבתות</string>
|
||||
<string name="su_snack_log_on">יומני רישום עבור %1$s פועלות</string>
|
||||
<string name="su_snack_log_off">יומני רישום עבור %1$s כבויות</string>
|
||||
<string name="su_snack_log_off">יומני רישום עבור %1$s מושבתות</string>
|
||||
<string name="su_revoke_title">להסיר?</string>
|
||||
<string name="su_revoke_msg">נא לאשר שלילת הרשאות עבור %1$s?</string>
|
||||
<string name="toast">הרמת כוסית</string>
|
||||
<string name="none">ללא</string>
|
||||
<string name="superuser_toggle_notification">התראות</string>
|
||||
<string name="superuser_toggle_revoke">הסרה</string>
|
||||
<string name="superuser_policy_none">לא נתבקשו הרשאות משתמש על על ידי שום יישום</string>
|
||||
<string name="superuser_policy_none">טרם נתבקשו הרשאות משתמש על על ידי יישומים</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">הינך ללא יומן רישום, יש לנסות להשתמש ביישומים מותאמים יותר למשתמש העל שלך</string>
|
||||
<string name="log_data_none">הינך ללא יומן, יש לנסות להשתמש יותר ביישומי השורש שלך</string>
|
||||
<string name="log_data_magisk_none">יומני רישום Magisk ריקים, זה מוזר</string>
|
||||
<string name="menuSaveLog">שמירת יומן רישום</string>
|
||||
<string name="menuClearLog">ניקוי יומן רישום כעת</string>
|
||||
@@ -92,7 +91,7 @@
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!-- MagiskHide -->
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">הצגת יישומי מערכת</string>
|
||||
<string name="show_os_app">הצגת יישומי מערכת הפעלה</string>
|
||||
<string name="hide_filter_hint">סינון לפי שם</string>
|
||||
@@ -100,14 +99,16 @@
|
||||
|
||||
<!--Module-->
|
||||
<string name="no_info_provided">(לא סופק מידע)</string>
|
||||
<string name="reboot_userspace">אתחול מהיר</string>
|
||||
<string name="reboot_userspace">אתחול רך</string>
|
||||
<string name="reboot_recovery">אתחול למצב שחזור</string>
|
||||
<string name="reboot_bootloader">אתחול מצב מנהל האתחול</string>
|
||||
<string name="reboot_download">אתחול מצב הורדה</string>
|
||||
<string name="reboot_bootloader">אתחול לטוען האתחול</string>
|
||||
<string name="reboot_download">אתחול למצב הורדה</string>
|
||||
<string name="reboot_edl">אתחול למצב EDL</string>
|
||||
<string name="reboot_safe_mode">מצב בטוח</string>
|
||||
<string name="module_version_author">%1$s מאת %2$s</string>
|
||||
<string name="module_state_remove">הסרה</string>
|
||||
<string name="module_state_restore">שיחזור</string>
|
||||
<string name="module_action">פעולה</string>
|
||||
<string name="module_state_restore">שחזור</string>
|
||||
<string name="module_action_install_external">התקנה מהאחסון</string>
|
||||
<string name="update_available">עדכונים זמינים</string>
|
||||
<string name="suspend_text_riru">מודול מושעה כי %1$s מופעל</string>
|
||||
@@ -117,7 +118,7 @@
|
||||
<string name="confirm_install">להתקין מודול %1$s?</string>
|
||||
<string name="confirm_install_title">אישור התקנה</string>
|
||||
|
||||
<!--Settings -->
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">מצב עיצוב</string>
|
||||
<string name="settings_dark_mode_message">נא לבחור מצב המתאים ביותר לסגנון שלך!</string>
|
||||
<string name="settings_dark_mode_light">תמיד בהיר</string>
|
||||
@@ -128,11 +129,11 @@
|
||||
<string name="settings_hide_app_title">הסתרת היישום Magisk</string>
|
||||
<string name="settings_hide_app_summary">התקנת יישום מתווך עם מזהה חבילה אקראי ותווית שם מותאמת אישית</string>
|
||||
<string name="settings_restore_app_title">שיחזור היישום Magisk</string>
|
||||
<string name="settings_restore_app_summary">יש לבטל את הסתרת היישום ולשחזור אותו ל-APK המקורי</string>
|
||||
<string name="settings_restore_app_summary">ביטול הסתרת היישום ושחזור אל ה-APK המקורי</string>
|
||||
<string name="language">שפה</string>
|
||||
<string name="system_default">(ברירת מחדל מערכת)</string>
|
||||
<string name="settings_check_update_title">בדיקת עדכונים</string>
|
||||
<string name="settings_check_update_summary">בדוק מעת לעת ברקע אם יש עדכונים</string>
|
||||
<string name="settings_check_update_summary">בדיקה מעת לעת ברקע אם יש עדכונים</string>
|
||||
<string name="settings_update_channel_title">ערוץ עדכון</string>
|
||||
<string name="settings_update_stable">יציב</string>
|
||||
<string name="settings_update_beta">בטא</string>
|
||||
@@ -161,12 +162,12 @@
|
||||
<string name="settings_su_request_60">60 שניות</string>
|
||||
<string name="superuser_access">גישת משתמש על</string>
|
||||
<string name="auto_response">תגובה אוטומטית</string>
|
||||
<string name="request_timeout">בקש פסק זמן</string>
|
||||
<string name="request_timeout">בקשת פסק זמן</string>
|
||||
<string name="superuser_notification">התראות משתמש על</string>
|
||||
<string name="settings_su_reauth_title">אימות מחדש לאחר שדרוג</string>
|
||||
<string name="settings_su_reauth_summary">אימות מחדש הרשאות של משתמש על לאחר שדרוג יישום</string>
|
||||
<string name="settings_su_tapjack_title">הפעלת הגנת Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או כיסוי אחר</string>
|
||||
<string name="settings_su_tapjack_title">הגנת Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או שכבת על אחרת</string>
|
||||
<string name="settings_su_auth_title">אימות משתמש</string>
|
||||
<string name="settings_su_auth_summary">בקשת אימות משתמש במהלך בקשות משתמש על</string>
|
||||
<string name="settings_su_auth_insecure">לא מוגדרת שיטת אימות בהתקן</string>
|
||||
@@ -174,6 +175,8 @@
|
||||
<string name="setting_add_shortcut_summary">הוספת קיצור דרך יפה במסך הבית למקרה שקשה לזהות את השם ואת הסמל לאחר הסתרת היישום</string>
|
||||
<string name="settings_doh_title">DNS על HTTPS</string>
|
||||
<string name="settings_doh_description">עקיפת DNS מורעל במדינות מסוימות</string>
|
||||
<string name="settings_random_name_title">שם פלט אקראי</string>
|
||||
<string name="settings_random_name_description">שם אקראי לקובץ הפלט של תמונות מתוקנות וקבצי tar כדי למנוע זיהוי</string>
|
||||
<string name="multiuser_mode">מצב מרובה משתמשים</string>
|
||||
<string name="settings_owner_only">בעל ההתקן בלבד</string>
|
||||
<string name="settings_owner_manage">אחראי ניהול ההתקן</string>
|
||||
@@ -205,10 +208,13 @@
|
||||
<string name="repo_install_title">מתקין %1$s %2$s(%3$d)</string>
|
||||
<string name="download">הורדה</string>
|
||||
<string name="reboot">הפעלה מחדש</string>
|
||||
<string name="close">סגירה</string>
|
||||
<string name="release_notes">הערות שחרור</string>
|
||||
<string name="flashing">צורב…</string>
|
||||
<string name="running">רץ…</string>
|
||||
<string name="done">בוצע!</string>
|
||||
<string name="failure">נכשל</string>
|
||||
<string name="done_action">בוצעה ריצת פעולה של %1$s</string>
|
||||
<string name="failure">נכשל!</string>
|
||||
<string name="hide_app_title">מסתיר את יישום Magisk…</string>
|
||||
<string name="open_link_failed_toast">לא נמצאו יישומים לפתיחת קישור זה</string>
|
||||
<string name="complete_uninstall">הסרה מלאה</string>
|
||||
@@ -237,4 +243,5 @@
|
||||
<string name="app_not_found">לא נמצא יישום לטיפול בפעולה זו</string>
|
||||
<string name="reboot_apply_change">ייש להפעיל מחדש כדי להחיל שינויים</string>
|
||||
<string name="restore_app_confirmation">פעולה זו תשחזר את היישום המוסתר חזרה ליישום המקורי. האם בוודאות ברצונך לעשות את זה?</string>
|
||||
|
||||
</resources>
|
||||
|
||||
252
app/core/src/main/res/values-ku/strings.xml
Normal file
252
app/core/src/main/res/values-ku/strings.xml
Normal file
@@ -0,0 +1,252 @@
|
||||
<resources>
|
||||
|
||||
<!--Sections-->
|
||||
<string name="modules">زیادکراوەکان</string>
|
||||
<string name="superuser">سوپەر یوسەر</string>
|
||||
<string name="logs">تۆمارەکان</string>
|
||||
<string name="settings">ڕێکخستنەکان</string>
|
||||
<string name="install">دامەزراندن</string>
|
||||
<string name="section_home">ماڵەوە</string>
|
||||
<string name="section_theme">ڕووکارەکان</string>
|
||||
<string name="denylist">پێڕستی ڕێگەپێنەدراوەکان</string>
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">هێڵ بەردەست نییە</string>
|
||||
<string name="app_changelog">گۆڕانکارییەکان</string>
|
||||
<string name="loading">کردنەوە…</string>
|
||||
<string name="update">بەرزکردنەوە</string>
|
||||
<string name="not_available">نییە</string>
|
||||
<string name="hide">شاردنەوە</string>
|
||||
<string name="home_package">پاکێج</string>
|
||||
<string name="home_app_title">ئەپ</string>
|
||||
|
||||
<string name="home_notice_content">تەنها لە گیتهەبی فەرمی ماجیسک دابگرە، لە شوێنی تر لەوانەیە زیانبەخش بێت</string>
|
||||
<string name="home_support_title">پشتگیریمان بکە</string>
|
||||
<string name="home_follow_title">شوێنمان بکەوە</string>
|
||||
<string name="home_item_source">سەرچاوە</string>
|
||||
<string name="home_support_content">ماجیسک بە خۆڕاییە و هەر واش ئەمێنێتەوە، بەهەرحاڵ ئەتوانیت پشتگیرییەکمان بکەی بۆ گرنگی پێدان</string>
|
||||
<string name="home_installed_version">داگیراوە</string>
|
||||
<string name="home_latest_version">دوایین وەشان</string>
|
||||
<string name="invalid_update_channel">کەناڵێکی نوێکردنەوەی هەڵە</string>
|
||||
<string name="uninstall_magisk_title">سڕینەوەی ماجیسک</string>
|
||||
<string name="uninstall_magisk_msg">هەموو زیادکراوەکان دەسڕێنەوە، ڕۆت دەسڕێتەوە، و هەر ڕەمزێنراوێک بەهۆی ماجیسک کرابێ لادەچێت!</string>
|
||||
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">ڕەمزاندنی بەزۆر بهێڵەوە</string>
|
||||
<string name="keep_dm_verity">بهێڵەوە AVB 2.0/dm-verity</string>
|
||||
<string name="recovery_mode">دۆخی ڕیکەڤەڕی</string>
|
||||
<string name="install_options_title">هەڵبژاردنەکان</string>
|
||||
<string name="install_method_title">ڕێگای</string>
|
||||
<string name="install_next">دواتر</string>
|
||||
<string name="install_start">با بیکەین</string>
|
||||
<string name="manager_download_install">بۆ داگرتن و ڕێکخستن کرتە بکە</string>
|
||||
<string name="direct_install">داگرتنی ڕاستەوخۆ(پێشنیارکراوە)</string>
|
||||
<string name="install_inactive_slot">دایبگرە بۆ خانەی ناچالاک(پاش OTA)</string>
|
||||
<string name="install_inactive_slot_msg">ئێستا ئامێرەکەت دەچێتە خانە ناچالاکەکە، ئەمە بەکاربهێنە تەنها دوای ئەپدەیت کردن لە ڕێگەی OTA، بەردەوام دەبیت؟</string>
|
||||
<string name="setup_title">ڕێکخستنی زیاتر</string>
|
||||
<string name="select_patch_file">فایلێک هەڵبژێرە و پینەی بکە</string>
|
||||
<string name="patch_file_msg">تکایە فایلێکی Tar یان img یان payload.bin هەڵبژێرە</string>
|
||||
<string name="reboot_delay_toast">ڕێستارت کردنەوە لە ماوەی ٥ چرکە…</string>
|
||||
<string name="flash_screen_title">ڕێکخستن</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">داواکاری سوپەریوسەر</string>
|
||||
<string name="touch_filtered_warning">ئەپێک لە سەر شاشەکەیە، ناتوانین دڵنیا بینەوە</string>
|
||||
<string name="deny">ڕەتکردنەوە</string>
|
||||
<string name="prompt">داواکاری</string>
|
||||
<string name="grant">ڕێگەپێدان</string>
|
||||
<string name="su_warning">ڕێگەپێدان بۆ تەواوی ئامێرەکەت، گەر دڵنیا نیت ڕەتی بکەوە</string>
|
||||
<string name="forever">بۆ هەمیشە</string>
|
||||
<string name="once">بۆ یەکجار</string>
|
||||
<string name="tenmin">بۆ ١٠ خولەک</string>
|
||||
<string name="twentymin">بۆ ٢٠ خولەک</string>
|
||||
<string name="thirtymin">بۆ ٣٠ خولەک</string>
|
||||
<string name="sixtymin">بۆ ٦٠ خولەک</string>
|
||||
<string name="su_allow_toast">%1$s ڕێگەپێدانی سوپەریوسەری بۆ زیادکرا</string>
|
||||
<string name="su_deny_toast">%1$s ڕێگەپێدانی سوپەریوسەر ڕەتکرایەوە</string>
|
||||
<string name="su_snack_grant">ڕێگەپێدانی سوپەریوسەری %1$s بۆ درا</string>
|
||||
<string name="su_snack_deny">ڕێگەپێدانی سوپەریوسەر %1$s ڕەتکرایەوە</string>
|
||||
<string name="su_snack_notif_on">ئاگەدارکردنەوەکانی %1$s کارا کراوە</string>
|
||||
<string name="su_snack_notif_off">ئاگەدارکردنەوەکانی %1$s کوژاوەتەوە</string>
|
||||
<string name="su_snack_log_on">تۆمارەکانی %1$s کراوەتەوە</string>
|
||||
<string name="su_snack_log_off">تۆمارەکانی %1$s کوژاوەتەوە</string>
|
||||
<string name="su_revoke_title">لابردن؟</string>
|
||||
<string name="su_revoke_msg">دڵنیابەوە بۆ لابردنی سوپەریوسەر بۆ %1$s </string>
|
||||
<string name="toast">هێنانەسەر</string>
|
||||
<string name="none">هیچ</string>
|
||||
|
||||
<string name="superuser_toggle_notification">ئاگادارییەکان</string>
|
||||
<string name="superuser_toggle_revoke">لابردن</string>
|
||||
<string name="superuser_policy_none">هیچ ئەپێک تا ئێستا داوای سوپەریوسەری نەکردووە</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">هیچ تۆمارێک نییە، ئەو ئەپانەی ڕۆتیان پێویستە زوزو بەکاریبێنە</string>
|
||||
<string name="log_data_magisk_none">تۆمارەکانی ماجیسک بەتاڵن، باشە بۆ؟</string>
|
||||
<string name="menuSaveLog">تۆمارەکان هەڵبگرە</string>
|
||||
<string name="menuClearLog">تۆمارەکان بسڕەوە</string>
|
||||
<string name="logs_cleared">بەسەرکەوتویی تۆمارەکان سڕانەوە</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">ئامانج UID: %1$d</string>
|
||||
<string name="target_pid">Mount ns target PID: %s</string>
|
||||
<string name="selinux_context">SELinux context: %s</string>
|
||||
<string name="supp_group">Supplementary group: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">پیشاندانی ئەپەکانی سیستەم</string>
|
||||
<string name="show_os_app">پیشاندانی ئەپەکان</string>
|
||||
<string name="hide_filter_hint">پاڵاوتنی بەپێی ناو</string>
|
||||
<string name="hide_search">گەڕان</string>
|
||||
|
||||
<!--Module-->
|
||||
<string name="no_info_provided">(هیچ زانیارییەک نییە)</string>
|
||||
<string name="reboot_userspace">ڕێستارت کردنەوە</string>
|
||||
<string name="reboot_recovery">چوونە ناو ڕیکەڤەری</string>
|
||||
<string name="reboot_bootloader">چوونە ناو بووتلۆدەر</string>
|
||||
<string name="reboot_download">چوونە ناو داونلۆد</string>
|
||||
<string name="reboot_edl">چوونە ناو EDL</string>
|
||||
<string name="reboot_safe_mode">دۆخی پارێزراو</string>
|
||||
<string name="module_version_author">%1$s by %2$s</string>
|
||||
<string name="module_state_remove">سڕینەوە</string>
|
||||
<string name="module_action">کارا</string>
|
||||
<string name="module_state_restore">گەڕاندنەوە</string>
|
||||
<string name="module_action_install_external">لە بیرگەکەتەوە ڕێکی بخە</string>
|
||||
<string name="update_available">وەشانی نوێ بەردەستە</string>
|
||||
<string name="suspend_text_riru">زیادکراوەکە کار ناکات چونکە %1$s کراوەتەوە</string>
|
||||
<string name="suspend_text_zygisk">زیادکراوەکە کارناکات چونکە %1$s نەکراوەتەوە</string>
|
||||
<string name="zygisk_module_unloaded">زیادکراوی Zygisk بەهۆی نەگونجان کارناکات</string>
|
||||
<string name="module_empty">هیچ زیادکراوێک دانەبەزیوە</string>
|
||||
<string name="confirm_install">دابەزاندنی زیادکراو %1$s?</string>
|
||||
<string name="confirm_install_title">دڵنیابوونەوە لە دابەزاندن</string>
|
||||
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">جۆری ڕووکار</string>
|
||||
<string name="settings_dark_mode_message">حەزت لە کامەی بوو ئەوە هەڵبژێرە</string>
|
||||
<string name="settings_dark_mode_light">هەمیشە دۆخی ڕوناک</string>
|
||||
<string name="settings_dark_mode_system">با بەگوێرەی سیستەمەکە بێت!</string>
|
||||
<string name="settings_dark_mode_dark">هەمیشە دۆخی تاریک</string>
|
||||
<string name="settings_download_path_title">شوێنی داگرتنەکە</string>
|
||||
<string name="settings_download_path_message">فایلەکان هەڵدەگیرێن لە %1$s</string>
|
||||
<string name="settings_hide_app_title">شاردنەوەی ئەپی ماجیسک</string>
|
||||
<string name="settings_hide_app_summary">داگرتنی ماجیسک بە ناوی جیاوە</string>
|
||||
<string name="settings_restore_app_title">ئەپە ڕەسەنەکە بهێنەوە</string>
|
||||
<string name="settings_restore_app_summary">ئەپەکە دەربخەوە و ڕەسەنڵ</string>
|
||||
<string name="language">زمان</string>
|
||||
<string name="system_default">(وەک هی ئامێرەکە)</string>
|
||||
<string name="settings_check_update_title">گەڕان بەدوای نوێکاری</string>
|
||||
<string name="settings_check_update_summary">گەڕان بەدوای نوێکاری خۆکارانە</string>
|
||||
<string name="settings_update_channel_title">کەناڵی نوێکاری</string>
|
||||
<string name="settings_update_stable">جێگیر</string>
|
||||
<string name="settings_update_beta">پێشوەختە(بێتا)</string>
|
||||
<string name="settings_update_custom">تایبەت</string>
|
||||
<string name="settings_update_custom_msg">بەستەرێکی تایبەت دابنێ</string>
|
||||
<string name="settings_zygisk_summary">کارپێکردنی بەشێکی ماجیسک لە zygote daemon</string>
|
||||
<string name="settings_denylist_title">پێڕستی نەرێنی کراوەکان</string>
|
||||
<string name="settings_denylist_summary">هەر ئەپێک لە پێڕستی نەرێنییەکان کاریگەریەکانی ماجیسکی لەسەر نییە</string>
|
||||
<string name="settings_denylist_config_title">دەستکاریکردنی پێڕستی نەرێنیکراوەکان</string>
|
||||
<string name="settings_denylist_config_summary">ئەو ئەپە هەڵبژێرە کە دەتەوێت نەرێنیی بکەیت</string>
|
||||
<string name="settings_hosts_title">هۆستی ناسیستەمی</string>
|
||||
<string name="settings_hosts_summary"> هۆستی ناسیستەمی بۆ لابردنی ڕیکلامەکان</string>
|
||||
<string name="settings_hosts_toast"> هۆستی ناسیستەمی زیادکرا</string>
|
||||
<string name="settings_app_name_hint">ناوی نوێ</string>
|
||||
<string name="settings_app_name_helper">ئەپەکە بەم ناوەوە دروست دەکرێتەوە</string>
|
||||
<string name="settings_app_name_error">هەڵەیە</string>
|
||||
<string name="settings_su_app_adb">ئەپەکان و ADB</string>
|
||||
<string name="settings_su_app">تەنها ئەپەکان</string>
|
||||
<string name="settings_su_adb">ADB تەنها</string>
|
||||
<string name="settings_su_disable">ناچالاک کراوە</string>
|
||||
<string name="settings_su_request_10">10 چرکە</string>
|
||||
<string name="settings_su_request_15">15 چرکە</string>
|
||||
<string name="settings_su_request_20">20 چرکە</string>
|
||||
<string name="settings_su_request_30">30 چرکە</string>
|
||||
<string name="settings_su_request_45">45 چرکە</string>
|
||||
<string name="settings_su_request_60">60 چرکە</string>
|
||||
<string name="superuser_access">دەسەڵاتی سوپەریوسەر</string>
|
||||
<string name="auto_response">وەڵامدانەوەی خۆکارانە</string>
|
||||
<string name="request_timeout">ماوەی وەڵامدانەوە</string>
|
||||
<string name="superuser_notification">ئاگەدارییەکانی سوپەریوسەر</string>
|
||||
<string name="settings_su_reauth_title">پرسیاربکەوە دوای هەر نوێکردنەوەیەک</string>
|
||||
<string name="settings_su_reauth_summary">دوای نوێکردنەوەی ئەپەکان دووبارە پرسیار بکەوە بۆ دەسەڵاتی سوپەریوسەر</string>
|
||||
<string name="settings_su_tapjack_title">پارێزگاری کردن لە ئەگەری دەستلێدانی تر</string>
|
||||
<string name="settings_su_tapjack_summary"> کاتێک ئەپێکی تر بەسەر شاشەکەوەیە، سوپەر یوسەر وەڵام ناداتەوە لە کردنی هەر بژاردەیەک لەبەر پارێزراوی</string>
|
||||
<string name="settings_su_auth_title">دڵنیاکردنەوەی کەسی</string>
|
||||
<string name="settings_su_auth_summary">داواکاری بکە بۆ دڵنیاکردنەوەی کەسی لەکاتی داواکاری سوپەریوسەر</string>
|
||||
<string name="settings_su_auth_insecure">هیچ دڵنیاکردنەوەیەک نییە</string>
|
||||
<string name="settings_customization">دەستکاریکردن</string>
|
||||
<string name="setting_add_shortcut_summary">یەک ئایکۆنی جوان زیادبکە بۆ سەر شاشەکە ئەگەر قورس بوو ئەوەی خۆی بدۆزیتەوە</string>
|
||||
<string name="settings_doh_title">DNS بەسەر HTTPS</string>
|
||||
<string name="settings_doh_description">Workaround DNS خراپە لە هەندێک شوێن</string>
|
||||
<string name="settings_random_name_title">ناوێک لەخۆیەوە</string>
|
||||
<string name="settings_random_name_description">دانانی ناوێک لەخۆوە تاوەکوو ئاشکرا نەبێت</string>
|
||||
|
||||
<string name="multiuser_mode">دۆخی فرەبەکارهێنەر</string>
|
||||
<string name="settings_owner_only">تەنها خاوەنی ئامێر</string>
|
||||
<string name="settings_owner_manage">خاوەنی ئامێر</string>
|
||||
<string name="settings_user_independent">بەکارهێنەری سەربەخۆ</string>
|
||||
<string name="owner_only_summary">تەنها خاوەنەکە دۆخی ڕۆتی هەیە</string>
|
||||
<string name="owner_manage_summary">تەنها خاوەنەکە دەسەڵاتی بەکارهێنانی ڕۆتی هەیە</string>
|
||||
<string name="user_independent_summary">هەر بەکارهێنەرێک یاسای جیاوازی هەیە</string>
|
||||
|
||||
<string name="mount_namespace_mode">چونە دۆخی بۆشایی ناو</string>
|
||||
<string name="settings_ns_global">بۆشاییناوی گشتی</string>
|
||||
<string name="settings_ns_requester">بۆشایی ناوی خۆیی</string>
|
||||
<string name="settings_ns_isolate">بۆشایی ناوی جیا</string>
|
||||
<string name="global_summary">هەمو ڕۆتەکان ناوی گشتی بەکار ئەهێنن</string>
|
||||
<string name="requester_summary">هەمو ڕۆتەکان ناوی خۆیی بەکار ئەهێنن</string>
|
||||
<string name="isolate_summary">هەمو ڕۆتەکان ناوی جیا بەکار ئەهێنن</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">نوێکردنەوەکانی ماجیسک</string>
|
||||
<string name="progress_channel">ئاگادارییە کاراکان</string>
|
||||
<string name="updated_channel">نوێکردنەوە سەرکەوتووبوو</string>
|
||||
<string name="download_complete">داگرتن سەرکەوتووبوو</string>
|
||||
<string name="download_file_error">هەڵەیەک رووی دا لەکاتی داگرتنی فایلەکە</string>
|
||||
<string name="magisk_update_title">وەشانی نوێی ماجیسک ئامادەیە!</string>
|
||||
<string name="updated_title">ماجیسک نوێکراوە!</string>
|
||||
<string name="updated_text">کرتە بکە بۆ کردنەوەی ئەپ</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">بەڵێ</string>
|
||||
<string name="no">نەخێر</string>
|
||||
<string name="repo_install_title">داگرتن %1$s %2$s(%3$d)</string>
|
||||
<string name="download">داگرتن</string>
|
||||
<string name="reboot">ڕێستارت</string>
|
||||
<string name="close">داخستن</string>
|
||||
<string name="release_notes">نێبینییەکان</string>
|
||||
<string name="flashing">فلاش کردن</string>
|
||||
<string name="running">کار کردن...</string>
|
||||
<string name="done">تەواو!</string>
|
||||
<string name="done_action">کارکردنی %1$s تەواو بوو</string>
|
||||
<string name="failure">Failed!</string>
|
||||
<string name="hide_app_title">شاردنەوەی ئەپی ماجیسک…</string>
|
||||
<string name="open_link_failed_toast">هیچ ئەپێک تییە تا لینکەکەی پێ بکرێتەوە</string>
|
||||
<string name="complete_uninstall">سڕینەوەی تەواوی</string>
|
||||
<string name="restore_img">هێنانەوەی img</string>
|
||||
<string name="restore_img_msg">هێنانەوە…</string>
|
||||
<string name="restore_done">هاتەوە!</string>
|
||||
<string name="restore_fail">هیچ فایلێکی هەڵگیراوت نیە!</string>
|
||||
<string name="setup_fail">ڕێکخستن شکستی هێنا</string>
|
||||
<string name="env_fix_title">پێویستی بە ڕێکخستنی زیاترە</string>
|
||||
<string name="env_fix_msg">مۆبایلەکەت پێویستی بە ڕێکخستنی زیاترە، ئایا ئەتەوێت بەردەوام بیت و ڕێستارتی بکەیتەوە؟</string>
|
||||
<string name="env_full_fix_msg"> پێویستە دوبارە ماجیسک دابگریتەوە بۆ ئەوەی بەباشی کاربکات تکایە ماجیسک دابگرەوە لەناو ئەپەکە خۆی چونکە لە ڕیکەڤەرییەوە ناتوانرێ زانیاری تەواو لەسەر ئامێرەکە دەستبخرێت </string>
|
||||
<string name="setup_msg">دەستپێکردن....</string>
|
||||
<string name="unsupport_magisk_title">وەشانی ماجیسک پاڵپشتینەکراوە</string>
|
||||
<string name="unsupport_magisk_msg">وەشانی ماجیسکەکەت زۆر کۆنە وەک ئەوە وایە هەر نەبێت، تکایە نوێی بکەوە بە زوترین کات</string>
|
||||
<string name="unsupport_general_title">باری نائاسایی</string>
|
||||
<string name="unsupport_system_app_msg">ئەم ئەپە وەکو ئەپی سیستەم کارناکات، تکایە بیگۆڕەوە بۆ ئەپی ئاسایی</string>
|
||||
<string name="unsupport_other_su_msg"> \"su\" binary یەکی بێگانە دۆزرایەوە، تکایە جگە لە ماجیسک ئەپی تر بەکارمەهێنە بۆ ڕۆت کردن </string>
|
||||
<string name="unsupport_external_storage_msg">ماجیسک لە بیرگەی دەرەکی داگیراوە، تکایە بیبەوە بۆ ناوەکی</string>
|
||||
<string name="unsupport_nonroot_stub_msg">ئەپە شاراوەکە کار ناکات چونکە ڕۆتەکە نەماوە، تکایە ئەپە ڕەسەنەکە بگەڕێنەوە</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">ڕەزامەندی بیرگە بدە تاوەکوو ئەمە کار بکات</string>
|
||||
<string name="post_notifications_denied">ڕەزامەندی ئاگاداری بکە تاوەکوو ئەمە کار بکات</string>
|
||||
<string name="install_unknown_denied">ڕەزامەندی "install unknown apps" تاوەکوو کار بکات</string>
|
||||
<string name="add_shortcut_title">زیادی بکە بۆ سەر شاشە</string>
|
||||
<string name="add_shortcut_msg">دوای شاردنەوەی ئەم ئەپە، ئەتەوێت یەک ئایکۆنی جوان زیادبکەیت بۆ سەر شاشەکە ئەگەر قورس بوو ئەوەی خۆی بدۆزیتەوە؟</string>
|
||||
<string name="app_not_found">هیچ ئەپێک نەدۆزرایەوە تاوەکوو ئەم کارەی پێ بکرێت</string>
|
||||
<string name="reboot_apply_change">ڕێستارت بکە تاوەکوو کاریگەریەکان کار بکەن</string>
|
||||
<string name="restore_app_confirmation">ئەمە ئەپە ڕەسەنەکە ئەهێنێتەوە، دڵنیایت لە کردنی؟</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<string name="reboot_safe_mode">Modo de segurança</string>
|
||||
<string name="module_version_author">%1$s por %2$s</string>
|
||||
<string name="module_state_remove">Remover</string>
|
||||
<string name="module_action">Ação</string>
|
||||
<string name="module_state_restore">Restaurar</string>
|
||||
<string name="module_action_install_external">Instalar a partir do armazenamento</string>
|
||||
<string name="update_available">Atualização disponível</string>
|
||||
@@ -173,7 +174,7 @@
|
||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||
<string name="settings_doh_description">Solução alternativa para envenenamento de DNS em alguns países</string>
|
||||
<string name="settings_random_name_title">Randomizar nome de saída</string>
|
||||
<string name="settings_random_name_description">Randomize o nome do arquivo de saída de imagens corrigidas e arquivos tar para evitar a detecção</string>
|
||||
<string name="settings_random_name_description">Randomize o nome do arquivo de saída de imagens corrigidas e arquivos tar (*.tar) para evitar a detecção</string>
|
||||
<string name="multiuser_mode">Modo multiusuário</string>
|
||||
<string name="settings_owner_only">Somente proprietário do dispositivo</string>
|
||||
<string name="settings_owner_manage">Gerenciado pelo proprietário do dispositivo</string>
|
||||
@@ -205,9 +206,12 @@
|
||||
<string name="repo_install_title">Instalar %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Baixar</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="close">Fechar</string>
|
||||
<string name="release_notes">Notas da atualização</string>
|
||||
<string name="flashing">Flashando…</string>
|
||||
<string name="running">Executando…</string>
|
||||
<string name="done">Concluído!</string>
|
||||
<string name="done_action">Ação de execução de %1$s concluída</string>
|
||||
<string name="failure">Falhou!</string>
|
||||
<string name="hide_app_title">Ocultando o app do Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nenhum app encontrado para abrir o link</string>
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<string name="reboot_safe_mode">Modo de segurança</string>
|
||||
<string name="module_version_author">%1$s por %2$s</string>
|
||||
<string name="module_state_remove">Remover</string>
|
||||
<string name="module_action">Ação</string>
|
||||
<string name="module_state_restore">Restaurar</string>
|
||||
<string name="module_action_install_external">Instalar a partir do armazenamento</string>
|
||||
<string name="update_available">Atualização disponível</string>
|
||||
@@ -173,7 +174,7 @@
|
||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||
<string name="settings_doh_description">Solução alternativa para envenenamento de DNS em alguns países</string>
|
||||
<string name="settings_random_name_title">Randomizar nome de saída</string>
|
||||
<string name="settings_random_name_description">Randomize o nome do arquivo de saída de imagens corrigidas e arquivos tar para evitar a detecção</string>
|
||||
<string name="settings_random_name_description">Randomize o nome do arquivo de saída de imagens corrigidas e arquivos tar (*.tar) para evitar a detecção</string>
|
||||
<string name="multiuser_mode">Modo multiusuário</string>
|
||||
<string name="settings_owner_only">Somente proprietário do dispositivo</string>
|
||||
<string name="settings_owner_manage">Gerenciado pelo proprietário do dispositivo</string>
|
||||
@@ -205,9 +206,12 @@
|
||||
<string name="repo_install_title">Instalar %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Baixar</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="close">Fechar</string>
|
||||
<string name="release_notes">Notas da atualização</string>
|
||||
<string name="flashing">Flashando…</string>
|
||||
<string name="running">Executando…</string>
|
||||
<string name="done">Concluído!</string>
|
||||
<string name="done_action">Ação de execução de %1$s concluída</string>
|
||||
<string name="failure">Falhou!</string>
|
||||
<string name="hide_app_title">Ocultando o app do Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nenhum app encontrado para abrir o link</string>
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
<string name="logs_cleared">Логи успешно очищены</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Целевой UID: %1$d</string>
|
||||
<string name="target_pid">Целевой PID пространства имён: %s</string>
|
||||
<string name="selinux_context">Контекст SELinux: %s</string>
|
||||
<string name="supp_group">Дополнительная группа: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
@@ -164,11 +167,16 @@
|
||||
<string name="settings_su_reauth_title">Повторная аутентификация</string>
|
||||
<string name="settings_su_reauth_summary">Повторный запрос прав суперпользователя после обновления приложений</string>
|
||||
<string name="settings_su_tapjack_title">Защита от перехвата нажатий</string>
|
||||
<string name="settings_su_auth_title">Аутентификация пользователя</string>
|
||||
<string name="settings_su_auth_summary">Требовать аутентификацию пользователя при запросах Superuser</string>
|
||||
<string name="settings_su_auth_insecure">На устройстве не настроен метод аутентификации</string>
|
||||
<string name="settings_su_tapjack_summary">Окно запроса прав суперпользователя будет неактивно пока активированы наложения экрана</string>
|
||||
<string name="settings_customization">Персонализация</string>
|
||||
<string name="setting_add_shortcut_summary">Добавить ярлык на рабочий стол для удобного восприятия приложения после его скрытия</string>
|
||||
<string name="settings_doh_title">DNS поверх HTTPS</string>
|
||||
<string name="settings_doh_description">Активировать DoH (используйте при проблемах с подключением к сети)</string>
|
||||
<string name="settings_random_name_title">Случайное имя образа</string>
|
||||
<string name="settings_random_name_description">Генерировать случайные имена для патченных образов и tar-файлов для предотвращения обнаружения</string>
|
||||
|
||||
<string name="multiuser_mode">Многопользовательский режим</string>
|
||||
<string name="settings_owner_only">Только администратор</string>
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
<string name="su_revoke_msg">Konfirmo për të hequr të drejtat e %1$s?</string>
|
||||
<string name="toast">Dolli</string>
|
||||
<string name="none">Asnjë</string>
|
||||
|
||||
<string name="superuser_toggle_notification">Njoftimet</string>
|
||||
<string name="superuser_toggle_revoke">Të drejtat</string>
|
||||
<string name="superuser_policy_none">Asnjë aplikacion nuk ka kërkuar akoma akses për super-përdoruesin.</string>
|
||||
@@ -91,8 +90,6 @@
|
||||
<string name="selinux_context">Konteksti SELinux: %s</string>
|
||||
<string name="supp_group">Grupi suplementar: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Shfaq aplikacionet e sistemit</string>
|
||||
<string name="show_os_app">Shfaq aplikacionet e sistemit operativ</string>
|
||||
@@ -109,6 +106,7 @@
|
||||
<string name="reboot_safe_mode">Rinis në safe mode</string>
|
||||
<string name="module_version_author">%1$s nga %2$s</string>
|
||||
<string name="module_state_remove">Hiqe</string>
|
||||
<string name="module_action">Veprim</string>
|
||||
<string name="module_state_restore">Rikëthe</string>
|
||||
<string name="module_action_install_external">Instaloni nga sdcard</string>
|
||||
<string name="update_available">Përditësimi në dispozicion</string>
|
||||
@@ -207,9 +205,12 @@
|
||||
<string name="repo_install_title">Instalo %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Shkarko</string>
|
||||
<string name="reboot">Rinis</string>
|
||||
<string name="close">Mbylle</string>
|
||||
<string name="release_notes">Shënimet e lëshimit</string>
|
||||
<string name="flashing">Duke flashuar…</string>
|
||||
<string name="running">Duke vepruar...</string>
|
||||
<string name="done">U krye!</string>
|
||||
<string name="done_action">Veprimi i ekzekutimit të %1$s u krye</string>
|
||||
<string name="failure">Dështoi!</string>
|
||||
<string name="hide_app_title">Fshehja e aplikacionit Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nuk u gjet asnjë aplikacion për të hapur lidhjen</string>
|
||||
|
||||
@@ -12,21 +12,21 @@
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">Bağlantı yok</string>
|
||||
<string name="app_changelog">Değişiklik günlüğü</string>
|
||||
<string name="app_changelog">Değişiklik Günlüğü</string>
|
||||
<string name="loading">Yükleniyor…</string>
|
||||
<string name="update">Güncelle</string>
|
||||
<string name="not_available">Yüklü değil</string>
|
||||
<string name="not_available">Yok</string>
|
||||
<string name="hide">Gizle</string>
|
||||
<string name="home_package">Paket</string>
|
||||
<string name="home_app_title">Uygulama</string>
|
||||
|
||||
<string name="home_notice_content">Magisk\'i YALNIZCA resmi GitHub sayfasından indirin. Bilinmeyen kaynaklardan gelen dosyalar kötü amaçlı olabilir!</string>
|
||||
<string name="home_notice_content">Magisk\'i YALNIZCA resmi GitHub sayfasından indirin. Bilinmeyen kaynaklardan gelen dosyalar zararlı olabilir!</string>
|
||||
<string name="home_support_title">Bizi Destekleyin</string>
|
||||
<string name="home_follow_title">Bizi Takip Edin</string>
|
||||
<string name="home_item_source">Kaynak</string>
|
||||
<string name="home_support_content">Magisk ücretsiz ve açık kaynaktır ve her zaman öyle kalacaktır. Ancak bir bağış yaparak bize destek olduğunuzu gösterebilirsiniz.</string>
|
||||
<string name="home_installed_version">Durum</string>
|
||||
<string name="home_latest_version">En son sürüm</string>
|
||||
<string name="home_support_content">Magisk her zaman ücretsiz ve açık kaynak olacaktır. Ancak, bir bağış yaparak bize destek olabilirsiniz.</string>
|
||||
<string name="home_installed_version">Yüklü Sürüm</string>
|
||||
<string name="home_latest_version">En Son Sürüm</string>
|
||||
<string name="invalid_update_channel">Geçersiz Güncelleme Kanalı</string>
|
||||
<string name="uninstall_magisk_title">Magisk\'i Kaldır</string>
|
||||
<string name="uninstall_magisk_msg">Tüm modüller devre dışı bırakılacak/kaldırılacak!\nKök kaldırılacak!\nMagisk kullanılarak şifrelenmemiş herhangi bir dahili depolama yeniden şifrelenecek!</string>
|
||||
@@ -38,14 +38,14 @@
|
||||
<string name="install_options_title">Seçenekler</string>
|
||||
<string name="install_method_title">Yöntem</string>
|
||||
<string name="install_next">Sonraki</string>
|
||||
<string name="install_start">Haydi başlayalım</string>
|
||||
<string name="install_start">Hadi başlayalım</string>
|
||||
<string name="manager_download_install">İndirmek ve yüklemek için basın</string>
|
||||
<string name="direct_install">Doğrudan Kurulum (Önerilir)</string>
|
||||
<string name="install_inactive_slot">Etkin Olmayan Yuvaya Yükle (OTA\'dan Sonra)</string>
|
||||
<string name="install_inactive_slot_msg">Yeniden başlatmanın ardından cihazınız mevcut etkin olmayan yuvaya önyükleme yapmaya ZORLANACAK!\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın.\nDevam edilsin mi?</string>
|
||||
<string name="direct_install">Doğrudan Yükleme (Önerilir)</string>
|
||||
<string name="install_inactive_slot">Etkin Olmayan Slot\'a Yükle (OTA Sonrası)</string>
|
||||
<string name="install_inactive_slot_msg">Cihazınız yeniden başlatıldıktan sonra zorunlu olarak mevcut etkin olmayan slota önyükleme yapılacaktır!\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın.\nDevam etmek istiyor musunuz?</string>
|
||||
<string name="setup_title">Ek Kurulum</string>
|
||||
<string name="select_patch_file">Bir Dosya Seç ve Yama Yap</string>
|
||||
<string name="patch_file_msg">Bir ham görüntü (*.img) veya bir ODIN tar dosyası (*.tar) veya bir payload.bin (*.bin) seçin</string>
|
||||
<string name="patch_file_msg">Ham bir görüntü (*.img) veya bir ODIN tar dosyası (*.tar) veya bir payload.bin (*.bin) seçin</string>
|
||||
<string name="reboot_delay_toast">5 saniye içinde yeniden başlatılıyor…</string>
|
||||
<string name="flash_screen_title">Yükleniyor</string>
|
||||
|
||||
@@ -53,42 +53,42 @@
|
||||
<string name="su_request_title">Süper Kullanıcı İsteği</string>
|
||||
<string name="touch_filtered_warning">Bir uygulama bir Süper Kullanıcı isteğini engellediği için Magisk yanıtınızı doğrulayamıyor</string>
|
||||
<string name="deny">Reddet</string>
|
||||
<string name="prompt">Sor</string>
|
||||
<string name="grant">İzin ver</string>
|
||||
<string name="su_warning">Cihazınıza tam erişim sağlar.\nEğer emin değilseniz reddedin!</string>
|
||||
<string name="prompt">İstem</string>
|
||||
<string name="grant">İzin Ver</string>
|
||||
<string name="su_warning">Cihazınıza tam erişim sağlar.\nEmin değilseniz reddedin!</string>
|
||||
<string name="forever">Daima</string>
|
||||
<string name="once">Bir kez</string>
|
||||
<string name="tenmin">10 dakika</string>
|
||||
<string name="twentymin">20 dakika</string>
|
||||
<string name="thirtymin">30 dakika</string>
|
||||
<string name="sixtymin">60 dakika</string>
|
||||
<string name="su_allow_toast">%1$s uygulamasının Süper Kullanıcı izni verildi</string>
|
||||
<string name="su_deny_toast">%1$s uygulamasının Süper Kullanıcı izni reddedildi</string>
|
||||
<string name="su_snack_grant">%1$s uygulamasının Süper Kullanıcı izni verildi</string>
|
||||
<string name="su_snack_deny">%1$s uygulamasının Süper Kullanıcı izni reddedildi</string>
|
||||
<string name="su_allow_toast">%1$s uygulamasının süper kullanıcı hakları verildi</string>
|
||||
<string name="su_deny_toast">%1$s uygulamasının süper kullanıcı hakları reddedildi</string>
|
||||
<string name="su_snack_grant">%1$s uygulamasının süper kullanıcı hakları verildi</string>
|
||||
<string name="su_snack_deny">%1$s uygulamasının süper kullanıcı hakları reddedildi</string>
|
||||
<string name="su_snack_notif_on">%1$s uygulamasının bildirimleri etkinleştirildi</string>
|
||||
<string name="su_snack_notif_off">%1$s uygulamasının bildirimleri devre dışı bırakıldı</string>
|
||||
<string name="su_snack_log_on">%1$s uygulamasının günlüğü etkinleştirildi</string>
|
||||
<string name="su_snack_log_off">%1$s uygulamasının günlüğü devre dışı bırakıldı</string>
|
||||
<string name="su_revoke_title">İptal et?</string>
|
||||
<string name="su_revoke_msg">%1$s uygulamasının Süper Kullanıcı haklarını iptal etmeyi onaylayın</string>
|
||||
<string name="toast">Tost</string>
|
||||
<string name="none">Hiçbiri</string>
|
||||
<string name="su_revoke_title">İptal Et?</string>
|
||||
<string name="su_revoke_msg">%1$s uygulamasının süper kullanıcı haklarını iptal etmek istediğinize emin misiniz?</string>
|
||||
<string name="toast">Bildirim</string>
|
||||
<string name="none">Yok</string>
|
||||
|
||||
<string name="superuser_toggle_notification">Bildirimler</string>
|
||||
<string name="superuser_toggle_revoke">İptal et</string>
|
||||
<string name="superuser_toggle_revoke">İptal Et</string>
|
||||
<string name="superuser_policy_none">Henüz hiçbir uygulama Süper Kullanıcı izni istemedi.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">Günlük kullanmıyorsunuz, kök uygulamalarınızı daha fazla kullanmayı deneyin</string>
|
||||
<string name="log_data_magisk_none">Magisk günlükleri boş, bu garip</string>
|
||||
<string name="log_data_none">Günlük kullanmıyorsunuz, root (kök) uygulamalarınızı daha çok kullanmayı deneyin</string>
|
||||
<string name="log_data_magisk_none">Magisk günlükleri boş, bu tuhaf</string>
|
||||
<string name="menuSaveLog">Günlüğü kaydet</string>
|
||||
<string name="menuClearLog">Günlüğü şimdi temizle</string>
|
||||
<string name="logs_cleared">Günlük kaydı başarıyla temizlendi</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Hedef UID: %1$d</string>
|
||||
<string name="target_pid">Ns hedef PID\'sini bağla: %s</string>
|
||||
<string name="selinux_context">SELinux içeriği: %s</string>
|
||||
<string name="target_pid">Mount ns hedef PID: %s</string>
|
||||
<string name="selinux_context">SELinux bağlamı: %s</string>
|
||||
<string name="supp_group">Ek grup: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
@@ -96,7 +96,7 @@
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Sistem uygulamalarını göster</string>
|
||||
<string name="show_os_app">İşletim sistemi uygulamalarını göster</string>
|
||||
<string name="hide_filter_hint">Ada göre filtrele</string>
|
||||
<string name="hide_filter_hint">İsme göre filtrele</string>
|
||||
<string name="hide_search">Ara</string>
|
||||
|
||||
<!--Module-->
|
||||
@@ -106,13 +106,14 @@
|
||||
<string name="reboot_bootloader">Önyükleyici modunda yeniden başlat</string>
|
||||
<string name="reboot_download">İndirme modunda yeniden başlat</string>
|
||||
<string name="reboot_edl">EDL modunda yeniden başlat</string>
|
||||
<string name="reboot_safe_mode">Güvenli mod</string>
|
||||
<string name="module_version_author">%1$s / %2$s</string>
|
||||
<string name="module_state_remove">Kaldır</string>
|
||||
<string name="module_state_restore">Geri yükle</string>
|
||||
<string name="module_state_restore">Geri Yükle</string>
|
||||
<string name="module_action_install_external">Depolamadan yükle</string>
|
||||
<string name="update_available">Güncelleme Mevcut</string>
|
||||
<string name="suspend_text_riru">%1$s etkinleştirildiği için modül askıya alındı</string>
|
||||
<string name="suspend_text_zygisk">%1$s etkinleştirilmediği için modül askıya alındı</string>
|
||||
<string name="suspend_text_riru">Modül, %1$s etkin olduğu için askıya alındı</string>
|
||||
<string name="suspend_text_zygisk">Modül, %1$s etkin olmadığı için askıya alındı</string>
|
||||
<string name="zygisk_module_unloaded">Uyumsuzluk nedeniyle Zygisk modülü yüklenmedi</string>
|
||||
<string name="module_empty">Yüklü modül yok</string>
|
||||
<string name="confirm_install">%1$s modülü yüklensin mi?</string>
|
||||
@@ -121,39 +122,39 @@
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Tema Modu</string>
|
||||
<string name="settings_dark_mode_message">Tarzınıza en uygun modu seçin!</string>
|
||||
<string name="settings_dark_mode_light">Daima Açık</string>
|
||||
<string name="settings_dark_mode_light">Her Zaman Aydınlık</string>
|
||||
<string name="settings_dark_mode_system">Sistemi Takip Et</string>
|
||||
<string name="settings_dark_mode_dark">Daima Koyu</string>
|
||||
<string name="settings_download_path_title">İndirme yolu</string>
|
||||
<string name="settings_dark_mode_dark">Her Zaman Karanlık</string>
|
||||
<string name="settings_download_path_title">İndirme Yolu</string>
|
||||
<string name="settings_download_path_message">Dosyalar %1$s konumuna kaydedilecek</string>
|
||||
<string name="settings_hide_app_title">Magisk uygulamasını gizle</string>
|
||||
<string name="settings_hide_app_summary">Rastgele bir paket kimliği ve özel uygulama etiketi olan bir proxy uygulaması yükleyin</string>
|
||||
<string name="settings_hide_app_summary">Rastgele bir paket kimliği ve özel uygulama etiketi olan bir vekil (proxy) uygulaması yükleyin</string>
|
||||
<string name="settings_restore_app_title">Magisk uygulamasını geri yükle</string>
|
||||
<string name="settings_restore_app_summary">Uygulamayı göster ve orijinal APK\'yı geri yükle</string>
|
||||
<string name="language">Dil</string>
|
||||
<string name="system_default">(Sistem Varsayılanı)</string>
|
||||
<string name="settings_check_update_title">Güncellemeleri Kontrol Et</string>
|
||||
<string name="settings_check_update_summary">Arka plandaki güncellemeleri düzenli olarak kontrol et</string>
|
||||
<string name="settings_check_update_summary">Arka planda düzenli olarak güncellemeleri kontrol et</string>
|
||||
<string name="settings_update_channel_title">Güncelleme Kanalı</string>
|
||||
<string name="settings_update_stable">Stabil</string>
|
||||
<string name="settings_update_stable">Kararlı</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Özel</string>
|
||||
<string name="settings_update_custom_msg">Özel bir kanal bağlantısı ekle</string>
|
||||
<string name="settings_zygisk_summary">Zygisk arka plan programında Magisk\'in bazı bölümlerini çalıştır</string>
|
||||
<string name="settings_update_custom_msg">Özel kanal URL\'si girin</string>
|
||||
<string name="settings_zygisk_summary">Magisk\'in bazı bölümlerini zygote daemon\'unda çalıştır</string>
|
||||
<string name="settings_denylist_title">Reddetme Listesini Zorla</string>
|
||||
<string name="settings_denylist_summary">Reddetme listesindeki işlemlerde tüm Magisk değişiklikleri geri alınır</string>
|
||||
<string name="settings_denylist_summary">Reddetme Listesindeki işlemler tüm Magisk değişikliklerini geri alacak</string>
|
||||
<string name="settings_denylist_config_title">Reddetme Listesini Yapılandır</string>
|
||||
<string name="settings_denylist_config_summary">Reddetme listesine dahil edilecek işlemleri seç</string>
|
||||
<string name="settings_denylist_config_summary">Reddetme Listesine dahil edilecek işlemleri seçin</string>
|
||||
<string name="settings_hosts_title">Sistemsiz ana makineler (systemless hosts)</string>
|
||||
<string name="settings_hosts_summary">Reklam engelleme uygulamaları için sistemsiz ana makineler (systemless hosts) desteği</string>
|
||||
<string name="settings_hosts_toast">Sistemsiz ana makineler (systemless hosts) modülü eklendi</string>
|
||||
<string name="settings_app_name_hint">Yeni ad</string>
|
||||
<string name="settings_app_name_helper">Uygulama bu adla yeniden paketlenecek</string>
|
||||
<string name="settings_app_name_helper">Uygulama bu isimle yeniden paketlenecek</string>
|
||||
<string name="settings_app_name_error">Geçersiz format</string>
|
||||
<string name="settings_su_app_adb">Uygulamalar ve ADB</string>
|
||||
<string name="settings_su_app">Yalnızca uygulamalar</string>
|
||||
<string name="settings_su_adb">Yalnızca ADB</string>
|
||||
<string name="settings_su_disable">Devre dışı</string>
|
||||
<string name="settings_su_app">Sadece Uygulamalar</string>
|
||||
<string name="settings_su_adb">Sadece ADB</string>
|
||||
<string name="settings_su_disable">Devre Dışı</string>
|
||||
<string name="settings_su_request_10">10 saniye</string>
|
||||
<string name="settings_su_request_15">15 saniye</string>
|
||||
<string name="settings_su_request_20">20 saniye</string>
|
||||
@@ -164,39 +165,41 @@
|
||||
<string name="auto_response">Otomatik Yanıt</string>
|
||||
<string name="request_timeout">İstek Zaman Aşımı</string>
|
||||
<string name="superuser_notification">Süper Kullanıcı Bildirimi</string>
|
||||
<string name="settings_su_reauth_title">Yükseltmeden sonra yeniden kimlik doğrulaması yap</string>
|
||||
<string name="settings_su_reauth_summary">Uygulamaları yükselttikten sonra Süper Kullanıcı izinlerini tekrar iste</string>
|
||||
<string name="settings_su_tapjack_title">Sahte Ekran (Tapjacking) Koruması</string>
|
||||
<string name="settings_su_tapjack_summary">Süper Kullanıcı bilgi istemi iletişim kutusu, herhangi bir başka pencere veya yer paylaşımı tarafından engellendiğinde girişe yanıt vermeyecektir.</string>
|
||||
<string name="settings_su_auth_title">Kullanıcı Kimlik Doğrulaması</string>
|
||||
<string name="settings_su_auth_summary">Süper Kullanıcı istekleri sırasında kullanıcı kimlik doğrulaması iste</string>
|
||||
<string name="settings_su_auth_insecure">Cihazda hiçbir kimlik doğrulama yöntemi yapılandırılmamış</string>
|
||||
<string name="settings_su_reauth_title">Yükseltme sonrası yeniden doğrulama yap</string>
|
||||
<string name="settings_su_reauth_summary">Uygulama güncellemelerinden sonra Süper Kullanıcı izinlerini tekrar iste</string>
|
||||
<string name="settings_su_tapjack_title">Tapjacking Koruması</string>
|
||||
<string name="settings_su_tapjack_summary">Süper Kullanıcı istemi diyalogu, başka bir pencere veya katman tarafından gizlendiğinde girdilere yanıt vermeyecektir</string>
|
||||
<string name="settings_su_auth_title">Kullanıcı Kimlik Doğrulama</string>
|
||||
<string name="settings_su_auth_summary">Süper Kullanıcı isteklerinde kullanıcı kimlik doğrulaması iste</string>
|
||||
<string name="settings_su_auth_insecure">Cihazda yapılandırılmış bir kimlik doğrulama yöntemi yok</string>
|
||||
<string name="settings_customization">Özelleştir</string>
|
||||
<string name="setting_add_shortcut_summary">Uygulamayı gizledikten sonra adın ve simgenin tanınmasının zor olması durumunda ana ekrana güzel bir kısayol ekle</string>
|
||||
<string name="settings_doh_title">HTTPS üzerinden DNS</string>
|
||||
<string name="settings_doh_description">Bazı ülkelerde DNS zehirlenmesine geçici çözüm</string>
|
||||
<string name="setting_add_shortcut_summary">Uygulamayı gizledikten sonra adı ve simgeyi tanımakta zorlanıyorsanız ana ekrana güzel bir kısayol ekle</string>
|
||||
<string name="settings_doh_title">DNS üzerinden HTTPS</string>
|
||||
<string name="settings_doh_description">Bazı ülkelerde DNS zehirlemesine karşı geçici çözüm</string>
|
||||
<string name="settings_random_name_title">Çıkış adını rastgele seç</string>
|
||||
<string name="settings_random_name_description">Algılamayı önlemek için yamalı resimlerin ve tar dosyalarının çıkış dosya adını rastgele seç</string>
|
||||
|
||||
<string name="multiuser_mode">Çok Kullanıcılı Mod</string>
|
||||
<string name="settings_owner_only">Yalnızca Cihaz Sahibi</string>
|
||||
<string name="settings_owner_manage">Cihaz Sahibi Tarafından Yönetildi</string>
|
||||
<string name="settings_owner_manage">Cihaz Sahibi Yönetiminde</string>
|
||||
<string name="settings_user_independent">Kullanıcıdan Bağımsız</string>
|
||||
<string name="owner_only_summary">Kök erişimi yalnızca sahibine aittir</string>
|
||||
<string name="owner_manage_summary">Kök erişimini yalnızca sahip yönetebilir ve istek istemlerini alabilir</string>
|
||||
<string name="user_independent_summary">Her kullanıcının kendi ayrı kök kuralları vardır</string>
|
||||
<string name="owner_only_summary">Yalnızca sahip kök (root) erişimine sahiptir</string>
|
||||
<string name="owner_manage_summary">Yalnızca sahip kök (root) erişimini yönetebilir ve istek uyarılarını alabilir</string>
|
||||
<string name="user_independent_summary">Her kullanıcının kendi ayrı kök (root) kuralları vardır</string>
|
||||
|
||||
<string name="mount_namespace_mode">Bağlama Ad Alanı Modu</string>
|
||||
<string name="settings_ns_global">Küresel Ad Alanı</string>
|
||||
<string name="settings_ns_requester">Ad Alanını Devral</string>
|
||||
<string name="settings_ns_isolate">İzole Edilmiş Ad Alanı</string>
|
||||
<string name="global_summary">Tüm kök oturumları, global bağlama ad alanını kullanır</string>
|
||||
<string name="requester_summary">Kök oturumları, istek sahibinin ad alanını devralır</string>
|
||||
<string name="isolate_summary">Her kök oturumun kendi izole edilmiş ad alanı olacaktır</string>
|
||||
<string name="settings_ns_isolate">İzolasyon Ad Alanı</string>
|
||||
<string name="global_summary">Tüm kök (root) oturumları küresel bağlama ad alanını kullanır</string>
|
||||
<string name="requester_summary">Kök (root) oturumları isteyicisinin ad alanını devralacak</string>
|
||||
<string name="isolate_summary">Her kök (root) oturumu kendi izole ad alanına sahip olacak</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Magisk Güncellemeleri</string>
|
||||
<string name="progress_channel">İlerleme Bildirimleri</string>
|
||||
<string name="updated_channel">Güncelleme Tamamlandı</string>
|
||||
<string name="download_complete">İndirme Tamamlandı</string>
|
||||
<string name="download_complete">İndirme tamamlandı</string>
|
||||
<string name="download_file_error">Dosya indirilirken hata oluştu</string>
|
||||
<string name="magisk_update_title">Magisk Güncellemesi Mevcut!</string>
|
||||
<string name="updated_title">Magisk Güncellendi</string>
|
||||
@@ -204,41 +207,41 @@
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Mevcut</string>
|
||||
<string name="no">Mevcut değil</string>
|
||||
<string name="repo_install_title">%1$s %2$s(%3$d) yükle</string>
|
||||
<string name="no">Mevcut Değil</string>
|
||||
<string name="repo_install_title">%1$s %2$s(%3$d) Kur</string>
|
||||
<string name="download">İndir</string>
|
||||
<string name="reboot">Yeniden başlat</string>
|
||||
<string name="release_notes">Sürüm notları</string>
|
||||
<string name="flashing">Flaşlanıyor…</string>
|
||||
<string name="reboot">Yeniden Başlat</string>
|
||||
<string name="release_notes">Sürüm Notları</string>
|
||||
<string name="flashing">Yükleniyor...</string>
|
||||
<string name="done">Tamamlandı!</string>
|
||||
<string name="failure">Başarısız!</string>
|
||||
<string name="hide_app_title">Magisk uygulaması gizleniyor…</string>
|
||||
<string name="open_link_failed_toast">Bağlantıyı açacak uygulama bulunamadı</string>
|
||||
<string name="complete_uninstall">Kaldırmayı Tamamla</string>
|
||||
<string name="open_link_failed_toast">Bağlantıyı açmak için uygulama bulunamadı</string>
|
||||
<string name="complete_uninstall">Tamamen Kaldır</string>
|
||||
<string name="restore_img">Görüntüleri Geri Yükle</string>
|
||||
<string name="restore_img_msg">Geri yükleniyor…</string>
|
||||
<string name="restore_img_msg">Geri Yükleniyor...</string>
|
||||
<string name="restore_done">Geri yükleme tamamlandı!</string>
|
||||
<string name="restore_fail">Stok yedeği mevcut değil!</string>
|
||||
<string name="setup_fail">Kurulum başarısız oldu</string>
|
||||
<string name="env_fix_title">Ek Kurulum Gerekiyor</string>
|
||||
<string name="env_fix_msg">Magisk\'in düzgün çalışması için cihazınızın ek kuruluma ihtiyacı var. Devam etmek ve yeniden başlatmak istiyor musunuz?</string>
|
||||
<string name="env_full_fix_msg">Cihazınızın düzgün çalışması için Magisk\'in yeniden başlatılması gerekiyor. Lütfen Magisk\'i uygulama içinden yeniden yükleyin, kurtarma modu doğru cihaz bilgilerini alamıyor.</string>
|
||||
<string name="setup_msg">Ortam kurulumu çalıştırılıyor…</string>
|
||||
<string name="env_fix_title">Ek Ayar Gerekiyor</string>
|
||||
<string name="env_fix_msg">Magisk\'in düzgün çalışabilmesi için cihazınızın ek ayarlar yapması gerekiyor. Devam etmek ve yeniden başlatmak istiyor musunuz?</string>
|
||||
<string name="env_full_fix_msg">Cihazınızın düzgün çalışabilmesi için Magisk\'in yeniden yüklenmesi gerekiyor. Lütfen Magisk\'i uygulama içinde yeniden yükleyin, kurtarma modu doğru cihaz bilgilerini alamaz.</string>
|
||||
<string name="setup_msg">Ortam kurulumu yapılıyor...</string>
|
||||
<string name="unsupport_magisk_title">Desteklenmeyen Magisk Sürümü</string>
|
||||
<string name="unsupport_magisk_msg">Uygulamanın bu sürümü %1$s altındaki Magisk sürümlerini desteklemiyor.\n\nUygulama Magisk yüklenmemiş gibi davranacak, lütfen en kısa sürede Magisk\'i yükseltin.</string>
|
||||
<string name="unsupport_magisk_msg">Bu uygulama sürümü, %1$s altındaki Magisk sürümlerini desteklemiyor.\n\nUygulama, Magisk yüklü değilmiş gibi davranacaktır, lütfen en kısa sürede Magisk\'i güncelleyin.</string>
|
||||
<string name="unsupport_general_title">Anormal Durum</string>
|
||||
<string name="unsupport_system_app_msg">Bu uygulamanın sistem uygulaması olarak çalıştırılması desteklenmiyor. Lütfen uygulamayı bir kullanıcı uygulamasına geri yükleyin.</string>
|
||||
<string name="unsupport_other_su_msg">Magisk\'ten olmayan bir \"su\" ikilisi algılandı. Lütfen rakip kök çözümlerini kaldırın ve/veya Magisk\'i yeniden yükleyin.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk harici depolamaya yüklenmiş. Lütfen uygulamayı dahili depolamaya taşıyın.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Kök (root) kaybolduğu için gizli Magisk uygulaması çalışmaya devam edemiyor. Lütfen orijinal APK\'yı geri yükleyin.</string>
|
||||
<string name="unsupport_system_app_msg">Bu uygulamanın sistem uygulaması olarak çalıştırılması desteklenmiyor. Lütfen uygulamayı kullanıcı uygulamasına geri döndürün.</string>
|
||||
<string name="unsupport_other_su_msg">Magisk\'ten gelmeyen bir "su" ikili dosyası tespit edildi. Lütfen herhangi bir rakip kök (root) çözümünü kaldırın ve/veya Magisk\'i yeniden yükleyin.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk harici depolamaya yüklendi. Lütfen uygulamayı dahili depolamaya taşıyın.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Gizli Magisk uygulaması kök (root) erişimi kaybolduğu için çalışmaya devam edemez. Lütfen orijinal APK\'yı geri yükleyin.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Bu işlevi etkinleştirmek için depolama izni veriniz.</string>
|
||||
<string name="post_notifications_denied">Bu işlevi etkinleştirmek için bildirim izni veriniz.</string>
|
||||
<string name="install_unknown_denied">Bu işlevi etkinleştirmek için "Bilinmeyen uygulamaları yükle" ayarına izin veriniz.</string>
|
||||
<string name="external_rw_permission_denied">Bu işlevselliği etkinleştirmek için depolama izni verin</string>
|
||||
<string name="post_notifications_denied">Bu işlevselliği etkinleştirmek için bildirim izni verin</string>
|
||||
<string name="install_unknown_denied">Bu işlevselliği etkinleştirmek için "bilinmeyen uygulamaları yükle" iznini verin</string>
|
||||
<string name="add_shortcut_title">Ana ekrana kısayol ekle</string>
|
||||
<string name="add_shortcut_msg">Bu uygulamayı gizledikten sonra adını ve simgesini tanımak zorlaşabilir. Ana ekrana güzel bir kısayol eklemek ister misiniz?</string>
|
||||
<string name="app_not_found">Bu eylemi gerçekleştirecek uygulama bulunamadı</string>
|
||||
<string name="add_shortcut_msg">Bu uygulamayı gizledikten sonra adı ve simgesi tanınmayabilir. Ana ekrana güzel bir kısayol eklemek ister misiniz?</string>
|
||||
<string name="app_not_found">Bu işlemi gerçekleştirecek uygulama bulunamadı</string>
|
||||
<string name="reboot_apply_change">Değişiklikleri uygulamak için yeniden başlatın</string>
|
||||
<string name="restore_app_confirmation">Bu işlem, gizli uygulamayı orijinal uygulama ile değiştirecektir. Bu işlemi yapmak istediğinizden emin misiniz?</string>
|
||||
<string name="restore_app_confirmation">Bu, gizli uygulamayı orijinal uygulamaya geri yükleyecektir. Gerçekten bunu yapmak istiyor musunuz?</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -130,8 +130,8 @@
|
||||
<string name="settings_update_custom">Власний</string>
|
||||
<string name="settings_update_custom_msg">Вставте власний URL</string>
|
||||
<string name="settings_zygisk_summary">Запускати частини Magisk в сервісі zygote</string>
|
||||
<string name="settings_denylist_title">Enforce DenyList</string>
|
||||
<string name="settings_denylist_summary">Processes on the denylist will have all Magisk modifications reverted</string>
|
||||
<string name="settings_denylist_title">Увімкнути DenyList</string>
|
||||
<string name="settings_denylist_summary">Всі зміни, внесені Magisk, будуть приховані від процесів, позначених у DenyList</string>
|
||||
<string name="settings_denylist_config_title">Налаштувати DenyList</string>
|
||||
<string name="settings_denylist_config_summary">Вибрати процеси, які будуть додані до denylist</string>
|
||||
<string name="settings_hosts_title">Позасистемні хости</string>
|
||||
@@ -156,7 +156,7 @@
|
||||
<string name="superuser_notification">Сповіщення суперкористувача</string>
|
||||
<string name="settings_su_reauth_title">Повторна автентифікація</string>
|
||||
<string name="settings_su_reauth_summary">Перевидача прав суперкористувача після оновлення застосунку</string>
|
||||
<string name="settings_su_tapjack_title">Увімкнути захист від Tapjack</string>
|
||||
<string name="settings_su_tapjack_title">Увімкнути захист від підміни натискань</string>
|
||||
<string name="settings_su_tapjack_summary">Діалогове вікно суперкористувача не буде отримувати ввід від користувача, коли вікно перекрито іншим застосунком чи вікном</string>
|
||||
<string name="settings_customization">Оформлення</string>
|
||||
<string name="setting_add_shortcut_summary">Додати ярлик на домашній екран для зручного сприйняття застосунку після його приховування</string>
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
<string name="reboot_safe_mode">安全模式</string>
|
||||
<string name="module_version_author">%1$s,作者 %2$s</string>
|
||||
<string name="module_state_remove">移除</string>
|
||||
<string name="module_action">操作</string>
|
||||
<string name="module_state_restore">还原</string>
|
||||
<string name="module_action_install_external">从本地安装</string>
|
||||
<string name="update_available">可更新</string>
|
||||
@@ -211,9 +212,12 @@
|
||||
<string name="repo_install_title">安装 %1$s %2$s(%3$d)</string>
|
||||
<string name="download">下载</string>
|
||||
<string name="reboot">重启</string>
|
||||
<string name="close">关闭</string>
|
||||
<string name="release_notes">发布说明</string>
|
||||
<string name="flashing">正在刷入</string>
|
||||
<string name="running">运行中……</string>
|
||||
<string name="done">完成!</string>
|
||||
<string name="done_action">%1$s 操作运行完成</string>
|
||||
<string name="failure">失败</string>
|
||||
<string name="hide_app_title">正在隐藏 Magisk 应用</string>
|
||||
<string name="open_link_failed_toast">找不到能打开此链接的应用</string>
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
<string name="reboot_safe_mode">Safe mode</string>
|
||||
<string name="module_version_author">%1$s by %2$s</string>
|
||||
<string name="module_state_remove">Remove</string>
|
||||
<string name="module_action">Action</string>
|
||||
<string name="module_state_restore">Restore</string>
|
||||
<string name="module_action_install_external">Install from storage</string>
|
||||
<string name="update_available">Update Available</string>
|
||||
@@ -211,9 +212,12 @@
|
||||
<string name="repo_install_title">Install %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="reboot">Reboot</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="release_notes">Release notes</string>
|
||||
<string name="flashing">Flashing…</string>
|
||||
<string name="running">Running…</string>
|
||||
<string name="done">Done!</string>
|
||||
<string name="done_action">Done running action of %1$s</string>
|
||||
<string name="failure">Failed!</string>
|
||||
<string name="hide_app_title">Hiding the Magisk app…</string>
|
||||
<string name="open_link_failed_toast">No app found to open the link</string>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:label="Magisk"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
public class ProviderInstaller {
|
||||
|
||||
public static boolean install(Context context) {
|
||||
private static final String GMS_PACKAGE_NAME = "com.google.android.gms";
|
||||
|
||||
public static void install(Context context) {
|
||||
try {
|
||||
// Check if gms is a system app
|
||||
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(GMS_PACKAGE_NAME, 0);
|
||||
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try installing new SSL provider from Google Play Service
|
||||
Context gms = context.createPackageContext("com.google.android.gms",
|
||||
Context gms = context.createPackageContext(GMS_PACKAGE_NAME,
|
||||
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
|
||||
gms.getClassLoader()
|
||||
.loadClass("com.google.android.gms.common.security.ProviderInstallerImpl")
|
||||
.getMethod("insertProvider", Context.class)
|
||||
.invoke(null, gms);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import dalvik.system.BaseDexClassLoader;
|
||||
public class DynamicClassLoader extends BaseDexClassLoader {
|
||||
|
||||
public DynamicClassLoader(File apk) {
|
||||
this(apk, getSystemClassLoader());
|
||||
this(apk, DynamicClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
public DynamicClassLoader(File apk, ClassLoader parent) {
|
||||
|
||||
@@ -15,7 +15,7 @@ android {
|
||||
val canary = !Config.version.contains(".")
|
||||
|
||||
val url = if (canary) null
|
||||
else "https://cdn.jsdelivr.net/gh/topjohnwu/magisk-files@${Config.version}/app-release.apk"
|
||||
else "https://github.com/topjohnwu/Magisk/releases/download/v${Config.version}/Magisk-v${Config.version}.apk"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
@@ -27,9 +27,9 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
proguardFiles("proguard-rules.pro")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = false
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
setupStub()
|
||||
setupStubApk()
|
||||
|
||||
dependencies {
|
||||
implementation(project(":app:shared"))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="upgrade_msg">عليك الترقية ماجـيسك Manager لإكمال تهيئة التطبيق.هل اكمل؟</string>
|
||||
<string name="no_internet_msg">يرجى الاتصال بالانترنيت! ترقية ماجـيسك مطلوب...</string>
|
||||
<string name="upgrade_msg">عليك الترقية Magisk لإكمال تهيئة التطبيق. هل تريد التنزيل والتثبيت؟</string>
|
||||
<string name="no_internet_msg">يرجى اللإتصال بالإنترنت! ترقية Magisk مطلوبة.</string>
|
||||
<string name="dling">جارٍ التنزيل</string>
|
||||
<string name="relaunch_app">يرجى إعادة تشغيل التطبيق يدوياً</string>
|
||||
</resources>
|
||||
|
||||
7
app/stub/src/main/res/values-ku/strings.xml
Normal file
7
app/stub/src/main/res/values-ku/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="upgrade_msg">ماجیسکەکەت بەرزبکەوە بۆ وەشانی تەواوەتی، دەتەوێت دایبگریت و ڕێکیبخەیت؟</string>
|
||||
<string name="no_internet_msg">تکایە پەیوەست ببە بە ئینتەرنێتەوە، پێویستە ماجیسکەکەت ڕێک بخەیت.</string>
|
||||
<string name="dling">داگرتن</string>
|
||||
<string name="relaunch_app">تکایە دووبارە ئەپەکە بکەوە</string>
|
||||
</resources>
|
||||
1
app/test/.gitignore
vendored
Normal file
1
app/test/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
30
app/test/build.gradle.kts
Normal file
30
app/test/build.gradle.kts
Normal file
@@ -0,0 +1,30 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk.test"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk.test"
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
proguardFile("proguard-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupTestApk()
|
||||
|
||||
dependencies {
|
||||
implementation(libs.test.runner)
|
||||
implementation(libs.test.rules)
|
||||
implementation(libs.test.junit)
|
||||
implementation(libs.test.uiautomator)
|
||||
}
|
||||
13
app/test/proguard-rules.pro
vendored
Normal file
13
app/test/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Keep all test dependencies
|
||||
-keep class org.junit.** { *; }
|
||||
-keep class androidx.test.** { *; }
|
||||
|
||||
# Make sure the classloader constructor is kept
|
||||
-keepclassmembers class com.topjohnwu.magisk.test.TestClassLoader { <init>(); }
|
||||
|
||||
# Repackage dependencies
|
||||
-repackageclasses 'deps'
|
||||
-allowaccessmodification
|
||||
|
||||
# Keep attributes for stacktrace
|
||||
-keepattributes *
|
||||
23
app/test/src/main/AndroidManifest.xml
Normal file
23
app/test/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<queries tools:node="removeAll" />
|
||||
|
||||
<application tools:node="replace">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:name="com.topjohnwu.magisk.test.AppTestRunner"
|
||||
android:targetPackage="com.topjohnwu.magisk" />
|
||||
|
||||
<instrumentation
|
||||
android:name="com.topjohnwu.magisk.test.TestRunner"
|
||||
android:targetPackage="com.topjohnwu.magisk.test" />
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import androidx.annotation.Keep
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AppMigrationTest {
|
||||
|
||||
companion object {
|
||||
private const val APP_PKG = "com.topjohnwu.magisk"
|
||||
private const val STUB_PKG = "repackaged.$APP_PKG"
|
||||
private const val RECEIVER_TIMEOUT = 20L
|
||||
}
|
||||
|
||||
private val instrumentation get() = InstrumentationRegistry.getInstrumentation()
|
||||
private val context get() = instrumentation.context
|
||||
private val uiAutomation get() = instrumentation.uiAutomation
|
||||
private val registeredReceivers = mutableListOf<BroadcastReceiver>()
|
||||
|
||||
class PackageRemoveMonitor(
|
||||
context: Context,
|
||||
private val packageName: String
|
||||
) : BroadcastReceiver() {
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
init {
|
||||
val filter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED)
|
||||
filter.addDataScheme("package")
|
||||
context.registerReceiver(this, filter)
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != Intent.ACTION_PACKAGE_REMOVED)
|
||||
return
|
||||
val data = intent.data ?: return
|
||||
val pkg = data.schemeSpecificPart
|
||||
if (pkg == packageName) latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
registeredReceivers.forEach(context::unregisterReceiver)
|
||||
}
|
||||
|
||||
private fun testAppMigration(pkg: String, method: String) {
|
||||
val receiver = PackageRemoveMonitor(context, pkg)
|
||||
registeredReceivers.add(receiver)
|
||||
|
||||
// Trigger the test to run migration
|
||||
val pfd = uiAutomation.executeShellCommand(
|
||||
"am instrument -w --user 0 -e class .Environment#$method " +
|
||||
"$pkg.test/${AppTestRunner::class.java.name}"
|
||||
)
|
||||
val output = AutoCloseInputStream(pfd).reader().use { it.readText() }
|
||||
assertTrue("$method failed, inst out: $output", output.contains("OK ("))
|
||||
|
||||
// Wait for migration to complete
|
||||
assertTrue(
|
||||
"$pkg uninstallation failed",
|
||||
receiver.latch.await(RECEIVER_TIMEOUT, TimeUnit.SECONDS)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAppHide() {
|
||||
testAppMigration(APP_PKG, "setupAppHide")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAppRestore() {
|
||||
testAppMigration(STUB_PKG, "setupAppRestore")
|
||||
}
|
||||
}
|
||||
35
app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt
Normal file
35
app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.runner.AndroidJUnitRunner
|
||||
|
||||
open class TestRunner : AndroidJUnitRunner() {
|
||||
override fun onCreate(arguments: Bundle) {
|
||||
// Support short-hand ".ClassName"
|
||||
arguments.getString("class")?.let {
|
||||
val classArg = it.split(",").joinToString(separator = ",") { clz ->
|
||||
if (clz.startsWith(".")) {
|
||||
"com.topjohnwu.magisk.test$clz"
|
||||
} else {
|
||||
clz
|
||||
}
|
||||
}
|
||||
arguments.putString("class", classArg)
|
||||
}
|
||||
super.onCreate(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
class AppTestRunner : TestRunner() {
|
||||
override fun onCreate(arguments: Bundle) {
|
||||
// Force using the target context's classloader to run tests
|
||||
arguments.putString("classLoader", TestClassLoader::class.java.name)
|
||||
super.onCreate(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
private val targetClassLoader inline get() =
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.classLoader
|
||||
|
||||
class TestClassLoader : ClassLoader(targetClassLoader)
|
||||
743
build.py
743
build.py
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import copy
|
||||
import glob
|
||||
import lzma
|
||||
import multiprocessing
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
@@ -38,6 +39,7 @@ def vprint(str):
|
||||
print(str)
|
||||
|
||||
|
||||
# Environment checks and detection
|
||||
is_windows = os.name == "nt"
|
||||
EXE_EXT = ".exe" if is_windows else ""
|
||||
|
||||
@@ -51,54 +53,32 @@ if is_windows:
|
||||
# We can't do ANSI color codes in terminal on Windows without colorama
|
||||
no_color = True
|
||||
|
||||
# Environment checks
|
||||
if not sys.version_info >= (3, 8):
|
||||
error("Requires Python 3.8+")
|
||||
|
||||
try:
|
||||
sdk_path = Path(os.environ["ANDROID_HOME"])
|
||||
except KeyError:
|
||||
try:
|
||||
sdk_path = Path(os.environ["ANDROID_SDK_ROOT"])
|
||||
except KeyError:
|
||||
error("Please set Android SDK path to environment variable ANDROID_HOME")
|
||||
|
||||
if shutil.which("sccache") is not None:
|
||||
os.environ["RUSTC_WRAPPER"] = "sccache"
|
||||
os.environ["NDK_CCACHE"] = "sccache"
|
||||
os.environ["CARGO_INCREMENTAL"] = "0"
|
||||
if shutil.which("ccache") is not None:
|
||||
os.environ["NDK_CCACHE"] = "ccache"
|
||||
|
||||
cpu_count = multiprocessing.cpu_count()
|
||||
os_name = platform.system().lower()
|
||||
|
||||
archs = ["armeabi-v7a", "x86", "arm64-v8a", "x86_64", "riscv64"]
|
||||
triples = [
|
||||
"armv7a-linux-androideabi",
|
||||
"i686-linux-android",
|
||||
"aarch64-linux-android",
|
||||
"x86_64-linux-android",
|
||||
"riscv64-linux-android",
|
||||
]
|
||||
# Common constants
|
||||
support_abis = {
|
||||
"armeabi-v7a": "thumbv7neon-linux-androideabi",
|
||||
"x86": "i686-linux-android",
|
||||
"arm64-v8a": "aarch64-linux-android",
|
||||
"x86_64": "x86_64-linux-android",
|
||||
"riscv64": "riscv64-linux-android",
|
||||
}
|
||||
default_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
|
||||
support_targets = default_targets | {"resetprop"}
|
||||
rust_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
|
||||
|
||||
ndk_root = sdk_path / "ndk"
|
||||
ndk_path = ndk_root / "magisk"
|
||||
ndk_build = ndk_path / "ndk-build"
|
||||
rust_bin = ndk_path / "toolchains" / "rust" / "bin"
|
||||
llvm_bin = ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
|
||||
cargo = rust_bin / f"cargo{EXE_EXT}"
|
||||
gradlew = Path("gradlew" + (".bat" if is_windows else "")).resolve()
|
||||
adb_path = sdk_path / "platform-tools" / f"adb{EXE_EXT}"
|
||||
native_gen_path = Path("native", "out", "generated").resolve()
|
||||
|
||||
# Global vars
|
||||
config = {}
|
||||
STDOUT = None
|
||||
build_tools = None
|
||||
args = {}
|
||||
build_abis = {}
|
||||
|
||||
###################
|
||||
# Helper functions
|
||||
###################
|
||||
|
||||
|
||||
def mv(source: Path, target: Path):
|
||||
@@ -137,114 +117,65 @@ def rm_on_error(func, path, _):
|
||||
|
||||
def rm_rf(path: Path):
|
||||
vprint(f"rm -rf {path}")
|
||||
shutil.rmtree(path, ignore_errors=False, onerror=rm_on_error)
|
||||
if sys.version_info >= (3, 12):
|
||||
shutil.rmtree(path, ignore_errors=False, onexc=rm_on_error)
|
||||
else:
|
||||
shutil.rmtree(path, ignore_errors=False, onerror=rm_on_error)
|
||||
|
||||
|
||||
def execv(cmd, env=None):
|
||||
return subprocess.run(cmd, stdout=STDOUT, env=env)
|
||||
def execv(cmds: list, env=None):
|
||||
out = None if args.force_out or args.verbose > 0 else subprocess.DEVNULL
|
||||
# Use shell on Windows to support PATHEXT
|
||||
return subprocess.run(cmds, stdout=out, env=env, shell=is_windows)
|
||||
|
||||
|
||||
def system(cmd):
|
||||
return subprocess.run(cmd, shell=True, stdout=STDOUT)
|
||||
|
||||
|
||||
def cmd_out(cmd, env=None):
|
||||
def cmd_out(cmds: list):
|
||||
return (
|
||||
subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env)
|
||||
subprocess.run(
|
||||
cmds,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
shell=is_windows,
|
||||
)
|
||||
.stdout.strip()
|
||||
.decode("utf-8")
|
||||
)
|
||||
|
||||
|
||||
def xz(data):
|
||||
return lzma.compress(data, preset=9, check=lzma.CHECK_NONE)
|
||||
|
||||
|
||||
def parse_props(file):
|
||||
props = {}
|
||||
with open(file, "r") as f:
|
||||
for line in [l.strip(" \t\r\n") for l in f]:
|
||||
if line.startswith("#") or len(line) == 0:
|
||||
continue
|
||||
prop = line.split("=")
|
||||
if len(prop) != 2:
|
||||
continue
|
||||
value = prop[1].strip(" \t\r\n")
|
||||
if len(value) == 0:
|
||||
continue
|
||||
props[prop[0].strip(" \t\r\n")] = value
|
||||
return props
|
||||
|
||||
|
||||
def load_config(args):
|
||||
commit_hash = cmd_out(["git", "rev-parse", "--short=8", "HEAD"])
|
||||
|
||||
# Default values
|
||||
config["version"] = commit_hash
|
||||
config["versionCode"] = 1000000
|
||||
config["outdir"] = "out"
|
||||
|
||||
args.config = Path(args.config)
|
||||
|
||||
# Load prop files
|
||||
if args.config.exists():
|
||||
config.update(parse_props(args.config))
|
||||
|
||||
if Path("gradle.properties").exists():
|
||||
for key, value in parse_props("gradle.properties").items():
|
||||
if key.startswith("magisk."):
|
||||
config[key[7:]] = value
|
||||
|
||||
try:
|
||||
config["versionCode"] = int(config["versionCode"])
|
||||
except ValueError:
|
||||
error('Config error: "versionCode" is required to be an integer')
|
||||
|
||||
config["outdir"] = Path(config["outdir"])
|
||||
|
||||
config["outdir"].mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
global STDOUT
|
||||
STDOUT = None if args.verbose > 0 else subprocess.DEVNULL
|
||||
###############
|
||||
# Build Native
|
||||
###############
|
||||
|
||||
|
||||
def clean_elf():
|
||||
if is_windows:
|
||||
elf_cleaner = Path("tools", "elf-cleaner.exe")
|
||||
else:
|
||||
elf_cleaner = Path("native", "out", "elf-cleaner")
|
||||
if not elf_cleaner.exists():
|
||||
execv(
|
||||
[
|
||||
"gcc",
|
||||
'-DPACKAGE_NAME="termux-elf-cleaner"',
|
||||
'-DPACKAGE_VERSION="2.1.1"',
|
||||
'-DCOPYRIGHT="Copyright (C) 2022 Termux."',
|
||||
"tools/termux-elf-cleaner/elf-cleaner.cpp",
|
||||
"tools/termux-elf-cleaner/arghandling.c",
|
||||
"-o",
|
||||
elf_cleaner,
|
||||
]
|
||||
)
|
||||
args = [elf_cleaner, "--api-level", "23"]
|
||||
args.extend(
|
||||
Path("native", "out", arch, bin)
|
||||
for arch in archs
|
||||
for bin in ["magisk", "magiskpolicy"]
|
||||
)
|
||||
execv(args)
|
||||
cargo_toml = Path("tools", "elf-cleaner", "Cargo.toml")
|
||||
cmds = ["run", "--release", "--manifest-path", cargo_toml]
|
||||
if args.verbose == 0:
|
||||
cmds.append("-q")
|
||||
elif args.verbose > 1:
|
||||
cmds.append("--verbose")
|
||||
cmds.append("--")
|
||||
cmds.extend(glob.glob("native/out/*/magisk"))
|
||||
cmds.extend(glob.glob("native/out/*/magiskpolicy"))
|
||||
run_cargo(cmds)
|
||||
|
||||
|
||||
def run_ndk_build(args, flags):
|
||||
def run_ndk_build(cmds: list):
|
||||
os.chdir("native")
|
||||
flags = "NDK_PROJECT_PATH=. NDK_APPLICATION_MK=src/Application.mk " + flags
|
||||
cmds.append("NDK_PROJECT_PATH=.")
|
||||
cmds.append("NDK_APPLICATION_MK=src/Application.mk")
|
||||
cmds.append(f"APP_ABI={' '.join(build_abis.keys())}")
|
||||
cmds.append(f"-j{cpu_count}")
|
||||
if args.verbose > 1:
|
||||
flags = "V=1 " + flags
|
||||
proc = system(f"{ndk_build} {flags} -j{cpu_count}")
|
||||
cmds.append("V=1")
|
||||
if not args.release:
|
||||
cmds.append("MAGISK_DEBUG=1")
|
||||
proc = execv([ndk_build, *cmds])
|
||||
if proc.returncode != 0:
|
||||
error("Build binary failed!")
|
||||
os.chdir("..")
|
||||
|
||||
for arch in archs:
|
||||
for arch in build_abis.keys():
|
||||
arch_dir = Path("native", "libs", arch)
|
||||
out_dir = Path("native", "out", arch)
|
||||
for source in arch_dir.iterdir():
|
||||
@@ -252,54 +183,52 @@ def run_ndk_build(args, flags):
|
||||
mv(source, target)
|
||||
|
||||
|
||||
def build_cpp_src(args, targets: set):
|
||||
dump_flag_header()
|
||||
|
||||
flags = ""
|
||||
def build_cpp_src(targets: set):
|
||||
cmds = []
|
||||
clean = False
|
||||
|
||||
if "magisk" in targets:
|
||||
flags += " B_MAGISK=1"
|
||||
cmds.append("B_MAGISK=1")
|
||||
clean = True
|
||||
|
||||
if "magiskpolicy" in targets:
|
||||
flags += " B_POLICY=1"
|
||||
cmds.append("B_POLICY=1")
|
||||
clean = True
|
||||
|
||||
if "magiskinit" in targets:
|
||||
flags += " B_PRELOAD=1"
|
||||
cmds.append("B_PRELOAD=1")
|
||||
|
||||
if "resetprop" in targets:
|
||||
flags += " B_PROP=1"
|
||||
cmds.append("B_PROP=1")
|
||||
|
||||
if flags:
|
||||
run_ndk_build(args, flags)
|
||||
if cmds:
|
||||
run_ndk_build(cmds)
|
||||
|
||||
flags = ""
|
||||
cmds.clear()
|
||||
|
||||
if "magiskinit" in targets:
|
||||
flags += " B_INIT=1"
|
||||
cmds.append("B_INIT=1")
|
||||
|
||||
if "magiskboot" in targets:
|
||||
flags += " B_BOOT=1"
|
||||
cmds.append("B_BOOT=1")
|
||||
|
||||
if flags:
|
||||
flags += " B_CRT0=1"
|
||||
run_ndk_build(args, flags)
|
||||
if cmds:
|
||||
cmds.append("B_CRT0=1")
|
||||
run_ndk_build(cmds)
|
||||
|
||||
if clean:
|
||||
clean_elf()
|
||||
|
||||
|
||||
def run_cargo(cmds):
|
||||
ensure_paths()
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f'{rust_bin}{os.pathsep}{env["PATH"]}'
|
||||
env["CARGO_BUILD_RUSTC"] = str(rust_bin / f"rustc{EXE_EXT}")
|
||||
env["RUSTFLAGS"] = f"-Clinker-plugin-lto -Zthreads={min(8, cpu_count)}"
|
||||
return execv([cargo, *cmds], env)
|
||||
env["RUSTUP_TOOLCHAIN"] = str(rust_sysroot)
|
||||
env["CARGO_BUILD_RUSTFLAGS"] = f"-Z threads={min(8, cpu_count)}"
|
||||
return execv(["cargo", *cmds], env)
|
||||
|
||||
|
||||
def build_rust_src(args, targets: set):
|
||||
def build_rust_src(targets: set):
|
||||
targets = targets.copy()
|
||||
if "resetprop" in targets:
|
||||
targets.add("magisk")
|
||||
@@ -309,54 +238,40 @@ def build_rust_src(args, targets: set):
|
||||
|
||||
os.chdir(Path("native", "src"))
|
||||
|
||||
native_out = Path("..", "out")
|
||||
native_out.mkdir(mode=0o755, exist_ok=True)
|
||||
|
||||
# Start building the actual build commands
|
||||
# Start building the build commands
|
||||
cmds = ["build", "-p", ""]
|
||||
rust_out = "debug"
|
||||
if args.release:
|
||||
cmds.append("-r")
|
||||
rust_out = "release"
|
||||
profile = "release"
|
||||
else:
|
||||
profile = "debug"
|
||||
if args.verbose == 0:
|
||||
cmds.append("-q")
|
||||
elif args.verbose > 1:
|
||||
cmds.append("--verbose")
|
||||
|
||||
cmds.append("--target")
|
||||
cmds.append("")
|
||||
for triple in build_abis.values():
|
||||
cmds.append("--target")
|
||||
cmds.append(triple)
|
||||
|
||||
for arch, triple in zip(archs, triples):
|
||||
rust_triple = (
|
||||
"thumbv7neon-linux-androideabi" if triple.startswith("armv7") else triple
|
||||
)
|
||||
cmds[-1] = rust_triple
|
||||
for tgt in targets:
|
||||
cmds[2] = tgt
|
||||
proc = run_cargo(cmds)
|
||||
if proc.returncode != 0:
|
||||
error("Build binary failed!")
|
||||
|
||||
for tgt in targets:
|
||||
cmds[2] = tgt
|
||||
proc = run_cargo(cmds)
|
||||
if proc.returncode != 0:
|
||||
error("Build binary failed!")
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
native_out = Path("native", "out")
|
||||
rust_out = native_out / "rust"
|
||||
for arch, triple in build_abis.items():
|
||||
arch_out = native_out / arch
|
||||
arch_out.mkdir(mode=0o755, exist_ok=True)
|
||||
for tgt in targets:
|
||||
source = Path("target", rust_triple, rust_out, f"lib{tgt}.a")
|
||||
source = rust_out / triple / profile / f"lib{tgt}.a"
|
||||
target = arch_out / f"lib{tgt}-rs.a"
|
||||
mv(source, target)
|
||||
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
|
||||
def run_cargo_cmd(args):
|
||||
global STDOUT
|
||||
STDOUT = None
|
||||
if len(args.commands) >= 1 and args.commands[0] == "--":
|
||||
args.commands = args.commands[1:]
|
||||
os.chdir(Path("native", "src"))
|
||||
run_cargo(args.commands)
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
|
||||
def write_if_diff(file_name: Path, text: str):
|
||||
do_write = True
|
||||
@@ -383,11 +298,18 @@ def dump_flag_header():
|
||||
flag_txt += f'#define MAGISK_VER_CODE {config["versionCode"]}\n'
|
||||
flag_txt += f"#define MAGISK_DEBUG {0 if args.release else 1}\n"
|
||||
|
||||
native_gen_path = Path("native", "out", "generated")
|
||||
native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
write_if_diff(Path(native_gen_path, "flags.h"), flag_txt)
|
||||
write_if_diff(native_gen_path / "flags.h", flag_txt)
|
||||
|
||||
rust_flag_txt = f'pub const MAGISK_VERSION: &str = "{config["version"]}";\n'
|
||||
rust_flag_txt += f'pub const MAGISK_VER_CODE: i32 = {config["versionCode"]};\n'
|
||||
write_if_diff(native_gen_path / "flags.rs", rust_flag_txt)
|
||||
|
||||
|
||||
def build_binary(args):
|
||||
def build_native():
|
||||
ensure_paths()
|
||||
|
||||
# Verify NDK install
|
||||
try:
|
||||
with open(Path(ndk_path, "ONDK_VERSION"), "r") as ondk_ver:
|
||||
@@ -402,10 +324,23 @@ def build_binary(args):
|
||||
if not targets:
|
||||
return
|
||||
|
||||
header("* Building binaries: " + " ".join(targets))
|
||||
header("* Building: " + " ".join(targets))
|
||||
|
||||
build_rust_src(args, targets)
|
||||
build_cpp_src(args, targets)
|
||||
if sccache := shutil.which("sccache"):
|
||||
os.environ["RUSTC_WRAPPER"] = sccache
|
||||
os.environ["NDK_CCACHE"] = sccache
|
||||
os.environ["CARGO_INCREMENTAL"] = "0"
|
||||
if ccache := shutil.which("ccache"):
|
||||
os.environ["NDK_CCACHE"] = ccache
|
||||
|
||||
dump_flag_header()
|
||||
build_rust_src(targets)
|
||||
build_cpp_src(targets)
|
||||
|
||||
|
||||
############
|
||||
# Build App
|
||||
############
|
||||
|
||||
|
||||
def find_jdk():
|
||||
@@ -434,13 +369,14 @@ def find_jdk():
|
||||
if no_jdk:
|
||||
error(
|
||||
"Please set Android Studio's path to environment variable ANDROID_STUDIO,\n"
|
||||
+ "or install JDK 17 and make sure 'javac' is available in PATH"
|
||||
+ "or install JDK 21 and make sure 'javac' is available in PATH"
|
||||
)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def build_apk(args, module):
|
||||
def build_apk(module: str):
|
||||
ensure_paths()
|
||||
env = find_jdk()
|
||||
|
||||
build_type = "Release" if args.release else "Debug"
|
||||
@@ -463,19 +399,20 @@ def build_apk(args, module):
|
||||
source = Path(*paths, "build", "outputs", "apk", build_type, apk)
|
||||
target = config["outdir"] / apk
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
return target
|
||||
|
||||
|
||||
def build_app(args):
|
||||
def build_app():
|
||||
header("* Building the Magisk app")
|
||||
build_apk(args, ":app:apk")
|
||||
apk = build_apk(":app:apk")
|
||||
|
||||
build_type = "release" if args.release else "debug"
|
||||
|
||||
# Rename apk-variant.apk to app-variant.apk
|
||||
source = config["outdir"] / f"apk-{build_type}.apk"
|
||||
target = config["outdir"] / f"app-{build_type}.apk"
|
||||
source = apk
|
||||
target = apk.parent / apk.name.replace("apk-", "app-")
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
|
||||
# Stub building is directly integrated into the main app
|
||||
# build process. Copy the stub APK into output directory.
|
||||
@@ -484,13 +421,35 @@ def build_app(args):
|
||||
cp(source, target)
|
||||
|
||||
|
||||
def build_stub(args):
|
||||
def build_stub():
|
||||
header("* Building the stub app")
|
||||
build_apk(args, ":app:stub")
|
||||
apk = build_apk(":app:stub")
|
||||
header(f"Output: {apk}")
|
||||
|
||||
|
||||
def cleanup(args):
|
||||
support_targets = {"native", "cpp", "rust", "java"}
|
||||
def build_test():
|
||||
global args
|
||||
args_bak = copy.copy(args)
|
||||
# Test APK has to be built as release to prevent classname clash
|
||||
args.release = True
|
||||
try:
|
||||
header("* Building the test app")
|
||||
source = build_apk(":app:test")
|
||||
target = source.parent / "test.apk"
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
finally:
|
||||
args = args_bak
|
||||
|
||||
|
||||
################
|
||||
# Build General
|
||||
################
|
||||
|
||||
|
||||
def cleanup():
|
||||
ensure_paths()
|
||||
support_targets = {"native", "cpp", "rust", "app"}
|
||||
if args.targets:
|
||||
targets = set(args.targets) & support_targets
|
||||
if "native" in targets:
|
||||
@@ -503,7 +462,6 @@ def cleanup(args):
|
||||
header("* Cleaning C++")
|
||||
rm_rf(Path("native", "libs"))
|
||||
rm_rf(Path("native", "obj"))
|
||||
rm_rf(Path("native", "out"))
|
||||
|
||||
if "rust" in targets:
|
||||
header("* Cleaning Rust")
|
||||
@@ -513,12 +471,47 @@ def cleanup(args):
|
||||
for rs_gen in glob.glob("native/**/*-rs.*pp", recursive=True):
|
||||
rm(rs_gen)
|
||||
|
||||
if "java" in targets:
|
||||
header("* Cleaning java")
|
||||
if "native" in targets:
|
||||
rm_rf(Path("native", "out"))
|
||||
rm_rf(Path("tools", "elf-cleaner", "target"))
|
||||
|
||||
if "app" in targets:
|
||||
header("* Cleaning app")
|
||||
execv([gradlew, ":app:clean"], env=find_jdk())
|
||||
|
||||
|
||||
def setup_ndk(args):
|
||||
def build_all():
|
||||
build_native()
|
||||
build_app()
|
||||
build_test()
|
||||
|
||||
|
||||
############
|
||||
# Utilities
|
||||
############
|
||||
|
||||
|
||||
def clippy_cli():
|
||||
args.force_out = True
|
||||
os.chdir(Path("native", "src"))
|
||||
cmds = ["clippy", "--no-deps", "--target"]
|
||||
for triple in build_abis.values():
|
||||
run_cargo(cmds + [triple])
|
||||
run_cargo(cmds + [triple, "--release"])
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
|
||||
def cargo_cli():
|
||||
args.force_out = True
|
||||
if len(args.commands) >= 1 and args.commands[0] == "--":
|
||||
args.commands = args.commands[1:]
|
||||
os.chdir(Path("native", "src"))
|
||||
run_cargo(args.commands)
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
|
||||
def setup_ndk():
|
||||
ensure_paths()
|
||||
ndk_ver = config["ondkVersion"]
|
||||
url = f"https://github.com/topjohnwu/ondk/releases/download/{ndk_ver}/ondk-{ndk_ver}-{os_name}.tar.xz"
|
||||
ndk_archive = url.split("/")[-1]
|
||||
@@ -537,14 +530,54 @@ def setup_ndk(args):
|
||||
mv(ondk_path, ndk_path)
|
||||
|
||||
|
||||
def push_files(args, script):
|
||||
def setup_rustup():
|
||||
wrapper_dir = Path(args.wrapper_dir)
|
||||
rm_rf(wrapper_dir)
|
||||
wrapper_dir.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
if "CARGO_HOME" in os.environ:
|
||||
cargo_home = Path(os.environ["CARGO_HOME"])
|
||||
else:
|
||||
cargo_home = Path.home() / ".cargo"
|
||||
cargo_bin = cargo_home / "bin"
|
||||
for src in cargo_bin.iterdir():
|
||||
tgt = wrapper_dir / src.name
|
||||
tgt.symlink_to(f"rustup{EXE_EXT}")
|
||||
|
||||
# Build rustup-wrapper
|
||||
wrapper_src = Path("tools", "rustup-wrapper")
|
||||
cargo_toml = wrapper_src / "Cargo.toml"
|
||||
cmds = ["build", "--release", f"--manifest-path={cargo_toml}"]
|
||||
if args.verbose > 1:
|
||||
cmds.append("--verbose")
|
||||
run_cargo(cmds)
|
||||
|
||||
# Replace rustup with wrapper
|
||||
wrapper = wrapper_dir / (f"rustup{EXE_EXT}")
|
||||
wrapper.unlink(missing_ok=True)
|
||||
cp(wrapper_src / "target" / "release" / (f"rustup-wrapper{EXE_EXT}"), wrapper)
|
||||
wrapper.chmod(0o755)
|
||||
|
||||
|
||||
##################
|
||||
# AVD and testing
|
||||
##################
|
||||
|
||||
|
||||
def push_files(script):
|
||||
if args.build:
|
||||
build_all()
|
||||
ensure_adb()
|
||||
|
||||
abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"])
|
||||
if not abi:
|
||||
error("Cannot detect emulator ABI")
|
||||
|
||||
apk = Path(
|
||||
config["outdir"], ("app-release.apk" if args.release else "app-debug.apk")
|
||||
)
|
||||
if args.apk:
|
||||
apk = Path(args.apk)
|
||||
else:
|
||||
apk = Path(
|
||||
config["outdir"], ("app-release.apk" if args.release else "app-debug.apk")
|
||||
)
|
||||
|
||||
# Extract busybox from APK
|
||||
busybox = Path(config["outdir"], "busybox")
|
||||
@@ -565,40 +598,31 @@ def push_files(args, script):
|
||||
error("adb push failed!")
|
||||
|
||||
|
||||
def setup_avd(args):
|
||||
if not args.skip:
|
||||
build_all(args)
|
||||
|
||||
def setup_avd():
|
||||
header("* Setting up emulator")
|
||||
|
||||
push_files(args, Path("scripts", "avd_magisk.sh"))
|
||||
push_files(Path("scripts", "avd_magisk.sh"))
|
||||
|
||||
proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_magisk.sh"])
|
||||
if proc.returncode != 0:
|
||||
error("avd_magisk.sh failed!")
|
||||
|
||||
|
||||
def patch_avd_file(args):
|
||||
if not args.skip:
|
||||
build_all(args)
|
||||
|
||||
def patch_avd_file():
|
||||
input = Path(args.image)
|
||||
if args.output:
|
||||
output = Path(args.output)
|
||||
else:
|
||||
output = input.parent / f"{input.name}.magisk"
|
||||
|
||||
src_file = f"/data/local/tmp/{input.name}"
|
||||
out_file = f"{src_file}.magisk"
|
||||
output = Path(args.output)
|
||||
|
||||
header(f"* Patching {input.name}")
|
||||
|
||||
push_files(args, Path("scripts", "avd_patch.sh"))
|
||||
push_files(Path("scripts", "avd_patch.sh"))
|
||||
|
||||
proc = execv([adb_path, "push", input, "/data/local/tmp"])
|
||||
if proc.returncode != 0:
|
||||
error("adb push failed!")
|
||||
|
||||
src_file = f"/data/local/tmp/{input.name}"
|
||||
out_file = f"{src_file}.magisk"
|
||||
|
||||
proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_patch.sh", src_file])
|
||||
if proc.returncode != 0:
|
||||
error("avd_patch.sh failed!")
|
||||
@@ -610,110 +634,201 @@ def patch_avd_file(args):
|
||||
header(f"Output: {output}")
|
||||
|
||||
|
||||
def build_all(args):
|
||||
build_binary(args)
|
||||
build_app(args)
|
||||
##########################
|
||||
# Config, paths, argparse
|
||||
##########################
|
||||
|
||||
|
||||
def setup_rustup(args):
|
||||
wrapper_dir = Path(args.wrapper_dir)
|
||||
rm_rf(wrapper_dir)
|
||||
wrapper_dir.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
if "CARGO_HOME" in os.environ:
|
||||
cargo_home = Path(os.environ["CARGO_HOME"])
|
||||
def ensure_paths():
|
||||
global sdk_path, ndk_root, ndk_path, ndk_build, rust_sysroot
|
||||
global llvm_bin, gradlew, adb_path, native_gen_path
|
||||
|
||||
# Skip if already initialized
|
||||
if "sdk_path" in globals():
|
||||
return
|
||||
|
||||
try:
|
||||
sdk_path = Path(os.environ["ANDROID_HOME"])
|
||||
except KeyError:
|
||||
try:
|
||||
sdk_path = Path(os.environ["ANDROID_SDK_ROOT"])
|
||||
except KeyError:
|
||||
error("Please set Android SDK path to environment variable ANDROID_HOME")
|
||||
|
||||
ndk_root = sdk_path / "ndk"
|
||||
ndk_path = ndk_root / "magisk"
|
||||
ndk_build = ndk_path / "ndk-build"
|
||||
rust_sysroot = ndk_path / "toolchains" / "rust"
|
||||
llvm_bin = (
|
||||
ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
|
||||
)
|
||||
adb_path = sdk_path / "platform-tools" / "adb"
|
||||
gradlew = Path.cwd() / "gradlew"
|
||||
|
||||
|
||||
# We allow using several functionality with only ADB
|
||||
def ensure_adb():
|
||||
global adb_path
|
||||
if "adb_path" not in globals():
|
||||
adb_path = shutil.which("adb")
|
||||
if not adb_path:
|
||||
error("Command 'adb' cannot be found in PATH")
|
||||
else:
|
||||
adb_path = Path(adb_path)
|
||||
|
||||
|
||||
def parse_props(file):
|
||||
props = {}
|
||||
with open(file, "r") as f:
|
||||
for line in [l.strip(" \t\r\n") for l in f]:
|
||||
if line.startswith("#") or len(line) == 0:
|
||||
continue
|
||||
prop = line.split("=")
|
||||
if len(prop) != 2:
|
||||
continue
|
||||
key = prop[0].strip(" \t\r\n")
|
||||
value = prop[1].strip(" \t\r\n")
|
||||
if not key or not value:
|
||||
continue
|
||||
props[key] = value
|
||||
return props
|
||||
|
||||
|
||||
def load_config():
|
||||
commit_hash = cmd_out(["git", "rev-parse", "--short=8", "HEAD"])
|
||||
|
||||
# Default values
|
||||
config["version"] = commit_hash
|
||||
config["versionCode"] = 1000000
|
||||
config["outdir"] = "out"
|
||||
|
||||
args.config = Path(args.config)
|
||||
|
||||
# Load prop files
|
||||
if args.config.exists():
|
||||
config.update(parse_props(args.config))
|
||||
|
||||
if Path("gradle.properties").exists():
|
||||
for key, value in parse_props("gradle.properties").items():
|
||||
if key.startswith("magisk."):
|
||||
config[key[7:]] = value
|
||||
|
||||
try:
|
||||
config["versionCode"] = int(config["versionCode"])
|
||||
except ValueError:
|
||||
error('Config error: "versionCode" is required to be an integer')
|
||||
|
||||
config["outdir"] = Path(config["outdir"])
|
||||
config["outdir"].mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
|
||||
if "abiList" in config:
|
||||
abiList = re.split("\\s*,\\s*", config["abiList"])
|
||||
archs = set(abiList) & support_abis.keys()
|
||||
else:
|
||||
cargo_home = Path.home() / ".cargo"
|
||||
cargo_bin = cargo_home / "bin"
|
||||
for src in cargo_bin.iterdir():
|
||||
tgt = wrapper_dir / src.name
|
||||
tgt.symlink_to(src)
|
||||
archs = {"armeabi-v7a", "x86", "arm64-v8a", "x86_64"}
|
||||
|
||||
# Build rustup_wrapper
|
||||
wrapper_src = Path("tools", "rustup_wrapper")
|
||||
cargo_toml = wrapper_src / "Cargo.toml"
|
||||
execv(
|
||||
[cargo, "build", "--release", f"--manifest-path={cargo_toml}"]
|
||||
+ (["--verbose"] if args.verbose > 1 else [])
|
||||
triples = map(support_abis.get, archs)
|
||||
|
||||
global build_abis
|
||||
build_abis = dict(zip(archs, triples))
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Magisk build script")
|
||||
parser.set_defaults(func=lambda x: None)
|
||||
parser.add_argument(
|
||||
"-r", "--release", action="store_true", help="compile in release mode"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="count", default=0, help="verbose output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
default="config.prop",
|
||||
help="custom config file (default: config.prop)",
|
||||
)
|
||||
subparsers = parser.add_subparsers(title="actions")
|
||||
|
||||
all_parser = subparsers.add_parser("all", help="build everything")
|
||||
|
||||
native_parser = subparsers.add_parser("native", help="build native binaries")
|
||||
native_parser.add_argument(
|
||||
"targets",
|
||||
nargs="*",
|
||||
help=f"{', '.join(support_targets)}, \
|
||||
or empty for defaults ({', '.join(default_targets)})",
|
||||
)
|
||||
|
||||
# Replace rustup with wrapper
|
||||
wrapper = wrapper_dir / (f"rustup{EXE_EXT}")
|
||||
wrapper.unlink(missing_ok=True)
|
||||
cp(wrapper_src / "target" / "release" / (f"rustup_wrapper{EXE_EXT}"), wrapper)
|
||||
wrapper.chmod(0o755)
|
||||
app_parser = subparsers.add_parser("app", help="build the Magisk app")
|
||||
|
||||
stub_parser = subparsers.add_parser("stub", help="build the stub app")
|
||||
|
||||
test_parser = subparsers.add_parser("test", help="build the test app")
|
||||
|
||||
clean_parser = subparsers.add_parser("clean", help="cleanup")
|
||||
clean_parser.add_argument(
|
||||
"targets", nargs="*", help="native, cpp, rust, java, or empty to clean all"
|
||||
)
|
||||
|
||||
ndk_parser = subparsers.add_parser("ndk", help="setup Magisk NDK")
|
||||
|
||||
emu_parser = subparsers.add_parser("emulator", help="setup AVD for development")
|
||||
emu_parser.add_argument("apk", help="a Magisk APK to use", nargs="?")
|
||||
emu_parser.add_argument(
|
||||
"-b", "--build", action="store_true", help="build before patching"
|
||||
)
|
||||
|
||||
avd_patch_parser = subparsers.add_parser(
|
||||
"avd_patch", help="patch AVD ramdisk.img or init_boot.img"
|
||||
)
|
||||
avd_patch_parser.add_argument("image", help="path to ramdisk.img or init_boot.img")
|
||||
avd_patch_parser.add_argument("output", help="output file name")
|
||||
avd_patch_parser.add_argument("--apk", help="a Magisk APK to use")
|
||||
avd_patch_parser.add_argument(
|
||||
"-b", "--build", action="store_true", help="build before patching"
|
||||
)
|
||||
|
||||
cargo_parser = subparsers.add_parser(
|
||||
"cargo", help="call 'cargo' commands against the project"
|
||||
)
|
||||
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER)
|
||||
|
||||
clippy_parser = subparsers.add_parser("clippy", help="run clippy on Rust sources")
|
||||
|
||||
rustup_parser = subparsers.add_parser("rustup", help="setup rustup wrapper")
|
||||
rustup_parser.add_argument(
|
||||
"wrapper_dir", help="path to setup rustup wrapper binaries"
|
||||
)
|
||||
|
||||
# Set callbacks
|
||||
all_parser.set_defaults(func=build_all)
|
||||
native_parser.set_defaults(func=build_native)
|
||||
cargo_parser.set_defaults(func=cargo_cli)
|
||||
clippy_parser.set_defaults(func=clippy_cli)
|
||||
rustup_parser.set_defaults(func=setup_rustup)
|
||||
app_parser.set_defaults(func=build_app)
|
||||
stub_parser.set_defaults(func=build_stub)
|
||||
test_parser.set_defaults(func=build_test)
|
||||
emu_parser.set_defaults(func=setup_avd)
|
||||
avd_patch_parser.set_defaults(func=patch_avd_file)
|
||||
clean_parser.set_defaults(func=cleanup)
|
||||
ndk_parser.set_defaults(func=setup_ndk)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="Magisk build script")
|
||||
parser.set_defaults(func=lambda x: None)
|
||||
parser.add_argument(
|
||||
"-r", "--release", action="store_true", help="compile in release mode"
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="count", default=0, help="verbose output")
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
default="config.prop",
|
||||
help="custom config file (default: config.prop)",
|
||||
)
|
||||
subparsers = parser.add_subparsers(title="actions")
|
||||
def main():
|
||||
global args
|
||||
args = parse_args()
|
||||
load_config()
|
||||
vars(args)["force_out"] = False
|
||||
args.func()
|
||||
|
||||
all_parser = subparsers.add_parser("all", help="build everything")
|
||||
all_parser.set_defaults(func=build_all)
|
||||
|
||||
binary_parser = subparsers.add_parser("binary", help="build binaries")
|
||||
binary_parser.add_argument(
|
||||
"targets",
|
||||
nargs="*",
|
||||
help=f"{', '.join(support_targets)}, \
|
||||
or empty for defaults ({', '.join(default_targets)})",
|
||||
)
|
||||
binary_parser.set_defaults(func=build_binary)
|
||||
|
||||
cargo_parser = subparsers.add_parser("cargo", help="run cargo with proper environment")
|
||||
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER)
|
||||
cargo_parser.set_defaults(func=run_cargo_cmd)
|
||||
|
||||
rustup_parser = subparsers.add_parser("rustup", help="setup rustup wrapper")
|
||||
rustup_parser.add_argument("wrapper_dir", help="path to setup rustup wrapper binaries")
|
||||
rustup_parser.set_defaults(func=setup_rustup)
|
||||
|
||||
app_parser = subparsers.add_parser("app", help="build the Magisk app")
|
||||
app_parser.set_defaults(func=build_app)
|
||||
|
||||
stub_parser = subparsers.add_parser("stub", help="build the stub app")
|
||||
stub_parser.set_defaults(func=build_stub)
|
||||
|
||||
avd_parser = subparsers.add_parser("emulator", help="setup AVD for development")
|
||||
avd_parser.add_argument(
|
||||
"-s", "--skip", action="store_true", help="skip building binaries and the app"
|
||||
)
|
||||
avd_parser.set_defaults(func=setup_avd)
|
||||
|
||||
avd_patch_parser = subparsers.add_parser(
|
||||
"avd_patch", help="patch AVD ramdisk.img or init_boot.img"
|
||||
)
|
||||
avd_patch_parser.add_argument("image", help="path to ramdisk.img or init_boot.img")
|
||||
avd_patch_parser.add_argument("output", help="optional output file name", nargs="?")
|
||||
avd_patch_parser.add_argument(
|
||||
"-s", "--skip", action="store_true", help="skip building binaries and the app"
|
||||
)
|
||||
avd_patch_parser.set_defaults(func=patch_avd_file)
|
||||
|
||||
clean_parser = subparsers.add_parser("clean", help="cleanup")
|
||||
clean_parser.add_argument(
|
||||
"targets", nargs="*", help="native, cpp, rust, java, or empty to clean all"
|
||||
)
|
||||
clean_parser.set_defaults(func=cleanup)
|
||||
|
||||
ndk_parser = subparsers.add_parser("ndk", help="setup Magisk NDK")
|
||||
ndk_parser.set_defaults(func=setup_ndk)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
load_config(args)
|
||||
|
||||
# Call corresponding functions
|
||||
args.func(args)
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
@@ -18,17 +18,17 @@ gradlePlugin {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
languageVersion = "2.0"
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
languageVersion = KotlinVersion.KOTLIN_2_0
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("gradle-plugin", "2.0.0"))
|
||||
implementation("com.android.tools.build:gradle:8.5.1")
|
||||
implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.0.0-1.0.23")
|
||||
implementation("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7")
|
||||
implementation("org.lsposed.lsparanoid:gradle-plugin:0.6.0")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.10.0.202406032230-r")
|
||||
implementation(kotlin("gradle-plugin", libs.versions.kotlin.get()))
|
||||
implementation(libs.android.gradle.plugin)
|
||||
implementation(libs.ksp.plugin)
|
||||
implementation(libs.navigation.safe.args.plugin)
|
||||
implementation(libs.lsparanoid.plugin)
|
||||
implementation(libs.jgit)
|
||||
}
|
||||
|
||||
7
buildSrc/settings.gradle.kts
Normal file
7
buildSrc/settings.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
122
buildSrc/src/main/java/DesugarClassVisitorFactory.kt
Normal file
122
buildSrc/src/main/java/DesugarClassVisitorFactory.kt
Normal file
@@ -0,0 +1,122 @@
|
||||
import com.android.build.api.instrumentation.AsmClassVisitorFactory
|
||||
import com.android.build.api.instrumentation.ClassContext
|
||||
import com.android.build.api.instrumentation.ClassData
|
||||
import com.android.build.api.instrumentation.InstrumentationParameters
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Opcodes.ASM9
|
||||
|
||||
private const val DESUGAR_CLASS_NAME = "com.topjohnwu.magisk.core.utils.Desugar"
|
||||
private const val ZIP_ENTRY_CLASS_NAME = "java.util.zip.ZipEntry"
|
||||
private const val ZIP_OUT_STREAM_CLASS_NAME = "org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream"
|
||||
private const val ZIP_UTIL_CLASS_NAME = "org/apache/commons/compress/archivers/zip/ZipUtil"
|
||||
private const val ZIP_ENTRY_GET_TIME_DESC = "()Ljava/nio/file/attribute/FileTime;"
|
||||
private const val DESUGAR_GET_TIME_DESC =
|
||||
"(Ljava/util/zip/ZipEntry;)Ljava/nio/file/attribute/FileTime;"
|
||||
|
||||
private fun ClassData.isTypeOf(name: String) = className == name || superClasses.contains(name)
|
||||
|
||||
abstract class DesugarClassVisitorFactory : AsmClassVisitorFactory<InstrumentationParameters.None> {
|
||||
override fun createClassVisitor(
|
||||
classContext: ClassContext,
|
||||
nextClassVisitor: ClassVisitor
|
||||
): ClassVisitor {
|
||||
return if (classContext.currentClassData.className == ZIP_OUT_STREAM_CLASS_NAME) {
|
||||
ZipEntryPatcher(classContext, ZipOutputStreamPatcher(nextClassVisitor))
|
||||
} else {
|
||||
ZipEntryPatcher(classContext, nextClassVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInstrumentable(classData: ClassData) = classData.className != DESUGAR_CLASS_NAME
|
||||
|
||||
// Patch ALL references to ZipEntry#getXXXTime
|
||||
class ZipEntryPatcher(
|
||||
private val classContext: ClassContext,
|
||||
cv: ClassVisitor
|
||||
) : ClassVisitor(ASM9, cv) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String?,
|
||||
descriptor: String?,
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?
|
||||
) = MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
|
||||
|
||||
inner class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
|
||||
override fun visitMethodInsn(
|
||||
opcode: Int,
|
||||
owner: String,
|
||||
name: String,
|
||||
descriptor: String,
|
||||
isInterface: Boolean
|
||||
) {
|
||||
if (!process(owner, name, descriptor)) {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
|
||||
}
|
||||
}
|
||||
|
||||
private fun process(owner: String, name: String, descriptor: String): Boolean {
|
||||
val classData = classContext.loadClassData(owner.replace("/", ".")) ?: return false
|
||||
if (!classData.isTypeOf(ZIP_ENTRY_CLASS_NAME))
|
||||
return false
|
||||
if (descriptor != ZIP_ENTRY_GET_TIME_DESC)
|
||||
return false
|
||||
return when (name) {
|
||||
"getLastModifiedTime", "getLastAccessTime", "getCreationTime" -> {
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
DESUGAR_CLASS_NAME.replace('.', '/'),
|
||||
name,
|
||||
DESUGAR_GET_TIME_DESC,
|
||||
false
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Patch ZipArchiveOutputStream#copyFromZipInputStream
|
||||
class ZipOutputStreamPatcher(cv: ClassVisitor) : ClassVisitor(ASM9, cv) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String,
|
||||
descriptor: String,
|
||||
signature: String?,
|
||||
exceptions: Array<out String?>?
|
||||
): MethodVisitor? {
|
||||
return if (name == "copyFromZipInputStream") {
|
||||
MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
|
||||
} else {
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
}
|
||||
}
|
||||
|
||||
class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
|
||||
override fun visitMethodInsn(
|
||||
opcode: Int,
|
||||
owner: String,
|
||||
name: String,
|
||||
descriptor: String?,
|
||||
isInterface: Boolean
|
||||
) {
|
||||
if (owner == ZIP_UTIL_CLASS_NAME && name == "checkRequestedFeatures") {
|
||||
// Redirect
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
DESUGAR_CLASS_NAME.replace('.', '/'),
|
||||
name,
|
||||
descriptor,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import java.util.Properties
|
||||
|
||||
private val props = Properties()
|
||||
private var commitHash = ""
|
||||
private val supportAbis = setOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64", "riscv64")
|
||||
private val defaultAbis = setOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
|
||||
|
||||
object Config {
|
||||
operator fun get(key: String): String? {
|
||||
@@ -20,6 +22,10 @@ object Config {
|
||||
val version: String get() = get("version") ?: commitHash
|
||||
val versionCode: Int get() = get("magisk.versionCode")!!.toInt()
|
||||
val stubVersion: String get() = get("magisk.stubVersion")!!
|
||||
val abiList: Set<String> get() {
|
||||
val abiList = get("abiList") ?: return defaultAbis
|
||||
return abiList.split(Regex("\\s*,\\s*")).toSet() intersect supportAbis
|
||||
}
|
||||
}
|
||||
|
||||
class MagiskPlugin : Plugin<Project> {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import com.android.build.api.artifact.ArtifactTransformationRequest
|
||||
import com.android.build.api.artifact.SingleArtifact
|
||||
import com.android.build.api.dsl.ApkSigningConfig
|
||||
import com.android.build.api.instrumentation.FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
|
||||
import com.android.build.api.instrumentation.InstrumentationScope
|
||||
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
@@ -69,18 +71,18 @@ private val Project.androidComponents
|
||||
|
||||
fun Project.setupCommon() {
|
||||
androidBase {
|
||||
compileSdkVersion(34)
|
||||
buildToolsVersion = "34.0.0"
|
||||
compileSdkVersion(36)
|
||||
buildToolsVersion = "36.0.0"
|
||||
ndkPath = "$sdkDirectory/ndk/magisk"
|
||||
ndkVersion = "27.0.11902837"
|
||||
ndkVersion = "29.0.13113456"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
@@ -108,22 +110,45 @@ fun Project.setupCommon() {
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
jvmTarget = JvmTarget.JVM_21
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.downloadFile(url: String, checksum: String): File {
|
||||
val file = layout.buildDirectory.file(checksum).get().asFile
|
||||
if (file.exists()) {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
file.inputStream().use { md.update(it.readAllBytes()) }
|
||||
val hash = HexFormat.of().formatHex(md.digest())
|
||||
if (hash != checksum) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
if (!file.exists()) {
|
||||
file.parentFile.mkdirs()
|
||||
URI(url).toURL().openStream().use { dl ->
|
||||
file.outputStream().use {
|
||||
dl.copyTo(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
const val BUSYBOX_DOWNLOAD_URL =
|
||||
"https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.0.zip"
|
||||
"https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.1.zip"
|
||||
const val BUSYBOX_ZIP_CHECKSUM =
|
||||
"ea4f3019b0087dcb68130b32ab59dc2db0ee0af11d8396124a94c4231c5ea441"
|
||||
"b4d0551feabaf314e53c79316c980e8f66432e9fb91a69dbbf10a93564b40951"
|
||||
|
||||
fun Project.setupCoreLib() {
|
||||
setupCommon()
|
||||
|
||||
val abiList = Config.abiList
|
||||
|
||||
val syncLibs by tasks.registering(Sync::class) {
|
||||
into("src/main/jniLibs")
|
||||
for (abi in arrayOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64", "riscv64")) {
|
||||
for (abi in abiList) {
|
||||
into(abi) {
|
||||
from(rootProject.file("native/out/$abi")) {
|
||||
include("magiskboot", "magiskinit", "magiskpolicy", "magisk", "libinit-ld.so")
|
||||
@@ -132,7 +157,7 @@ fun Project.setupCoreLib() {
|
||||
}
|
||||
}
|
||||
onlyIf {
|
||||
if (inputs.sourceFiles.files.size != 25)
|
||||
if (inputs.sourceFiles.files.size != abiList.size * 5)
|
||||
throw StopExecutionException("Please build binaries first! (./build.py binary)")
|
||||
true
|
||||
}
|
||||
@@ -140,24 +165,8 @@ fun Project.setupCoreLib() {
|
||||
|
||||
val downloadBusybox by tasks.registering(Copy::class) {
|
||||
dependsOn(syncLibs)
|
||||
val bb = layout.buildDirectory.file(BUSYBOX_ZIP_CHECKSUM).get().asFile
|
||||
if (bb.exists()) {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
bb.inputStream().use { md.update(it.readAllBytes()) }
|
||||
val hash = HexFormat.of().formatHex(md.digest())
|
||||
if (hash != BUSYBOX_ZIP_CHECKSUM) {
|
||||
bb.delete()
|
||||
}
|
||||
}
|
||||
if (!bb.exists()) {
|
||||
bb.parentFile.mkdirs()
|
||||
URI(BUSYBOX_DOWNLOAD_URL).toURL().openStream().use { dl ->
|
||||
bb.outputStream().use {
|
||||
dl.copyTo(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
from(zipTree(bb))
|
||||
from(zipTree(downloadFile(BUSYBOX_DOWNLOAD_URL, BUSYBOX_ZIP_CHECKSUM)))
|
||||
include(abiList.map { "$it/libbusybox.so" })
|
||||
into("src/main/jniLibs")
|
||||
}
|
||||
|
||||
@@ -293,7 +302,10 @@ fun Project.setupAppCommon() {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
targetSdk = 34
|
||||
targetSdk = 36
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt")
|
||||
)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -345,7 +357,34 @@ fun Project.setupAppCommon() {
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.setupStub() {
|
||||
fun Project.setupMainApk() {
|
||||
setupAppCommon()
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
versionName = Config.version
|
||||
versionCode = Config.versionCode
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
|
||||
debugSymbolLevel = "FULL"
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
variant.instrumentation.apply {
|
||||
setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)
|
||||
transformClassesWith(
|
||||
DesugarClassVisitorFactory::class.java, InstrumentationScope.ALL) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.setupStubApk() {
|
||||
setupAppCommon()
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
@@ -372,8 +411,8 @@ fun Project.setupStub() {
|
||||
val outAppClassDir = layout.buildDirectory.file("generated/source/app/${variantLowered}").get().asFile
|
||||
val outResDir = layout.buildDirectory.dir("generated/source/res/${variantLowered}").get().asFile
|
||||
val aapt = File(androidApp.sdkDirectory, "build-tools/${androidApp.buildToolsVersion}/aapt2")
|
||||
val apk = layout.buildDirectory.file("intermediates/processed_res/" +
|
||||
"${variantLowered}/process${variantCapped}Resources/out/resources-${variantLowered}.ap_").get().asFile
|
||||
val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" +
|
||||
"${variantLowered}/process${variantCapped}Resources/linked-resources-binary-format-${variantLowered}.ap_").get().asFile
|
||||
|
||||
val genManifestTask = tasks.register("generate${variantCapped}ObfuscatedClass") {
|
||||
inputs.property("seed", RAND_SEED)
|
||||
@@ -414,8 +453,8 @@ fun Project.setupStub() {
|
||||
registerJavaGeneratingTask(processResourcesTask, outResDir)
|
||||
}
|
||||
// Override optimizeReleaseResources task
|
||||
val apk = layout.buildDirectory.file("intermediates/processed_res/" +
|
||||
"release/processReleaseResources/out/resources-release.ap_").get().asFile
|
||||
val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" +
|
||||
"release/processReleaseResources/linked-resources-binary-format-release.ap_").get().asFile
|
||||
val optRes = layout.buildDirectory.file("intermediates/optimized_processed_res/" +
|
||||
"release/optimizeReleaseResources/resources-release-optimize.ap_").get().asFile
|
||||
afterEvaluate {
|
||||
@@ -427,3 +466,31 @@ fun Project.setupStub() {
|
||||
delete.addAll(listOf("src/debug/AndroidManifest.xml", "src/release/AndroidManifest.xml"))
|
||||
}
|
||||
}
|
||||
|
||||
const val LSPOSED_DOWNLOAD_URL =
|
||||
"https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip"
|
||||
const val LSPOSED_CHECKSUM =
|
||||
"0ebc6bcb465d1c4b44b7220ab5f0252e6b4eb7fe43da74650476d2798bb29622"
|
||||
|
||||
const val SHAMIKO_DOWNLOAD_URL =
|
||||
"https://github.com/LSPosed/LSPosed.github.io/releases/download/shamiko-383/Shamiko-v1.2.1-383-release.zip"
|
||||
const val SHAMIKO_CHECKSUM =
|
||||
"93754a038c2d8f0e985bad45c7303b96f70a93d8335060e50146f028d3a9b13f"
|
||||
|
||||
fun Project.setupTestApk() {
|
||||
setupAppCommon()
|
||||
|
||||
androidApp.applicationVariants.all {
|
||||
val variantCapped = name.replaceFirstChar { it.uppercase() }
|
||||
val dlTask by tasks.register("download${variantCapped}Lsposed", Sync::class) {
|
||||
from(downloadFile(LSPOSED_DOWNLOAD_URL, LSPOSED_CHECKSUM)) {
|
||||
rename { "lsposed.zip" }
|
||||
}
|
||||
from(downloadFile(SHAMIKO_DOWNLOAD_URL, SHAMIKO_CHECKSUM)) {
|
||||
rename { "shamiko.zip" }
|
||||
}
|
||||
into("src/${this@all.name}/assets")
|
||||
}
|
||||
mergeAssetsProvider.configure { dependsOn(dlTask) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ version=string
|
||||
# Output path. Default: out
|
||||
outdir=string
|
||||
|
||||
# List of ABIs to build, separated with ','
|
||||
# Default: armeabi-v7a,x86,arm64-v8a,x86_64
|
||||
abiList=[string]
|
||||
|
||||
#####################################################
|
||||
# Signing configs for signing zips and APKs
|
||||
# These 4 variables has to be either all set or not
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user