mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 07:07:27 +00:00
Compare commits
253 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5a16418543 | ||
![]() |
7297aba15a | ||
![]() |
bc5d5f9502 | ||
![]() |
1761986c1b | ||
![]() |
1e034e3e0e | ||
![]() |
bbf9756bfa | ||
![]() |
96e559fb0e | ||
![]() |
4c45775131 | ||
![]() |
c072b4254d | ||
![]() |
2dbb812126 | ||
![]() |
be50f17f55 | ||
![]() |
6f77f190f2 | ||
![]() |
6bdc57cbe4 | ||
![]() |
de00f1d5a9 | ||
![]() |
e9b9bf987b | ||
![]() |
f4b6385f9f | ||
![]() |
75d905a56d | ||
![]() |
b1363ee479 | ||
![]() |
51afe43a30 | ||
![]() |
189c03c047 | ||
![]() |
ae9d270a32 | ||
![]() |
e47e869f6b | ||
![]() |
c39038a439 | ||
![]() |
69174e2c13 | ||
![]() |
474268a0af | ||
![]() |
eadb0307fa | ||
![]() |
5a5d0d5d72 | ||
![]() |
a1273bc467 | ||
![]() |
0c28a916be | ||
![]() |
0ba573b789 | ||
![]() |
ec42ee152c | ||
![]() |
abcb487361 | ||
![]() |
d12d9e82f1 | ||
![]() |
275208e81b | ||
![]() |
41226c12b8 | ||
![]() |
f86c66c99d | ||
![]() |
93876b5fd3 | ||
![]() |
b5b14ce343 | ||
![]() |
350d0d600c | ||
![]() |
f924ffcbf3 | ||
![]() |
0f5963f231 | ||
![]() |
1961ff2c40 | ||
![]() |
40003691d6 | ||
![]() |
8290358241 | ||
![]() |
ee34f775c3 | ||
![]() |
feb47cd88c | ||
![]() |
c6efb51f61 | ||
![]() |
a5acf33ccd | ||
![]() |
ab9ee449e4 | ||
![]() |
9571b6f9be | ||
![]() |
207d7fd3f6 | ||
![]() |
bcdcfa1104 | ||
![]() |
e0a4230dac | ||
![]() |
17ba5cba3e | ||
![]() |
f2e109ad7d | ||
![]() |
c83e141a1c | ||
![]() |
6089cc36de | ||
![]() |
9638dc0a66 | ||
![]() |
b191a14a23 | ||
![]() |
cf1bc82537 | ||
![]() |
6141bb5bb3 | ||
![]() |
4d2b62da0d | ||
![]() |
39383229d1 | ||
![]() |
08bfbb154a | ||
![]() |
d390ca2fdf | ||
![]() |
7ad77a14ae | ||
![]() |
f33343b4e6 | ||
![]() |
af65d07456 | ||
![]() |
16d728f379 | ||
![]() |
c97ab690b6 | ||
![]() |
4caed73fe0 | ||
![]() |
4856da1584 | ||
![]() |
0a07018fec | ||
![]() |
64c82e1f2c | ||
![]() |
e8e8afa6c2 | ||
![]() |
af2207433d | ||
![]() |
75ba62d588 | ||
![]() |
606d97ae4d | ||
![]() |
d778b0b0a7 | ||
![]() |
5ee6daf126 | ||
![]() |
43b9a09c9b | ||
![]() |
8475a2bb94 | ||
![]() |
d8692de2f4 | ||
![]() |
33a9abc946 | ||
![]() |
ee943afbc9 | ||
![]() |
1f7c3e9f14 | ||
![]() |
46770db18b | ||
![]() |
92f980601c | ||
![]() |
d0b8c16651 | ||
![]() |
a470ee6f93 | ||
![]() |
ff1c56683d | ||
![]() |
4ee4cbada6 | ||
![]() |
dbc2236dd2 | ||
![]() |
a8c4a33e91 | ||
![]() |
279f955a84 | ||
![]() |
fbd1dbb20c | ||
![]() |
6c09fc2e64 | ||
![]() |
f3304b482c | ||
![]() |
0a85ef61c3 | ||
![]() |
dc26ad7125 | ||
![]() |
24b1c607f3 | ||
![]() |
732a161b67 | ||
![]() |
9c7cf340a1 | ||
![]() |
399b9e5eba | ||
![]() |
5805573625 | ||
![]() |
a6b1149b9f | ||
![]() |
51e985ae7f | ||
![]() |
9929b25339 | ||
![]() |
2359cfc480 | ||
![]() |
81493475f9 | ||
![]() |
0493829231 | ||
![]() |
e2d1952ad9 | ||
![]() |
7450965458 | ||
![]() |
f45384685b | ||
![]() |
8abcccc262 | ||
![]() |
a9c89cbbbb | ||
![]() |
d2eaa6e6c1 | ||
![]() |
53257b6ea1 | ||
![]() |
c874391be4 | ||
![]() |
7e8e013832 | ||
![]() |
037f46f7f0 | ||
![]() |
d3e1c496ca | ||
![]() |
d7d0a44693 | ||
![]() |
9d6f6764cb | ||
![]() |
cb3ab63815 | ||
![]() |
caae932117 | ||
![]() |
e9cf27eb5a | ||
![]() |
6ee6685f4c | ||
![]() |
d15017b777 | ||
![]() |
a9387e63e1 | ||
![]() |
23c1f0111b | ||
![]() |
866386e21f | ||
![]() |
bf10496fa9 | ||
![]() |
607e6547a7 | ||
![]() |
6b21091fe2 | ||
![]() |
e58f98e844 | ||
![]() |
b8cb9cd84d | ||
![]() |
c1038ac6f9 | ||
![]() |
c556dd0aac | ||
![]() |
d2fbcd07b7 | ||
![]() |
bf6359abaa | ||
![]() |
d1621845b8 | ||
![]() |
f33f1d25d0 | ||
![]() |
40f25f4d56 | ||
![]() |
e13775ec2c | ||
![]() |
ee4dad7a13 | ||
![]() |
5e2ef1b7f4 | ||
![]() |
f8c38eab74 | ||
![]() |
305e8b3d14 | ||
![]() |
2a654e5d7f | ||
![]() |
57afae3425 | ||
![]() |
feb44f875e | ||
![]() |
7eebe62bb6 | ||
![]() |
9ea9f01933 | ||
![]() |
665c6bdc4b | ||
![]() |
c79bc83275 | ||
![]() |
c30fbdf145 | ||
![]() |
f12951bd1d | ||
![]() |
52f2e8c4a0 | ||
![]() |
1b2af1ed6d | ||
![]() |
0f9b2a7df8 | ||
![]() |
f2846694e1 | ||
![]() |
e668dbf6f7 | ||
![]() |
d77a368176 | ||
![]() |
ad0da08610 | ||
![]() |
0c52385ad4 | ||
![]() |
5b8b48ccc1 | ||
![]() |
659b9c6fee | ||
![]() |
ec31cab5a7 | ||
![]() |
dd93556ad8 | ||
![]() |
533aeadd38 | ||
![]() |
18d0cedbe2 | ||
![]() |
5a94ef9106 | ||
![]() |
8e8f01f8b5 | ||
![]() |
7087badf87 | ||
![]() |
47d2d4e3a5 | ||
![]() |
6c47d8f556 | ||
![]() |
8c9d0314fb | ||
![]() |
69144942e3 | ||
![]() |
5627053b74 | ||
![]() |
0f666de5e6 | ||
![]() |
eddc862fa3 | ||
![]() |
4327682120 | ||
![]() |
af5bdee78f | ||
![]() |
0e36e86dbf | ||
![]() |
f95478f1f1 | ||
![]() |
9fe8741a02 | ||
![]() |
a5768e02ea | ||
![]() |
f5aaff2b1e | ||
![]() |
655f778171 | ||
![]() |
2e77a426b2 | ||
![]() |
2bcf2e76f1 | ||
![]() |
57bd450798 | ||
![]() |
582cad1b8d | ||
![]() |
6ca2a3d841 | ||
![]() |
91773c3311 | ||
![]() |
dc61033b2c | ||
![]() |
f8d62a4b6c | ||
![]() |
1d2145b1b7 | ||
![]() |
1f7f84b74a | ||
![]() |
cd7a335d0f | ||
![]() |
17569005a4 | ||
![]() |
f36b21bae5 | ||
![]() |
fe1ca52f6d | ||
![]() |
1be647a279 | ||
![]() |
2ea1a47bec | ||
![]() |
2d799dae0d | ||
![]() |
c6408babac | ||
![]() |
a8c1ed8795 | ||
![]() |
e3cb5f8ddd | ||
![]() |
e160e211dd | ||
![]() |
22d05ca399 | ||
![]() |
bd2651057d | ||
![]() |
1610092ec4 | ||
![]() |
b9e6937996 | ||
![]() |
a207f03952 | ||
![]() |
851153dd7c | ||
![]() |
583ffc8177 | ||
![]() |
7518092ad2 | ||
![]() |
8c2ad3883a | ||
![]() |
d364554425 | ||
![]() |
726ffdcd98 | ||
![]() |
f9d22cf8ee | ||
![]() |
ee50da566f | ||
![]() |
9f7d410959 | ||
![]() |
bc94ea4334 | ||
![]() |
c0c9204848 | ||
![]() |
c0d1bf63bc | ||
![]() |
bbda0cdffe | ||
![]() |
7b5ff99cd1 | ||
![]() |
21ddb26db8 | ||
![]() |
7bf2e3875f | ||
![]() |
b136aba1e2 | ||
![]() |
0d84f80b3c | ||
![]() |
af45aeb771 | ||
![]() |
1c5a435e1f | ||
![]() |
0ea1257dcd | ||
![]() |
4c92677b5a | ||
![]() |
979260bd62 | ||
![]() |
f7de649a36 | ||
![]() |
0cf0d2b821 | ||
![]() |
3733c9a091 | ||
![]() |
e9f32e4f68 | ||
![]() |
68c2817d40 | ||
![]() |
83d837d868 | ||
![]() |
093eb15ee1 | ||
![]() |
c6412c1b1b | ||
![]() |
1151393d74 | ||
![]() |
468f3efb13 | ||
![]() |
d6b19b9d4c | ||
![]() |
709f25f600 | ||
![]() |
4b16e4b026 | ||
![]() |
cdfbc02922 |
78
.github/workflows/build.yml
vendored
78
.github/workflows/build.yml
vendored
@@ -2,17 +2,17 @@ name: Magisk Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'app/**'
|
||||
- 'native/**'
|
||||
- 'stub/**'
|
||||
- 'buildSrc/**'
|
||||
- 'build.py'
|
||||
- 'gradle.properties'
|
||||
- '.github/workflows/build.yml'
|
||||
- "app/**"
|
||||
- "native/**"
|
||||
- "stub/**"
|
||||
- "buildSrc/**"
|
||||
- "build.py"
|
||||
- "gradle.properties"
|
||||
- ".github/workflows/build.yml"
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -22,36 +22,24 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
env:
|
||||
NDK_CCACHE: ccache
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
RUSTC_WRAPPER: sccache
|
||||
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
||||
- name: Set up Python 3
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Set up ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Set up sccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
@@ -59,6 +47,7 @@ jobs:
|
||||
variant: sccache
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}
|
||||
max-size: 10000M
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v3
|
||||
@@ -106,3 +95,40 @@ jobs:
|
||||
with:
|
||||
name: ${{ github.sha }}-symbols
|
||||
path: app/build/outputs
|
||||
|
||||
test:
|
||||
name: Test on ${{ matrix.api }}
|
||||
runs-on: macos-latest
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
api: [23, 26, 28, 29, 34]
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
||||
- name: Set up Python 3
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ github.sha }}
|
||||
path: out
|
||||
|
||||
- name: AVD test
|
||||
run: |
|
||||
brew install coreutils
|
||||
scripts/avd_test.sh ${{ matrix.api }}
|
||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -19,9 +19,6 @@
|
||||
[submodule "nanopb"]
|
||||
path = native/src/external/nanopb
|
||||
url = https://github.com/nanopb/nanopb.git
|
||||
[submodule "mincrypt"]
|
||||
path = native/src/external/mincrypt
|
||||
url = https://github.com/topjohnwu/mincrypt.git
|
||||
[submodule "pcre"]
|
||||
path = native/src/external/pcre
|
||||
url = https://android.googlesource.com/platform/external/pcre
|
||||
@@ -43,6 +40,9 @@
|
||||
[submodule "lsplt"]
|
||||
path = native/src/external/lsplt
|
||||
url = https://github.com/LSPosed/LSPlt.git
|
||||
[submodule "system_properties"]
|
||||
path = native/src/external/system_properties
|
||||
url = https://github.com/topjohnwu/system_properties.git
|
||||
[submodule "termux-elf-cleaner"]
|
||||
path = tools/termux-elf-cleaner
|
||||
url = https://github.com/termux/termux-elf-cleaner.git
|
||||
|
29
README.MD
29
README.MD
@@ -18,14 +18,15 @@ Some highlight features:
|
||||
|
||||
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
|
||||
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v25.2)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v26.0)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v26.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v26.2)
|
||||
[](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-release.apk)
|
||||
[](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)
|
||||
- [Building and Development](https://topjohnwu.github.io/Magisk/build.html)
|
||||
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
|
||||
|
||||
## Bug Reports
|
||||
@@ -36,30 +37,6 @@ For installation issues, upload both boot image and install logs.<br>
|
||||
For Magisk issues, upload boot logcat or dmesg.<br>
|
||||
For Magisk app crashes, record and upload the logcat when the crash occurs.
|
||||
|
||||
## Building and Development
|
||||
|
||||
- Magisk builds on any OS Android Studio supports. Install Android Studio and do the initial setups.
|
||||
- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
|
||||
- Install Python 3.8+ \
|
||||
(Windows only: select **'Add Python to PATH'** in installer, and run `pip install colorama` after install)
|
||||
- Configure to use the JDK bundled in Android Studio:
|
||||
- macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/Contents/Home"`
|
||||
- Linux: `export PATH="/path/to/androidstudio/jre/bin:$PATH"`
|
||||
- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH`
|
||||
- Set environment variable `ANDROID_SDK_ROOT` to the Android SDK folder (can be found in Android Studio settings)
|
||||
- Run `./build.py ndk` to let the script download and install NDK for you
|
||||
- To start building, run `build.py` to see your options. \
|
||||
For each action, use `-h` to access help (e.g. `./build.py all -h`)
|
||||
- To start development, open the project with Android Studio. The IDE can be used for both app (Kotlin/Java) and native sources.
|
||||
- Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided.
|
||||
|
||||
## Signing and Distribution
|
||||
|
||||
- The certificate of the key used to sign the final Magisk APK product is also directly embedded into some executables. In release builds, Magisk's root daemon will enforce this certificate check and reject and forcefully uninstall any non-matching Magisk apps to protect users from malicious and unverified Magisk APKs.
|
||||
- To do any development on Magisk itself, switch to an **official debug build and reinstall Magisk** to bypass the signature check.
|
||||
- To distribute your own Magisk builds signed with your own keys, set your signing configs in `config.prop`.
|
||||
- Check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key) for more details on generating your own key.
|
||||
|
||||
## Translation Contributions
|
||||
|
||||
Default string resources for the Magisk app and its stub APK are located here:
|
||||
|
@@ -74,13 +74,13 @@ dependencies {
|
||||
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
|
||||
implementation("com.github.topjohnwu:lz4-java:1.7.1")
|
||||
implementation("com.jakewharton.timber:timber:5.0.1")
|
||||
implementation("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
implementation("org.bouncycastle:bcpkix-jdk18on:1.76")
|
||||
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.1")
|
||||
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2")
|
||||
implementation("io.noties.markwon:core:4.6.2")
|
||||
|
||||
val vLibsu = "5.0.5"
|
||||
val vLibsu = "5.2.1"
|
||||
implementation("com.github.topjohnwu.libsu:core:${vLibsu}")
|
||||
implementation("com.github.topjohnwu.libsu:service:${vLibsu}")
|
||||
implementation("com.github.topjohnwu.libsu:nio:${vLibsu}")
|
||||
@@ -90,21 +90,21 @@ dependencies {
|
||||
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
|
||||
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
|
||||
|
||||
val vOkHttp = "4.10.0"
|
||||
val vOkHttp = "4.11.0"
|
||||
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
|
||||
|
||||
val vMoshi = "1.14.0"
|
||||
val vMoshi = "1.15.0"
|
||||
implementation("com.squareup.moshi:moshi:${vMoshi}")
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
|
||||
|
||||
val vRoom = "2.5.1"
|
||||
val vRoom = "2.6.0-beta01"
|
||||
implementation("androidx.room:room-runtime:${vRoom}")
|
||||
implementation("androidx.room:room-ktx:${vRoom}")
|
||||
kapt("androidx.room:room-compiler:${vRoom}")
|
||||
|
||||
val vNav = "2.5.3"
|
||||
val vNav = "2.7.1"
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:${vNav}")
|
||||
|
||||
@@ -112,10 +112,10 @@ dependencies {
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.5.6")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.1")
|
||||
implementation("androidx.fragment:fragment-ktx:1.6.1")
|
||||
implementation("androidx.transition:transition:1.4.1")
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.core:core-splashscreen:1.0.0")
|
||||
implementation("com.google.android.material:material:1.8.0")
|
||||
implementation("androidx.core:core-ktx:1.10.1")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("com.google.android.material:material:1.9.0")
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
|
||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||
|
@@ -1,12 +0,0 @@
|
||||
package androidx.lifecycle;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ProcessLifecycleAccessor {
|
||||
public static void init(@NonNull Context context) {
|
||||
LifecycleDispatcher.init(context);
|
||||
ProcessLifecycleOwner.init(context);
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@ import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.OnRebindCallback
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.navigation.NavDirections
|
||||
import com.topjohnwu.magisk.BR
|
||||
|
||||
@@ -37,6 +38,9 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
||||
it.setVariable(BR.viewModel, viewModel)
|
||||
it.lifecycleOwner = viewLifecycleOwner
|
||||
}
|
||||
if (this is MenuProvider) {
|
||||
activity?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.STARTED)
|
||||
}
|
||||
savedInstanceState?.let { viewModel.onRestoreState(it) }
|
||||
return binding.root
|
||||
}
|
||||
@@ -66,8 +70,6 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (this is MenuProvider)
|
||||
activity?.addMenuProvider(this, viewLifecycleOwner)
|
||||
binding.addOnRebindCallback(object : OnRebindCallback<Binding>() {
|
||||
override fun onPreBind(binding: Binding): Boolean {
|
||||
this@BaseFragment.onPreBind(binding)
|
||||
@@ -91,5 +93,4 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
||||
fun NavDirections.navigate() {
|
||||
navigation?.currentDestination?.getAction(actionId)?.let { navigation!!.navigate(this) }
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -5,10 +5,10 @@ import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.ProcessLifecycleAccessor
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.utils.DispatcherExecutor
|
||||
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.core.utils.ShellInit
|
||||
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||
@@ -81,7 +81,7 @@ open class App() : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
ProcessLifecycleAccessor.init(this)
|
||||
ProcessLifecycle.init(this)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
|
@@ -1,22 +1,19 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Xml
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.edit
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.repository.BoolDBPropertyNoWrite
|
||||
import com.topjohnwu.magisk.core.repository.DBConfig
|
||||
import com.topjohnwu.magisk.core.repository.PreferenceConfig
|
||||
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||
import com.topjohnwu.magisk.ui.theme.Theme
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
object Config : PreferenceConfig, DBConfig {
|
||||
|
||||
@@ -25,13 +22,12 @@ object Config : PreferenceConfig, DBConfig {
|
||||
override val context get() = ServiceLocator.deContext
|
||||
override val coroutineScope get() = GlobalScope
|
||||
|
||||
@get:SuppressLint("ApplySharedPref")
|
||||
val prefsFile: File get() {
|
||||
// Flush prefs to disk
|
||||
prefs.edit().apply {
|
||||
remove(Key.ASKED_HOME)
|
||||
}.commit()
|
||||
return File("${context.filesDir.parent}/shared_prefs", "${fileName}.xml")
|
||||
private val prefsFile = File("${context.filesDir.parent}/shared_prefs", "${fileName}.xml")
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
fun getPrefsFile(): File {
|
||||
prefs.edit().remove(Key.ASKED_HOME).commit()
|
||||
return prefsFile
|
||||
}
|
||||
|
||||
object Key {
|
||||
@@ -118,7 +114,6 @@ object Config : PreferenceConfig, DBConfig {
|
||||
|
||||
@JvmField var keepVerity = false
|
||||
@JvmField var keepEnc = false
|
||||
@JvmField var patchVbmeta = false
|
||||
@JvmField var recovery = false
|
||||
|
||||
var bootId by preference(Key.BOOT_ID, "")
|
||||
@@ -172,9 +167,8 @@ object Config : PreferenceConfig, DBConfig {
|
||||
fun load(pkg: String?) {
|
||||
// Only try to load prefs when fresh install and a previous package name is set
|
||||
if (pkg != null && prefs.all.isEmpty()) runCatching {
|
||||
context.contentResolver.openInputStream(Provider.preferencesUri(pkg))?.use {
|
||||
prefs.edit { parsePrefs(it) }
|
||||
}
|
||||
context.contentResolver.openInputStream(Provider.preferencesUri(pkg))?.writeTo(prefsFile)
|
||||
return
|
||||
}
|
||||
|
||||
prefs.edit {
|
||||
@@ -191,52 +185,4 @@ object Config : PreferenceConfig, DBConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.Editor.parsePrefs(input: InputStream) {
|
||||
runCatching {
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
parser.setInput(input, "UTF-8")
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.START_TAG, null, "map")
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG)
|
||||
continue
|
||||
val key: String = parser.getAttributeValue(null, "name")
|
||||
fun value() = parser.getAttributeValue(null, "value")!!
|
||||
when (parser.name) {
|
||||
"string" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "string")
|
||||
putString(key, parser.nextText())
|
||||
parser.require(XmlPullParser.END_TAG, null, "string")
|
||||
}
|
||||
"boolean" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "boolean")
|
||||
putBoolean(key, value().toBoolean())
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.END_TAG, null, "boolean")
|
||||
}
|
||||
"int" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "int")
|
||||
putInt(key, value().toInt())
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.END_TAG, null, "int")
|
||||
}
|
||||
"long" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "long")
|
||||
putLong(key, value().toLong())
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.END_TAG, null, "long")
|
||||
}
|
||||
"float" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "int")
|
||||
putFloat(key, value().toFloat())
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.END_TAG, null, "int")
|
||||
}
|
||||
else -> parser.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
@@ -28,17 +27,17 @@ object Info {
|
||||
// Device state
|
||||
@JvmStatic val env by lazy { loadState() }
|
||||
@JvmField var isSAR = false
|
||||
var legacySAR = false
|
||||
var isAB = false
|
||||
@JvmField val isZygiskEnabled = System.getenv("ZYGISK_ENABLED") == "1"
|
||||
@JvmStatic val isFDE get() = crypto == "block"
|
||||
@JvmField var ramdisk = false
|
||||
@JvmField var vbmeta = false
|
||||
var patchBootVbmeta = false
|
||||
var crypto = ""
|
||||
var noDataExec = false
|
||||
var isRooted = false
|
||||
|
||||
@JvmField var hasGMS = true
|
||||
val isSamsung = Build.MANUFACTURER.equals("samsung", ignoreCase = true)
|
||||
@JvmField val isEmulator =
|
||||
getProperty("ro.kernel.qemu", "0") == "1" ||
|
||||
getProperty("ro.boot.qemu", "0") == "1"
|
||||
|
@@ -16,7 +16,7 @@ class Provider : BaseProvider() {
|
||||
|
||||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
|
||||
return when (uri.encodedPath ?: return null) {
|
||||
"/prefs_file" -> ParcelFileDescriptor.open(Config.prefsFile, MODE_READ_ONLY)
|
||||
"/prefs_file" -> ParcelFileDescriptor.open(Config.getPrefsFile(), MODE_READ_ONLY)
|
||||
else -> super.openFile(uri, mode)
|
||||
}
|
||||
}
|
||||
|
@@ -1,15 +1,27 @@
|
||||
package com.topjohnwu.magisk.core.data
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.topjohnwu.magisk.core.model.su.SuLog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
@Database(version = 1, entities = [SuLog::class], exportSchema = false)
|
||||
@Database(version = 2, entities = [SuLog::class], exportSchema = false)
|
||||
abstract class SuLogDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun suLogDao(): SuLogDao
|
||||
|
||||
companion object {
|
||||
val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) = with(database) {
|
||||
execSQL("ALTER TABLE logs ADD COLUMN target INTEGER NOT NULL DEFAULT -1")
|
||||
execSQL("ALTER TABLE logs ADD COLUMN context TEXT NOT NULL DEFAULT ''")
|
||||
execSQL("ALTER TABLE logs ADD COLUMN gids TEXT NOT NULL DEFAULT ''")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Dao
|
||||
|
@@ -12,6 +12,7 @@ import com.topjohnwu.magisk.core.data.magiskdb.StringDao
|
||||
import com.topjohnwu.magisk.core.ktx.deviceProtectedContext
|
||||
import com.topjohnwu.magisk.core.repository.LogRepository
|
||||
import com.topjohnwu.magisk.core.repository.NetworkService
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.utils.NoCopySpannableFactory
|
||||
|
||||
@@ -23,6 +24,7 @@ object ServiceLocator {
|
||||
lateinit var context: Context
|
||||
val deContext by lazy { context.deviceProtectedContext }
|
||||
val timeoutPrefs by lazy { deContext.getSharedPreferences("su_timeout", 0) }
|
||||
val biometrics by lazy { BiometricHelper(context) }
|
||||
|
||||
// Database
|
||||
val policyDB = PolicyDao()
|
||||
@@ -45,6 +47,7 @@ object ServiceLocator {
|
||||
|
||||
private fun createSuLogDatabase(context: Context) =
|
||||
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
|
||||
.addMigrations(SuLogDatabase.MIGRATION_1_2)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
|
||||
|
@@ -12,7 +12,6 @@ import android.graphics.Canvas
|
||||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Process
|
||||
@@ -21,16 +20,12 @@ import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.getSystemService
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import java.io.File
|
||||
import kotlin.Array
|
||||
import kotlin.String
|
||||
import java.lang.reflect.Array as JArray
|
||||
|
||||
fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
||||
|
||||
@@ -56,110 +51,6 @@ val Context.deviceProtectedContext: Context get() =
|
||||
createDeviceProtectedStorageContext()
|
||||
} else { this }
|
||||
|
||||
fun Intent.startActivityWithRoot() {
|
||||
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
|
||||
val cmd = toCommand(args).joinToString(" ")
|
||||
Shell.cmd(cmd).submit()
|
||||
}
|
||||
|
||||
fun Intent.toCommand(args: MutableList<String> = mutableListOf()): MutableList<String> {
|
||||
action?.also {
|
||||
args.add("-a")
|
||||
args.add(it)
|
||||
}
|
||||
component?.also {
|
||||
args.add("-n")
|
||||
args.add(it.flattenToString())
|
||||
}
|
||||
data?.also {
|
||||
args.add("-d")
|
||||
args.add(it.toString())
|
||||
}
|
||||
categories?.also {
|
||||
for (cat in it) {
|
||||
args.add("-c")
|
||||
args.add(cat)
|
||||
}
|
||||
}
|
||||
type?.also {
|
||||
args.add("-t")
|
||||
args.add(it)
|
||||
}
|
||||
extras?.also {
|
||||
loop@ for (key in it.keySet()) {
|
||||
val v = it[key] ?: continue
|
||||
var value: Any = v
|
||||
val arg: String
|
||||
when {
|
||||
v is String -> arg = "--es"
|
||||
v is Boolean -> arg = "--ez"
|
||||
v is Int -> arg = "--ei"
|
||||
v is Long -> arg = "--el"
|
||||
v is Float -> arg = "--ef"
|
||||
v is Uri -> arg = "--eu"
|
||||
v is ComponentName -> {
|
||||
arg = "--ecn"
|
||||
value = v.flattenToString()
|
||||
}
|
||||
v is List<*> -> {
|
||||
if (v.isEmpty())
|
||||
continue@loop
|
||||
|
||||
arg = if (v[0] is Int)
|
||||
"--eial"
|
||||
else if (v[0] is Long)
|
||||
"--elal"
|
||||
else if (v[0] is Float)
|
||||
"--efal"
|
||||
else if (v[0] is String)
|
||||
"--esal"
|
||||
else
|
||||
continue@loop /* Unsupported */
|
||||
|
||||
val sb = StringBuilder()
|
||||
for (o in v) {
|
||||
sb.append(o.toString().replace(",", "\\,"))
|
||||
sb.append(',')
|
||||
}
|
||||
// Remove trailing comma
|
||||
sb.deleteCharAt(sb.length - 1)
|
||||
value = sb
|
||||
}
|
||||
v.javaClass.isArray -> {
|
||||
arg = if (v is IntArray)
|
||||
"--eia"
|
||||
else if (v is LongArray)
|
||||
"--ela"
|
||||
else if (v is FloatArray)
|
||||
"--efa"
|
||||
else if (v is Array<*> && v.isArrayOf<String>())
|
||||
"--esa"
|
||||
else
|
||||
continue@loop /* Unsupported */
|
||||
|
||||
val sb = StringBuilder()
|
||||
val len = JArray.getLength(v)
|
||||
for (i in 0 until len) {
|
||||
sb.append(JArray.get(v, i)!!.toString().replace(",", "\\,"))
|
||||
sb.append(',')
|
||||
}
|
||||
// Remove trailing comma
|
||||
sb.deleteCharAt(sb.length - 1)
|
||||
value = sb
|
||||
}
|
||||
else -> continue@loop
|
||||
} /* Unsupported */
|
||||
|
||||
args.add(arg)
|
||||
args.add(key)
|
||||
args.add(value.toString())
|
||||
}
|
||||
}
|
||||
args.add("-f")
|
||||
args.add(flags.toString())
|
||||
return args
|
||||
}
|
||||
|
||||
fun Context.cachedFile(name: String) = File(cacheDir, name)
|
||||
|
||||
fun ApplicationInfo.getLabel(pm: PackageManager): String {
|
||||
|
@@ -1,8 +1,6 @@
|
||||
package com.topjohnwu.magisk.core.ktx
|
||||
|
||||
import android.content.Context
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -15,12 +13,4 @@ fun reboot(reason: String = if (Config.recovery) "recovery" else "") {
|
||||
Shell.cmd("/system/bin/svc power reboot $reason || /system/bin/reboot $reason").submit()
|
||||
}
|
||||
|
||||
fun relaunchApp(context: Context) {
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) ?: return
|
||||
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
|
||||
val cmd = intent.toCommand(args).joinToString(separator = " ")
|
||||
Shell.cmd("run_delay 1 \"$cmd\"").exec()
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
|
||||
suspend fun Shell.Job.await() = withContext(Dispatchers.IO) { exec() }
|
||||
|
@@ -14,7 +14,10 @@ class SuLog(
|
||||
val packageName: String,
|
||||
val appName: String,
|
||||
val command: String,
|
||||
val action: Boolean,
|
||||
val action: Int,
|
||||
val target: Int,
|
||||
val context: String,
|
||||
val gids: String,
|
||||
val time: Long = System.currentTimeMillis()
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true) var id: Int = 0
|
||||
@@ -25,7 +28,10 @@ fun PackageManager.createSuLog(
|
||||
toUid: Int,
|
||||
fromPid: Int,
|
||||
command: String,
|
||||
policy: Int
|
||||
policy: Int,
|
||||
target: Int,
|
||||
context: String,
|
||||
gids: String,
|
||||
): SuLog {
|
||||
val appInfo = info.applicationInfo
|
||||
return SuLog(
|
||||
@@ -35,7 +41,10 @@ fun PackageManager.createSuLog(
|
||||
packageName = getNameForUid(appInfo.uid)!!,
|
||||
appName = appInfo.getLabel(this),
|
||||
command = command,
|
||||
action = policy == SuPolicy.ALLOW
|
||||
action = policy,
|
||||
target = target,
|
||||
context = context,
|
||||
gids = gids,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,7 +53,10 @@ fun createSuLog(
|
||||
toUid: Int,
|
||||
fromPid: Int,
|
||||
command: String,
|
||||
policy: Int
|
||||
policy: Int,
|
||||
target: Int,
|
||||
context: String,
|
||||
gids: String,
|
||||
): SuLog {
|
||||
return SuLog(
|
||||
fromUid = fromUid,
|
||||
@@ -53,6 +65,9 @@ fun createSuLog(
|
||||
packageName = "[UID] $fromUid",
|
||||
appName = "[UID] $fromUid",
|
||||
command = command,
|
||||
action = policy == SuPolicy.ALLOW
|
||||
action = policy,
|
||||
target = target,
|
||||
context = context,
|
||||
gids = gids,
|
||||
)
|
||||
}
|
||||
|
@@ -57,17 +57,20 @@ object SuCallbackHandler {
|
||||
val toUid = data.getIntComp("to.uid", -1)
|
||||
val pid = data.getIntComp("pid", -1)
|
||||
val command = data.getString("command", "")
|
||||
val target = data.getIntComp("target", -1)
|
||||
val seContext = data.getString("context", "")
|
||||
val gids = data.getString("gids", "")
|
||||
|
||||
val pm = context.packageManager
|
||||
|
||||
val log = runCatching {
|
||||
pm.getPackageInfo(fromUid, pid)?.let {
|
||||
pm.createSuLog(it, toUid, pid, command, policy)
|
||||
pm.createSuLog(it, toUid, pid, command, policy, target, seContext, gids)
|
||||
}
|
||||
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy)
|
||||
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
|
||||
|
||||
if (notify)
|
||||
notify(context, log.action, log.appName)
|
||||
notify(context, log.action == SuPolicy.ALLOW, log.appName)
|
||||
|
||||
runBlocking { ServiceLocator.logRepo.insert(log) }
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.DataOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -22,7 +23,7 @@ class SuRequestHandler(
|
||||
private val policyDB: PolicyDao
|
||||
) {
|
||||
|
||||
private lateinit var output: DataOutputStream
|
||||
private lateinit var output: File
|
||||
private lateinit var policy: SuPolicy
|
||||
lateinit var pkgInfo: PackageInfo
|
||||
private set
|
||||
@@ -52,37 +53,27 @@ class SuRequestHandler(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun close() {
|
||||
if (::output.isInitialized)
|
||||
runCatching { output.close() }
|
||||
}
|
||||
|
||||
private suspend fun init(intent: Intent) = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val fifo = intent.getStringExtra("fifo") ?: throw IOException("fifo == null")
|
||||
output = DataOutputStream(FileOutputStream(fifo))
|
||||
val uid = intent.getIntExtra("uid", -1)
|
||||
if (uid <= 0) {
|
||||
throw IOException("uid == $uid")
|
||||
}
|
||||
policy = SuPolicy(uid)
|
||||
val pid = intent.getIntExtra("pid", -1)
|
||||
try {
|
||||
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
|
||||
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
||||
// We only fill in sharedUserId and leave other fields uninitialized
|
||||
sharedUserId = name.split(":")[0]
|
||||
}
|
||||
return@withContext true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
respond(SuPolicy.DENY, -1)
|
||||
return@withContext false
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
close()
|
||||
return@withContext false
|
||||
private suspend fun init(intent: Intent): Boolean {
|
||||
val uid = intent.getIntExtra("uid", -1)
|
||||
val pid = intent.getIntExtra("pid", -1)
|
||||
val fifo = intent.getStringExtra("fifo")
|
||||
if (uid <= 0 || pid <= 0 || fifo == null) {
|
||||
return false
|
||||
}
|
||||
output = File(fifo)
|
||||
policy = SuPolicy(uid)
|
||||
try {
|
||||
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
|
||||
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
||||
// We only fill in sharedUserId and leave other fields uninitialized
|
||||
sharedUserId = name.split(":")[0]
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Timber.e(e)
|
||||
respond(SuPolicy.DENY, -1)
|
||||
return false
|
||||
}
|
||||
return output.canWrite()
|
||||
}
|
||||
|
||||
suspend fun respond(action: Int, time: Int) {
|
||||
@@ -97,14 +88,15 @@ class SuRequestHandler(
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
output.writeInt(policy.policy)
|
||||
output.flush()
|
||||
DataOutputStream(FileOutputStream(output)).use {
|
||||
it.writeInt(policy.policy)
|
||||
it.flush()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
} finally {
|
||||
close()
|
||||
if (until >= 0)
|
||||
policyDB.update(policy)
|
||||
}
|
||||
if (until >= 0) {
|
||||
policyDB.update(policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,30 @@
|
||||
package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.net.Uri
|
||||
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.BuildConfig
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.*
|
||||
import com.topjohnwu.magisk.core.AppApkPath
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
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.withStreams
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.signing.SignBoot
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import com.topjohnwu.superuser.internal.NOPList
|
||||
@@ -40,6 +46,7 @@ import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
abstract class MagiskInstallImpl protected constructor(
|
||||
protected val console: MutableList<String> = NOPList.getInstance(),
|
||||
@@ -111,7 +118,9 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
val name = n.substring(3, n.length - 3)
|
||||
val dest = File(installDir, name)
|
||||
zf.getInputStream(it).writeTo(dest)
|
||||
dest.setExecutable(true)
|
||||
}
|
||||
zf.close()
|
||||
} else {
|
||||
val info = context.applicationInfo
|
||||
var libs = File(info.nativeLibraryDir).listFiles { _, name ->
|
||||
@@ -119,7 +128,8 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
} ?: emptyArray()
|
||||
|
||||
// Also symlink magisk32 on non 64-bit only 64-bit devices
|
||||
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String?
|
||||
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir")
|
||||
.get(info) as String?
|
||||
if (lib32 != null) {
|
||||
libs += File(lib32, "libmagisk32.so")
|
||||
}
|
||||
@@ -164,102 +174,215 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun InputStream.cleanPump(out: OutputStream) = withStreams(this, out) { src, _ ->
|
||||
src.copyTo(out)
|
||||
}
|
||||
private fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyTo(it) }
|
||||
|
||||
private fun newTarEntry(name: String, size: Long): TarEntry {
|
||||
console.add("-- Writing: $name")
|
||||
return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */))
|
||||
}
|
||||
|
||||
private class LZ4InputStream(s: InputStream) : LZ4FrameInputStream(s) {
|
||||
// Workaround bug in LZ4FrameInputStream
|
||||
override fun available() = 0
|
||||
}
|
||||
|
||||
private class NoBootException : IOException()
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun processTar(input: InputStream, output: OutputStream): OutputStream {
|
||||
private fun processTar(tarIn: TarInputStream, tarOut: TarOutputStream): ExtendedFile {
|
||||
console.add("- Processing tar file")
|
||||
val tarOut = TarOutputStream(output)
|
||||
TarInputStream(input).use { tarIn ->
|
||||
lateinit var entry: TarEntry
|
||||
lateinit var entry: TarEntry
|
||||
|
||||
fun decompressedStream(): InputStream {
|
||||
val src = if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
|
||||
return object : FilterInputStream(src) {
|
||||
override fun available() = 0 /* Workaround bug in LZ4FrameInputStream */
|
||||
override fun close() { /* Never close src stream */ }
|
||||
}
|
||||
}
|
||||
fun decompressedStream(): InputStream {
|
||||
return if (entry.name.endsWith(".lz4")) LZ4InputStream(tarIn) else tarIn
|
||||
}
|
||||
|
||||
while (tarIn.nextEntry?.let { entry = it } != null) {
|
||||
if (entry.name.startsWith("boot.img") ||
|
||||
entry.name.startsWith("init_boot.img") ||
|
||||
(Config.recovery && entry.name.contains("recovery.img"))) {
|
||||
val name = entry.name.replace(".lz4", "")
|
||||
console.add("-- Extracting: $name")
|
||||
while (tarIn.nextEntry?.let { entry = it } != null) {
|
||||
if (entry.name.startsWith("boot.img") ||
|
||||
entry.name.startsWith("init_boot.img") ||
|
||||
(Config.recovery && entry.name.contains("recovery.img"))) {
|
||||
val name = entry.name.replace(".lz4", "")
|
||||
console.add("-- Extracting: $name")
|
||||
|
||||
val extract = installDir.getChildFile(name)
|
||||
decompressedStream().cleanPump(extract.newOutputStream())
|
||||
} else if (entry.name.contains("vbmeta.img")) {
|
||||
val rawData = decompressedStream().readBytes()
|
||||
// Valid vbmeta.img should be at least 256 bytes
|
||||
if (rawData.size < 256)
|
||||
continue
|
||||
val extract = installDir.getChildFile(name)
|
||||
decompressedStream().copyAndCloseOut(extract.newOutputStream())
|
||||
} else if (entry.name.contains("vbmeta.img")) {
|
||||
val rawData = decompressedStream().readBytes()
|
||||
// Valid vbmeta.img should be at least 256 bytes
|
||||
if (rawData.size < 256)
|
||||
continue
|
||||
|
||||
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |
|
||||
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
|
||||
console.add("-- Patching: vbmeta.img")
|
||||
ByteBuffer.wrap(rawData).putInt(120, 3)
|
||||
tarOut.putNextEntry(newTarEntry("vbmeta.img", rawData.size.toLong()))
|
||||
tarOut.write(rawData)
|
||||
} else {
|
||||
console.add("-- Copying: ${entry.name}")
|
||||
tarOut.putNextEntry(entry)
|
||||
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
|
||||
}
|
||||
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |
|
||||
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
|
||||
console.add("-- Patching: vbmeta.img")
|
||||
ByteBuffer.wrap(rawData).putInt(120, 3)
|
||||
tarOut.putNextEntry(newTarEntry("vbmeta.img", rawData.size.toLong()))
|
||||
tarOut.write(rawData)
|
||||
// vbmeta partition exist, disable boot vbmeta patch
|
||||
Info.patchBootVbmeta = false
|
||||
} else if (entry.name.contains("userdata.img")) {
|
||||
continue
|
||||
} else {
|
||||
console.add("-- Copying: ${entry.name}")
|
||||
tarOut.putNextEntry(entry)
|
||||
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
|
||||
}
|
||||
}
|
||||
|
||||
val boot = installDir.getChildFile("boot.img")
|
||||
val initBoot = installDir.getChildFile("init_boot.img")
|
||||
val recovery = installDir.getChildFile("recovery.img")
|
||||
if (Config.recovery && recovery.exists() && boot.exists()) {
|
||||
// Install to recovery
|
||||
srcBoot = recovery
|
||||
// Repack boot image to prevent auto restore
|
||||
arrayOf(
|
||||
"cd $installDir",
|
||||
"chmod -R 755 .",
|
||||
"./magiskboot unpack boot.img",
|
||||
"./magiskboot repack boot.img",
|
||||
"cat new-boot.img > boot.img",
|
||||
"./magiskboot cleanup",
|
||||
"rm -f new-boot.img",
|
||||
"cd /").sh()
|
||||
boot.newInputStream().use {
|
||||
tarOut.putNextEntry(newTarEntry("boot.img", boot.length()))
|
||||
|
||||
fun ExtendedFile.copyToTar() {
|
||||
newInputStream().use {
|
||||
tarOut.putNextEntry(newTarEntry(name, length()))
|
||||
it.copyTo(tarOut)
|
||||
}
|
||||
boot.delete()
|
||||
} else {
|
||||
srcBoot = when {
|
||||
initBoot.exists() -> initBoot
|
||||
boot.exists() -> boot
|
||||
else -> {
|
||||
console.add("! No boot image found")
|
||||
throw IOException()
|
||||
delete()
|
||||
}
|
||||
|
||||
// Patch priority: recovery > init_boot > boot
|
||||
return when {
|
||||
recovery.exists() -> {
|
||||
if (boot.exists()) {
|
||||
// Repack boot image to prevent auto restore
|
||||
arrayOf(
|
||||
"cd $installDir",
|
||||
"chmod -R 755 .",
|
||||
"./magiskboot unpack boot.img",
|
||||
"./magiskboot repack boot.img",
|
||||
"cat new-boot.img > boot.img",
|
||||
"./magiskboot cleanup",
|
||||
"rm -f new-boot.img",
|
||||
"cd /").sh()
|
||||
boot.copyToTar()
|
||||
}
|
||||
recovery
|
||||
}
|
||||
initBoot.exists() -> {
|
||||
if (boot.exists())
|
||||
boot.copyToTar()
|
||||
initBoot
|
||||
}
|
||||
boot.exists() -> boot
|
||||
else -> throw NoBootException()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun processZip(zipIn: ZipInputStream): ExtendedFile {
|
||||
console.add("- Processing zip file")
|
||||
val boot = installDir.getChildFile("boot.img")
|
||||
val initBoot = installDir.getChildFile("init_boot.img")
|
||||
lateinit var entry: ZipEntry
|
||||
while (zipIn.nextEntry?.also { entry = it } != null) {
|
||||
if (entry.isDirectory) continue
|
||||
when (entry.name.substringAfterLast('/')) {
|
||||
"payload.bin" -> {
|
||||
try {
|
||||
return processPayload(zipIn)
|
||||
} catch (e: IOException) {
|
||||
// No boot image in payload.bin, continue to find boot images
|
||||
}
|
||||
}
|
||||
"init_boot.img" -> {
|
||||
console.add("- Extracting init_boot.img")
|
||||
zipIn.copyAndCloseOut(initBoot.newOutputStream())
|
||||
return initBoot
|
||||
}
|
||||
"boot.img" -> {
|
||||
console.add("- Extracting boot.img")
|
||||
zipIn.copyAndCloseOut(boot.newOutputStream())
|
||||
// Don't return here since there might be an init_boot.img
|
||||
}
|
||||
}
|
||||
}
|
||||
return tarOut
|
||||
if (boot.exists()) {
|
||||
return boot
|
||||
} else {
|
||||
throw NoBootException()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun processPayload(input: InputStream): ExtendedFile {
|
||||
var fifo: File? = null
|
||||
try {
|
||||
console.add("- Processing payload.bin")
|
||||
fifo = File.createTempFile("payload-fifo-", null, installDir)
|
||||
fifo.delete()
|
||||
Os.mkfifo(fifo.path, 420 /* 0644 */)
|
||||
|
||||
// Enqueue the shell command first, or the subsequent FIFO open will block
|
||||
val future = arrayOf(
|
||||
"cd $installDir",
|
||||
"./magiskboot extract $fifo",
|
||||
"cd /"
|
||||
).eq()
|
||||
|
||||
val fd = Os.open(fifo.path, O_WRONLY, 0)
|
||||
try {
|
||||
val bufSize = 1024 * 1024
|
||||
val buf = ByteBuffer.allocate(bufSize)
|
||||
buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()
|
||||
while (buf.hasRemaining()) {
|
||||
try {
|
||||
Os.write(fd, buf)
|
||||
} catch (e: ErrnoException) {
|
||||
if (e.errno != OsConstants.EPIPE)
|
||||
throw e
|
||||
// If SIGPIPE, then the other side is closed, we're done
|
||||
break
|
||||
}
|
||||
if (!buf.hasRemaining()) {
|
||||
buf.limit(bufSize)
|
||||
buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Os.close(fd)
|
||||
}
|
||||
|
||||
val success = try { future.get().isSuccess } catch (e: Exception) { false }
|
||||
if (!success) {
|
||||
console.add("! Error while extracting payload.bin")
|
||||
throw IOException()
|
||||
}
|
||||
val boot = installDir.getChildFile("boot.img")
|
||||
val initBoot = installDir.getChildFile("init_boot.img")
|
||||
return when {
|
||||
initBoot.exists() -> {
|
||||
console.add("-- Extract init_boot.img")
|
||||
initBoot
|
||||
}
|
||||
boot.exists() -> {
|
||||
console.add("-- Extract boot.img")
|
||||
boot
|
||||
}
|
||||
else -> {
|
||||
throw NoBootException()
|
||||
}
|
||||
}
|
||||
} catch (e: ErrnoException) {
|
||||
throw IOException(e)
|
||||
} finally {
|
||||
fifo?.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFile(uri: Uri): Boolean {
|
||||
val outStream: OutputStream
|
||||
var outFile: MediaStoreUtils.UriFile? = null
|
||||
val outFile: MediaStoreUtils.UriFile
|
||||
|
||||
// Process input file
|
||||
try {
|
||||
uri.inputStream().buffered().use { src ->
|
||||
src.mark(500)
|
||||
val magic = ByteArray(5)
|
||||
if (src.skip(257) != 257L || src.read(magic) != magic.size) {
|
||||
val magic = ByteArray(4)
|
||||
val tarMagic = ByteArray(5)
|
||||
if (src.read(magic) != magic.size || src.skip(253) != 253L ||
|
||||
src.read(tarMagic) != tarMagic.size
|
||||
) {
|
||||
console.add("! Invalid input file")
|
||||
return false
|
||||
}
|
||||
@@ -275,29 +398,52 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
toString()
|
||||
}
|
||||
|
||||
outStream = if (magic.contentEquals("ustar".toByteArray())) {
|
||||
srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) {
|
||||
// tar file
|
||||
outFile = MediaStoreUtils.getFile("$filename.tar", true)
|
||||
processTar(src, outFile!!.uri.outputStream())
|
||||
outStream = TarOutputStream(outFile.uri.outputStream())
|
||||
|
||||
try {
|
||||
processTar(TarInputStream(src), outStream)
|
||||
} catch (e: IOException) {
|
||||
outStream.close()
|
||||
outFile.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
// raw image
|
||||
srcBoot = installDir.getChildFile("boot.img")
|
||||
console.add("- Copying image to cache")
|
||||
src.cleanPump(srcBoot.newOutputStream())
|
||||
outFile = MediaStoreUtils.getFile("$filename.img", true)
|
||||
outFile!!.uri.outputStream()
|
||||
outStream = outFile.uri.outputStream()
|
||||
|
||||
try {
|
||||
if (magic.contentEquals("CrAU".toByteArray())) {
|
||||
processPayload(src)
|
||||
} else if (magic.contentEquals("PK\u0003\u0004".toByteArray())) {
|
||||
processZip(ZipInputStream(src))
|
||||
} else {
|
||||
console.add("- Copying image to cache")
|
||||
installDir.getChildFile("boot.img").also {
|
||||
src.copyAndCloseOut(it.newOutputStream())
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
outStream.close()
|
||||
outFile.delete()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
if (e is NoBootException)
|
||||
console.add("! No boot image found")
|
||||
console.add("! Process error")
|
||||
outFile?.delete()
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
|
||||
// Patch file
|
||||
if (!patchBoot()) {
|
||||
outFile!!.delete()
|
||||
outFile.delete()
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -314,7 +460,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
}
|
||||
outStream.putNextEntry(newTarEntry(name, newBoot.length()))
|
||||
}
|
||||
newBoot.newInputStream().cleanPump(outStream)
|
||||
newBoot.newInputStream().copyAndClose(outStream)
|
||||
newBoot.delete()
|
||||
|
||||
console.add("")
|
||||
@@ -324,7 +470,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
console.add("****************************")
|
||||
} catch (e: IOException) {
|
||||
console.add("! Failed to output to $outFile")
|
||||
outFile!!.delete()
|
||||
outFile.delete()
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
@@ -337,22 +483,6 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
}
|
||||
|
||||
private fun patchBoot(): Boolean {
|
||||
var isSigned = false
|
||||
if (!srcBoot.isCharacter) {
|
||||
try {
|
||||
srcBoot.newInputStream().use {
|
||||
if (SignBoot.verifySignature(it, null)) {
|
||||
isSigned = true
|
||||
console.add("- Boot image is signed with AVB 1.0")
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
console.add("! Unable to check signature")
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
val newBoot = installDir.getChildFile("new-boot.img")
|
||||
if (!useRootDir) {
|
||||
// Create output files before hand
|
||||
@@ -364,34 +494,15 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
"cd $installDir",
|
||||
"KEEPFORCEENCRYPT=${Config.keepEnc} " +
|
||||
"KEEPVERITY=${Config.keepVerity} " +
|
||||
"PATCHVBMETAFLAG=${Config.patchVbmeta} " +
|
||||
"PATCHVBMETAFLAG=${Info.patchBootVbmeta} " +
|
||||
"RECOVERYMODE=${Config.recovery} " +
|
||||
"SYSTEM_ROOT=${Info.isSAR} " +
|
||||
"LEGACYSAR=${Info.legacySAR} " +
|
||||
"sh boot_patch.sh $srcBoot")
|
||||
val isSuccess = cmds.sh().isSuccess
|
||||
|
||||
if (!cmds.sh().isSuccess)
|
||||
return false
|
||||
shell.newJob().add("./magiskboot cleanup", "cd /").exec()
|
||||
|
||||
val job = shell.newJob().add("./magiskboot cleanup", "cd /")
|
||||
|
||||
if (isSigned) {
|
||||
console.add("- Signing boot image with verity keys")
|
||||
val signed = File.createTempFile("signed", ".img", context.cacheDir)
|
||||
try {
|
||||
val src = newBoot.newInputStream().buffered()
|
||||
val out = signed.outputStream().buffered()
|
||||
withStreams(src, out) { _, _ ->
|
||||
SignBoot.doSignature(null, null, src, out, "/boot")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
console.add("! Unable to sign image")
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
job.add("cat $signed > $newBoot", "rm -f $signed")
|
||||
}
|
||||
job.exec()
|
||||
return true
|
||||
return isSuccess
|
||||
}
|
||||
|
||||
private fun flashBoot() = "direct_install $installDir $srcBoot".sh().isSuccess
|
||||
@@ -413,6 +524,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Array<String>.eq() = shell.newJob().add(*this).to(console, logs).enqueue()
|
||||
private fun String.sh() = shell.newJob().add(this).to(console, logs).exec()
|
||||
private fun Array<String>.sh() = shell.newJob().add(*this).to(console, logs).exec()
|
||||
private fun String.fsh() = ShellUtils.fastCmd(shell, this)
|
||||
|
@@ -1,30 +1,24 @@
|
||||
package com.topjohnwu.magisk.core.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
|
||||
object BiometricHelper {
|
||||
class BiometricHelper(context: Context) {
|
||||
|
||||
private val mgr by lazy { BiometricManager.from(AppContext) }
|
||||
private val mgr = BiometricManager.from(context)
|
||||
|
||||
val isSupported get() = when (mgr.canAuthenticate()) {
|
||||
val isSupported get() = when (mgr.canAuthenticate(Authenticators.BIOMETRIC_WEAK)) {
|
||||
BiometricManager.BIOMETRIC_SUCCESS -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val isEnabled: Boolean get() {
|
||||
val enabled = Config.suBiometric
|
||||
if (enabled && !isSupported) {
|
||||
Config.suBiometric = false
|
||||
return false
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
val isEnabled get() = isSupported && Config.suBiometric
|
||||
|
||||
fun authenticate(
|
||||
activity: FragmentActivity,
|
||||
@@ -48,7 +42,7 @@ object BiometricHelper {
|
||||
)
|
||||
val info = BiometricPrompt.PromptInfo.Builder()
|
||||
.setConfirmationRequired(true)
|
||||
.setDeviceCredentialAllowed(false)
|
||||
.setAllowedAuthenticators(Authenticators.BIOMETRIC_WEAK)
|
||||
.setTitle(activity.getString(R.string.authenticate))
|
||||
.setNegativeButtonText(activity.getString(android.R.string.cancel))
|
||||
.build()
|
||||
|
@@ -102,6 +102,8 @@ object MediaStoreUtils {
|
||||
|
||||
fun Uri.outputStream() = cr.openOutputStream(this, "rwt") ?: throw FileNotFoundException()
|
||||
|
||||
fun Uri.fileDescriptor(mode: String) = cr.openFileDescriptor(this, mode) ?: throw FileNotFoundException()
|
||||
|
||||
val Uri.displayName: String get() {
|
||||
if (scheme == "file") {
|
||||
// Simple uri wrapper over file, directly get file name
|
||||
|
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
}
|
||||
}
|
@@ -73,17 +73,17 @@ class ShellInit : Shell.Initializer() {
|
||||
fun getVar(name: String) = fastCmd("echo \$$name")
|
||||
fun getBool(name: String) = getVar(name).toBoolean()
|
||||
|
||||
Info.isSAR = getBool("SYSTEM_ROOT")
|
||||
Info.isSAR = getBool("SYSTEM_AS_ROOT")
|
||||
Info.ramdisk = getBool("RAMDISKEXIST")
|
||||
Info.vbmeta = getBool("VBMETAEXIST")
|
||||
Info.isAB = getBool("ISAB")
|
||||
Info.crypto = getVar("CRYPTOTYPE")
|
||||
Info.patchBootVbmeta = getBool("PATCHVBMETAFLAG")
|
||||
Info.legacySAR = getBool("LEGACYSAR")
|
||||
|
||||
// Default presets
|
||||
Config.recovery = getBool("RECOVERYMODE")
|
||||
Config.keepVerity = getBool("KEEPVERITY")
|
||||
Config.keepEnc = getBool("KEEPFORCEENCRYPT")
|
||||
Config.patchVbmeta = getBool("PATCHVBMETAFLAG")
|
||||
|
||||
return true
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.events
|
||||
import com.topjohnwu.magisk.arch.ActivityExecutor
|
||||
import com.topjohnwu.magisk.arch.UIActivity
|
||||
import com.topjohnwu.magisk.arch.ViewEvent
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
|
||||
class BiometricEvent(
|
||||
builder: Builder.() -> Unit
|
||||
@@ -17,7 +17,7 @@ class BiometricEvent(
|
||||
}
|
||||
|
||||
override fun invoke(activity: UIActivity<*>) {
|
||||
BiometricHelper.authenticate(
|
||||
ServiceLocator.biometrics.authenticate(
|
||||
activity,
|
||||
onError = listenerOnFailure,
|
||||
onSuccess = listenerOnSuccess
|
||||
|
@@ -1,115 +0,0 @@
|
||||
package com.topjohnwu.magisk.signing;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.ECPrivateKeySpec;
|
||||
import java.security.spec.ECPublicKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CryptoUtils {
|
||||
|
||||
static final Map<String, String> ID_TO_ALG;
|
||||
static final Map<String, String> ALG_TO_ID;
|
||||
|
||||
static {
|
||||
ID_TO_ALG = new HashMap<>();
|
||||
ALG_TO_ID = new HashMap<>();
|
||||
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA256.getId(), "SHA256withECDSA");
|
||||
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA384.getId(), "SHA384withECDSA");
|
||||
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA512.getId(), "SHA512withECDSA");
|
||||
ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
|
||||
ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
|
||||
ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
|
||||
ALG_TO_ID.put("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256.getId());
|
||||
ALG_TO_ID.put("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384.getId());
|
||||
ALG_TO_ID.put("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512.getId());
|
||||
ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
|
||||
ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
|
||||
ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
|
||||
}
|
||||
|
||||
static String getSignatureAlgorithm(Key key) throws Exception {
|
||||
if ("EC".equals(key.getAlgorithm())) {
|
||||
int curveSize;
|
||||
KeyFactory factory = KeyFactory.getInstance("EC");
|
||||
if (key instanceof PublicKey) {
|
||||
ECPublicKeySpec spec = factory.getKeySpec(key, ECPublicKeySpec.class);
|
||||
curveSize = spec.getParams().getCurve().getField().getFieldSize();
|
||||
} else if (key instanceof PrivateKey) {
|
||||
ECPrivateKeySpec spec = factory.getKeySpec(key, ECPrivateKeySpec.class);
|
||||
curveSize = spec.getParams().getCurve().getField().getFieldSize();
|
||||
} else {
|
||||
throw new InvalidKeySpecException();
|
||||
}
|
||||
if (curveSize <= 256) {
|
||||
return "SHA256withECDSA";
|
||||
} else if (curveSize <= 384) {
|
||||
return "SHA384withECDSA";
|
||||
} else {
|
||||
return "SHA512withECDSA";
|
||||
}
|
||||
} else if ("RSA".equals(key.getAlgorithm())) {
|
||||
return "SHA256withRSA";
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
|
||||
}
|
||||
}
|
||||
|
||||
static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception {
|
||||
String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
|
||||
}
|
||||
return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id));
|
||||
}
|
||||
|
||||
public static X509Certificate readCertificate(InputStream input)
|
||||
throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
return (X509Certificate) cf.generateCertificate(input);
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Read a PKCS#8 format private key. */
|
||||
public static PrivateKey readPrivateKey(InputStream input)
|
||||
throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
ByteArrayStream buf = new ByteArrayStream();
|
||||
buf.readFrom(input);
|
||||
byte[] bytes = buf.toByteArray();
|
||||
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
|
||||
/*
|
||||
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
|
||||
* OID and use that to construct a KeyFactory.
|
||||
*/
|
||||
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
|
||||
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
|
||||
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
|
||||
return KeyFactory.getInstance(algOid).generatePrivate(spec);
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,382 +0,0 @@
|
||||
package com.topjohnwu.magisk.signing;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1Object;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.DEROctetString;
|
||||
import org.bouncycastle.asn1.DERPrintableString;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SignBoot {
|
||||
|
||||
private static final int BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET = 1632;
|
||||
private static final int BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET = 1648;
|
||||
|
||||
// Arbitrary maximum header version value; when greater assume the field is dt/extra size
|
||||
private static final int BOOT_IMAGE_HEADER_VERSION_MAXIMUM = 8;
|
||||
|
||||
// Maximum header size byte value to read (currently the bootimg minimum page size)
|
||||
private static final int BOOT_IMAGE_HEADER_SIZE_MAXIMUM = 2048;
|
||||
|
||||
private static class PushBackRWStream extends FilterInputStream {
|
||||
private OutputStream out;
|
||||
private int pos = 0;
|
||||
private byte[] backBuf;
|
||||
|
||||
PushBackRWStream(InputStream in, OutputStream o) {
|
||||
super(in);
|
||||
out = o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b;
|
||||
if (backBuf != null && backBuf.length > pos) {
|
||||
b = backBuf[pos++];
|
||||
} else {
|
||||
b = super.read();
|
||||
out.write(b);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] bytes, int off, int len) throws IOException {
|
||||
int read = 0;
|
||||
if (backBuf != null && backBuf.length > pos) {
|
||||
read = Math.min(len, backBuf.length - pos);
|
||||
System.arraycopy(backBuf, pos, bytes, off, read);
|
||||
pos += read;
|
||||
off += read;
|
||||
len -= read;
|
||||
}
|
||||
if (len > 0) {
|
||||
int ar = super.read(bytes, off, len);
|
||||
read += ar;
|
||||
out.write(bytes, off, ar);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
void unread(byte[] buf) {
|
||||
backBuf = buf;
|
||||
}
|
||||
}
|
||||
|
||||
private static int fullRead(InputStream in, byte[] b) throws IOException {
|
||||
return fullRead(in, b, 0, b.length);
|
||||
}
|
||||
|
||||
private static int fullRead(InputStream in, byte[] b, int off, int len) throws IOException {
|
||||
int n = 0;
|
||||
while (n < len) {
|
||||
int count = in.read(b, off + n, len - n);
|
||||
if (count <= 0)
|
||||
break;
|
||||
n += count;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public static boolean doSignature(
|
||||
@Nullable X509Certificate cert, @Nullable PrivateKey key,
|
||||
@NonNull InputStream imgIn, @NonNull OutputStream imgOut, @NonNull String target
|
||||
) {
|
||||
try {
|
||||
PushBackRWStream in = new PushBackRWStream(imgIn, imgOut);
|
||||
byte[] hdr = new byte[BOOT_IMAGE_HEADER_SIZE_MAXIMUM];
|
||||
// First read the header
|
||||
fullRead(in, hdr);
|
||||
int signableSize = getSignableImageSize(hdr);
|
||||
// Unread header
|
||||
in.unread(hdr);
|
||||
BootSignature bootsig = new BootSignature(target, signableSize);
|
||||
if (cert == null) {
|
||||
cert = CryptoUtils.readCertificate(
|
||||
new ByteArrayInputStream(KeyData.verityCert()));
|
||||
}
|
||||
bootsig.setCertificate(cert);
|
||||
if (key == null) {
|
||||
key = CryptoUtils.readPrivateKey(
|
||||
new ByteArrayInputStream(KeyData.verityKey()));
|
||||
}
|
||||
byte[] sig = bootsig.sign(key, in, signableSize);
|
||||
bootsig.setSignature(sig, CryptoUtils.getSignatureAlgorithmIdentifier(key));
|
||||
byte[] encoded_bootsig = bootsig.getEncoded();
|
||||
imgOut.write(encoded_bootsig);
|
||||
imgOut.flush();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verifySignature(InputStream imgIn, X509Certificate cert) {
|
||||
try {
|
||||
// Read the header for size
|
||||
byte[] hdr = new byte[BOOT_IMAGE_HEADER_SIZE_MAXIMUM];
|
||||
if (fullRead(imgIn, hdr) != hdr.length) {
|
||||
System.err.println("Unable to read image header");
|
||||
return false;
|
||||
}
|
||||
int signableSize = getSignableImageSize(hdr);
|
||||
|
||||
// Read the rest of the image
|
||||
byte[] rawImg = Arrays.copyOf(hdr, signableSize);
|
||||
int remain = signableSize - hdr.length;
|
||||
if (fullRead(imgIn, rawImg, hdr.length, remain) != remain) {
|
||||
System.err.println("Unable to read image");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read footer, which contains the signature
|
||||
byte[] signature = new byte[4096];
|
||||
if (imgIn.read(signature) == -1 || Arrays.equals(signature, new byte [signature.length])) {
|
||||
System.err.println("Invalid image: not signed");
|
||||
return false;
|
||||
}
|
||||
|
||||
BootSignature bootsig = new BootSignature(signature);
|
||||
if (cert != null) {
|
||||
bootsig.setCertificate(cert);
|
||||
}
|
||||
if (bootsig.verify(rawImg, signableSize)) {
|
||||
System.err.println("Signature is VALID");
|
||||
return true;
|
||||
} else {
|
||||
System.err.println("Signature is INVALID");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getSignableImageSize(byte[] data) throws Exception {
|
||||
if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
|
||||
"ANDROID!".getBytes("US-ASCII"))) {
|
||||
throw new IllegalArgumentException("Invalid image header: missing magic");
|
||||
}
|
||||
ByteBuffer image = ByteBuffer.wrap(data);
|
||||
image.order(ByteOrder.LITTLE_ENDIAN);
|
||||
image.getLong(); // magic
|
||||
int kernelSize = image.getInt();
|
||||
image.getInt(); // kernel_addr
|
||||
int ramdskSize = image.getInt();
|
||||
image.getInt(); // ramdisk_addr
|
||||
int secondSize = image.getInt();
|
||||
image.getLong(); // second_addr + tags_addr
|
||||
int pageSize = image.getInt();
|
||||
if (pageSize >= 0x02000000) {
|
||||
throw new IllegalArgumentException("Invalid image header: PXA header detected");
|
||||
}
|
||||
int length = pageSize // include the page aligned image header
|
||||
+ ((kernelSize + pageSize - 1) / pageSize) * pageSize
|
||||
+ ((ramdskSize + pageSize - 1) / pageSize) * pageSize
|
||||
+ ((secondSize + pageSize - 1) / pageSize) * pageSize;
|
||||
int headerVersion = image.getInt(); // boot image header version or dt/extra size
|
||||
if (headerVersion > 0 && headerVersion < BOOT_IMAGE_HEADER_VERSION_MAXIMUM) {
|
||||
image.position(BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET);
|
||||
int recoveryDtboLength = image.getInt();
|
||||
length += ((recoveryDtboLength + pageSize - 1) / pageSize) * pageSize;
|
||||
image.getLong(); // recovery_dtbo address
|
||||
int headerSize = image.getInt();
|
||||
if (headerVersion == 2) {
|
||||
image.position(BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET);
|
||||
int dtbLength = image.getInt();
|
||||
length += ((dtbLength + pageSize - 1) / pageSize) * pageSize;
|
||||
image.getLong(); // dtb address
|
||||
}
|
||||
if (image.position() != headerSize) {
|
||||
throw new IllegalArgumentException("Invalid image header: invalid header length");
|
||||
}
|
||||
} else {
|
||||
// headerVersion is 0 or actually dt/extra size in this case
|
||||
length += ((headerVersion + pageSize - 1) / pageSize) * pageSize;
|
||||
}
|
||||
length = ((length + pageSize - 1) / pageSize) * pageSize;
|
||||
if (length <= 0) {
|
||||
throw new IllegalArgumentException("Invalid image header: invalid length");
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static class BootSignature extends ASN1Object {
|
||||
private ASN1Integer formatVersion;
|
||||
private ASN1Encodable certificate;
|
||||
private AlgorithmIdentifier algId;
|
||||
private DERPrintableString target;
|
||||
private ASN1Integer length;
|
||||
private DEROctetString signature;
|
||||
private PublicKey publicKey;
|
||||
private static final int FORMAT_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Initializes the object for signing an image file
|
||||
* @param target Target name, included in the signed data
|
||||
* @param length Length of the image, included in the signed data
|
||||
*/
|
||||
public BootSignature(String target, int length) {
|
||||
this.formatVersion = new ASN1Integer(FORMAT_VERSION);
|
||||
this.target = new DERPrintableString(target);
|
||||
this.length = new ASN1Integer(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the object for verifying a signed image file
|
||||
* @param signature Signature footer
|
||||
*/
|
||||
public BootSignature(byte[] signature) throws Exception {
|
||||
ASN1InputStream stream = new ASN1InputStream(signature);
|
||||
ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
|
||||
formatVersion = (ASN1Integer) sequence.getObjectAt(0);
|
||||
if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
|
||||
throw new IllegalArgumentException("Unsupported format version");
|
||||
}
|
||||
certificate = sequence.getObjectAt(1);
|
||||
byte[] encoded = ((ASN1Object) certificate).getEncoded();
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
|
||||
publicKey = c.getPublicKey();
|
||||
ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
|
||||
this.algId = new AlgorithmIdentifier((ASN1ObjectIdentifier) algId.getObjectAt(0));
|
||||
ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
|
||||
target = (DERPrintableString) attrs.getObjectAt(0);
|
||||
length = (ASN1Integer) attrs.getObjectAt(1);
|
||||
this.signature = (DEROctetString) sequence.getObjectAt(4);
|
||||
}
|
||||
|
||||
public ASN1Object getAuthenticatedAttributes() {
|
||||
ASN1EncodableVector attrs = new ASN1EncodableVector();
|
||||
attrs.add(target);
|
||||
attrs.add(length);
|
||||
return new DERSequence(attrs);
|
||||
}
|
||||
|
||||
public byte[] getEncodedAuthenticatedAttributes() throws IOException {
|
||||
return getAuthenticatedAttributes().getEncoded();
|
||||
}
|
||||
|
||||
public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
|
||||
this.algId = algId;
|
||||
signature = new DEROctetString(sig);
|
||||
}
|
||||
|
||||
public void setCertificate(X509Certificate cert)
|
||||
throws CertificateEncodingException, IOException {
|
||||
ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
|
||||
certificate = s.readObject();
|
||||
publicKey = cert.getPublicKey();
|
||||
}
|
||||
|
||||
public byte[] sign(PrivateKey key, InputStream is, int len) throws Exception {
|
||||
Signature signer = Signature.getInstance(CryptoUtils.getSignatureAlgorithm(key));
|
||||
signer.initSign(key);
|
||||
int read;
|
||||
byte buffer[] = new byte[4096];
|
||||
while ((read = is.read(buffer, 0, Math.min(len, buffer.length))) > 0) {
|
||||
signer.update(buffer, 0, read);
|
||||
len -= read;
|
||||
}
|
||||
signer.update(getEncodedAuthenticatedAttributes());
|
||||
return signer.sign();
|
||||
}
|
||||
|
||||
public boolean verify(byte[] image, int length) throws Exception {
|
||||
if (this.length.getValue().intValue() != length) {
|
||||
throw new IllegalArgumentException("Invalid image length");
|
||||
}
|
||||
String algName = CryptoUtils.ID_TO_ALG.get(algId.getAlgorithm().getId());
|
||||
if (algName == null) {
|
||||
throw new IllegalArgumentException("Unsupported algorithm " + algId.getAlgorithm());
|
||||
}
|
||||
Signature verifier = Signature.getInstance(algName);
|
||||
verifier.initVerify(publicKey);
|
||||
verifier.update(image, 0, length);
|
||||
verifier.update(getEncodedAuthenticatedAttributes());
|
||||
return verifier.verify(signature.getOctets());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASN1Primitive toASN1Primitive() {
|
||||
ASN1EncodableVector v = new ASN1EncodableVector();
|
||||
v.add(formatVersion);
|
||||
v.add(certificate);
|
||||
v.add(algId);
|
||||
v.add(getAuthenticatedAttributes());
|
||||
v.add(signature);
|
||||
return new DERSequence(v);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0 && "-verify".equals(args[0])) {
|
||||
X509Certificate cert = null;
|
||||
if (args.length >= 2) {
|
||||
// args[1] is the path to a public key certificate
|
||||
cert = CryptoUtils.readCertificate(new FileInputStream(args[1]));
|
||||
}
|
||||
boolean signed = SignBoot.verifySignature(System.in, cert);
|
||||
System.exit(signed ? 0 : 1);
|
||||
} else if (args.length > 0 && "-sign".equals(args[0])) {
|
||||
X509Certificate cert = null;
|
||||
PrivateKey key = null;
|
||||
String name = "/boot";
|
||||
|
||||
if (args.length >= 3) {
|
||||
cert = CryptoUtils.readCertificate(new FileInputStream(args[1]));
|
||||
key = CryptoUtils.readPrivateKey(new FileInputStream(args[2]));
|
||||
}
|
||||
if (args.length == 2) {
|
||||
name = args[1];
|
||||
} else if (args.length >= 4) {
|
||||
name = args[3];
|
||||
}
|
||||
|
||||
boolean result = SignBoot.doSignature(cert, key, System.in, System.out, name);
|
||||
System.exit(result ? 0 : 1);
|
||||
} else {
|
||||
System.err.println(
|
||||
"BootSigner <actions> [args]\n" +
|
||||
"Input from stdin, output to stdout\n" +
|
||||
"\n" +
|
||||
"Actions:\n" +
|
||||
" -verify [x509.pem]\n" +
|
||||
" verify image. cert is optional.\n" +
|
||||
" -sign [x509.pem] [pk8] [name]\n" +
|
||||
" sign image. name and the cert/key pair are optional.\n" +
|
||||
" name should be either /boot (default) or /recovery.\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -5,7 +5,7 @@ import androidx.databinding.Bindable
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
@@ -34,7 +34,7 @@ class FlashViewModel : BaseViewModel() {
|
||||
|
||||
private val _state = MutableLiveData(State.FLASHING)
|
||||
val state: LiveData<State> get() = _state
|
||||
val flashing = Transformations.map(state) { it == State.FLASHING }
|
||||
val flashing = state.map { it == State.FLASHING }
|
||||
|
||||
@get:Bindable
|
||||
var showReboot = Info.isRooted
|
||||
|
@@ -24,6 +24,10 @@ private interface RikkaImpl : Dev {
|
||||
override val name get() = "RikkaW"
|
||||
}
|
||||
|
||||
private interface CanyieImpl : Dev {
|
||||
override val name get() = "canyie"
|
||||
}
|
||||
|
||||
sealed class DeveloperItem : Dev {
|
||||
|
||||
abstract val items: List<IconLink>
|
||||
@@ -61,6 +65,14 @@ sealed class DeveloperItem : Dev {
|
||||
object : IconLink.Github.User(), RikkaImpl {}
|
||||
)
|
||||
}
|
||||
|
||||
object Canyie : DeveloperItem(), CanyieImpl {
|
||||
override val items =
|
||||
listOf<IconLink>(
|
||||
object : IconLink.Twitter() { override val name = "canyie2977" },
|
||||
object : IconLink.Github.User(), CanyieImpl {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class IconLink : RvItem() {
|
||||
|
@@ -37,8 +37,7 @@ import java.io.IOException
|
||||
class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel() {
|
||||
|
||||
val isRooted get() = Info.isRooted
|
||||
val hideVbmeta = Info.vbmeta || Info.isSamsung || Info.isAB
|
||||
val skipOptions = Info.isEmulator || (Info.isSAR && !Info.isFDE && hideVbmeta && Info.ramdisk)
|
||||
val skipOptions = Info.isEmulator || (Info.isSAR && !Info.isFDE && Info.ramdisk)
|
||||
val noSecondSlot = !isRooted || !Info.isAB || Info.isEmulator
|
||||
|
||||
@get:Bindable
|
||||
@@ -105,7 +104,6 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
||||
step,
|
||||
Config.keepVerity,
|
||||
Config.keepEnc,
|
||||
Config.patchVbmeta,
|
||||
Config.recovery
|
||||
))
|
||||
}
|
||||
@@ -116,7 +114,6 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
||||
step = it.step
|
||||
Config.keepVerity = it.keepVerity
|
||||
Config.keepEnc = it.keepEnc
|
||||
Config.patchVbmeta = it.patchVbmeta
|
||||
Config.recovery = it.recovery
|
||||
}
|
||||
}
|
||||
@@ -137,7 +134,6 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
||||
val step: Int,
|
||||
val keepVerity: Boolean,
|
||||
val keepEnc: Boolean,
|
||||
val patchVbmeta: Boolean,
|
||||
val recovery: Boolean,
|
||||
) : Parcelable
|
||||
|
||||
|
@@ -3,6 +3,7 @@ package com.topjohnwu.magisk.ui.log
|
||||
import androidx.databinding.Bindable
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.ktx.timeDateFormat
|
||||
import com.topjohnwu.magisk.core.ktx.toTime
|
||||
import com.topjohnwu.magisk.core.model.su.SuLog
|
||||
@@ -14,7 +15,7 @@ class SuLogRvItem(val log: SuLog) : ObservableRvItem(), DiffItem<SuLogRvItem> {
|
||||
|
||||
override val layoutRes = R.layout.item_log_access_md2
|
||||
|
||||
val date = log.time.toTime(timeDateFormat)
|
||||
val info = genInfo()
|
||||
|
||||
@get:Bindable
|
||||
var isTop = false
|
||||
@@ -25,4 +26,28 @@ class SuLogRvItem(val log: SuLog) : ObservableRvItem(), DiffItem<SuLogRvItem> {
|
||||
set(value) = set(value, field, { field = it }, BR.bottom)
|
||||
|
||||
override fun itemSameAs(other: SuLogRvItem) = log.appName == other.log.appName
|
||||
|
||||
private fun genInfo(): String {
|
||||
val res = AppContext.resources
|
||||
val sb = StringBuilder()
|
||||
val date = log.time.toTime(timeDateFormat)
|
||||
val toUid = res.getString(R.string.target_uid, log.toUid)
|
||||
val fromPid = res.getString(R.string.pid, log.fromPid)
|
||||
sb.append("$date\n$toUid $fromPid")
|
||||
if (log.target != -1) {
|
||||
val pid = if (log.target == 0) "magiskd" else log.target.toString()
|
||||
val target = res.getString(R.string.target_pid, pid)
|
||||
sb.append(" $target")
|
||||
}
|
||||
if (log.context.isNotEmpty()) {
|
||||
val context = res.getString(R.string.selinux_context, log.context)
|
||||
sb.append("\n$context")
|
||||
}
|
||||
if (log.gids.isNotEmpty()) {
|
||||
val gids = res.getString(R.string.supp_group, log.gids)
|
||||
sb.append("\n$gids")
|
||||
}
|
||||
sb.append("\n${log.command}")
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
|
@@ -12,9 +12,9 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.ktx.activity
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.availableLocales
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
@@ -286,14 +286,16 @@ object Tapjack : BaseSettingsItem.Toggle() {
|
||||
object Biometrics : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.settings_su_biometric_title.asText()
|
||||
override var description = R.string.settings_su_biometric_summary.asText()
|
||||
override var value by Config::suBiometric
|
||||
override var value
|
||||
get() = ServiceLocator.biometrics.isEnabled
|
||||
set(value) {
|
||||
Config.suBiometric = value
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
isEnabled = BiometricHelper.isSupported
|
||||
isEnabled = ServiceLocator.biometrics.isSupported
|
||||
if (!isEnabled) {
|
||||
value = false
|
||||
description = R.string.no_biometric.asText()
|
||||
notifyPropertyChanged(BR.checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -73,6 +73,8 @@ class PolicyRvItem(
|
||||
viewModel.deletePressed(this)
|
||||
}
|
||||
|
||||
override fun itemSameAs(other: PolicyRvItem) = item.uid == other.item.uid
|
||||
override fun itemSameAs(other: PolicyRvItem) = packageName == other.packageName
|
||||
|
||||
override fun contentSameAs(other: PolicyRvItem) = item.policy == other.item.policy
|
||||
|
||||
}
|
||||
|
@@ -13,9 +13,9 @@ import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.ktx.getLabel
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.databinding.*
|
||||
import com.topjohnwu.magisk.dialog.SuperuserRevokeDialog
|
||||
@@ -106,14 +106,14 @@ class SuperuserViewModel(
|
||||
fun updateState() = viewModelScope.launch {
|
||||
db.delete(item.item.uid)
|
||||
val list = ArrayList(itemsPolicies)
|
||||
list.removeAll { it.itemSameAs(item) }
|
||||
list.removeAll { it.item.uid == item.item.uid }
|
||||
itemsPolicies.update(list)
|
||||
if (list.isEmpty() && itemsHelpers.isEmpty()) {
|
||||
itemsHelpers.add(itemNoData)
|
||||
}
|
||||
}
|
||||
|
||||
if (BiometricHelper.isEnabled) {
|
||||
if (ServiceLocator.biometrics.isEnabled) {
|
||||
BiometricEvent {
|
||||
onSuccess { updateState() }
|
||||
}.publish()
|
||||
@@ -155,21 +155,20 @@ class SuperuserViewModel(
|
||||
}
|
||||
|
||||
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
||||
val items = itemsPolicies.filter { it.item.uid == item.item.uid }
|
||||
fun updateState() {
|
||||
viewModelScope.launch {
|
||||
val res = if (enable) R.string.su_snack_grant else R.string.su_snack_deny
|
||||
item.item.policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
|
||||
db.update(item.item)
|
||||
itemsPolicies.forEach {
|
||||
if (it.item.uid == item.item.uid) {
|
||||
it.notifyPropertyChanged(BR.enabled)
|
||||
}
|
||||
items.forEach {
|
||||
it.notifyPropertyChanged(BR.enabled)
|
||||
}
|
||||
SnackbarEvent(res.asText(item.appName)).publish()
|
||||
}
|
||||
}
|
||||
|
||||
if (BiometricHelper.isEnabled) {
|
||||
if (ServiceLocator.biometrics.isEnabled) {
|
||||
BiometricEvent {
|
||||
onSuccess { updateState() }
|
||||
}.publish()
|
||||
|
@@ -22,17 +22,18 @@ import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.ktx.getLabel
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY
|
||||
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
import com.topjohnwu.magisk.events.BiometricEvent
|
||||
import com.topjohnwu.magisk.events.DieEvent
|
||||
import com.topjohnwu.magisk.events.ShowUIEvent
|
||||
import com.topjohnwu.magisk.utils.TextHolder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
|
||||
@@ -70,12 +71,13 @@ class SuRequestViewModel(
|
||||
}
|
||||
|
||||
private val handler = SuRequestHandler(AppContext.packageManager, policyDB)
|
||||
private lateinit var timer: CountDownTimer
|
||||
private val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||
private var timer = SuTimer(millis, 1000)
|
||||
private var initialized = false
|
||||
|
||||
fun grantPressed() {
|
||||
cancelTimer()
|
||||
if (BiometricHelper.isEnabled) {
|
||||
if (ServiceLocator.biometrics.isEnabled) {
|
||||
BiometricEvent {
|
||||
onSuccess {
|
||||
respond(ALLOW)
|
||||
@@ -96,7 +98,7 @@ class SuRequestViewModel(
|
||||
}
|
||||
|
||||
fun handleRequest(intent: Intent) {
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
if (handler.start(intent))
|
||||
showDialog()
|
||||
else
|
||||
@@ -125,8 +127,7 @@ class SuRequestViewModel(
|
||||
selectedItemPosition = timeoutPrefs.getInt(packageName, 0)
|
||||
|
||||
// Set timer
|
||||
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||
timer = SuTimer(millis, 1000).apply { start() }
|
||||
timer.start()
|
||||
|
||||
// Actually show the UI
|
||||
ShowUIEvent(if (Config.suTapjack) EmptyAccessibilityDelegate else null).publish()
|
||||
|
@@ -31,7 +31,7 @@ object Shortcuts {
|
||||
val info = ShortcutInfoCompat.Builder(context, Const.Nav.HOME)
|
||||
.setShortLabel(context.getString(R.string.magisk))
|
||||
.setIntent(intent)
|
||||
.setIcon(IconCompat.createFromIcon(context, context.getIcon(R.drawable.ic_launcher)))
|
||||
.setIcon(context.getIconCompat(R.drawable.ic_launcher))
|
||||
.build()
|
||||
ShortcutManagerCompat.requestPinShortcut(context, info, null)
|
||||
}
|
||||
@@ -47,6 +47,18 @@ object Shortcuts {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.getIconCompat(id: Int): IconCompat {
|
||||
return if (isRunningAsStub) {
|
||||
val bitmap = getBitmap(id)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
IconCompat.createWithAdaptiveBitmap(bitmap)
|
||||
else
|
||||
IconCompat.createWithBitmap(bitmap)
|
||||
} else {
|
||||
IconCompat.createWithResource(this, id)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = 25)
|
||||
private fun getShortCuts(context: Context): List<ShortcutInfo> {
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
||||
|
@@ -242,6 +242,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/l_50" />
|
||||
|
||||
<include
|
||||
item="@{DeveloperItem.Canyie.INSTANCE}"
|
||||
layout="@layout/item_developer"
|
||||
viewModel="@{viewModel}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/l_50" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
@@ -108,15 +108,6 @@
|
||||
android:text="@string/keep_force_encryption"
|
||||
app:tint="?colorPrimary" />
|
||||
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
gone="@{viewModel.hideVbmeta}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={Config.patchVbmeta}"
|
||||
android:text="@string/patch_vbmeta"
|
||||
app:tint="?colorPrimary" />
|
||||
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
gone="@{Info.ramdisk}"
|
||||
|
@@ -29,8 +29,13 @@
|
||||
tools:listitem="@layout/item_log_access_md2"
|
||||
tools:paddingTop="24dp" />
|
||||
|
||||
<ProgressBar
|
||||
style="@style/WidgetFoundation.ProgressBar.Indeterminate"
|
||||
goneUnless="@{viewModel.loading}"
|
||||
android:layout_marginTop="@dimen/l1" />
|
||||
|
||||
<FrameLayout
|
||||
gone="@{!viewModel.items.empty}"
|
||||
gone="@{viewModel.loading || !viewModel.items.empty}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
@@ -25,9 +25,9 @@
|
||||
|
||||
<include
|
||||
android:id="@+id/log_track_container"
|
||||
bullet="@{item.log.action ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}"
|
||||
bullet="@{item.log.action == 2 ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}"
|
||||
isBottom="@{item.isBottom}"
|
||||
isSelected="@{!item.log.action}"
|
||||
isSelected="@{item.log.action != 2}"
|
||||
isTop="@{item.isTop}"
|
||||
layout="@layout/item_log_track_md2"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -54,48 +54,22 @@
|
||||
android:text="@{item.log.appName}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Body"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/log_date"
|
||||
app:layout_constraintBottom_toTopOf="@+id/log_info"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/magisk" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/log_date"
|
||||
android:id="@+id/log_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.date}"
|
||||
android:text="@{item.info}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
|
||||
app:layout_constraintBottom_toTopOf="@+id/log_app_details"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/log_app_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="06:00 PM, 10 Oct 2019" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/log_app_details"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{String.format(`%s %s`, @string/pid(item.log.fromPid), @string/target_uid(item.log.toUid))}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
|
||||
app:layout_constraintBottom_toTopOf="@id/log_command"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/log_date"
|
||||
tools:text="PID: 7196 Target UID: 0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/log_command"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:text="@{item.log.command}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/log_app_details"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:text="/system/bin/sh" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
@@ -24,11 +24,15 @@
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/module"
|
||||
style="@style/WidgetFoundation.Card"
|
||||
isEnabled="@{!item.removed && item.enabled && !item.showNotice}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="@{!item.removed && item.enabled && !item.showNotice}"
|
||||
android:focusable="@{!item.removed && item.enabled && !item.showNotice}"
|
||||
android:nextFocusRight="@id/module_indicator"
|
||||
android:onClick="@{() -> item.setEnabled(!item.enabled)}"
|
||||
app:cardBackgroundColor="@color/color_card_background_color_selector"
|
||||
tools:isEnabled="false"
|
||||
tools:layout_gravity="center"
|
||||
@@ -95,6 +99,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/l_50"
|
||||
android:checked="@={item.enabled}"
|
||||
android:nextFocusLeft="@id/module"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/module_version_author"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
|
@@ -17,10 +17,13 @@
|
||||
android:layout_gravity="center">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/policy"
|
||||
style="@style/WidgetFoundation.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="@{item.enabled ? 1f : .5f}"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@id/policy_indicator"
|
||||
android:onClick="@{() -> item.toggleExpand()}">
|
||||
|
||||
<LinearLayout
|
||||
@@ -35,10 +38,10 @@
|
||||
<ImageView
|
||||
android:id="@+id/policy_app_icon"
|
||||
style="@style/WidgetFoundation.Image"
|
||||
srcCompat="@{item.icon}"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l1"
|
||||
srcCompat="@{item.icon}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
@@ -86,8 +89,9 @@
|
||||
android:id="@+id/policy_indicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={item.enabled}"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:checked="@={item.enabled}"
|
||||
android:nextFocusLeft="@id/policy"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
@@ -96,20 +100,20 @@
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/policy_expand_container"
|
||||
android:orientation="horizontal"
|
||||
gone="@{!item.isExpanded}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorSurfaceVariant"
|
||||
android:orientation="horizontal"
|
||||
tools:visibility="visible">
|
||||
|
||||
<Button
|
||||
android:id="@+id/policy_notify"
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
isSelected="@{item.shouldNotify}"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:minHeight="24dp"
|
||||
android:onClick="@{() -> item.toggleNotify()}"
|
||||
android:text="@string/superuser_toggle_notification"
|
||||
@@ -131,9 +135,9 @@
|
||||
android:id="@+id/policy_log"
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
isSelected="@{item.shouldLog}"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:minHeight="24dp"
|
||||
android:onClick="@{() -> item.toggleLog()}"
|
||||
android:text="@string/logs"
|
||||
@@ -154,9 +158,9 @@
|
||||
<Button
|
||||
android:id="@+id/policy_delete"
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:minHeight="24dp"
|
||||
android:onClick="@{() -> item.revoke()}"
|
||||
android:text="@string/superuser_toggle_revoke"
|
||||
|
@@ -154,7 +154,7 @@ check_boot_ramdisk() {
|
||||
$ISAB && return 0
|
||||
|
||||
# If we are using legacy SAR, but not A/B, assume we do not have ramdisk
|
||||
if grep ' / ' /proc/mounts | grep -q '/dev/root'; then
|
||||
if $LEGACYSAR; then
|
||||
# Override recovery mode to true
|
||||
RECOVERYMODE=true
|
||||
return 1
|
||||
@@ -193,25 +193,32 @@ check_encryption() {
|
||||
mount_partitions() {
|
||||
[ "$(getprop ro.build.ab_update)" = "true" ] && SLOT=$(getprop ro.boot.slot_suffix)
|
||||
# Check whether non rootfs root dir exists
|
||||
SYSTEM_ROOT=false
|
||||
grep ' / ' /proc/mounts | grep -qv 'rootfs' && SYSTEM_ROOT=true
|
||||
SYSTEM_AS_ROOT=false
|
||||
grep ' / ' /proc/mounts | grep -qv 'rootfs' && SYSTEM_AS_ROOT=true
|
||||
}
|
||||
|
||||
get_flags() {
|
||||
KEEPVERITY=$SYSTEM_ROOT
|
||||
KEEPVERITY=$SYSTEM_AS_ROOT
|
||||
ISENCRYPTED=false
|
||||
[ "$(getprop ro.crypto.state)" = "encrypted" ] && ISENCRYPTED=true
|
||||
KEEPFORCEENCRYPT=$ISENCRYPTED
|
||||
# Although this most certainly won't work without root, keep it just in case
|
||||
if [ -e /dev/block/by-name/vbmeta_a ] || [ -e /dev/block/by-name/vbmeta ]; then
|
||||
VBMETAEXIST=true
|
||||
if [ -n "$(getprop ro.boot.vbmeta.device)" -o -n "$(getprop ro.boot.vbmeta.size)" ]; then
|
||||
PATCHVBMETAFLAG=false
|
||||
elif getprop ro.product.ab_ota_partitions | grep -wq vbmeta; then
|
||||
PATCHVBMETAFLAG=false
|
||||
else
|
||||
VBMETAEXIST=false
|
||||
PATCHVBMETAFLAG=true
|
||||
fi
|
||||
# Preset PATCHVBMETAFLAG to false in the non-root case
|
||||
PATCHVBMETAFLAG=false
|
||||
# Make sure RECOVERYMODE has value
|
||||
[ -z $RECOVERYMODE ] && RECOVERYMODE=false
|
||||
if $SYSTEM_AS_ROOT; then
|
||||
if grep ' / ' /proc/mounts | grep -q '/dev/root'; then
|
||||
LEGACYSAR=true
|
||||
else
|
||||
LEGACYSAR=false
|
||||
fi
|
||||
else
|
||||
LEGACYSAR=false
|
||||
fi
|
||||
}
|
||||
|
||||
run_migrations() { return; }
|
||||
@@ -224,9 +231,9 @@ grep_prop() { return; }
|
||||
|
||||
app_init() {
|
||||
mount_partitions
|
||||
get_flags
|
||||
RAMDISKEXIST=false
|
||||
check_boot_ramdisk && RAMDISKEXIST=true
|
||||
get_flags
|
||||
run_migrations
|
||||
SHA1=$(grep_prop SHA1 $MAGISKTMP/.magisk/config)
|
||||
check_encryption
|
||||
|
@@ -1,79 +1,154 @@
|
||||
<resources>
|
||||
|
||||
<!--Welcome Activity-->
|
||||
<!--Sections-->
|
||||
<string name="modules">Модули</string>
|
||||
<string name="superuser">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>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="invalid_update_channel">Невалиден канал за актуализации</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="keep_force_encryption">Запазване на наложеното криптиране</string>
|
||||
<string name="keep_dm_verity">Запазване на AVB 2.0/dm-verity</string>
|
||||
<string name="uninstall_magisk_title">Деинсталиране на Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Всички модули ще бъдат изключени/премахнати. Руут достъпът ще бъде премахнат и е възможно криптиране на данните Ви.</string>
|
||||
<string name="update">Актуализация</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(Не е представена информация)</string>
|
||||
<string name="reboot_recovery">Рестартиране в режима за възстановяване</string>
|
||||
<string name="reboot_bootloader">Рестартиране в буутлоудъра</string>
|
||||
<string name="reboot_download">Рестартиране в даунлоуд режима</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Налице е актуализация.</string>
|
||||
<string name="home_installed_version">Инсталирани</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Запазване на доклад</string>
|
||||
<string name="menuClearLog">Изчистване на дневника</string>
|
||||
<string name="logs_cleared">Успешно изчистване на дневника.</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<!--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>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">Инсталиране на %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Изтегляне</string>
|
||||
<string name="reboot">Рестартиране</string>
|
||||
<string name="magisk_update_title">Достъпно е издание на Magisk.</string>
|
||||
<string name="release_notes">Бележки</string>
|
||||
<string name="manager_download_install">Докоснете за изтегляне и инсталиране.</string>
|
||||
<string name="update_channel">Актуализации на Magisk</string>
|
||||
<string name="flashing">Инсталиране</string>
|
||||
<string name="open_link_failed_toast">Не е намерено приложение за отваряне на препратката.</string>
|
||||
<string name="direct_install">Директно инсталиране (Препоръчва се.)</string>
|
||||
<string name="install_inactive_slot">Инсталиране на неактивен слот (След OTA)</string>
|
||||
<string name="install_inactive_slot_msg">Вашето устройство НАЛОЖИТЕЛНО ще стартира текущия неактивен слот при следващото рестартиране.\nИзползвайте тази опция само след като приключи инсталирането на OTA.\nПродължаване?</string>
|
||||
.
|
||||
<string name="complete_uninstall">Пълно деинсталиране</string>
|
||||
<string name="restore_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="setup_title">Допълнително надстройване</string>
|
||||
<string name="setup_msg">Надстройването на средата е в ход…</string>
|
||||
<string name="download_file_error">Грешка при изтегляне на файла.</string>
|
||||
<string name="home_notice_content">Изтегляйте Magisk САМО от официалната страница в GitHub. Файловете от неизвестни източници могат да бъдат зловредни!</string>
|
||||
<string name="home_support_title">Подкрепете ни</string>
|
||||
<string name="home_follow_title">Последвайте ни</string>
|
||||
<string name="home_item_source">Изходен код</string>
|
||||
<string name="home_support_content">Magisk е, и винаги ще бъде, безплатен и с отворен изходен код. Въпреки това можете да покажете подкрапата си чрез дарение.</string>
|
||||
<string name="home_installed_version">Инсталиранo</string>
|
||||
<string name="home_latest_version">Последно</string>
|
||||
<string name="invalid_update_channel">Каналът за обновяване е недействителен</string>
|
||||
<string name="uninstall_magisk_title">Премахване на Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Всички модули ще бъдат изключени/премахнати. Достъпът до правата на суперпотребителя ще бъде премахнат!\nАко вътрешното хранилище е разшифровано с Magisk, то ще бъде шифровано отново!</string>
|
||||
|
||||
<!--Settings Activity -->
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">Запазване на наложеното шифроване</string>
|
||||
<string name="keep_dm_verity">Запазване на AVB 2.0/dm-verity</string>
|
||||
<string name="patch_vbmeta">Закърпване на vbmeta в образ на boot</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">Устройство ЗАДЪЛЖИТЕЛНО ще зареди от текущия неактивен дял при следващия рестарт.\nИзползвайте само при приключила инсталация на OTA.\nПродължаване?</string>
|
||||
<string name="setup_title">Допълнителни настройки</string>
|
||||
<string name="select_patch_file">Избор и закърпване на файл</string>
|
||||
<string name="patch_file_msg">Изберете образ (*.img) или архив на ODIN (*.tar)</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="prompt">Запитване</string>
|
||||
<string name="grant">Разрешаване</string>
|
||||
<string name="su_warning">Дава пълен достъп до устройството.\nОткажете, ако не сте сигурни.</string>
|
||||
<string name="forever">Винаги</string>
|
||||
<string name="once">Веднъж</string>
|
||||
<string name="tenmin">10 мин.</string>
|
||||
<string name="twentymin">20 мин.</string>
|
||||
<string name="thirtymin">30 мин.</string>
|
||||
<string name="sixtymin">60 мин.</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">Дневникът на Magisk е празен</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>
|
||||
|
||||
<!--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">Рестарт в bootloader</string>
|
||||
<string name="reboot_download">Рестарт в режим за изтегляне</string>
|
||||
<string name="reboot_edl">Рестарт в EDL</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_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">Скриване на приложението на Magisk</string>
|
||||
<string name="settings_hide_app_summary">Инсталира междинно приложение с произволен идестификатор и име</string>
|
||||
<string name="settings_restore_app_title">Възстановяване на приложението на Magisk</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="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_hosts_title">Несистемни хостове</string>
|
||||
<string name="settings_hosts_summary">Поддръжка на несистемни хостове за използване на приложения, блокиращи реклами.</string>
|
||||
<string name="settings_hosts_toast">Добавен е модул с несистемни хостове.</string>
|
||||
|
||||
<string name="settings_update_custom_msg">Адрес на потребителски канал</string>
|
||||
<string name="settings_zygisk_summary">Изпълняване на части от Magisk в демон на zygote</string>
|
||||
<string name="settings_denylist_title">Налагане на черен списък</string>
|
||||
<string name="settings_denylist_summary">На процесите в черния списък ще бъдат възстановени всички модификации, направени от Magisk</string>
|
||||
<string name="settings_denylist_error">Изизква %1$s да бъде включено</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>
|
||||
@@ -84,55 +159,86 @@
|
||||
<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">Superuser достъп</string>
|
||||
<string name="superuser_access">Достъп до суперпотребителя</string>
|
||||
<string name="auto_response">Автоматичен отговор</string>
|
||||
<string name="request_timeout">Време за запитване</string>
|
||||
<string name="superuser_notification">Superuser известие</string>
|
||||
<string name="settings_su_reauth_title">Повторно запитване след актуализация</string>
|
||||
<string name="settings_su_reauth_summary">Повторно запитване за Superuser достъп след актуализация на приложенията.</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_biometric_title">Биометрично удостоверяване</string>
|
||||
<string name="settings_su_biometric_summary">За получаване на достъп до суперпотребителя е необходимо биометрично удостоверяване</string>
|
||||
<string name="no_biometric">Устройството не се поддръжа или липсват биометрични настройки</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">Заобикаля „отравянето“ на DNS в някои държави</string>
|
||||
|
||||
<string name="multiuser_mode">Потребителски достъп</string>
|
||||
<string name="settings_owner_only">Само собственик</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="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="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>
|
||||
<string name="global_summary">Всички сесии с достъп до суперпотребителя използват общото пространство от имена</string>
|
||||
<string name="requester_summary">Всички сесии с достъп до суперпотребителя наследяват пространството от имена на запитващото приложение</string>
|
||||
<string name="isolate_summary">Всички сесии с достъп до суперпотребителя получават изолирани пространства от имена</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Запитване за Superuser достъп</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">10 мин</string>
|
||||
<string name="twentymin">20 мин</string>
|
||||
<string name="thirtymin">30 мин</string>
|
||||
<string name="sixtymin">60 мин</string>
|
||||
<string name="su_allow_toast">На %1$s е разрешен Superuser достъп.</string>
|
||||
<string name="su_deny_toast">На %1$s е отказан Superuser достъп.</string>
|
||||
<string name="su_snack_grant">На %1$s е предоставен Superuser достъп.</string>
|
||||
<string name="su_snack_deny">На %1$s е отказан Superuser достъп.</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">Toast</string>
|
||||
<string name="none">Без</string>
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Обновяване на Magisk</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">Има обновяване на Magisk!</string>
|
||||
<string name="updated_title">Magisk е обновен</string>
|
||||
<string name="updated_text">За да отворите приложението, докоснете</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="target_uid">Целеви UID: %1$d</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="release_notes">Бележки по изданието</string>
|
||||
<string name="flashing">Инсталиране…</string>
|
||||
<string name="done">Готово!</string>
|
||||
<string name="failure">Грешка!</string>
|
||||
<string name="hide_app_title">Скриване на приложението на Magisk…</string>
|
||||
<string name="open_link_failed_toast">Не е намерено приложение, с което препратката да бъде отворена</string>
|
||||
<string name="complete_uninstall">Премахване</string>
|
||||
<string name="restore_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">За да работи Magisk нормално, устройството се нуждае от допълнителна настройка. Да бъде ли продължено след рестарт?</string>
|
||||
<string name="env_full_fix_msg">За да работи Magisk нормално, е необходимо да бъде инсталиран отново. Преинсталирайте Magisk от приложението, защото режимът за възстановяване не получава необходимата информация за устройството.</string>
|
||||
<string name="setup_msg">Надстройка на средата…</string>
|
||||
<string name="authenticate">Удостоверяване</string>
|
||||
<string name="unsupport_magisk_title">Неподдържано издание на Magisk</string>
|
||||
<string name="unsupport_magisk_msg">Това издание на приложението не поддържа издания на Magisk преди %1$s.\n\nПриложението ще се държи все едно няма инсталиран Magisk. Обновете Magisk възможно най-скоро.</string>
|
||||
<string name="unsupport_general_title">Неочаквано състояние</string>
|
||||
<string name="unsupport_system_app_msg">Приложението не се поддържа да работи като системно. Върнете го като потребителско.</string>
|
||||
<string name="unsupport_other_su_msg">Намерен е двоичен файл „su“, който не е от Magisk. Премахнете всички други решения за достъп до правата на суперпотребителя и/или инсталирайте Magisk отново.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk е инсталиран във външно хранилище. Преместете приложението във вътрешното хранилище.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Скритото приложение на Magisk не може да продължи да работи, защото достъпът до правата на суперпотребителя е загубен. Възстановете оригиналното приложение.</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">За да използвате тази възможност, разрешете „инсталиране на неизвестни приложения“</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>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<string name="settings">Nastavení</string>
|
||||
<string name="install">Instalovat</string>
|
||||
<string name="section_home">Domů</string>
|
||||
<string name="section_theme">Téma</string>
|
||||
<string name="section_theme">Témata</string>
|
||||
<string name="denylist">DenyList</string>
|
||||
|
||||
<!--Home-->
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Požadavek SuperUser</string>
|
||||
<string name="touch_filtered_warning">Protože aplikace zavírá požadavek SuperUser, Magisk nemůže ověřit Vaši odpověď</string>
|
||||
<string name="touch_filtered_warning">Magisk nemůže ověřit Vaši odpověď, protože aplikace zavírá požadavek SuperUser</string>
|
||||
<string name="deny">Zakázat</string>
|
||||
<string name="prompt">Zeptat se</string>
|
||||
<string name="grant">Povolit</string>
|
||||
@@ -72,7 +72,7 @@
|
||||
<string name="su_snack_log_on">Protokolování %1$s je povoleno</string>
|
||||
<string name="su_snack_log_off">Protokolování %1$s je zakázáno</string>
|
||||
<string name="su_revoke_title">Smazat?</string>
|
||||
<string name="su_revoke_msg">Chcete smazat protokol k oprávnění pro %1$s?</string>
|
||||
<string name="su_revoke_msg">Potvrzením odeberete %1$s oprávnění SuperUser</string>
|
||||
<string name="toast">Text</string>
|
||||
<string name="none">Žádné</string>
|
||||
|
||||
@@ -81,13 +81,16 @@
|
||||
<string name="superuser_policy_none">Žádná aplikace nepožádala o oprávnění SuperUser.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">Žádné protokoly. Vyzkoušejte nějakou aplikaci vyžadující oprávnění SuperUser.</string>
|
||||
<string name="log_data_magisk_none">Protokoly Magisk jsou prázdné. To je zvláštní!</string>
|
||||
<string name="log_data_none">Nemáte žádné protokoly. Vyzkoušejte nějakou aplikaci vyžadující oprávnění SuperUser</string>
|
||||
<string name="log_data_magisk_none">Protokoly Magisk jsou prázdné. To je zvláštní</string>
|
||||
<string name="menuSaveLog">Uložit protokol</string>
|
||||
<string name="menuClearLog">Smazat protokol</string>
|
||||
<string name="logs_cleared">Protokol byl smazán</string>
|
||||
<string name="pid">PID:%1$d</string>
|
||||
<string name="target_uid">Cílové UID: %1$d</string>
|
||||
<string name="target_pid">Připojit PID cílového ns: %s</string>
|
||||
<string name="selinux_context">SELinux kontext: %s</string>
|
||||
<string name="supp_group">Sekundární skupina: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
@@ -109,8 +112,8 @@
|
||||
<string name="module_state_restore">Obnovit</string>
|
||||
<string name="module_action_install_external">Instalace z úložiště</string>
|
||||
<string name="update_available">Dostupná aktualizace</string>
|
||||
<string name="suspend_text_riru">Modul pozastaven, protože je povolen %1$s</string>
|
||||
<string name="suspend_text_zygisk">Modul pozastaven, protože %1$s není povoleno</string>
|
||||
<string name="suspend_text_riru">Modul byl pozastaven, protože je povolen %1$s</string>
|
||||
<string name="suspend_text_zygisk">Modul byl pozastaven, protože %1$s není povoleno</string>
|
||||
<string name="zygisk_module_unloaded">Modul Zygisk nebyl načten z důvodu nekompatibility</string>
|
||||
<string name="module_empty">Není nainstalován žádný modul</string>
|
||||
<string name="confirm_install">Instalovat modul %1$s?</string>
|
||||
@@ -125,29 +128,29 @@
|
||||
<string name="settings_download_path_title">Složka pro stahování</string>
|
||||
<string name="settings_download_path_message">Soubory budou uloženy do %1$s.</string>
|
||||
<string name="settings_hide_app_title">Skrýt aplikaci Magisk</string>
|
||||
<string name="settings_hide_app_summary">Skryjete aplikaci náhodným ID balíčku a vlastním názvem aplikace.</string>
|
||||
<string name="settings_hide_app_summary">Skryje aplikaci náhodným ID balíčku a vlastním názvem aplikace</string>
|
||||
<string name="settings_restore_app_title">Obnovit aplikaci Magisk</string>
|
||||
<string name="settings_restore_app_summary">Obnovíte aplikaci zpět do původního APK.</string>
|
||||
<string name="settings_restore_app_summary">Obnoví aplikaci zpět do původního APK</string>
|
||||
<string name="language">Jazyk</string>
|
||||
<string name="system_default">(výchozí podle systému)</string>
|
||||
<string name="settings_check_update_title">Zkontrolovat aktualizace</string>
|
||||
<string name="settings_check_update_summary">Povolíte pravidelnou kontrolu aktualizace na pozadí.</string>
|
||||
<string name="settings_check_update_summary">Povolí pravidelnou kontrolu aktualizace na pozadí</string>
|
||||
<string name="settings_update_channel_title">Kanál aktualizace</string>
|
||||
<string name="settings_update_stable">Stabilní</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Vlastní</string>
|
||||
<string name="settings_update_custom_msg">Vložte vlastní URL</string>
|
||||
<string name="settings_zygisk_summary">Spusťte části Magisku v Zygote daemon</string>
|
||||
<string name="settings_zygisk_summary">Spustí části Magisku v démonu Zygote</string>
|
||||
<string name="settings_denylist_title">Vynutit DenyList</string>
|
||||
<string name="settings_denylist_summary">Na procesy v DenyListu nebudou aplikovány žádné změny nebo modifikace</string>
|
||||
<string name="settings_denylist_summary">Na procesy v Denylistu nebudou aplikovány žádné změny, nebo modifikace způsobené Magiskem</string>
|
||||
<string name="settings_denylist_error">Tato funkce vyžaduje povolení %1$s</string>
|
||||
<string name="settings_denylist_config_title">Nastavit DenyList</string>
|
||||
<string name="settings_denylist_config_summary">Vyberte procesy, které mají být zahrnuty do DenyListu</string>
|
||||
<string name="settings_hosts_title">Nesystémový hostitel</string>
|
||||
<string name="settings_hosts_summary">Přidáte modul pro podporu nesystémových hostitelů v aplikaci AdBlock.</string>
|
||||
<string name="settings_hosts_toast">Přidán systémový modul hostitelů</string>
|
||||
<string name="settings_denylist_config_summary">Vyberte procesy, které mají být zahrnuty v DenyListu</string>
|
||||
<string name="settings_hosts_title">Nesystémoví hostitelé</string>
|
||||
<string name="settings_hosts_summary">Přidá modul pro podporu nesystémových hostitelů v aplikaci AdBlock</string>
|
||||
<string name="settings_hosts_toast">Modul nesystémových hostitelů přidán</string>
|
||||
<string name="settings_app_name_hint">Nový název</string>
|
||||
<string name="settings_app_name_helper">Název aplikace bude nahrazen tímto názvem.</string>
|
||||
<string name="settings_app_name_helper">Název aplikace bude nahrazen tímto názvem</string>
|
||||
<string name="settings_app_name_error">Neplatný formát</string>
|
||||
<string name="settings_su_app_adb">Aplikace a ADB</string>
|
||||
<string name="settings_su_app">Pouze aplikace</string>
|
||||
@@ -166,22 +169,22 @@
|
||||
<string name="settings_su_reauth_title">Opětovné ověření po aktualizaci</string>
|
||||
<string name="settings_su_reauth_summary">Opětovné ověření oprávnění SuperUser po aktualizaci aplikace.</string>
|
||||
<string name="settings_su_tapjack_title">Povolit ochranu před TapJack</string>
|
||||
<string name="settings_su_tapjack_summary">Okno dialogu SuperUser nebude reagovat na kliknutí v případě, že je zavřené nebo překryté jiným oknem.</string>
|
||||
<string name="settings_su_tapjack_summary">Okno dialogu SuperUser nebude reagovat na kliknutí v případě, že je zavřené nebo překryté jiným oknem</string>
|
||||
<string name="settings_su_biometric_title">Povolit biometrické ověření</string>
|
||||
<string name="settings_su_biometric_summary">Použijte biometrické ověření pro povolení požadavků SuperUser.</string>
|
||||
<string name="no_biometric">Nepodporované zařízení nebo není biometrické ověření povolené</string>
|
||||
<string name="settings_su_biometric_summary">Použije biometrické ověření pro povolení požadavků SuperUser</string>
|
||||
<string name="no_biometric">Nepodporované zařízení, nebo není povolené biometrické ověření</string>
|
||||
<string name="settings_customization">Přizpůsobit</string>
|
||||
<string name="setting_add_shortcut_summary">Přidejte odkaz na domovskou obrazovku v případě, že se po skrytí aplikace její název a ikona těžko rozpoznávají.</string>
|
||||
<string name="settings_doh_title">DNS nebo HTTPS</string>
|
||||
<string name="settings_doh_description">Řešení pro opravy DNS v některých zemích.</string>
|
||||
<string name="setting_add_shortcut_summary">Přidá odkaz na domovskou obrazovku v případě, že se po skrytí aplikace její název a ikona těžko rozpoznávají.</string>
|
||||
<string name="settings_doh_title">DNS přes HTTPS</string>
|
||||
<string name="settings_doh_description">Obejde DNS v některých zemích</string>
|
||||
|
||||
<string name="multiuser_mode">Režim více uživatelů</string>
|
||||
<string name="settings_owner_only">Vlastník zařízení</string>
|
||||
<string name="settings_owner_manage">Správce zařízení</string>
|
||||
<string name="settings_user_independent">Všichni uživatelé</string>
|
||||
<string name="owner_only_summary">Pouze vlastník má přístup ROOT.</string>
|
||||
<string name="owner_manage_summary">Přístup ROOT má správce zařízení, který přijímá požadavky k přístupu.</string>
|
||||
<string name="user_independent_summary">Každý uživatel má svá vlastní pravidla přístupu ROOT.</string>
|
||||
<string name="owner_only_summary">Pouze vlastník má přístup ROOT</string>
|
||||
<string name="owner_manage_summary">Přístup ROOT má správce zařízení, který přijímá požadavky k přístupu</string>
|
||||
<string name="user_independent_summary">Každý uživatel má svá vlastní pravidla přístupu ROOT</string>
|
||||
|
||||
<string name="mount_namespace_mode">Režim připojení jmenného prostoru</string>
|
||||
<string name="settings_ns_global">Globální jmenný prostor</string>
|
||||
@@ -199,7 +202,7 @@
|
||||
<string name="download_file_error">Chyba při stahování souboru</string>
|
||||
<string name="magisk_update_title">Aktualizace Magisk je dostupná!</string>
|
||||
<string name="updated_title">Magisk aktualizován</string>
|
||||
<string name="updated_text">Ťukněte pro otevření aplikace</string>
|
||||
<string name="updated_text">Klepnutím otevřete aplikaci</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Ano</string>
|
||||
@@ -211,7 +214,7 @@
|
||||
<string name="flashing">Flashuji…</string>
|
||||
<string name="done">Hotovo!</string>
|
||||
<string name="failure">Nepodařilo se!</string>
|
||||
<string name="hide_app_title">Skrývám Magisk aplikaci…</string>
|
||||
<string name="hide_app_title">Skrývám aplikaci Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nebyla nalezena žádná aplikace pro otevření odkazu</string>
|
||||
<string name="complete_uninstall">Kompletní odinstalace</string>
|
||||
<string name="restore_img">Obnovit obrazy</string>
|
||||
@@ -220,25 +223,25 @@
|
||||
<string name="restore_fail">Původní záloha neexistuje!</string>
|
||||
<string name="setup_fail">Nastavení selhalo</string>
|
||||
<string name="env_fix_title">Vyžadováno dodatečné nastavení</string>
|
||||
<string name="env_fix_msg">Aby Magisk správně fungoval, potřebuje vaše zařízení další nastavení. Chcete pokračovat a restartovat?</string>
|
||||
<string name="env_full_fix_msg">Aby vaše zařízení správně fungovalo, potřebuje se Magisk znovu flashnotu. Znovu nainstalujte Magisk v aplikaci, režim obnovení nemůže získat správné informace o zařízení.</string>
|
||||
<string name="env_fix_msg">Aby mohl Magisk správně fungovat, je třeba zařízení dodatečně nastavit. Chcete pokračovat a restartovat zařízení?</string>
|
||||
<string name="env_full_fix_msg">Aby vaše zařízení správně fungovalo, potřebuje se Magisk znovu flashnout. Nainstalujte znovu Magisk v aplikaci, režim Recovery nemůže získat správné informace o zařízení.</string>
|
||||
<string name="setup_msg">Probíhá nastavení prostředí…</string>
|
||||
<string name="authenticate">Ověřit</string>
|
||||
<string name="unsupport_magisk_title">Nepodporovaná verze Magisk</string>
|
||||
<string name="unsupport_magisk_msg">ato verze aplikace nepodporuje verze Magisk nižší než %1$s.\n\nAplikace se bude chovat, jako by Magisk nebyl nainstalován, aktualizujte prosím Magisk co nejdříve.</string>
|
||||
<string name="unsupport_magisk_msg">Tato verze aplikace nepodporuje Magisk ve verzi nižší než %1$s.\n\nAplikace se bude chovat, jako by žádný Magisk nebyl nainstalován. Aktualizujte prosím Magisk co nejdříve.</string>
|
||||
<string name="unsupport_general_title">Abnormální stav</string>
|
||||
<string name="unsupport_system_app_msg">Spuštění této aplikace jako systémové aplikace není podporováno. Vraťte aplikaci zpět na uživatelskou aplikaci.</string>
|
||||
<string name="unsupport_other_su_msg">Byl detekován binární soubor \"su\", který nepochází z Magisk. Odeberte jakékoli konkurenční kořenové řešení a/nebo přeinstalujte Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk je nainstalován na externí úložiště. Přesuňte aplikaci do interního úložiště.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Skrytá aplikace Magisk nemůže pokračovat v práci, protože byl ztracen root. Obnovte prosím původní soubor APK.</string>
|
||||
<string name="unsupport_system_app_msg">Spuštění této aplikace jako systémové, není podporováno. Změňte prosím aplikaci zpět na uživatelskou.</string>
|
||||
<string name="unsupport_other_su_msg">Byl detekován binární soubor \"su\", který nepochází z Magisk. Odeberte jakékoli konkurenční ROOT řešení a/nebo přeinstalujte Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk je nainstalován na externím úložišti. Přesuňte prosím aplikaci do interního úložiště.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Skrytá aplikace Magisk nemůže dále fungovat, protože byl ztracen ROOT. Obnovte prosím původní soubor APK.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Chcete-li tuto funkci povolit, udělte oprávnění úložiště</string>
|
||||
<string name="post_notifications_denied">Chcete-li tuto funkci povolit, udělte oprávnění oznámení</string>
|
||||
<string name="install_unknown_denied">Povolte "instalovat aplikace z neznámých zdrojů" pro aktivaci této funkce</string>
|
||||
<string name="external_rw_permission_denied">Pro povolení této funkce, povolte oprávnění pro přístup k úložišti</string>
|
||||
<string name="post_notifications_denied">Pro povolení této funkce, povolte oznámení</string>
|
||||
<string name="install_unknown_denied">Pro povolení této funkce, povolte "instalovat aplikace z neznámých zdrojů"</string>
|
||||
<string name="add_shortcut_title">Přidat zástupce na domovskou obrazovku</string>
|
||||
<string name="add_shortcut_msg">Po skrytí této aplikace může být obtížné rozpoznat její název a ikonu. Chcete přidat hezkou zkratku na domovskou obrazovku?</string>
|
||||
<string name="app_not_found">Nebyla nalezena žádná aplikace, která by tuto akci zvládla</string>
|
||||
<string name="reboot_apply_change">Restartujte pro použití změn</string>
|
||||
<string name="restore_app_confirmation">Tím se skrytá aplikace obnoví zpět do původní aplikace. Opravdu to chcete udělat?</string>
|
||||
<string name="add_shortcut_msg">Po skrytí této aplikace může být obtížné rozpoznat její název a ikonu. Chcete přidat na domovskou obrazovku hezkého zástupce?</string>
|
||||
<string name="app_not_found">Nebyla nalezena žádná aplikace, která by tuto akci provedla</string>
|
||||
<string name="reboot_apply_change">Pro použití změn restartujte zařízení</string>
|
||||
<string name="restore_app_confirmation">Tímto skrytou aplikaci obnovíte zpět na původní aplikaci. Opravdu to chcete udělat?</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -46,7 +46,7 @@
|
||||
<string name="install_inactive_slot_msg">Dein Gerät wird gezwungen, nach einem Neustart in den inaktiven Slot zu booten! Diese Option nur verwenden, wenn OTA abgeschlossen ist.\nWeiter?</string>
|
||||
<string name="setup_title">Zusätzliche Einstellungen</string>
|
||||
<string name="select_patch_file">Eine Datei auswählen und patchen</string>
|
||||
<string name="patch_file_msg">Raw Image (*.img) oder ein ODIN tarfile (*.tar) auswählen</string>
|
||||
<string name="patch_file_msg">Ein Raw Image (*.img), eine ODIN Tar-Datei (*.tar) oder eine Payload.bin (*.bin) auswählen</string>
|
||||
<string name="reboot_delay_toast">Neustart in 5 Sekunden …</string>
|
||||
<string name="flash_screen_title">Installieren</string>
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<resources>
|
||||
|
||||
<!--Sections-->
|
||||
<!--Secciones-->
|
||||
<string name="modules">Módulos</string>
|
||||
<string name="superuser">Superusuario</string>
|
||||
<string name="logs">Registros</string>
|
||||
@@ -8,148 +8,150 @@
|
||||
<string name="install">Instalar</string>
|
||||
<string name="section_home">Inicio</string>
|
||||
<string name="section_theme">Temas</string>
|
||||
<string name="denylist">Lista de Denegacion</string>
|
||||
<string name="denylist">Lista de denegación</string>
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">Conexión no disponible</string>
|
||||
<!--Inicio/Pantalla principal-->
|
||||
<string name="no_connection">Sin conexión disponible</string>
|
||||
<string name="app_changelog">Registro de cambios</string>
|
||||
<string name="loading">Cargando…</string>
|
||||
<string name="update">Actualizar</string>
|
||||
<string name="not_available">No disponible</string>
|
||||
<string name="hide">Ocultar</string>
|
||||
<string name="home_package">Paquete</string>
|
||||
<string name="home_package">Nombre de paquete:</string>
|
||||
<string name="home_app_title">App</string>
|
||||
|
||||
<string name="home_notice_content">Descarga Magisk SÓLO de la página de GitHub oficial. Archivos de fuentes desconocidas pueden resultar maliciosos!</string>
|
||||
<string name="home_support_title">Apóyenos</string>
|
||||
<string name="home_follow_title">Síganos</string>
|
||||
<string name="home_notice_content">Descarga Magisk únicamente desde la página oficial de GitHub. ¡Las descargas desde fuentes desconocidas pueden ser maliciosas!</string>
|
||||
<string name="home_support_title">Apóyanos</string>
|
||||
<string name="home_follow_title">Síguenos</string>
|
||||
<string name="home_item_source">Código fuente</string>
|
||||
<string name="home_support_content">Magisk es, y siempre será gratuito y de código abierto. Sin embargo, puede apoyarnos enviando una pequeña donación.</string>
|
||||
<string name="home_installed_version">Instalado</string>
|
||||
<string name="home_latest_version">Última</string>
|
||||
<string name="home_support_content">Magisk es y siempre será gratis y de código abierto. Sin embargo, puedes mostrarnos tu apoyo mediante una donación.</string>
|
||||
<string name="home_installed_version">Versión instalada:</string>
|
||||
<string name="home_latest_version">Última versión disponible:</string>
|
||||
<string name="invalid_update_channel">Canal de actualización inválido</string>
|
||||
<string name="uninstall_magisk_title">Desinstalar Magisk</string>
|
||||
<string name="uninstall_magisk_msg">¡Todos los módulos serán desactivados/eliminados!\nSe eliminará la raíz!\n¡Sus datos serán potencialmente encriptados si no lo están ya!</string>
|
||||
<string name="uninstall_magisk_msg">Antes de desinstalar Magisk, ten en cuenta lo siguiente:\n• Todos los módulos serán desinstalados\n• Perderás el acceso root\n• Tu almacenamiento interno será reencriptado en caso de que no lo esté</string>
|
||||
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">Mantener cifrado forzado</string>
|
||||
<!--Instalación-->
|
||||
<string name="keep_force_encryption">Mantener FDE (Full-Disk Encryption)</string>
|
||||
<string name="keep_dm_verity">Mantener AVB 2.0/dm-verity</string>
|
||||
<string name="patch_vbmeta">Parche vbmeta en la imagen de arranque</string>
|
||||
<string name="patch_vbmeta">Parchar vbmeta en el archivo boot.img</string>
|
||||
<string name="recovery_mode">Modo recovery</string>
|
||||
<string name="install_options_title">Opciones</string>
|
||||
<string name="install_method_title">Método</string>
|
||||
<string name="install_options_title">Opciones de instalación:</string>
|
||||
<string name="install_method_title">Método de instalación:</string>
|
||||
<string name="install_next">Siguiente</string>
|
||||
<string name="install_start">Comencemos</string>
|
||||
<string name="manager_download_install">Pulse para descargar e instalar</string>
|
||||
<string name="install_start">Instalar</string>
|
||||
<string name="manager_download_install">Pulsa para descargar e instalar</string>
|
||||
<string name="direct_install">Instalación directa (recomendado)</string>
|
||||
<string name="install_inactive_slot">Instalar en ranura inactiva (después de OTA)</string>
|
||||
<string name="install_inactive_slot_msg">¡Su dispositivo será forzado a arrancar en la ranura inactiva actual después de un reinicio!\nSólo use esta opción después de que OTA haya terminado.\n¿Continuar?</string>
|
||||
<string name="install_inactive_slot">Instalar en el slot inactivo (tras una OTA)</string>
|
||||
<string name="install_inactive_slot_msg">Tu dispositivo será forzado a bootear en el slot inactivo actual después de un reinicio\nUsa esta opción solo después de que la actualización OTA se complete.\n¿Quieres continuar?</string>
|
||||
<string name="setup_title">Configuración adicional</string>
|
||||
<string name="select_patch_file">Seleccionar y parchar un archivo</string>
|
||||
<string name="patch_file_msg">Seleccione una imagen raw (*.img) o un archivo tar de ODIN (*.tar)</string>
|
||||
<string name="patch_file_msg">Selecciona una imagen raw (.img) o un archivo comprimido que la contenga (.tar), en caso de que quieras usar Odin (Samsung)</string>
|
||||
<string name="reboot_delay_toast">Reiniciando en 5 segundos…</string>
|
||||
<string name="flash_screen_title">Instalación</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Petición de superusuario</string>
|
||||
<string name="touch_filtered_warning">Como otra app está escondiendo la solicitud de superusuario, Magisk no puede verificar tu respuesta</string>
|
||||
<!--Superusuario-->
|
||||
<string name="su_request_title">Solicitud de superusuario</string>
|
||||
<string name="touch_filtered_warning">Hay una app superponiéndose a la solicitud de superusuario, por lo que Magisk no puede verificar tu respuesta.</string>
|
||||
<string name="deny">Denegar</string>
|
||||
<string name="prompt">Preguntar</string>
|
||||
<string name="grant">Permitir</string>
|
||||
<string name="su_warning">Permite acceso total a tu dispositivo.\n¡Denegar si no está seguro!</string>
|
||||
<string name="grant">Conceder</string>
|
||||
<string name="su_warning">Le concedes a esta app acceso completo a tu dispositivo.\n¡Rechaza la solicitud si desconfías de ella!</string>
|
||||
<string name="forever">Siempre</string>
|
||||
<string name="once">Una vez</string>
|
||||
<string name="tenmin">10 min</string>
|
||||
<string name="twentymin">20 min</string>
|
||||
<string name="thirtymin">30 min</string>
|
||||
<string name="sixtymin">60 min</string>
|
||||
<string name="su_allow_toast">Permitidos derechos de superusuario para %1$s</string>
|
||||
<string name="su_deny_toast">Denegados derechos de superusuario para %1$s</string>
|
||||
<string name="su_snack_grant">Derechos de superusuario para %1$s permitidos</string>
|
||||
<string name="su_snack_deny">Derechos de superusuario para %1$s denegados</string>
|
||||
<string name="tenmin">10 minutos</string>
|
||||
<string name="twentymin">20 minutos</string>
|
||||
<string name="thirtymin">30 minutos</string>
|
||||
<string name="sixtymin">1 hora</string>
|
||||
<string name="su_allow_toast">Le concediste privilegios de superusuario a %1$s</string>
|
||||
<string name="su_deny_toast">Le denegaste privilegios de superusuario a %1$s</string>
|
||||
<string name="su_snack_grant">Privilegios de superusuario para %1$s concedidos</string>
|
||||
<string name="su_snack_deny">Privilegios de superusuario para %1$s denegados</string>
|
||||
<string name="su_snack_notif_on">Notificaciones de %1$s habilitadas</string>
|
||||
<string name="su_snack_notif_off">Notificaciones de %1$s deshabilitadas</string>
|
||||
<string name="su_snack_log_on">Registros de %1$s habilitados</string>
|
||||
<string name="su_snack_log_off">Registros de %1$s deshabilitados</string>
|
||||
<string name="su_revoke_title">¿Revocar?</string>
|
||||
<string name="su_revoke_msg">¿Confirmar para revocar derechos de %1$s?</string>
|
||||
<string name="toast">Aviso</string>
|
||||
<string name="none">Nada</string>
|
||||
<string name="su_revoke_title">Revocar privilegios</string>
|
||||
<string name="su_revoke_msg">Confirma para revocarle los privilegios de superusuario a %1$s</string>
|
||||
<string name="toast">Mensaje emergente</string>
|
||||
<string name="none">Ninguno</string>
|
||||
|
||||
<string name="superuser_toggle_notification">Notificaciones</string>
|
||||
<string name="superuser_toggle_revoke">Revocar</string>
|
||||
<string name="superuser_policy_none">Ninguna aplicación ha pedido permiso de superusuario todavía.</string>
|
||||
<string name="superuser_policy_none">Ninguna app ha solicitado privilegios de superusuario aún.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">No se tiene registro, intente usar más sus aplicaciones habilitadas para SU.</string>
|
||||
<string name="log_data_magisk_none">Los registros de Magisk están vacíos, esto es muy raro.</string>
|
||||
<!--Registros-->
|
||||
<string name="log_data_none">No hay registros de apps disponibles</string>
|
||||
<string name="log_data_magisk_none">No hay registros de Magisk disponibles</string>
|
||||
<string name="menuSaveLog">Guardar registro</string>
|
||||
<string name="menuClearLog">Limpiar registro ahora</string>
|
||||
<string name="logs_cleared">Registro limpiado correctamente</string>
|
||||
<string name="menuClearLog">Limpiar registro</string>
|
||||
<string name="logs_cleared">Registros limpiados correctamente</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">UID de objetivo: %1$d</string>
|
||||
<string name="target_uid">Target UID: %1$d</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
<!--SafetyNet/PlayIntegrity-->
|
||||
|
||||
<!-- MagiskHide -->
|
||||
<!--Denylist-->
|
||||
<string name="show_system_app">Mostrar apps del sistema</string>
|
||||
<string name="show_os_app">Mostrar apps del OS</string>
|
||||
<string name="hide_filter_hint">Filtrar por nombre</string>
|
||||
<string name="show_os_app">Mostrar apps del sistema operativo</string>
|
||||
<string name="hide_filter_hint">Nombre de la app o del paquete</string>
|
||||
<string name="hide_search">Buscar</string>
|
||||
|
||||
<!--Module -->
|
||||
<string name="no_info_provided">(No hay información)</string>
|
||||
<!--Módulos-->
|
||||
<string name="no_info_provided">El creador del módulo no proporcionó ninguna descripción.</string>
|
||||
<string name="reboot_userspace">Reinicio suave</string>
|
||||
<string name="reboot_recovery">Reiniciar en modo recovery</string>
|
||||
<string name="reboot_bootloader">Reiniciar en modo bootloader</string>
|
||||
<string name="reboot_download">Reiniciar en modo download</string>
|
||||
<string name="reboot_edl">Reiniciar en modo EDL</string>
|
||||
<string name="module_version_author">%1$s por %2$s</string>
|
||||
<string name="module_state_remove">Eliminar</string>
|
||||
<string name="module_state_restore">Restaurar</string>
|
||||
<string name="reboot_recovery">Modo Recovery</string>
|
||||
<string name="reboot_bootloader">Modo Bootloader</string>
|
||||
<string name="reboot_download">Modo Download</string>
|
||||
<string name="reboot_edl">Modo EDL</string>
|
||||
<string name="module_version_author">Versión %1$s, por %2$s</string>
|
||||
<string name="module_state_remove">Desinstalar</string>
|
||||
<string name="module_state_restore">Reinstalar</string>
|
||||
<string name="module_action_install_external">Instalar desde almacenamiento</string>
|
||||
<string name="update_available">Actualización disponible</string>
|
||||
<string name="suspend_text_riru">Módulo suspendido porque %1$s está habilitado</string>
|
||||
<string name="suspend_text_zygisk">Módulo suspendido porque %1$s no está habilitado</string>
|
||||
<string name="zygisk_module_unloaded">Módulo Zygisk no cargado debido a incompatibilidad</string>
|
||||
<string name="module_empty">Ningún módulo instalado</string>
|
||||
<string name="suspend_text_riru">Módulo suspendido porque %1$s está activado</string>
|
||||
<string name="suspend_text_zygisk">Módulo suspendido porque %1$s está desactivado</string>
|
||||
<string name="zygisk_module_unloaded">El módulo Zygisk no se cargó por problemas de compatibilidad</string>
|
||||
<string name="module_empty">No hay módulos instalados</string>
|
||||
<string name="confirm_install">¿Quieres instalar el módulo %1$s?</string>
|
||||
<string name="confirm_install_title">Confirmar instalación</string>
|
||||
|
||||
<!--Settings -->
|
||||
<string name="settings_dark_mode_title">Elegir modo</string>
|
||||
<string name="settings_dark_mode_message">¡Seleccione el modo que mejor se adapte a su estilo!</string>
|
||||
<!--Ajustes-->
|
||||
<string name="settings_dark_mode_title">Modo del tema</string>
|
||||
<string name="settings_dark_mode_message">¡Elige el modo que más se adapte a tu estilo!</string>
|
||||
<string name="settings_dark_mode_light">Claro</string>
|
||||
<string name="settings_dark_mode_system">Seguir al sistema</string>
|
||||
<string name="settings_dark_mode_dark">Oscuro</string>
|
||||
<string name="settings_download_path_title">Ruta de Descarga</string>
|
||||
<string name="settings_download_path_message">Los archivos se guardarán en %1$s</string>
|
||||
<string name="settings_hide_app_title">Esconder la app de Magisk</string>
|
||||
<string name="settings_hide_app_summary">Instalar una nueva app proxy con una ID de paquete aleatoria y una etiqueta personalizada</string>
|
||||
<string name="settings_restore_app_title">Restaurar la app de Magisk</string>
|
||||
<string name="settings_restore_app_summary">Descubrir la app y restaurarla al APK original</string>
|
||||
<string name="settings_dark_mode_system">Predeterminado del sistema</string>
|
||||
<string name="settings_download_path_title">Ruta de descarga</string>
|
||||
<string name="settings_download_path_message">Los archivos serán guardados en la siguiente ruta:\n%1$s</string>
|
||||
<string name="settings_hide_app_title">Ocultar la app de Magisk</string>
|
||||
<string name="settings_hide_app_summary">Instalar una versión proxy de la app con un nombre de paquete aleatorio y una etiqueta personalizada</string>
|
||||
<string name="settings_restore_app_title">Restaurar la app</string>
|
||||
<string name="settings_restore_app_summary">Desoculta la app y la reemplaza por la original</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="system_default">(Idioma del sistema)</string>
|
||||
<string name="settings_check_update_title">Comprobar Actualizaciones</string>
|
||||
<string name="settings_check_update_summary">Buscar periódicamente en segundo plano si existen actualizaciones</string>
|
||||
<string name="system_default">Predeterminado del sistema</string>
|
||||
<string name="settings_check_update_title">Buscar actualizaciones</string>
|
||||
<string name="settings_check_update_summary">Busca actualizaciones periódicamente en segundo plano</string>
|
||||
<string name="settings_update_channel_title">Canal de actualización</string>
|
||||
<string name="settings_update_stable">Estable</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Personalizado</string>
|
||||
<string name="settings_update_custom_msg">Insertar una URL personalizada</string>
|
||||
<string name="settings_zygisk_summary">Corre partes de magisk en zygote</string>
|
||||
<string name="settings_denylist_title">Aplicar lista de denegacion</string>
|
||||
<string name="settings_denylist_summary">Los procesos en la lista de denegacion tandran todas las modificaciones de magisk revertidas</string>
|
||||
<string name="settings_denylist_error">Esta requere de %1$s para ser activada</string>
|
||||
<string name="settings_denylist_config_title">Configurar lista de denegacion</string>
|
||||
<string name="settings_denylist_config_summary">Seleccione los procesos para ser incluidos en la lista de denegacion</string>
|
||||
<string name="settings_hosts_title">Systemless Hosts</string>
|
||||
<string name="settings_hosts_summary">Soporte para bloqueadores de publicidad fuera de la partición system</string>
|
||||
<string name="settings_hosts_toast">Módulo systemless hosts agregado</string>
|
||||
<string name="settings_app_name_hint">Nuevo Nombre</string>
|
||||
<string name="settings_app_name_helper">La app se reempaquetará</string>
|
||||
<string name="settings_app_name_error">Formato inválido</string>
|
||||
<string name="settings_su_app_adb">Aplicaciones y ADB</string>
|
||||
<string name="settings_su_app">Sólo aplicaciones</string>
|
||||
<string name="settings_su_adb">Sólo ADB</string>
|
||||
<string name="settings_update_custom_msg">Ingresa la URL del canal personalizado</string>
|
||||
<string name="settings_zygisk_summary">Ejecuta Magisk en el proceso Zygote de Android</string>
|
||||
<string name="settings_denylist_title">Aplicar lista de denegación</string>
|
||||
<string name="settings_denylist_summary">Las apps en la lista de denegación no serán modificadas por Magisk</string>
|
||||
<string name="settings_denylist_error">Necesitas activar %1$s para usar esta función</string>
|
||||
<string name="settings_denylist_config_title">Configurar lista de denegación</string>
|
||||
<string name="settings_denylist_config_summary">Selecciona los procesos de cada app que se incluirán en la lista</string>
|
||||
<string name="settings_hosts_title">Systemless hosts</string>
|
||||
<string name="settings_hosts_summary">Prepara al archivo hosts para que los bloqueadores de anuncios puedan modificarlo sin alterar el sistema</string>
|
||||
<string name="settings_hosts_toast">El módulo se instaló correctamente, reinicia para aplicar los cambios.</string>
|
||||
<string name="settings_app_name_hint">Nuevo nombre de la app</string>
|
||||
<string name="settings_app_name_helper">La app de Magisk será reempaquetada con este nombre</string>
|
||||
<string name="settings_app_name_error">El nombre es inválido</string>
|
||||
<string name="settings_su_app_adb">Permitir tanto a las apps como a ADB</string>
|
||||
<string name="settings_su_app">Permitir solo a las apps</string>
|
||||
<string name="settings_su_adb">Permitir solo a ADB</string>
|
||||
<string name="settings_su_disable">Deshabilitado</string>
|
||||
<string name="settings_su_request_10">10 segundos</string>
|
||||
<string name="settings_su_request_15">15 segundos</string>
|
||||
@@ -158,48 +160,48 @@
|
||||
<string name="settings_su_request_45">45 segundos</string>
|
||||
<string name="settings_su_request_60">60 segundos</string>
|
||||
<string name="superuser_access">Acceso de superusuario</string>
|
||||
<string name="auto_response">Respuesta automática</string>
|
||||
<string name="request_timeout">Tiempo de petición</string>
|
||||
<string name="auto_response">Respuesta predeterminada</string>
|
||||
<string name="request_timeout">Duración de la solicitud</string>
|
||||
<string name="superuser_notification">Notificación de superusuario</string>
|
||||
<string name="settings_su_reauth_title">Re-autenticación</string>
|
||||
<string name="settings_su_reauth_summary">Pedir permisos de superusuario nuevamente si una aplicación es actualizada o reinstalada</string>
|
||||
<string name="settings_su_tapjack_title">Habilitar Protección contra Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">El cuadro de solicitud de superusuario no responderá mientras esté oculto por cualquier otra ventana o superposición</string>
|
||||
<string name="settings_su_biometric_title">Habilitar autenticación biométrica</string>
|
||||
<string name="settings_su_biometric_summary">Usar autenticación biométrica para permitir solicitudes de superusuario</string>
|
||||
<string name="no_biometric">Dispositivo no compatible o las configuraciones biométricas no están habilitadas</string>
|
||||
<string name="settings_su_reauth_title">Reautenticar después de una actualización</string>
|
||||
<string name="settings_su_reauth_summary">Las apps volverán a solicitar privilegios de superusuario tras actualizarse</string>
|
||||
<string name="settings_su_tapjack_title">Protección contra tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">No podrás interactuar con las solicitudes de superusuario mientras estén tapadas por cualquier otra ventana o superposición</string>
|
||||
<string name="settings_su_biometric_title">Autenticación biométrica</string>
|
||||
<string name="settings_su_biometric_summary">Usa tu rostro, iris o huella digital para conceder privilegios de superusuario</string>
|
||||
<string name="no_biometric">El dispositivo no está soportado o no has configurado ningún dato biométrico</string>
|
||||
<string name="settings_customization">Personalización</string>
|
||||
<string name="setting_add_shortcut_summary">Añade un bonito atajo en la pantalla de inicio en caso de que el nombre y el icono sean difíciles de reconocer después de ocultar la aplicación</string>
|
||||
<string name="setting_add_shortcut_summary">Añade un atajo a la app con el ícono de Magisk a la pantalla de inicio en caso de que te resulte difícil reconocerla tras haberle cambiado el nombre y el ícono</string>
|
||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||
<string name="settings_doh_description">Evitar envenenamiento de DNS en algunos países</string>
|
||||
<string name="settings_doh_description">Proporciona una solución alternativa (workaround) contra el envenenamiento de DNS en algunos países</string>
|
||||
|
||||
<string name="multiuser_mode">Modo multiusuario</string>
|
||||
<string name="settings_owner_only">Sólo administrador del dispositivo</string>
|
||||
<string name="settings_owner_manage">Administrador del dispositivo</string>
|
||||
<string name="settings_user_independent">Usuario independiente</string>
|
||||
<string name="owner_only_summary">Sólo el administrador tiene acceso root</string>
|
||||
<string name="owner_manage_summary">Sólo el administrador puede supervisar el acceso root y recibir solicitudes de otros usuarios</string>
|
||||
<string name="user_independent_summary">Cada usuario tiene separadas sus propias reglas de root </string>
|
||||
<string name="settings_owner_only">Propietario del dispositivo</string>
|
||||
<string name="settings_owner_manage">Administrado por el propietario del dispositivo</string>
|
||||
<string name="settings_user_independent">Acceso independiente</string>
|
||||
<string name="owner_only_summary">Solo el administrador tiene acceso root</string>
|
||||
<string name="owner_manage_summary">Solo el administrador puede manipular y recibir solicitudes de superusuario</string>
|
||||
<string name="user_independent_summary">Cada usuario tiene sus propias reglas y acceso al root</string>
|
||||
|
||||
<string name="mount_namespace_mode">Montar Namespace </string>
|
||||
<string name="settings_ns_global">Global Namespace</string>
|
||||
<string name="settings_ns_requester">Heredar Namespace</string>
|
||||
<string name="settings_ns_isolate">Aislar Namespace</string>
|
||||
<string name="global_summary">Todas las sesiones de root utilizan el soporte Global Namespace</string>
|
||||
<string name="requester_summary">Las sesiones de root heredarán las peticiones Namespace</string>
|
||||
<string name="isolate_summary">Cada sesión root tendrá su propia Namespace</string>
|
||||
<string name="mount_namespace_mode">Modo del namespace de montaje</string>
|
||||
<string name="settings_ns_global">Namespace global</string>
|
||||
<string name="settings_ns_requester">Namespace heredado</string>
|
||||
<string name="settings_ns_isolate">Namespace aislado</string>
|
||||
<string name="global_summary">Todas las sesiones de superusuario usarán el namespace de montaje global</string>
|
||||
<string name="requester_summary">Las sesiones de superusuario heredarán el namespace de montaje de quien las solicita</string>
|
||||
<string name="isolate_summary">Cada sesión de superusuario tendrá su propio namespace de montaje aislado</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Actualización de Magisk</string>
|
||||
<!--Notificaciones-->
|
||||
<string name="update_channel">Actualizaciones de Magisk</string>
|
||||
<string name="progress_channel">Notificaciones de progreso</string>
|
||||
<string name="updated_channel">Actualización completa</string>
|
||||
<string name="download_complete">Descarga Completa</string>
|
||||
<string name="download_file_error">Error descargando archivo</string>
|
||||
<string name="updated_channel">Actualización finalizada</string>
|
||||
<string name="download_complete">Descarga finalizada</string>
|
||||
<string name="download_file_error">Ocurrió un error en la descarga</string>
|
||||
<string name="magisk_update_title">¡Actualización de Magisk disponible!</string>
|
||||
<string name="updated_title">Magisk Actualizado</string>
|
||||
<string name="updated_text">Toca para abrir la aplicación</string>
|
||||
<string name="updated_title">Se actualizó Magisk</string>
|
||||
<string name="updated_text">Pulsa para abrir la app</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<!--Alertas y diálogos-->
|
||||
<string name="yes">Sí</string>
|
||||
<string name="no">No</string>
|
||||
<string name="repo_install_title">Instalar %1$s %2$s(%3$d)</string>
|
||||
@@ -207,34 +209,36 @@
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="release_notes">Notas de lanzamiento</string>
|
||||
<string name="flashing">Flasheando…</string>
|
||||
<string name="done">¡Hecho!</string>
|
||||
<string name="failure">Ha fallado</string>
|
||||
<string name="hide_app_title">Escondiendo la app de Magisk…</string>
|
||||
<string name="open_link_failed_toast">No se encontró ninguna aplicación para abrir el enlace…</string>
|
||||
<string name="done">¡Listo!</string>
|
||||
<string name="failure">¡Ocurrió un error!</string>
|
||||
<string name="hide_app_title">Ocultando la app de Magisk…</string>
|
||||
<string name="open_link_failed_toast">No se encontró ninguna app que pueda abrir este enlace</string>
|
||||
<string name="complete_uninstall">Desinstalación completa</string>
|
||||
<string name="restore_img">Restaurar imágenes</string>
|
||||
<string name="restore_img">Solo restaurar las imágenes</string>
|
||||
<string name="restore_img_msg">Restaurando…</string>
|
||||
<string name="restore_done">¡Restauración Terminada!</string>
|
||||
<string name="restore_fail">¡El respaldo de la imagen boot Stock no existe!</string>
|
||||
<string name="setup_fail">Instalación fallida</string>
|
||||
<string name="env_fix_title">Se requiere una instalación adicional</string>
|
||||
<string name="env_fix_msg">Tu dispositivo necesita configuración adicional para el correcto funcionamiento de Magisk. ¿Quieres proceder y reiniciar?</string>
|
||||
<string name="restore_done">¡Restauración completa!</string>
|
||||
<string name="restore_fail">Oops... no se encontró una copia de seguridad de las imágenes originales</string>
|
||||
<string name="setup_fail">Ocurrió un error en la configuración</string>
|
||||
<string name="env_fix_title">Configuración adicional</string>
|
||||
<string name="env_fix_msg">Tu dispositivo necesita una configuración adicional para que Magisk funcione correctamente. ¿Quieres continuar y reiniciar?</string>
|
||||
<string name="env_full_fix_msg">Tu dispositivo necesita reinstalar Magisk para que este funcione correctamente. Por favor, reinstala Magisk desde la app, el modo recovery no puede obtener la información correcta del dispositivo.</string>
|
||||
<string name="setup_msg">Ejecutando configuración de entorno…</string>
|
||||
<string name="authenticate">Autenticar</string>
|
||||
<string name="unsupport_magisk_title">Versión de Magisk no soportada</string>
|
||||
<string name="unsupport_magisk_msg">Esta versión de la app no soporta versiones de Magisk inferiores a la %1$s.\n\nLa aplicación se comportará como si no tuvieses Magisk instalado, por favor actualiza Magisk tan pronto como sea posible.</string>
|
||||
<string name="unsupport_magisk_msg">La versión actual de la app no soporta versiones de Magisk inferiores a la %1$s.\n\nLa app se comportará como si Magisk no estuviese instalado, por favor, actualízalo tan pronto como puedas</string>
|
||||
<string name="unsupport_general_title">Estado anormal</string>
|
||||
<string name="unsupport_system_app_msg">No se admite la ejecución de esta aplicación como una aplicación del sistema. Revierta la aplicación a una aplicación de usuario.</string>
|
||||
<string name="unsupport_other_su_msg">Se ha detectado un binario \"su\" que no es de Magisk. Elimine cualquier solución raíz de la competencia y/o reinstale Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk está instalado en el almacenamiento externo. Mueva la aplicación al almacenamiento interno.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">La aplicación Magisk oculta no puede seguir funcionando porque se perdió la raíz. Restaura el APK original.</string>
|
||||
<string name="unsupport_system_app_msg">La ejecución de esta app como app del sistema no está soportada. Por favor, vuelve a instalarla como usuario</string>
|
||||
<string name="unsupport_other_su_msg">Se detectó un binario \"su\" ajeno a Magisk. Por favor, desinstala cualquier solución root de la competencia y/o reinstala Magisk</string>
|
||||
<string name="unsupport_external_storage_msg">La app de Magisk está instalada en un almacenamiento externo, por favor, muévela al almacenamiento interno.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">La app de Magisk oculta no puede seguir funcionando porque se perdió el acceso root. Por favor, restaura el APK original</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Conceder permiso de almacenamiento para habilitar esta funcionalidad</string>
|
||||
<string name="install_unknown_denied">Permitir "instalar aplicaciones desconocidas" para habilitar esta funcionalidad</string>
|
||||
<string name="external_rw_permission_denied">Permite el acceso al almacenamiento para activar esta funcionalidad</string>
|
||||
<string name="post_notifications_denied">Permite mostrar notificaciones para activar esta funcionalidad</string>
|
||||
<string name="install_unknown_denied">Permite la instalación de apps desconocidas para activar esta funcionalidad</string>
|
||||
<string name="add_shortcut_title">Añadir un atajo a la pantalla de inicio</string>
|
||||
<string name="add_shortcut_msg">Tras esconder la app, su nombre e icono pueden resultar difíciles de reconocer. ¿Quieres añadir un acceso directo más bonito a la pantalla principal?</string>
|
||||
<string name="app_not_found">No se ha encontrado ninguna app para manejar esta acción</string>
|
||||
<string name="reboot_apply_change">Reiniciar para aplicar cambios</string>
|
||||
<string name="restore_app_confirmation">Esto restaurará la aplicación oculta a la aplicación original. ¿Realmente quieres hacer esto?</string>
|
||||
<string name="add_shortcut_msg">Añade un atajo a la app con el ícono de Magisk a la pantalla de inicio en caso de que te resulte difícil reconocerla tras haberle cambiado el nombre y el ícono</string>
|
||||
<string name="app_not_found">No se encontró ninguna app que pueda realizar esta acción</string>
|
||||
<string name="reboot_apply_change">Reinicia para aplicar los cambios</string>
|
||||
<string name="restore_app_confirmation">Se restaurará la app oculta de vuelta a la original. ¿Quieres continuar?</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -112,9 +112,9 @@
|
||||
<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">Stable</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Custom Channel</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">اضافه کردن یک URL سفارشی</string>
|
||||
<string name="settings_hosts_title">نصب بدون حذف یا تغییر در فایل ها</string>
|
||||
<string name="settings_hosts_summary">نصب بدون حذف یا تغییر در فایل ها رای ساپورت از برنامه های Adblock</string>
|
||||
@@ -153,16 +153,16 @@
|
||||
<string name="user_independent_summary">هر کاربر قوانین روت جداگانه خود را دارد</string>
|
||||
|
||||
<string name="mount_namespace_mode">نصب کردن Namespace Mode</string>
|
||||
<string name="settings_ns_global">Global Namespace</string>
|
||||
<string name="settings_ns_requester">Inherit Namespace</string>
|
||||
<string name="settings_ns_isolate">Isolated Namespace</string>
|
||||
<string name="settings_ns_global">سراسری Namespace</string>
|
||||
<string name="settings_ns_requester">وراثتی Namespace</string>
|
||||
<string name="settings_ns_isolate">منزوی Namespace</string>
|
||||
<string name="global_summary">همه سشن های روت از global namespace نصب شده استفاده میکنند</string>
|
||||
<string name="requester_summary">سشن های روت namespace دراخواست کننده را به ارث می برند</string>
|
||||
<string name="isolate_summary">هر سشن روت namespace جداگانه خود را دارد</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Magisk بروزرسانی های</string>
|
||||
<string name="progress_channel">Progress اعلان</string>
|
||||
<string name="progress_channel">اعلان پیشرفت</string>
|
||||
<string name="download_complete">دانلود کامل شد</string>
|
||||
<string name="download_file_error">خطا در دانلود فایل</string>
|
||||
<string name="magisk_update_title">بروزرسانی Magisk در دسترس است!</string>
|
||||
|
@@ -46,7 +46,7 @@
|
||||
<string name="install_inactive_slot_msg">Votre appareil sera obligatoirement réamorcé à partir de l’espace (slot) actuellement inactif après son redémarrage !\nN’utilisez uniquement cette option qu’après que la mise à jour OTA a été effectuée.\nVoulez‑vous continuer ?</string>
|
||||
<string name="setup_title">Configuration supplémentaire</string>
|
||||
<string name="select_patch_file">Sélectionnez le fichier cible du correctif</string>
|
||||
<string name="patch_file_msg">Sélectionnez une image brute (*.img) ou une archive TAR ODIN (*.tar)</string>
|
||||
<string name="patch_file_msg">Sélectionnez une image brute (*.img) ou une archive TAR ODIN (*.tar) ou un fichier payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Redémarrage dans 5 secondes…</string>
|
||||
<string name="flash_screen_title">Installation</string>
|
||||
|
||||
|
@@ -20,7 +20,7 @@
|
||||
<string name="home_package">Paket</string>
|
||||
<string name="home_app_title">Aplikasi</string>
|
||||
|
||||
<string name="home_notice_content">Unduh Magisk HANYA dari halaman GitHub resmi kami. File dari sumber yang tidak dikenal bisa berbahaya!</string>
|
||||
<string name="home_notice_content">Unduh Magisk HANYA dari halaman GitHub resmi kami. File dari sumber yang tidak dikenal dapat berbahaya!</string>
|
||||
<string name="home_support_title">Dukung kami</string>
|
||||
<string name="home_follow_title">Ikuti Kami</string>
|
||||
<string name="home_item_source">Sumber</string>
|
||||
@@ -113,6 +113,8 @@
|
||||
<string name="suspend_text_zygisk">Modul ditangguhkan karena %1$s tidak diaktifkan</string>
|
||||
<string name="zygisk_module_unloaded">Modul Zygisk tidak dimuat karena ketidakcocokan</string>
|
||||
<string name="module_empty">Tidak ada modul terpasang</string>
|
||||
<string name="confirm_install">Pasang modul %1$s?</string>
|
||||
<string name="confirm_install_title">Konfirmasi Pasang</string>
|
||||
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Mode tema</string>
|
||||
@@ -219,6 +221,7 @@
|
||||
<string name="setup_fail">Penyiapan gagal</string>
|
||||
<string name="env_fix_title">Perlu penyiapan tambahan</string>
|
||||
<string name="env_fix_msg">Perangkat Anda membutuhkan pengaturan tambahan untuk Magisk agar berfungsi dengan benar. Apakah Anda ingin melanjutkan dan Menyalakan ulang?</string>
|
||||
<string name="env_full_fix_msg">Perangkat Anda membutuhkan pemasangan ulang Magisk agar berfungsi dengan baik. Silakan pasang ulang Magisk dalam aplikasi, mode recovery tidak dapat memperoleh info perangkat yang benar.</string>
|
||||
<string name="setup_msg">Memproses penyiapan lingkungan…</string>
|
||||
<string name="authenticate">Autentikasi</string>
|
||||
<string name="unsupport_magisk_title">Versi Magisk tidak didukung</string>
|
||||
|
@@ -16,7 +16,7 @@
|
||||
<string name="loading">Caricamento…</string>
|
||||
<string name="update">Aggiorna</string>
|
||||
<string name="not_available">N/D</string>
|
||||
<string name="hide">Hide</string>
|
||||
<string name="hide">Nascondi</string>
|
||||
<string name="home_package">Pacchetto</string>
|
||||
<string name="home_app_title">App</string>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<string name="install_inactive_slot_msg">Questo dispositivo verrà FORZATO ad avviarsi usando lo slot inattivo!\nUsa questo metodo solo dopo che un aggiornamento OTA è stato installato.\nVuoi continuare?</string>
|
||||
<string name="setup_title">Configurazione aggiuntiva</string>
|
||||
<string name="select_patch_file">Seleziona e aggiorna un file</string>
|
||||
<string name="patch_file_msg">Seleziona un\'immagine in formato .img o un file ODIN .tar</string>
|
||||
<string name="patch_file_msg">Seleziona un\'immagine in formato .img, un file .tar di ODIN o un file payload.bin</string>
|
||||
<string name="reboot_delay_toast">Riavvio fra 5 secondi…</string>
|
||||
<string name="flash_screen_title">Installazione</string>
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
<string name="module_empty">Nessun modulo installato</string>
|
||||
|
||||
<!--Settings -->
|
||||
<string name="confirm_install">Vuoi installare il modulo %1$s?</string>
|
||||
<string name="confirm_install_title">Conferma installazione</string>
|
||||
<string name="settings_dark_mode_title">Impostazioni tema</string>
|
||||
<string name="settings_dark_mode_message">Seleziona il tema che si adatta meglio al tuo stile!</string>
|
||||
<string name="settings_dark_mode_light">Chiaro</string>
|
||||
@@ -217,6 +219,7 @@
|
||||
<string name="setup_fail">Configurazione fallita</string>
|
||||
<string name="env_fix_title">Configurazione aggiuntiva richiesta</string>
|
||||
<string name="env_fix_msg">Il tuo dispositivo necessita di una configurazione aggiuntiva per far funzionare Magisk correttamente. Vuoi procedere e riavviare il dispositivo?</string>
|
||||
<string name="env_full_fix_msg">È necessario eseguire di nuovo il flash di Magisk per farlo funzionare correttamente. Reinstalla Magisk utilizzando l\'app, in modalità recovery non è possibile ottenere informazioni corrette sul dispositivo.</string>
|
||||
<string name="setup_msg">Configurazione dell\'ambiente in corso…</string>
|
||||
<string name="authenticate">Autentica</string>
|
||||
<string name="unsupport_magisk_title">Versione di Magisk non supportata</string>
|
||||
@@ -228,6 +231,7 @@
|
||||
<string name="unsupport_nonroot_stub_msg">Dal momento che i permessi di root sono stati persi, l\'app di Magisk nascosta non può più funzionare. Ripristina l\'APK originale.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Consenti l\'accesso alla memoria del dispositivo per abilitare questa opzione</string>
|
||||
<string name="post_notifications_denied">Consenti l\'invio di notifiche per abilitare questa opzione</string>
|
||||
<string name="install_unknown_denied">Consenti l\'installazione di app sconosciute per abilitare questa funzionalità</string>
|
||||
<string name="add_shortcut_title">Aggiungi collegamento alla schermata iniziale</string>
|
||||
<string name="add_shortcut_msg">Dopo aver nascosto quest\'app, il suo nome e la sua icona potrebbero diventare difficili da riconoscere. Vuoi aggiungere una scorciatoia alla schermata principale?</string>
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<string name="denylist">거부목록</string>
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">사용 가능한 연결 없음</string>
|
||||
<string name="no_connection">인터넷 연결 없음</string>
|
||||
<string name="app_changelog">앱 변경 사항</string>
|
||||
<string name="loading">로딩중…</string>
|
||||
<string name="update">업데이트</string>
|
||||
@@ -33,7 +33,7 @@
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">강제 암호화 유지</string>
|
||||
<string name="keep_dm_verity">AVB 2.0/dm-verity 유지</string>
|
||||
<string name="patch_vbmeta">부트 이미지에 vbmeta 패치</string>
|
||||
<string name="patch_vbmeta">부트 이미지의 vbmeta 패치</string>
|
||||
<string name="recovery_mode">리커버리 모드</string>
|
||||
<string name="install_options_title">옵션</string>
|
||||
<string name="install_method_title">설치 방법</string>
|
||||
@@ -80,7 +80,7 @@
|
||||
<string name="superuser_policy_none">슈퍼유저 권한을 요청한 앱이 없습니다.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">로그가 없습니다. SU 권한을 필요로 하는 앱을 사용하십시오.</string>
|
||||
<string name="log_data_none">로그가 없습니다. 슈퍼유저 권한을 필요로 하는 앱을 사용하십시오.</string>
|
||||
<string name="log_data_magisk_none">Magisk 로그가 없습니다.</string>
|
||||
<string name="menuSaveLog">로그 저장</string>
|
||||
<string name="menuClearLog">지금 로그 삭제</string>
|
||||
@@ -108,8 +108,8 @@
|
||||
<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="suspend_text_riru">%1$s 가 활성화 되어있어 모듈 로드가 일시정지 되었습니다.</string>
|
||||
<string name="suspend_text_zygisk">%1$s 가 비활성화 되어있어 모듈이 로드되지 않았습니다.</string>
|
||||
<string name="zygisk_module_unloaded">호환성 문제로 인해 Zygisk 모듈이 로드되지 않았습니다.</string>
|
||||
|
||||
<!--Settings-->
|
||||
@@ -121,8 +121,8 @@
|
||||
<string name="settings_download_path_title">다운로드 경로</string>
|
||||
<string name="settings_download_path_message">파일이 %1$s에 저장됩니다</string>
|
||||
<string name="settings_hide_app_title">Magisk 앱 숨기기</string>
|
||||
<string name="settings_hide_app_summary">랜덤 패키지 ID와 커스텀 앱 라벨을 사용하여 프록시 앱을 설치합니다.</string>
|
||||
<string name="settings_restore_app_title">Magisk 앱 복구</string>
|
||||
<string name="settings_hide_app_summary">랜덤 패키지 ID와 커스텀 앱 이름으로 Magisk 프록시 앱을 설치합니다.</string>
|
||||
<string name="settings_restore_app_title">Magisk 앱 복원</string>
|
||||
<string name="settings_restore_app_summary">앱 숨기기를 해제하고 원래 APK로 복원합니다.</string>
|
||||
<string name="language">언어</string>
|
||||
<string name="system_default">(시스템 기본값)</string>
|
||||
@@ -135,10 +135,10 @@
|
||||
<string name="settings_update_custom_msg">사용자 지정 URL 입력</string>
|
||||
<string name="settings_zygisk_summary">zygote 데몬에서 Magisk 부분 실행</string>
|
||||
<string name="settings_denylist_title">DenyList 적용</string>
|
||||
<string name="settings_denylist_summary">DenyList의 프로세스는 Magisk의 모든 수정사항을 되돌립니다</string>
|
||||
<string name="settings_denylist_summary">DenyList에 있는 프로세스를 실행할때 Magisk의 모든 수정사항을 되돌리고 실행합니다.</string>
|
||||
<string name="settings_denylist_error">이 기능을 사용하려면 %1$s 활성화가 필요합니다</string>
|
||||
<string name="settings_denylist_config_title">DenyList 구성</string>
|
||||
<string name="settings_denylist_config_summary">denylist에 포함할 프로세스를 선택합니다.</string>
|
||||
<string name="settings_denylist_config_summary">Denylist에 포함할 프로세스를 선택합니다.</string>
|
||||
<string name="settings_hosts_title">Systemless hosts</string>
|
||||
<string name="settings_hosts_summary">광고 차단 앱에서 사용하는 systemless hosts를 지원합니다.</string>
|
||||
<string name="settings_hosts_toast">Systemless hosts 모듈 추가됨</string>
|
||||
@@ -160,9 +160,9 @@
|
||||
<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_reauth_summary">앱이 업데이트되면 권한 승인 요청을 다시 합니다.</string>
|
||||
<string name="settings_su_tapjack_title">탭재킹 보호 활성화</string>
|
||||
<string name="settings_su_tapjack_summary">슈퍼유저 프롬프트 대화 상자가 다른 창이나 오버레이로 가려지는 동안 입력에 응답하지 않습니다.</string>
|
||||
<string name="settings_su_tapjack_summary">슈퍼유저 프롬프트가 다른 창이나 오버레이로 가려지는 동안의 입력을 무시합니다.</string>
|
||||
<string name="settings_su_biometric_title">생체 인증 활성화</string>
|
||||
<string name="settings_su_biometric_summary">슈퍼유저 요청 허용에 생체 인증을 사용합니다.</string>
|
||||
<string name="no_biometric">지원되지 않는 기기이거나 등록된 생체 정보가 없습니다.</string>
|
||||
@@ -176,16 +176,16 @@
|
||||
<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="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>
|
||||
<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">Magisk 업데이트</string>
|
||||
@@ -194,7 +194,7 @@
|
||||
<string name="download_complete">다운로드 완료</string>
|
||||
<string name="download_file_error">파일 다운로드 오류</string>
|
||||
<string name="magisk_update_title">새 버전의 Magisk를 사용할 수 있습니다!</string>
|
||||
<string name="updated_title">업데이트 된 Magisk</string>
|
||||
<string name="updated_title">Magisk가 업데이트 되었습니다!</string>
|
||||
<string name="updated_text">터치하여 앱 열기</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
@@ -203,8 +203,8 @@
|
||||
<string name="repo_install_title">%1$s %2$s(%3$d) 설치</string>
|
||||
<string name="download">다운로드</string>
|
||||
<string name="reboot">다시 시작</string>
|
||||
<string name="release_notes">릴리즈 노트</string>
|
||||
<string name="flashing">플래싱 중…</string>
|
||||
<string name="release_notes">수정사항</string>
|
||||
<string name="flashing">설치 중…</string>
|
||||
<string name="done">완료!</string>
|
||||
<string name="failure">실패!</string>
|
||||
<string name="hide_app_title">Magisk 앱 숨기는 중…</string>
|
||||
@@ -213,22 +213,22 @@
|
||||
<string name="restore_img">이미지 복구</string>
|
||||
<string name="restore_img_msg">복구하는 중…</string>
|
||||
<string name="restore_done">복구 완료!</string>
|
||||
<string name="restore_fail">순정 백업이 존재하지 않습니다!</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_fix_msg">Magisk가 제대로 작동하려면 추가 설정이 필요합니다. 다시 시작 하시겠습니까?</string>
|
||||
<string name="setup_msg">환경 설정 진행 중…</string>
|
||||
<string name="authenticate">인증</string>
|
||||
<string name="unsupport_magisk_title">지원되지 않는 Magisk 버전</string>
|
||||
<string name="unsupport_magisk_msg">이 버전의 앱은 %1$s 미만의 Magisk 버전을 지원하지 않습니다.\n\n해당앱은 Magisk가 설치되지 않은것처럼 동작할것입니다. 가능한 빨리 Magisk 를 업데이트 하세요.</string>
|
||||
<string name="unsupport_general_title">비정상적인 상태</string>
|
||||
<string name="unsupport_system_app_msg">해당 앱을 시스템 앱으로 실행하는 것은 지원하지 않습니다. 앱을 일반사용자 앱으로 실행해 주세요.</string>
|
||||
<string name="unsupport_other_su_msg">Magisk 로 부터 설치되지 않은 \"su\" 바이너리가 감지되었습니다. 다른 루팅 솔루션을 제거하거나, Magisk 를 다시 설치해주세요.</string>
|
||||
<string name="unsupport_system_app_msg">해당 앱을 시스템 앱으로 실행하는 것은 지원되지 않습니다. 앱을 일반 사용자 앱으로 실행해 주세요.</string>
|
||||
<string name="unsupport_other_su_msg">Magisk으로 부터 설치되지 않은 \"su\" 바이너리가 감지되었습니다. 다른 루팅 방법을 제거하거나, Magisk 를 다시 설치해주세요.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk 가 외부 저장소에 설치되어 있습니다. Magisk 를 내부 저장소에 설치 해주세요.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">숨겨진 Magisk 앱은 루팅이 풀려 더이상 동작하지 않습니다. 원본 APK 를 복원하거나 재설치 해주세요.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">숨겨진 Magisk 앱은 루팅이 풀려 더이상 작동하지 못합니다. 본래 APK 를 복원하거나 재설치 해주세요.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">해당 기능을 사용하려면 저장소 권한을 허용해 주십시오.</string>
|
||||
<string name="install_unknown_denied">이 기능을 활성화 하려면 "알수 없는 앱 설치"를 허용해주세요.</string>
|
||||
<string name="install_unknown_denied">이 기능을 활성화 하려면 "알 수 없는 앱 설치"를 허용해주세요.</string>
|
||||
<string name="add_shortcut_title">홈 화면에 바로가기 추가</string>
|
||||
<string name="add_shortcut_msg">앱을 숨긴 후 아이콘과 이름을 알아보기 힘들 경우를 위해 알아보기 쉬운 바로가기를 홈 화면에 추가합니다.</string>
|
||||
<string name="app_not_found">해당 작업을 처리할 어플리케이션이 없습니다.</string>
|
||||
|
@@ -1,133 +1,244 @@
|
||||
<resources>
|
||||
|
||||
<!--Welcome Activity-->
|
||||
<string name="modules">Papildiniai</string>
|
||||
<string name="superuser">Supervartotojas</string>
|
||||
<string name="logs">Įvykių sąrašas</string>
|
||||
<!--Sections-->
|
||||
<string name="modules">Moduliai</string>
|
||||
<string name="superuser">Supernaudotojas</string>
|
||||
<string name="logs">Žurnalas</string>
|
||||
<string name="settings">Nustatymai</string>
|
||||
<string name="install">Instaliuoti</string>
|
||||
<string name="install">Įdiegti</string>
|
||||
<string name="section_home">Pagrindinis</string>
|
||||
<string name="section_theme">Temos</string>
|
||||
<string name="denylist">DenyList</string>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="invalid_update_channel">Neteisingas atnaujinimų nustatymas</string>
|
||||
<!--Home-->
|
||||
<string name="no_connection">Nėra ryšio</string>
|
||||
<string name="app_changelog">Keitimų sąrašas</string>
|
||||
<string name="loading">Įkeliama…</string>
|
||||
<string name="update">Naujinti</string>
|
||||
<string name="not_available">Neįdiegta</string>
|
||||
<string name="hide">Slėpti</string>
|
||||
<string name="home_package">Paketas</string>
|
||||
<string name="home_app_title">Programėlė</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="keep_force_encryption">Palikti priverstinį šifravimą</string>
|
||||
<string name="keep_dm_verity">Palikti dm-verity</string>
|
||||
<string name="home_notice_content">Magisk siųskites TIK iš oficialiojo GitHub puslapio. Failai iš nežinomų šaltinių gali būti kenkėjiški!</string>
|
||||
<string name="home_support_title">Palaikykite mus</string>
|
||||
<string name="home_follow_title">Sekite mus</string>
|
||||
<string name="home_item_source">Pirminis kodas</string>
|
||||
<string name="home_support_content">Magisk yra ir visada bus nemokamas, atvirojo kodo. Tačiau galite paaukoti pinigų, jei norite parodyti, kad jums rūpi.</string>
|
||||
<string name="home_installed_version">Įdiegta versija</string>
|
||||
<string name="home_latest_version">Naujausia versija</string>
|
||||
<string name="invalid_update_channel">Neteisingas naujinimų kanalas</string>
|
||||
<string name="uninstall_magisk_title">Pašalinti Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Visi papildiniai bus išjungti/pašalinti. Root bus panaikintas. Yra galimybė, kad duomenys bus užšifruoti…</string>
|
||||
<string name="update">Atnaujinti</string>
|
||||
<string name="uninstall_magisk_msg">Visi moduliai bus išjungti/pašalinti!\nRoot prieiga bus pašalinta!\nVidinė atmintis, iššifruota su Magisk, bus iš naujo užšifruota!</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">Neišjungti priverstinio šifravimo</string>
|
||||
<string name="keep_dm_verity">Neišjungti AVB 2.0/dm-verity</string>
|
||||
<string name="patch_vbmeta">Taisyti vbmeta boot vaizdą</string>
|
||||
<string name="recovery_mode">Atkūrimo režimas</string>
|
||||
<string name="install_options_title">Parinktys</string>
|
||||
<string name="install_method_title">Būdas</string>
|
||||
<string name="install_next">Toliau</string>
|
||||
<string name="install_start">Įdiegti</string>
|
||||
<string name="manager_download_install">Spustelėkite, kad atsisiųstumėte ir įdiegtumėte</string>
|
||||
<string name="direct_install">Tiesioginis įdiegimas (rekomenduojama)</string>
|
||||
<string name="install_inactive_slot">Įdiegti į antrąją vietą (po OTA naujinimo)</string>
|
||||
<string name="install_inactive_slot_msg">Jūsų įremginys bus PRIVERSTAS pasileisti į dabartinę neaktyvią vietą po paleidimo iš naujo!\nNaudokite šią parinktį, kai OTA naujinimas yra baigtas.\nTęsti?</string>
|
||||
<string name="setup_title">Išplėstinė sąranka</string>
|
||||
<string name="select_patch_file">Pasirinkite ir taisykite failą</string>
|
||||
<string name="patch_file_msg">Pasirinkite vaizdo failą (*.img) arba ODIN archyvą (*.tar)</string>
|
||||
<string name="reboot_delay_toast">Paleidžiama iš naujo po 5 sekundžių…</string>
|
||||
<string name="flash_screen_title">Įdiegimas</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Supernaudotojo užklausa</string>
|
||||
<string name="touch_filtered_warning">Programėlė užstoja supernaudotojo užklausą, todėl Magisk negali patvirtinti jūsų atsakymo</string>
|
||||
<string name="deny">Atmesti</string>
|
||||
<string name="prompt">Klausti</string>
|
||||
<string name="grant">Leisti</string>
|
||||
<string name="su_warning">Suteikia prieigą prie jūsų įrenginio.\nAtmeskite, jei nesate tikri!</string>
|
||||
<string name="forever">Amžinai</string>
|
||||
<string name="once">Kartą</string>
|
||||
<string name="tenmin">10 minučių</string>
|
||||
<string name="twentymin">20 minučių</string>
|
||||
<string name="thirtymin">30 minučių</string>
|
||||
<string name="sixtymin">60 minučių</string>
|
||||
<string name="su_allow_toast">%1$s suteiktos supernaudotojo teisės</string>
|
||||
<string name="su_deny_toast">%1$s atmestos supernaudotojo teisės</string>
|
||||
<string name="su_snack_grant">%1$s supernaudotojo teisės yra suteiktos</string>
|
||||
<string name="su_snack_deny">%1$s supernaudotojo teisės yra atmestos</string>
|
||||
<string name="su_snack_notif_on">%1$s pranešimai yra įjungti</string>
|
||||
<string name="su_snack_notif_off">%1$s pranešimai yra išjungti</string>
|
||||
<string name="su_snack_log_on">%1$s registravimas yra įjungtas</string>
|
||||
<string name="su_snack_log_off">%1$s registravimas yra išjungtas</string>
|
||||
<string name="su_revoke_title">Atšaukti?</string>
|
||||
<string name="su_revoke_msg">Patvirtinkite %1$s supernaudotojo teisių atšaukimą</string>
|
||||
<string name="toast">Iššokantys pranešimai</string>
|
||||
<string name="none">Nėra</string>
|
||||
|
||||
<string name="superuser_toggle_notification">Pranešimai</string>
|
||||
<string name="superuser_toggle_revoke">Atšaukti</string>
|
||||
<string name="superuser_policy_none">Kol kas jokia programėlė neprašė supernaudotojo teisių.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">Žurnalas tuščias</string>
|
||||
<string name="log_data_magisk_none">Magisk žurnalas yra tuščias, keistoka</string>
|
||||
<string name="menuSaveLog">Išsaugoti žurnalą</string>
|
||||
<string name="menuClearLog">Valyti žurnalą</string>
|
||||
<string name="logs_cleared">Žurnalas išvalytas</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Tikslinis UID: %1$d</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Rodyti sistemos programėles</string>
|
||||
<string name="show_os_app">Rodyti OS programėles</string>
|
||||
<string name="hide_filter_hint">Filtruoti pagal pavadinimą</string>
|
||||
<string name="hide_search">Ieškoti</string>
|
||||
|
||||
<!--Module-->
|
||||
<string name="no_info_provided">(Nėra informacijos)</string>
|
||||
<string name="reboot_recovery">Perkrauti į Recovery</string>
|
||||
<string name="reboot_bootloader">Perkrauti į Bootloader</string>
|
||||
<string name="reboot_download">Perkrauti į Download režimą</string>
|
||||
<string name="reboot_userspace">Paleidimas neišjungus</string>
|
||||
<string name="reboot_recovery">Paleidimas į Recovery</string>
|
||||
<string name="reboot_bootloader">Paleidimas į Bootloader</string>
|
||||
<string name="reboot_download">Paleidimas į Download</string>
|
||||
<string name="reboot_edl">Paleidimas į EDL</string>
|
||||
<string name="module_version_author">%1$s nuo %2$s</string>
|
||||
<string name="module_state_remove">Šalinti</string>
|
||||
<string name="module_state_restore">Atkurti</string>
|
||||
<string name="module_action_install_external">Įdiegti iš saugyklos</string>
|
||||
<string name="update_available">Prieinamas naujinimas</string>
|
||||
<string name="suspend_text_riru">Modulis išjungtas, nes %1$s yra įjungtas</string>
|
||||
<string name="suspend_text_zygisk">Modulis išjungtas, nes %1$s nėra įjungtas</string>
|
||||
<string name="zygisk_module_unloaded">Zygisk moduis neįkeltas dėl nesuderinamumo</string>
|
||||
<string name="module_empty">Modulių nėra</string>
|
||||
<string name="confirm_install">Įdiegti modulį %1$s?</string>
|
||||
<string name="confirm_install_title">Įdiegimo patvirtinimas</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Galimas atnaujinimas</string>
|
||||
<string name="home_installed_version">Instaliuota</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Išsaugoti įvykių sąrašą</string>
|
||||
<string name="menuClearLog">Išvalyti įvykių sąrašą</string>
|
||||
<string name="logs_cleared">Įvykių sąrašas pašalintas</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<string name="app_changelog">Pakeitimų sąrašas</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">Instaliuoti %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Atsisiųsti</string>
|
||||
<string name="reboot">Perkrauti</string>
|
||||
<string name="magisk_update_title">Atsirado nauja Magisk versija!</string>
|
||||
<string name="release_notes">Pakeitimai</string>
|
||||
<string name="manager_download_install">Paspauskite, kad atsisiųstumėte ir instaliuotumėte</string>
|
||||
<string name="update_channel">Magisk Atnaujinimai</string>
|
||||
<string name="flashing">Instaliuojama</string>
|
||||
<string name="direct_install">Tiesioginis instaliavimas (Rekomenduojamas)</string>
|
||||
<string name="complete_uninstall">Pilnas pašalinimas</string>
|
||||
<string name="restore_img">Atstatyti boot failą</string>
|
||||
<string name="restore_img_msg">Atstatome…</string>
|
||||
<string name="restore_done">Atstatymas įvykdytas!</string>
|
||||
<string name="restore_fail">Gamyklinis atstatymo failas neegzistuoja!</string>
|
||||
<string name="setup_fail">Pasiruošimas nesėkmingas</string>
|
||||
<string name="env_fix_title">Reikalingas papildomas pasiruošimas</string>
|
||||
<string name="setup_title">Papildomas pasiruošimas</string>
|
||||
<string name="setup_msg">Paruošiama aplinka…</string>
|
||||
<string name="download_file_error">Atsisiunčiant failą įvyko klaida</string>
|
||||
|
||||
<!--Settings Activity -->
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Temos režimas</string>
|
||||
<string name="settings_dark_mode_message">Pasirinkite režimą, kuris geriausiai atitinka jūsų stilių!</string>
|
||||
<string name="settings_dark_mode_light">Visada šviesi</string>
|
||||
<string name="settings_dark_mode_system">Sistemos</string>
|
||||
<string name="settings_dark_mode_dark">Visada tamsi</string>
|
||||
<string name="settings_download_path_title">Atsisiuntimo kelias</string>
|
||||
<string name="settings_download_path_message">Failai bus išsaugoti %1$s</string>
|
||||
<string name="settings_hide_app_title">Slėpti Magisk programėlę</string>
|
||||
<string name="settings_hide_app_summary">Įdiegti įgaliotąją programėlę su atsitiktiniu paketo ID ir kitu pavadinimu</string>
|
||||
<string name="settings_restore_app_title">Atkurti Magisk programėlę</string>
|
||||
<string name="settings_restore_app_summary">Atkurti programėlę ir originalų APK</string>
|
||||
<string name="language">Kalba</string>
|
||||
<string name="system_default">(Sistemos)</string>
|
||||
<string name="settings_check_update_title">Automatiškas atnaujinimų ieškojimas</string>
|
||||
<string name="settings_check_update_summary">Automatiškai ieškoti Magisk ir Magisk Manager atnaujinimų</string>
|
||||
<string name="settings_update_channel_title">Atnaujinimų tipai</string>
|
||||
<string name="settings_update_stable">Stabilūs</string>
|
||||
<string name="system_default">(Sistemos numatytoji)</string>
|
||||
<string name="settings_check_update_title">Naujinimų tikrinimas</string>
|
||||
<string name="settings_check_update_summary">Periodiškai fone tikrinti, ar nėra naujinimų</string>
|
||||
<string name="settings_update_channel_title">Naujinimų kanalas</string>
|
||||
<string name="settings_update_stable">Stabilus</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Pasirinktiniai</string>
|
||||
<string name="settings_update_custom_msg">Įvesti pasirinktinį URL</string>
|
||||
<string name="settings_hosts_title">Sistemos padejėjai</string>
|
||||
<string name="settings_hosts_summary">Įgalinti sistemos padejėjus Adblock programėlėms</string>
|
||||
|
||||
<string name="settings_su_app_adb">Programėlėms ir ADB</string>
|
||||
<string name="settings_su_app">Tik programėlėms</string>
|
||||
<string name="settings_update_custom">Tinkintas</string>
|
||||
<string name="settings_update_custom_msg">Įdėkite tinkinto kanalo URL</string>
|
||||
<string name="settings_zygisk_summary">Vykdyti Magisk dalis zygote tarnyboje</string>
|
||||
<string name="settings_denylist_title">Aktyvinti DenyList</string>
|
||||
<string name="settings_denylist_summary">Visos procesų, esančių denylist, Magisk modifikacijos bus atkurtos</string>
|
||||
<string name="settings_denylist_error">Šiai funkcijai reikia, kad būtų įjungta %1$s</string>
|
||||
<string name="settings_denylist_config_title">Konfigūruoti DenyList</string>
|
||||
<string name="settings_denylist_config_summary">Pasirinkite procesus, kuriuos norite įtraukti į denylist</string>
|
||||
<string name="settings_hosts_title">Basisteminis hosts</string>
|
||||
<string name="settings_hosts_summary">Basisteminis hosts palaiko reklamų blokatorių programėlėms</string>
|
||||
<string name="settings_hosts_toast">Pridėtas besisteminio hosts modulis</string>
|
||||
<string name="settings_app_name_hint">Naujas pavadinimas</string>
|
||||
<string name="settings_app_name_helper">Programėlė bus perpakuota su šiuo pavadinimu</string>
|
||||
<string name="settings_app_name_error">Netinkamas formatas</string>
|
||||
<string name="settings_su_app_adb">Programėlės ir ADB</string>
|
||||
<string name="settings_su_app">Tik programėlės</string>
|
||||
<string name="settings_su_adb">Tik ADB</string>
|
||||
<string name="settings_su_disable">Išjungta</string>
|
||||
<string name="settings_su_request_10">10 sekundžių</string>
|
||||
<string name="settings_su_request_15">15 sekundžių</string>
|
||||
<string name="settings_su_request_20">20 sekundžių</string>
|
||||
<string name="settings_su_request_30">30 sekundžių</string>
|
||||
<string name="settings_su_request_45">45 sekundžių</string>
|
||||
<string name="settings_su_request_45">45 sekundės</string>
|
||||
<string name="settings_su_request_60">60 sekundžių</string>
|
||||
<string name="superuser_access">Supervartotojo prieiga</string>
|
||||
<string name="superuser_access">Prieigos lygis</string>
|
||||
<string name="auto_response">Automatinis atsakymas</string>
|
||||
<string name="request_timeout">Prašymo laikas baigiasi po</string>
|
||||
<string name="superuser_notification">Supervartotojo pranešimai</string>
|
||||
<string name="settings_su_reauth_title">Pakartotinai patvirtinti po atnaujinimo</string>
|
||||
<string name="settings_su_reauth_summary">Pakartotinai patvirtinti supervartotojo leidimus po programėlės atnaujinimo</string>
|
||||
<string name="request_timeout">Atsakymo laukimas</string>
|
||||
<string name="superuser_notification">Supernaudotojo pranešimas</string>
|
||||
<string name="settings_su_reauth_title">Pakartotinis autentifikavimas</string>
|
||||
<string name="settings_su_reauth_summary">Iš naujo prašyti supernaudotojo teisių po programėlių naujinimo</string>
|
||||
<string name="settings_su_tapjack_title">Bakstelėjimų perėmimo apsauga</string>
|
||||
<string name="settings_su_tapjack_summary">Supernaudotojo užklausos langas bus neaktyvus, kol jis yra užstotas kito lango</string>
|
||||
<string name="settings_su_biometric_title">Biometrinis autentifikavimas</string>
|
||||
<string name="settings_su_biometric_summary">Naudoti biometrinį autentifikavimą supernaudotojo užklausų patvirtinimui</string>
|
||||
<string name="no_biometric">Nepalaikomas įrenginys arba biometriniai nustatymai neįjungti</string>
|
||||
<string name="settings_customization">Tinkinimas</string>
|
||||
<string name="setting_add_shortcut_summary">Pridėti nuorodą į pagrindinį ekraną, jei po programėlės paslėpimo yra sunku atpažinti jos pavadinimą ir piktogramą</string>
|
||||
<string name="settings_doh_title">DNS virš HTTPS</string>
|
||||
<string name="settings_doh_description">Aktyvinti DoH (naudokite, jei kyla problemų jungiantis prie tinklo</string>
|
||||
|
||||
<string name="multiuser_mode">Daugialypio vartotojo režimas</string>
|
||||
<string name="multiuser_mode">Daugelio naudotojų režimas</string>
|
||||
<string name="settings_owner_only">Tik įrenginio savininkas</string>
|
||||
<string name="settings_owner_manage">Įrenginio savininko valdomas</string>
|
||||
<string name="settings_user_independent">Visų</string>
|
||||
<string name="owner_only_summary">Tik įrenginio savininkas turi root prieigą</string>
|
||||
<string name="owner_manage_summary">Tik įrenginio savinkas gali tvarkyti root prieigą ir gauti lenteles prašančias root prieigos</string>
|
||||
<string name="user_independent_summary">Kiekvienas vartotojas turi savo atskiras root taisykles</string>
|
||||
<string name="settings_owner_manage">Valdoma įrenginio savininko</string>
|
||||
<string name="settings_user_independent">Naudotojų taisyklės</string>
|
||||
<string name="owner_only_summary">Tik savininkas turi root prieigą</string>
|
||||
<string name="owner_manage_summary">Tik savininkas gali tvarkyti root prieigą ir gauti užklausas</string>
|
||||
<string name="user_independent_summary">Kiekvienas naudotojas turi savo atskiras root taisykles</string>
|
||||
|
||||
<string name="mount_namespace_mode">Root sesijos vardų srities režimas</string>
|
||||
<string name="settings_ns_global">Globali vardų sritis</string>
|
||||
<string name="settings_ns_requester">Paveldima vardų sritis</string>
|
||||
<string name="settings_ns_isolate">Izoliuota vardų sritis</string>
|
||||
<string name="global_summary">Visos root sesijos naudoja globalią vardų sritį</string>
|
||||
<string name="requester_summary">Root sesijos paveldi jos išprašytojo/s vardų sritį</string>
|
||||
<string name="isolate_summary">Kiekviena root sesija turi savo izoliuotą vardų sritį</string>
|
||||
<string name="mount_namespace_mode">Pavadinimų erdvės režimas</string>
|
||||
<string name="settings_ns_global">Bendra pavadinimų erdvė</string>
|
||||
<string name="settings_ns_requester">Paveldima pavadinimų erdvė</string>
|
||||
<string name="settings_ns_isolate">Izoliuota pavadinimų erdvė</string>
|
||||
<string name="global_summary">Visos root sesijos naudos bendrą pavainimų erdvę</string>
|
||||
<string name="requester_summary">Root sesijos paveldės prašytojų pavadinimų erdvę</string>
|
||||
<string name="isolate_summary">Kiekviena root sesija turės savo izoliuotą pavadinimų erdvę</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Supervartotojo prašymas</string>
|
||||
<string name="deny">Atmesti</string>
|
||||
<string name="prompt">Klausti</string>
|
||||
<string name="grant">Suteikti(a)</string>
|
||||
<string name="su_warning">Supervartotojo prieiga suteikia pilną prieigą prie jūsų įrenginio\nAtmeskite jei neesate tikri dėl programėlės patikimumo!</string>
|
||||
<string name="forever">Visados</string>
|
||||
<string name="once">Vieną kartą</string>
|
||||
<string name="tenmin">10 min</string>
|
||||
<string name="twentymin">20 min</string>
|
||||
<string name="thirtymin">30 min</string>
|
||||
<string name="sixtymin">60 min</string>
|
||||
<string name="su_allow_toast">%1$s gavo supervartotojo teises</string>
|
||||
<string name="su_deny_toast">%1$s negavo supervartotojo teisių</string>
|
||||
<string name="su_snack_grant">%1$s supervartotojo teisių prašymas buvo atsakytas teigiamai</string>
|
||||
<string name="su_snack_deny">%1$s supervartotojo teisių prašymas buvo atmestas</string>
|
||||
<string name="su_snack_notif_on">Buvo įjungta %1$s pranešimai</string>
|
||||
<string name="su_snack_notif_off">Buvo išjungta %1$s pranešimai</string>
|
||||
<string name="su_snack_log_on">%1$s įvykių surašymas yra įgalintas</string>
|
||||
<string name="su_snack_log_off">%1$s įvykių surašymas yra išjungtas</string>
|
||||
<string name="su_revoke_title">Neleisti?</string>
|
||||
<string name="su_revoke_msg">Neleisti %1$s naudotis supervartotojo teisėmis?</string>
|
||||
<string name="toast">Išmesti</string>
|
||||
<string name="none">Nėra</string>
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Magisk naujinimai</string>
|
||||
<string name="progress_channel">Progreso pranešimai</string>
|
||||
<string name="updated_channel">Naujinimas baigtas</string>
|
||||
<string name="download_complete">Atsisiuntimas baigtas</string>
|
||||
<string name="download_file_error">Klaida atsisiunčiant failą</string>
|
||||
<string name="magisk_update_title">Prieinamas Magisk naujinimas!</string>
|
||||
<string name="updated_title">Magisk atnaujintas</string>
|
||||
<string name="updated_text">Bakstelėkite, kad atidarytumėte programėlę</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="target_uid">Target UID: %1$d</string>
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Taip</string>
|
||||
<string name="no">Ne</string>
|
||||
<string name="repo_install_title">Įdiegimas %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Atsisiųsti</string>
|
||||
<string name="reboot">Paleisti iš naujo</string>
|
||||
<string name="release_notes">Laidos informacija</string>
|
||||
<string name="flashing">Diegiama…</string>
|
||||
<string name="done">Baigta!</string>
|
||||
<string name="failure">Nepavyko!</string>
|
||||
<string name="hide_app_title">Slepiama Magisk programėlė…</string>
|
||||
<string name="open_link_failed_toast">Nerasta programėlių nuorodos atidarymui</string>
|
||||
<string name="complete_uninstall">Visiškas pašalinimas</string>
|
||||
<string name="restore_img">Atkurti vaizdą</string>
|
||||
<string name="restore_img_msg">Atkuriama…</string>
|
||||
<string name="restore_done">Atkūrimas baigtas!</string>
|
||||
<string name="restore_fail">Nėra atsarginės kopijos!</string>
|
||||
<string name="setup_fail">Sąranka nepavyko</string>
|
||||
<string name="env_fix_title">Reikalinga išplėstinė sąranka</string>
|
||||
<string name="env_fix_msg">Jūsų įrenginiui reikalinga išplėstinė sąranka, kad Magisk veiktų tinkamai. Norite tęsti ir įrenginį paleisti iš naujo?</string>
|
||||
<string name="env_full_fix_msg">Jūsų įrenginiui reikia iš naujo įdiegti Magisk, kad jis veiktų tinkamai. Iš naujo įdiekite Magisk su programėle, atkūrimo režimas negali gauti teisingos įrenginio informacijos.</string>
|
||||
<string name="setup_msg">Vykdoma Running aplinkos sąranka…</string>
|
||||
<string name="authenticate">Autentifikavimas</string>
|
||||
<string name="unsupport_magisk_title">Nepalaikoma Magisk versija</string>
|
||||
<string name="unsupport_magisk_msg">Ši programėlės versija nepalaiko Magisk versijų, žemesnių už %1$s.\n\nProgramėlė veiks taip, lyg Magisk yra neįdiegtas. Kuo greičiau atnaujinkite Magisk.</string>
|
||||
<string name="unsupport_general_title">Nenormali būsena</string>
|
||||
<string name="unsupport_system_app_msg">Šios programėlės vykdymas kaip sistemos programėlės yra nepalaikomas. Nustatykite ją atgal į naudotojo programėlę.</string>
|
||||
<string name="unsupport_other_su_msg">Aptiktas dvejetainis failas \"su\" ne iš Magisk. Pašalinkite pašalinį root teisių tiekėją ir/arba iš naujo įdiekite Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk yra įdiegtas išorinėje saugykloje. Perkelkite programėlę į vidinę saugyklą.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Paslėpta Magisk programėlė negali tęsti darbo, nes root teisės buvo prarastos. Atkurkite originalų APk.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Suteikti prieigą prie saugyklos</string>
|
||||
<string name="post_notifications_denied">Suteikti prieigą prie pranešimų</string>
|
||||
<string name="install_unknown_denied">Suteikti prieigą prie įdiegimo iš nežinomų šaltinių</string>
|
||||
<string name="add_shortcut_title">Pridėti nuorodą į pagrindinį ekraną</string>
|
||||
<string name="add_shortcut_msg">Po šios programėlės paslėpimo jos pavadinimas, piktograma gali būti sunkiai atpažįstamos. Norite pridėti nuorodą į pagrindinį ekraną?</string>
|
||||
<string name="app_not_found">Nerasta programėlė, galinti atlikti šį veiksmą</string>
|
||||
<string name="reboot_apply_change">Paleiskite iš naujo, kad pritaikytumėte pakeitimus</string>
|
||||
<string name="restore_app_confirmation">Šis veiksmas atkurs paslėptą programėlę į originalią būseną. Tikrai norite tai padaryti?</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -45,7 +45,7 @@
|
||||
<string name="install_inactive_slot_msg">Seu dispositivo será FORÇADO a inicializar no slot inativo atual após uma reinicialização!\nSó use esta opção após a conclusão do OTA.\nDeseja continuar?</string>
|
||||
<string name="setup_title">Configuração adicional</string>
|
||||
<string name="select_patch_file">Selecione e corrija um arquivo</string>
|
||||
<string name="patch_file_msg">Selecione um arquivo imagem (*.img) ou um arquivo tar (*.tar)</string>
|
||||
<string name="patch_file_msg">Selecione um arquivo imagem (*.img) ou um arquivo tar (*.tar) ou um arquivo payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Reiniciando em 5 segundos…</string>
|
||||
<string name="flash_screen_title">Instalação</string>
|
||||
|
||||
@@ -86,6 +86,9 @@
|
||||
<string name="logs_cleared">Registro limpo com sucesso.</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Alvo UID: %1$d</string>
|
||||
<string name="target_pid">Alvo PID: %s</string>
|
||||
<string name="selinux_context">Contexto SELinux: %s</string>
|
||||
<string name="supp_group">Grupo suplementar: %s</string>
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Mostrar apps do Sistema</string>
|
||||
@@ -109,6 +112,8 @@
|
||||
<string name="suspend_text_zygisk">Módulo suspenso porque %1$s não está ativo</string>
|
||||
<string name="zygisk_module_unloaded">Modulo Zygisk não carregado devido a incompatibilidade</string>
|
||||
<string name="module_empty">Nenhum módulo instalado</string>
|
||||
<string name="confirm_install">Instalar módulo %1$s?</string>
|
||||
<string name="confirm_install_title">Confirmação de Instalação</string>
|
||||
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Modo de tema</string>
|
||||
@@ -213,6 +218,7 @@
|
||||
<string name="setup_fail">Falha na instalação</string>
|
||||
<string name="env_fix_title">Configuração adicional exigida</string>
|
||||
<string name="env_fix_msg">Seu dispositivo exige uma configuração adicional para o Magisk funcionar corretamente. Deseja continuar e reiniciar?</string>
|
||||
<string name="env_full_fix_msg">Seu dispositivo precisa refazer o flash do Magisk para funcionar corretamente. Reinstale o Magisk no aplicativo, o modo de recuperação não pode obter as devidas informações do dispositivo.</string>
|
||||
<string name="setup_msg">Executando a configuração do ambiente…</string>
|
||||
<string name="authenticate">Autenticar</string>
|
||||
<string name="unsupport_magisk_title">Versão do Magisk não suportada</string>
|
||||
|
@@ -45,7 +45,7 @@
|
||||
<string name="install_inactive_slot_msg">Seu dispositivo será FORÇADO a inicializar no slot inativo atual após uma reinicialização!\nSó use esta opção após a conclusão do OTA.\nDeseja continuar?</string>
|
||||
<string name="setup_title">Configuração adicional</string>
|
||||
<string name="select_patch_file">Selecione e corrija um arquivo</string>
|
||||
<string name="patch_file_msg">Selecione um arquivo imagem (*.img) ou um arquivo tar (*.tar)</string>
|
||||
<string name="patch_file_msg">Selecione um arquivo imagem (*.img) ou um arquivo tar (*.tar) ou um arquivo payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Reiniciando em 5 segundos…</string>
|
||||
<string name="flash_screen_title">Instalação</string>
|
||||
|
||||
@@ -86,6 +86,9 @@
|
||||
<string name="logs_cleared">Registro limpo com sucesso.</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Alvo UID: %1$d</string>
|
||||
<string name="target_pid">Alvo PID: %s</string>
|
||||
<string name="selinux_context">Contexto SELinux: %s</string>
|
||||
<string name="supp_group">Grupo suplementar: %s</string>
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Mostrar apps do Sistema</string>
|
||||
@@ -109,6 +112,8 @@
|
||||
<string name="suspend_text_zygisk">Módulo suspenso porque %1$s não está ativo</string>
|
||||
<string name="zygisk_module_unloaded">Modulo Zygisk não carregado devido a incompatibilidade</string>
|
||||
<string name="module_empty">Nenhum módulo instalado</string>
|
||||
<string name="confirm_install">Instalar módulo %1$s?</string>
|
||||
<string name="confirm_install_title">Confirmação de Instalação</string>
|
||||
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Modo de tema</string>
|
||||
@@ -213,6 +218,7 @@
|
||||
<string name="setup_fail">Falha na instalação</string>
|
||||
<string name="env_fix_title">Configuração adicional exigida</string>
|
||||
<string name="env_fix_msg">Seu dispositivo exige uma configuração adicional para o Magisk funcionar corretamente. Deseja continuar e reiniciar?</string>
|
||||
<string name="env_full_fix_msg">Seu dispositivo precisa refazer o flash do Magisk para funcionar corretamente. Reinstale o Magisk no aplicativo, o modo de recuperação não pode obter as devidas informações do dispositivo.</string>
|
||||
<string name="setup_msg">Executando a configuração do ambiente…</string>
|
||||
<string name="authenticate">Autenticar</string>
|
||||
<string name="unsupport_magisk_title">Versão do Magisk não suportada</string>
|
||||
|
@@ -46,7 +46,7 @@
|
||||
<string name="install_inactive_slot_msg">Vaše zariadenie bude po reštarte PRINÚTENÉ nabootovať do aktuálne neaktívneho slotu!\nTúto voľbu použite iba po skončení OTA.\nPokračovať?</string>
|
||||
<string name="setup_title">Ďalšie nastavenia</string>
|
||||
<string name="select_patch_file">Vybrať a zaplátať súbor</string>
|
||||
<string name="patch_file_msg">Vyberte raw súbor (*.img) alebo tar súbor ODIN (*.tar)</string>
|
||||
<string name="patch_file_msg">Vyberte raw súbor (*.img) alebo tar súbor ODIN (*.tar) alebo payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Reštart o 5 sekúnd…</string>
|
||||
<string name="flash_screen_title">Inštalácia</string>
|
||||
|
||||
@@ -87,7 +87,10 @@
|
||||
<string name="menuClearLog">Odstrániť záznam</string>
|
||||
<string name="logs_cleared">Záznam úspešne odstránený</string>
|
||||
<string name="pid">PID:%1$d</string>
|
||||
<string name="target_uid">Cieľový UID: %1$d</string>
|
||||
<string name="target_uid">Cieľové UID: %1$d</string>
|
||||
<string name="target_pid">Pripojiť ns cieľový PID: %s</string>
|
||||
<string name="selinux_context">SELinux kontext: %s</string>
|
||||
<string name="supp_group">Doplnková skupina: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
|
@@ -46,7 +46,7 @@
|
||||
<string name="install_inactive_slot_msg">Pajisja juaj do të detyrohet të fillojë në folenë aktuale joaktive pas një rindezje!\nPërdoreni këtë opsion vetëm pasi të keni përfunduar OTA.\nVazhdo?</string>
|
||||
<string name="setup_title">Konfigurimet shtesë</string>
|
||||
<string name="select_patch_file">Zgjidhni dhe Patch një skader</string>
|
||||
<string name="patch_file_msg">Zgjidhni një imazh të papërpunuar (*.img) ose një ODIN tar skader (*.tar)</string>
|
||||
<string name="patch_file_msg">Zgjidhni një imazh të papërpunuar (*.img) ose një skedar ODIN (*.tar) ose një payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Rinisje pas 5 sekondash…</string>
|
||||
<string name="flash_screen_title">Instalimi</string>
|
||||
|
||||
@@ -88,6 +88,9 @@
|
||||
<string name="logs_cleared">Regjistrat u pastuan me sukses</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Target UID: %1$d</string>
|
||||
<string name="target_pid">Montoni PID të synuar ns: %s</string>
|
||||
<string name="selinux_context">Konteksti SELinux: %s</string>
|
||||
<string name="supp_group">Grupi suplementar: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
|
@@ -26,7 +26,7 @@
|
||||
<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 bağış yaparak bize değer verdiğinizi gösterebilirsiniz.</string>
|
||||
<string name="home_installed_version">Yüklendi</string>
|
||||
<string name="home_latest_version">En sonuncu</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ın</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>
|
||||
@@ -137,7 +137,7 @@
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Özel</string>
|
||||
<string name="settings_update_custom_msg">Özel bir kanal URL\'si ekleyin</string>
|
||||
<string name="settings_zygisk_summary">Zygote arka plan programında Magisk\'in bazı bölümlerini çalıştırın</string>
|
||||
<string name="settings_zygisk_summary">Zygisk arka plan programında Magisk\'in bazı bölümlerini çalıştırın</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_error">Bu özelliğin etkinleştirilmesi için %1$s gerekir</string>
|
||||
@@ -165,7 +165,7 @@
|
||||
<string name="superuser_notification">Süper Kullanıcı Bildirimi</string>
|
||||
<string name="settings_su_reauth_title">Yükseltmeden sonra yeniden kimlik doğrulaması yapın</string>
|
||||
<string name="settings_su_reauth_summary">Uygulamaları yükselttikten sonra Süper Kullanıcı izinlerini tekrar isteyin</string>
|
||||
<string name="settings_su_tapjack_title">Tapjacking Koruması</string>
|
||||
<string name="settings_su_tapjack_title">Sahte Ekran 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_biometric_title">Biyometrik Kimlik Doğrulama</string>
|
||||
<string name="settings_su_biometric_summary">Süper Kullanıcı isteklerine izin vermek için biyometrik kimlik doğrulamayı kullanın</string>
|
||||
|
@@ -46,7 +46,7 @@
|
||||
<string name="install_inactive_slot_msg">将在重启后强制切换到另一个槽位!注意只能在 OTA 更新完成后的重启之前使用。</string>
|
||||
<string name="setup_title">修复安装</string>
|
||||
<string name="select_patch_file">选择并修补一个文件</string>
|
||||
<string name="patch_file_msg">选择一个原始映像文件(*.img)或一个 Odin 包(*.tar)</string>
|
||||
<string name="patch_file_msg">选择一个原始映像文件(*.img)、Odin 包(*.tar)或 payload.bin(*.bin)</string>
|
||||
<string name="reboot_delay_toast">设备将在 5 秒后重启</string>
|
||||
<string name="flash_screen_title">安装</string>
|
||||
|
||||
@@ -88,6 +88,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-->
|
||||
|
||||
|
@@ -46,7 +46,7 @@
|
||||
<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 的 TAR 檔案 (*.tar) 或 payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">將在 5 秒後重新啟動……</string>
|
||||
<string name="flash_screen_title">安裝</string>
|
||||
|
||||
|
@@ -46,7 +46,7 @@
|
||||
<string name="install_inactive_slot_msg">Your device will be FORCED to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue?</string>
|
||||
<string name="setup_title">Additional Setup</string>
|
||||
<string name="select_patch_file">Select and Patch a File</string>
|
||||
<string name="patch_file_msg">Select a raw image (*.img) or an ODIN tarfile (*.tar)</string>
|
||||
<string name="patch_file_msg">Select a raw image (*.img) or an ODIN tarfile (*.tar) or a payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Rebooting in 5 seconds…</string>
|
||||
<string name="flash_screen_title">Installation</string>
|
||||
|
||||
@@ -88,6 +88,9 @@
|
||||
<string name="logs_cleared">Log successfully cleared</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Target 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-->
|
||||
|
||||
|
664
build.py
664
build.py
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import errno
|
||||
import glob
|
||||
import lzma
|
||||
import multiprocessing
|
||||
import os
|
||||
@@ -10,24 +10,27 @@ import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import textwrap
|
||||
import urllib.request
|
||||
import tarfile
|
||||
from zipfile import ZipFile
|
||||
|
||||
|
||||
def color_print(code, str):
|
||||
if no_color:
|
||||
print(str)
|
||||
else:
|
||||
str = str.replace("\n", f"\033[0m\n{code}")
|
||||
print(f"{code}{str}\033[0m")
|
||||
|
||||
|
||||
def error(str):
|
||||
if no_color:
|
||||
print(f'\n! {str}\n')
|
||||
else:
|
||||
print(f'\n\033[41m{str}\033[0m\n')
|
||||
color_print("\033[41;39m", f"\n! {str}\n")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def header(str):
|
||||
if no_color:
|
||||
print(f'\n{str}\n')
|
||||
else:
|
||||
print(f'\n\033[44m{str}\033[0m\n')
|
||||
color_print("\033[44;39m", f"\n{str}\n")
|
||||
|
||||
|
||||
def vprint(str):
|
||||
@@ -35,13 +38,14 @@ def vprint(str):
|
||||
print(str)
|
||||
|
||||
|
||||
is_windows = os.name == 'nt'
|
||||
EXE_EXT = '.exe' if is_windows else ''
|
||||
is_windows = os.name == "nt"
|
||||
EXE_EXT = ".exe" if is_windows else ""
|
||||
|
||||
no_color = 'CI' in os.environ and os.environ['CI'] == 'true'
|
||||
if not no_color and is_windows:
|
||||
no_color = False
|
||||
if is_windows:
|
||||
try:
|
||||
import colorama
|
||||
|
||||
colorama.init()
|
||||
except ImportError:
|
||||
# We can't do ANSI color codes in terminal on Windows without colorama
|
||||
@@ -49,33 +53,42 @@ if not no_color and is_windows:
|
||||
|
||||
# Environment checks
|
||||
if not sys.version_info >= (3, 8):
|
||||
error('Requires Python 3.8+')
|
||||
error("Requires Python 3.8+")
|
||||
|
||||
if 'ANDROID_SDK_ROOT' not in os.environ:
|
||||
error('Please add Android SDK path to ANDROID_SDK_ROOT environment variable!')
|
||||
if "ANDROID_SDK_ROOT" not in os.environ:
|
||||
error("Please set Android SDK path to environment variable ANDROID_SDK_ROOT!")
|
||||
|
||||
try:
|
||||
subprocess.run(['javac', '-version'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except FileNotFoundError:
|
||||
error('Please install JDK and make sure \'javac\' is available in PATH')
|
||||
if shutil.which("sccache") is not None:
|
||||
os.environ["RUSTC_WRAPPER"] = "sccache"
|
||||
os.environ["NDK_CCACHE"] = "sccache"
|
||||
os.environ["CARGO_INCREMENTAL"] = "0"
|
||||
|
||||
cpu_count = multiprocessing.cpu_count()
|
||||
archs = ['armeabi-v7a', 'x86', 'arm64-v8a', 'x86_64']
|
||||
triples = ['armv7a-linux-androideabi', 'i686-linux-android', 'aarch64-linux-android', 'x86_64-linux-android']
|
||||
default_targets = ['magisk', 'magiskinit', 'magiskboot', 'magiskpolicy', 'busybox']
|
||||
support_targets = default_targets + ['resetprop']
|
||||
rust_targets = ['magisk', 'magiskinit', 'magiskboot', 'magiskpolicy']
|
||||
os_name = platform.system().lower()
|
||||
|
||||
sdk_path = os.environ['ANDROID_SDK_ROOT']
|
||||
ndk_root = op.join(sdk_path, 'ndk')
|
||||
ndk_path = op.join(ndk_root, 'magisk')
|
||||
ndk_build = op.join(ndk_path, 'ndk-build')
|
||||
rust_bin = op.join(ndk_path, 'toolchains', 'rust', 'bin')
|
||||
cargo = op.join(rust_bin, 'cargo' + EXE_EXT)
|
||||
gradlew = op.join('.', 'gradlew' + ('.bat' if is_windows else ''))
|
||||
adb_path = op.join(sdk_path, 'platform-tools', 'adb' + EXE_EXT)
|
||||
native_gen_path = op.realpath(op.join('native', 'out', 'generated'))
|
||||
archs = ["armeabi-v7a", "x86", "arm64-v8a", "x86_64"]
|
||||
triples = [
|
||||
"armv7a-linux-androideabi",
|
||||
"i686-linux-android",
|
||||
"aarch64-linux-android",
|
||||
"x86_64-linux-android",
|
||||
]
|
||||
default_targets = ["magisk", "magiskinit", "magiskboot", "magiskpolicy", "busybox"]
|
||||
support_targets = default_targets + ["resetprop"]
|
||||
rust_targets = ["magisk", "magiskinit", "magiskboot", "magiskpolicy"]
|
||||
|
||||
sdk_path = os.environ["ANDROID_SDK_ROOT"]
|
||||
ndk_root = op.join(sdk_path, "ndk")
|
||||
ndk_path = op.join(ndk_root, "magisk")
|
||||
ndk_build = op.join(ndk_path, "ndk-build")
|
||||
rust_bin = op.join(ndk_path, "toolchains", "rust", "bin")
|
||||
llvm_bin = op.join(
|
||||
ndk_path, "toolchains", "llvm", "prebuilt", f"{os_name}-x86_64", "bin"
|
||||
)
|
||||
cargo = op.join(rust_bin, "cargo" + EXE_EXT)
|
||||
gradlew = op.join(".", "gradlew" + (".bat" if is_windows else ""))
|
||||
adb_path = op.join(sdk_path, "platform-tools", "adb" + EXE_EXT)
|
||||
native_gen_path = op.realpath(op.join("native", "out", "generated"))
|
||||
|
||||
# Global vars
|
||||
config = {}
|
||||
@@ -86,7 +99,7 @@ build_tools = None
|
||||
def mv(source, target):
|
||||
try:
|
||||
shutil.move(source, target)
|
||||
vprint(f'mv {source} -> {target}')
|
||||
vprint(f"mv {source} -> {target}")
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -94,7 +107,7 @@ def mv(source, target):
|
||||
def cp(source, target):
|
||||
try:
|
||||
shutil.copyfile(source, target)
|
||||
vprint(f'cp {source} -> {target}')
|
||||
vprint(f"cp {source} -> {target}")
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -102,22 +115,24 @@ def cp(source, target):
|
||||
def rm(file):
|
||||
try:
|
||||
os.remove(file)
|
||||
vprint(f'rm {file}')
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
vprint(f"rm {file}")
|
||||
except FileNotFoundError as e:
|
||||
pass
|
||||
|
||||
|
||||
def rm_on_error(func, path, _):
|
||||
# Remove a read-only file on Windows will get "WindowsError: [Error 5] Access is denied"
|
||||
# Clear the "read-only" and retry
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
os.unlink(path)
|
||||
# Removing a read-only file on Windows will get "WindowsError: [Error 5] Access is denied"
|
||||
# Clear the "read-only" bit and retry
|
||||
try:
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
os.unlink(path)
|
||||
except FileNotFoundError as e:
|
||||
pass
|
||||
|
||||
|
||||
def rm_rf(path):
|
||||
vprint(f'rm -rf {path}')
|
||||
shutil.rmtree(path, ignore_errors=True, onerror=rm_on_error)
|
||||
vprint(f"rm -rf {path}")
|
||||
shutil.rmtree(path, ignore_errors=False, onerror=rm_on_error)
|
||||
|
||||
|
||||
def mkdir(path, mode=0o755):
|
||||
@@ -140,8 +155,11 @@ def system(cmd):
|
||||
|
||||
|
||||
def cmd_out(cmd, env=None):
|
||||
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env) \
|
||||
.stdout.strip().decode('utf-8')
|
||||
return (
|
||||
subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env)
|
||||
.stdout.strip()
|
||||
.decode("utf-8")
|
||||
)
|
||||
|
||||
|
||||
def xz(data):
|
||||
@@ -150,205 +168,210 @@ def xz(data):
|
||||
|
||||
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:
|
||||
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('=')
|
||||
prop = line.split("=")
|
||||
if len(prop) != 2:
|
||||
continue
|
||||
value = prop[1].strip(' \t\r\n')
|
||||
value = prop[1].strip(" \t\r\n")
|
||||
if len(value) == 0:
|
||||
continue
|
||||
props[prop[0].strip(' \t\r\n')] = value
|
||||
props[prop[0].strip(" \t\r\n")] = value
|
||||
return props
|
||||
|
||||
|
||||
def load_config(args):
|
||||
commit_hash = cmd_out(['git', 'rev-parse', '--short=8', 'HEAD'])
|
||||
commit_hash = cmd_out(["git", "rev-parse", "--short=8", "HEAD"])
|
||||
|
||||
# Default values
|
||||
config['version'] = commit_hash
|
||||
config['outdir'] = 'out'
|
||||
config["version"] = commit_hash
|
||||
config["outdir"] = "out"
|
||||
|
||||
# Load prop files
|
||||
if op.exists(args.config):
|
||||
config.update(parse_props(args.config))
|
||||
|
||||
for key, value in parse_props('gradle.properties').items():
|
||||
if key.startswith('magisk.'):
|
||||
for key, value in parse_props("gradle.properties").items():
|
||||
if key.startswith("magisk."):
|
||||
config[key[7:]] = value
|
||||
|
||||
try:
|
||||
config['versionCode'] = int(config['versionCode'])
|
||||
config["versionCode"] = int(config["versionCode"])
|
||||
except ValueError:
|
||||
error('Config error: "versionCode" is required to be an integer')
|
||||
|
||||
mkdir_p(config['outdir'])
|
||||
mkdir_p(config["outdir"])
|
||||
global STDOUT
|
||||
STDOUT = None if args.verbose else subprocess.DEVNULL
|
||||
|
||||
|
||||
def clean_elf():
|
||||
if is_windows:
|
||||
elf_cleaner = op.join('tools', 'elf-cleaner.exe')
|
||||
elf_cleaner = op.join("tools", "elf-cleaner.exe")
|
||||
else:
|
||||
elf_cleaner = op.join('native', 'out', 'elf-cleaner')
|
||||
elf_cleaner = op.join("native", "out", "elf-cleaner")
|
||||
if not op.exists(elf_cleaner):
|
||||
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])
|
||||
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(op.join('native', 'out', arch, bin)
|
||||
for arch in archs for bin in ['magisk', 'magiskpolicy'])
|
||||
args.extend(
|
||||
op.join("native", "out", arch, bin)
|
||||
for arch in archs
|
||||
for bin in ["magisk", "magiskpolicy"]
|
||||
)
|
||||
execv(args)
|
||||
|
||||
|
||||
def run_ndk_build(flags):
|
||||
os.chdir('native')
|
||||
flags = 'NDK_PROJECT_PATH=. NDK_APPLICATION_MK=src/Application.mk ' + flags
|
||||
proc = system(f'{ndk_build} {flags} -j{cpu_count}')
|
||||
os.chdir("native")
|
||||
flags = "NDK_PROJECT_PATH=. NDK_APPLICATION_MK=src/Application.mk " + flags
|
||||
proc = system(f"{ndk_build} {flags} -j{cpu_count}")
|
||||
if proc.returncode != 0:
|
||||
error('Build binary failed!')
|
||||
os.chdir('..')
|
||||
error("Build binary failed!")
|
||||
os.chdir("..")
|
||||
for arch in archs:
|
||||
for tgt in support_targets + ['libinit-ld.so', 'libzygisk-ld.so']:
|
||||
source = op.join('native', 'libs', arch, tgt)
|
||||
target = op.join('native', 'out', arch, tgt)
|
||||
for tgt in support_targets + ["libinit-ld.so", "libzygisk-ld.so"]:
|
||||
source = op.join("native", "libs", arch, tgt)
|
||||
target = op.join("native", "out", arch, tgt)
|
||||
mv(source, target)
|
||||
|
||||
|
||||
def run_cargo_build(args):
|
||||
os.chdir(op.join('native', 'src'))
|
||||
targets = set(args.target) & set(rust_targets)
|
||||
if 'resetprop' in args.target:
|
||||
targets.add('magisk')
|
||||
|
||||
def run_cargo(cmds, triple="aarch64-linux-android"):
|
||||
env = os.environ.copy()
|
||||
env['CARGO_BUILD_RUSTC'] = op.join(rust_bin, 'rustc' + EXE_EXT)
|
||||
env["PATH"] = f'{rust_bin}{os.pathsep}{env["PATH"]}'
|
||||
env["CARGO_BUILD_RUSTC"] = op.join(rust_bin, "rustc" + EXE_EXT)
|
||||
env["RUSTFLAGS"] = "-Clinker-plugin-lto"
|
||||
env["TARGET_CC"] = op.join(llvm_bin, "clang" + EXE_EXT)
|
||||
env["TARGET_CFLAGS"] = f"--target={triple}23"
|
||||
return execv([cargo, *cmds], env)
|
||||
|
||||
# Install cxxbridge and generate C++ bindings
|
||||
native_out = op.join('..', 'out')
|
||||
local_cargo_root = op.join(native_out, '.cargo')
|
||||
cfg = op.join('.cargo', 'config.toml')
|
||||
cfg_bak = op.join('.cargo', 'config.toml.bak')
|
||||
try:
|
||||
# Hide the config file for cargo install
|
||||
mv(cfg, cfg_bak)
|
||||
cxx_src = op.join('external', 'cxx-rs', 'gen', 'cmd')
|
||||
mkdir_p(local_cargo_root)
|
||||
cmds = [cargo, 'install', '--root', local_cargo_root, '--path', cxx_src]
|
||||
if not args.verbose:
|
||||
cmds.append('-q')
|
||||
proc = execv(cmds, env)
|
||||
if proc.returncode != 0:
|
||||
error('cxxbridge-cmd installation failed!')
|
||||
finally:
|
||||
# Make sure the config file rename is always reverted
|
||||
mv(cfg_bak, cfg)
|
||||
cxxbridge = op.join(local_cargo_root, 'bin', 'cxxbridge' + EXE_EXT)
|
||||
mkdir(native_gen_path)
|
||||
for p in ['base', 'boot', 'core', 'init', 'sepolicy']:
|
||||
text = cmd_out([cxxbridge, op.join(p, 'lib.rs')])
|
||||
write_if_diff(op.join(native_gen_path, f'{p}-rs.cpp'), text)
|
||||
text = cmd_out([cxxbridge, '--header', op.join(p, 'lib.rs')])
|
||||
write_if_diff(op.join(native_gen_path, f'{p}-rs.hpp'), text)
|
||||
|
||||
def run_cargo_build(args):
|
||||
native_out = op.join("..", "out")
|
||||
mkdir(native_out)
|
||||
|
||||
targets = set(args.target) & set(rust_targets)
|
||||
if "resetprop" in args.target:
|
||||
targets.add("magisk")
|
||||
|
||||
if len(targets) == 0:
|
||||
return
|
||||
|
||||
# Start building the actual build commands
|
||||
cmds = [cargo, 'build', '-Z', 'build-std=std,panic_abort',
|
||||
'-Z', 'build-std-features=panic_immediate_abort']
|
||||
cmds = ["build"]
|
||||
for target in targets:
|
||||
cmds.append('-p')
|
||||
cmds.append("-p")
|
||||
cmds.append(target)
|
||||
rust_out = 'debug'
|
||||
rust_out = "debug"
|
||||
if args.release:
|
||||
cmds.append('-r')
|
||||
rust_out = 'release'
|
||||
cmds.append("-r")
|
||||
rust_out = "release"
|
||||
if not args.verbose:
|
||||
cmds.append('-q')
|
||||
cmds.append("-q")
|
||||
|
||||
os_name = platform.system().lower()
|
||||
llvm_bin = op.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', f'{os_name}-x86_64', 'bin')
|
||||
env['TARGET_CC'] = op.join(llvm_bin, 'clang' + EXE_EXT)
|
||||
env['RUSTFLAGS'] = '-Clinker-plugin-lto'
|
||||
for (arch, triple) in zip(archs, triples):
|
||||
env['TARGET_CFLAGS'] = f'--target={triple}23'
|
||||
rust_triple = 'thumbv7neon-linux-androideabi' if triple.startswith('armv7') else triple
|
||||
proc = execv([*cmds, '--target', rust_triple], env)
|
||||
cmds.append("--target")
|
||||
cmds.append("")
|
||||
|
||||
for arch, triple in zip(archs, triples):
|
||||
rust_triple = (
|
||||
"thumbv7neon-linux-androideabi" if triple.startswith("armv7") else triple
|
||||
)
|
||||
cmds[-1] = rust_triple
|
||||
proc = run_cargo(cmds, triple)
|
||||
if proc.returncode != 0:
|
||||
error('Build binary failed!')
|
||||
error("Build binary failed!")
|
||||
|
||||
arch_out = op.join(native_out, arch)
|
||||
mkdir(arch_out)
|
||||
for tgt in targets:
|
||||
source = op.join('target', rust_triple, rust_out, f'lib{tgt}.a')
|
||||
target = op.join(arch_out, f'lib{tgt}-rs.a')
|
||||
source = op.join("target", rust_triple, rust_out, f"lib{tgt}.a")
|
||||
target = op.join(arch_out, f"lib{tgt}-rs.a")
|
||||
mv(source, target)
|
||||
|
||||
os.chdir(op.join('..', '..'))
|
||||
|
||||
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(op.join("native", "src"))
|
||||
run_cargo(args.commands)
|
||||
os.chdir(op.join("..", ".."))
|
||||
|
||||
|
||||
def write_if_diff(file_name, text):
|
||||
do_write = True
|
||||
if op.exists(file_name):
|
||||
with open(file_name, 'r') as f:
|
||||
with open(file_name, "r") as f:
|
||||
orig = f.read()
|
||||
do_write = orig != text
|
||||
if do_write:
|
||||
with open(file_name, 'w') as f:
|
||||
with open(file_name, "w") as f:
|
||||
f.write(text)
|
||||
|
||||
|
||||
def binary_dump(src, var_name, compressor=xz):
|
||||
out_str = f'constexpr unsigned char {var_name}[] = {{'
|
||||
out_str = f"constexpr unsigned char {var_name}[] = {{"
|
||||
for i, c in enumerate(compressor(src.read())):
|
||||
if i % 16 == 0:
|
||||
out_str += '\n'
|
||||
out_str += f'0x{c:02X},'
|
||||
out_str += '\n};\n'
|
||||
out_str += "\n"
|
||||
out_str += f"0x{c:02X},"
|
||||
out_str += "\n};\n"
|
||||
return out_str
|
||||
|
||||
|
||||
def dump_bin_header(args):
|
||||
mkdir_p(native_gen_path)
|
||||
for arch in archs:
|
||||
preload = op.join('native', 'out', arch, 'libinit-ld.so')
|
||||
with open(preload, 'rb') as src:
|
||||
text = binary_dump(src, 'init_ld_xz')
|
||||
preload = op.join('native', 'out', arch, 'libzygisk-ld.so')
|
||||
with open(preload, 'rb') as src:
|
||||
text += binary_dump(src, 'zygisk_ld', compressor=lambda x: x)
|
||||
write_if_diff(op.join(native_gen_path, f'{arch}_binaries.h'), text)
|
||||
preload = op.join("native", "out", arch, "libinit-ld.so")
|
||||
with open(preload, "rb") as src:
|
||||
text = binary_dump(src, "init_ld_xz")
|
||||
preload = op.join("native", "out", arch, "libzygisk-ld.so")
|
||||
with open(preload, "rb") as src:
|
||||
text += binary_dump(src, "zygisk_ld", compressor=lambda x: x)
|
||||
write_if_diff(op.join(native_gen_path, f"{arch}_binaries.h"), text)
|
||||
|
||||
|
||||
def dump_flag_header():
|
||||
flag_txt = textwrap.dedent('''\
|
||||
flag_txt = textwrap.dedent(
|
||||
"""\
|
||||
#pragma once
|
||||
#define quote(s) #s
|
||||
#define str(s) quote(s)
|
||||
#define MAGISK_FULL_VER MAGISK_VERSION "(" str(MAGISK_VER_CODE) ")"
|
||||
#define NAME_WITH_VER(name) str(name) " " MAGISK_FULL_VER
|
||||
''')
|
||||
"""
|
||||
)
|
||||
flag_txt += f'#define MAGISK_VERSION "{config["version"]}"\n'
|
||||
flag_txt += f'#define MAGISK_VER_CODE {config["versionCode"]}\n'
|
||||
flag_txt += f'#define MAGISK_DEBUG {0 if args.release else 1}\n'
|
||||
flag_txt += f"#define MAGISK_DEBUG {0 if args.release else 1}\n"
|
||||
|
||||
mkdir_p(native_gen_path)
|
||||
write_if_diff(op.join(native_gen_path, 'flags.h'), flag_txt)
|
||||
write_if_diff(op.join(native_gen_path, "flags.h"), flag_txt)
|
||||
|
||||
|
||||
def build_binary(args):
|
||||
# Verify NDK install
|
||||
try:
|
||||
with open(op.join(ndk_path, 'ONDK_VERSION'), 'r') as ondk_ver:
|
||||
assert ondk_ver.read().strip(' \t\r\n') == config['ondkVersion']
|
||||
with open(op.join(ndk_path, "ONDK_VERSION"), "r") as ondk_ver:
|
||||
assert ondk_ver.read().strip(" \t\r\n") == config["ondkVersion"]
|
||||
except:
|
||||
error('Unmatched NDK. Please install/upgrade NDK with "build.py ndk"')
|
||||
|
||||
if 'target' not in vars(args):
|
||||
vars(args)['target'] = []
|
||||
if "target" not in vars(args):
|
||||
vars(args)["target"] = []
|
||||
|
||||
if args.target:
|
||||
args.target = set(args.target) & set(support_targets)
|
||||
@@ -357,159 +380,239 @@ def build_binary(args):
|
||||
else:
|
||||
args.target = default_targets
|
||||
|
||||
header('* Building binaries: ' + ' '.join(args.target))
|
||||
header("* Building binaries: " + " ".join(args.target))
|
||||
|
||||
os.chdir(op.join("native", "src"))
|
||||
run_cargo_build(args)
|
||||
os.chdir(op.join("..", ".."))
|
||||
|
||||
dump_flag_header()
|
||||
|
||||
flag = ''
|
||||
flag = ""
|
||||
clean = False
|
||||
|
||||
if 'magisk' in args.target or 'magiskinit' in args.target:
|
||||
flag += ' B_PRELOAD=1'
|
||||
if "magisk" in args.target or "magiskinit" in args.target:
|
||||
flag += " B_PRELOAD=1"
|
||||
|
||||
if 'magiskpolicy' in args.target:
|
||||
flag += ' B_POLICY=1'
|
||||
if "magiskpolicy" in args.target:
|
||||
flag += " B_POLICY=1"
|
||||
clean = True
|
||||
|
||||
if 'test' in args.target:
|
||||
flag += ' B_TEST=1'
|
||||
if "test" in args.target:
|
||||
flag += " B_TEST=1"
|
||||
|
||||
if 'magiskinit' in args.target:
|
||||
flag += ' B_PRELOAD=1'
|
||||
if "magiskinit" in args.target:
|
||||
flag += " B_PRELOAD=1"
|
||||
|
||||
if 'resetprop' in args.target:
|
||||
flag += ' B_PROP=1'
|
||||
if "resetprop" in args.target:
|
||||
flag += " B_PROP=1"
|
||||
|
||||
if 'magiskboot' in args.target:
|
||||
flag += ' B_BOOT=1'
|
||||
if "magiskboot" in args.target:
|
||||
flag += " B_BOOT=1"
|
||||
|
||||
if flag:
|
||||
run_ndk_build(flag)
|
||||
|
||||
# magiskinit and magisk embeds preload.so
|
||||
|
||||
flag = ''
|
||||
flag = ""
|
||||
|
||||
if 'magisk' in args.target:
|
||||
flag += ' B_MAGISK=1'
|
||||
if "magisk" in args.target:
|
||||
flag += " B_MAGISK=1"
|
||||
clean = True
|
||||
|
||||
if 'magiskinit' in args.target:
|
||||
flag += ' B_INIT=1'
|
||||
if "magiskinit" in args.target:
|
||||
flag += " B_INIT=1"
|
||||
|
||||
if flag:
|
||||
dump_bin_header(args)
|
||||
run_ndk_build(flag)
|
||||
|
||||
if clean:
|
||||
clean_elf()
|
||||
|
||||
# BusyBox is built with different libc
|
||||
|
||||
if 'busybox' in args.target:
|
||||
run_ndk_build('B_BB=1')
|
||||
if "busybox" in args.target:
|
||||
run_ndk_build("B_BB=1")
|
||||
|
||||
|
||||
def find_jdk():
|
||||
env = os.environ.copy()
|
||||
if "ANDROID_STUDIO" in env:
|
||||
studio = env["ANDROID_STUDIO"]
|
||||
jbr = op.join(studio, "jbr", "bin")
|
||||
if not op.exists(jbr):
|
||||
jbr = op.join(studio, "Contents", "jbr", "Contents", "Home", "bin")
|
||||
if op.exists(jbr):
|
||||
env["PATH"] = f'{jbr}{os.pathsep}{env["PATH"]}'
|
||||
|
||||
no_jdk = False
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
"javac -version",
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
env=env,
|
||||
shell=True,
|
||||
)
|
||||
no_jdk = proc.returncode != 0
|
||||
except FileNotFoundError:
|
||||
no_jdk = True
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def build_apk(args, module):
|
||||
build_type = 'Release' if args.release else 'Debug'
|
||||
env = find_jdk()
|
||||
|
||||
proc = execv([gradlew, f'{module}:assemble{build_type}',
|
||||
'-PconfigPath=' + op.abspath(args.config)])
|
||||
build_type = "Release" if args.release else "Debug"
|
||||
proc = execv(
|
||||
[
|
||||
gradlew,
|
||||
f"{module}:assemble{build_type}",
|
||||
"-PconfigPath=" + op.abspath(args.config),
|
||||
],
|
||||
env=env,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
error(f'Build {module} failed!')
|
||||
error(f"Build {module} failed!")
|
||||
|
||||
build_type = build_type.lower()
|
||||
|
||||
apk = f'{module}-{build_type}.apk'
|
||||
source = op.join(module, 'build', 'outputs', 'apk', build_type, apk)
|
||||
target = op.join(config['outdir'], apk)
|
||||
apk = f"{module}-{build_type}.apk"
|
||||
source = op.join(module, "build", "outputs", "apk", build_type, apk)
|
||||
target = op.join(config["outdir"], apk)
|
||||
mv(source, target)
|
||||
header('Output: ' + target)
|
||||
header("Output: " + target)
|
||||
|
||||
|
||||
def build_app(args):
|
||||
header('* Building the Magisk app')
|
||||
build_apk(args, 'app')
|
||||
header("* Building the Magisk app")
|
||||
build_apk(args, "app")
|
||||
|
||||
# Stub building is directly integrated into the main app
|
||||
# build process. Copy the stub APK into output directory.
|
||||
build_type = 'release' if args.release else 'debug'
|
||||
apk = f'stub-{build_type}.apk'
|
||||
source = op.join('app', 'src', build_type, 'assets', 'stub.apk')
|
||||
target = op.join(config['outdir'], apk)
|
||||
build_type = "release" if args.release else "debug"
|
||||
apk = f"stub-{build_type}.apk"
|
||||
source = op.join("app", "src", build_type, "assets", "stub.apk")
|
||||
target = op.join(config["outdir"], apk)
|
||||
cp(source, target)
|
||||
|
||||
|
||||
def build_stub(args):
|
||||
header('* Building the stub app')
|
||||
build_apk(args, 'stub')
|
||||
header("* Building the stub app")
|
||||
build_apk(args, "stub")
|
||||
|
||||
|
||||
def cleanup(args):
|
||||
support_targets = {'native', 'java'}
|
||||
support_targets = {"native", "cpp", "rust", "java"}
|
||||
if args.target:
|
||||
args.target = set(args.target) & support_targets
|
||||
if "native" in args.target:
|
||||
args.target.add("cpp")
|
||||
args.target.add("rust")
|
||||
else:
|
||||
args.target = support_targets
|
||||
|
||||
if 'native' in args.target:
|
||||
header('* Cleaning native')
|
||||
rm_rf(op.join('native', 'libs'))
|
||||
rm_rf(op.join('native', 'obj'))
|
||||
rm_rf(op.join('native', 'out'))
|
||||
rm_rf(op.join('native', 'src', 'target'))
|
||||
rm_rf(op.join('native', 'src', 'external', 'cxx-rs', 'target'))
|
||||
if "cpp" in args.target:
|
||||
header("* Cleaning C++")
|
||||
rm_rf(op.join("native", "libs"))
|
||||
rm_rf(op.join("native", "obj"))
|
||||
rm_rf(op.join("native", "out"))
|
||||
|
||||
if 'java' in args.target:
|
||||
header('* Cleaning java')
|
||||
execv([gradlew, 'app:clean', 'app:shared:clean', 'stub:clean'])
|
||||
rm_rf(op.join('app', 'src', 'debug'))
|
||||
rm_rf(op.join('app', 'src', 'release'))
|
||||
if "rust" in args.target:
|
||||
header("* Cleaning Rust")
|
||||
rm_rf(op.join("native", "src", "target"))
|
||||
rm(op.join("native", "src", "boot", "proto", "mod.rs"))
|
||||
rm(op.join("native", "src", "boot", "proto", "update_metadata.rs"))
|
||||
for rs_gen in glob.glob("native/**/*-rs.*pp", recursive=True):
|
||||
rm(rs_gen)
|
||||
|
||||
if "java" in args.target:
|
||||
header("* Cleaning java")
|
||||
execv([gradlew, "app:clean", "app:shared:clean", "stub:clean"], env=find_jdk())
|
||||
rm_rf(op.join("app", "src", "debug"))
|
||||
rm_rf(op.join("app", "src", "release"))
|
||||
|
||||
|
||||
def setup_ndk(args):
|
||||
os_name = platform.system().lower()
|
||||
ndk_ver = config['ondkVersion']
|
||||
url = f'https://github.com/topjohnwu/ondk/releases/download/{ndk_ver}/ondk-{ndk_ver}-{os_name}.tar.gz'
|
||||
ndk_archive = url.split('/')[-1]
|
||||
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]
|
||||
ondk_path = op.join(ndk_root, f"ondk-{ndk_ver}")
|
||||
|
||||
header(f'* Downloading and extracting {ndk_archive}')
|
||||
header(f"* Downloading and extracting {ndk_archive}")
|
||||
rm_rf(ondk_path)
|
||||
with urllib.request.urlopen(url) as response:
|
||||
with tarfile.open(mode='r|gz', fileobj=response) as tar:
|
||||
with tarfile.open(mode="r|xz", fileobj=response) as tar:
|
||||
tar.extractall(ndk_root)
|
||||
|
||||
rm_rf(ndk_path)
|
||||
mv(op.join(ndk_root, f'ondk-{ndk_ver}'), ndk_path)
|
||||
mv(ondk_path, ndk_path)
|
||||
|
||||
header('* Patching static libs')
|
||||
for target in ['arm-linux-androideabi', 'i686-linux-android']:
|
||||
arch = target.split('-')[0]
|
||||
header("* Patching static libs")
|
||||
for target in ["arm-linux-androideabi", "i686-linux-android"]:
|
||||
arch = target.split("-")[0]
|
||||
lib_dir = op.join(
|
||||
ndk_path, 'toolchains', 'llvm', 'prebuilt', f'{os_name}-x86_64',
|
||||
'sysroot', 'usr', 'lib', f'{target}', '23')
|
||||
ndk_path,
|
||||
"toolchains",
|
||||
"llvm",
|
||||
"prebuilt",
|
||||
f"{os_name}-x86_64",
|
||||
"sysroot",
|
||||
"usr",
|
||||
"lib",
|
||||
f"{target}",
|
||||
"23",
|
||||
)
|
||||
if not op.exists(lib_dir):
|
||||
continue
|
||||
src_dir = op.join('tools', 'ndk-bins', '21', arch)
|
||||
rm(op.join(src_dir, '.DS_Store'))
|
||||
src_dir = op.join("tools", "ndk-bins", arch)
|
||||
rm(op.join(src_dir, ".DS_Store"))
|
||||
shutil.copytree(src_dir, lib_dir, copy_function=cp, dirs_exist_ok=True)
|
||||
|
||||
|
||||
def push_files(args, script):
|
||||
abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"])
|
||||
apk = config["outdir"] + ("/app-release.apk" if args.release else "/app-debug.apk")
|
||||
|
||||
# Extract busybox from APK
|
||||
busybox = f'{config["outdir"]}/busybox'
|
||||
with ZipFile(apk) as zf:
|
||||
with zf.open(f"lib/{abi}/libbusybox.so") as libbb:
|
||||
with open(busybox, "wb") as bb:
|
||||
bb.write(libbb.read())
|
||||
|
||||
try:
|
||||
proc = execv([adb_path, "push", busybox, script, "/data/local/tmp"])
|
||||
if proc.returncode != 0:
|
||||
error("adb push failed!")
|
||||
finally:
|
||||
rm_rf(busybox)
|
||||
|
||||
proc = execv([adb_path, "push", apk, "/data/local/tmp/magisk.apk"])
|
||||
if proc.returncode != 0:
|
||||
error("adb push failed!")
|
||||
|
||||
|
||||
def setup_avd(args):
|
||||
if not args.skip:
|
||||
build_all(args)
|
||||
|
||||
header('* Setting up emulator')
|
||||
header("* Setting up emulator")
|
||||
|
||||
abi = cmd_out([adb_path, 'shell', 'getprop', 'ro.product.cpu.abi'])
|
||||
proc = execv([adb_path, 'push', f'native/out/{abi}/busybox', 'scripts/avd_magisk.sh', '/data/local/tmp'])
|
||||
if proc.returncode != 0:
|
||||
error('adb push failed!')
|
||||
push_files(args, "scripts/avd_magisk.sh")
|
||||
|
||||
apk = 'out/app-release.apk' if args.release else 'out/app-debug.apk'
|
||||
proc = execv([adb_path, 'push', apk, '/data/local/tmp/magisk.apk'])
|
||||
proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_magisk.sh"])
|
||||
if proc.returncode != 0:
|
||||
error('adb push failed!')
|
||||
|
||||
proc = execv([adb_path, 'shell', 'sh', '/data/local/tmp/avd_magisk.sh'])
|
||||
if proc.returncode != 0:
|
||||
error('avd_magisk.sh failed!')
|
||||
error("avd_magisk.sh failed!")
|
||||
|
||||
|
||||
def patch_avd_ramdisk(args):
|
||||
@@ -517,46 +620,38 @@ def patch_avd_ramdisk(args):
|
||||
args.release = False
|
||||
build_all(args)
|
||||
|
||||
header('* Patching emulator ramdisk.img')
|
||||
header("* Patching emulator ramdisk.img")
|
||||
|
||||
# Create a backup to prevent accidental overwrites
|
||||
backup = args.ramdisk + '.bak'
|
||||
backup = args.ramdisk + ".bak"
|
||||
if not op.exists(backup):
|
||||
cp(args.ramdisk, backup)
|
||||
|
||||
ini = op.join(op.dirname(args.ramdisk), 'advancedFeatures.ini')
|
||||
with open(ini, 'r') as f:
|
||||
ini = op.join(op.dirname(args.ramdisk), "advancedFeatures.ini")
|
||||
with open(ini, "r") as f:
|
||||
adv_ft = f.read()
|
||||
|
||||
# Need to turn off system as root
|
||||
if 'SystemAsRoot = on' in adv_ft:
|
||||
if "SystemAsRoot = on" in adv_ft:
|
||||
# Create a backup
|
||||
cp(ini, ini + '.bak')
|
||||
adv_ft = adv_ft.replace('SystemAsRoot = on', 'SystemAsRoot = off')
|
||||
with open(ini, 'w') as f:
|
||||
cp(ini, ini + ".bak")
|
||||
adv_ft = adv_ft.replace("SystemAsRoot = on", "SystemAsRoot = off")
|
||||
with open(ini, "w") as f:
|
||||
f.write(adv_ft)
|
||||
|
||||
abi = cmd_out([adb_path, 'shell', 'getprop', 'ro.product.cpu.abi'])
|
||||
proc = execv([adb_path, 'push', f'native/out/{abi}/busybox', 'scripts/avd_patch.sh', '/data/local/tmp'])
|
||||
if proc.returncode != 0:
|
||||
error('adb push failed!')
|
||||
push_files(args, "scripts/avd_patch.sh")
|
||||
|
||||
apk = 'out/app-release.apk' if args.release else 'out/app-debug.apk'
|
||||
proc = execv([adb_path, 'push', apk, '/data/local/tmp/magisk.apk'])
|
||||
proc = execv([adb_path, "push", backup, "/data/local/tmp/ramdisk.cpio.tmp"])
|
||||
if proc.returncode != 0:
|
||||
error('adb push failed!')
|
||||
error("adb push failed!")
|
||||
|
||||
proc = execv([adb_path, 'push', backup, '/data/local/tmp/ramdisk.cpio.tmp'])
|
||||
proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_patch.sh"])
|
||||
if proc.returncode != 0:
|
||||
error('adb push failed!')
|
||||
error("avd_patch.sh failed!")
|
||||
|
||||
proc = execv([adb_path, 'shell', 'sh', '/data/local/tmp/avd_patch.sh'])
|
||||
proc = execv([adb_path, "pull", "/data/local/tmp/ramdisk.cpio.gz", args.ramdisk])
|
||||
if proc.returncode != 0:
|
||||
error('avd_patch.sh failed!')
|
||||
|
||||
proc = execv([adb_path, 'pull', '/data/local/tmp/ramdisk.cpio.gz', args.ramdisk])
|
||||
if proc.returncode != 0:
|
||||
error('adb pull failed!')
|
||||
error("adb pull failed!")
|
||||
|
||||
|
||||
def build_all(args):
|
||||
@@ -564,51 +659,62 @@ def build_all(args):
|
||||
build_app(args)
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Magisk build script')
|
||||
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='store_true',
|
||||
help='verbose output')
|
||||
parser.add_argument('-c', '--config', default='config.prop',
|
||||
help='custom config file (default: config.prop)')
|
||||
subparsers = parser.add_subparsers(title='actions')
|
||||
parser.add_argument(
|
||||
"-r", "--release", action="store_true", help="compile in release mode"
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="store_true", 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')
|
||||
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 = subparsers.add_parser("binary", help="build binaries")
|
||||
binary_parser.add_argument(
|
||||
'target', nargs='*', help=f"{', '.join(support_targets)}, \
|
||||
or empty for defaults ({', '.join(default_targets)})")
|
||||
"target",
|
||||
nargs="*",
|
||||
help=f"{', '.join(support_targets)}, \
|
||||
or empty for defaults ({', '.join(default_targets)})",
|
||||
)
|
||||
binary_parser.set_defaults(func=build_binary)
|
||||
|
||||
app_parser = subparsers.add_parser('app', help='build the Magisk app')
|
||||
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)
|
||||
|
||||
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 = 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 = 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')
|
||||
avd_patch_parser.add_argument('ramdisk', help='path to ramdisk.img')
|
||||
avd_patch_parser.add_argument('-s', '--skip', action='store_true',
|
||||
help='skip building binaries and the app')
|
||||
avd_patch_parser = subparsers.add_parser("avd_patch", help="patch AVD ramdisk.img")
|
||||
avd_patch_parser.add_argument("ramdisk", help="path to ramdisk.img")
|
||||
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_ramdisk)
|
||||
|
||||
clean_parser = subparsers.add_parser('clean', help='cleanup')
|
||||
clean_parser = subparsers.add_parser("clean", help="cleanup")
|
||||
clean_parser.add_argument(
|
||||
'target', nargs='*', help='native, java, or empty to clean both')
|
||||
"target", 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 = subparsers.add_parser("ndk", help="setup Magisk NDK")
|
||||
ndk_parser.set_defaults(func=setup_ndk)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
|
@@ -16,9 +16,9 @@ gradlePlugin {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("gradle-plugin", "1.8.10"))
|
||||
implementation("com.android.tools.build:gradle:8.1.0-alpha11")
|
||||
implementation("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3")
|
||||
implementation(kotlin("gradle-plugin", "1.9.0"))
|
||||
implementation("com.android.tools.build:gradle:8.1.1")
|
||||
implementation("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.1")
|
||||
implementation("org.lsposed.lsparanoid:gradle-plugin:0.5.2")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.4.0.202211300538-r")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r")
|
||||
}
|
||||
|
@@ -64,19 +64,6 @@ private fun PrintStream.byteField(name: String, bytes: ByteArray) {
|
||||
println("}")
|
||||
}
|
||||
|
||||
fun genKeyData(keysDir: File, outSrc: File) {
|
||||
outSrc.parentFile.mkdirs()
|
||||
PrintStream(outSrc).use {
|
||||
it.println("package com.topjohnwu.magisk.signing;")
|
||||
it.println("public final class KeyData {")
|
||||
|
||||
it.byteField("verityCert", File(keysDir, "verity.x509.pem").readBytes())
|
||||
it.byteField("verityKey", File(keysDir, "verity.pk8").readBytes())
|
||||
|
||||
it.println("}")
|
||||
}
|
||||
}
|
||||
|
||||
@CacheableTask
|
||||
abstract class ManifestUpdater: DefaultTask() {
|
||||
@get:Input
|
||||
|
@@ -69,13 +69,13 @@ private val Project.androidComponents
|
||||
|
||||
fun Project.setupCommon() {
|
||||
androidBase {
|
||||
compileSdkVersion(33)
|
||||
buildToolsVersion = "33.0.1"
|
||||
compileSdkVersion(34)
|
||||
buildToolsVersion = "34.0.0"
|
||||
ndkPath = "$sdkDirectory/ndk/magisk"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
targetSdk = 33
|
||||
targetSdk = 34
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -305,19 +305,6 @@ fun Project.setupApp() {
|
||||
}
|
||||
}
|
||||
mergeAssetsProvider.configure { dependsOn(syncAssets) }
|
||||
|
||||
val keysDir = rootProject.file("tools/keys")
|
||||
val outSrcDir = File(buildDir, "generated/source/keydata/$name")
|
||||
val outSrc = File(outSrcDir, "com/topjohnwu/magisk/signing/KeyData.java")
|
||||
|
||||
val genSrcTask = tasks.register("generate${variantCapped}KeyData") {
|
||||
inputs.dir(keysDir)
|
||||
outputs.file(outSrc)
|
||||
doLast {
|
||||
genKeyData(keysDir, outSrc)
|
||||
}
|
||||
}
|
||||
registerJavaGeneratingTask(genSrcTask, outSrcDir)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,8 @@
|
||||
|
||||
The following sections are for developers
|
||||
|
||||
- [Developer Guides](guides.md)
|
||||
- [Building and Developing Magisk](build.md) (for developing Magisk itself)
|
||||
- [Developer Guides](guides.md) (for developers **using** Magisk)
|
||||
- [Magisk Tools](tools.md)
|
||||
- [Internal Details](details.md)
|
||||
- [Android Booting Shenanigans](boot.md)
|
||||
|
64
docs/build.md
Normal file
64
docs/build.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Building and Development
|
||||
|
||||
## Setup Environment
|
||||
|
||||
- Supported platforms:
|
||||
- Linux x64
|
||||
- macOS x64 (Intel)
|
||||
- macOS arm64 (Apple Silicon)
|
||||
- Windows x64
|
||||
- Windows only: Enable [developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development). This is required because we need symbolic link support.
|
||||
- Install Python 3.8+:
|
||||
- On Unix, install python3 using your favorite package manager
|
||||
- On Windows, download and install the latest Python version on the [offical website](https://www.python.org/downloads/windows/).<br>
|
||||
Make sure to select **"Add Python to PATH"** during installation.
|
||||
- (Optional on Windows): Run `pip install colorama` to install the `colorama` python package
|
||||
- Install Git:
|
||||
- On Unix, install git with your favorite package manager
|
||||
- On Windows, download the install the latest Git version on the [official website](https://git-scm.com/download/win).<br>
|
||||
Make sure to **"Enable symbolic links"** during installation.
|
||||
- Install Android Studio and follow the instructions and go through the initial setup.
|
||||
- Set environment variable `ANDROID_SDK_ROOT` to the Android SDK folder. This path can be found in Android Studio settings.
|
||||
- Setup JDK:
|
||||
- The recommended option is to set environment variable `ANDROID_STUDIO` to the path where your Android Studio is installed. The build script will automatically find and use the bundled JDK.
|
||||
- You can also setup JDK 17 yourself, but this guide will not cover the instructions.
|
||||
- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
|
||||
- Run `./build.py ndk` to let the script download and install NDK for you
|
||||
|
||||
## Building
|
||||
|
||||
- To build everything and create the final Magisk APK, run `./build.py all`.
|
||||
- You can also build specific sub-components; call `build.py` to see your options. \
|
||||
For each action, use `-h` to access help (e.g. `./build.py binary -h`)
|
||||
- Configure the build by using `config.prop`. A sample `config.prop.sample` is provided.
|
||||
|
||||
## IDE Support
|
||||
|
||||
- The repository can be directly opened with Android Studio as a project.
|
||||
- The Kotlin, Java, C++, and C code in the project should be properly supported in Android Studio out of the box.
|
||||
- Run `./build.py binary` before working on native code, as some generated code is only created during the build process.
|
||||
|
||||
### Developing Rust in Android Studio
|
||||
|
||||
Because the Magisk NDK package, [ONDK](https://github.com/topjohnwu/ondk) (the one installed with `./build.py ndk`), contains a fully self contained Clang + Rust toolchain, building the Magisk project alone does not require configuring toolchains. However, due to the way how the Intellij Rust plugin works, you'll have to go through some additional setup to make Android Studio work with Magisk's Rust codebase:
|
||||
|
||||
- Install [rustup](https://rustup.rs/), the official Rust toolchain manager
|
||||
- Link the ONDK Rust toolchain and set it as default:
|
||||
|
||||
```bash
|
||||
# Link the ONDK toolchain with the name "magisk"
|
||||
rustup toolchain link magisk "$ANDROID_SDK_ROOT/ndk/magisk/toolchains/rust"
|
||||
# Set as default
|
||||
rustup default magisk
|
||||
```
|
||||
|
||||
- Install the [Intellij Rust plugin](https://www.jetbrains.com/rust/) in Android Studio
|
||||
- In Preferences > Languages & Frameworks > Rust, set `$ANDROID_SDK_ROOT/ndk/magisk/toolchains/rust/bin` as the toolchain location
|
||||
- Open `native/src/Cargo.toml`, and select "Attach" in the "No Cargo projects found" banner
|
||||
|
||||
## Signing and Distribution
|
||||
|
||||
- In release builds, the certificate of the key signing the Magisk APK will be used by Magisk's root daemon as a reference to reject and forcefully uninstall any non-matching Magisk apps to protect users from malicious and unverified Magisk APKs.
|
||||
- To do any development on Magisk itself, switch to an **official debug build and reinstall Magisk** to turn off the signature check.
|
||||
- To distribute your own Magisk builds signed with your own keys, set your signing configs in `config.prop`.
|
||||
- Check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key) for more details on generating your own key.
|
@@ -1,5 +1,30 @@
|
||||
# Magisk Changelog
|
||||
|
||||
### v26.3
|
||||
|
||||
- [General] Fix device information detection script
|
||||
- [General] Update BusyBox to 1.36.1
|
||||
- [General] Update toolchain that produces broken arm32 executables
|
||||
- [App] Fix root service unable to bind on OnePlus devices
|
||||
|
||||
### v26.2
|
||||
|
||||
- [MagiskBoot] Support extracting boot image from `payload.bin`
|
||||
- [MagiskBoot] Support cpio files containing character files
|
||||
- [MagiskBoot] Support listing cpio content
|
||||
- [MagiskBoot] Directly handle AVB 1.0 signing and verification without going through Java implementation
|
||||
- [Daemon] Make daemon socket a fixed path in MAGISKTMP
|
||||
- [resetprop] Support printing property context
|
||||
- [resetprop] Support only printing persistent properties from storage
|
||||
- [resetprop] Properly support setting persistent properties bypassing property_service
|
||||
- [MagiskSU] Support `-g` and `-G` options
|
||||
- [MagiskSU] Support switching mount namespace to PID with `-t`
|
||||
- [MagiskPolicy] Fix patching extended permissions
|
||||
- [MagiskPolicy] Support more syntax for extended permissions
|
||||
- [MagiskPolicy] Support printing out the loaded sepolicy rules
|
||||
- [App] Support patching boot image from ROM zips
|
||||
- [App] Properly preserve `boot.img` when patching Samsung firmware with `init_boot.img`
|
||||
|
||||
### v26.1
|
||||
|
||||
- [App] Fix crashing when revoking root permissions
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
### Paths in "Magisk tmpfs directory"
|
||||
|
||||
Magisk will mount a `tmpfs` directory to store some temporary data. For devices with the `/sbin` folder, it will be chosen as it will also act as an overlay to inject binaries into `PATH`. From Android 11 onwards, the `/sbin` folder might not exist, so Magisk will randomly create a folder under `/dev` and use it as the base folder.
|
||||
Magisk will mount a `tmpfs` directory to store some temporary data. For devices with the `/sbin` folder, it will be chosen as it will also act as an overlay to inject binaries into `PATH`. From Android 11 onwards, the `/sbin` folder might not exist, so Magisk will use `/debug_ramdisk` as the base folder.
|
||||
|
||||
```
|
||||
# In order to get the current base folder Magisk is using,
|
||||
|
@@ -184,26 +184,28 @@ The `customize.sh` script runs in Magisk's BusyBox `ash` shell with "Standalone
|
||||
|
||||
```
|
||||
ui_print <msg>
|
||||
print <msg> to console
|
||||
Print <msg> to console
|
||||
Avoid using 'echo' as it will not display in custom recovery's console
|
||||
|
||||
abort <msg>
|
||||
print error message <msg> to console and terminate the installation
|
||||
Print error message <msg> to console and terminate the installation
|
||||
Avoid using 'exit' as it will skip the termination cleanup steps
|
||||
|
||||
set_perm <target> <owner> <group> <permission> [context]
|
||||
if [context] is not set, the default is "u:object_r:system_file:s0"
|
||||
this function is a shorthand for the following commands:
|
||||
If [context] is not specified, the default is "u:object_r:system_file:s0"
|
||||
This function is a shorthand for the following commands:
|
||||
chown owner.group target
|
||||
chmod permission target
|
||||
chcon context target
|
||||
|
||||
set_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission> [context]
|
||||
if [context] is not set, the default is "u:object_r:system_file:s0"
|
||||
for all files in <directory>, it will call:
|
||||
set_perm file owner group filepermission context
|
||||
for all directories in <directory> (including itself), it will call:
|
||||
set_perm dir owner group dirpermission context
|
||||
If [context] is not specified, the default is "u:object_r:system_file:s0"
|
||||
This function is a shorthand for the following psuedo code:
|
||||
set_perm <directory> owner group dirpermission context
|
||||
for file in <directory>:
|
||||
set_perm file owner group filepermission context
|
||||
for dir in <directory>:
|
||||
set_perm_recursive dir owner group dirpermission context
|
||||
```
|
||||
|
||||
For convenience, you can also declare a list of folders you want to replace in the variable name `REPLACE`. The module installer script will create the `.replace` file into the folders listed in `REPLACE`. For example:
|
||||
|
@@ -20,7 +20,7 @@ The result of **Ramdisk** determines whether your device has ramdisk in the boot
|
||||
|
||||
> _(Unfortunately, there are exceptions as some devices' bootloader accepts ramdisk even if it shouldn't. In this case, you will have to follow the instructions as if your device's boot partition **does** include ramdisk. There is no way to detect this, so the only way to know for sure is to actually try. Fortunately, as far as we know, only some Xiaomi devices are known to have this property, so most people can simply ignore this piece of information.)_
|
||||
|
||||
If you are using a Samsung device that is launched with Android 9.0 or higher, you can now jump to [its own section](#samsung-system-as-root).
|
||||
If you are using a Samsung device, you can now jump to [its own section](#samsung-devices).
|
||||
|
||||
If your device has boot ramdisk, get a copy of the `boot.img` (or `init_boot.img` if exists).<br>
|
||||
If your device does **NOT** have boot ramdisk, get a copy of the `recovery.img`.<br>
|
||||
@@ -80,30 +80,54 @@ As a summary, after installing Magisk in recovery **(starting from power off)**:
|
||||
|
||||
(Note: You **CANNOT** use custom recoveries to install or upgrade Magisk in this case!!)
|
||||
|
||||
## Samsung (System-as-root)
|
||||
## Samsung Devices
|
||||
|
||||
> If your Samsung device is NOT launched with Android 9.0 or higher, you are reading the wrong section.
|
||||
Before proceeding, please acknowledge that:
|
||||
|
||||
### Before Installing Magisk
|
||||
- Installing Magisk **WILL** trip your Knox Warranty Bit, this action is not reversible in any way.
|
||||
- Installing Magisk for the first time **REQUIRES** a full data wipe (this is **NOT** counting the data wipe when unlocking bootloader). Please make a backup your data.
|
||||
|
||||
- Installing Magisk **WILL** trip KNOX
|
||||
- Installing Magisk for the first time **REQUIRES** a full data wipe (this is **NOT** counting the data wipe when unlocking bootloader). Backup your data before continue.
|
||||
- Download Odin (for Windows) or [Heimdall](https://www.glassechidna.com.au/heimdall/) (for Linux) that supports your device.
|
||||
### Flashing Tools
|
||||
|
||||
### Unlocking Bootloader
|
||||
- [Samsung Odin3](https://dl2018.sammobile.com/Odin.zip) (Windows only) (requires [Samsung USB Drivers](https://developer.samsung.com/android-usb-driver))
|
||||
- [Samsung Odin4](https://forum.xda-developers.com/t/official-samsung-odin-v4-1-2-1-dc05e3ea-for-linux.4453423/) (Linux only)
|
||||
- [Heimdall](https://www.glassechidna.com.au/heimdall/) (or [Grimler's fork](https://git.sr.ht/~grimler/Heimdall))
|
||||
|
||||
Unlocking the bootloader on modern Samsung devices have some caveats. The newly introduced `VaultKeeper` service will make the bootloader reject any unofficial partitions in some circumstances.
|
||||
### Requirements
|
||||
|
||||
To verify whether or not Magisk can be installed in your Samsung device, you first must check the OEM Lock and KnoxGuard (RMM) status, to do so boot your device in Download mode with its key combo.
|
||||
|
||||
Possible OEM Lock values are the following:
|
||||
- **ON (L)**: fully locked.
|
||||
- **ON (U)**: bootloader locked, OEM unlocking enabled.
|
||||
- **OFF (U)**: fully unlocked.
|
||||
|
||||
To unlock your bootloader, follow the instructions below. If no OEM Lock value is shown in Download mode, your device is probably not unlockable due to market limitations (USA/Canada devices).
|
||||
|
||||
Possible KnoxGuard values are the following:
|
||||
|
||||
- `Active`, `Locked`: your device has been remotely locked by your telecom operator or your insurance company.
|
||||
- `Prenormal`: your device is temporarely locked, reaching 168h of uptime should trigger unlock.
|
||||
- `Checking`, `Completed`, `Broken`: your device is unlocked.
|
||||
|
||||
Having KnoxGuard active will prevent you from installing/running Magisk regardless of your bootloader lock state.
|
||||
|
||||
### Unlocking the bootloader
|
||||
|
||||
- Allow bootloader unlocking in **Developer options → OEM unlocking**
|
||||
- Reboot to download mode: power off your device and press the download mode key combo for your device
|
||||
- Long press volume up to unlock the bootloader. **This will wipe your data and automatically reboot.**
|
||||
- Go through the initial setup. Skip through all the steps since data will be wiped again in later steps. **Connect the device to Internet during the setup.**
|
||||
- Enable developer options, and **confirm that the OEM unlocking option exists and is grayed out.** This means the `VaultKeeper` service has unleashed the bootloader.
|
||||
- Enable developer options, and **confirm that the OEM unlocking option exists and is grayed out.** This means KnoxGuard hasn't locked your device.
|
||||
- Your bootloader now accepts unofficial images in download mode
|
||||
|
||||
### Instructions
|
||||
|
||||
- Use either [samfirm.js](https://github.com/jesec/samfirm.js), [Frija](https://forum.xda-developers.com/s10-plus/how-to/tool-frija-samsung-firmware-downloader-t3910594), or [Samloader](https://forum.xda-developers.com/s10-plus/how-to/tool-samloader-samfirm-frija-replacement-t4105929) to download the latest firmware zip of your device directly from Samsung servers.
|
||||
- Download the latest firmware package for your device, you can use one of the tools below to download it directly from Samsung servers:
|
||||
- [SamFirm.NET](https://github.com/jesec/SamFirm.NET), [samfirm.js](https://github.com/jesec/samfirm.js)
|
||||
- [Frija](https://forum.xda-developers.com/s10-plus/how-to/tool-frija-samsung-firmware-downloader-t3910594)
|
||||
- [Samloader](https://forum.xda-developers.com/s10-plus/how-to/tool-samloader-samfirm-frija-replacement-t4105929)
|
||||
- [Bifrost](https://forum.xda-developers.com/t/tool-samsung-samsung-firmware-downloader.4240719/)
|
||||
- Unzip the firmware and copy the `AP` tar file to your device. It is normally named as `AP_[device_model_sw_ver].tar.md5`
|
||||
- Press the **Install** button in the Magisk card
|
||||
- If your device does **NOT** have boot ramdisk, check the **"Recovery Mode"** option
|
||||
@@ -112,7 +136,7 @@ Unlocking the bootloader on modern Samsung devices have some caveats. The newly
|
||||
`adb pull /sdcard/Download/magisk_patched_[random_strings].tar`<br>
|
||||
**DO NOT USE MTP** as it is known to corrupt large files.
|
||||
- Reboot to download mode. Open Odin on your PC, and flash `magisk_patched.tar` as `AP`, together with `BL`, `CP`, and `CSC` (**NOT** `HOME_CSC` because we want to **wipe data**) from the original firmware.
|
||||
- Your device should reboot automatically once Odin finished flashing. Agree to do a factory reset if asked.
|
||||
- Your device should reboot automatically once Odin finished flashing. **Agree to do a factory reset if asked.**
|
||||
- If your device does **NOT** have boot ramdisk, reboot to recovery now to enable Magisk (reason stated in [Magisk in Recovery](#magisk-in-recovery)).
|
||||
- Install the Magisk app you've already downloaded and launch the app. It should show a dialog asking for additional setup.
|
||||
- Let the app do its job and automatically reboot the device. Voila!
|
||||
@@ -123,9 +147,8 @@ Once you have rooted your Samsung device, you can no longer upgrade your Android
|
||||
|
||||
### Important Notes
|
||||
|
||||
- **Never, ever** try to restore either `boot`, `recovery`, or `vbmeta` partitions back to stock! You can brick your device by doing so, and the only way to recover from this is to do a full Odin restore with data wipe.
|
||||
- **Never, ever** try to restore either `boot`, `init_boot`, `recovery`, or `vbmeta` partitions back to stock! You can brick your device by doing so, and the only way to recover from this is to do a full Odin restore with data wipe.
|
||||
- To upgrade your device with a new firmware, **NEVER** directly use the stock `AP` tar file with reasons mentioned above. **Always** patch `AP` in the Magisk app and use that instead.
|
||||
- Never just flash only `AP`, or else Odin may shrink your `/data` filesystem size. Flash `AP` + `BL` + `CP` + `HOME_CSC` when upgrading.
|
||||
|
||||
## Custom Recovery
|
||||
|
||||
|
19
docs/releases/26200.md
Normal file
19
docs/releases/26200.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## 2023.8.27 Magisk v26.2
|
||||
|
||||
- [MagiskBoot] Support extracting boot image from `payload.bin`
|
||||
- [MagiskBoot] Support cpio files containing character files
|
||||
- [MagiskBoot] Support listing cpio content
|
||||
- [MagiskBoot] Directly handle AVB 1.0 signing and verification without going through Java implementation
|
||||
- [Daemon] Make daemon socket a fixed path in MAGISKTMP
|
||||
- [resetprop] Support printing property context
|
||||
- [resetprop] Support only printing persistent properties from storage
|
||||
- [resetprop] Properly support setting persistent properties bypassing property_service
|
||||
- [MagiskSU] Support `-g` and `-G` options
|
||||
- [MagiskSU] Support switching mount namespace to PID with `-t`
|
||||
- [MagiskPolicy] Fix patching extended permissions
|
||||
- [MagiskPolicy] Support more syntax for extended permissions
|
||||
- [MagiskPolicy] Support printing out the loaded sepolicy rules
|
||||
- [App] Support patching boot image from ROM zips
|
||||
- [App] Properly preserve `boot.img` when patching Samsung firmware with `init_boot.img`
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
28
docs/releases/26300.md
Normal file
28
docs/releases/26300.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## 2023.9.4 Magisk v26.3
|
||||
|
||||
### v26.3
|
||||
|
||||
- [General] Fix device information detection script
|
||||
- [General] Update BusyBox to 1.36.1
|
||||
- [General] Update toolchain that produces broken arm32 executables
|
||||
- [App] Fix root service unable to bind on OnePlus devices
|
||||
|
||||
### v26.2
|
||||
|
||||
- [MagiskBoot] Support extracting boot image from `payload.bin`
|
||||
- [MagiskBoot] Support cpio files containing character files
|
||||
- [MagiskBoot] Support listing cpio content
|
||||
- [MagiskBoot] Directly handle AVB 1.0 signing and verification without going through Java implementation
|
||||
- [Daemon] Make daemon socket a fixed path in MAGISKTMP
|
||||
- [resetprop] Support printing property context
|
||||
- [resetprop] Support only printing persistent properties from storage
|
||||
- [resetprop] Properly support setting persistent properties bypassing property_service
|
||||
- [MagiskSU] Support `-g` and `-G` options
|
||||
- [MagiskSU] Support switching mount namespace to PID with `-t`
|
||||
- [MagiskPolicy] Fix patching extended permissions
|
||||
- [MagiskPolicy] Support more syntax for extended permissions
|
||||
- [MagiskPolicy] Support printing out the loaded sepolicy rules
|
||||
- [App] Support patching boot image from ROM zips
|
||||
- [App] Properly preserve `boot.img` when patching Samsung firmware with `init_boot.img`
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
@@ -1,5 +1,7 @@
|
||||
# Release Notes
|
||||
|
||||
- [v26.3](26300.md)
|
||||
- [v26.2](26200.md)
|
||||
- [v26.1](26100.md)
|
||||
- [v26.0](26000.md)
|
||||
- [v25.2](25200.md)
|
||||
|
@@ -10,7 +10,7 @@
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
org.gradle.jvmargs=-Xmx2560m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx2560m -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
@@ -23,9 +23,9 @@ org.gradle.caching=true
|
||||
# Android
|
||||
android.useAndroidX=true
|
||||
android.injected.testOnly=false
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonFinalResIds=false
|
||||
|
||||
# Magisk
|
||||
magisk.stubVersion=36
|
||||
magisk.versionCode=26100
|
||||
magisk.ondkVersion=r25.2
|
||||
magisk.stubVersion=37
|
||||
magisk.versionCode=26300
|
||||
magisk.ondkVersion=r26.0
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-rc-2-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
7
gradlew
vendored
7
gradlew
vendored
@@ -85,9 +85,6 @@ done
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
@@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
|
2
native/.gitignore
vendored
2
native/.gitignore
vendored
@@ -3,3 +3,5 @@ obj
|
||||
libs
|
||||
/.externalNativeBuild
|
||||
/.cxx
|
||||
*-rs.cpp
|
||||
*-rs.hpp
|
||||
|
@@ -1,27 +0,0 @@
|
||||
# Native Development
|
||||
|
||||
## Prerequisite
|
||||
|
||||
Install the NDK required to build and develop Magisk with `./build.py ndk`. The NDK will be installed to `$ANDROID_SDK_ROOT/ndk/magisk`. You don't need to manually install a Rust toolchain with `rustup`, as the NDK installed already has a Rust toolchain bundled.
|
||||
|
||||
## Build Configs
|
||||
|
||||
All C/C++ code and its dependencies are built with [`ndk-build`](https://developer.android.com/ndk/guides/ndk-build) and configured with several `*.mk` files scattered in many places.
|
||||
|
||||
The `src` folder is also a proper Cargo workspace, and all Rust code is built with `cargo` just like normal Rust projects.
|
||||
|
||||
## Rust + C/C++
|
||||
|
||||
To reduce complexity involved in linking, all Rust code is built as `staticlib` and linked to C++ targets to ensure our final product is built with an officially supported NDK build system. Each C++ target can at most link to **one** Rust `staticlib` or else multiple definitions error will occur.
|
||||
|
||||
We use the [`cxx`](https://cxx.rs) project for Rust and C++ interop.
|
||||
|
||||
## Development / IDE
|
||||
|
||||
All C++ code should be recognized and properly indexed by Android Studio out of the box. For Rust:
|
||||
|
||||
- Install the [Rust plugin](https://www.jetbrains.com/rust/) in Android Studio
|
||||
- In Preferences > Languages & Frameworks > Rust, set `$ANDROID_SDK_ROOT/ndk/magisk/toolchains/rust/bin` as the toolchain location
|
||||
- Open `native/src/Cargo.toml`, and select "Attach" in the "No Cargo projects found" banner
|
||||
|
||||
Note: run `./build.py binary` before developing to make sure generated code is created.
|
@@ -1,4 +1,25 @@
|
||||
[build]
|
||||
# This is only used to make the IDE happy, the actual compilation will
|
||||
# have the target overriden by command-line
|
||||
# Choose arm64 as the default target to make the IDE happy.
|
||||
# The actual compilation will have the target overriden by command-line.
|
||||
target = "aarch64-linux-android"
|
||||
|
||||
[unstable]
|
||||
build-std = ["std", "panic_abort"]
|
||||
build-std-features = ["panic_immediate_abort"]
|
||||
profile-rustflags = true
|
||||
|
||||
# Workaround bug for undefined symbol errors that occur with the
|
||||
# combination of `-Zbuild-std`, `opt-level = "z"`, and `lto = true`.
|
||||
# compiler_builtins are expected to be built with special flags.
|
||||
# https://github.com/rust-lang/rust/issues/108853
|
||||
# https://github.com/rust-lang/wg-cargo-std-aware/issues/62
|
||||
|
||||
[profile.release.package.compiler_builtins]
|
||||
rustflags = ["-Zshare-generics=off"]
|
||||
overflow-checks = false
|
||||
debug-assertions = false
|
||||
|
||||
[profile.dev.package.compiler_builtins]
|
||||
rustflags = ["-Zshare-generics=off"]
|
||||
overflow-checks = false
|
||||
debug-assertions = false
|
||||
|
@@ -14,7 +14,6 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
libsystemproperties \
|
||||
libphmap \
|
||||
liblsplt \
|
||||
libmincrypt \
|
||||
libmagisk-rs
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
@@ -25,18 +24,17 @@ LOCAL_SRC_FILES := \
|
||||
core/socket.cpp \
|
||||
core/db.cpp \
|
||||
core/package.cpp \
|
||||
core/cert.cpp \
|
||||
core/scripting.cpp \
|
||||
core/restorecon.cpp \
|
||||
core/module.cpp \
|
||||
core/logging.cpp \
|
||||
core/thread.cpp \
|
||||
resetprop/persist.cpp \
|
||||
resetprop/resetprop.cpp \
|
||||
su/su.cpp \
|
||||
su/connect.cpp \
|
||||
su/pts.cpp \
|
||||
su/su_daemon.cpp \
|
||||
core/resetprop/persist.cpp \
|
||||
core/resetprop/resetprop.cpp \
|
||||
core/core-rs.cpp \
|
||||
core/su/su.cpp \
|
||||
core/su/connect.cpp \
|
||||
core/su/pts.cpp \
|
||||
core/su/su_daemon.cpp \
|
||||
zygisk/entry.cpp \
|
||||
zygisk/main.cpp \
|
||||
zygisk/utils.cpp \
|
||||
@@ -86,7 +84,8 @@ LOCAL_SRC_FILES := \
|
||||
init/rootdir.cpp \
|
||||
init/getinfo.cpp \
|
||||
init/twostage.cpp \
|
||||
init/selinux.cpp
|
||||
init/selinux.cpp \
|
||||
init/init-rs.cpp
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
@@ -99,7 +98,6 @@ LOCAL_MODULE := magiskboot
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libbase \
|
||||
libcompat \
|
||||
libmincrypt \
|
||||
liblzma \
|
||||
liblz4 \
|
||||
libbz2 \
|
||||
@@ -111,13 +109,10 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
LOCAL_SRC_FILES := \
|
||||
boot/main.cpp \
|
||||
boot/bootimg.cpp \
|
||||
boot/hexpatch.cpp \
|
||||
boot/compress.cpp \
|
||||
boot/format.cpp \
|
||||
boot/dtb.cpp \
|
||||
boot/ramdisk.cpp \
|
||||
boot/pattern.cpp \
|
||||
boot/cpio.cpp
|
||||
boot/boot-rs.cpp
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
@@ -150,8 +145,8 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
core/applet_stub.cpp \
|
||||
resetprop/resetprop.cpp \
|
||||
resetprop/persist.cpp \
|
||||
core/resetprop/resetprop.cpp \
|
||||
core/resetprop/persist.cpp
|
||||
|
||||
LOCAL_CFLAGS := -DAPPLET_STUB_MAIN=resetprop_main
|
||||
include $(BUILD_EXECUTABLE)
|
||||
@@ -174,7 +169,8 @@ LOCAL_SRC_FILES := \
|
||||
sepolicy/sepolicy.cpp \
|
||||
sepolicy/rules.cpp \
|
||||
sepolicy/policydb.cpp \
|
||||
sepolicy/statement.cpp
|
||||
sepolicy/statement.cpp \
|
||||
sepolicy/policy-rs.cpp
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include src/Android-rs.mk
|
||||
|
935
native/src/Cargo.lock
generated
935
native/src/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,47 @@
|
||||
[workspace]
|
||||
exclude = ["external"]
|
||||
members = ["base", "boot", "core", "init", "sepolicy"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
cxx = { path = "external/cxx-rs" }
|
||||
cxx-gen = { path = "external/cxx-rs/gen/lib" }
|
||||
libc = "0.2"
|
||||
cfg-if = "1.0"
|
||||
num-traits = "0.2"
|
||||
num-derive = "0.3"
|
||||
thiserror = "1.0"
|
||||
byteorder = "1"
|
||||
size = "0.4"
|
||||
sha1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
digest = "0.10"
|
||||
p256 = "0.13"
|
||||
p384 = "0.13"
|
||||
rsa = "0.9"
|
||||
x509-cert = "0.2"
|
||||
der = "0.7"
|
||||
|
||||
[workspace.dependencies.argh]
|
||||
git = "https://github.com/topjohnwu/argh.git"
|
||||
rev = "f0ca7d9d7f66b4ff56fbbd44ce19a96c625bffd8"
|
||||
|
||||
[workspace.dependencies.pb-rs]
|
||||
git = "https://github.com/tafia/quick-protobuf.git"
|
||||
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
|
||||
|
||||
[workspace.dependencies.quick-protobuf]
|
||||
git = "https://github.com/tafia/quick-protobuf.git"
|
||||
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[patch.crates-io]
|
||||
cxx = { path = "external/cxx-rs" }
|
||||
|
@@ -4,10 +4,15 @@ LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libbase
|
||||
LOCAL_C_INCLUDES := src/include $(LOCAL_PATH)/include out/generated
|
||||
LOCAL_C_INCLUDES := \
|
||||
src/include \
|
||||
$(LOCAL_PATH)/include \
|
||||
$(LOCAL_PATH)/../external/cxx-rs/include \
|
||||
out/generated
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_EXPORT_STATIC_LIBRARIES := libcxx
|
||||
LOCAL_STATIC_LIBRARIES := libcxx
|
||||
LOCAL_CFLAGS := -DRUST_CXX_NO_EXCEPTIONS
|
||||
LOCAL_SRC_FILES := \
|
||||
new.cpp \
|
||||
files.cpp \
|
||||
@@ -15,6 +20,7 @@ LOCAL_SRC_FILES := \
|
||||
selinux.cpp \
|
||||
logging.cpp \
|
||||
stream.cpp \
|
||||
base-rs.cpp \
|
||||
../external/cxx-rs/src/cxx.cc
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
@@ -22,10 +28,13 @@ include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libcompat
|
||||
# Workaround "hacky" libc.a missing symbols
|
||||
# To build Magisk with vanilla NDK, comment out the next line
|
||||
LOCAL_SRC_FILES := compat/compat.cpp
|
||||
# Fix static variables' ctor/dtor when using LTO
|
||||
# See: https://github.com/android/ndk/issues/1461
|
||||
LOCAL_EXPORT_LDFLAGS := -static -T src/lto_fix.lds
|
||||
LOCAL_EXPORT_LDFLAGS := -static -T src/lto_fix.lds -Wl,--wrap=rename -Wl,--wrap=renameat
|
||||
# For some reason, using the hacky libc.a with x86 will trigger stack protection violation
|
||||
# when mixing Rust and C++ code. Disable stack protector to bypass this issue.
|
||||
ifeq ($(TARGET_ARCH), x86)
|
||||
LOCAL_EXPORT_CFLAGS := -fno-stack-protector
|
||||
endif
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
@@ -6,7 +6,12 @@ edition = "2021"
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[build-dependencies]
|
||||
cxx-gen = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
cxx = { path = "../external/cxx-rs" }
|
||||
libc = "0.2"
|
||||
cfg-if = "1.0"
|
||||
cxx = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
argh = { workspace = true }
|
||||
|
8
native/src/base/build.rs
Normal file
8
native/src/base/build.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use crate::gen::gen_cxx_binding;
|
||||
|
||||
#[path = "../include/gen.rs"]
|
||||
mod gen;
|
||||
|
||||
fn main() {
|
||||
gen_cxx_binding("base-rs");
|
||||
}
|
@@ -1,17 +1,21 @@
|
||||
// This file implements all missing symbols that should exist in normal API 21
|
||||
// This file implements all missing symbols that should exist in normal API 23
|
||||
// libc.a but missing in our extremely lean libc.a replacements.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <mntent.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
#if !defined(__LP64__)
|
||||
extern "C" {
|
||||
|
||||
// Add "hacky" libc.a missing symbols back
|
||||
// All symbols in this file are weak, so a vanilla NDK should still link properly
|
||||
|
||||
#include "fortify.hpp"
|
||||
|
||||
// Original source: https://github.com/freebsd/freebsd/blob/master/contrib/file/src/getline.c
|
||||
// License: BSD, full copyright notice please check original source
|
||||
@@ -58,41 +62,8 @@ ssize_t getline(char **buf, size_t *bufsiz, FILE *fp) {
|
||||
return getdelim(buf, bufsiz, '\n', fp);
|
||||
}
|
||||
|
||||
[[gnu::weak]]
|
||||
FILE *setmntent(const char *path, const char *mode) {
|
||||
return fopen(path, mode);
|
||||
}
|
||||
|
||||
[[gnu::weak]]
|
||||
int endmntent(FILE *fp) {
|
||||
if (fp != nullptr) {
|
||||
fclose(fp);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Missing system call wrappers
|
||||
|
||||
[[gnu::weak]]
|
||||
int setns(int fd, int nstype) {
|
||||
return syscall(__NR_setns, fd, nstype);
|
||||
}
|
||||
|
||||
[[gnu::weak]]
|
||||
int unshare(int flags) {
|
||||
return syscall(__NR_unshare, flags);
|
||||
}
|
||||
|
||||
[[gnu::weak]]
|
||||
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) {
|
||||
return syscall(__NR_accept4, sockfd, addr, addrlen, flags);
|
||||
}
|
||||
|
||||
[[gnu::weak]]
|
||||
int dup3(int oldfd, int newfd, int flags) {
|
||||
return syscall(__NR_dup3, oldfd, newfd, flags);
|
||||
}
|
||||
|
||||
[[gnu::weak]]
|
||||
ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz) {
|
||||
return syscall(__NR_readlinkat, dirfd, pathname, buf, bufsiz);
|
||||
@@ -109,11 +80,6 @@ int linkat(int olddirfd, const char *oldpath,
|
||||
return syscall(__NR_linkat, olddirfd, oldpath, newdirfd, newpath, flags);
|
||||
}
|
||||
|
||||
[[gnu::weak]]
|
||||
int inotify_init1(int flags) {
|
||||
return syscall(__NR_inotify_init1, flags);
|
||||
}
|
||||
|
||||
[[gnu::weak]]
|
||||
int faccessat(int dirfd, const char *pathname, int mode, int flags) {
|
||||
return syscall(__NR_faccessat, dirfd, pathname, mode, flags);
|
||||
@@ -142,39 +108,26 @@ int ftruncate64(int fd, off64_t length) {
|
||||
[[gnu::weak]]
|
||||
void android_set_abort_message(const char *) {}
|
||||
|
||||
// Original source: <android/legacy_signal_inlines.h>
|
||||
[[gnu::weak]]
|
||||
int sigaddset(sigset_t *set, int signum) {
|
||||
/* Signal numbers start at 1, but bit positions start at 0. */
|
||||
int bit = signum - 1;
|
||||
auto *local_set = (unsigned long *)set;
|
||||
if (set == nullptr || bit < 0 || bit >= (int)(8 * sizeof(sigset_t))) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
local_set[bit / LONG_BIT] |= 1UL << (bit % LONG_BIT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[gnu::weak]]
|
||||
int sigemptyset(sigset_t *set) {
|
||||
if (set == nullptr) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(set, 0, sizeof(sigset_t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef vsnprintf
|
||||
#undef snprintf
|
||||
#include "fortify.hpp"
|
||||
|
||||
extern FILE __sF[];
|
||||
|
||||
[[gnu::weak]] FILE* stdin = &__sF[0];
|
||||
[[gnu::weak]] FILE* stdout = &__sF[1];
|
||||
[[gnu::weak]] FILE* stderr = &__sF[2];
|
||||
|
||||
#endif // !defined(__LP64__)
|
||||
|
||||
[[maybe_unused]]
|
||||
int __wrap_renameat(int old_dir_fd, const char *old_path, int new_dir_fd, const char *new_path) {
|
||||
long out = syscall(__NR_renameat, old_dir_fd, old_path, new_dir_fd, new_path);
|
||||
if (out == -1 && errno == ENOSYS) {
|
||||
out = syscall(__NR_renameat2, old_dir_fd, old_path, new_dir_fd, new_path, 0);
|
||||
}
|
||||
return static_cast<int>(out);
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
int __wrap_rename(const char *old_path, const char *new_path) {
|
||||
return __wrap_renameat(AT_FDCWD, old_path, AT_FDCWD, new_path);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
@@ -140,5 +140,5 @@ int __openat_2(int fd, const char* pathname, int flags) {
|
||||
int __vsnprintf_chk(char* dst, size_t supplied_size, int /*flags*/,
|
||||
size_t dst_len_from_compiler, const char* format, va_list va) {
|
||||
__check_buffer_access("vsnprintf", "write into", supplied_size, dst_len_from_compiler);
|
||||
return vsnprintf(dst, supplied_size, format, va);
|
||||
return __call_bypassing_fortify(vsnprintf)(dst, supplied_size, format, va);
|
||||
}
|
||||
|
68
native/src/base/cxx_extern.rs
Normal file
68
native/src/base/cxx_extern.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
// Functions in this file are only for exporting to C++, DO NOT USE IN RUST
|
||||
|
||||
use std::fmt::Write;
|
||||
use std::io;
|
||||
use std::os::fd::{BorrowedFd, OwnedFd, RawFd};
|
||||
|
||||
use cxx::private::c_char;
|
||||
use libc::mode_t;
|
||||
|
||||
pub(crate) use crate::xwrap::*;
|
||||
use crate::{
|
||||
fd_path, map_fd, map_file, mkdirs, realpath, rm_rf, slice_from_ptr_mut, Directory, ResultExt,
|
||||
Utf8CStr,
|
||||
};
|
||||
|
||||
pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize {
|
||||
fd_path(fd, buf)
|
||||
.log_cxx_with_msg(|w| w.write_str("fd_path failed"))
|
||||
.map_or(-1_isize, |v| v as isize)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
|
||||
match Utf8CStr::from_ptr(path) {
|
||||
Ok(p) => realpath(p, slice_from_ptr_mut(buf, bufsz)).map_or(-1, |v| v as isize),
|
||||
Err(_) => -1,
|
||||
}
|
||||
}
|
||||
|
||||
#[export_name = "mkdirs"]
|
||||
unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 {
|
||||
match Utf8CStr::from_ptr(path) {
|
||||
Ok(p) => mkdirs(p, mode).map_or(-1, |_| 0),
|
||||
Err(_) => -1,
|
||||
}
|
||||
}
|
||||
|
||||
#[export_name = "rm_rf"]
|
||||
unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool {
|
||||
match Utf8CStr::from_ptr(path) {
|
||||
Ok(p) => rm_rf(p).map_or(false, |_| true),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool {
|
||||
fn inner(fd: OwnedFd) -> io::Result<()> {
|
||||
Directory::try_from(fd)?.remove_all()
|
||||
}
|
||||
inner(fd).map_or(false, |_| true)
|
||||
}
|
||||
|
||||
pub(crate) fn map_file_for_cxx(path: &[u8], rw: bool) -> &'static mut [u8] {
|
||||
unsafe {
|
||||
map_file(Utf8CStr::from_bytes_unchecked(path), rw)
|
||||
.log_cxx()
|
||||
.unwrap_or(&mut [])
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_fd_for_cxx(fd: RawFd, sz: usize, rw: bool) -> &'static mut [u8] {
|
||||
unsafe {
|
||||
map_fd(BorrowedFd::borrow_raw(fd), sz, rw)
|
||||
.log_cxx()
|
||||
.unwrap_or(&mut [])
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <linux/fs.h>
|
||||
@@ -6,13 +7,12 @@
|
||||
#include <libgen.h>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <misc.hpp>
|
||||
#include <selinux.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int fd_pathat(int dirfd, const char *name, char *path, size_t size) {
|
||||
if (fd_path(dirfd, byte_slice(path, size)) < 0)
|
||||
if (fd_path(dirfd, byte_data(path, size)) < 0)
|
||||
return -1;
|
||||
auto len = strlen(path);
|
||||
path[len] = '/';
|
||||
@@ -20,65 +20,6 @@ int fd_pathat(int dirfd, const char *name, char *path, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
static void post_order_walk(int dirfd, const Func &fn) {
|
||||
auto dir = xopen_dir(dirfd);
|
||||
if (!dir) return;
|
||||
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
if (entry->d_type == DT_DIR)
|
||||
post_order_walk(xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC), fn);
|
||||
fn(dirfd, entry);
|
||||
}
|
||||
}
|
||||
|
||||
enum walk_result {
|
||||
CONTINUE, SKIP, ABORT
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
static walk_result pre_order_walk(int dirfd, const Func &fn) {
|
||||
auto dir = xopen_dir(dirfd);
|
||||
if (!dir) {
|
||||
close(dirfd);
|
||||
return SKIP;
|
||||
}
|
||||
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
switch (fn(dirfd, entry)) {
|
||||
case CONTINUE:
|
||||
break;
|
||||
case SKIP:
|
||||
continue;
|
||||
case ABORT:
|
||||
return ABORT;
|
||||
}
|
||||
if (entry->d_type == DT_DIR) {
|
||||
int fd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
if (pre_order_walk(fd, fn) == ABORT)
|
||||
return ABORT;
|
||||
}
|
||||
}
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
static void remove_at(int dirfd, struct dirent *entry) {
|
||||
unlinkat(dirfd, entry->d_name, entry->d_type == DT_DIR ? AT_REMOVEDIR : 0);
|
||||
}
|
||||
|
||||
void rm_rf(const char *path) {
|
||||
struct stat st;
|
||||
if (lstat(path, &st) < 0)
|
||||
return;
|
||||
if (S_ISDIR(st.st_mode))
|
||||
frm_rf(xopen(path, O_RDONLY | O_CLOEXEC));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void frm_rf(int dirfd) {
|
||||
post_order_walk(dirfd, remove_at);
|
||||
}
|
||||
|
||||
void mv_path(const char *src, const char *dest) {
|
||||
file_attr attr;
|
||||
getattr(src, &attr);
|
||||
@@ -433,77 +374,25 @@ sFILE make_file(FILE *fp) {
|
||||
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
|
||||
}
|
||||
|
||||
int byte_data::patch(bool log, str_pairs list) {
|
||||
if (buf == nullptr)
|
||||
return 0;
|
||||
int count = 0;
|
||||
for (uint8_t *p = buf, *eof = buf + sz; p < eof; ++p) {
|
||||
for (auto [from, to] : list) {
|
||||
if (memcmp(p, from.data(), from.length() + 1) == 0) {
|
||||
if (log) LOGD("Replace [%s] -> [%s]\n", from.data(), to.data());
|
||||
memset(p, 0, from.length());
|
||||
memcpy(p, to.data(), to.length());
|
||||
++count;
|
||||
p += from.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool byte_data::contains(string_view pattern, bool log) const {
|
||||
if (buf == nullptr)
|
||||
return false;
|
||||
for (uint8_t *p = buf, *eof = buf + sz; p < eof; ++p) {
|
||||
if (memcmp(p, pattern.data(), pattern.length() + 1) == 0) {
|
||||
if (log) LOGD("Found pattern [%s]\n", pattern.data());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void byte_data::swap(byte_data &o) {
|
||||
std::swap(buf, o.buf);
|
||||
std::swap(sz, o.sz);
|
||||
}
|
||||
|
||||
mmap_data::mmap_data(const char *name, bool rw) {
|
||||
int fd = xopen(name, (rw ? O_RDWR : O_RDONLY) | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return;
|
||||
struct stat st;
|
||||
if (fstat(fd, &st))
|
||||
return;
|
||||
if (S_ISBLK(st.st_mode)) {
|
||||
uint64_t size;
|
||||
ioctl(fd, BLKGETSIZE64, &size);
|
||||
sz = size;
|
||||
} else {
|
||||
sz = st.st_size;
|
||||
auto slice = rust::map_file(byte_view(name), rw);
|
||||
if (!slice.empty()) {
|
||||
_buf = slice.data();
|
||||
_sz = slice.size();
|
||||
}
|
||||
void *b = sz > 0
|
||||
? xmmap(nullptr, sz, PROT_READ | PROT_WRITE, rw ? MAP_SHARED : MAP_PRIVATE, fd, 0)
|
||||
: nullptr;
|
||||
close(fd);
|
||||
buf = static_cast<uint8_t *>(b);
|
||||
}
|
||||
|
||||
string find_apk_path(const char *pkg) {
|
||||
char buf[PATH_MAX];
|
||||
size_t len = strlen(pkg);
|
||||
pre_order_walk(xopen("/data/app", O_RDONLY), [&](int dfd, dirent *entry) -> walk_result {
|
||||
if (entry->d_type != DT_DIR)
|
||||
return SKIP;
|
||||
if (strncmp(entry->d_name, pkg, len) == 0 && entry->d_name[len] == '-') {
|
||||
fd_pathat(dfd, entry->d_name, buf, sizeof(buf));
|
||||
return ABORT;
|
||||
} else if (strncmp(entry->d_name, "~~", 2) == 0) {
|
||||
return CONTINUE;
|
||||
} else return SKIP;
|
||||
});
|
||||
string path(buf);
|
||||
return path.append("/base.apk");
|
||||
mmap_data::mmap_data(int fd, size_t sz, bool rw) {
|
||||
auto slice = rust::map_fd(fd, sz, rw);
|
||||
if (!slice.empty()) {
|
||||
_buf = slice.data();
|
||||
_sz = slice.size();
|
||||
}
|
||||
}
|
||||
|
||||
mmap_data::~mmap_data() {
|
||||
if (_buf)
|
||||
munmap(_buf, _sz);
|
||||
}
|
||||
|
||||
string resolve_preinit_dir(const char *base_dir) {
|
||||
|
@@ -1,14 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <mntent.h>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xwrap.hpp"
|
||||
#include <linux/fs.h>
|
||||
#include "misc.hpp"
|
||||
|
||||
template <typename T>
|
||||
static inline T align_to(T v, int a) {
|
||||
@@ -26,19 +25,6 @@ struct file_attr {
|
||||
char con[128];
|
||||
};
|
||||
|
||||
struct byte_data {
|
||||
using str_pairs = std::initializer_list<std::pair<std::string_view, std::string_view>>;
|
||||
|
||||
uint8_t *buf = nullptr;
|
||||
size_t sz = 0;
|
||||
|
||||
int patch(str_pairs list) { return patch(true, list); }
|
||||
int patch(bool log, str_pairs list);
|
||||
bool contains(std::string_view pattern, bool log = true) const;
|
||||
protected:
|
||||
void swap(byte_data &o);
|
||||
};
|
||||
|
||||
struct mount_info {
|
||||
unsigned int id;
|
||||
unsigned int parent;
|
||||
@@ -57,24 +43,25 @@ struct mount_info {
|
||||
};
|
||||
|
||||
struct mmap_data : public byte_data {
|
||||
mmap_data() = default;
|
||||
mmap_data(const mmap_data&) = delete;
|
||||
mmap_data(mmap_data &&o) { swap(o); }
|
||||
mmap_data(const char *name, bool rw = false);
|
||||
~mmap_data() { if (buf) munmap(buf, sz); }
|
||||
mmap_data& operator=(mmap_data &&other) { swap(other); return *this; }
|
||||
static_assert((sizeof(void *) == 8 && BLKGETSIZE64 == 0x80081272) ||
|
||||
(sizeof(void *) == 4 && BLKGETSIZE64 == 0x80041272));
|
||||
ALLOW_MOVE_ONLY(mmap_data)
|
||||
|
||||
explicit mmap_data(const char *name, bool rw = false);
|
||||
mmap_data(int fd, size_t sz, bool rw = false);
|
||||
~mmap_data();
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
int mkdirs(const char *path, mode_t mode);
|
||||
ssize_t canonical_path(const char * __restrict__ path, char * __restrict__ buf, size_t bufsiz);
|
||||
bool rm_rf(const char *path);
|
||||
bool frm_rf(int dirfd);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
using rust::fd_path;
|
||||
int fd_pathat(int dirfd, const char *name, char *path, size_t size);
|
||||
void rm_rf(const char *path);
|
||||
void mv_path(const char *src, const char *dest);
|
||||
void mv_dir(int src, int dest);
|
||||
void cp_afc(const char *src, const char *dest);
|
||||
@@ -103,10 +90,8 @@ void file_readline(const char *file, const std::function<bool(std::string_view)>
|
||||
void parse_prop_file(FILE *fp, const std::function<bool(std::string_view, std::string_view)> &fn);
|
||||
void parse_prop_file(const char *file,
|
||||
const std::function<bool(std::string_view, std::string_view)> &fn);
|
||||
void frm_rf(int dirfd);
|
||||
void clone_dir(int src, int dest);
|
||||
std::vector<mount_info> parse_mount_info(const char *pid);
|
||||
std::string find_apk_path(const char *pkg);
|
||||
std::string resolve_preinit_dir(const char *base_dir);
|
||||
|
||||
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
|
||||
|
@@ -1,142 +1,527 @@
|
||||
use mem::MaybeUninit;
|
||||
use std::cmp::min;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::BufRead;
|
||||
use std::io::{BufRead, Read, Seek, SeekFrom, Write};
|
||||
use std::ops::Deref;
|
||||
use std::os::android::fs::MetadataExt;
|
||||
use std::os::fd::{AsFd, BorrowedFd, IntoRawFd};
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::path::Path;
|
||||
use std::{io, mem, ptr, slice};
|
||||
|
||||
use libc::{c_char, c_uint, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH};
|
||||
use libc::{c_char, c_uint, dirent, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH, O_RDONLY, O_RDWR};
|
||||
|
||||
use crate::{bfmt_cstr, errno, xopen};
|
||||
use crate::{bfmt_cstr, copy_cstr, cstr, errno, error, FlatData, LibcReturn, Utf8CStr};
|
||||
|
||||
pub mod unsafe_impl {
|
||||
use std::ffi::CStr;
|
||||
|
||||
use libc::c_char;
|
||||
|
||||
use crate::slice_from_ptr_mut;
|
||||
|
||||
pub unsafe fn readlink(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
|
||||
let r = libc::readlink(path, buf.cast(), bufsz - 1);
|
||||
if r >= 0 {
|
||||
*buf.offset(r) = b'\0';
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
|
||||
super::realpath(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn __open_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> Option<OwnedFd> {
|
||||
pub fn __open_fd_impl(path: &Utf8CStr, flags: i32, mode: mode_t) -> io::Result<OwnedFd> {
|
||||
unsafe {
|
||||
let fd = libc::open(path.as_ptr(), flags, mode as c_uint);
|
||||
if fd >= 0 {
|
||||
Some(OwnedFd::from_raw_fd(fd))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn __xopen_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> Option<OwnedFd> {
|
||||
let fd = xopen(path.as_ptr(), flags, mode);
|
||||
if fd >= 0 {
|
||||
unsafe { Some(OwnedFd::from_raw_fd(fd)) }
|
||||
} else {
|
||||
None
|
||||
let fd = libc::open(path.as_ptr(), flags, mode as c_uint).check_os_err()?;
|
||||
Ok(OwnedFd::from_raw_fd(fd))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! open_fd {
|
||||
($path:expr, $flags:expr) => {
|
||||
crate::__open_fd_impl($path, $flags, 0)
|
||||
$crate::__open_fd_impl($path, $flags, 0)
|
||||
};
|
||||
($path:expr, $flags:expr, $mode:expr) => {
|
||||
crate::__open_fd_impl($path, $flags, $mode)
|
||||
$crate::__open_fd_impl($path, $flags, $mode)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! xopen_fd {
|
||||
($path:expr, $flags:expr) => {
|
||||
crate::__xopen_fd_impl($path, $flags, 0)
|
||||
};
|
||||
($path:expr, $flags:expr, $mode:expr) => {
|
||||
crate::__xopen_fd_impl($path, $flags, $mode)
|
||||
};
|
||||
pub(crate) unsafe fn readlink_unsafe(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
|
||||
let r = libc::readlink(path, buf.cast(), bufsz - 1);
|
||||
if r >= 0 {
|
||||
*buf.offset(r) = b'\0';
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
pub fn readlink(path: &CStr, data: &mut [u8]) -> isize {
|
||||
unsafe { unsafe_impl::readlink(path.as_ptr(), data.as_mut_ptr(), data.len()) }
|
||||
pub fn readlink(path: &Utf8CStr, data: &mut [u8]) -> io::Result<usize> {
|
||||
let r =
|
||||
unsafe { readlink_unsafe(path.as_ptr(), data.as_mut_ptr(), data.len()) }.check_os_err()?;
|
||||
Ok(r as usize)
|
||||
}
|
||||
|
||||
pub fn fd_path(fd: RawFd, buf: &mut [u8]) -> isize {
|
||||
let mut fd_buf: [u8; 40] = [0; 40];
|
||||
pub fn fd_path(fd: RawFd, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let mut fd_buf = [0_u8; 40];
|
||||
let fd_path = bfmt_cstr!(&mut fd_buf, "/proc/self/fd/{}", fd);
|
||||
readlink(fd_path, buf)
|
||||
}
|
||||
|
||||
// Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp
|
||||
pub fn realpath(path: &CStr, buf: &mut [u8]) -> isize {
|
||||
if let Some(fd) = open_fd!(path, O_PATH | O_CLOEXEC) {
|
||||
let mut st1: libc::stat;
|
||||
let mut st2: libc::stat;
|
||||
let mut skip_check = false;
|
||||
unsafe {
|
||||
st1 = std::mem::zeroed();
|
||||
if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 {
|
||||
// This shall only fail on Linux < 3.6
|
||||
skip_check = true;
|
||||
}
|
||||
}
|
||||
let len = fd_path(fd.as_raw_fd(), buf);
|
||||
unsafe {
|
||||
st2 = std::mem::zeroed();
|
||||
if libc::stat(buf.as_ptr().cast(), &mut st2) < 0
|
||||
|| (!skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino))
|
||||
{
|
||||
*errno() = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
} else {
|
||||
*errno() = ENOENT;
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn strscpy(dst: *mut c_char, src: *const c_char, size: usize) -> usize;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn mkdirs(path: *const c_char, mode: mode_t) -> i32 {
|
||||
pub fn realpath(path: &Utf8CStr, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let fd = open_fd!(path, O_PATH | O_CLOEXEC)?;
|
||||
let mut st1: libc::stat;
|
||||
let mut st2: libc::stat;
|
||||
let mut skip_check = false;
|
||||
unsafe {
|
||||
let mut buf = [0 as u8; 4096];
|
||||
let ptr: *mut c_char = buf.as_mut_ptr().cast();
|
||||
let len = strscpy(ptr, path, buf.len());
|
||||
let mut curr = &mut buf[1..len];
|
||||
while let Some(p) = curr.iter().position(|c| *c == b'/') {
|
||||
curr[p] = b'\0';
|
||||
if libc::mkdir(ptr, mode) < 0 && *errno() != EEXIST {
|
||||
return -1;
|
||||
st1 = mem::zeroed();
|
||||
if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 {
|
||||
// This shall only fail on Linux < 3.6
|
||||
skip_check = true;
|
||||
}
|
||||
}
|
||||
let len = fd_path(fd.as_raw_fd(), buf)?;
|
||||
unsafe {
|
||||
st2 = mem::zeroed();
|
||||
if libc::stat(buf.as_ptr().cast(), &mut st2) < 0
|
||||
|| (!skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino))
|
||||
{
|
||||
*errno() = ENOENT;
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
pub fn mkdirs(path: &Utf8CStr, mode: mode_t) -> io::Result<()> {
|
||||
let mut buf = [0_u8; 4096];
|
||||
let len = copy_cstr(&mut buf, path);
|
||||
let buf = &mut buf[..len];
|
||||
let mut off = 1;
|
||||
unsafe {
|
||||
while let Some(p) = buf[off..].iter().position(|c| *c == b'/') {
|
||||
buf[off + p] = b'\0';
|
||||
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
curr[p] = b'/';
|
||||
curr = &mut curr[(p + 1)..];
|
||||
buf[off + p] = b'/';
|
||||
off += p + 1;
|
||||
}
|
||||
if libc::mkdir(ptr, mode) < 0 && *errno() != EEXIST {
|
||||
return -1;
|
||||
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
0
|
||||
}
|
||||
*errno() = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait ReadExt {
|
||||
fn skip(&mut self, len: usize) -> io::Result<()>;
|
||||
fn read_flat_data<F: FlatData>(&mut self, data: &mut F) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Read> ReadExt for T {
|
||||
fn skip(&mut self, mut len: usize) -> io::Result<()> {
|
||||
let mut buf = MaybeUninit::<[u8; 4096]>::uninit();
|
||||
let buf = unsafe { buf.assume_init_mut() };
|
||||
while len > 0 {
|
||||
let l = min(buf.len(), len);
|
||||
self.read_exact(&mut buf[..l])?;
|
||||
len -= l;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_flat_data<F: FlatData>(&mut self, data: &mut F) -> io::Result<()> {
|
||||
self.read_exact(data.as_raw_bytes_mut())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_lines<P: AsRef<Path>>(path: P) -> io::Result<io::Lines<io::BufReader<File>>> {
|
||||
let file = File::open(path)?;
|
||||
Ok(io::BufReader::new(file).lines())
|
||||
pub trait ReadSeekExt {
|
||||
fn skip(&mut self, len: usize) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Read + Seek> ReadSeekExt for T {
|
||||
fn skip(&mut self, len: usize) -> io::Result<()> {
|
||||
if self.seek(SeekFrom::Current(len as i64)).is_err() {
|
||||
// If the file is not actually seekable, fallback to read
|
||||
ReadExt::skip(self, len)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BufReadExt {
|
||||
fn foreach_lines<F: FnMut(&mut String) -> bool>(&mut self, f: F);
|
||||
fn foreach_props<F: FnMut(&str, &str) -> bool>(&mut self, f: F);
|
||||
}
|
||||
|
||||
impl<T: BufRead> BufReadExt for T {
|
||||
fn foreach_lines<F: FnMut(&mut String) -> bool>(&mut self, mut f: F) {
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
match self.read_line(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(_) => {
|
||||
if !f(&mut buf) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn foreach_props<F: FnMut(&str, &str) -> bool>(&mut self, mut f: F) {
|
||||
self.foreach_lines(|line| {
|
||||
let line = line.trim();
|
||||
if line.starts_with('#') {
|
||||
return true;
|
||||
}
|
||||
if let Some((key, value)) = line.split_once('=') {
|
||||
return f(key.trim(), value.trim());
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WriteExt {
|
||||
fn write_zeros(&mut self, len: usize) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Write> WriteExt for T {
|
||||
fn write_zeros(&mut self, mut len: usize) -> io::Result<()> {
|
||||
let buf = [0_u8; 4096];
|
||||
while len > 0 {
|
||||
let l = min(buf.len(), len);
|
||||
self.write_all(&buf[..l])?;
|
||||
len -= l;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirEntry<'a> {
|
||||
dir: &'a Directory,
|
||||
entry: &'a dirent,
|
||||
d_name_len: usize,
|
||||
}
|
||||
|
||||
impl DirEntry<'_> {
|
||||
pub fn d_name(&self) -> &CStr {
|
||||
unsafe {
|
||||
CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(
|
||||
self.d_name.as_ptr().cast(),
|
||||
self.d_name_len,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let mut len = self.dir.path(buf)?;
|
||||
buf[len] = b'/';
|
||||
len += 1;
|
||||
len += copy_cstr(&mut buf[len..], self.d_name());
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
self.d_type == libc::DT_DIR
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
self.d_type == libc::DT_REG
|
||||
}
|
||||
|
||||
pub fn is_lnk(&self) -> bool {
|
||||
self.d_type == libc::DT_LNK
|
||||
}
|
||||
|
||||
pub fn unlink(&self) -> io::Result<()> {
|
||||
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
|
||||
unsafe {
|
||||
libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag).check_os_err()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn open_as_dir(&self) -> io::Result<Directory> {
|
||||
if !self.is_dir() {
|
||||
return Err(io::Error::from(io::ErrorKind::NotADirectory));
|
||||
}
|
||||
unsafe {
|
||||
let fd = libc::openat(
|
||||
self.dir.as_raw_fd(),
|
||||
self.d_name.as_ptr(),
|
||||
O_RDONLY | O_CLOEXEC,
|
||||
)
|
||||
.check_os_err()?;
|
||||
Directory::try_from(OwnedFd::from_raw_fd(fd))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_as_file(&self, flags: i32) -> io::Result<File> {
|
||||
if self.is_dir() {
|
||||
return Err(io::Error::from(io::ErrorKind::IsADirectory));
|
||||
}
|
||||
unsafe {
|
||||
let fd = libc::openat(
|
||||
self.dir.as_raw_fd(),
|
||||
self.d_name.as_ptr(),
|
||||
flags | O_CLOEXEC,
|
||||
)
|
||||
.check_os_err()?;
|
||||
Ok(File::from_raw_fd(fd))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DirEntry<'_> {
|
||||
type Target = dirent;
|
||||
|
||||
fn deref(&self) -> &dirent {
|
||||
self.entry
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Directory {
|
||||
dirp: *mut libc::DIR,
|
||||
}
|
||||
|
||||
pub enum WalkResult {
|
||||
Continue,
|
||||
Abort,
|
||||
Skip,
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
pub fn open(path: &Utf8CStr) -> io::Result<Directory> {
|
||||
let dirp = unsafe { libc::opendir(path.as_ptr()) }.check_os_err()?;
|
||||
Ok(Directory { dirp })
|
||||
}
|
||||
|
||||
pub fn read(&mut self) -> io::Result<Option<DirEntry<'_>>> {
|
||||
*errno() = 0;
|
||||
let e = unsafe { libc::readdir(self.dirp) };
|
||||
if e.is_null() {
|
||||
return if *errno() != 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(None)
|
||||
};
|
||||
}
|
||||
// Skip both "." and ".."
|
||||
unsafe {
|
||||
let entry = &*e;
|
||||
let d_name = CStr::from_ptr(entry.d_name.as_ptr());
|
||||
return if d_name == cstr!(".") || d_name == cstr!("..") {
|
||||
self.read()
|
||||
} else {
|
||||
let e = DirEntry {
|
||||
dir: self,
|
||||
entry,
|
||||
d_name_len: d_name.to_bytes_with_nul().len(),
|
||||
};
|
||||
Ok(Some(e))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewind(&mut self) {
|
||||
unsafe { libc::rewinddir(self.dirp) }
|
||||
}
|
||||
|
||||
pub fn path(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
fd_path(self.as_raw_fd(), buf)
|
||||
}
|
||||
|
||||
pub fn post_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
|
||||
&mut self,
|
||||
mut f: F,
|
||||
) -> io::Result<WalkResult> {
|
||||
self.post_order_walk_impl(&mut f)
|
||||
}
|
||||
|
||||
pub fn pre_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
|
||||
&mut self,
|
||||
mut f: F,
|
||||
) -> io::Result<WalkResult> {
|
||||
self.pre_order_walk_impl(&mut f)
|
||||
}
|
||||
|
||||
pub fn remove_all(&mut self) -> io::Result<()> {
|
||||
self.post_order_walk(|e| {
|
||||
e.unlink()?;
|
||||
Ok(WalkResult::Continue)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
fn post_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
|
||||
&mut self,
|
||||
f: &mut F,
|
||||
) -> io::Result<WalkResult> {
|
||||
use WalkResult::*;
|
||||
loop {
|
||||
match self.read()? {
|
||||
None => return Ok(Continue),
|
||||
Some(ref e) => {
|
||||
if e.is_dir() {
|
||||
let mut dir = e.open_as_dir()?;
|
||||
if let Abort = dir.post_order_walk_impl(f)? {
|
||||
return Ok(Abort);
|
||||
}
|
||||
}
|
||||
match f(e)? {
|
||||
Abort => return Ok(Abort),
|
||||
Skip => return Ok(Continue),
|
||||
Continue => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
|
||||
&mut self,
|
||||
f: &mut F,
|
||||
) -> io::Result<WalkResult> {
|
||||
use WalkResult::*;
|
||||
loop {
|
||||
match self.read()? {
|
||||
None => return Ok(Continue),
|
||||
Some(ref e) => match f(e)? {
|
||||
Abort => return Ok(Abort),
|
||||
Skip => continue,
|
||||
Continue => {
|
||||
if e.is_dir() {
|
||||
let mut dir = e.open_as_dir()?;
|
||||
if let Abort = dir.pre_order_walk_impl(f)? {
|
||||
return Ok(Abort);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<OwnedFd> for Directory {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(fd: OwnedFd) -> io::Result<Self> {
|
||||
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) }.check_os_err()?;
|
||||
Ok(Directory { dirp })
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Directory {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
unsafe { libc::dirfd(self.dirp) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for Directory {
|
||||
fn as_fd(&self) -> BorrowedFd {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Directory {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
libc::closedir(self.dirp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rm_rf(path: &Utf8CStr) -> io::Result<()> {
|
||||
unsafe {
|
||||
let f = File::from(open_fd!(path, O_RDONLY | O_CLOEXEC)?);
|
||||
let st = f.metadata()?;
|
||||
if st.is_dir() {
|
||||
let mut dir = Directory::try_from(OwnedFd::from(f))?;
|
||||
dir.remove_all()?;
|
||||
}
|
||||
libc::remove(path.as_ptr()).check_os_err()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct MappedFile(&'static mut [u8]);
|
||||
|
||||
impl MappedFile {
|
||||
pub fn open(path: &Utf8CStr) -> io::Result<MappedFile> {
|
||||
Ok(MappedFile(map_file(path, false)?))
|
||||
}
|
||||
|
||||
pub fn open_rw(path: &Utf8CStr) -> io::Result<MappedFile> {
|
||||
Ok(MappedFile(map_file(path, true)?))
|
||||
}
|
||||
|
||||
pub fn create(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<MappedFile> {
|
||||
Ok(MappedFile(map_fd(fd, sz, rw)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for MappedFile {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for MappedFile {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MappedFile {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
libc::munmap(self.0.as_mut_ptr().cast(), self.0.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We mark the returned slice static because it is valid until explicitly unmapped
|
||||
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> io::Result<&'static mut [u8]> {
|
||||
extern "C" {
|
||||
// Don't use the declaration from the libc crate as request should be u32 not i32
|
||||
fn ioctl(fd: RawFd, request: u32, ...) -> i32;
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const BLKGETSIZE64: u32 = 0x80081272;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
const BLKGETSIZE64: u32 = 0x80041272;
|
||||
|
||||
let flag = if rw { O_RDWR } else { O_RDONLY };
|
||||
let f = File::from(open_fd!(path, flag | O_CLOEXEC)?);
|
||||
|
||||
let st = f.metadata()?;
|
||||
let sz = if st.file_type().is_block_device() {
|
||||
let mut sz = 0_u64;
|
||||
unsafe { ioctl(f.as_raw_fd(), BLKGETSIZE64, &mut sz) }.check_os_err()?;
|
||||
sz
|
||||
} else {
|
||||
st.st_size()
|
||||
};
|
||||
|
||||
map_fd(f.as_fd(), sz as usize, rw)
|
||||
}
|
||||
|
||||
pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<&'static mut [u8]> {
|
||||
let flag = if rw {
|
||||
libc::MAP_SHARED
|
||||
} else {
|
||||
libc::MAP_PRIVATE
|
||||
};
|
||||
unsafe {
|
||||
let ptr = libc::mmap(
|
||||
ptr::null_mut(),
|
||||
sz,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
flag,
|
||||
fd.as_raw_fd(),
|
||||
0,
|
||||
);
|
||||
if ptr == libc::MAP_FAILED {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(slice::from_raw_parts_mut(ptr.cast(), sz))
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../missing.hpp"
|
||||
#include "../xwrap.hpp"
|
||||
#include "../files.hpp"
|
||||
#include "../misc.hpp"
|
||||
#include "../logging.hpp"
|
||||
#include <base-rs.hpp>
|
||||
#include "../missing.hpp"
|
||||
#include "../base-rs.hpp"
|
||||
|
||||
using rust::xpipe2;
|
||||
using rust::fd_path;
|
@@ -19,13 +19,9 @@
|
||||
// Unconstrained domain the daemon and root processes run in
|
||||
#define SEPOL_PROC_DOMAIN "magisk"
|
||||
#define MAGISK_PROC_CON "u:r:" SEPOL_PROC_DOMAIN ":s0"
|
||||
// Highly constrained domain, sole purpose is to connect to daemon
|
||||
#define SEPOL_CLIENT_DOMAIN "magisk_client"
|
||||
// Unconstrained file type that anyone can access
|
||||
#define SEPOL_FILE_TYPE "magisk_file"
|
||||
#define MAGISK_FILE_CON "u:object_r:" SEPOL_FILE_TYPE ":s0"
|
||||
// Special file type to allow clients to transit to client domain automatically
|
||||
#define SEPOL_EXEC_TYPE "magisk_exec"
|
||||
|
||||
extern void (*freecon)(char *con);
|
||||
extern int (*setcon)(const char *con);
|
||||
|
@@ -6,97 +6,89 @@
|
||||
|
||||
#include "../files.hpp"
|
||||
|
||||
class stream {
|
||||
public:
|
||||
virtual ssize_t read(void *buf, size_t len);
|
||||
virtual ssize_t readFully(void *buf, size_t len);
|
||||
virtual ssize_t readv(const iovec *iov, int iovcnt);
|
||||
virtual bool write(const void *buf, size_t len);
|
||||
struct out_stream {
|
||||
virtual bool write(const void *buf, size_t len) = 0;
|
||||
virtual ssize_t writev(const iovec *iov, int iovcnt);
|
||||
virtual off_t seek(off_t off, int whence);
|
||||
virtual ~stream() = default;
|
||||
virtual ~out_stream() = default;
|
||||
};
|
||||
|
||||
using stream_ptr = std::unique_ptr<stream>;
|
||||
using out_strm_ptr = std::unique_ptr<out_stream>;
|
||||
|
||||
// Delegates all operations to base stream
|
||||
class filter_stream : public stream {
|
||||
class filter_out_stream : public out_stream {
|
||||
public:
|
||||
filter_stream(stream_ptr &&base) : base(std::move(base)) {}
|
||||
|
||||
ssize_t read(void *buf, size_t len) override;
|
||||
filter_out_stream(out_strm_ptr &&base) : base(std::move(base)) {}
|
||||
bool write(const void *buf, size_t len) override;
|
||||
virtual bool write(const void *buf, size_t len, bool final);
|
||||
|
||||
// Seeking while filtering does not make sense
|
||||
off_t seek(off_t off, int whence) final { return stream::seek(off, whence); }
|
||||
|
||||
protected:
|
||||
stream_ptr base;
|
||||
out_strm_ptr base;
|
||||
};
|
||||
|
||||
using filter_strm_ptr = std::unique_ptr<filter_stream>;
|
||||
|
||||
// Buffered output stream, writing in chunks
|
||||
class chunk_out_stream : public filter_stream {
|
||||
class chunk_out_stream : public filter_out_stream {
|
||||
public:
|
||||
chunk_out_stream(stream_ptr &&base, size_t buf_sz, size_t chunk_sz)
|
||||
: filter_stream(std::move(base)), chunk_sz(chunk_sz), buf_sz(buf_sz) {}
|
||||
chunk_out_stream(out_strm_ptr &&base, size_t buf_sz, size_t chunk_sz)
|
||||
: filter_out_stream(std::move(base)), chunk_sz(chunk_sz), data(buf_sz) {}
|
||||
|
||||
chunk_out_stream(stream_ptr &&base, size_t buf_sz = 4096)
|
||||
chunk_out_stream(out_strm_ptr &&base, size_t buf_sz = 4096)
|
||||
: chunk_out_stream(std::move(base), buf_sz, buf_sz) {}
|
||||
|
||||
~chunk_out_stream() override { delete[] _buf; }
|
||||
|
||||
// Reading does not make sense
|
||||
ssize_t read(void *buf, size_t len) final { return stream::read(buf, len); }
|
||||
bool write(const void *buf, size_t len) final;
|
||||
bool write(const void *buf, size_t len, bool final) final;
|
||||
|
||||
protected:
|
||||
// Classes inheriting this class has to call finalize() in its destructor
|
||||
void finalize();
|
||||
virtual bool write_chunk(const void *buf, size_t len, bool final) = 0;
|
||||
virtual bool write_chunk(const void *buf, size_t len, bool final);
|
||||
|
||||
size_t chunk_sz;
|
||||
|
||||
private:
|
||||
size_t buf_sz;
|
||||
size_t buf_off = 0;
|
||||
uint8_t *_buf = nullptr;
|
||||
heap_data data;
|
||||
};
|
||||
|
||||
// Byte stream that dynamically allocates memory
|
||||
class byte_stream : public stream {
|
||||
struct in_stream {
|
||||
virtual ssize_t read(void *buf, size_t len) = 0;
|
||||
virtual ssize_t readFully(void *buf, size_t len);
|
||||
virtual ssize_t readv(const iovec *iov, int iovcnt);
|
||||
virtual ~in_stream() = default;
|
||||
};
|
||||
|
||||
// A channel is something that is writable, readable, and seekable
|
||||
struct channel : public out_stream, public in_stream {
|
||||
virtual off_t seek(off_t off, int whence) = 0;
|
||||
virtual ~channel() = default;
|
||||
};
|
||||
|
||||
using channel_ptr = std::unique_ptr<channel>;
|
||||
|
||||
// Byte channel that dynamically allocates memory
|
||||
class byte_channel : public channel {
|
||||
public:
|
||||
byte_stream(uint8_t *&buf, size_t &len);
|
||||
template <class Byte>
|
||||
byte_stream(Byte *&buf, size_t &len) : byte_stream(reinterpret_cast<uint8_t *&>(buf), len) {}
|
||||
byte_channel(heap_data &data) : _data(data) {}
|
||||
|
||||
ssize_t read(void *buf, size_t len) override;
|
||||
bool write(const void *buf, size_t len) override;
|
||||
off_t seek(off_t off, int whence) override;
|
||||
|
||||
private:
|
||||
uint8_t *&_buf;
|
||||
size_t &_len;
|
||||
heap_data &_data;
|
||||
size_t _pos = 0;
|
||||
size_t _cap = 0;
|
||||
|
||||
void resize(size_t new_pos, bool zero = false);
|
||||
void resize(size_t new_sz, bool zero = false);
|
||||
};
|
||||
|
||||
class file_stream : public stream {
|
||||
class file_channel : public channel {
|
||||
public:
|
||||
bool write(const void *buf, size_t len) final;
|
||||
protected:
|
||||
virtual ssize_t do_write(const void *buf, size_t len) = 0;
|
||||
};
|
||||
|
||||
// File stream but does not close the file descriptor at any time
|
||||
class fd_stream : public file_stream {
|
||||
// File channel but does not close the file descriptor at any time
|
||||
class fd_channel : public file_channel {
|
||||
public:
|
||||
fd_stream(int fd) : fd(fd) {}
|
||||
fd_channel(int fd) : fd(fd) {}
|
||||
ssize_t read(void *buf, size_t len) override;
|
||||
ssize_t readv(const iovec *iov, int iovcnt) override;
|
||||
ssize_t writev(const iovec *iov, int iovcnt) override;
|
||||
@@ -108,14 +100,14 @@ private:
|
||||
};
|
||||
|
||||
/* ****************************************
|
||||
* Bridge between stream class and C stdio
|
||||
* Bridge between channel class and C stdio
|
||||
* ****************************************/
|
||||
|
||||
// sFILE -> stream_ptr
|
||||
class fp_stream final : public file_stream {
|
||||
// sFILE -> channel_ptr
|
||||
class fp_channel final : public file_channel {
|
||||
public:
|
||||
fp_stream(FILE *fp = nullptr) : fp(fp, fclose) {}
|
||||
fp_stream(sFILE &&fp) : fp(std::move(fp)) {}
|
||||
fp_channel(FILE *fp = nullptr) : fp(fp, fclose) {}
|
||||
fp_channel(sFILE &&fp) : fp(std::move(fp)) {}
|
||||
ssize_t read(void *buf, size_t len) override;
|
||||
off_t seek(off_t off, int whence) override;
|
||||
protected:
|
||||
@@ -124,10 +116,10 @@ private:
|
||||
sFILE fp;
|
||||
};
|
||||
|
||||
// stream_ptr -> sFILE
|
||||
sFILE make_stream_fp(stream_ptr &&strm);
|
||||
// channel_ptr -> sFILE
|
||||
sFILE make_channel_fp(channel_ptr &&strm);
|
||||
|
||||
template <class T, class... Args>
|
||||
sFILE make_stream_fp(Args &&... args) {
|
||||
return make_stream_fp(stream_ptr(new T(std::forward<Args>(args)...)));
|
||||
sFILE make_channel_fp(Args &&... args) {
|
||||
return make_channel_fp(channel_ptr(new T(std::forward<Args>(args)...)));
|
||||
}
|
||||
|
@@ -1,12 +1,15 @@
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#![feature(format_args_nl)]
|
||||
#![feature(io_error_more)]
|
||||
|
||||
pub use libc;
|
||||
|
||||
use cxx_extern::*;
|
||||
pub use files::*;
|
||||
pub use logging::*;
|
||||
pub use misc::*;
|
||||
pub use xwrap::*;
|
||||
|
||||
mod cxx_extern;
|
||||
mod files;
|
||||
mod logging;
|
||||
mod misc;
|
||||
@@ -16,24 +19,34 @@ mod xwrap;
|
||||
pub mod ffi {
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum LogLevel {
|
||||
ErrorCxx,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("misc.hpp");
|
||||
fn mut_u8_patch(buf: &mut [u8], from: &[u8], to: &[u8]) -> Vec<usize>;
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
fn log_with_rs(level: LogLevel, msg: &str);
|
||||
#[cxx_name = "log_with_rs"]
|
||||
fn log_from_cxx(level: LogLevel, msg: &[u8]);
|
||||
fn exit_on_error(b: bool);
|
||||
fn set_log_level_state(level: LogLevel, enabled: bool);
|
||||
fn cmdline_logging();
|
||||
}
|
||||
}
|
||||
|
||||
#[cxx::bridge(namespace = "rust")]
|
||||
pub mod ffi2 {
|
||||
#[namespace = "rust"]
|
||||
extern "Rust" {
|
||||
fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32;
|
||||
fn fd_path(fd: i32, buf: &mut [u8]) -> isize;
|
||||
#[cxx_name = "fd_path"]
|
||||
fn fd_path_for_cxx(fd: i32, buf: &mut [u8]) -> isize;
|
||||
#[cxx_name = "map_file"]
|
||||
fn map_file_for_cxx(path: &[u8], rw: bool) -> &'static mut [u8];
|
||||
#[cxx_name = "map_fd"]
|
||||
fn map_fd_for_cxx(fd: i32, sz: usize, rw: bool) -> &'static mut [u8];
|
||||
}
|
||||
}
|
||||
|
@@ -6,20 +6,19 @@
|
||||
#include <flags.h>
|
||||
#include <base.hpp>
|
||||
|
||||
// Just need to include it somewhere
|
||||
#include <base-rs.cpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#undef vsnprintf
|
||||
static int fmt_and_log_with_rs(LogLevel level, const char *fmt, va_list ap) {
|
||||
char buf[4096];
|
||||
int ret = vssprintf(buf, sizeof(buf), fmt, ap);
|
||||
log_with_rs(level, rust::Str(buf, ret));
|
||||
return ret;
|
||||
constexpr int sz = 4096;
|
||||
char buf[sz];
|
||||
buf[0] = '\0';
|
||||
// Fortify logs when a fatal error occurs. Do not run through fortify again
|
||||
int len = std::min(__call_bypassing_fortify(vsnprintf)(buf, sz, fmt, ap), sz - 1);
|
||||
log_with_rs(level, byte_view(buf, len));
|
||||
return len;
|
||||
}
|
||||
|
||||
int (*cpp_logger)(LogLevel level, const char *fmt, va_list ap) = fmt_and_log_with_rs;
|
||||
|
||||
// Used to override external C library logging
|
||||
extern "C" int magisk_log_print(int prio, const char *tag, const char *fmt, ...) {
|
||||
LogLevel level;
|
||||
@@ -34,7 +33,7 @@ extern "C" int magisk_log_print(int prio, const char *tag, const char *fmt, ...)
|
||||
level = LogLevel::Warn;
|
||||
break;
|
||||
case ANDROID_LOG_ERROR:
|
||||
level = LogLevel::Error;
|
||||
level = LogLevel::ErrorCxx;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
@@ -52,17 +51,16 @@ extern "C" int magisk_log_print(int prio, const char *tag, const char *fmt, ...)
|
||||
}
|
||||
va_list argv;
|
||||
va_start(argv, fmt);
|
||||
int ret = cpp_logger(level, fmt_buf, argv);
|
||||
int ret = fmt_and_log_with_rs(level, fmt_buf, argv);
|
||||
va_end(argv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define LOG_BODY(level) { \
|
||||
va_list argv; \
|
||||
va_start(argv, fmt); \
|
||||
cpp_logger(LogLevel::level, fmt, argv); \
|
||||
va_end(argv); \
|
||||
}
|
||||
#define LOG_BODY(level) \
|
||||
va_list argv; \
|
||||
va_start(argv, fmt); \
|
||||
fmt_and_log_with_rs(LogLevel::level, fmt, argv); \
|
||||
va_end(argv); \
|
||||
|
||||
// LTO will optimize out the NOP function
|
||||
#if MAGISK_DEBUG
|
||||
@@ -72,9 +70,9 @@ void LOGD(const char *fmt, ...) {}
|
||||
#endif
|
||||
void LOGI(const char *fmt, ...) { LOG_BODY(Info) }
|
||||
void LOGW(const char *fmt, ...) { LOG_BODY(Warn) }
|
||||
void LOGE(const char *fmt, ...) { LOG_BODY(Error) }
|
||||
void LOGE(const char *fmt, ...) { LOG_BODY(ErrorCxx) }
|
||||
|
||||
// Export raw symbol to fortify compat
|
||||
extern "C" void __vloge(const char* fmt, va_list ap) {
|
||||
cpp_logger(LogLevel::Error, fmt, ap);
|
||||
fmt_and_log_with_rs(LogLevel::ErrorCxx, fmt, ap);
|
||||
}
|
||||
|
@@ -3,10 +3,6 @@
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
|
||||
#include <base-rs.hpp>
|
||||
|
||||
extern int (*cpp_logger)(LogLevel level, const char *fmt, va_list ap);
|
||||
|
||||
void LOGD(const char *fmt, ...) __printflike(1, 2);
|
||||
void LOGI(const char *fmt, ...) __printflike(1, 2);
|
||||
void LOGW(const char *fmt, ...) __printflike(1, 2);
|
||||
|
@@ -1,8 +1,24 @@
|
||||
use std::fmt::Arguments;
|
||||
use std::fmt;
|
||||
use std::fmt::{Arguments, Display, Write as fWrite};
|
||||
use std::io::{stderr, stdout, Write};
|
||||
use std::panic::Location;
|
||||
use std::process::exit;
|
||||
|
||||
use crate::ffi::LogLevel;
|
||||
use crate::BufFormatter;
|
||||
|
||||
// Error handling and logging throughout the Rust codebase in Magisk:
|
||||
//
|
||||
// All errors should be logged and consumed as soon as possible and converted into LoggedError.
|
||||
// Implement `From<ErrorType> for LoggedError` for non-standard error types so that we can
|
||||
// directly use the `?` operator to propagate LoggedResult.
|
||||
//
|
||||
// To log an error with more information, use `ResultExt::log_with_msg()`.
|
||||
//
|
||||
// The "cxx" method variants in `ResultExt` are only used for C++ interop and
|
||||
// should not be used directly in any Rust code.
|
||||
//
|
||||
// For general logging, use the <level>!(...) macros.
|
||||
|
||||
// Ugly hack to avoid using enum
|
||||
#[allow(non_snake_case, non_upper_case_globals)]
|
||||
@@ -17,14 +33,12 @@ mod LogFlag {
|
||||
// We don't need to care about thread safety, because all
|
||||
// logger changes will only happen on the main thread.
|
||||
pub static mut LOGGER: Logger = Logger {
|
||||
fmt: |_, _| {},
|
||||
write: |_, _| {},
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Logger {
|
||||
pub fmt: fn(level: LogLevel, args: Arguments),
|
||||
pub write: fn(level: LogLevel, msg: &[u8]),
|
||||
pub flags: u32,
|
||||
}
|
||||
@@ -40,9 +54,9 @@ pub fn exit_on_error(b: bool) {
|
||||
}
|
||||
|
||||
impl LogLevel {
|
||||
fn to_disable_flag(&self) -> u32 {
|
||||
fn as_disable_flag(&self) -> u32 {
|
||||
match *self {
|
||||
LogLevel::Error => LogFlag::DisableError,
|
||||
LogLevel::Error | LogLevel::ErrorCxx => LogFlag::DisableError,
|
||||
LogLevel::Warn => LogFlag::DisableWarn,
|
||||
LogLevel::Info => LogFlag::DisableInfo,
|
||||
LogLevel::Debug => LogFlag::DisableDebug,
|
||||
@@ -52,7 +66,7 @@ impl LogLevel {
|
||||
}
|
||||
|
||||
pub fn set_log_level_state(level: LogLevel, enabled: bool) {
|
||||
let flag = level.to_disable_flag();
|
||||
let flag = level.as_disable_flag();
|
||||
unsafe {
|
||||
if enabled {
|
||||
LOGGER.flags &= !flag
|
||||
@@ -62,36 +76,35 @@ pub fn set_log_level_state(level: LogLevel, enabled: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_with_rs(level: LogLevel, msg: &str) {
|
||||
fn do_log<F: FnOnce(fn(level: LogLevel, msg: &[u8]))>(level: LogLevel, f: F) {
|
||||
let logger = unsafe { LOGGER };
|
||||
if (logger.flags & level.to_disable_flag()) != 0 {
|
||||
if (logger.flags & level.as_disable_flag()) != 0 {
|
||||
return;
|
||||
}
|
||||
(logger.write)(level, msg.as_bytes());
|
||||
if level == LogLevel::Error && (logger.flags & LogFlag::ExitOnError) != 0 {
|
||||
f(logger.write);
|
||||
if level == LogLevel::ErrorCxx && (logger.flags & LogFlag::ExitOnError) != 0 {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_impl(level: LogLevel, args: Arguments) {
|
||||
let logger = unsafe { LOGGER };
|
||||
if (logger.flags & level.to_disable_flag()) != 0 {
|
||||
return;
|
||||
}
|
||||
(logger.fmt)(level, args);
|
||||
if level == LogLevel::Error && (logger.flags & LogFlag::ExitOnError) != 0 {
|
||||
exit(1);
|
||||
}
|
||||
pub fn log_from_cxx(level: LogLevel, msg: &[u8]) {
|
||||
do_log(level, |write| write(level, msg));
|
||||
}
|
||||
|
||||
pub fn log_with_formatter<F: FnOnce(&mut BufFormatter) -> fmt::Result>(level: LogLevel, f: F) {
|
||||
do_log(level, |write| {
|
||||
let mut buf = [0_u8; 4096];
|
||||
let mut w = BufFormatter::new(&mut buf);
|
||||
let len = if f(&mut w).is_ok() { w.used } else { 0 };
|
||||
write(level, &buf[..len]);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn log_with_args(level: LogLevel, args: Arguments) {
|
||||
log_with_formatter(level, |w| w.write_fmt(args));
|
||||
}
|
||||
|
||||
pub fn cmdline_logging() {
|
||||
fn cmdline_print(level: LogLevel, args: Arguments) {
|
||||
if level == LogLevel::Info {
|
||||
print!("{}", args);
|
||||
} else {
|
||||
eprint!("{}", args);
|
||||
}
|
||||
}
|
||||
fn cmdline_write(level: LogLevel, msg: &[u8]) {
|
||||
if level == LogLevel::Info {
|
||||
stdout().write_all(msg).ok();
|
||||
@@ -101,7 +114,6 @@ pub fn cmdline_logging() {
|
||||
}
|
||||
|
||||
let logger = Logger {
|
||||
fmt: cmdline_print,
|
||||
write: cmdline_write,
|
||||
flags: LogFlag::ExitOnError,
|
||||
};
|
||||
@@ -110,44 +122,33 @@ pub fn cmdline_logging() {
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! perror {
|
||||
($fmt:expr) => {
|
||||
$crate::log_impl($crate::ffi::LogLevel::Error, format_args_nl!(
|
||||
concat!($fmt, " failed with {}: {}"),
|
||||
crate::errno(),
|
||||
crate::error_str()
|
||||
))
|
||||
};
|
||||
($fmt:expr, $($args:tt)*) => {
|
||||
$crate::log_impl($crate::ffi::LogLevel::Error, format_args_nl!(
|
||||
concat!($fmt, " failed with {}: {}"),
|
||||
$($args)*,
|
||||
crate::errno(),
|
||||
crate::error_str()
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($args:tt)+) => ($crate::log_impl($crate::ffi::LogLevel::Error, format_args_nl!($($args)+)))
|
||||
($($args:tt)+) => {
|
||||
$crate::log_with_args($crate::ffi::LogLevel::Error, format_args_nl!($($args)+))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($args:tt)+) => ($crate::log_impl($crate::ffi::LogLevel::Warn, format_args_nl!($($args)+)))
|
||||
($($args:tt)+) => {
|
||||
$crate::log_with_args($crate::ffi::LogLevel::Warn, format_args_nl!($($args)+))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($($args:tt)+) => ($crate::log_impl($crate::ffi::LogLevel::Info, format_args_nl!($($args)+)))
|
||||
($($args:tt)+) => {
|
||||
$crate::log_with_args($crate::ffi::LogLevel::Info, format_args_nl!($($args)+))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($args:tt)+) => ($crate::log_impl($crate::ffi::LogLevel::Debug, format_args_nl!($($args)+)))
|
||||
($($args:tt)+) => {
|
||||
$crate::log_with_args($crate::ffi::LogLevel::Debug, format_args_nl!($($args)+))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
@@ -155,3 +156,157 @@ macro_rules! debug {
|
||||
macro_rules! debug {
|
||||
($($args:tt)+) => {};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LoggedError {}
|
||||
|
||||
// Automatically handle all printable errors
|
||||
impl<T: Display> From<T> for LoggedError {
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn from(e: T) -> Self {
|
||||
log_with_args(LogLevel::Error, format_args_nl!("{:#}", e));
|
||||
LoggedError::default()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(debug_assertions)]
|
||||
fn from(e: T) -> Self {
|
||||
let caller = Location::caller();
|
||||
log_with_args(
|
||||
LogLevel::Error,
|
||||
format_args_nl!("[{}:{}] {:#}", caller.file(), caller.line(), e),
|
||||
);
|
||||
LoggedError::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub type LoggedResult<T> = Result<T, LoggedError>;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_err {
|
||||
($msg:literal $(,)?) => {{
|
||||
$crate::log_with_args($crate::ffi::LogLevel::Error, format_args_nl!($msg));
|
||||
$crate::LoggedError::default()
|
||||
}};
|
||||
($err:expr $(,)?) => {{
|
||||
$crate::log_with_args($crate::ffi::LogLevel::Error, format_args_nl!("{}", $err));
|
||||
$crate::LoggedError::default()
|
||||
}};
|
||||
($($args:tt)+) => {{
|
||||
$crate::log_with_args($crate::ffi::LogLevel::Error, format_args_nl!($($args)+));
|
||||
$crate::LoggedError::default()
|
||||
}};
|
||||
}
|
||||
|
||||
pub trait ResultExt<T>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn log(self) -> LoggedResult<T> {
|
||||
self.log_impl(LogLevel::Error, None)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(debug_assertions)]
|
||||
fn log(self) -> LoggedResult<T> {
|
||||
self.log_impl(LogLevel::Error, Some(Location::caller()))
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn log_with_msg<F: FnOnce(&mut BufFormatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
|
||||
self.log_with_msg_impl(LogLevel::Error, None, f)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(debug_assertions)]
|
||||
fn log_with_msg<F: FnOnce(&mut BufFormatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
|
||||
self.log_with_msg_impl(LogLevel::Error, Some(Location::caller()), f)
|
||||
}
|
||||
|
||||
fn log_cxx(self) -> LoggedResult<T> {
|
||||
self.log_impl(LogLevel::ErrorCxx, None)
|
||||
}
|
||||
|
||||
fn log_cxx_with_msg<F: FnOnce(&mut BufFormatter) -> fmt::Result>(
|
||||
self,
|
||||
f: F,
|
||||
) -> LoggedResult<T> {
|
||||
self.log_with_msg_impl(LogLevel::ErrorCxx, None, f)
|
||||
}
|
||||
|
||||
fn log_impl(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedResult<T>;
|
||||
fn log_with_msg_impl<F: FnOnce(&mut BufFormatter) -> fmt::Result>(
|
||||
self,
|
||||
level: LogLevel,
|
||||
caller: Option<&'static Location>,
|
||||
f: F,
|
||||
) -> LoggedResult<T>;
|
||||
}
|
||||
|
||||
impl<T> ResultExt<T> for LoggedResult<T> {
|
||||
fn log_impl(self, _: LogLevel, _: Option<&'static Location>) -> LoggedResult<T> {
|
||||
self
|
||||
}
|
||||
|
||||
fn log_with_msg_impl<F: FnOnce(&mut BufFormatter) -> fmt::Result>(
|
||||
self,
|
||||
level: LogLevel,
|
||||
caller: Option<&'static Location>,
|
||||
f: F,
|
||||
) -> LoggedResult<T> {
|
||||
match self {
|
||||
Ok(v) => Ok(v),
|
||||
Err(_) => {
|
||||
log_with_formatter(level, |w| {
|
||||
if let Some(caller) = caller {
|
||||
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
|
||||
}
|
||||
f(w)?;
|
||||
w.write_char('\n')
|
||||
});
|
||||
Err(LoggedError::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E: Display> ResultExt<T> for Result<T, E> {
|
||||
fn log_impl(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedResult<T> {
|
||||
match self {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => {
|
||||
if let Some(caller) = caller {
|
||||
log_with_args(
|
||||
level,
|
||||
format_args_nl!("[{}:{}] {:#}", caller.file(), caller.line(), e),
|
||||
);
|
||||
} else {
|
||||
log_with_args(level, format_args_nl!("{:#}", e));
|
||||
}
|
||||
Err(LoggedError::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn log_with_msg_impl<F: FnOnce(&mut BufFormatter) -> fmt::Result>(
|
||||
self,
|
||||
level: LogLevel,
|
||||
caller: Option<&'static Location>,
|
||||
f: F,
|
||||
) -> LoggedResult<T> {
|
||||
match self {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => {
|
||||
log_with_formatter(level, |w| {
|
||||
if let Some(caller) = caller {
|
||||
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
|
||||
}
|
||||
f(w)?;
|
||||
writeln!(w, ": {:#}", e)
|
||||
});
|
||||
Err(LoggedError::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user