You've already forked KernelSU
mirror of
https://github.com/tiann/KernelSU.git
synced 2025-08-27 23:46:34 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a4ea27e9d | ||
|
|
339e75be24 | ||
|
|
cf210d629f | ||
|
|
ca480a5ec3 | ||
|
|
23263a55de | ||
|
|
f65ea5a340 | ||
|
|
ad6e2390f5 | ||
|
|
383f164453 | ||
|
|
f675ce9aba | ||
|
|
7fd760f4f4 | ||
|
|
972d347a14 | ||
|
|
ca8a88f0cc | ||
|
|
e39be55db8 | ||
|
|
d00d3cbf82 | ||
|
|
7168a974be | ||
|
|
ddc086c4ef | ||
|
|
076511e275 | ||
|
|
9d5529fb09 | ||
|
|
ceb00dfdfd | ||
|
|
39504ec2f3 | ||
|
|
1642e92c6f | ||
|
|
0f220f4044 | ||
|
|
2c34ec1742 | ||
|
|
7568d55be1 | ||
|
|
e3998c0744 | ||
|
|
50914ce39b | ||
|
|
6af2480008 | ||
|
|
625e1aafd1 | ||
|
|
d77988c1ac | ||
|
|
fe7ec370d4 | ||
|
|
ce5aa990ed | ||
|
|
22a1276a22 | ||
|
|
f9a4186dc7 | ||
|
|
8b29137e83 | ||
|
|
ae17001033 | ||
|
|
0eea198c2f | ||
|
|
425713fad3 | ||
|
|
7611accc33 | ||
|
|
323eaa0242 | ||
|
|
217755bb5a | ||
|
|
170cd3f912 | ||
|
|
1d7d406745 | ||
|
|
c8fc6a0656 | ||
|
|
3829894d4d | ||
|
|
cd772fa250 | ||
|
|
dbe43b1540 | ||
|
|
8a59fe1969 | ||
|
|
2a4fa94af0 | ||
|
|
6de330b00a | ||
|
|
8c0d06bc68 | ||
|
|
ed254b7ab4 | ||
|
|
5355625ed6 | ||
|
|
7b89ec89c0 | ||
|
|
5aa025c3f0 | ||
|
|
e39a80f91e | ||
|
|
622a7d73dc | ||
|
|
922703d2ff | ||
|
|
f459dfad54 | ||
|
|
3e2de84a81 | ||
|
|
796f8a448a | ||
|
|
7775ce3938 | ||
|
|
2fb5334ac6 | ||
|
|
afe0e691aa | ||
|
|
532796de48 | ||
|
|
e691c62811 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.bat eol=crlf
|
||||
71
.github/manifests/android-14-avd_x86_64.xml
vendored
Normal file
71
.github/manifests/android-14-avd_x86_64.xml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://ci.android.com/builds/submitted/9964412/kernel_virt_x86_64/latest/manifest_9964412.xml-->
|
||||
<manifest>
|
||||
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/" />
|
||||
|
||||
<default revision="master" remote="aosp" sync-j="4" />
|
||||
|
||||
<superproject name="kernel/superproject" remote="aosp" revision="common-android14-6.1" />
|
||||
|
||||
<project path="build/kernel" name="kernel/build" revision="b0377a072bb3f78cdacfd6d809914a9d1b0c0148">
|
||||
<linkfile dest="tools/bazel" src="kleaf/bazel.sh" />
|
||||
|
||||
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE" />
|
||||
|
||||
<linkfile dest="build/build.sh" src="build.sh" />
|
||||
|
||||
<linkfile dest="build/build_abi.sh" src="build_abi.sh" />
|
||||
|
||||
<linkfile dest="build/build_test.sh" src="build_test.sh" />
|
||||
|
||||
<linkfile dest="build/build_utils.sh" src="build_utils.sh" />
|
||||
|
||||
<linkfile dest="build/config.sh" src="config.sh" />
|
||||
|
||||
<linkfile dest="build/envsetup.sh" src="envsetup.sh" />
|
||||
|
||||
<linkfile dest="build/_setup_env.sh" src="_setup_env.sh" />
|
||||
|
||||
<linkfile dest="build/multi-switcher.sh" src="multi-switcher.sh" />
|
||||
|
||||
<linkfile dest="build/abi" src="abi" />
|
||||
|
||||
<linkfile dest="build/static_analysis" src="static_analysis" />
|
||||
</project>
|
||||
|
||||
<project path="common" name="kernel/common" revision="7e35917775b8b3e3346a87f294e334e258bf15e6">
|
||||
<linkfile dest=".source_date_epoch_dir" src="." />
|
||||
</project>
|
||||
|
||||
<project path="kernel/tests" name="kernel/tests" revision="c90a1c1b226b975cc31e709fa96fc1c6ecdbe272" />
|
||||
|
||||
<project path="kernel/configs" name="kernel/configs" revision="52a7267d6a9f9efabf3cb43839bb5e7f7ff05be3" />
|
||||
|
||||
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="0d03de3246301028775f05ea388c2c444344a268" />
|
||||
|
||||
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" clone-depth="1" revision="4f7e5adc160ab726ac5bafb260de98e612904c50" />
|
||||
|
||||
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" revision="f7b0d5b0ee369864d5ac3e96ae24ec9e2b6a52da" />
|
||||
|
||||
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" revision="dc92e06585a7647bf739a2309a721b82fcfa01d4" />
|
||||
|
||||
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="5611871963f54c688d3ac49e527aecdef21e8567" />
|
||||
|
||||
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" revision="2597cb1b5525e419b7fa806373be673054a68d29" />
|
||||
|
||||
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="2680066d0844544b3e78d6022cd21321d31837c3" />
|
||||
|
||||
<project path="prebuilts/bazel/linux-x86_64" name="platform/prebuilts/bazel/linux-x86_64" clone-depth="1" revision="4fdb9395071ff22118311d434d697c2b6fd887b4" />
|
||||
|
||||
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" clone-depth="1" revision="491e6aa056676f29c4541f71bd738e4e876e4ba2" />
|
||||
|
||||
<project path="prebuilts/ndk-r23" name="toolchain/prebuilts/ndk/r23" clone-depth="1" revision="19ac7e4eded12adb99d4f613490dde6dd0e72664" />
|
||||
|
||||
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" revision="f998e5dc13c03f0eae9e373263d3afff0932c738" />
|
||||
|
||||
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" revision="707b2c5fe3d0d7d934a93e00a8a4062e83557831" />
|
||||
|
||||
<project path="external/stardoc" name="platform/external/stardoc" revision="e83f522ee95419e55d2c5654aa6e0143beeef595" />
|
||||
|
||||
<project path="external/python/absl-py" name="platform/external/python/absl-py" revision="393d0b1e3f0fea3e95944a2fd3282cc9f76d4f14" />
|
||||
</manifest>
|
||||
37
.github/manifests/android-15-avd_aarch64.xml
vendored
Normal file
37
.github/manifests/android-15-avd_aarch64.xml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<manifest>
|
||||
<!-- https://ci.android.com/builds/submitted/11275718/kernel_virt_aarch64/latest/manifest_11275718.xml -->
|
||||
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/"/>
|
||||
<default revision="main" remote="aosp" sync-j="4"/>
|
||||
<superproject name="kernel/superproject" remote="aosp" revision="common-android15-6.6"/>
|
||||
<project path="build/kernel" name="kernel/build" groups="ddk" revision="43337ece156eabd735426a0b007637bb52fa7339">
|
||||
<linkfile dest="tools/bazel" src="kleaf/bazel.sh"/>
|
||||
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE"/>
|
||||
</project>
|
||||
<project path="common" name="kernel/common" revision="515a956763d8c874d9a7e23528332bf907b31748"/>
|
||||
<project path="kernel/common-patches" name="kernel/common-patches" revision="495419530db8761a40f8db9fb734a9be78fa25fd">
|
||||
<linkfile dest="common/patches" src="android-mainline"/>
|
||||
</project>
|
||||
<project path="kernel/tests" name="kernel/tests" revision="99abfd276063ab3a7748939aa55e107aaca903f6"/>
|
||||
<project path="kernel/configs" name="kernel/configs" revision="786dab5f2f49b983b7c448d45a50c62d36e9f7f9"/>
|
||||
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="5c7466d6fc47f7ee2245f9ee6471cd78e6ff82f0"/>
|
||||
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" revision="1400cf7d2f0d28c425737d7b58c1f67c52db087f" clone-depth="1" groups="ddk"/>
|
||||
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" groups="ddk" revision="99e41849eb3895574b1dc7e854ca15cb3fb11a71"/>
|
||||
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="93e69718fd53f0c3dd7f0087657dfe2e78926830"/>
|
||||
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="bf33473342630944198190ff74e6ca09a9bcfdeb"/>
|
||||
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="4b68c02455e6ce5c87ccf8e09e629f0177771a8c"/>
|
||||
<project path="prebuilts/rust" name="platform/prebuilts/rust" revision="7e636e2a1b7cf415c3c01981e439d98843dc4973" clone-depth="1"/>
|
||||
<project path="prebuilts/tradefed" name="platform/tools/tradefederation/prebuilts" clone-depth="1" revision="79d9ec16c1e4e747b0bfad9a80a1665080c42d7b"/>
|
||||
<project path="prebuilts/asuite" name="platform/prebuilts/asuite" clone-depth="1" revision="9e2738f6242785a3b2096010ce63363a160c122f"/>
|
||||
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="722e6fa37d508b190fafa9a8ce9f6d571fad3a8c"/>
|
||||
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" revision="06351a976e772d8a9cdb133bb982032c64ee9e53" clone-depth="1" groups="ddk"/>
|
||||
<project path="prebuilts/ndk-r26" name="toolchain/prebuilts/ndk/r26" clone-depth="1" groups="ddk" revision="e87abe7cbe9143d239ba54f32c64ca697adcba75"/>
|
||||
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" groups="ddk" revision="930baaa09975eb3809629a72806acbe9494dd224"/>
|
||||
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" groups="ddk" revision="5fb8d26dfb2565e0e2d8b6630d241b5f5675e0b1"/>
|
||||
<project path="external/libcap-ng" name="platform/external/libcap-ng" revision="2bcc92ae19481dd2b8d3ce3abdfbbee49261abe6"/>
|
||||
<project path="external/libcap" name="platform/external/libcap" revision="9577b17009379649c9220edca7d0077311445b95"/>
|
||||
<project path="external/stardoc" name="platform/external/stardoc" groups="ddk" revision="85b0f239303220d902ad919ff27d2da475fc12e2"/>
|
||||
<project path="external/python/absl-py" name="platform/external/python/absl-py" groups="ddk" revision="8cc5fc4798ef442b0c5b70c1ecc7e45a8f6eb2f8"/>
|
||||
<project path="external/bazelbuild-rules_cc" name="platform/external/bazelbuild-rules_cc" groups="ddk" revision="9a4853f0327e0266818c8d6b4967e2e8f36b1a88"/>
|
||||
<project path="external/bazelbuild-rules_python" name="platform/external/bazelbuild-rules_python" groups="ddk" revision="3e21f23d9400ba51f10e9b76016ff6d472829b4e"/>
|
||||
<project path="external/bazelbuild-rules_rust" name="platform/external/bazelbuild-rules_rust" revision="bdd24099a80555ff8a4441e8bb47f4c7cfe413f8"/>
|
||||
</manifest>
|
||||
164
.github/workflows/avd-kernel.yml
vendored
Normal file
164
.github/workflows/avd-kernel.yml
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
name: GKI Kernel Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version_name:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
With SUBLEVEL of kernel,
|
||||
for example: android12-5.10.66
|
||||
arch:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
Build arch: aarch64/x86_64
|
||||
debug:
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
manifest_name:
|
||||
required: false
|
||||
type: string
|
||||
description: >
|
||||
Local repo manifest xml path,
|
||||
typically for AVD kernel build.
|
||||
secrets:
|
||||
BOOT_SIGN_KEY:
|
||||
required: false
|
||||
CHAT_ID:
|
||||
required: false
|
||||
BOT_TOKEN:
|
||||
required: false
|
||||
MESSAGE_THREAD_ID:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.version_name }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Maximize build space
|
||||
uses: easimon/maximize-build-space@master
|
||||
with:
|
||||
root-reserve-mb: 8192
|
||||
temp-reserve-mb: 2048
|
||||
remove-dotnet: 'true'
|
||||
remove-android: 'true'
|
||||
remove-haskell: 'true'
|
||||
remove-codeql: 'true'
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup need_upload
|
||||
id: need_upload
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
echo "UPLOAD=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup kernel source
|
||||
run: |
|
||||
echo "Free space:"
|
||||
df -h
|
||||
cd $GITHUB_WORKSPACE
|
||||
sudo apt-get install repo -y
|
||||
mkdir android-kernel && cd android-kernel
|
||||
repo init --depth=1 -u https://android.googlesource.com/kernel/manifest -m "$GITHUB_WORKSPACE/KernelSU/.github/manifests/${{ inputs.manifest_name }}" --repo-rev=v2.16
|
||||
repo --version
|
||||
repo --trace sync -c -j$(nproc --all) --no-tags
|
||||
df -h
|
||||
|
||||
- name: Setup KernelSU
|
||||
env:
|
||||
PATCH_PATH: ${{ inputs.patch_path }}
|
||||
IS_DEBUG_KERNEL: ${{ inputs.debug }}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/android-kernel
|
||||
echo "[+] KernelSU setup"
|
||||
GKI_ROOT=$(pwd)
|
||||
echo "[+] GKI_ROOT: $GKI_ROOT"
|
||||
echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
|
||||
echo "[+] Add KernelSU driver to Makefile"
|
||||
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
|
||||
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-y += kernelsu/\n" >> "$DRIVER_MAKEFILE"
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch || echo "[-] No patch found"
|
||||
|
||||
if [ "$IS_DEBUG_KERNEL" = "true" ]; then
|
||||
echo "[+] Enable debug features for kernel"
|
||||
printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
|
||||
fi
|
||||
repo status
|
||||
echo "[+] KernelSU setup done."
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "VERSION: $VERSION"
|
||||
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Make working directory clean to avoid dirty
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
|
||||
git config --global user.email "bot@kernelsu.org"
|
||||
git config --global user.name "KernelSUBot"
|
||||
cd common/ && git add -A && git commit -a -m "Add KernelSU"
|
||||
repo status
|
||||
|
||||
- name: Build kernel
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
|
||||
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
|
||||
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
|
||||
fi
|
||||
tools/bazel run --config=android_ci --config=stamp --lto=thin //common-modules/virtual-device:virtual_device_${{ inputs.arch }}_dist -- --dist_dir=dist
|
||||
NAME=kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }}
|
||||
TARGET_IMAGE=dist/bzImage
|
||||
if [ ! -e $TARGET_IMAGE ]; then
|
||||
TARGET_IMAGE=dist/Image
|
||||
fi
|
||||
mv $TARGET_IMAGE $NAME
|
||||
echo "file_path=android-kernel/$NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Kernel
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }}
|
||||
path: "${{ env.file_path }}"
|
||||
|
||||
- name: Bot session cache
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.ref_type == 'tag'
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: scripts/ksubot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Post to Telegram
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.ref_type == 'tag'
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
run: |
|
||||
TITLE=kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}
|
||||
echo "[+] title: $TITLE"
|
||||
export TITLE
|
||||
export VERSION="${{ env.kernelsu_version }}"
|
||||
echo "[+] Image to upload"
|
||||
ls -l "${{ env.file_path }}"
|
||||
if [ -n "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
pip3 install telethon==1.31.1
|
||||
python3 "$GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py" "${{ env.file_path }}"
|
||||
fi
|
||||
6
.github/workflows/build-kernel-a12.yml
vendored
6
.github/workflows/build-kernel-a12.yml
vendored
@@ -44,7 +44,9 @@ jobs:
|
||||
- sub_level: 185
|
||||
os_patch_level: 2023-09
|
||||
- sub_level: 198
|
||||
os_patch_level: 2023-11
|
||||
os_patch_level: 2024-01
|
||||
- sub_level: 205
|
||||
os_patch_level: 2024-03
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -132,4 +134,4 @@ jobs:
|
||||
version_name: android12-5.10.177
|
||||
tag: android12-5.10-2023-06
|
||||
os_patch_level: 2023-06
|
||||
patch_path: "5.10"
|
||||
patch_path: "5.10"
|
||||
13
.github/workflows/build-kernel-a13.yml
vendored
13
.github/workflows/build-kernel-a13.yml
vendored
@@ -35,7 +35,10 @@ jobs:
|
||||
os_patch_level: 2023-05
|
||||
- version: "5.10"
|
||||
sub_level: 177
|
||||
os_patch_level: 2023-06
|
||||
os_patch_level: 2023-07
|
||||
- version: "5.10"
|
||||
sub_level: 177
|
||||
os_patch_level: 2024-02
|
||||
- version: "5.10"
|
||||
sub_level: 186
|
||||
os_patch_level: 2023-08
|
||||
@@ -48,6 +51,9 @@ jobs:
|
||||
- version: "5.10"
|
||||
sub_level: 198
|
||||
os_patch_level: 2023-12
|
||||
- version: "5.10"
|
||||
sub_level: 205
|
||||
os_patch_level: 2024-02
|
||||
- version: "5.15"
|
||||
sub_level: 41
|
||||
os_patch_level: 2022-11
|
||||
@@ -72,6 +78,9 @@ jobs:
|
||||
- version: "5.15"
|
||||
sub_level: 137
|
||||
os_patch_level: 2023-12
|
||||
- version: "5.15"
|
||||
sub_level: 144
|
||||
os_patch_level: 2024-02
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -168,4 +177,4 @@ jobs:
|
||||
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
|
||||
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
16
.github/workflows/build-kernel-a14.yml
vendored
16
.github/workflows/build-kernel-a14.yml
vendored
@@ -27,6 +27,12 @@ jobs:
|
||||
- version: "5.15"
|
||||
sub_level: 131
|
||||
os_patch_level: 2023-11
|
||||
- version: "5.15"
|
||||
sub_level: 137
|
||||
os_patch_level: 2024-01
|
||||
- version: "5.15"
|
||||
sub_level: 144
|
||||
os_patch_level: 2024-03
|
||||
- version: "6.1"
|
||||
sub_level: 25
|
||||
os_patch_level: 2023-10
|
||||
@@ -35,7 +41,10 @@ jobs:
|
||||
os_patch_level: 2023-11
|
||||
- version: "6.1"
|
||||
sub_level: 57
|
||||
os_patch_level: 2023-12
|
||||
os_patch_level: 2024-01
|
||||
- version: "6.1"
|
||||
sub_level: 68
|
||||
os_patch_level: 2024-02
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -123,10 +132,13 @@ jobs:
|
||||
- version: "5.15"
|
||||
sub_level: 110
|
||||
os_patch_level: 2023-09
|
||||
- version: "6.1"
|
||||
sub_level: 68
|
||||
os_patch_level: 2024-02
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android14-${{ matrix.version }}
|
||||
version_name: android14-${{ matrix.version }}.${{ matrix.sub_level }}
|
||||
tag: android14-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
37
.github/workflows/build-kernel-avd.yml
vendored
Normal file
37
.github/workflows/build-kernel-avd.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Build Kernel - AVD
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "ci", "checkci"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-avd.yml"
|
||||
- ".github/workflows/avd-kernel.yml"
|
||||
- ".github/workflows/manifests/*xml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-avd.yml"
|
||||
- ".github/workflows/avd-kernel.yml"
|
||||
- ".github/workflows/manifests/*.xml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build-kernel:
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
||||
uses: ./.github/workflows/avd-kernel.yml
|
||||
secrets: inherit
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "android-14-avd_x86_64"
|
||||
manifest: "android-14-avd_x86_64.xml"
|
||||
arch: "x86_64"
|
||||
- version: "android-15-avd_aarch64"
|
||||
manifest: "android-15-avd_aarch64.xml"
|
||||
arch: "aarch64"
|
||||
with:
|
||||
version_name: ${{ matrix.version }}
|
||||
manifest_name: ${{ matrix.manifest }}
|
||||
arch: ${{ matrix.arch }}
|
||||
debug: true
|
||||
14
.github/workflows/build-ksud.yml
vendored
14
.github/workflows/build-ksud.yml
vendored
@@ -18,8 +18,20 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-linux-android
|
||||
- target: x86_64-pc-windows-gnu # only for build
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-pc-windows-gnu # windows pc
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-apple-darwin # Intel mac
|
||||
os: macos-latest
|
||||
- target: aarch64-apple-darwin # M chip mac
|
||||
os: macos-latest
|
||||
- target: aarch64-unknown-linux-musl # arm64 Linux
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-unknown-linux-musl # x86 Linux
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
42
.github/workflows/build-lkm.yml
vendored
Normal file
42
.github/workflows/build-lkm.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Build LKM for KernelSU
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "ci", "checkci"]
|
||||
paths:
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
- ".github/workflows/build-lkm.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
- ".github/workflows/build-lkm.yml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
jobs:
|
||||
build-lkm:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "android12-5.10"
|
||||
sub_level: 198
|
||||
os_patch_level: "2024-01"
|
||||
- version: "android13-5.10"
|
||||
sub_level: 198
|
||||
os_patch_level: 2023-12
|
||||
- version: "android13-5.15"
|
||||
sub_level: 137
|
||||
os_patch_level: 2023-12
|
||||
- version: "android14-5.15"
|
||||
sub_level: 110
|
||||
os_patch_level: 2023-09
|
||||
- version: "android14-6.1"
|
||||
sub_level: 43
|
||||
os_patch_level: 2023-11
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: ${{ matrix.version }}
|
||||
version_name: ${{ matrix.version }}.${{ matrix.sub_level }}
|
||||
tag: ${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
build_lkm: true
|
||||
64
.github/workflows/gki-kernel.yml
vendored
64
.github/workflows/gki-kernel.yml
vendored
@@ -29,7 +29,7 @@ on:
|
||||
for example: 2021-11
|
||||
default: 2022-05
|
||||
patch_path:
|
||||
required: true
|
||||
required: false
|
||||
type: string
|
||||
description: >
|
||||
Directory name of .github/patches/<patch_path>
|
||||
@@ -49,6 +49,10 @@ on:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
build_lkm:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
BOOT_SIGN_KEY:
|
||||
required: false
|
||||
@@ -124,13 +128,13 @@ jobs:
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
|
||||
echo "[+] Add KernelSU driver to Makefile"
|
||||
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || printf "\nobj-y += kernelsu/\n" >> $DRIVER_MAKEFILE
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch || echo "[-] No patch found"
|
||||
|
||||
if [ "$IS_DEBUG_KERNEL" = "true" ]; then
|
||||
echo "[+] Enable debug features for kernel"
|
||||
echo "ccflags-y += -DCONFIG_KSU_DEBUG" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
|
||||
printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
|
||||
fi
|
||||
repo status
|
||||
echo "[+] KernelSU setup done."
|
||||
@@ -154,6 +158,34 @@ jobs:
|
||||
max-size: 2G
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Setup for LKM
|
||||
if: ${{ inputs.build_lkm == true }}
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
pip install ast-grep-cli
|
||||
sudo apt-get install llvm-15 -y
|
||||
ast-grep -U -p '$$$ check_exports($$$) {$$$}' -r '' common/scripts/mod/modpost.c
|
||||
ast-grep -U -p 'check_exports($$$);' -r '' common/scripts/mod/modpost.c
|
||||
sed -i '1i KSU_MODULE := 1' $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
|
||||
echo "drivers/kernelsu/kernelsu.ko" >> common/android/gki_aarch64_modules
|
||||
|
||||
# bazel build, android14-5.15, android14-6.1 use bazel
|
||||
if [ ! -e build/build.sh ]; then
|
||||
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
|
||||
if [ -e common/modules.bzl ]; then
|
||||
sed -i 's/_COMMON_GKI_MODULES_LIST = \[/_COMMON_GKI_MODULES_LIST = \[ "drivers\/kernelsu\/kernelsu.ko",/g' common/modules.bzl
|
||||
fi
|
||||
else
|
||||
TARGET_FILE="build/kernel/build.sh"
|
||||
if [ ! -e "$TARGET_FILE" ]; then
|
||||
TARGET_FILE="build/build.sh"
|
||||
fi
|
||||
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' $TARGET_FILE || echo "No unknown symbol in $TARGET_FILE"
|
||||
sed -i 's/if ! diff -u "\${KERNEL_DIR}\/\${MODULES_ORDER}" "\${OUT_DIR}\/modules\.order"; then/if false; then/g' $TARGET_FILE
|
||||
sed -i 's@${ROOT_DIR}/build/abi/compare_to_symbol_list@echo@g' $TARGET_FILE
|
||||
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
|
||||
fi
|
||||
|
||||
- name: Make working directory clean to avoid dirty
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
@@ -163,7 +195,7 @@ jobs:
|
||||
cd common/ && git add -A && git commit -a -m "Add KernelSU"
|
||||
repo status
|
||||
|
||||
- name: Build boot.img
|
||||
- name: Build Kernel/LKM
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
|
||||
@@ -184,20 +216,34 @@ jobs:
|
||||
OUTDIR=android-kernel/dist
|
||||
fi
|
||||
mkdir output
|
||||
cp $OUTDIR/Image ./output/
|
||||
cp $OUTDIR/Image.lz4 ./output/
|
||||
git clone https://github.com/Kernel-SU/AnyKernel3
|
||||
rm -rf ./AnyKernel3/.git
|
||||
cp $OUTDIR/Image ./AnyKernel3/
|
||||
if [ "${{ inputs.build_lkm}}" = "true" ]; then
|
||||
llvm-strip-15 -d $OUTDIR/kernelsu.ko
|
||||
mv $OUTDIR/kernelsu.ko ./output/${{ inputs.version }}_kernelsu.ko
|
||||
else
|
||||
cp $OUTDIR/Image ./output/
|
||||
cp $OUTDIR/Image.lz4 ./output/
|
||||
git clone https://github.com/Kernel-SU/AnyKernel3
|
||||
rm -rf ./AnyKernel3/.git
|
||||
cp $OUTDIR/Image ./AnyKernel3/
|
||||
fi
|
||||
|
||||
- name: Upload Image and Image.gz
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ inputs.build_lkm == false }}
|
||||
with:
|
||||
name: Image-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
||||
path: ./output/*
|
||||
|
||||
- name: Upload AnyKernel3
|
||||
if: ${{ inputs.build_lkm == false }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
||||
path: ./AnyKernel3/*
|
||||
|
||||
- name: Upload LKM
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ inputs.build_lkm == true }}
|
||||
with:
|
||||
name: ${{ inputs.version }}_lkm
|
||||
path: ./output/*_kernelsu.ko
|
||||
14
.github/workflows/ksud.yml
vendored
14
.github/workflows/ksud.yml
vendored
@@ -5,19 +5,27 @@ on:
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
os:
|
||||
required: false
|
||||
type: string
|
||||
default: ubuntu-latest
|
||||
use_cache:
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ inputs.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# cross build failed after Rust 1.68, see https://github.com/cross-rs/cross/issues/1222
|
||||
- run: rustup default 1.67.0
|
||||
- name: Setup rustup
|
||||
run: |
|
||||
rustup default 1.67.0
|
||||
rustup target add x86_64-apple-darwin
|
||||
rustup target add aarch64-apple-darwin
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: userspace/ksud
|
||||
@@ -33,4 +41,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ksud-${{ inputs.target }}
|
||||
path: userspace/ksud/target/**/release/ksud
|
||||
path: userspace/ksud/target/**/release/ksud*
|
||||
|
||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -9,6 +9,9 @@ jobs:
|
||||
build-manager:
|
||||
uses: ./.github/workflows/build-manager.yml
|
||||
secrets: inherit
|
||||
build-lkm:
|
||||
uses: ./.github/workflows/build-lkm.yml
|
||||
secrets: inherit
|
||||
build-a12-kernel:
|
||||
uses: ./.github/workflows/build-kernel-a12.yml
|
||||
secrets: inherit
|
||||
@@ -27,6 +30,7 @@ jobs:
|
||||
release:
|
||||
needs:
|
||||
- build-manager
|
||||
- build-lkm
|
||||
- build-a12-kernel
|
||||
- build-a13-kernel
|
||||
- build-a14-kernel
|
||||
@@ -71,6 +75,7 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
manager/*.apk
|
||||
*-lkm/*_kernelsu.ko
|
||||
AnyKernel3-*.zip
|
||||
boot-images-*/Image-*/*.img.gz
|
||||
kernel-WSA*.zip
|
||||
|
||||
45
js/index.d.ts
vendored
Normal file
45
js/index.d.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
interface ExecOptions {
|
||||
cwd?: string,
|
||||
env?: { [key: string]: string }
|
||||
}
|
||||
|
||||
interface ExecResults {
|
||||
errno: number,
|
||||
stdout: string,
|
||||
stderr: string
|
||||
}
|
||||
|
||||
declare function exec(command: string): Promise<ExecResults>;
|
||||
declare function exec(command: string, options: ExecOptions): Promise<ExecResults>;
|
||||
|
||||
interface SpawnOptions {
|
||||
cwd?: string,
|
||||
env?: { [key: string]: string }
|
||||
}
|
||||
|
||||
interface Stdio {
|
||||
on(event: 'data', callback: (data: string) => void)
|
||||
}
|
||||
|
||||
interface ChildProcess {
|
||||
stdout: Stdio,
|
||||
stderr: Stdio,
|
||||
on(event: 'exit', callback: (code: number) => void)
|
||||
on(event: 'error', callback: (err: any) => void)
|
||||
}
|
||||
|
||||
declare function spawn(command: string): ChildProcess;
|
||||
declare function spawn(command: string, args: string[]): ChildProcess;
|
||||
declare function spawn(command: string, options: SpawnOptions): ChildProcess;
|
||||
declare function spawn(command: string, args: string[], options: SpawnOptions): ChildProcess;
|
||||
|
||||
declare function fullScreen(isFullScreen: boolean);
|
||||
|
||||
declare function toast(message: string);
|
||||
|
||||
export {
|
||||
exec,
|
||||
spawn,
|
||||
fullScreen,
|
||||
toast
|
||||
}
|
||||
@@ -71,7 +71,7 @@ function Stdio() {
|
||||
export function spawn(command, args, options) {
|
||||
if (typeof args === "undefined") {
|
||||
args = [];
|
||||
} else if (typeof args === "object") {
|
||||
} else if (!(args instanceof Array)) {
|
||||
// allow for (command, options) signature
|
||||
options = args;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"version": "1.0.6",
|
||||
"description": "Library for KernelSU's module WebUI",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
"test": "npm run test"
|
||||
},
|
||||
|
||||
4
justfile
4
justfile
@@ -8,3 +8,7 @@ build_manager: build_ksud
|
||||
cp userspace/ksud/target/aarch64-linux-android/release/ksud manager/app/src/main/jniLibs/arm64-v8a/libksud.so
|
||||
cd manager && ./gradlew aDebug
|
||||
|
||||
clippy:
|
||||
cargo fmt --manifest-path ./userspace/ksud/Cargo.toml
|
||||
cross clippy --target x86_64-pc-windows-gnu --release --manifest-path ./userspace/ksud/Cargo.toml
|
||||
cross clippy --target aarch64-linux-android --release --manifest-path ./userspace/ksud/Cargo.toml
|
||||
|
||||
@@ -14,4 +14,11 @@ config KSU_DEBUG
|
||||
help
|
||||
Enable KernelSU debug mode
|
||||
|
||||
config KSU_MODULE
|
||||
bool "Build KernelSU as a module"
|
||||
depends on KSU
|
||||
default n
|
||||
help
|
||||
Build KernelSU as a loadable kernel module
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
obj-y += ksu.o
|
||||
obj-y += allowlist.o
|
||||
kernelsu-objs := apk_sign.o
|
||||
obj-y += kernelsu.o
|
||||
obj-y += module_api.o
|
||||
obj-y += sucompat.o
|
||||
obj-y += uid_observer.o
|
||||
obj-y += manager.o
|
||||
obj-y += core_hook.o
|
||||
obj-y += ksud.o
|
||||
obj-y += embed_ksud.o
|
||||
obj-y += kernel_compat.o
|
||||
kernelsu-objs := ksu.o
|
||||
kernelsu-objs += allowlist.o
|
||||
kernelsu-objs += apk_sign.o
|
||||
kernelsu-objs += module_api.o
|
||||
kernelsu-objs += sucompat.o
|
||||
kernelsu-objs += uid_observer.o
|
||||
kernelsu-objs += manager.o
|
||||
kernelsu-objs += core_hook.o
|
||||
kernelsu-objs += ksud.o
|
||||
kernelsu-objs += embed_ksud.o
|
||||
kernelsu-objs += kernel_compat.o
|
||||
|
||||
kernelsu-objs += selinux/selinux.o
|
||||
kernelsu-objs += selinux/sepolicy.o
|
||||
kernelsu-objs += selinux/rules.o
|
||||
ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
|
||||
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
|
||||
|
||||
ifndef KSU_MODULE
|
||||
obj-y += kernelsu.o
|
||||
else
|
||||
obj-m += kernelsu.o
|
||||
endif
|
||||
|
||||
obj-y += selinux/
|
||||
# .git is a text file while the module is imported by 'git submodule add'.
|
||||
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
|
||||
$(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow)
|
||||
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD)
|
||||
# ksu_version: major * 10000 + git version + 200 for historical reasons
|
||||
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 200))
|
||||
@@ -24,6 +35,14 @@ $(warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git subm
|
||||
ccflags-y += -DKSU_VERSION=16
|
||||
endif
|
||||
|
||||
ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0)
|
||||
ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID
|
||||
endif
|
||||
|
||||
ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0)
|
||||
ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE
|
||||
endif
|
||||
|
||||
ifndef KSU_EXPECTED_SIZE
|
||||
KSU_EXPECTED_SIZE := 0x033b
|
||||
endif
|
||||
@@ -42,5 +61,8 @@ $(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
||||
|
||||
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
|
||||
ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\"
|
||||
|
||||
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
|
||||
ccflags-y += -Wno-declaration-after-statement
|
||||
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
|
||||
|
||||
# Keep a new line here!! Because someone may append config
|
||||
|
||||
@@ -4,12 +4,20 @@
|
||||
#include "linux/err.h"
|
||||
#include "linux/init.h"
|
||||
#include "linux/init_task.h"
|
||||
#include "linux/kallsyms.h"
|
||||
#include "linux/kernel.h"
|
||||
#include "linux/kprobes.h"
|
||||
#include "linux/list.h"
|
||||
#include "linux/lsm_hooks.h"
|
||||
#include "linux/mm.h"
|
||||
#include "linux/mm_types.h"
|
||||
#include "linux/nsproxy.h"
|
||||
#include "linux/path.h"
|
||||
#include "linux/printk.h"
|
||||
#include "linux/sched.h"
|
||||
#include "linux/security.h"
|
||||
#include "linux/stddef.h"
|
||||
#include "linux/types.h"
|
||||
#include "linux/uaccess.h"
|
||||
#include "linux/uidgid.h"
|
||||
#include "linux/version.h"
|
||||
@@ -25,6 +33,7 @@
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksu.h"
|
||||
#include "ksud.h"
|
||||
#include "linux/vmalloc.h"
|
||||
#include "manager.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "uid_observer.h"
|
||||
@@ -220,7 +229,9 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// pr_info("option: 0x%x, cmd: %ld\n", option, arg2);
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("option: 0x%x, cmd: %ld\n", option, arg2);
|
||||
#endif
|
||||
|
||||
if (arg2 == CMD_BECOME_MANAGER) {
|
||||
// quick check
|
||||
@@ -231,8 +242,10 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
return 0;
|
||||
}
|
||||
if (ksu_is_manager_uid_valid()) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("manager already exist: %d\n",
|
||||
ksu_get_manager_uid());
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -722,14 +735,181 @@ void __init ksu_lsm_hook_init(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef MODULE
|
||||
static int override_security_head(void *head, const void *new_head, size_t len)
|
||||
{
|
||||
unsigned long base = (unsigned long)head & PAGE_MASK;
|
||||
unsigned long offset = offset_in_page(head);
|
||||
|
||||
// this is impossible for our case because the page alignment
|
||||
// but be careful for other cases!
|
||||
BUG_ON(offset + len > PAGE_SIZE);
|
||||
struct page *page = phys_to_page(__pa(base));
|
||||
if (!page) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
void *addr = vmap(&page, 1, VM_MAP, PAGE_KERNEL);
|
||||
if (!addr) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(addr + offset, new_head, len);
|
||||
vunmap(addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_security_hook_list(struct hlist_head *head)
|
||||
{
|
||||
struct hlist_node *temp;
|
||||
struct security_hook_list *entry;
|
||||
|
||||
if (!head)
|
||||
return;
|
||||
|
||||
hlist_for_each_entry_safe (entry, temp, head, list) {
|
||||
hlist_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
|
||||
kfree(head);
|
||||
}
|
||||
|
||||
struct hlist_head *copy_security_hlist(struct hlist_head *orig)
|
||||
{
|
||||
struct hlist_head *new_head = kmalloc(sizeof(*new_head), GFP_KERNEL);
|
||||
if (!new_head)
|
||||
return NULL;
|
||||
|
||||
INIT_HLIST_HEAD(new_head);
|
||||
|
||||
struct security_hook_list *entry;
|
||||
struct security_hook_list *new_entry;
|
||||
|
||||
hlist_for_each_entry (entry, orig, list) {
|
||||
new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL);
|
||||
if (!new_entry) {
|
||||
free_security_hook_list(new_head);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*new_entry = *entry;
|
||||
|
||||
hlist_add_tail_rcu(&new_entry->list, new_head);
|
||||
}
|
||||
|
||||
return new_head;
|
||||
}
|
||||
|
||||
#define LSM_SEARCH_MAX 180 // This should be enough to iterate
|
||||
static void *find_head_addr(void *security_ptr, int *index)
|
||||
{
|
||||
if (!security_ptr) {
|
||||
return NULL;
|
||||
}
|
||||
struct hlist_head *head_start =
|
||||
(struct hlist_head *)&security_hook_heads;
|
||||
|
||||
for (int i = 0; i < LSM_SEARCH_MAX; i++) {
|
||||
struct hlist_head *head = head_start + i;
|
||||
struct security_hook_list *pos;
|
||||
hlist_for_each_entry (pos, head, list) {
|
||||
if (pos->hook.capget == security_ptr) {
|
||||
if (index) {
|
||||
*index = i;
|
||||
}
|
||||
return head;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define GET_SYMBOL_ADDR(sym) \
|
||||
({ \
|
||||
void *addr = kallsyms_lookup_name(#sym ".cfi_jt"); \
|
||||
if (!addr) { \
|
||||
addr = kallsyms_lookup_name(#sym); \
|
||||
} \
|
||||
addr; \
|
||||
})
|
||||
|
||||
#define KSU_LSM_HOOK_HACK_INIT(head_ptr, name, func) \
|
||||
do { \
|
||||
static struct security_hook_list hook = { \
|
||||
.hook = { .name = func } \
|
||||
}; \
|
||||
hook.head = head_ptr; \
|
||||
hook.lsm = "ksu"; \
|
||||
struct hlist_head *new_head = copy_security_hlist(hook.head); \
|
||||
if (!new_head) { \
|
||||
pr_err("Failed to copy security list: %s\n", #name); \
|
||||
break; \
|
||||
} \
|
||||
hlist_add_tail_rcu(&hook.list, new_head); \
|
||||
if (override_security_head(hook.head, new_head, \
|
||||
sizeof(*new_head))) { \
|
||||
free_security_hook_list(new_head); \
|
||||
pr_err("Failed to hack lsm for: %s\n", #name); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void __init ksu_lsm_hook_init_hack(void)
|
||||
{
|
||||
void *cap_prctl = GET_SYMBOL_ADDR(cap_task_prctl);
|
||||
void *prctl_head = find_head_addr(cap_prctl, NULL);
|
||||
if (prctl_head) {
|
||||
if (prctl_head != &security_hook_heads.task_prctl) {
|
||||
pr_warn("prctl's address has shifted!\n");
|
||||
}
|
||||
KSU_LSM_HOOK_HACK_INIT(prctl_head, task_prctl, ksu_task_prctl);
|
||||
} else {
|
||||
pr_warn("Failed to find task_prctl!\n");
|
||||
}
|
||||
|
||||
int inode_killpriv_index = -1;
|
||||
void *cap_killpriv = GET_SYMBOL_ADDR(cap_inode_killpriv);
|
||||
find_head_addr(cap_killpriv, &inode_killpriv_index);
|
||||
if (inode_killpriv_index < 0) {
|
||||
pr_warn("Failed to find inode_rename, use kprobe instead!\n");
|
||||
register_kprobe(&renameat_kp);
|
||||
} else {
|
||||
int inode_rename_index = inode_killpriv_index +
|
||||
&security_hook_heads.inode_rename -
|
||||
&security_hook_heads.inode_killpriv;
|
||||
struct hlist_head *head_start =
|
||||
(struct hlist_head *)&security_hook_heads;
|
||||
void *inode_rename_head = head_start + inode_rename_index;
|
||||
if (inode_rename_head != &security_hook_heads.inode_rename) {
|
||||
pr_warn("inode_rename's address has shifted!\n");
|
||||
}
|
||||
KSU_LSM_HOOK_HACK_INIT(inode_rename_head, inode_rename,
|
||||
ksu_inode_rename);
|
||||
}
|
||||
void *cap_setuid = GET_SYMBOL_ADDR(cap_task_fix_setuid);
|
||||
void *setuid_head = find_head_addr(cap_setuid, NULL);
|
||||
if (setuid_head) {
|
||||
if (setuid_head != &security_hook_heads.task_fix_setuid) {
|
||||
pr_warn("setuid's address has shifted!\n");
|
||||
}
|
||||
KSU_LSM_HOOK_HACK_INIT(setuid_head, task_fix_setuid,
|
||||
ksu_task_fix_setuid);
|
||||
} else {
|
||||
pr_warn("Failed to find task_fix_setuid!\n");
|
||||
}
|
||||
smp_mb();
|
||||
}
|
||||
#endif
|
||||
|
||||
void __init ksu_core_init(void)
|
||||
{
|
||||
#ifndef MODULE
|
||||
pr_info("ksu_lsm_hook_init\n");
|
||||
ksu_lsm_hook_init();
|
||||
|
||||
#else
|
||||
pr_info("ksu_kprobe_init\n");
|
||||
ksu_kprobe_init();
|
||||
pr_info("ksu_lsm_hook_init hack!!!!\n");
|
||||
ksu_lsm_hook_init_hack();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
* Huawei Hisi Kernel EBITMAP Enable or Disable Flag ,
|
||||
* From ss/ebitmap.h
|
||||
*/
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) && \
|
||||
LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) && \
|
||||
LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || \
|
||||
LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) && \
|
||||
LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
|
||||
#ifdef HISI_SELINUX_EBITMAP_RO
|
||||
#define CONFIG_IS_HW_HISI
|
||||
#endif
|
||||
|
||||
@@ -37,6 +37,23 @@ fun parseKernelVersion(version: String): KernelVersion {
|
||||
}
|
||||
}
|
||||
|
||||
fun parseKMI(input: String): String? {
|
||||
val regex = Regex("(.* )?(\\d+\\.\\d+)(\\S+)?(android\\d+)(.*)")
|
||||
val result = regex.find(input)
|
||||
|
||||
return result?.let {
|
||||
val androidVersion = it.groups[4]?.value ?: ""
|
||||
val kernelVersion = it.groups[2]?.value ?: ""
|
||||
"$androidVersion-$kernelVersion"
|
||||
}
|
||||
}
|
||||
|
||||
fun getKMI(): String? {
|
||||
Os.uname().release.let {
|
||||
return parseKMI(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun getKernelVersion(): KernelVersion {
|
||||
Os.uname().release.let {
|
||||
return parseKernelVersion(it)
|
||||
|
||||
@@ -5,13 +5,7 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -27,11 +21,9 @@ import com.ramcosta.composedestinations.navigation.popBackStack
|
||||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.ksuApp
|
||||
import me.weishu.kernelsu.ui.component.rememberDialogHostState
|
||||
import me.weishu.kernelsu.ui.screen.BottomBarDestination
|
||||
import me.weishu.kernelsu.ui.screen.NavGraphs
|
||||
import me.weishu.kernelsu.ui.theme.KernelSUTheme
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.util.rootAvailable
|
||||
|
||||
@@ -54,7 +46,6 @@ class MainActivity : ComponentActivity() {
|
||||
) { innerPadding ->
|
||||
CompositionLocalProvider(
|
||||
LocalSnackbarHost provides snackbarHostState,
|
||||
LocalDialogHost provides rememberDialogHostState(),
|
||||
) {
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -52,11 +53,9 @@ fun AboutCard() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AboutDialog(showAboutDialog: MutableState<Boolean>) {
|
||||
if (showAboutDialog.value) {
|
||||
Dialog(onDismissRequest = { showAboutDialog.value = false }) {
|
||||
AboutCard()
|
||||
}
|
||||
fun AboutDialog(dismiss: () -> Unit) {
|
||||
Dialog(onDismissRequest = { dismiss() }) {
|
||||
AboutCard()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package me.weishu.kernelsu.ui.component
|
||||
|
||||
import android.graphics.text.LineBreaker
|
||||
import android.os.Parcelable
|
||||
import android.text.Layout
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -10,14 +12,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
@@ -28,48 +26,48 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.utils.NoCopySpannableFactory
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
interface DialogVisuals
|
||||
private const val TAG = "DialogComponent"
|
||||
|
||||
interface LoadingDialogVisuals : DialogVisuals
|
||||
|
||||
interface PromptDialogVisuals : DialogVisuals {
|
||||
interface ConfirmDialogVisuals : Parcelable {
|
||||
val title: String
|
||||
val content: String
|
||||
}
|
||||
|
||||
interface ConfirmDialogVisuals : PromptDialogVisuals {
|
||||
val isMarkdown: Boolean
|
||||
val confirm: String?
|
||||
val dismiss: String?
|
||||
val isMarkdown: Boolean
|
||||
}
|
||||
|
||||
|
||||
sealed interface DialogData {
|
||||
val visuals: DialogVisuals
|
||||
@Parcelize
|
||||
private data class ConfirmDialogVisualsImpl(
|
||||
override val title: String,
|
||||
override val content: String,
|
||||
override val isMarkdown: Boolean,
|
||||
override val confirm: String?,
|
||||
override val dismiss: String?,
|
||||
) : ConfirmDialogVisuals {
|
||||
companion object {
|
||||
val Empty: ConfirmDialogVisuals = ConfirmDialogVisualsImpl("", "", false, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
interface LoadingDialogData : DialogData {
|
||||
override val visuals: LoadingDialogVisuals
|
||||
fun dismiss()
|
||||
interface DialogHandle {
|
||||
val isShown: Boolean
|
||||
val dialogType: String
|
||||
fun show()
|
||||
fun hide()
|
||||
}
|
||||
|
||||
interface PromptDialogData : DialogData {
|
||||
override val visuals: PromptDialogVisuals
|
||||
fun dismiss()
|
||||
}
|
||||
|
||||
interface ConfirmDialogData : PromptDialogData {
|
||||
override val visuals: ConfirmDialogVisuals
|
||||
fun confirm()
|
||||
interface LoadingDialogHandle : DialogHandle {
|
||||
suspend fun <R> withLoading(block: suspend () -> R): R
|
||||
fun showLoading()
|
||||
}
|
||||
|
||||
sealed interface ConfirmResult {
|
||||
@@ -77,143 +75,313 @@ sealed interface ConfirmResult {
|
||||
object Canceled : ConfirmResult
|
||||
}
|
||||
|
||||
class DialogHostState {
|
||||
interface ConfirmDialogHandle : DialogHandle {
|
||||
val visuals: ConfirmDialogVisuals
|
||||
|
||||
private object LoadingDialogVisualsImpl : LoadingDialogVisuals
|
||||
|
||||
private data class PromptDialogVisualsImpl(
|
||||
override val title: String, override val content: String
|
||||
) : PromptDialogVisuals
|
||||
|
||||
private data class ConfirmDialogVisualsImpl(
|
||||
override val title: String,
|
||||
override val content: String,
|
||||
override val confirm: String?,
|
||||
override val dismiss: String?,
|
||||
override val isMarkdown: Boolean,
|
||||
) : ConfirmDialogVisuals
|
||||
|
||||
private data class LoadingDialogDataImpl(
|
||||
override val visuals: LoadingDialogVisuals,
|
||||
private val continuation: CancellableContinuation<Unit>,
|
||||
) : LoadingDialogData {
|
||||
override fun dismiss() {
|
||||
if (continuation.isActive) continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private data class PromptDialogDataImpl(
|
||||
override val visuals: PromptDialogVisuals,
|
||||
private val continuation: CancellableContinuation<Unit>,
|
||||
) : PromptDialogData {
|
||||
override fun dismiss() {
|
||||
if (continuation.isActive) continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private data class ConfirmDialogDataImpl(
|
||||
override val visuals: ConfirmDialogVisuals,
|
||||
private val continuation: CancellableContinuation<ConfirmResult>
|
||||
) : ConfirmDialogData {
|
||||
|
||||
override fun confirm() {
|
||||
if (continuation.isActive) continuation.resume(ConfirmResult.Confirmed)
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
if (continuation.isActive) continuation.resume(ConfirmResult.Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
var currentDialogData by mutableStateOf<DialogData?>(null)
|
||||
private set
|
||||
|
||||
suspend fun showLoading() {
|
||||
try {
|
||||
mutex.withLock {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
currentDialogData = LoadingDialogDataImpl(
|
||||
visuals = LoadingDialogVisualsImpl, continuation = continuation
|
||||
)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
currentDialogData = null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <R> withLoading(block: suspend () -> R) = coroutineScope {
|
||||
val showLoading = launch {
|
||||
showLoading()
|
||||
}
|
||||
|
||||
val result = block()
|
||||
|
||||
showLoading.cancel()
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
suspend fun showPrompt(title: String, content: String) {
|
||||
try {
|
||||
mutex.withLock {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
currentDialogData = PromptDialogDataImpl(
|
||||
visuals = PromptDialogVisualsImpl(title, content),
|
||||
continuation = continuation
|
||||
)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
currentDialogData = null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun showConfirm(
|
||||
fun showConfirm(
|
||||
title: String,
|
||||
content: String,
|
||||
markdown: Boolean = false,
|
||||
confirm: String? = null,
|
||||
dismiss: String? = null
|
||||
): ConfirmResult = mutex.withLock {
|
||||
try {
|
||||
return@withLock suspendCancellableCoroutine { continuation ->
|
||||
currentDialogData = ConfirmDialogDataImpl(
|
||||
visuals = ConfirmDialogVisualsImpl(title, content, confirm, dismiss, markdown),
|
||||
continuation = continuation
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun awaitConfirm(
|
||||
title: String,
|
||||
content: String,
|
||||
markdown: Boolean = false,
|
||||
confirm: String? = null,
|
||||
dismiss: String? = null
|
||||
): ConfirmResult
|
||||
}
|
||||
|
||||
private abstract class DialogHandleBase(
|
||||
protected val visible: MutableState<Boolean>,
|
||||
protected val coroutineScope: CoroutineScope
|
||||
) : DialogHandle {
|
||||
override val isShown: Boolean
|
||||
get() = visible.value
|
||||
|
||||
override fun show() {
|
||||
coroutineScope.launch {
|
||||
visible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
final override fun hide() {
|
||||
coroutineScope.launch {
|
||||
visible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return dialogType
|
||||
}
|
||||
}
|
||||
|
||||
private class LoadingDialogHandleImpl(
|
||||
visible: MutableState<Boolean>,
|
||||
coroutineScope: CoroutineScope
|
||||
) : LoadingDialogHandle, DialogHandleBase(visible, coroutineScope) {
|
||||
override suspend fun <R> withLoading(block: suspend () -> R): R {
|
||||
return coroutineScope.async {
|
||||
try {
|
||||
visible.value = true
|
||||
block()
|
||||
} finally {
|
||||
visible.value = false
|
||||
}
|
||||
}.await()
|
||||
}
|
||||
|
||||
override fun showLoading() {
|
||||
show()
|
||||
}
|
||||
|
||||
override val dialogType: String get() = "LoadingDialog"
|
||||
}
|
||||
|
||||
typealias NullableCallback = (() -> Unit)?
|
||||
|
||||
interface ConfirmCallback {
|
||||
|
||||
val onConfirm: NullableCallback
|
||||
|
||||
val onDismiss: NullableCallback
|
||||
|
||||
val isEmpty: Boolean get() = onConfirm == null && onDismiss == null
|
||||
|
||||
companion object {
|
||||
operator fun invoke(onConfirmProvider: () -> NullableCallback, onDismissProvider: () -> NullableCallback): ConfirmCallback {
|
||||
return object : ConfirmCallback {
|
||||
override val onConfirm: NullableCallback
|
||||
get() = onConfirmProvider()
|
||||
override val onDismiss: NullableCallback
|
||||
get() = onDismissProvider()
|
||||
}
|
||||
} finally {
|
||||
currentDialogData = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ConfirmDialogHandleImpl(
|
||||
visible: MutableState<Boolean>,
|
||||
coroutineScope: CoroutineScope,
|
||||
callback: ConfirmCallback,
|
||||
override var visuals: ConfirmDialogVisuals = ConfirmDialogVisualsImpl.Empty,
|
||||
private val resultFlow: ReceiveChannel<ConfirmResult>
|
||||
) : ConfirmDialogHandle, DialogHandleBase(visible, coroutineScope) {
|
||||
private class ResultCollector(
|
||||
private val callback: ConfirmCallback
|
||||
) : FlowCollector<ConfirmResult> {
|
||||
fun handleResult(result: ConfirmResult) {
|
||||
Log.d(TAG, "handleResult: ${result.javaClass.simpleName}")
|
||||
when (result) {
|
||||
ConfirmResult.Confirmed -> onConfirm()
|
||||
ConfirmResult.Canceled -> onDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
fun onConfirm() {
|
||||
callback.onConfirm?.invoke()
|
||||
}
|
||||
|
||||
fun onDismiss() {
|
||||
callback.onDismiss?.invoke()
|
||||
}
|
||||
|
||||
override suspend fun emit(value: ConfirmResult) {
|
||||
handleResult(value)
|
||||
}
|
||||
}
|
||||
|
||||
private val resultCollector = ResultCollector(callback)
|
||||
|
||||
private var awaitContinuation: CancellableContinuation<ConfirmResult>? = null
|
||||
|
||||
private val isCallbackEmpty = callback.isEmpty
|
||||
|
||||
init {
|
||||
coroutineScope.launch {
|
||||
resultFlow
|
||||
.consumeAsFlow()
|
||||
.onEach { result ->
|
||||
awaitContinuation?.let {
|
||||
awaitContinuation = null
|
||||
if (it.isActive) {
|
||||
it.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onEach { hide() }
|
||||
.collect(resultCollector)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun awaitResult(): ConfirmResult {
|
||||
return suspendCancellableCoroutine {
|
||||
awaitContinuation = it.apply {
|
||||
if (isCallbackEmpty) {
|
||||
invokeOnCancellation {
|
||||
visible.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateVisuals(visuals: ConfirmDialogVisuals) {
|
||||
this.visuals = visuals
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
if (visuals !== ConfirmDialogVisualsImpl.Empty) {
|
||||
super.show()
|
||||
} else {
|
||||
throw UnsupportedOperationException("can't show confirm dialog with the Empty visuals")
|
||||
}
|
||||
}
|
||||
|
||||
override fun showConfirm(
|
||||
title: String,
|
||||
content: String,
|
||||
markdown: Boolean,
|
||||
confirm: String?,
|
||||
dismiss: String?
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun awaitConfirm(
|
||||
title: String,
|
||||
content: String,
|
||||
markdown: Boolean,
|
||||
confirm: String?,
|
||||
dismiss: String?
|
||||
): ConfirmResult {
|
||||
coroutineScope.launch {
|
||||
updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))
|
||||
show()
|
||||
}
|
||||
return awaitResult()
|
||||
}
|
||||
|
||||
override val dialogType: String get() = "ConfirmDialog"
|
||||
|
||||
override fun toString(): String {
|
||||
return "${super.toString()}(visuals: $visuals)"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun Saver(
|
||||
visible: MutableState<Boolean>,
|
||||
coroutineScope: CoroutineScope,
|
||||
callback: ConfirmCallback,
|
||||
resultChannel: ReceiveChannel<ConfirmResult>
|
||||
) = Saver<ConfirmDialogHandle, ConfirmDialogVisuals>(
|
||||
save = {
|
||||
it.visuals
|
||||
},
|
||||
restore = {
|
||||
Log.d(TAG, "ConfirmDialog restore, visuals: $it")
|
||||
ConfirmDialogHandleImpl(visible, coroutineScope, callback, it, resultChannel)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomDialogHandleImpl(
|
||||
visible: MutableState<Boolean>,
|
||||
coroutineScope: CoroutineScope
|
||||
) : DialogHandleBase(visible, coroutineScope) {
|
||||
override val dialogType: String get() = "CustomDialog"
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberDialogHostState(): DialogHostState {
|
||||
fun rememberLoadingDialog(): LoadingDialogHandle {
|
||||
val visible = remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
if (visible.value) {
|
||||
LoadingDialog()
|
||||
}
|
||||
|
||||
return remember {
|
||||
DialogHostState()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T : DialogData> DialogData?.tryInto(): T? {
|
||||
return when (this) {
|
||||
is T -> this
|
||||
else -> null
|
||||
LoadingDialogHandleImpl(visible, coroutineScope)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoadingDialog(
|
||||
state: DialogHostState = LocalDialogHost.current,
|
||||
) {
|
||||
state.currentDialogData.tryInto<LoadingDialogData>() ?: return
|
||||
val dialogProperties = remember {
|
||||
DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)
|
||||
private fun rememberConfirmDialog(visuals: ConfirmDialogVisuals, callback: ConfirmCallback): ConfirmDialogHandle {
|
||||
val visible = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
Dialog(onDismissRequest = {}, properties = dialogProperties) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val resultChannel = remember {
|
||||
Channel<ConfirmResult>()
|
||||
}
|
||||
|
||||
val handle = rememberSaveable(
|
||||
saver = ConfirmDialogHandleImpl.Saver(visible, coroutineScope, callback, resultChannel),
|
||||
init = {
|
||||
ConfirmDialogHandleImpl(visible, coroutineScope, callback, visuals, resultChannel)
|
||||
}
|
||||
)
|
||||
|
||||
if (visible.value) {
|
||||
ConfirmDialog(
|
||||
handle.visuals,
|
||||
confirm = { coroutineScope.launch { resultChannel.send(ConfirmResult.Confirmed) } },
|
||||
dismiss = { coroutineScope.launch { resultChannel.send(ConfirmResult.Canceled) } }
|
||||
)
|
||||
}
|
||||
|
||||
return handle
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberConfirmCallback(onConfirm: NullableCallback, onDismiss: NullableCallback): ConfirmCallback {
|
||||
val currentOnConfirm by rememberUpdatedState(newValue = onConfirm)
|
||||
val currentOnDismiss by rememberUpdatedState(newValue = onDismiss)
|
||||
return remember {
|
||||
ConfirmCallback({ currentOnConfirm }, { currentOnDismiss })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberConfirmDialog(onConfirm: NullableCallback = null, onDismiss: NullableCallback = null): ConfirmDialogHandle {
|
||||
return rememberConfirmDialog(rememberConfirmCallback(onConfirm, onDismiss))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberConfirmDialog(callback: ConfirmCallback): ConfirmDialogHandle {
|
||||
return rememberConfirmDialog(ConfirmDialogVisualsImpl.Empty, callback)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberCustomDialog(composable: @Composable (dismiss: () -> Unit) -> Unit): DialogHandle {
|
||||
val visible = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
if (visible.value) {
|
||||
composable { visible.value = false }
|
||||
}
|
||||
return remember {
|
||||
CustomDialogHandleImpl(visible, coroutineScope)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingDialog() {
|
||||
Dialog(
|
||||
onDismissRequest = {},
|
||||
properties = DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.size(100.dp), shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
@@ -227,41 +395,10 @@ fun LoadingDialog(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PromptDialog(
|
||||
state: DialogHostState = LocalDialogHost.current,
|
||||
) {
|
||||
val promptDialogData = state.currentDialogData.tryInto<PromptDialogData>() ?: return
|
||||
|
||||
val visuals = promptDialogData.visuals
|
||||
private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, dismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
promptDialogData.dismiss()
|
||||
},
|
||||
title = {
|
||||
Text(text = visuals.title)
|
||||
},
|
||||
text = {
|
||||
Text(text = visuals.content)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { promptDialogData.dismiss() }) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = null,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
|
||||
val confirmDialogData = state.currentDialogData.tryInto<ConfirmDialogData>() ?: return
|
||||
|
||||
val visuals = confirmDialogData.visuals
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
confirmDialogData.dismiss()
|
||||
dismiss()
|
||||
},
|
||||
title = {
|
||||
Text(text = visuals.title)
|
||||
@@ -274,17 +411,18 @@ fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { confirmDialogData.confirm() }) {
|
||||
TextButton(onClick = confirm) {
|
||||
Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { confirmDialogData.dismiss() }) {
|
||||
TextButton(onClick = dismiss) {
|
||||
Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MarkdownContent(content: String) {
|
||||
val contentColor = LocalContentColor.current
|
||||
@@ -307,5 +445,6 @@ private fun MarkdownContent(content: String) {
|
||||
update = {
|
||||
Markwon.create(it.context).setMarkdown(it, content)
|
||||
it.setTextColor(contentColor.toArgb())
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -53,6 +54,7 @@ import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.profile.Capabilities
|
||||
import me.weishu.kernelsu.profile.Groups
|
||||
import me.weishu.kernelsu.ui.component.rememberCustomDialog
|
||||
import me.weishu.kernelsu.ui.util.isSepolicyValid
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -187,10 +189,7 @@ fun RootProfileConfig(
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>) -> Unit) {
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialog) {
|
||||
val selectGroupsDialog = rememberCustomDialog { dismiss: () -> Unit ->
|
||||
val groups = Groups.values().sortedWith(
|
||||
compareBy<Groups> { if (selected.contains(it)) 0 else 1 }
|
||||
.then(compareBy {
|
||||
@@ -217,7 +216,7 @@ fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>)
|
||||
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
||||
closeSelection(selection)
|
||||
}, onCloseRequest = {
|
||||
showDialog = false
|
||||
dismiss()
|
||||
}),
|
||||
header = Header.Default(
|
||||
title = stringResource(R.string.profile_groups),
|
||||
@@ -241,7 +240,7 @@ fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.clickable {
|
||||
showDialog = true
|
||||
selectGroupsDialog.show()
|
||||
}) {
|
||||
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
@@ -265,10 +264,7 @@ fun CapsPanel(
|
||||
selected: Collection<Capabilities>,
|
||||
closeSelection: (selection: Set<Capabilities>) -> Unit
|
||||
) {
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialog) {
|
||||
val selectCapabilitiesDialog = rememberCustomDialog { dismiss ->
|
||||
val caps = Capabilities.values().sortedWith(
|
||||
compareBy<Capabilities> { if (selected.contains(it)) 0 else 1 }
|
||||
.then(compareBy { it.name })
|
||||
@@ -286,7 +282,7 @@ fun CapsPanel(
|
||||
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
||||
closeSelection(selection)
|
||||
}, onCloseRequest = {
|
||||
showDialog = false
|
||||
dismiss()
|
||||
}),
|
||||
header = Header.Default(
|
||||
title = stringResource(R.string.profile_capabilities),
|
||||
@@ -309,7 +305,7 @@ fun CapsPanel(
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.clickable {
|
||||
showDialog = true
|
||||
selectCapabilitiesDialog.show()
|
||||
}) {
|
||||
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
@@ -377,8 +373,7 @@ private fun SELinuxPanel(
|
||||
profile: Natives.Profile,
|
||||
onSELinuxChange: (domain: String, rules: String) -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
if (showDialog) {
|
||||
val editSELinuxDialog = rememberCustomDialog { dismiss ->
|
||||
var domain by remember { mutableStateOf(profile.context) }
|
||||
var rules by remember { mutableStateOf(profile.rules) }
|
||||
|
||||
@@ -430,7 +425,7 @@ private fun SELinuxPanel(
|
||||
onSELinuxChange(domain, rules)
|
||||
},
|
||||
onCloseRequest = {
|
||||
showDialog = false
|
||||
dismiss()
|
||||
}),
|
||||
header = Header.Default(
|
||||
title = stringResource(R.string.profile_selinux_context),
|
||||
@@ -449,7 +444,7 @@ private fun SELinuxPanel(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
showDialog = true
|
||||
editSELinuxDialog.show()
|
||||
},
|
||||
enabled = false,
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||
|
||||
@@ -183,7 +183,7 @@ private fun AppProfileInner(
|
||||
} else {
|
||||
Mode.Custom
|
||||
}
|
||||
var mode by remember {
|
||||
var mode by rememberSaveable {
|
||||
mutableStateOf(initialMode)
|
||||
}
|
||||
ProfileBox(mode, true) {
|
||||
|
||||
189
manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt
Normal file
189
manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt
Normal file
@@ -0,0 +1,189 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.KeyEventBlocker
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.util.installBoot
|
||||
import me.weishu.kernelsu.ui.util.installModule
|
||||
import me.weishu.kernelsu.ui.util.reboot
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/1/1.
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
@Destination
|
||||
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
|
||||
var text by rememberSaveable { mutableStateOf("") }
|
||||
val logContent = rememberSaveable { StringBuilder() }
|
||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (text.isNotEmpty()) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
flashIt(flashIt, onFinish = { showReboot ->
|
||||
if (showReboot) {
|
||||
showFloatAction = true
|
||||
}
|
||||
}, onStdout = {
|
||||
text += "$it\n"
|
||||
logContent.append(it).append("\n")
|
||||
}, onStderr = {
|
||||
logContent.append(it).append("\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
onBack = {
|
||||
navigator.popBackStack()
|
||||
},
|
||||
onSave = {
|
||||
scope.launch {
|
||||
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
|
||||
val date = format.format(Date())
|
||||
val file = File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
"KernelSU_install_log_${date}.log"
|
||||
)
|
||||
file.writeText(logContent.toString())
|
||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (showFloatAction) {
|
||||
val reboot = stringResource(id = R.string.reboot)
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
reboot()
|
||||
}
|
||||
}
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Refresh, reboot) },
|
||||
text = { Text(text = reboot) },
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
) { innerPadding ->
|
||||
KeyEventBlocker {
|
||||
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(1f)
|
||||
.padding(innerPadding)
|
||||
.verticalScroll(scrollState),
|
||||
) {
|
||||
LaunchedEffect(text) {
|
||||
scrollState.animateScrollTo(scrollState.maxValue)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = text,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
sealed class FlashIt : Parcelable {
|
||||
data class FlashBoot(val bootUri: Uri? = null, val koUri: Uri, val ota: Boolean) : FlashIt()
|
||||
|
||||
data class FlashModule(val uri: Uri) : FlashIt()
|
||||
}
|
||||
|
||||
fun flashIt(
|
||||
flashIt: FlashIt, onFinish: (Boolean) -> Unit,
|
||||
onStdout: (String) -> Unit,
|
||||
onStderr: (String) -> Unit
|
||||
) {
|
||||
when (flashIt) {
|
||||
is FlashIt.FlashBoot -> installBoot(
|
||||
flashIt.bootUri,
|
||||
flashIt.koUri,
|
||||
flashIt.ota,
|
||||
onFinish,
|
||||
onStdout,
|
||||
onStderr
|
||||
)
|
||||
|
||||
is FlashIt.FlashModule -> installModule(flashIt.uri, onFinish, onStdout, onStderr)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.install)) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onSave) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Save,
|
||||
contentDescription = "Localized description"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun InstallPreview() {
|
||||
// InstallScreen(DestinationsNavigator(), uri = Uri.EMPTY)
|
||||
}
|
||||
@@ -5,11 +5,13 @@ import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.system.Os
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Archive
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.outlined.Block
|
||||
@@ -29,12 +31,11 @@ import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.weishu.kernelsu.*
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
||||
import me.weishu.kernelsu.ui.component.ConfirmResult
|
||||
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
||||
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
||||
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.*
|
||||
|
||||
@@ -42,9 +43,13 @@ import me.weishu.kernelsu.ui.util.*
|
||||
@Destination
|
||||
@Composable
|
||||
fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
val kernelVersion = getKernelVersion()
|
||||
|
||||
Scaffold(topBar = {
|
||||
TopBar(onSettingsClick = {
|
||||
TopBar(kernelVersion, onSettingsClick = {
|
||||
navigator.navigate(SettingScreenDestination)
|
||||
}, onInstallClick = {
|
||||
navigator.navigate(InstallScreenDestination)
|
||||
})
|
||||
}) { innerPadding ->
|
||||
Column(
|
||||
@@ -54,14 +59,15 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
val kernelVersion = getKernelVersion()
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
SideEffect {
|
||||
if (isManager) install()
|
||||
}
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
StatusCard(kernelVersion, ksuVersion)
|
||||
StatusCard(kernelVersion, ksuVersion) {
|
||||
navigator.navigate(InstallScreenDestination)
|
||||
}
|
||||
if (isManager && Natives.requireNewKernel()) {
|
||||
WarningCard(
|
||||
stringResource(id = R.string.require_kernel_version).format(
|
||||
@@ -69,7 +75,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
)
|
||||
}
|
||||
if (!rootAvailable()) {
|
||||
if (ksuVersion != null && !rootAvailable()) {
|
||||
WarningCard(
|
||||
stringResource(id = R.string.grant_root_failed)
|
||||
)
|
||||
@@ -84,7 +90,6 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
DonateCard()
|
||||
LearnMoreCard()
|
||||
Spacer(Modifier)
|
||||
ConfirmDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,28 +104,28 @@ fun UpdateCard() {
|
||||
val newVersionCode = newVersion.first
|
||||
val newVersionUrl = newVersion.second
|
||||
val changelog = newVersion.third
|
||||
if (newVersionCode <= currentVersionCode) {
|
||||
return
|
||||
}
|
||||
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val dialogHost = LocalDialogHost.current
|
||||
val title = stringResource(id = R.string.module_changelog)
|
||||
val updateText = stringResource(id = R.string.module_update)
|
||||
val scope = rememberCoroutineScope()
|
||||
WarningCard(
|
||||
message = stringResource(id = R.string.new_version_available).format(newVersionCode),
|
||||
MaterialTheme.colorScheme.outlineVariant
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = newVersionCode >= currentVersionCode,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
scope.launch {
|
||||
if (changelog.isEmpty() || dialogHost.showConfirm(
|
||||
val updateDialog = rememberConfirmDialog(onConfirm = { uriHandler.openUri(newVersionUrl) })
|
||||
WarningCard(
|
||||
message = stringResource(id = R.string.new_version_available).format(newVersionCode),
|
||||
MaterialTheme.colorScheme.outlineVariant
|
||||
) {
|
||||
if (changelog.isNotEmpty()) {
|
||||
updateDialog.showConfirm(
|
||||
title = title,
|
||||
content = changelog,
|
||||
markdown = true,
|
||||
confirm = updateText,
|
||||
) == ConfirmResult.Confirmed
|
||||
) {
|
||||
uriHandler.openUri(newVersionUrl)
|
||||
confirm = updateText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,8 +142,17 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(onSettingsClick: () -> Unit) {
|
||||
private fun TopBar(kernelVersion: KernelVersion, onInstallClick: () -> Unit, onSettingsClick: () -> Unit) {
|
||||
TopAppBar(title = { Text(stringResource(R.string.app_name)) }, actions = {
|
||||
if (kernelVersion.isGKI()) {
|
||||
IconButton(onClick = onInstallClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Archive,
|
||||
contentDescription = stringResource(id = R.string.install)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = {
|
||||
showDropdown = true
|
||||
@@ -176,7 +190,7 @@ private fun TopBar(onSettingsClick: () -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?, onClickInstall: () -> Unit = {}) {
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
||||
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
|
||||
@@ -187,8 +201,8 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (kernelVersion.isGKI() && ksuVersion == null) {
|
||||
uriHandler.openUri("https://kernelsu.org/guide/installation.html")
|
||||
if (kernelVersion.isGKI()) {
|
||||
onClickInstall()
|
||||
}
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
|
||||
@@ -1,140 +1,269 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.KeyEventBlocker
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.util.installModule
|
||||
import me.weishu.kernelsu.ui.util.reboot
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
||||
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
||||
import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.DownloadListener
|
||||
import me.weishu.kernelsu.ui.util.download
|
||||
import me.weishu.kernelsu.ui.util.getLKMUrl
|
||||
import me.weishu.kernelsu.ui.util.isAbDevice
|
||||
import me.weishu.kernelsu.ui.util.rootAvailable
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/1/1.
|
||||
* @date 2024/3/12.
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
@Destination
|
||||
fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
|
||||
|
||||
var text by rememberSaveable { mutableStateOf("") }
|
||||
val logContent = StringBuilder()
|
||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
@Composable
|
||||
fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollState = rememberScrollState()
|
||||
val loadingDialog = rememberLoadingDialog()
|
||||
val context = LocalContext.current
|
||||
var installMethod by remember {
|
||||
mutableStateOf<InstallMethod?>(null)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (text.isNotEmpty()) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
installModule(uri, onFinish = { success ->
|
||||
if (success) {
|
||||
showFloatAction = true
|
||||
val onFileDownloaded = { uri: Uri ->
|
||||
|
||||
installMethod?.let {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
when (it) {
|
||||
InstallMethod.DirectInstall -> {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashBoot(
|
||||
null,
|
||||
uri,
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
InstallMethod.DirectInstallToInactiveSlot -> {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashBoot(
|
||||
null,
|
||||
uri,
|
||||
true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is InstallMethod.SelectFile -> {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashBoot(
|
||||
it.uri,
|
||||
uri,
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}, onStdout = {
|
||||
text += "$it\n"
|
||||
scope.launch {
|
||||
scrollState.animateScrollTo(scrollState.maxValue)
|
||||
}
|
||||
logContent.append(it).append("\n")
|
||||
}, onStderr = {
|
||||
logContent.append(it).append("\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
onBack = {
|
||||
navigator.popBackStack()
|
||||
},
|
||||
onSave = {
|
||||
scope.launch {
|
||||
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
|
||||
val date = format.format(Date())
|
||||
val file = File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
"KernelSU_install_log_${date}.log"
|
||||
)
|
||||
file.writeText(logContent.toString())
|
||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
||||
}
|
||||
Scaffold(topBar = {
|
||||
TopBar {
|
||||
navigator.popBackStack()
|
||||
}
|
||||
}) {
|
||||
Column(modifier = Modifier.padding(it)) {
|
||||
SelectInstallMethod { method ->
|
||||
installMethod = method
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
|
||||
DownloadListener(context = context) { uri ->
|
||||
onFileDownloaded(uri)
|
||||
loadingDialog.hide()
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (showFloatAction) {
|
||||
val reboot = stringResource(id = R.string.reboot)
|
||||
ExtendedFloatingActionButton(
|
||||
|
||||
val failedMessage = stringResource(id = R.string.failed_to_fetch_lkm_url)
|
||||
val downloadingMessage = stringResource(id = R.string.downloading)
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = installMethod != null,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
reboot()
|
||||
loadingDialog.showLoading()
|
||||
scope.launch(Dispatchers.IO) {
|
||||
getLKMUrl().onFailure { throwable ->
|
||||
loadingDialog.hide()
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
failedMessage.format(throwable.message),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}.onSuccess { result ->
|
||||
download(
|
||||
context = context,
|
||||
url = result.second,
|
||||
fileName = result.first,
|
||||
description = downloadingMessage.format(
|
||||
result.first
|
||||
),
|
||||
onDownloaded = { uri ->
|
||||
onFileDownloaded(uri)
|
||||
loadingDialog.hide()
|
||||
},
|
||||
onDownloading = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Refresh, reboot) },
|
||||
text = { Text(text = reboot) },
|
||||
}) {
|
||||
Text(
|
||||
stringResource(id = R.string.install_next),
|
||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class InstallMethod {
|
||||
data class SelectFile(val uri: Uri? = null, override val label: Int = R.string.select_file) :
|
||||
InstallMethod()
|
||||
|
||||
object DirectInstall : InstallMethod() {
|
||||
override val label: Int
|
||||
get() = R.string.direct_install
|
||||
}
|
||||
|
||||
object DirectInstallToInactiveSlot : InstallMethod() {
|
||||
override val label: Int
|
||||
get() = R.string.install_inactive_slot
|
||||
}
|
||||
|
||||
abstract val label: Int
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||
val rootAvailable = rootAvailable()
|
||||
val isAbDevice = isAbDevice()
|
||||
val radioOptions = mutableListOf<InstallMethod>(InstallMethod.SelectFile())
|
||||
if (rootAvailable) {
|
||||
radioOptions.add(InstallMethod.DirectInstall)
|
||||
|
||||
if (isAbDevice) {
|
||||
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
|
||||
}
|
||||
}
|
||||
|
||||
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
|
||||
val selectImageLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
it.data?.data?.let { uri ->
|
||||
val option = InstallMethod.SelectFile(uri)
|
||||
selectedOption = option
|
||||
onSelected(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val confirmDialog = rememberConfirmDialog(onConfirm = {
|
||||
selectedOption = InstallMethod.DirectInstallToInactiveSlot
|
||||
onSelected(InstallMethod.DirectInstallToInactiveSlot)
|
||||
}, onDismiss = null)
|
||||
val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)
|
||||
val dialogContent = stringResource(id = R.string.install_inactive_slot_warning)
|
||||
|
||||
val onClick = { option: InstallMethod ->
|
||||
|
||||
when (option) {
|
||||
is InstallMethod.SelectFile -> {
|
||||
selectImageLauncher.launch(
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
type = "application/octet-stream"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is InstallMethod.DirectInstall -> {
|
||||
selectedOption = option
|
||||
onSelected(option)
|
||||
}
|
||||
|
||||
is InstallMethod.DirectInstallToInactiveSlot -> {
|
||||
confirmDialog.showConfirm(dialogTitle, dialogContent)
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
KeyEventBlocker {
|
||||
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(1f)
|
||||
.padding(innerPadding)
|
||||
.verticalScroll(scrollState),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = text,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||
)
|
||||
}
|
||||
|
||||
Column {
|
||||
radioOptions.forEach { option ->
|
||||
Row(verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
onClick(option)
|
||||
}) {
|
||||
RadioButton(selected = option.javaClass == selectedOption?.javaClass, onClick = {
|
||||
onClick(option)
|
||||
})
|
||||
Text(text = stringResource(id = option.label))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
||||
private fun TopBar(onBack: () -> Unit = {}) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.install)) },
|
||||
navigationIcon = {
|
||||
@@ -142,19 +271,11 @@ private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
||||
onClick = onBack
|
||||
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onSave) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Save,
|
||||
contentDescription = "Localized description"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun InstallPreview() {
|
||||
// InstallScreen(DestinationsNavigator(), uri = Uri.EMPTY)
|
||||
@Preview
|
||||
fun SelectInstall_Preview() {
|
||||
// InstallScreen(DestinationsNavigator())
|
||||
}
|
||||
@@ -40,10 +40,10 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
||||
import me.weishu.kernelsu.ui.component.ConfirmResult
|
||||
import me.weishu.kernelsu.ui.component.LoadingDialog
|
||||
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
||||
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
||||
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
||||
import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination
|
||||
import me.weishu.kernelsu.ui.screen.destinations.WebScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.*
|
||||
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||
@@ -81,7 +81,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
val data = it.data ?: return@rememberLauncherForActivityResult
|
||||
val uri = data.data ?: return@rememberLauncherForActivityResult
|
||||
|
||||
navigator.navigate(InstallScreenDestination(uri))
|
||||
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
|
||||
|
||||
viewModel.markNeedRefresh()
|
||||
|
||||
@@ -101,10 +101,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}) { innerPadding ->
|
||||
|
||||
ConfirmDialog()
|
||||
|
||||
LoadingDialog()
|
||||
|
||||
when {
|
||||
hasMagisk -> {
|
||||
Box(
|
||||
@@ -127,7 +123,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
.fillMaxSize(),
|
||||
onInstallModule =
|
||||
{
|
||||
navigator.navigate(InstallScreenDestination(it))
|
||||
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it)))
|
||||
}, onClickModule = { id, name, hasWebUi ->
|
||||
if (hasWebUi) {
|
||||
navigator.navigate(WebScreenDestination(id, name))
|
||||
@@ -162,17 +158,19 @@ private fun ModuleList(
|
||||
val startDownloadingText = stringResource(R.string.module_start_downloading)
|
||||
val fetchChangeLogFailed = stringResource(R.string.module_changelog_failed)
|
||||
|
||||
val dialogHost = LocalDialogHost.current
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val loadingDialog = rememberLoadingDialog()
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
|
||||
suspend fun onModuleUpdate(
|
||||
module: ModuleViewModel.ModuleInfo,
|
||||
changelogUrl: String,
|
||||
downloadUrl: String,
|
||||
fileName: String
|
||||
) {
|
||||
val changelogResult = dialogHost.withLoading {
|
||||
val changelogResult = loadingDialog.withLoading {
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
OkHttpClient().newCall(
|
||||
@@ -201,7 +199,7 @@ private fun ModuleList(
|
||||
}
|
||||
|
||||
// changelog is not empty, show it and wait for confirm
|
||||
val confirmResult = dialogHost.showConfirm(
|
||||
val confirmResult = confirmDialog.awaitConfirm(
|
||||
changelogText,
|
||||
content = changelog,
|
||||
markdown = true,
|
||||
@@ -232,7 +230,7 @@ private fun ModuleList(
|
||||
}
|
||||
|
||||
suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) {
|
||||
val confirmResult = dialogHost.showConfirm(
|
||||
val confirmResult = confirmDialog.awaitConfirm(
|
||||
moduleStr,
|
||||
content = moduleUninstallConfirm.format(module.name),
|
||||
confirm = uninstall,
|
||||
@@ -242,7 +240,7 @@ private fun ModuleList(
|
||||
return
|
||||
}
|
||||
|
||||
val success = dialogHost.withLoading {
|
||||
val success = loadingDialog.withLoading {
|
||||
withContext(Dispatchers.IO) {
|
||||
uninstallModule(module.id)
|
||||
}
|
||||
@@ -327,7 +325,7 @@ private fun ModuleList(
|
||||
scope.launch { onModuleUninstall(module) }
|
||||
}, onCheckChanged = {
|
||||
scope.launch {
|
||||
val success = dialogHost.withLoading {
|
||||
val success = loadingDialog.withLoading {
|
||||
withContext(Dispatchers.IO) {
|
||||
toggleModule(module.id, !isChecked)
|
||||
}
|
||||
|
||||
@@ -4,15 +4,10 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material.icons.filled.ContactPage
|
||||
import androidx.compose.material.icons.filled.Fence
|
||||
import androidx.compose.material.icons.filled.RemoveModerator
|
||||
import androidx.compose.material.icons.filled.Update
|
||||
import androidx.compose.material.icons.filled.Upgrade
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -29,10 +24,10 @@ import me.weishu.kernelsu.BuildConfig
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.AboutDialog
|
||||
import me.weishu.kernelsu.ui.component.LoadingDialog
|
||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||
import me.weishu.kernelsu.ui.component.rememberCustomDialog
|
||||
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
||||
import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import me.weishu.kernelsu.ui.util.getBugreportFile
|
||||
|
||||
/**
|
||||
@@ -42,7 +37,6 @@ import me.weishu.kernelsu.ui.util.getBugreportFile
|
||||
@Destination
|
||||
@Composable
|
||||
fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(onBack = {
|
||||
@@ -50,16 +44,15 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
})
|
||||
}
|
||||
) { paddingValues ->
|
||||
LoadingDialog()
|
||||
|
||||
val showAboutDialog = remember { mutableStateOf(false) }
|
||||
AboutDialog(showAboutDialog)
|
||||
val aboutDialog = rememberCustomDialog {
|
||||
AboutDialog(it)
|
||||
}
|
||||
val loadingDialog = rememberLoadingDialog()
|
||||
|
||||
Column(modifier = Modifier.padding(paddingValues)) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val dialogHost = LocalDialogHost.current
|
||||
|
||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
||||
ListItem(
|
||||
@@ -101,6 +94,21 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
checkUpdate = it
|
||||
}
|
||||
|
||||
var enableWebDebugging by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("enable_web_debugging", false)
|
||||
)
|
||||
}
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.DeveloperMode,
|
||||
title = stringResource(id = R.string.enable_web_debugging),
|
||||
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
||||
checked = enableWebDebugging
|
||||
) {
|
||||
prefs.edit().putBoolean("enable_web_debugging", it).apply()
|
||||
enableWebDebugging = it
|
||||
}
|
||||
|
||||
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
@@ -112,7 +120,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
headlineContent = { Text(stringResource(id = R.string.send_log)) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
val bugreport = dialogHost.withLoading {
|
||||
val bugreport = loadingDialog.withLoading {
|
||||
withContext(Dispatchers.IO) {
|
||||
getBugreportFile(context)
|
||||
}
|
||||
@@ -150,7 +158,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
},
|
||||
headlineContent = { Text(about) },
|
||||
modifier = Modifier.clickable {
|
||||
showAboutDialog.value = true
|
||||
aboutDialog.show()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
||||
import me.weishu.kernelsu.ui.component.SearchAppBar
|
||||
import me.weishu.kernelsu.ui.screen.destinations.AppProfileScreenDestination
|
||||
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
||||
@@ -95,9 +94,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
|
||||
ConfirmDialog()
|
||||
|
||||
val refreshState = rememberPullRefreshState(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
onRefresh = { scope.launch { viewModel.fetchAppList() } },
|
||||
|
||||
@@ -2,6 +2,8 @@ package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
@@ -31,6 +33,8 @@ fun WebScreen(navigator: DestinationsNavigator, moduleId: String, moduleName: St
|
||||
val context = LocalContext.current
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
WebView.setWebContentsDebuggingEnabled(prefs.getBoolean("enable_web_debugging", false))
|
||||
onDispose {
|
||||
if (WebViewInterface.isHideSystemUI && context is Activity) {
|
||||
showSystemUI(context.window)
|
||||
|
||||
@@ -2,12 +2,7 @@ package me.weishu.kernelsu.ui.util
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import me.weishu.kernelsu.ui.component.DialogHostState
|
||||
|
||||
val LocalSnackbarHost = compositionLocalOf<SnackbarHostState> {
|
||||
error("CompositionLocal LocalSnackbarController not present")
|
||||
}
|
||||
|
||||
val LocalDialogHost = compositionLocalOf<DialogHostState> {
|
||||
error("CompositionLocal LocalDialogController not present")
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import android.net.Uri
|
||||
import android.os.Environment
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import me.weishu.kernelsu.getKMI
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -94,6 +95,38 @@ fun checkNewVersion(): Triple<Int, String, String> {
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
fun getLKMUrl(): Result<Pair<String, String>> {
|
||||
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest"
|
||||
|
||||
val kmi = getKMI() ?: return Result.failure(RuntimeException("Get KMI failed"))
|
||||
runCatching {
|
||||
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
||||
.use { response ->
|
||||
val body = response.body?.string() ?: return Result.failure(RuntimeException("request body failed"))
|
||||
if (!response.isSuccessful) {
|
||||
return Result.failure(RuntimeException("Request failed, code: ${response.code}, message: $body"))
|
||||
}
|
||||
val json = org.json.JSONObject(body)
|
||||
|
||||
val assets = json.getJSONArray("assets")
|
||||
for (i in 0 until assets.length()) {
|
||||
val asset = assets.getJSONObject(i)
|
||||
val name = asset.getString("name")
|
||||
if (!name.endsWith(".ko")) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (name.contains(kmi)) {
|
||||
return Result.success(Pair(name, asset.getString("browser_download_url")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
return Result.failure(it)
|
||||
}
|
||||
|
||||
return Result.failure(RuntimeException("Cannot find LKM for $kmi"))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.weishu.kernelsu.ui.util
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
@@ -138,6 +139,85 @@ fun installModule(
|
||||
}
|
||||
}
|
||||
|
||||
fun installBoot(
|
||||
bootUri: Uri?,
|
||||
lkmUri: Uri,
|
||||
ota: Boolean,
|
||||
onFinish: (Boolean) -> Unit,
|
||||
onStdout: (String) -> Unit,
|
||||
onStderr: (String) -> Unit
|
||||
): Boolean {
|
||||
val resolver = ksuApp.contentResolver
|
||||
|
||||
with(resolver.openInputStream(lkmUri)) {
|
||||
val lkmFile = File(ksuApp.cacheDir, "kernelsu.ko")
|
||||
lkmFile.outputStream().use { output ->
|
||||
this?.copyTo(output)
|
||||
}
|
||||
|
||||
if (!lkmFile.exists()) {
|
||||
onStdout("- kernelsu.ko not found")
|
||||
onFinish(false)
|
||||
return false
|
||||
}
|
||||
|
||||
val bootFile = bootUri?.let { uri ->
|
||||
with(resolver.openInputStream(uri)) {
|
||||
val bootFile = File(ksuApp.cacheDir, "boot.img")
|
||||
bootFile.outputStream().use { output ->
|
||||
this?.copyTo(output)
|
||||
}
|
||||
|
||||
bootFile
|
||||
}
|
||||
}
|
||||
|
||||
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
||||
var cmd = "boot-patch -m ${lkmFile.absolutePath} --magiskboot ${magiskboot.absolutePath}"
|
||||
|
||||
cmd += if (bootFile == null) {
|
||||
// no boot.img, use -f to force install
|
||||
" -f"
|
||||
} else {
|
||||
" -b ${bootFile.absolutePath}"
|
||||
}
|
||||
|
||||
if (ota) {
|
||||
cmd += " -u"
|
||||
}
|
||||
|
||||
// output dir
|
||||
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
cmd += " -o $downloadsDir"
|
||||
|
||||
val shell = createRootShell()
|
||||
|
||||
val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||
override fun onAddElement(s: String?) {
|
||||
onStdout(s ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
val stderrCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||
override fun onAddElement(s: String?) {
|
||||
onStderr(s ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
val result =
|
||||
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(stdoutCallback, stderrCallback)
|
||||
.exec()
|
||||
Log.i("KernelSU", "install boot $lkmUri result: ${result.isSuccess}")
|
||||
|
||||
lkmFile.delete()
|
||||
bootFile?.delete()
|
||||
|
||||
// if boot uri is empty, it is direct install, when success, we should show reboot button
|
||||
onFinish(bootUri == null && result.isSuccess)
|
||||
return result.isSuccess
|
||||
}
|
||||
}
|
||||
|
||||
fun reboot(reason: String = "") {
|
||||
val shell = getRootShell()
|
||||
if (reason == "recovery") {
|
||||
@@ -152,6 +232,11 @@ fun rootAvailable(): Boolean {
|
||||
return shell.isRoot
|
||||
}
|
||||
|
||||
fun isAbDevice(): Boolean {
|
||||
val shell = getRootShell()
|
||||
return ShellUtils.fastCmd(shell, "getprop ro.build.ab_update").trim().toBoolean()
|
||||
}
|
||||
|
||||
fun overlayFsAvailable(): Boolean {
|
||||
val shell = getRootShell()
|
||||
// check /proc/filesystems
|
||||
|
||||
@@ -27,7 +27,7 @@ class WebViewInterface(val context: Context, private val webView: WebView) {
|
||||
|
||||
@JavascriptInterface
|
||||
fun exec(cmd: String): String {
|
||||
val shell = createRootShell()
|
||||
val shell = createRootShell(true)
|
||||
return ShellUtils.fastCmd(shell, cmd)
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class WebViewInterface(val context: Context, private val webView: WebView) {
|
||||
processOptions(finalCommand, options)
|
||||
finalCommand.append(cmd)
|
||||
|
||||
val shell = createRootShell()
|
||||
val shell = createRootShell(true)
|
||||
val result = shell.newJob().add(finalCommand.toString()).to(ArrayList(), ArrayList()).exec()
|
||||
val stdout = result.out.joinToString(separator = "\n")
|
||||
val stderr = result.err.joinToString(separator = "\n")
|
||||
@@ -97,7 +97,7 @@ class WebViewInterface(val context: Context, private val webView: WebView) {
|
||||
finalCommand.append(command)
|
||||
}
|
||||
|
||||
val shell = createRootShell()
|
||||
val shell = createRootShell(true)
|
||||
|
||||
val emitData = fun(name: String, data: String) {
|
||||
val jsCode =
|
||||
|
||||
BIN
manager/app/src/main/jniLibs/arm64-v8a/libmagiskboot.so
Normal file
BIN
manager/app/src/main/jniLibs/arm64-v8a/libmagiskboot.so
Normal file
Binary file not shown.
@@ -106,4 +106,15 @@
|
||||
<string name="open">فتح</string>
|
||||
<string name="settings_check_update_summary">التحقق تلقائيًا من وجود تحديثات عند فتح التطبيق</string>
|
||||
<string name="settings_check_update">التحقق من التحديث</string>
|
||||
<string name="enable_web_debugging">تمكين تصحيح أخطاء WebView</string>
|
||||
<string name="enable_web_debugging_summary">يمكن استخدامه لتصحيح أخطاء WebUI، يرجى تمكينه فقط عند الحاجة.</string>
|
||||
<string name="install_next">التالي</string>
|
||||
<string name="failed_to_fetch_lkm_url">فشل جلب رابط LKM: %1$s</string>
|
||||
<string name="downloading">تحميل %1$s</string>
|
||||
<string name="select_file">اختيار ملف</string>
|
||||
<string name="direct_install">تثبيت مباشر (موصى به)</string>
|
||||
<string name="install_inactive_slot">التثبيت على فتحة غير نشطة (بعد OTA)</string>
|
||||
<string name="install_inactive_slot_warning">سيتم **إجبار** جهازك على التمهيد إلى الفتحة غير النشطة الحالية بعد إعادة التشغيل!
|
||||
\nاستخدم هذا الخيار فقط بعد انتهاء التحديث.
|
||||
\nأستمرار؟</string>
|
||||
</resources>
|
||||
@@ -102,4 +102,10 @@
|
||||
<string name="profile_umount_modules_summary">Selle valiku lubamine lubab KernelSU-l taastada selle rakenduse moodulite poolt mistahes muudetud faile.</string>
|
||||
<string name="app_profile_template_export_empty">Eksportimiseks kohalikku malli ei leitud!</string>
|
||||
<string name="settings_umount_modules_default_summary">Globaalne vaikeväärtus \"Lahtihaagitud moodulitele\" rakenduseprofiilides. Lubamisel eemaldab see kõik moodulite süsteemimuudatused rakendustele, millel ei ole profiili määratud.</string>
|
||||
<string name="enable_web_debugging_summary">Saab kasutada WebUI silumiseks, palun luba ainult vajadusel.</string>
|
||||
<string name="grant_root_failed">Juurkasutaja andmine ebaõnnestus!</string>
|
||||
<string name="settings_check_update">Kontrolli uuendusi</string>
|
||||
<string name="settings_check_update_summary">Rakenduse avamisel kontrolli automaatselt uuendusi</string>
|
||||
<string name="open">Ava</string>
|
||||
<string name="enable_web_debugging">Luba WebView silumine</string>
|
||||
</resources>
|
||||
@@ -51,7 +51,8 @@
|
||||
<string name="home_support_title">Soutenez-nous</string>
|
||||
<string name="home_click_to_learn_kernelsu">Découvrez comment installer KernelSU et utiliser les modules</string>
|
||||
<string name="home_support_content">KernelSU est, et restera toujours, gratuit et open source. Vous pouvez cependant nous témoigner de votre soutien en nous faisant un don.</string>
|
||||
<string name="about_source_code">Voir le code source sur %1$s<br/>Rejoindre notre canal %2$s</string>
|
||||
<string name="about_source_code">Voir le code source sur %1$s<br/>
|
||||
\nRejoindre notre canal %2$s</string>
|
||||
<string name="profile_template">Modèle</string>
|
||||
<string name="profile_default">Par défaut</string>
|
||||
<string name="profile_custom">Personnalisé</string>
|
||||
@@ -78,7 +79,7 @@
|
||||
<string name="force_stop_app">Forcer l\'arrêt</string>
|
||||
<string name="restart_app">Relancer l\'application</string>
|
||||
<string name="failed_to_update_sepolicy">Échec de la mise à jour des règles SELinux pour : %s</string>
|
||||
<string name="require_kernel_version">La version actuelle de KernelSU (%d) est trop ancienne pour que le gestionnaire fonctionne correctement. Veuillez passer à la version %d ou à une version supérieure !</string>
|
||||
<string name="require_kernel_version">La version actuelle de KernelSU (%d) est trop ancienne pour que le gestionnaire fonctionne correctement. Veuillez passer à la version %d ou à une version supérieure !</string>
|
||||
<string name="app_profile_template_import_success">Importation réussie</string>
|
||||
<string name="app_profile_export_to_clipboard">Exporter vers le presse-papiers</string>
|
||||
<string name="app_profile_template_export_empty">Impossible de trouver un modèle local à exporter !</string>
|
||||
@@ -90,7 +91,7 @@
|
||||
<string name="app_profile_template_id_invalid">id de modèle invalide</string>
|
||||
<string name="app_profile_template_sync">Synchroniser les modèles en ligne</string>
|
||||
<string name="app_profile_template_create">Créer un modèle</string>
|
||||
<string name="app_profile_template_readonly">en lecture seule</string>
|
||||
<string name="app_profile_template_readonly">lecture seule</string>
|
||||
<string name="app_profile_import_export">Importer/exporter</string>
|
||||
<string name="app_profile_template_save_failed">Échec de l\'enregistrement du modèle</string>
|
||||
<string name="app_profile_template_edit">Modifier le modèle</string>
|
||||
@@ -104,4 +105,17 @@
|
||||
<string name="app_profile_template_view">Voir le modèle</string>
|
||||
<string name="settings_check_update_summary">Vérifier automatiquement les mises à jour à l\'ouverture de l\'application</string>
|
||||
<string name="settings_check_update">Vérifier les mises à jour</string>
|
||||
<string name="enable_web_debugging">Activer le débogage de WebView</string>
|
||||
<string name="enable_web_debugging_summary">Peut être utilisé pour déboguer WebUI, n\'activez cette option que si nécessaire.</string>
|
||||
<string name="grant_root_failed">Échec de l\'octroi des privilèges root !</string>
|
||||
<string name="open">Ouvert</string>
|
||||
<string name="direct_install">Installation directe (recommandé)</string>
|
||||
<string name="select_file">Sélectionner un fichier</string>
|
||||
<string name="install_inactive_slot">Installer dans l\'emplacement inactif (après OTA)</string>
|
||||
<string name="install_inactive_slot_warning">Votre appareil sera **FORCÉ** à démarrer sur l\'emplacement inactif actuel après un redémarrage !
|
||||
\nN\'utilisez cette option qu\'une fois la mise à jour OTA terminée.
|
||||
\nContinuer ?</string>
|
||||
<string name="install_next">Suivant</string>
|
||||
<string name="downloading">Téléchargement de %1$s</string>
|
||||
<string name="failed_to_fetch_lkm_url">Échec de la récupération de l\'URL du LKM : %1$s</string>
|
||||
</resources>
|
||||
@@ -105,6 +105,8 @@
|
||||
<string name="app_profile_template_view">テンプレートを表示</string>
|
||||
<string name="settings_check_update">アップデートを確認</string>
|
||||
<string name="settings_check_update_summary">アプリを開いたときにアップデートを自動的に確認する</string>
|
||||
<string name="grant_root_failed">root の付与に失敗しました!</string>
|
||||
<string name="open">開ける</string>
|
||||
<string name="grant_root_failed">root の付与に失敗しました!</string>
|
||||
<string name="open">開く</string>
|
||||
<string name="enable_web_debugging">WebView デバッグを有効にする</string>
|
||||
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合にのみ有効にしてください。</string>
|
||||
</resources>
|
||||
@@ -102,4 +102,10 @@
|
||||
<string name="module_changelog_failed">Ophalen van wijzigingslogboek mislukt: %s</string>
|
||||
<string name="app_profile_export_to_clipboard">Exporteren naar klembord</string>
|
||||
<string name="settings_check_update">Controleer update</string>
|
||||
<string name="enable_web_debugging">Schakel WebView-foutopsporing</string>
|
||||
<string name="enable_web_debugging_summary">Kan worden gebruikt om WebUI te debuggen. Schakel dit alleen in als dat nodig is.</string>
|
||||
<string name="app_profile_template_export_empty">Kan geen lokale sjabloon vinden om te exporteren!</string>
|
||||
<string name="app_profile_template_import_success">Succesvol geïmporteerd</string>
|
||||
<string name="open">Open</string>
|
||||
<string name="settings_check_update_summary">Controleer automatisch op updates bij het openen van de app</string>
|
||||
</resources>
|
||||
@@ -18,8 +18,8 @@
|
||||
<string name="selinux_status_permissive">Permissivo</string>
|
||||
<string name="selinux_status_unknown">Desconhecido</string>
|
||||
<string name="superuser">SuperUsuário</string>
|
||||
<string name="module_failed_to_enable">Falha ao ativar o módulo: %s</string>
|
||||
<string name="module_failed_to_disable">Falha ao desativar o módulo: %s</string>
|
||||
<string name="module_failed_to_enable">Falha ao ativar o módulo %s</string>
|
||||
<string name="module_failed_to_disable">Falha ao desativar o módulo %s</string>
|
||||
<string name="module_empty">Nenhum módulo instalado</string>
|
||||
<string name="module">Módulo</string>
|
||||
<string name="uninstall">Desinstalar</string>
|
||||
@@ -33,24 +33,24 @@
|
||||
<string name="reboot_download">Reiniciar em modo Download</string>
|
||||
<string name="reboot_edl">Reiniciar em modo EDL</string>
|
||||
<string name="about">Sobre</string>
|
||||
<string name="module_uninstall_confirm">Tem certeza de que deseja desinstalar o módulo %s?</string>
|
||||
<string name="module_uninstall_success">%s foi desinstalado</string>
|
||||
<string name="module_uninstall_failed">Falha ao desinstalar: %s</string>
|
||||
<string name="module_uninstall_confirm">Tem certeza que deseja desinstalar o módulo %s?</string>
|
||||
<string name="module_uninstall_success">%s desinstalado</string>
|
||||
<string name="module_uninstall_failed">Falha ao desinstalar %s</string>
|
||||
<string name="module_version">Versão</string>
|
||||
<string name="module_author">Autor</string>
|
||||
<string name="module_overlay_fs_not_available">OverlayFS não está disponível. O módulo não pode funcionar!</string>
|
||||
<string name="module_overlay_fs_not_available">Os módulos estão indisponíveis porque OverlayFS está desabilitado pelo kernel!</string>
|
||||
<string name="refresh">Atualizar</string>
|
||||
<string name="show_system_apps">Mostrar apps do sistema</string>
|
||||
<string name="hide_system_apps">Ocultar apps do sistema</string>
|
||||
<string name="send_log">Reportar registro</string>
|
||||
<string name="safe_mode">Modo de segurança</string>
|
||||
<string name="reboot_to_apply">Reinicie para entrar em vigor</string>
|
||||
<string name="module_magisk_conflict">Os módulos estão desativados porque estão em conflito com os do Magisk!</string>
|
||||
<string name="home_learn_kernelsu">Leia mais sobre o KernelSU</string>
|
||||
<string name="module_magisk_conflict">Os módulos estão indisponíveis devido a um conflito com Magisk!</string>
|
||||
<string name="home_learn_kernelsu">Saiba mais sobre o KernelSU</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/pt_BR/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">Aprenda como instalar o KernelSU e usar módulos</string>
|
||||
<string name="home_click_to_learn_kernelsu">Saiba como instalar o KernelSU e usar os módulos</string>
|
||||
<string name="home_support_title">Apoie-nos</string>
|
||||
<string name="home_support_content">KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode agradecer enviando uma pequena doação.</string>
|
||||
<string name="home_support_content">KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação.</string>
|
||||
<string name="about_source_code"><![CDATA[Veja o código-fonte no %1$s<br/>Junte-se ao nosso canal do %2$s]]></string>
|
||||
<string name="profile" translatable="false">Perfil do Aplicativo</string>
|
||||
<string name="profile_default">Padrão</string>
|
||||
@@ -72,9 +72,9 @@
|
||||
<string name="profile_selinux_domain">Domínio</string>
|
||||
<string name="profile_selinux_rules">Regras</string>
|
||||
<string name="module_update">Atualizar</string>
|
||||
<string name="module_downloading">Baixando módulo: %s</string>
|
||||
<string name="module_start_downloading">Iniciar download: %s</string>
|
||||
<string name="new_version_available">Nova versão: %s está disponível, clique para atualizar.</string>
|
||||
<string name="module_downloading">Baixando módulo %s</string>
|
||||
<string name="module_start_downloading">Iniciando download: %s</string>
|
||||
<string name="new_version_available">Nova versão %s está disponível, clique para atualizar.</string>
|
||||
<string name="launch_app">Iniciar</string>
|
||||
<string name="force_stop_app">Forçar parada</string>
|
||||
<string name="restart_app">Reiniciar</string>
|
||||
@@ -103,8 +103,19 @@
|
||||
<string name="app_profile_template_delete">Excluir</string>
|
||||
<string name="app_profile_template_import_empty">A área de transferência está vazia!</string>
|
||||
<string name="app_profile_template_view">Ver modelo</string>
|
||||
<string name="settings_check_update">Verificar atualização</string>
|
||||
<string name="settings_check_update">Verificar por atualização</string>
|
||||
<string name="settings_check_update_summary">Verifique automaticamente se há atualizações ao abrir o app</string>
|
||||
<string name="grant_root_failed">Falha ao conceder acesso root!</string>
|
||||
<string name="open">Abrir</string>
|
||||
<string name="enable_web_debugging">Ativar depuração do WebView</string>
|
||||
<string name="enable_web_debugging_summary">Pode ser usado para depurar o WebUI. Por favor, ative somente quando necessário.</string>
|
||||
<string name="select_file">Selecione um arquivo</string>
|
||||
<string name="direct_install">Instalação direta (recomendada)</string>
|
||||
<string name="install_inactive_slot">Instalar no slot inativo (após o OTA)</string>
|
||||
<string name="install_inactive_slot_warning">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="downloading">Baixando %1$s</string>
|
||||
<string name="install_next">Próximo</string>
|
||||
<string name="failed_to_fetch_lkm_url">Falha ao buscar a URL do LKM: %1$s</string>
|
||||
</resources>
|
||||
@@ -104,4 +104,8 @@
|
||||
<string name="app_profile_template_view">Vizualizare șablon</string>
|
||||
<string name="settings_check_update">Verifică actualizarea</string>
|
||||
<string name="settings_check_update_summary">Se verifică automat actualizările când deschizi aplicația</string>
|
||||
<string name="enable_web_debugging">Activează depanarea WebView</string>
|
||||
<string name="enable_web_debugging_summary">Poate fi folosit pentru a depana WebUI, activează numai când este necesar.</string>
|
||||
<string name="grant_root_failed">Nu s-a acordat acces root!</string>
|
||||
<string name="open">Deschide</string>
|
||||
</resources>
|
||||
@@ -18,7 +18,7 @@
|
||||
<string name="selinux_status_enforcing">Принудительный</string>
|
||||
<string name="selinux_status_permissive">Разрешающий</string>
|
||||
<string name="selinux_status_unknown">Неизвестно</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="superuser">SU пользователь</string>
|
||||
<!--Don't translate this string!-->
|
||||
<string name="module_failed_to_enable">Не удалось включить модуль: %s</string>
|
||||
<string name="module_failed_to_disable">Не удалось отключить модуль: %s</string>
|
||||
@@ -106,8 +106,19 @@
|
||||
<string name="app_profile_template_delete">Удалить</string>
|
||||
<string name="app_profile_template_import_empty">Буфер обмена пуст!</string>
|
||||
<string name="app_profile_template_view">Просмотр шаблона</string>
|
||||
<string name="settings_check_update">Проверка обновления</string>
|
||||
<string name="settings_check_update">Проверка обновлений</string>
|
||||
<string name="settings_check_update_summary">Автоматическая проверка обновлений при открытии приложения</string>
|
||||
<string name="grant_root_failed">Не удалось выдать root!</string>
|
||||
<string name="open">Открыть</string>
|
||||
<string name="enable_web_debugging">Включить отладку WebView</string>
|
||||
<string name="enable_web_debugging_summary">Используется для отладки веб-интерфейса. Пожалуйста, включайте только при необходимости.</string>
|
||||
<string name="downloading">Загрузка %1$s</string>
|
||||
<string name="failed_to_fetch_lkm_url">Не удалось получить LKM по адресу: %1$s</string>
|
||||
<string name="direct_install">Прямая установка (Рекомендуется)</string>
|
||||
<string name="install_inactive_slot">Установка в неактивный слот (После OTA)</string>
|
||||
<string name="install_next">Далее</string>
|
||||
<string name="select_file">Выбрать файл</string>
|
||||
<string name="install_inactive_slot_warning">Ваше устройство будет **ПРИНУДИТЕЛЬНО** загружено в текущий неактивный слот после перезагрузки!
|
||||
\n Используйте эту опцию только после завершения OTA.
|
||||
\n Продолжать?</string>
|
||||
</resources>
|
||||
@@ -2,8 +2,8 @@
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">KernelSU</string>
|
||||
<string name="home">Ana Sayfa</string>
|
||||
<string name="home_not_installed">Yüklenmedi</string>
|
||||
<string name="home_click_to_install">Yüklemek için tıklayın</string>
|
||||
<string name="home_not_installed">Kurulmadı</string>
|
||||
<string name="home_click_to_install">Kurmak için tıklayın</string>
|
||||
<string name="home_working">Çalışıyor</string>
|
||||
<string name="home_working_version">Sürüm: %d</string>
|
||||
<string name="home_superuser_count">Süper kullanıcılar: %d</string>
|
||||
@@ -21,11 +21,11 @@
|
||||
<string name="superuser">Süper kullanıcı</string>
|
||||
<string name="module_failed_to_enable">Modül etkinleştirilemedi: %s</string>
|
||||
<string name="module_failed_to_disable">Modül devre dışı bırakılamadı: %s</string>
|
||||
<string name="module_empty">Yüklü modül yok</string>
|
||||
<string name="module_empty">Kurulu modül yok</string>
|
||||
<string name="module">Modül</string>
|
||||
<string name="uninstall">Kaldır</string>
|
||||
<string name="module_install">Yükle</string>
|
||||
<string name="install">Yükle</string>
|
||||
<string name="module_install">Kur</string>
|
||||
<string name="install">Kur</string>
|
||||
<string name="reboot">Cihazı yeniden başlat</string>
|
||||
<string name="settings">Ayarlar</string>
|
||||
<string name="reboot_userspace">Hızlı yeniden başlat</string>
|
||||
@@ -52,7 +52,7 @@
|
||||
<string name="home_click_to_learn_kernelsu">KernelSU\'nun nasıl kurulacağını ve modüllerin nasıl kullanılacağını öğrenin</string>
|
||||
<string name="home_support_title">Bizi destekleyin</string>
|
||||
<string name="home_support_content">KernelSU ücretsiz ve açık kaynaklı bir yazılımdır ve her zaman öyle kalacaktır. Ancak bağış yaparak bize destek olduğunuzu gösterebilirsiniz.</string>
|
||||
<string name="about_source_code">Kaynak kodunu %1$s\'tan görüntüleyin<br/>%2$s kanalımıza katılın</string>
|
||||
<string name="about_source_code">%1$s adresinde kaynak kodunu görüntüleyin<br/>%2$s kanalımıza katılın</string>
|
||||
<string name="profile" translatable="false">Uygulama profili</string>
|
||||
<string name="profile_default">Varsayılan</string>
|
||||
<string name="profile_template">Şablon</string>
|
||||
@@ -108,4 +108,15 @@
|
||||
<string name="settings_check_update_summary">Uygulamayı açarken güncellemeleri otomatik denetle</string>
|
||||
<string name="grant_root_failed">Root izni verilemedi!</string>
|
||||
<string name="open">Aç</string>
|
||||
<string name="enable_web_debugging">Web Görünümü Hata Ayıklamasını Etkinleştir</string>
|
||||
<string name="enable_web_debugging_summary">Web kullanıcı arayüzünde hata ayıklamak için kullanılabilir, lütfen yalnızca gerektiğinde etkinleştirin.</string>
|
||||
<string name="direct_install">Doğrudan Kur (Tavsiye Edilen)</string>
|
||||
<string name="select_file">Bir Dosya Seçin</string>
|
||||
<string name="install_inactive_slot">Etkin Olmayan Yuvaya Kur (OTA\'dan Sonra)</string>
|
||||
<string name="install_inactive_slot_warning">Aygıtınız yeniden başlatıldıktan sonra geçerli etkin olmayan yuvaya **ZORLA** önyükleme yapılacaktır!
|
||||
\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın.
|
||||
\nDevam edilsin mi?</string>
|
||||
<string name="install_next">Sonraki</string>
|
||||
<string name="failed_to_fetch_lkm_url">LKM URL\'si getirilemedi: %1$s</string>
|
||||
<string name="downloading">%1$s indiriliyor</string>
|
||||
</resources>
|
||||
@@ -21,10 +21,10 @@
|
||||
<string name="about_source_code"><![CDATA[Xem mã nguồn tại %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
|
||||
<string name="module_magisk_conflict">Các mô-đun bị vô hiệu hóa vì chúng xung đột với Magisk!</string>
|
||||
<string name="module_uninstall_confirm">Bạn có muốn gỡ cài đặt mô-đun %s không\?</string>
|
||||
<string name="send_log">báo cáo nhật ký</string>
|
||||
<string name="send_log">Nhật ký báo cáo</string>
|
||||
<string name="home">Trang chủ</string>
|
||||
<string name="home_not_installed">Chưa cài đặt</string>
|
||||
<string name="home_click_to_install">Nhấn để cài dặt</string>
|
||||
<string name="home_click_to_install">Nhấn để cài đặt</string>
|
||||
<string name="home_working">Đang hoạt động</string>
|
||||
<string name="home_working_version">Phiên bản: %d</string>
|
||||
<string name="home_unsupported">Không được hỗ trợ</string>
|
||||
@@ -40,19 +40,19 @@
|
||||
<string name="superuser">SuperUser</string>
|
||||
<string name="module_failed_to_enable">Không thể kích hoạt mô-đun: %s</string>
|
||||
<string name="module_failed_to_disable">Không thể vô hiệu hóa mô-đun: %s</string>
|
||||
<string name="module_empty">Chưa có mô-đun nào được cài đặt</string>
|
||||
<string name="module_empty">Chưa cài đặt mô-đun nào</string>
|
||||
<string name="module">Mô-đun</string>
|
||||
<string name="uninstall">Gỡ cài đặt</string>
|
||||
<string name="module_install">Cài đặt</string>
|
||||
<string name="install">Cài đặt</string>
|
||||
<string name="reboot">Khởi động lại</string>
|
||||
<string name="settings">Cài đặt</string>
|
||||
<string name="settings">Thiết đặt</string>
|
||||
<string name="reboot_userspace">Khởi động mềm</string>
|
||||
<string name="reboot_recovery">Khởi động lại vào Recovery</string>
|
||||
<string name="reboot_bootloader">Khởi động lại vào Bootloader</string>
|
||||
<string name="reboot_download">Khởi động lại vào Download Mode</string>
|
||||
<string name="reboot_edl">Khởi động lại vào EDL</string>
|
||||
<string name="about">Về ứng dụng</string>
|
||||
<string name="about">Giới thiệu</string>
|
||||
<string name="module_uninstall_success">%s được gỡ cài đặt</string>
|
||||
<string name="module_uninstall_failed">Lỗi khi gỡ cài đặt: %s</string>
|
||||
<string name="module_version">Phiên bản</string>
|
||||
@@ -68,16 +68,16 @@
|
||||
<string name="home_module_count">Số mô-đun: %d</string>
|
||||
<string name="profile_selinux_domain">Phạm vi</string>
|
||||
<string name="profile_selinux_rules">Quy định</string>
|
||||
<string name="launch_app">Mở</string>
|
||||
<string name="launch_app">Khởi chạy</string>
|
||||
<string name="restart_app">Khởi động lại</string>
|
||||
<string name="profile_namespace">Danh sách mount</string>
|
||||
<string name="profile_namespace">Gắn namespace</string>
|
||||
<string name="profile_capabilities">Quyền</string>
|
||||
<string name="failed_to_update_sepolicy">Không thể cập nhật quy định SELinux cho: %s</string>
|
||||
<string name="force_stop_app">Buộc dừng</string>
|
||||
<string name="profile_namespace_inherited">Thừa hưởng</string>
|
||||
<string name="profile_namespace_global">Chung</string>
|
||||
<string name="profile_namespace_individual">Riêng</string>
|
||||
<string name="profile_selinux_context">SELinux context</string>
|
||||
<string name="profile_selinux_context">Bối cảnh SELinux</string>
|
||||
<string name="profile_umount_modules">Ngắt mô-đun</string>
|
||||
<string name="require_kernel_version">KernelSU phiên bản %d quá thấp để trình quản lý hoạt động, hãy cập nhật lên %d hoặc mới hơn!</string>
|
||||
<string name="app_profile_template_import_success">Đã nhập thành công</string>
|
||||
@@ -86,7 +86,7 @@
|
||||
<string name="app_profile_template_id_exist">id bản mẫu đã tồn tại!</string>
|
||||
<string name="module_changelog">Nhật ký thay đổi</string>
|
||||
<string name="app_profile_import_from_clipboard">Nhập từ khay nhớ tạm</string>
|
||||
<string name="module_changelog_failed">Tìm nạp nhật ký thay đổi không thành công: %s</string>
|
||||
<string name="module_changelog_failed">Không nạp được nhật ký thay đổi: %s</string>
|
||||
<string name="app_profile_template_name">Tên</string>
|
||||
<string name="app_profile_template_id_invalid">Id mẫu không hợp lệ</string>
|
||||
<string name="app_profile_template_sync">Đồng bộ hóa các mẫu trực tuyến</string>
|
||||
@@ -99,8 +99,14 @@
|
||||
<string name="app_profile_template_save">Lưu</string>
|
||||
<string name="settings_profile_template_summary">Quản lý mẫu Hồ sơ Ứng dụng cục bộ và trực tuyến</string>
|
||||
<string name="app_profile_template_delete">Xóa</string>
|
||||
<string name="app_profile_template_import_empty">Bảng tạm trống!</string>
|
||||
<string name="app_profile_template_import_empty">Clipboard trống!</string>
|
||||
<string name="app_profile_template_view">Xem Bản Mẫu</string>
|
||||
<string name="app_profile_template_readonly">chỉ đọc</string>
|
||||
<string name="app_profile_template_id">id</string>
|
||||
<string name="enable_web_debugging">Bật gỡ lỗi WebView</string>
|
||||
<string name="enable_web_debugging_summary">Có thể được sử dụng để gỡ lỗi WebUI, vui lòng chỉ bật khi cần.</string>
|
||||
<string name="grant_root_failed">Không cấp được quyền root!</string>
|
||||
<string name="settings_check_update">Kiểm tra cập nhật</string>
|
||||
<string name="settings_check_update_summary">Tự động kiểm tra cập nhật khi mở ứng dụng</string>
|
||||
<string name="open">Mở</string>
|
||||
</resources>
|
||||
@@ -106,4 +106,13 @@
|
||||
<string name="settings_check_update_summary">在应用启动后自动检查是否有最新版</string>
|
||||
<string name="grant_root_failed">获取 root 失败!</string>
|
||||
<string name="open">打开</string>
|
||||
<string name="enable_web_debugging">启用 WebView 调试</string>
|
||||
<string name="enable_web_debugging_summary">可用于调试 WebUI ,请仅在需要时启用。</string>
|
||||
<string name="direct_install">直接安装(推荐)</string>
|
||||
<string name="select_file">选择一个文件</string>
|
||||
<string name="install_inactive_slot">安装到未使用的槽位(OTA 后)</string>
|
||||
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认?</string>
|
||||
<string name="install_next">下一步</string>
|
||||
<string name="failed_to_fetch_lkm_url">获取 LKM 链接失败:%1$s</string>
|
||||
<string name="downloading">正在下载:%1$s</string>
|
||||
</resources>
|
||||
@@ -52,27 +52,27 @@
|
||||
<string name="home_support_title">支援開發</string>
|
||||
<string name="home_support_content">KernelSU 將保持免費和開源,您可以考慮向開發人員贊助以表示支持。</string>
|
||||
<string name="about_source_code"><![CDATA[在 %1$s 中檢視原始碼<br/>加入我們的 %2$s 頻道]]></string>
|
||||
<string name="profile_umount_modules">解除安裝模組</string>
|
||||
<string name="profile_umount_modules">卸載模組</string>
|
||||
<string name="failed_to_update_app_profile">無法更新 %s 應用程式設定檔</string>
|
||||
<string name="require_kernel_version">目前安裝的 KernelSU 版本 %d 過低,管理器無法正常工作,請升級核心 KernelSU 版本至 %d 或以上!</string>
|
||||
<string name="settings_umount_modules_default">預設解除安裝模組</string>
|
||||
<string name="settings_umount_modules_default_summary">應用程式設定檔中「解除安裝模組」的全域預設值,如果啟用,將會為沒有設定檔的應用程式移除所有模組針對系統的修改。</string>
|
||||
<string name="settings_umount_modules_default">預設卸載模組</string>
|
||||
<string name="settings_umount_modules_default_summary">應用程式設定檔中「卸載模組」的全域預設值,如果啟用,將會為沒有設定檔的應用程式移除所有模組針對系統的修改。</string>
|
||||
<string name="profile_umount_modules_summary">啟用後將允許 KernelSU 為本應用程式還原被模組修改過的檔案。</string>
|
||||
<string name="profile_default">預設</string>
|
||||
<string name="profile_custom">自訂</string>
|
||||
<string name="profile_capabilities">功能</string>
|
||||
<string name="profile_capabilities">權限</string>
|
||||
<string name="profile_selinux_rules">規則</string>
|
||||
<string name="module_downloading">正在下載模組:%s</string>
|
||||
<string name="restart_app">重新啟動</string>
|
||||
<string name="profile_template">範本</string>
|
||||
<string name="profile_template">模板</string>
|
||||
<string name="profile_name">設定檔名稱</string>
|
||||
<string name="profile_namespace">掛載命名空間</string>
|
||||
<string name="profile_namespace_inherited">繼承</string>
|
||||
<string name="profile_namespace_global">全域</string>
|
||||
<string name="profile_namespace_individual">個人</string>
|
||||
<string name="profile_namespace_individual">私人</string>
|
||||
<string name="profile_groups">群組</string>
|
||||
<string name="profile_selinux_context">SELinux 環境</string>
|
||||
<string name="profile_selinux_domain">網域</string>
|
||||
<string name="profile_selinux_context">SELinux context</string>
|
||||
<string name="profile_selinux_domain">域</string>
|
||||
<string name="module_update">更新</string>
|
||||
<string name="module_start_downloading">開始下載:%s</string>
|
||||
<string name="new_version_available">發現新版本:%s 已可供使用,按一下即可升級</string>
|
||||
@@ -86,15 +86,15 @@
|
||||
<string name="app_profile_template_id">模板 ID</string>
|
||||
<string name="settings_profile_template">App Profile 模板</string>
|
||||
<string name="settings_profile_template_summary">管理本地和線上的 App Profile 模板</string>
|
||||
<string name="app_profile_template_import_success">成功匯出</string>
|
||||
<string name="app_profile_template_import_success">成功匯入</string>
|
||||
<string name="app_profile_export_to_clipboard">匯出至剪貼簿</string>
|
||||
<string name="app_profile_template_export_empty">本地沒有模板可匯出!</string>
|
||||
<string name="app_profile_template_export_empty">沒有本地模板可匯出!</string>
|
||||
<string name="app_profile_template_id_exist">模板 ID 已存在!</string>
|
||||
<string name="app_profile_import_from_clipboard">從剪貼簿匯入</string>
|
||||
<string name="module_changelog_failed">獲取更新日誌失敗:%s</string>
|
||||
<string name="app_profile_template_name">名稱</string>
|
||||
<string name="app_profile_template_sync">與線上規則同步</string>
|
||||
<string name="app_profile_template_readonly">只讀</string>
|
||||
<string name="app_profile_template_readonly">唯讀</string>
|
||||
<string name="app_profile_import_export">匯出 / 匯入</string>
|
||||
<string name="app_profile_template_save_failed">模板儲存失敗</string>
|
||||
<string name="app_profile_template_description">描述</string>
|
||||
@@ -102,4 +102,19 @@
|
||||
<string name="app_profile_template_delete">刪除</string>
|
||||
<string name="app_profile_template_import_empty">剪貼簿沒有內容!</string>
|
||||
<string name="app_profile_template_view">檢查模板</string>
|
||||
<string name="enable_web_debugging_summary">可用於偵錯 WebUI,請僅在需要時啟用。</string>
|
||||
<string name="enable_web_debugging">啟用 WebView 偵錯</string>
|
||||
<string name="grant_root_failed">取得root失敗!</string>
|
||||
<string name="open">開啟</string>
|
||||
<string name="settings_check_update">檢查更新</string>
|
||||
<string name="settings_check_update_summary">在打開App時自動檢查更新</string>
|
||||
<string name="select_file">選擇一個檔案</string>
|
||||
<string name="install_inactive_slot">安裝到非使用中的槽位(在 OTA 更新後)</string>
|
||||
<string name="install_inactive_slot_warning">您的裝置將在下次重新啟動後強制切換到非使用中的槽位!
|
||||
\n這個選項僅在 OTA 更新完畢後使用。
|
||||
\n請問是否繼續?</string>
|
||||
<string name="direct_install">直接安裝(建議)</string>
|
||||
<string name="install_next">下一步</string>
|
||||
<string name="failed_to_fetch_lkm_url">取得 LKM 下載鏈接失敗:%1$s</string>
|
||||
<string name="downloading">正在下載:%1$s</string>
|
||||
</resources>
|
||||
@@ -108,4 +108,13 @@
|
||||
<string name="settings_check_update_summary">Automatically check for updates when opening the app</string>
|
||||
<string name="grant_root_failed">Failed to grant root!</string>
|
||||
<string name="open">Open</string>
|
||||
<string name="enable_web_debugging">Enable WebView Debugging</string>
|
||||
<string name="enable_web_debugging_summary">Can be used to debug WebUI, please enable only when needed.</string>
|
||||
<string name="direct_install">Direct Install (Recommended)</string>
|
||||
<string name="select_file">Select a File</string>
|
||||
<string name="install_inactive_slot">Install to Inactive Slot (After OTA)</string>
|
||||
<string name="install_inactive_slot_warning">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="install_next">Next</string>
|
||||
<string name="failed_to_fetch_lkm_url">Failed to fetch LKM url: %1$s</string>
|
||||
<string name="downloading">Downloading %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "8.2.2"
|
||||
agp = "8.3.0"
|
||||
kotlin = "1.8.10"
|
||||
ksp = "1.8.10-1.0.9"
|
||||
compose-bom = "2023.06.01"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
184
manager/gradlew.bat
vendored
184
manager/gradlew.bat
vendored
@@ -1,92 +1,92 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
7
userspace/ksud/Cargo.lock
generated
7
userspace/ksud/Cargo.lock
generated
@@ -537,12 +537,6 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
@@ -806,7 +800,6 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"env_logger",
|
||||
"extattr",
|
||||
"fs_extra",
|
||||
"getopts",
|
||||
"hole-punch",
|
||||
"humansize",
|
||||
|
||||
@@ -37,7 +37,6 @@ sha256 = "1"
|
||||
tempdir = "0.3"
|
||||
chrono = "0.4"
|
||||
hole-punch = { git = "https://github.com/tiann/hole-punch" }
|
||||
fs_extra = "1.3"
|
||||
|
||||
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
|
||||
rustix = { git = "https://github.com/Kernel-SU/rustix.git", branch = "main", features = ["all-apis"] }
|
||||
|
||||
BIN
userspace/ksud/bin/aarch64/bootctl
Normal file
BIN
userspace/ksud/bin/aarch64/bootctl
Normal file
Binary file not shown.
BIN
userspace/ksud/bin/aarch64/ksuinit
Executable file
BIN
userspace/ksud/bin/aarch64/ksuinit
Executable file
Binary file not shown.
BIN
userspace/ksud/bin/x86_64/ksuinit
Executable file
BIN
userspace/ksud/bin/x86_64/ksuinit
Executable file
Binary file not shown.
@@ -1,29 +1,38 @@
|
||||
use anyhow::Result;
|
||||
use const_format::concatcp;
|
||||
use rust_embed::RustEmbed;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{defs::BINARY_DIR, utils};
|
||||
|
||||
pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop");
|
||||
pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox");
|
||||
pub const BOOTCTL_PATH: &str = concatcp!(BINARY_DIR, "bootctl");
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "bin/aarch64"]
|
||||
struct Asset;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(target_arch = "x86_64", target_os = "android"))]
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "bin/x86_64"]
|
||||
struct Asset;
|
||||
|
||||
// IF NOT x86_64 ANDROID, ie. macos, linux, windows, always use aarch64
|
||||
#[cfg(not(all(target_arch = "x86_64", target_os = "android")))]
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "bin/aarch64"]
|
||||
struct Asset;
|
||||
|
||||
pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> {
|
||||
for file in Asset::iter() {
|
||||
utils::ensure_binary(
|
||||
format!("{BINARY_DIR}{file}"),
|
||||
&Asset::get(&file).unwrap().data,
|
||||
ignore_if_exist,
|
||||
)?
|
||||
if file == "ksuinit" {
|
||||
continue;
|
||||
}
|
||||
let asset = Asset::get(&file).ok_or(anyhow::anyhow!("asset not found: {}", file))?;
|
||||
utils::ensure_binary(format!("{BINARY_DIR}{file}"), &asset.data, ignore_if_exist)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn copy_assets_to_file(name: &str, dst: impl AsRef<Path>) -> Result<()> {
|
||||
let asset = Asset::get(name).ok_or(anyhow::anyhow!("asset not found: {}", name))?;
|
||||
std::fs::write(dst, asset.data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,27 +1,52 @@
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use anyhow::ensure;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use is_executable::IsExecutable;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
use which::which;
|
||||
|
||||
use crate::utils;
|
||||
use crate::{assets, utils};
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(target_os = "android")]
|
||||
fn ensure_gki_kernel() -> Result<()> {
|
||||
let version =
|
||||
procfs::sys::kernel::Version::current().with_context(|| "get kernel version failed")?;
|
||||
let is_gki = version.major == 5 && version.minor >= 10 || version.major > 5;
|
||||
let version = get_kernel_version()?;
|
||||
let is_gki = version.0 == 5 && version.1 >= 10 || version.2 > 5;
|
||||
ensure!(is_gki, "only support GKI kernel");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn get_kernel_version() -> Result<(i32, i32, i32)> {
|
||||
use regex::Regex;
|
||||
let uname = rustix::system::uname();
|
||||
let version = uname.release().to_string_lossy();
|
||||
let re = Regex::new(r"(\d+)\.(\d+)\.(\d+)")?;
|
||||
if let Some(captures) = re.captures(&version) {
|
||||
let major = captures
|
||||
.get(1)
|
||||
.and_then(|m| m.as_str().parse::<i32>().ok())
|
||||
.ok_or_else(|| anyhow!("Major version parse error"))?;
|
||||
let minor = captures
|
||||
.get(2)
|
||||
.and_then(|m| m.as_str().parse::<i32>().ok())
|
||||
.ok_or_else(|| anyhow!("Minor version parse error"))?;
|
||||
let patch = captures
|
||||
.get(3)
|
||||
.and_then(|m| m.as_str().parse::<i32>().ok())
|
||||
.ok_or_else(|| anyhow!("Patch version parse error"))?;
|
||||
Ok((major, minor, patch))
|
||||
} else {
|
||||
Err(anyhow!("Invalid kernel version string"))
|
||||
}
|
||||
}
|
||||
|
||||
fn do_cpio_cmd(magiskboot: &Path, workding_dir: &Path, cmd: &str) -> Result<()> {
|
||||
let status = Command::new(magiskboot)
|
||||
.current_dir(workding_dir)
|
||||
@@ -36,6 +61,18 @@ fn do_cpio_cmd(magiskboot: &Path, workding_dir: &Path, cmd: &str) -> Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_magisk_patched(magiskboot: &Path, workding_dir: &Path) -> Result<bool> {
|
||||
let status = Command::new(magiskboot)
|
||||
.current_dir(workding_dir)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.args(["cpio", "ramdisk.cpio", "test"])
|
||||
.status()?;
|
||||
|
||||
// 0: stock, 1: magisk
|
||||
Ok(status.code() == Some(1))
|
||||
}
|
||||
|
||||
fn dd<P: AsRef<Path>, Q: AsRef<Path>>(ifile: P, ofile: Q) -> Result<()> {
|
||||
let status = Command::new("dd")
|
||||
.stdout(Stdio::null())
|
||||
@@ -63,8 +100,28 @@ pub fn patch(
|
||||
out: Option<PathBuf>,
|
||||
magiskboot_path: Option<PathBuf>,
|
||||
) -> Result<()> {
|
||||
let result = do_patch(image, kernel, kmod, init, ota, flash, out, magiskboot_path);
|
||||
if let Err(ref e) = result {
|
||||
println!("- Install Error: {e}");
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn do_patch(
|
||||
image: Option<PathBuf>,
|
||||
kernel: Option<PathBuf>,
|
||||
kmod: Option<PathBuf>,
|
||||
init: Option<PathBuf>,
|
||||
ota: bool,
|
||||
flash: bool,
|
||||
out: Option<PathBuf>,
|
||||
magiskboot_path: Option<PathBuf>,
|
||||
) -> Result<()> {
|
||||
println!(include_str!("banner"));
|
||||
|
||||
if image.is_none() {
|
||||
#[cfg(unix)]
|
||||
#[cfg(target_os = "android")]
|
||||
ensure_gki_kernel()?;
|
||||
}
|
||||
|
||||
@@ -76,19 +133,17 @@ pub fn patch(
|
||||
"init and module must not be specified."
|
||||
);
|
||||
} else {
|
||||
ensure!(
|
||||
init.is_some() && kmod.is_some(),
|
||||
"init and module must be specified"
|
||||
);
|
||||
ensure!(kmod.is_some(), "module must be specified");
|
||||
}
|
||||
|
||||
let workding_dir = tempdir::TempDir::new("KernelSU")?;
|
||||
let workding_dir =
|
||||
tempdir::TempDir::new("KernelSU").with_context(|| "create temp dir failed")?;
|
||||
|
||||
let bootimage;
|
||||
|
||||
let mut bootdevice = None;
|
||||
|
||||
if let Some(image) = image {
|
||||
if let Some(ref image) = image {
|
||||
ensure!(image.exists(), "boot image not found");
|
||||
bootimage = std::fs::canonicalize(image)?;
|
||||
} else {
|
||||
@@ -111,7 +166,7 @@ pub fn patch(
|
||||
format!("/dev/block/by-name/boot{slot_suffix}")
|
||||
};
|
||||
|
||||
println!("bootdevice: {boot_partition}");
|
||||
println!("- Bootdevice: {boot_partition}");
|
||||
let tmp_boot_path = workding_dir.path().join("boot.img");
|
||||
|
||||
dd(&boot_partition, &tmp_boot_path)?;
|
||||
@@ -122,37 +177,56 @@ pub fn patch(
|
||||
bootdevice = Some(boot_partition);
|
||||
};
|
||||
|
||||
println!("boot image: {bootimage:?}");
|
||||
// try extract magiskboot/bootctl
|
||||
let _ = assets::ensure_binaries(false);
|
||||
|
||||
let magiskboot = magiskboot_path
|
||||
.map(std::fs::canonicalize)
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| "magiskboot".into());
|
||||
|
||||
if !magiskboot.is_executable() {
|
||||
#[cfg(unix)]
|
||||
std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755))
|
||||
.with_context(|| "set magiskboot executable failed".to_string())?;
|
||||
}
|
||||
|
||||
ensure!(magiskboot.exists(), "magiskboot not found");
|
||||
// extract magiskboot
|
||||
let magiskboot = {
|
||||
if which("magiskboot").is_ok() {
|
||||
let _ = assets::ensure_binaries(true);
|
||||
"magiskboot".into()
|
||||
} else {
|
||||
// magiskboot is not in $PATH, use builtin or specified one
|
||||
let magiskboot = if let Some(magiskboot_path) = magiskboot_path {
|
||||
std::fs::canonicalize(magiskboot_path)?
|
||||
} else {
|
||||
let magiskboot_path = workding_dir.path().join("magiskboot");
|
||||
assets::copy_assets_to_file("magiskboot", &magiskboot_path)
|
||||
.with_context(|| "copy magiskboot failed")?;
|
||||
magiskboot_path
|
||||
};
|
||||
ensure!(magiskboot.exists(), "{magiskboot:?} is not exist");
|
||||
#[cfg(unix)]
|
||||
let _ = std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755));
|
||||
magiskboot
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(kernel) = kernel {
|
||||
std::fs::copy(kernel, workding_dir.path().join("kernel"))
|
||||
.with_context(|| "copy kernel from failed".to_string())?;
|
||||
}
|
||||
|
||||
if let (Some(kmod), Some(init)) = (kmod, init) {
|
||||
if let Some(kmod) = kmod {
|
||||
println!("- Preparing assets");
|
||||
|
||||
std::fs::copy(kmod, workding_dir.path().join("kernelsu.ko"))
|
||||
.with_context(|| "copy kernel module failed".to_string())?;
|
||||
std::fs::copy(init, workding_dir.path().join("init"))
|
||||
.with_context(|| "copy init failed".to_string())?;
|
||||
let init_file = workding_dir.path().join("init");
|
||||
if let Some(init) = init {
|
||||
std::fs::copy(init, workding_dir.path().join("init"))
|
||||
.with_context(|| "copy init failed".to_string())?;
|
||||
} else {
|
||||
crate::assets::copy_assets_to_file("ksuinit", init_file)
|
||||
.with_context(|| "copy ksuinit failed")?;
|
||||
}
|
||||
|
||||
// magiskboot unpack boot.img
|
||||
// magiskboot cpio ramdisk.cpio 'cp init init.real'
|
||||
// magiskboot cpio ramdisk.cpio 'add 0755 ksuinit init'
|
||||
// magiskboot cpio ramdisk.cpio 'add 0755 <kmod> kernelsu.ko'
|
||||
|
||||
println!("- Unpacking boot image");
|
||||
let status = Command::new(&magiskboot)
|
||||
.current_dir(workding_dir.path())
|
||||
.stdout(Stdio::null())
|
||||
@@ -162,10 +236,22 @@ pub fn patch(
|
||||
.status()?;
|
||||
ensure!(status.success(), "magiskboot unpack failed");
|
||||
|
||||
let status = do_cpio_cmd(&magiskboot, workding_dir.path(), "exists init");
|
||||
if status.is_ok() {
|
||||
// init exist, backup it.
|
||||
do_cpio_cmd(&magiskboot, workding_dir.path(), "mv init init.real")?;
|
||||
let no_ramdisk = !workding_dir.path().join("ramdisk.cpio").exists();
|
||||
let is_magisk_patched = is_magisk_patched(&magiskboot, workding_dir.path())?;
|
||||
ensure!(
|
||||
no_ramdisk || !is_magisk_patched,
|
||||
"Cannot work with Magisk patched image"
|
||||
);
|
||||
|
||||
println!("- Adding KernelSU LKM");
|
||||
let is_kernelsu_patched =
|
||||
do_cpio_cmd(&magiskboot, workding_dir.path(), "exists kernelsu.ko").is_ok();
|
||||
if !is_kernelsu_patched {
|
||||
// kernelsu.ko is not exist, backup init if necessary
|
||||
let status = do_cpio_cmd(&magiskboot, workding_dir.path(), "exists init");
|
||||
if status.is_ok() {
|
||||
do_cpio_cmd(&magiskboot, workding_dir.path(), "mv init init.real")?;
|
||||
}
|
||||
}
|
||||
|
||||
do_cpio_cmd(&magiskboot, workding_dir.path(), "add 0755 init init")?;
|
||||
@@ -176,6 +262,7 @@ pub fn patch(
|
||||
)?;
|
||||
}
|
||||
|
||||
println!("- Repacking boot image");
|
||||
// magiskboot repack boot.img
|
||||
let status = Command::new(&magiskboot)
|
||||
.current_dir(workding_dir.path())
|
||||
@@ -185,18 +272,25 @@ pub fn patch(
|
||||
.arg(bootimage.display().to_string())
|
||||
.status()?;
|
||||
ensure!(status.success(), "magiskboot repack failed");
|
||||
let new_boot = workding_dir.path().join("new-boot.img");
|
||||
|
||||
let out = out.unwrap_or(std::env::current_dir()?);
|
||||
if image.is_some() {
|
||||
// if image is specified, write to output file
|
||||
let output_dir = out.unwrap_or(std::env::current_dir()?);
|
||||
let now = chrono::Utc::now();
|
||||
let output_image =
|
||||
output_dir.join(format!("kernelsu_boot_{}.img", now.format("%Y%m%d_%H%M%S")));
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let output_image = out.join(format!(
|
||||
"kernelsu_patched_boot_{}.img",
|
||||
now.format("%Y%m%d_%H%M%S")
|
||||
));
|
||||
std::fs::copy(workding_dir.path().join("new-boot.img"), &output_image)
|
||||
.with_context(|| "copy out new boot failed".to_string())?;
|
||||
if std::fs::rename(&new_boot, &output_image).is_err() {
|
||||
std::fs::copy(&new_boot, &output_image)
|
||||
.with_context(|| "copy out new boot failed".to_string())?;
|
||||
}
|
||||
println!("- Output file is written to");
|
||||
println!("- {}", output_image.display().to_string().trim_matches('"'));
|
||||
}
|
||||
|
||||
if flash {
|
||||
println!("- Flashing new boot image");
|
||||
let Some(bootdevice) = bootdevice else {
|
||||
bail!("boot device not found")
|
||||
};
|
||||
@@ -206,7 +300,52 @@ pub fn patch(
|
||||
.status()?;
|
||||
ensure!(status.success(), "set boot device rw failed");
|
||||
|
||||
dd(&output_image, &bootdevice).with_context(|| "flash boot failed")?;
|
||||
dd(&new_boot, &bootdevice).with_context(|| "flash boot failed")?;
|
||||
|
||||
if ota {
|
||||
post_ota()?;
|
||||
}
|
||||
}
|
||||
|
||||
println!("- Done!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_ota() -> Result<()> {
|
||||
use crate::defs::ADB_DIR;
|
||||
use assets::BOOTCTL_PATH;
|
||||
let status = Command::new(BOOTCTL_PATH).arg("hal-info").status()?;
|
||||
if !status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let current_slot = Command::new(BOOTCTL_PATH)
|
||||
.arg("get-current-slot")
|
||||
.output()?
|
||||
.stdout;
|
||||
let current_slot = String::from_utf8(current_slot)?;
|
||||
let current_slot = current_slot.trim();
|
||||
let target_slot = if current_slot == "0" { 1 } else { 0 };
|
||||
|
||||
Command::new(BOOTCTL_PATH)
|
||||
.arg(format!("set-active-boot-slot {target_slot}"))
|
||||
.status()?;
|
||||
|
||||
let post_ota_sh = std::path::Path::new(ADB_DIR)
|
||||
.join("post-fs-data.d")
|
||||
.join("post_ota.sh");
|
||||
|
||||
let sh_content = format!(
|
||||
r###"
|
||||
{BOOTCTL_PATH} mark-boot-successful
|
||||
rm -f {BOOTCTL_PATH}
|
||||
rm -f /data/adb/post-fs-data.d/post_ota.sh
|
||||
"###
|
||||
);
|
||||
|
||||
std::fs::write(&post_ota_sh, sh_content)?;
|
||||
#[cfg(unix)]
|
||||
std::fs::set_permissions(post_ota_sh, std::fs::Permissions::from_mode(0o755))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use android_logger::Config;
|
||||
#[cfg(target_os = "android")]
|
||||
use log::LevelFilter;
|
||||
|
||||
use crate::{apk_sign, debug, defs, event, module, utils};
|
||||
use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
|
||||
|
||||
/// KernelSU userspace cli
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -60,10 +60,10 @@ enum Commands {
|
||||
kernel: Option<PathBuf>,
|
||||
|
||||
/// LKM module path to replace
|
||||
#[arg(short, long, requires("init"))]
|
||||
#[arg(short, long)]
|
||||
module: Option<PathBuf>,
|
||||
|
||||
/// init to be replaced, if use LKM, this must be specified
|
||||
/// init to be replaced
|
||||
#[arg(short, long, requires("module"))]
|
||||
init: Option<PathBuf>,
|
||||
|
||||
@@ -239,7 +239,7 @@ pub fn run() -> Result<()> {
|
||||
// the kernel executes su with argv[0] = "su" and replace it with us
|
||||
let arg0 = std::env::args().next().unwrap_or_default();
|
||||
if arg0 == "su" || arg0 == "/system/bin/su" {
|
||||
return crate::ksu::root_shell();
|
||||
return crate::su::root_shell();
|
||||
}
|
||||
|
||||
let cli = Args::parse();
|
||||
@@ -247,8 +247,8 @@ pub fn run() -> Result<()> {
|
||||
log::info!("command: {:?}", cli.command);
|
||||
|
||||
let result = match cli.command {
|
||||
Commands::PostFsData => event::on_post_data_fs(),
|
||||
Commands::BootCompleted => event::on_boot_completed(),
|
||||
Commands::PostFsData => init_event::on_post_data_fs(),
|
||||
Commands::BootCompleted => init_event::on_boot_completed(),
|
||||
|
||||
Commands::Module { command } => {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
@@ -265,13 +265,13 @@ pub fn run() -> Result<()> {
|
||||
Module::Shrink => module::shrink_ksu_images(),
|
||||
}
|
||||
}
|
||||
Commands::Install => event::install(),
|
||||
Commands::Install => utils::install(),
|
||||
Commands::Sepolicy { command } => match command {
|
||||
Sepolicy::Patch { sepolicy } => crate::sepolicy::live_patch(&sepolicy),
|
||||
Sepolicy::Apply { file } => crate::sepolicy::apply_file(file),
|
||||
Sepolicy::Check { sepolicy } => crate::sepolicy::check_rule(&sepolicy),
|
||||
},
|
||||
Commands::Services => event::on_services(),
|
||||
Commands::Services => init_event::on_services(),
|
||||
Commands::Profile { command } => match command {
|
||||
Profile::GetSepolicy { package } => crate::profile::get_sepolicy(package),
|
||||
Profile::SetSepolicy { package, policy } => {
|
||||
@@ -291,11 +291,11 @@ pub fn run() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
Debug::Version => {
|
||||
println!("Kernel Version: {}", crate::ksu::get_version());
|
||||
println!("Kernel Version: {}", ksucalls::get_version());
|
||||
Ok(())
|
||||
}
|
||||
Debug::Su { global_mnt } => crate::ksu::grant_root(global_mnt),
|
||||
Debug::Mount => event::mount_systemlessly(defs::MODULE_DIR),
|
||||
Debug::Su { global_mnt } => crate::su::grant_root(global_mnt),
|
||||
Debug::Mount => init_event::mount_modules_systemlessly(defs::MODULE_DIR),
|
||||
Debug::Xcp {
|
||||
src,
|
||||
dst,
|
||||
@@ -304,7 +304,7 @@ pub fn run() -> Result<()> {
|
||||
utils::copy_sparse_file(src, dst, punch_hole)?;
|
||||
Ok(())
|
||||
}
|
||||
Debug::Test => todo!(),
|
||||
Debug::Test => assets::ensure_binaries(false),
|
||||
},
|
||||
|
||||
Commands::BootPatch {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use log::{info, warn};
|
||||
#[cfg(target_os = "android")]
|
||||
use std::path::PathBuf;
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use crate::module::prune_modules;
|
||||
use crate::{
|
||||
assets, defs, mount, restorecon,
|
||||
utils::{self, ensure_clean_dir, ensure_dir_exists},
|
||||
assets, defs, ksucalls, mount, restorecon,
|
||||
utils::{self, ensure_clean_dir},
|
||||
};
|
||||
|
||||
fn mount_partition(partition_name: &str, lowerdir: &Vec<String>) -> Result<()> {
|
||||
@@ -35,7 +33,7 @@ fn mount_partition(partition_name: &str, lowerdir: &Vec<String>) -> Result<()> {
|
||||
mount::mount_overlay(&partition, lowerdir, workdir, upperdir)
|
||||
}
|
||||
|
||||
pub fn mount_systemlessly(module_dir: &str) -> Result<()> {
|
||||
pub fn mount_modules_systemlessly(module_dir: &str) -> Result<()> {
|
||||
// construct overlay mount params
|
||||
let dir = std::fs::read_dir(module_dir);
|
||||
let Ok(dir) = dir else {
|
||||
@@ -99,12 +97,14 @@ pub fn mount_systemlessly(module_dir: &str) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn on_post_data_fs() -> Result<()> {
|
||||
crate::ksu::report_post_fs_data();
|
||||
ksucalls::report_post_fs_data();
|
||||
|
||||
utils::umask(0);
|
||||
|
||||
#[cfg(unix)]
|
||||
let _ = catch_bootlog();
|
||||
let _ = catch_bootlog("logcat", vec!["logcat"]);
|
||||
#[cfg(unix)]
|
||||
let _ = catch_bootlog("dmesg", vec!["dmesg", "-w"]);
|
||||
|
||||
if utils::has_magisk() {
|
||||
warn!("Magisk detected, skip post-fs-data!");
|
||||
@@ -162,7 +162,7 @@ pub fn on_post_data_fs() -> Result<()> {
|
||||
.with_context(|| "mount module image failed".to_string())?;
|
||||
|
||||
// tell kernel that we've mount the module, so that it can do some optimization
|
||||
crate::ksu::report_module_mounted();
|
||||
ksucalls::report_module_mounted();
|
||||
|
||||
// if we are in safe mode, we should disable all modules
|
||||
if safe_mode {
|
||||
@@ -207,7 +207,7 @@ pub fn on_post_data_fs() -> Result<()> {
|
||||
}
|
||||
|
||||
// mount module systemlessly by overlay
|
||||
if let Err(e) = mount_systemlessly(module_dir) {
|
||||
if let Err(e) = mount_modules_systemlessly(module_dir) {
|
||||
warn!("do systemless mount failed: {}", e);
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ pub fn on_services() -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn on_boot_completed() -> Result<()> {
|
||||
crate::ksu::report_boot_complete();
|
||||
ksucalls::report_boot_complete();
|
||||
info!("on_boot_completed triggered!");
|
||||
let module_update_img = Path::new(defs::MODULE_UPDATE_IMG);
|
||||
let module_img = Path::new(defs::MODULE_IMG);
|
||||
@@ -266,38 +266,15 @@ pub fn on_boot_completed() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install() -> Result<()> {
|
||||
ensure_dir_exists(defs::ADB_DIR)?;
|
||||
std::fs::copy("/proc/self/exe", defs::DAEMON_PATH)?;
|
||||
restorecon::lsetfilecon(defs::DAEMON_PATH, restorecon::ADB_CON)?;
|
||||
// install binary assets
|
||||
assets::ensure_binaries(false).with_context(|| "Failed to extract assets")?;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
link_ksud_to_bin()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn link_ksud_to_bin() -> Result<()> {
|
||||
let ksu_bin = PathBuf::from(defs::DAEMON_PATH);
|
||||
let ksu_bin_link = PathBuf::from(defs::DAEMON_LINK_PATH);
|
||||
if ksu_bin.exists() && !ksu_bin_link.exists() {
|
||||
std::os::unix::fs::symlink(&ksu_bin, &ksu_bin_link)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn catch_bootlog() -> Result<()> {
|
||||
fn catch_bootlog(logname: &str, command: Vec<&str>) -> Result<()> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Stdio;
|
||||
|
||||
let logdir = Path::new(defs::LOG_DIR);
|
||||
utils::ensure_dir_exists(logdir)?;
|
||||
let bootlog = logdir.join("boot.log");
|
||||
let oldbootlog = logdir.join("boot.old.log");
|
||||
let bootlog = logdir.join(format!("{logname}.log"));
|
||||
let oldbootlog = logdir.join(format!("{logname}.old.log"));
|
||||
|
||||
if bootlog.exists() {
|
||||
std::fs::rename(&bootlog, oldbootlog)?;
|
||||
@@ -305,6 +282,8 @@ fn catch_bootlog() -> Result<()> {
|
||||
|
||||
let bootlog = std::fs::File::create(bootlog)?;
|
||||
|
||||
let mut args = vec!["-s", "9", "30s"];
|
||||
args.extend_from_slice(&command);
|
||||
// timeout -s 9 30s logcat > boot.log
|
||||
let result = unsafe {
|
||||
std::process::Command::new("timeout")
|
||||
@@ -313,10 +292,7 @@ fn catch_bootlog() -> Result<()> {
|
||||
utils::switch_cgroups();
|
||||
Ok(())
|
||||
})
|
||||
.arg("-s")
|
||||
.arg("9")
|
||||
.arg("30s")
|
||||
.arg("logcat")
|
||||
.args(args)
|
||||
.stdout(Stdio::from(bootlog))
|
||||
.spawn()
|
||||
};
|
||||
43
userspace/ksud/src/ksucalls.rs
Normal file
43
userspace/ksud/src/ksucalls.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
const EVENT_POST_FS_DATA: u64 = 1;
|
||||
const EVENT_BOOT_COMPLETED: u64 = 2;
|
||||
const EVENT_MODULE_MOUNTED: u64 = 3;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn get_version() -> i32 {
|
||||
rustix::process::ksu_get_version()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn get_version() -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn report_event(event: u64) {
|
||||
rustix::process::ksu_report_event(event)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
fn report_event(_event: u64) {}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn check_kernel_safemode() -> bool {
|
||||
rustix::process::ksu_check_kernel_safemode()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn check_kernel_safemode() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn report_post_fs_data() {
|
||||
report_event(EVENT_POST_FS_DATA);
|
||||
}
|
||||
|
||||
pub fn report_boot_complete() {
|
||||
report_event(EVENT_BOOT_COMPLETED);
|
||||
}
|
||||
|
||||
pub fn report_module_mounted() {
|
||||
report_event(EVENT_MODULE_MOUNTED);
|
||||
}
|
||||
@@ -4,13 +4,14 @@ mod boot_patch;
|
||||
mod cli;
|
||||
mod debug;
|
||||
mod defs;
|
||||
mod event;
|
||||
mod ksu;
|
||||
mod init_event;
|
||||
mod ksucalls;
|
||||
mod module;
|
||||
mod mount;
|
||||
mod profile;
|
||||
mod restorecon;
|
||||
mod sepolicy;
|
||||
mod su;
|
||||
mod utils;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use crate::utils::*;
|
||||
use crate::{
|
||||
assets, defs, mount,
|
||||
assets, defs, ksucalls, mount,
|
||||
restorecon::{restore_syscon, setsyscon},
|
||||
sepolicy, utils,
|
||||
};
|
||||
@@ -12,6 +12,7 @@ use is_executable::is_executable;
|
||||
use java_properties::PropertiesIter;
|
||||
use log::{info, warn};
|
||||
|
||||
use std::fs::OpenOptions;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env::var as env_var,
|
||||
@@ -52,7 +53,7 @@ fn exec_install_script(module_file: &str) -> Result<()> {
|
||||
),
|
||||
)
|
||||
.env("KSU", "true")
|
||||
.env("KSU_KERNEL_VER_CODE", crate::ksu::get_version().to_string())
|
||||
.env("KSU_KERNEL_VER_CODE", ksucalls::get_version().to_string())
|
||||
.env("KSU_VER", defs::VERSION_NAME)
|
||||
.env("KSU_VER_CODE", defs::VERSION_CODE)
|
||||
.env("OUTFD", "1")
|
||||
@@ -177,7 +178,7 @@ fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
|
||||
.arg(path.as_ref())
|
||||
.env("ASH_STANDALONE", "1")
|
||||
.env("KSU", "true")
|
||||
.env("KSU_KERNEL_VER_CODE", crate::ksu::get_version().to_string())
|
||||
.env("KSU_KERNEL_VER_CODE", ksucalls::get_version().to_string())
|
||||
.env("KSU_VER_CODE", defs::VERSION_CODE)
|
||||
.env("KSU_VER", defs::VERSION_NAME)
|
||||
.env(
|
||||
@@ -391,22 +392,30 @@ fn _install_module(zip: &str) -> Result<()> {
|
||||
println!("- Legacy image, migrating to new format, please be patient...");
|
||||
create_module_image(tmp_module_img, sparse_image_size, journal_size)?;
|
||||
let _dontdrop =
|
||||
mount::AutoMountExt4::try_new(tmp_module_img, module_update_tmp_dir, true)?;
|
||||
fs_extra::dir::copy(
|
||||
defs::MODULE_DIR,
|
||||
module_update_tmp_dir,
|
||||
&fs_extra::dir::CopyOptions::new()
|
||||
.overwrite(true)
|
||||
.content_only(true),
|
||||
)?;
|
||||
mount::AutoMountExt4::try_new(tmp_module_img, module_update_tmp_dir, true)
|
||||
.with_context(|| format!("Failed to mount {tmp_module_img}"))?;
|
||||
utils::copy_module_files(defs::MODULE_DIR, module_update_tmp_dir)
|
||||
.with_context(|| "Failed to migrate module files".to_string())?;
|
||||
} else {
|
||||
utils::copy_sparse_file(modules_img, tmp_module_img, true).with_context(|| {
|
||||
format!(
|
||||
"Failed to copy {} to {}",
|
||||
modules_img.display(),
|
||||
tmp_module_img
|
||||
)
|
||||
})?;
|
||||
utils::copy_sparse_file(modules_img, tmp_module_img, true)
|
||||
.with_context(|| "Failed to copy module image".to_string())?;
|
||||
|
||||
if std::fs::metadata(tmp_module_img)?.len() < sparse_image_size {
|
||||
// truncate the file to new size
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.open(tmp_module_img)
|
||||
.context("Failed to open ext4 image")?
|
||||
.set_len(sparse_image_size)
|
||||
.context("Failed to truncate ext4 image")?;
|
||||
|
||||
// resize the image to new size
|
||||
check_image(tmp_module_img)?;
|
||||
Command::new("resize2fs")
|
||||
.arg(tmp_module_img)
|
||||
.stdout(Stdio::piped())
|
||||
.status()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,8 +55,13 @@ impl Drop for AutoMountExt4 {
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn mount_ext4(source: impl AsRef<Path>, target: impl AsRef<Path>) -> Result<()> {
|
||||
let new_loopback = loopdev::LoopControl::open()?.next_free()?;
|
||||
new_loopback.with().attach(source)?;
|
||||
let new_loopback = loopdev::LoopControl::open()?
|
||||
.next_free()
|
||||
.with_context(|| "Failed to alloc loop")?;
|
||||
new_loopback
|
||||
.with()
|
||||
.attach(source)
|
||||
.with_context(|| "Failed to attach loop")?;
|
||||
let lo = new_loopback.path().ok_or(anyhow!("no loop"))?;
|
||||
if let Result::Ok(fs) = fsopen("ext4", FsOpenFlags::FSOPEN_CLOEXEC) {
|
||||
let fs = fs.as_fd();
|
||||
@@ -104,14 +109,21 @@ pub fn mount_overlayfs(
|
||||
upperdir,
|
||||
workdir
|
||||
);
|
||||
if let Result::Ok(fs) = fsopen("overlay", FsOpenFlags::FSOPEN_CLOEXEC) {
|
||||
|
||||
let upperdir = upperdir
|
||||
.filter(|up| up.exists())
|
||||
.map(|e| e.display().to_string());
|
||||
let workdir = workdir
|
||||
.filter(|wd| wd.exists())
|
||||
.map(|e| e.display().to_string());
|
||||
|
||||
let result = (|| {
|
||||
let fs = fsopen("overlay", FsOpenFlags::FSOPEN_CLOEXEC)?;
|
||||
let fs = fs.as_fd();
|
||||
fsconfig_set_string(fs, "lowerdir", lowerdir_config)?;
|
||||
if let (Some(upperdir), Some(workdir)) = (upperdir, workdir) {
|
||||
if upperdir.exists() && workdir.exists() {
|
||||
fsconfig_set_string(fs, "upperdir", upperdir.display().to_string())?;
|
||||
fsconfig_set_string(fs, "workdir", workdir.display().to_string())?;
|
||||
}
|
||||
fsconfig_set_string(fs, "lowerdir", &lowerdir_config)?;
|
||||
if let (Some(upperdir), Some(workdir)) = (&upperdir, &workdir) {
|
||||
fsconfig_set_string(fs, "upperdir", upperdir)?;
|
||||
fsconfig_set_string(fs, "workdir", workdir)?;
|
||||
}
|
||||
fsconfig_set_string(fs, "source", KSU_OVERLAY_SOURCE)?;
|
||||
fsconfig_create(fs)?;
|
||||
@@ -122,17 +134,14 @@ pub fn mount_overlayfs(
|
||||
CWD,
|
||||
dest.as_ref(),
|
||||
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
|
||||
)?;
|
||||
} else {
|
||||
)
|
||||
})();
|
||||
|
||||
if let Err(e) = result {
|
||||
warn!("fsopen mount failed: {:#}, fallback to mount", e);
|
||||
let mut data = format!("lowerdir={lowerdir_config}");
|
||||
if let (Some(upperdir), Some(workdir)) = (upperdir, workdir) {
|
||||
if upperdir.exists() && workdir.exists() {
|
||||
data = format!(
|
||||
"{data},upperdir={},workdir={}",
|
||||
upperdir.display(),
|
||||
workdir.display()
|
||||
);
|
||||
}
|
||||
data = format!("{data},upperdir={upperdir},workdir={workdir}");
|
||||
}
|
||||
mount(
|
||||
KSU_OVERLAY_SOURCE,
|
||||
|
||||
@@ -17,10 +17,6 @@ use rustix::{
|
||||
thread::{set_thread_res_gid, set_thread_res_uid, Gid, Uid},
|
||||
};
|
||||
|
||||
const EVENT_POST_FS_DATA: u64 = 1;
|
||||
const EVENT_BOOT_COMPLETED: u64 = 2;
|
||||
const EVENT_MODULE_MOUNTED: u64 = 3;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn grant_root(global_mnt: bool) -> Result<()> {
|
||||
const KERNEL_SU_OPTION: u32 = 0xDEAD_BEEF;
|
||||
@@ -49,6 +45,9 @@ pub fn grant_root(global_mnt: bool) -> Result<()> {
|
||||
std::result::Result::Ok(())
|
||||
})
|
||||
};
|
||||
// add /data/adb/ksu/bin to PATH
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
add_path_to_env(defs::BINARY_DIR)?;
|
||||
Err(command.exec().into())
|
||||
}
|
||||
|
||||
@@ -80,12 +79,12 @@ fn set_identity(uid: u32, gid: u32, groups: &[u32]) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn root_shell() -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn root_shell() -> Result<()> {
|
||||
// we are root now, this was set in kernel!
|
||||
|
||||
@@ -300,43 +299,3 @@ fn add_path_to_env(path: &str) -> Result<()> {
|
||||
env::set_var("PATH", new_path_env);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn get_version() -> i32 {
|
||||
rustix::process::ksu_get_version()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn get_version() -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn report_event(event: u64) {
|
||||
rustix::process::ksu_report_event(event)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
fn report_event(_event: u64) {}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn check_kernel_safemode() -> bool {
|
||||
rustix::process::ksu_check_kernel_safemode()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn check_kernel_safemode() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn report_post_fs_data() {
|
||||
report_event(EVENT_POST_FS_DATA);
|
||||
}
|
||||
|
||||
pub fn report_boot_complete() {
|
||||
report_event(EVENT_BOOT_COMPLETED);
|
||||
}
|
||||
|
||||
pub fn report_module_mounted() {
|
||||
report_event(EVENT_MODULE_MOUNTED);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use crate::defs;
|
||||
use crate::{assets, defs, ksucalls, restorecon};
|
||||
use std::fs::metadata;
|
||||
#[allow(unused_imports)]
|
||||
use std::fs::{set_permissions, Permissions};
|
||||
@@ -15,14 +15,17 @@ use std::os::unix::prelude::PermissionsExt;
|
||||
use hole_punch::*;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use jwalk::WalkDir;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use rustix::{
|
||||
process,
|
||||
thread::{move_into_link_name_space, unshare, LinkNameSpaceType, UnshareFlags},
|
||||
};
|
||||
|
||||
pub fn ensure_clean_dir(dir: &str) -> Result<()> {
|
||||
let path = Path::new(dir);
|
||||
pub fn ensure_clean_dir(dir: impl AsRef<Path>) -> Result<()> {
|
||||
let path = dir.as_ref();
|
||||
log::debug!("ensure_clean_dir: {}", path.display());
|
||||
if path.exists() {
|
||||
log::debug!("ensure_clean_dir: {} exists, remove it", path.display());
|
||||
@@ -106,7 +109,7 @@ pub fn is_safe_mode() -> bool {
|
||||
if safemode {
|
||||
return true;
|
||||
}
|
||||
let safemode = crate::ksu::check_kernel_safemode();
|
||||
let safemode = ksucalls::check_kernel_safemode();
|
||||
log::info!("kernel_safemode: {}", safemode);
|
||||
safemode
|
||||
}
|
||||
@@ -191,6 +194,29 @@ pub fn get_tmp_path() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn link_ksud_to_bin() -> Result<()> {
|
||||
let ksu_bin = PathBuf::from(defs::DAEMON_PATH);
|
||||
let ksu_bin_link = PathBuf::from(defs::DAEMON_LINK_PATH);
|
||||
if ksu_bin.exists() && !ksu_bin_link.exists() {
|
||||
std::os::unix::fs::symlink(&ksu_bin, &ksu_bin_link)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install() -> Result<()> {
|
||||
ensure_dir_exists(defs::ADB_DIR)?;
|
||||
std::fs::copy("/proc/self/exe", defs::DAEMON_PATH)?;
|
||||
restorecon::lsetfilecon(defs::DAEMON_PATH, restorecon::ADB_CON)?;
|
||||
// install binary assets
|
||||
assets::ensure_binaries(false).with_context(|| "Failed to extract assets")?;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
link_ksud_to_bin()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: use libxcp to improve the speed if cross's MSRV is 1.70
|
||||
pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(
|
||||
src: P,
|
||||
@@ -241,3 +267,102 @@ pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn copy_xattrs(src_path: impl AsRef<Path>, dest_path: impl AsRef<Path>) -> Result<()> {
|
||||
use rustix::path::Arg;
|
||||
let std::result::Result::Ok(xattrs) = extattr::llistxattr(src_path.as_ref()) else {
|
||||
return Ok(());
|
||||
};
|
||||
for xattr in xattrs {
|
||||
let std::result::Result::Ok(value) = extattr::lgetxattr(src_path.as_ref(), &xattr) else {
|
||||
continue;
|
||||
};
|
||||
log::info!(
|
||||
"Set {:?} xattr {} = {}",
|
||||
dest_path.as_ref(),
|
||||
xattr.to_string_lossy(),
|
||||
value.to_string_lossy(),
|
||||
);
|
||||
if let Err(e) =
|
||||
extattr::lsetxattr(dest_path.as_ref(), &xattr, &value, extattr::Flags::empty())
|
||||
{
|
||||
log::warn!("Failed to set xattr: {}", e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn copy_module_files(source: impl AsRef<Path>, destination: impl AsRef<Path>) -> Result<()> {
|
||||
use rustix::fs::FileTypeExt;
|
||||
use rustix::fs::MetadataExt;
|
||||
|
||||
for entry in WalkDir::new(source.as_ref()).into_iter() {
|
||||
let entry = entry.context("Failed to access entry")?;
|
||||
let source_path = entry.path();
|
||||
let relative_path = source_path
|
||||
.strip_prefix(source.as_ref())
|
||||
.context("Failed to generate relative path")?;
|
||||
let dest_path = destination.as_ref().join(relative_path);
|
||||
|
||||
if let Some(parent) = dest_path.parent() {
|
||||
std::fs::create_dir_all(parent).context("Failed to create directory")?;
|
||||
}
|
||||
|
||||
if entry.file_type().is_file() {
|
||||
std::fs::copy(&source_path, &dest_path).with_context(|| {
|
||||
format!("Failed to copy file from {source_path:?} to {dest_path:?}",)
|
||||
})?;
|
||||
copy_xattrs(&source_path, &dest_path)?;
|
||||
} else if entry.file_type().is_symlink() {
|
||||
if dest_path.exists() {
|
||||
std::fs::remove_file(&dest_path).context("Failed to remove file")?;
|
||||
}
|
||||
let target = std::fs::read_link(entry.path()).context("Failed to read symlink")?;
|
||||
log::info!("Symlink: {:?} -> {:?}", dest_path, target);
|
||||
std::os::unix::fs::symlink(target, &dest_path).context("Failed to create symlink")?;
|
||||
copy_xattrs(&source_path, &dest_path)?;
|
||||
} else if entry.file_type().is_dir() {
|
||||
create_dir_all(&dest_path)?;
|
||||
let metadata = std::fs::metadata(&source_path).context("Failed to read metadata")?;
|
||||
std::fs::set_permissions(&dest_path, metadata.permissions())
|
||||
.with_context(|| format!("Failed to set permissions for {dest_path:?}"))?;
|
||||
copy_xattrs(&source_path, &dest_path)?;
|
||||
} else if entry.file_type().is_char_device() {
|
||||
if dest_path.exists() {
|
||||
std::fs::remove_file(&dest_path).context("Failed to remove file")?;
|
||||
}
|
||||
let metadata = std::fs::metadata(&source_path).context("Failed to read metadata")?;
|
||||
let mode = metadata.permissions().mode();
|
||||
let dev = metadata.rdev();
|
||||
if dev == 0 {
|
||||
log::info!(
|
||||
"Found a char device with major 0: {}",
|
||||
entry.path().display()
|
||||
);
|
||||
rustix::fs::mknodat(
|
||||
rustix::fs::CWD,
|
||||
&dest_path,
|
||||
rustix::fs::FileType::CharacterDevice,
|
||||
mode.into(),
|
||||
dev,
|
||||
)
|
||||
.with_context(|| format!("Failed to create device file at {dest_path:?}"))?;
|
||||
copy_xattrs(&source_path, &dest_path)?;
|
||||
}
|
||||
} else {
|
||||
log::info!(
|
||||
"Unknown file type: {:?}, {:?},",
|
||||
entry.file_type(),
|
||||
entry.path(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn copy_module_files(_source: impl AsRef<Path>, _destination: impl AsRef<Path>) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ function sidebarGuide() {
|
||||
items: [
|
||||
{ text: 'KernelSU とは?', link: '/ja_JP/guide/what-is-kernelsu' },
|
||||
{ text: 'インストール', link: '/ja_JP/guide/installation' },
|
||||
{ text: 'ビルドするには?', link: '/guide/how-to-build' },
|
||||
{ text: '非 GKI デバイスでの実装', link: '/guide/how-to-integrate-for-non-gki' },
|
||||
{ text: 'ビルドするには?', link: '/ja_JP/guide/how-to-build' },
|
||||
{ text: '非 GKI デバイスでの実装', link: '/ja_JP/guide/how-to-integrate-for-non-gki' },
|
||||
{ text: '非公式の対応デバイス', link: '/ja_JP/guide/unofficially-support-devices.md' },
|
||||
{ text: 'モジュールのガイド', link: '/ja_JP/guide/module.md' },
|
||||
{ text: 'ブートループからの復旧', link: '/ja_JP/guide/rescue-from-bootloop.md' },
|
||||
|
||||
@@ -51,7 +51,7 @@ function sidebarGuide() {
|
||||
{ text: '如何为非GKI设备集成 KernelSU', link: '/zh_CN/guide/how-to-integrate-for-non-gki'},
|
||||
{ text: '非官方支持设备', link: '/zh_CN/guide/unofficially-support-devices.md' },
|
||||
{ text: '模块开发指南', link: '/zh_CN/guide/module.md' },
|
||||
{ text: '模块 Web 界面', link: '/guide/module-webui.md' },
|
||||
{ text: '模块 Web 界面', link: '/zh_CN/guide/module-webui.md' },
|
||||
{ text: 'App Profile', link: '/zh_CN/guide/app-profile.md' },
|
||||
{ text: '救砖', link: '/zh_CN/guide/rescue-from-bootloop.md' },
|
||||
{ text: '常见问题', link: '/zh_CN/guide/faq' },
|
||||
|
||||
@@ -45,11 +45,14 @@ function sidebarGuide() {
|
||||
text: 'Guide',
|
||||
items: [
|
||||
{ text: '什麼是 KernelSU?', link: '/zh_TW/guide/what-is-kernelsu' },
|
||||
{ text: 'KernelSU 與 Magisk 的差異', link: '/zh_TW/guide/difference-with-magisk' },
|
||||
{ text: '安裝', link: '/zh_TW/guide/installation' },
|
||||
{ text: '如何建置?', link: '/zh_TW/guide/how-to-build' },
|
||||
{ text: '如何為非 GKI 核心整合 KernelSU', link: '/zh_TW/guide/how-to-integrate-for-non-gki'},
|
||||
{ text: '非官方支援裝置', link: '/zh_TW/guide/unofficially-support-devices.md' },
|
||||
{ text: '模組指南', link: '/zh_TW/guide/module.md' },
|
||||
{ text: '模組 WebUI', link: '/zh_TW/guide/module-webui.md' },
|
||||
{ text: 'App Profile', link: '/zh_TW/guide/app-profile.md' },
|
||||
{ text: '搶救開機迴圈', link: '/zh_TW/guide/rescue-from-bootloop.md' },
|
||||
{ text: '常見問題', link: '/zh_TW/guide/faq' },
|
||||
{ text: '隱藏功能', link: '/zh_TW/guide/hidden-features' },
|
||||
|
||||
@@ -41,7 +41,7 @@ It is possible. But you should download the kernel source and intergrate KernelS
|
||||
It is device's kernel that affect KernelSU's compatability and it has nothing to do with Android version.The only restriction is that devices launched with Android 12 must be kernel 5.10+(GKI devices). So:
|
||||
|
||||
1. Devices launched with Android 12 must be supported.
|
||||
2. Devices with has an old kernel (Some Android 12 devices is also old kernel) is compatable (You should build kernel yourself)
|
||||
2. Devices which have an old kernel (Some Android 12 devices is also old kernel) are compatible (You should build kernel yourself)
|
||||
|
||||
## Can KernelSU support old kernel?
|
||||
|
||||
@@ -67,8 +67,12 @@ We do not recommend you to modify the system partition directly. You should use
|
||||
|
||||
Of course. But KernelSU doesn't have builtin hosts support, you can install [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) to do it.
|
||||
|
||||
## Why is there a huge 1T file?
|
||||
## Why is there a huge 1 TB file?
|
||||
|
||||
The 1T size `modules.img` is a disk image file, **don't worry about its size**, it's a special type of file known as a [sparse file](https://en.wikipedia.org/wiki/Sparse_file), its actual size is only the size of the module you use, and it will dynamically shrink after you delete the module; it does not actually occupy 1T of disk space (actually your mobile phone may not have that much space).
|
||||
The 1 TB size `modules.img` is a disk image file, **don't worry about its size**, it's a special type of file known as a [sparse file](https://en.wikipedia.org/wiki/Sparse_file), its actual size is only the size of the module you use, and it will dynamically shrink after you delete the module; it does not actually occupy 1 TB of disk space (actually your mobile phone may not have that much space).
|
||||
|
||||
If you're really unhappy with the size of this file, you can use the `resize2fs -M` command to make it the actual size; but the module may not work properly at this time, and we won't provide any support for this.
|
||||
|
||||
## Why does my device show wrong storage size?
|
||||
|
||||
Certain devices use non-standard methods for calculating device's storage size, potentially leading to inaccurate storage calculations in system apps and menus, especially when dealing with 1 TB sparse files. While this problem seems to be specific to Samsung devices, affecting only Samsung apps and services, it's essential to note that the discrepancy is primarily in the total storage size, and the free space calculation remains accurate.
|
||||
|
||||
@@ -40,6 +40,14 @@ Starting from Android 13, the kernel is built by `bazel`:
|
||||
tools/bazel build --config=fast //common:kernel_aarch64_dist
|
||||
```
|
||||
|
||||
:::info
|
||||
For some Android 14 Kernel, to make Wi-Fi/Bluetooth work. It may be necessary to remove all the GKI protected exports:
|
||||
|
||||
```sh
|
||||
rm common/android/abi_gki_protected_exports_*
|
||||
```
|
||||
:::
|
||||
|
||||
## Build Kernel with KernelSU
|
||||
|
||||
If you can build the kernel successfully, then build KernelSU is so easy, Select any one run in Kernel source root dir:
|
||||
|
||||
@@ -70,7 +70,7 @@ If your device's `boot.img` uses a commonly used compression format, you can use
|
||||
|
||||
### Find proper boot.img
|
||||
|
||||
KernelSU provides a generic boot.img for GKI devices and you should flush the boot.img to the boot partition of the device.
|
||||
KernelSU provides a generic boot.img for GKI devices and you should flash the boot.img to the boot partition of the device.
|
||||
|
||||
You can download boot.img from [GitHub Release](https://github.com/tiann/KernelSU/releases), please note that you should use the correct version of boot.img. If you don't know which file to download, please carefully read the description of [KMI](#kmi) and [Security Patch Level](#security-patch-level) in this document.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ KernelSU provides a JavaScript library and [publishes it on npm](https://www.npm
|
||||
|
||||
For example, you can execute a shell command to obtain a specific configuration or modify a property:
|
||||
|
||||
```javascript
|
||||
```JavaScript
|
||||
import { exec } from 'kernelsu';
|
||||
|
||||
const { errno, stdout } = exec("getprop ro.product.model");
|
||||
|
||||
303
website/docs/ja_JP/guide/how-to-integrate-for-non-gki.md
Normal file
303
website/docs/ja_JP/guide/how-to-integrate-for-non-gki.md
Normal file
@@ -0,0 +1,303 @@
|
||||
# 非 GKI カーネルで KernelSU を統合する方法は?
|
||||
|
||||
KernelSU は非 GKI カーネルに統合することが可能であり、4.14 以下のバージョンにバックポートされました。
|
||||
|
||||
非 GKI カーネルの断片化のため、統一されたビルド方法がありませんので、非 GKI ブートイメージを提供することができません。しかし、KernelSU を統合して自分自身でカーネルをビルドすることができます。
|
||||
|
||||
まず、カーネルソースコードからブート可能なカーネルをビルドできる能力が必要です。もしカーネルがオープンソースでない場合、あなたのデバイスで KernelSU を実行することは困難です。
|
||||
|
||||
ブート可能なカーネルをビルドできるなら、カーネルソースコードに KernelSU を統合する方法は二つあります:
|
||||
|
||||
1. `kprobe` で自動的に
|
||||
2. 手動で
|
||||
|
||||
## kprobe で統合する
|
||||
|
||||
KernelSU は kprobe を使ってカーネルフックを行います。もし *kprobe* があなたのカーネルでうまく動作する場合、この方法を使うことを推奨します。
|
||||
|
||||
まず、KernelSU をカーネルソースツリーに追加してください:
|
||||
|
||||
```sh
|
||||
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -
|
||||
```
|
||||
|
||||
次に、*kprobe* がカーネル設定で有効になっているか確認してください。もし有効でなければ、これらの設定を追加してください:
|
||||
|
||||
```
|
||||
CONFIG_KPROBES=y
|
||||
CONFIG_HAVE_KPROBES=y
|
||||
CONFIG_KPROBE_EVENTS=y
|
||||
```
|
||||
そしてカーネルを再度ビルドしてください。KernelSU はうまく動作するはずです。
|
||||
|
||||
KPROBES がまだ有効化されていない場合は、CONFIG_MODULES を有効化して試みることができます。(それでも効果がない場合は、make menuconfig を使って KPROBES の他の依存関係を検索してください)
|
||||
|
||||
しかし、KernelSU を統合した際にブートループに遭遇した場合、それは *kprobe* があなたのカーネルで破損している可能性があります。kprobe のバグを修正するか、二番目の方法を使用するべきです。
|
||||
|
||||
:::tip kprobe が破損しているかどうかを確認する方法は?
|
||||
|
||||
`KernelSU/kernel/ksu.c` にある `ksu_enable_sucompat()` と `ksu_enable_ksud()` をコメントアウトし、デバイスが正常にブートするか試してください。もし正常にブートするならば、kprobe が破損している可能性があります。
|
||||
|
||||
## カーネルソースを手動で変更する
|
||||
|
||||
もし kprobe があなたのカーネルで機能しない場合(上流のバグや 4.8 以下のカーネルバグが原因かもしれません)、以下の方法を試すことができます。
|
||||
|
||||
まず、KernelSU をカーネルソースツリーに追加してください:
|
||||
|
||||
::: code-group
|
||||
## カーネルソースを手動で変更する
|
||||
|
||||
もし kprobe があなたのカーネルで機能しない場合(上流のバグや 4.8 以下のカーネルバグが原因かもしれません)、以下の方法を試すことができます。
|
||||
|
||||
まず、KernelSU をカーネルソースツリーに追加してください:
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh
|
||||
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -
|
||||
```
|
||||
|
||||
[ main branch(dev)]
|
||||
```sh
|
||||
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s main
|
||||
```
|
||||
|
||||
[Select tag(Such as v0.5.2)]
|
||||
```sh
|
||||
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.5.2
|
||||
```
|
||||
|
||||
|
||||
:::
|
||||
|
||||
いくつかのデバイスでは、あなたの defconfig が `arch/arm64/configs` にあったり、または他のケースでは `arch/arm64/configs/vendor/your_defconfig` にあることを念頭に置いてください。例えばあなたの defconfig で、`CONFIG_KSU` を y で有効に、または n で無効に設定します。あなたのパスは次のようになるでしょう:
|
||||
`arch/arm64/configs/...`
|
||||
```
|
||||
# KernelSU
|
||||
CONFIG_KSU=y
|
||||
```
|
||||
次に、KernelSU の呼び出しをカーネルソースに追加します。こちらは参照のためのパッチです:
|
||||
|
||||
::: code-group
|
||||
|
||||
```diff[exec.c]
|
||||
diff --git a/fs/exec.c b/fs/exec.c
|
||||
index ac59664eaecf..bdd585e1d2cc 100644
|
||||
--- a/fs/exec.c
|
||||
+++ b/fs/exec.c
|
||||
@@ -1890,11 +1890,14 @@ static int __do_execve_file(int fd, struct filename *filename,
|
||||
return retval;
|
||||
}
|
||||
|
||||
+#ifdef CONFIG_KSU
|
||||
+extern bool ksu_execveat_hook __read_mostly;
|
||||
+extern int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
|
||||
+ void *envp, int *flags);
|
||||
+extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||
+ void *argv, void *envp, int *flags);
|
||||
+#endif
|
||||
static int do_execveat_common(int fd, struct filename *filename,
|
||||
struct user_arg_ptr argv,
|
||||
struct user_arg_ptr envp,
|
||||
int flags)
|
||||
{
|
||||
+ #ifdef CONFIG_KSU
|
||||
+ if (unlikely(ksu_execveat_hook))
|
||||
+ ksu_handle_execveat(&fd, &filename, &argv, &envp, &flags);
|
||||
+ else
|
||||
+ ksu_handle_execveat_sucompat(&fd, &filename, &argv, &envp, &flags);
|
||||
+ #endif
|
||||
return __do_execve_file(fd, filename, argv, envp, flags, NULL);
|
||||
}
|
||||
```
|
||||
```diff[open.c]
|
||||
diff --git a/fs/open.c b/fs/open.c
|
||||
index 05036d819197..965b84d486b8 100644
|
||||
--- a/fs/open.c
|
||||
+++ b/fs/open.c
|
||||
@@ -348,6 +348,8 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len)
|
||||
return ksys_fallocate(fd, mode, offset, len);
|
||||
}
|
||||
|
||||
+#ifdef CONFIG_KSU
|
||||
+extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||
+ int *flags);
|
||||
+#endif
|
||||
/*
|
||||
* access() needs to use the real uid/gid, not the effective uid/gid.
|
||||
* We do this by temporarily clearing all FS-related capabilities and
|
||||
@@ -355,6 +357,7 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len)
|
||||
*/
|
||||
long do_faccessat(int dfd, const char __user *filename, int mode)
|
||||
{
|
||||
const struct cred *old_cred;
|
||||
struct cred *override_cred;
|
||||
struct path path;
|
||||
struct inode *inode;
|
||||
struct vfsmount *mnt;
|
||||
int res;
|
||||
unsigned int lookup_flags = LOOKUP_FOLLOW;
|
||||
+ #ifdef CONFIG_KSU
|
||||
+ ksu_handle_faccessat(&dfd, &filename, &mode, NULL);
|
||||
+ #endif
|
||||
|
||||
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
|
||||
return -EINVAL;
|
||||
```
|
||||
```diff[read_write.c]
|
||||
diff --git a/fs/read_write.c b/fs/read_write.c
|
||||
index 650fc7e0f3a6..55be193913b6 100644
|
||||
--- a/fs/read_write.c
|
||||
+++ b/fs/read_write.c
|
||||
@@ -434,10 +434,14 @@ ssize_t kernel_read(struct file *file, void *buf, size_t count, loff_t *pos)
|
||||
}
|
||||
EXPORT_SYMBOL(kernel_read);
|
||||
|
||||
+#ifdef CONFIG_KSU
|
||||
+extern bool ksu_vfs_read_hook __read_mostly;
|
||||
+extern int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
+ size_t *count_ptr, loff_t **pos);
|
||||
+#endif
|
||||
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
|
||||
{
|
||||
ssize_t ret;
|
||||
+ #ifdef CONFIG_KSU
|
||||
+ if (unlikely(ksu_vfs_read_hook))
|
||||
+ ksu_handle_vfs_read(&file, &buf, &count, &pos);
|
||||
+ #endif
|
||||
+
|
||||
if (!(file->f_mode & FMODE_READ))
|
||||
return -EBADF;
|
||||
if (!(file->f_mode & FMODE_CAN_READ))
|
||||
```
|
||||
```diff[stat.c]
|
||||
diff --git a/fs/stat.c b/fs/stat.c
|
||||
index 376543199b5a..82adcef03ecc 100644
|
||||
--- a/fs/stat.c
|
||||
+++ b/fs/stat.c
|
||||
@@ -148,6 +148,8 @@ int vfs_statx_fd(unsigned int fd, struct kstat *stat,
|
||||
}
|
||||
EXPORT_SYMBOL(vfs_statx_fd);
|
||||
|
||||
+#ifdef CONFIG_KSU
|
||||
+extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
||||
+#endif
|
||||
+
|
||||
/**
|
||||
* vfs_statx - Get basic and extra attributes by filename
|
||||
* @dfd: A file descriptor representing the base dir for a relative filename
|
||||
@@ -170,6 +172,7 @@ int vfs_statx(int dfd, const char __user *filename, int flags,
|
||||
int error = -EINVAL;
|
||||
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
|
||||
|
||||
+ #ifdef CONFIG_KSU
|
||||
+ ksu_handle_stat(&dfd, &filename, &flags);
|
||||
+ #endif
|
||||
if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
|
||||
AT_EMPTY_PATH | KSTAT_QUERY_FLAGS)) != 0)
|
||||
return -EINVAL;
|
||||
```
|
||||
カーネル ソースには 4 つの関数があるはずです。
|
||||
|
||||
1. do_faccessat、通常は `fs/open.c` にあります
|
||||
2. do_execveat_common (通常は `fs/exec.c` にあります)
|
||||
3. vfs_read (通常は `fs/read_write.c` にあります)
|
||||
4. vfs_statx (通常は「fs/stat.c」にあります)
|
||||
|
||||
カーネルに `vfs_statx` がない場合は、代わりに `vfs_fstatat` を使用してください:
|
||||
|
||||
```diff
|
||||
diff --git a/fs/stat.c b/fs/stat.c
|
||||
index 068fdbcc9e26..5348b7bb9db2 100644
|
||||
--- a/fs/stat.c
|
||||
+++ b/fs/stat.c
|
||||
@@ -87,6 +87,8 @@ int vfs_fstat(unsigned int fd, struct kstat *stat)
|
||||
}
|
||||
EXPORT_SYMBOL(vfs_fstat);
|
||||
|
||||
+#ifdef CONFIG_KSU
|
||||
+extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
||||
+#endif
|
||||
int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,
|
||||
int flag)
|
||||
{
|
||||
@@ -94,6 +96,8 @@ int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,
|
||||
int error = -EINVAL;
|
||||
unsigned int lookup_flags = 0;
|
||||
+ #ifdef CONFIG_KSU
|
||||
+ ksu_handle_stat(&dfd, &filename, &flag);
|
||||
+ #endif
|
||||
+
|
||||
if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
|
||||
AT_EMPTY_PATH)) != 0)
|
||||
goto out;
|
||||
```
|
||||
|
||||
4.17 より前のカーネルの場合、`do faccessat` が見つからない場合は、`faccessat` システムコールの定義に移動して、そこで呼び出しを実行します。
|
||||
|
||||
```diff
|
||||
diff --git a/fs/open.c b/fs/open.c
|
||||
index 2ff887661237..e758d7db7663 100644
|
||||
--- a/fs/open.c
|
||||
+++ b/fs/open.c
|
||||
@@ -355,6 +355,9 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len)
|
||||
return error;
|
||||
}
|
||||
|
||||
+#ifdef CONFIG_KSU
|
||||
+extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||
+ int *flags);
|
||||
+#endif
|
||||
+
|
||||
/*
|
||||
* access() needs to use the real uid/gid, not the effective uid/gid.
|
||||
* We do this by temporarily clearing all FS-related capabilities and
|
||||
@@ -370,6 +373,8 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
|
||||
int res;
|
||||
unsigned int lookup_flags = LOOKUP_FOLLOW;
|
||||
+ #ifdef CONFIG_KSU
|
||||
+ ksu_handle_faccessat(&dfd, &filename, &mode, NULL);
|
||||
+ #endif
|
||||
+
|
||||
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
|
||||
return -EINVAL;
|
||||
```
|
||||
|
||||
KernelSU の組み込み SafeMode を有効にするには、`drivers/input/input.c` の `input_handle_event` も変更する必要があります。
|
||||
|
||||
:::ヒント
|
||||
この機能を有効にすることを強くお勧めします。ブートループを防ぐのに非常に役立ちます!
|
||||
:::
|
||||
|
||||
```diff
|
||||
diff --git a/drivers/input/input.c b/drivers/input/input.c
|
||||
index 45306f9ef247..815091ebfca4 100755
|
||||
--- a/drivers/input/input.c
|
||||
+++ b/drivers/input/input.c
|
||||
@@ -367,10 +367,13 @@ static int input_get_disposition(struct input_dev *dev,
|
||||
return disposition;
|
||||
}
|
||||
|
||||
+#ifdef CONFIG_KSU
|
||||
+extern bool ksu_input_hook __read_mostly;
|
||||
+extern int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int *value);
|
||||
+#endif
|
||||
+
|
||||
static void input_handle_event(struct input_dev *dev,
|
||||
unsigned int type, unsigned int code, int value)
|
||||
{
|
||||
int disposition = input_get_disposition(dev, type, code, &value);
|
||||
+ #ifdef CONFIG_KSU
|
||||
+ if (unlikely(ksu_input_hook))
|
||||
+ ksu_handle_input_handle_event(&type, &code, &value);
|
||||
+ #endif
|
||||
|
||||
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
|
||||
add_input_randomness(type, code, value);
|
||||
```
|
||||
|
||||
最後に、カーネルを再度ビルドすると、KernelSU が正常に動作するはずです。
|
||||
|
||||
:::info 誤ってセーフ モードに入ってしまった場合は、
|
||||
手動統合を使用し、`CONFIG_KPROBES` を無効にしない場合、ユーザーは起動後に音量を下げるボタンを押してセーフ モードをトリガーする可能性があります。 したがって、手動統合を使用する場合は、`CONFIG_KPROBES` を無効にする必要があります。
|
||||
:::
|
||||
@@ -6,7 +6,7 @@ KernelSU のモジュールは、ブートスクリプトの実行やシステ
|
||||
|
||||
## webroot ディレクトリ
|
||||
|
||||
Web リソースファイルは、モジュールのルートディレクトリの webroot サブディレクトリに置かれるべきであり、index.html という名前のファイルが必ず存在しなければなりません。これがモジュールページのエントリです。Web インターフェイスを含む最もシンプルなモジュール構造は以下の通りです:
|
||||
Web リソースファイルは、モジュールのルートディレクトリの `webroot` サブディレクトリに置かれるべきであり、`index.html` という名前のファイルが必ず存在しなければなりません。これがモジュールページのエントリです。Web インターフェイスを含む最もシンプルなモジュール構造は以下の通りです:
|
||||
|
||||
```txt
|
||||
❯ tree .
|
||||
@@ -22,15 +22,15 @@ Web リソースファイルは、モジュールのルートディレクトリ
|
||||
|
||||
ページに css や JavaScript が含まれている場合は、このディレクトリに配置する必要があります。
|
||||
|
||||
## Javascript API
|
||||
## JavaScript API
|
||||
|
||||
単なる表示ページであれば、通常の Web ページとの違いはありません。より重要なのは、KernelSU がモジュールの固有機能を実装させるための一連のシステム API を提供することです。
|
||||
|
||||
KernelSU は Javascript ライブラリを提供し、[npm で公開しています](https://www.npmjs.com/package/kernelsu)。これを Web ページの JavaScript コードで使用することができます。
|
||||
KernelSU は JavaScript ライブラリを提供し、[npm で公開しています](https://www.npmjs.com/package/kernelsu)。これを Web ページの JavaScript コードで使用することができます。
|
||||
|
||||
たとえば、特定の設定を取得したり、プロパティを変更するために、シェルコマンドを実行することができます:
|
||||
|
||||
```javascript
|
||||
```JavaScript
|
||||
import { exec } from 'kernelsu';
|
||||
|
||||
const { errno, stdout } = exec("getprop ro.product.model");
|
||||
@@ -44,5 +44,5 @@ const { errno, stdout } = exec("getprop ro.product.model");
|
||||
|
||||
## いくつかのヒント
|
||||
|
||||
1. localStorage を通常通りに使用してデータを保存することができますが、Manager アプリをアンインストールした後には失われます。永続的に保存する必要がある場合は、自分でいくつかのディレクトリにデータを書き込むことができます。
|
||||
1. `localStorage` を通常通りに使用してデータを保存することができますが、Manager アプリをアンインストールした後には失われます。永続的に保存する必要がある場合は、自分でいくつかのディレクトリにデータを書き込むことができます。
|
||||
2. シンプルなページには、[parceljs](https://parceljs.org/)を使用することをお勧めします。設定が不要で非常に便利です。しかし、フロントエンドの達人である場合や、自分の好みがある場合は、気に入ったものを選んでください!
|
||||
|
||||
@@ -67,8 +67,12 @@ Não recomendamos que você modifique a partição do sistema diretamente. Você
|
||||
|
||||
Claro. Mas o KernelSU não tem suporte a hosts integrados, você pode instalar [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) para fazer isso.
|
||||
|
||||
## Por que existe um enorme arquivo de 1T?
|
||||
## Por que existe um enorme arquivo de 1 TB?
|
||||
|
||||
O arquivo `modules.img` de 1T é um arquivo de imagem de disco, **não se preocupe com seu tamanho**, é um tipo especial de arquivo conhecido como [arquivo esparso](https://en.wikipedia.org/wiki/Sparse_file), seu tamanho real é apenas o tamanho do módulo que você usa e diminuirá dinamicamente após você excluir o módulo. Na verdade, ele não ocupa 1T de espaço em disco (na verdade, seu celular pode não ter tanto espaço).
|
||||
O arquivo `modules.img` de 1 TB é um arquivo de imagem de disco, **não se preocupe com seu tamanho**, é um tipo especial de arquivo conhecido como [arquivo esparso](https://en.wikipedia.org/wiki/Sparse_file), seu tamanho real é apenas o tamanho do módulo que você usa e diminuirá dinamicamente após você excluir o módulo. Na verdade, ele não ocupa 1 TB de espaço em disco (na verdade, seu celular pode não ter tanto espaço).
|
||||
|
||||
Se você estiver realmente insatisfeito com o tamanho deste arquivo, você pode usar o comando `resize2fs -M` para torná-lo seu tamanho real, mas o módulo pode não funcionar corretamente neste momento e não forneceremos nenhum suporte para isso.
|
||||
|
||||
## Por que meu dispositivo mostra o tamanho do armazenamento errado?
|
||||
|
||||
Certos dispositivos usam métodos não padrão para calcular o tamanho do armazenamento do dispositivo, potencialmente levando a cálculos de armazenamento imprecisos em apps e menus do sistema, especialmente ao lidar com arquivos esparsos de 1 TB. Embora esse problema pareça ser específico para os dispositivos Samsung, afetando apenas apps e serviços da Samsung, é essencial observar que a discrepância está principalmente no tamanho total do armazenamento, e o cálculo do espaço livre permanece preciso.
|
||||
|
||||
@@ -40,6 +40,14 @@ A partir do Android 13, o kernel é construído pelo `bazel`:
|
||||
tools/bazel build --config=fast //common:kernel_aarch64_dist
|
||||
```
|
||||
|
||||
:::info INFORMAÇÕES
|
||||
Para alguns kernel do Android 14, para fazer o Wi-Fi/Bluetooth funcionar. Pode ser necessário remover todas as exportações protegidas por GKI:
|
||||
|
||||
```sh
|
||||
rm common/android/abi_gki_protected_exports_*
|
||||
```
|
||||
:::
|
||||
|
||||
## Construir o kernel com KernelSU
|
||||
|
||||
Se você conseguir construir o kernel com sucesso, então construir o KernelSU é muito fácil. Selecione qualquer um executado no diretório raiz de origem do kernel:
|
||||
|
||||
@@ -70,7 +70,7 @@ Se o `boot.img` do seu dispositivo usa um formato de compactação comumente usa
|
||||
|
||||
### Encontre o boot.img adequado
|
||||
|
||||
O KernelSU fornece um boot.img genérico para dispositivos GKI e você deve liberar o boot.img para a partição boot do dispositivo.
|
||||
O KernelSU fornece um boot.img genérico para dispositivos GKI e você deve flashar o boot.img para a partição boot do dispositivo.
|
||||
|
||||
Você pode baixar o boot.img em [GitHub Release](https://github.com/tiann/KernelSU/releases), por favor, observe que você deve usar a versão correta do boot.img. Se você não sabe qual arquivo baixar, leia atentamente a descrição de [KMI](#kmi) e [Nível do patch de segurança](#security-patch-level) neste documento.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ KernelSU fornece uma biblioteca JavaScript e [publica-a no npm](https://www.npmj
|
||||
|
||||
Por exemplo, você pode executar um comando shell para obter uma configuração específica ou modificar uma propriedade:
|
||||
|
||||
```javascript
|
||||
```JavaScript
|
||||
import { exec } from 'kernelsu';
|
||||
|
||||
const { errno, stdout } = exec("getprop ro.product.model");
|
||||
|
||||
@@ -23,6 +23,6 @@ features:
|
||||
- title: Controle de acesso root
|
||||
details: Somente apps permitidos podem acessar ou ver su, todos os outros apps não estão cientes disso.
|
||||
- title: Privilégios de root personalizáveis
|
||||
details: KernelSU permite a personalização de uid, gid, grupos, capacidades e regras SELinux do su, bloqueando privilégios de root.
|
||||
details: KernelSU permite a personalização de su, uid, gid, grupos, capacidades e regras SELinux, bloqueando privilégios de root.
|
||||
- title: Módulos
|
||||
details: Os módulos podem modificar /system sem sistema usando OverlayFS permitindo uma grande potência.
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
"context":"u:r:su:s0",
|
||||
"namespace":"INHERITED",
|
||||
"locales": {
|
||||
"zh_TW": {
|
||||
"name": "Adaway Root",
|
||||
"description": "僅允許 Adaway 修改 hosts 和執行 Web 伺服器的必要權限"
|
||||
},
|
||||
"bn": {
|
||||
"name": "অ্যাডঅ্যাওয়ে রুট",
|
||||
"description": "অ্যাডঅ্যাওয়ে সিস্টেমের হোস্ট ফাইল পরিবর্তন এবং ওয়েবসার্ভার চালু করতে কমপক্ষে যা অনুমতি লাগে।"
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
"name": "Adb 模版",
|
||||
"description": "大多数使用 ADB 权限应用所需要的最小权限"
|
||||
},
|
||||
"zh_TW": {
|
||||
"name": "Adb",
|
||||
"description": "大多數使用 ADB 權限應用程式所需要的最低權限"
|
||||
},
|
||||
"tr": {
|
||||
"name": "Adb",
|
||||
"description": "ADB ayrıcalığını kullanan çoğu uygulama için gereken minimum kurallar."
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
"name": "西米露Cemiuiler",
|
||||
"description": "授予Cemiuiler能正常工作——重启作用域应用的最小限度权限。"
|
||||
},
|
||||
"zh_TW": {
|
||||
"name": "Cemiuiler",
|
||||
"description": "授予Cemiuiler能正常運作-重啟作用域應用程式的最低權限"
|
||||
},
|
||||
"tr": {
|
||||
"name": "Cemiuiler",
|
||||
"description": "Cemiuiler uygulamasına düzgün çalışması için minimum izinleri verin."
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
"name": "无能的 Root",
|
||||
"description": "无任何权能的 root 用户。"
|
||||
},
|
||||
"zh_TW": {
|
||||
"name": "無能的 Root",
|
||||
"description": "無任何權限的 root"
|
||||
},
|
||||
"tr": {
|
||||
"name": "Yetersiz root",
|
||||
"description": "Herhangi bir özelliği olmayan yetersiz bir root kullanıcısı."
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
"context":"u:r:su:s0",
|
||||
"namespace":"INHERITED",
|
||||
"locales": {
|
||||
"zh_TW": {
|
||||
"name": "核心管理器",
|
||||
"description": "常用於 FKM 和 SmartPack 等核心管理器"
|
||||
},
|
||||
"in": {
|
||||
"name": "Kernel Manager",
|
||||
"description": "Umumnya digunakan pada Kernel manager seperti FKM dan SmartPack."
|
||||
|
||||
22
website/docs/public/templates/nethunter.root
Normal file
22
website/docs/public/templates/nethunter.root
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id":"nethunter.root",
|
||||
"name":"Kali NetHunter",
|
||||
"author":"cachiusa",
|
||||
"description":"Required permissions for Kali NetHunter app to chroot",
|
||||
"namespace":"INHERITED",
|
||||
"uid":0,
|
||||
"gid":0,
|
||||
"groups":[
|
||||
"ROOT"
|
||||
],
|
||||
"capabilities":[
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_DAC_READ_SEARCH",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_SYS_PTRACE",
|
||||
"CAP_SYS_ADMIN"
|
||||
"CAP_SETGID",
|
||||
],
|
||||
"context":"u:r:su:s0",
|
||||
"rules":[""]
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
"context":"u:r:su:s0",
|
||||
"namespace":"INHERITED",
|
||||
"locales": {
|
||||
"zh_TW": {
|
||||
"name": "Root Explorer",
|
||||
"description": "具有根目錄讀寫權限的檔案管理器"
|
||||
},
|
||||
"in": {
|
||||
"name": "Root Explorer",
|
||||
"description": "File manager dengan kemampuan Root."
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
"context":"u:r:su:s0",
|
||||
"namespace":"INHERITED",
|
||||
"locales": {
|
||||
"zh_TW": {
|
||||
"name": "Shizuku",
|
||||
"description": "只有啟動 Shizuku 服務所需的權限"
|
||||
},
|
||||
"bn": {
|
||||
"name": "শিজুকু সার্ভিস",
|
||||
"description": "শিজুকু চালানোর জন্য শুধু যে অনুমতি গুলি লাগে।"
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
"name": "Android 系统",
|
||||
"description": "Android 系统运行的权限级别,但没有任何权能。"
|
||||
},
|
||||
"zh_TW": {
|
||||
"name": "Android 系統",
|
||||
"description": "Android 系統運作的權限級別,但沒有任何權能"
|
||||
},
|
||||
"tr": {
|
||||
"name": "Sistem",
|
||||
"description": "Android sisteminin herhangi bir yetenek olmadan çalıştığı izin düzeyi."
|
||||
|
||||
@@ -67,8 +67,8 @@ GKI1 跟 GKI2 完全是两个东西,所以你需要自行编译内核。
|
||||
|
||||
当然可以。但这个功能 KernelSU 没有内置,你可以安装这个 [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module)
|
||||
|
||||
## 为什么有个 1T 的超大文件?
|
||||
## 为什么有个 1 TB 的超大文件?
|
||||
|
||||
1T 大小的 `modules.img` 是一个磁盘镜像文件,**不要担心它的大小**,它是一种被称之为[稀疏文件](https://en.wikipedia.org/wiki/Sparse_file)的文件格式,它的实际大小只有你使用的模块的大小,并且你在删除模块后它会动态缩小;它并不实际占用 1T 大小的磁盘空间(实际上你手机可能并没有这么多空间)。
|
||||
1 TB 大小的 `modules.img` 是一个磁盘镜像文件,**不要担心它的大小**,它是一种被称之为[稀疏文件](https://en.wikipedia.org/wiki/Sparse_file)的文件格式,它的实际大小只有你使用的模块的大小,并且你在删除模块后它会动态缩小;它并不实际占用 1 TB 大小的磁盘空间(实际上你手机可能并没有这么多空间)。
|
||||
|
||||
如果你真的对这个文件的大小感到不爽,你可以使用 `resize2fs -M` 命令让它变成实际大小;但此时模块可能无法正常工作,我们也不会为此提供任何支持。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
KernelSU 的模块除了执行启动脚本和修改系统文件之外,还支持显示 UI 界面和与用户交互。
|
||||
|
||||
你可以通过任何 Web 技术编写 HTML + CSS + Javascript 页面,KernelSU 的管理器将通过WebView 显示这些页面。此外,KernelSU 还提供了一些用于与系统交互的 Javascript API,例如执行shell命令。
|
||||
你可以通过任何 Web 技术编写 HTML + CSS + JavaScript 页面,KernelSU 的管理器将通过WebView 显示这些页面。此外,KernelSU 还提供了一些用于与系统交互的 JavaScript API,例如执行shell命令。
|
||||
|
||||
## WebUI 根目录
|
||||
|
||||
@@ -20,17 +20,17 @@ Web 资源文件应放置在模块根目录的 `webroot` 子目录中,并且
|
||||
安装模块时,KernelSU 会自动设置 `webroot` 目录的权限和 SELinux context,如果您不知道自己在做什么,请不要自行设置该目录的权限!
|
||||
:::
|
||||
|
||||
如果您的页面包含 css 和 javascript,您也需要将其放入此目录中。
|
||||
如果您的页面包含 CSS 和 JavaScript,您也需要将其放入此目录中。
|
||||
|
||||
## JavaScript API
|
||||
|
||||
如果只是一个显示页面,那它和普通网页没有什么区别。更重要的是,KernelSU 提供了一系列的系统API,可以让您实现模块特有的功能。
|
||||
|
||||
KernelSU 提供了一个 Javascript 库并[在 npm 上发布](https://www.npmjs.com/package/kernelsu),您可以在网页的 javascript 代码中使用它。
|
||||
KernelSU 提供了一个 JavaScript 库并[在 npm 上发布](https://www.npmjs.com/package/kernelsu),您可以在网页的 JavaScript 代码中使用它。
|
||||
|
||||
例如,您可以执行 shell 命令来获取特定配置或修改属性:
|
||||
|
||||
```javascript
|
||||
```JavaScript
|
||||
import { exec } from 'kernelsu';
|
||||
|
||||
const { errno, stdout } = await exec("getprop ro.product.model");
|
||||
|
||||
118
website/docs/zh_TW/guide/app-profile.md
Normal file
118
website/docs/zh_TW/guide/app-profile.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# App Profile
|
||||
|
||||
App Profile 是 KernelSU 提供的一種針對各種應用程式自訂其使用配置的機制。
|
||||
|
||||
對於授予了root 權限(也即可以使用 `su`)的應用程式來說,App Profile 也可以稱為Root Profile,它可以自訂 `su` 的 `uid`, `gid` , `groups` , ` capabilities` 以及 `SELinux context` 規則,從而限制 root 使用者的權限;例如可以針對防火牆應用程式僅授予網路權限,而不授予檔案存取權限,針對凍結類別應用程式僅授予 shell 權限而不是直接給 root ;透過最小化權限原則**把權力關進籠子裡**。
|
||||
|
||||
對於沒有被授予 root 權限的普通應用,App Profile 可以控制核心以及模組系統對此應用的行為;例如是否需要針對此應用程式卸載模組造成的修改等。核心和模組系統可以透過此配置決定是否要做一些類似「隱藏痕跡」類別的操作。
|
||||
|
||||
## Root Profile
|
||||
|
||||
### UID、GID 和 groups
|
||||
|
||||
Linux 系統中有使用者和群組兩個概念。每個使用者都有一個使用者 ID(UID),一個使用者可以屬於多個群組,每個群組也有群組 ID(GID)。此 ID 用於識別系統的使用者並確定使用者可以存取哪些系統資源。
|
||||
|
||||
UID 為 0 的使用者稱為 root 使用者,GID 為 0 的群組稱為 root 群組;root 使用者群組通常擁有系統的最高權限。
|
||||
|
||||
對於 Android 系統來說,每個應用程式都是一個單獨的使用者(不考慮 share uid 的情況),擁有一個唯一的 UID。例如 `0` 是 root 使用者,`1000` 是 `system`,`2000` 是 ADB shell,10000-19999 的是一般使用者。
|
||||
|
||||
:::info
|
||||
此處的 UID 跟 Android 系統的多使用者,或者說工作資料(Work Profile),不是概念。工作資料實際上是對 UID 進行分片實現的,例如 10000-19999 是主使用者,110000-119999 是工作資料;他們中的任何一個普通應用都擁有自己獨有的 UID。
|
||||
:::
|
||||
|
||||
每一個應用程式可以有若干個群組,GID 使其主要的群組,通常與 UID 一致;其他的群組稱為補充群組(groups)。某些權限是透過群組控制的,例如網路訪問,藍牙等。
|
||||
|
||||
例如,如果我們在 ADB shell 中執行 `id` 指令,會得到以下輸出:
|
||||
|
||||
```sh
|
||||
oriole:/ $ id
|
||||
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),1078(ext_data_ww) (ext_obb_rw),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid),3012(readreadtracefs:s05:
|
||||
```
|
||||
|
||||
其中,UID 為`2000`,GID 也即主要組ID 也為`2000`;除此之外它還在許多補充組裡面,例如`inet` 組代表可以創建`AF_INET` 和`AF_INET6` 的socket(存取網路),`sdcard_rw` 代表可以讀寫sdcard 等。
|
||||
|
||||
KernelSU 的 Root Profile 可以自訂執行 `su` 後 root 程式的 UID, GID 和 groups。例如,你可以設定某個 root 應用程式的Root Profile 其UID 為`2000`,這表示此應用程式在使用`su` 的時候,它的實際權限是ADB Shell 等級;你可以去掉groups 中的`inet` ,這樣這個`su` 就無法存取網路。
|
||||
|
||||
:::tip 注意
|
||||
App Profile 只是控制 root 應用程式使用 `su` 後的權限,它並非控制應用程式本身的權限!如果應用程式本身申請了網路存取權限,那麼它即使不使用 `su` 也可以存取網路;為 `su` 去掉 `inet` 群組只是讓 `su` 無法存取網路。
|
||||
:::
|
||||
|
||||
與應用程式透過 `su` 主動切換使用者或群組不同,Root Profile 是在核心中強制實施的,不依賴 root 應用程式的自覺行為,`su` 權限的授予完全取決於使用者而非開發者。
|
||||
|
||||
### Capabilities
|
||||
|
||||
Capabilities 是 Linux 的一種分權機制。
|
||||
|
||||
傳統的 UNIX 系統為了執行權限檢查,將流程分為兩類:特權程式(其有效使用者 ID 為 0,稱為超級使用者或 root)和非特權程式(其有效 UID 為非零)。特權程式會繞過所有核心權限檢查,而非特權程式則根據其憑證(通常是有效UID、有效GID和補充群組清單)進行完整的權限檢查。
|
||||
|
||||
從 Linux 2.2開始,Linux 將傳統上與超級使用者關聯的特權分解為獨立的單元,稱為 Capabilities(有的也翻譯為「權能」),它們可以獨立啟用和停用。
|
||||
|
||||
每一個 Capability 代表一個或一類權限。例如 `CAP_DAC_READ_SEARCH` 就代表是否有能力繞過檔案讀取權限檢查和目錄讀取和執行權限檢查。如果一個有效 UID 為 `0` 的使用者(root 使用者)沒有 `CAP_DAC_READ_SEARCH` 或更高 Capalities,這表示即使它是 root 也不能隨意讀取檔案。
|
||||
|
||||
KernelSU 的 Root Profile 可以自訂執行 `su` 後 root 程式的 Capabilities,從而實現只授予「部分 root 權限」。與上面介紹的UID, GID 不同,某些 root 應用就是需要 `su` 後 UID 是 `0`,此時我們可以透過限制這個 UID 為 `0` 的 root 使用者的 Capabilities,就可以限制它能夠執行的操作。
|
||||
|
||||
:::tip 強烈建議
|
||||
Linux 系統關於 Capability 的[官方文件](https://man7.org/linux/man-pages/man7/capabilities.7.html),解釋了每一項Capability 所代表的能力,寫的非常詳細,如果你想要自訂Capabilities,請務必先閱讀此文件。
|
||||
:::
|
||||
|
||||
### SELinux
|
||||
|
||||
SELinux 是一種強大的強制權限存取控制(MAC)機制。它按照**預設拒絕**的原則運作:任何未經明確允許的行為都會被拒絕。
|
||||
|
||||
SELinux 可依兩種全域模式運作:
|
||||
|
||||
1. 寬容模式:權限拒絕事件會被記錄下來,但不會被強制執行。
|
||||
2. 強制模式:權限拒絕事件會被記錄下來**並且**強制執行。
|
||||
|
||||
:::warning 警告
|
||||
現代的 Android 系統極度依賴 SELinux 來保障整個系統的安全性,我們強烈建議您不要使用任何以「寬容模式」運作的自訂系統,因為那樣與裸奔沒什麼區別。
|
||||
:::
|
||||
|
||||
SELinux 的完整概念比較複雜,我們這裡不打算講解它的具體運作方式,建議你先透過以下資料來了解其運作原理:
|
||||
|
||||
1. [wikipedia](https://en.wikipedia.org/wiki/Security-Enhanced_Linux)
|
||||
2. [Redhat: what-is-selinux](https://www.redhat.com/en/topics/linux/what-is-selinux)
|
||||
3. [ArchLinux: SELinux](https://wiki.archlinux.org/title/SELinux)
|
||||
|
||||
KernelSU 的 Root Profile 可以自訂執行 `su` 後 root 程式的 SELinux context,並且可以針對這個 context 設定特定的存取控制規則,從而更精細地控制 root 權限。
|
||||
|
||||
通常情況下,應用程式執行 `su` 後,會將進程切換到一個**不受任何限制** 的SELinux 域,例如`u:r:su:s0`,透過 Root Profile,我們可以將它切換到一個自訂的網域,例如 `u:r:app1:s0`,然後為這個網域制定一系列規則:
|
||||
|
||||
```sh
|
||||
type app1
|
||||
enforce app1
|
||||
typeattribute app1 mlstrustedsubject
|
||||
allow app1 * * *
|
||||
```
|
||||
|
||||
注意:此處的 `allow app1 * * *` 僅僅作為演示方便而使用,實際過程中不應使用這個規則,因為它跟 permissive 區別不大。
|
||||
|
||||
### 逃逸
|
||||
|
||||
如果 Root Profile 的配置不合理,那麼可能會發生逃逸的情況:Root Profile 的限制會意外失效。
|
||||
|
||||
例如,如果你為ADB shell 使用者設定允許root 權限(這是相當常見的情況);然後你給某個普通應用程式允許root 權限,但是配置它的root profile 中的UID 為2000(ADB shell 使用者的UID);那麼此時,這個App 可以透過執行兩次 `su` 來獲得完整的root 權限:
|
||||
|
||||
1. 第一次執行 `su`,由於 App Profile 強制生效,會正常切換到 UID 為 `2000(adb shell)` 而非 `0(root)`。
|
||||
2. 第二次執行 `su`,由於此時它 UID 是 `2000`,而你給 `2000(adb shell)` 配置了允許 root,它會獲得完整的 root 權限!
|
||||
|
||||
:::warning 注意
|
||||
這是完全符合預期的行為,並非 BUG!因此我們建議:
|
||||
|
||||
如果你的確需要給 adb 授予 root 權限(例如你是開發者),那麼不建議你在配置 Root Profile 的時候將 UID 改成 `2000`,用 `1000(system)` 會更好。
|
||||
:::
|
||||
|
||||
## Non Root Profile
|
||||
|
||||
### 卸載模組
|
||||
|
||||
KernelSU 提供了一種 systemless 的方式來修改系統分區,這是透過掛載 overlayfs 來實現的。但有些情況下,App 可能會對這種行為比較敏感;因此,我們可以透過設定「卸載模組」來卸載掛載在這些應用程式上的模組。
|
||||
|
||||
另外,KernelSU 管理器的設定介面還提供了一個「預設卸載模組」的開關,這個開關預設是**開啟**的,這表示**如果不對應用程式做額外的設定**,預設情況下 KernelSU 或某些模組會對此應用程式執行卸載操作。當然,如果你不喜歡這個設定或這個設定會影響某些 App,你可以有以下選擇:
|
||||
|
||||
1. 保持「預設卸載模組」的開關,然後針對不需要「卸載模組」的應用程式進行單獨的設置,在 App Profile 中關閉「卸載模組」;(相當於「白名單」)。
|
||||
2. 關閉「預設卸載模組」的開關,然後針對需要「卸載模組」的應用程式進行單獨的設置,在 App Profile 中開啟「卸載模組」;(相當於「黑名單」)。
|
||||
|
||||
:::info
|
||||
KernelSU 在 5.10 及以上內核上,內核會執行“卸載模組”的操作;但在 5.10 以下的設備上,這個開關僅僅是一個“設定”,KernelSU 本身不會做任何動作,一些模組(如 Zygisksu 會透過這個模組決定是否需要卸載)
|
||||
:::
|
||||
@@ -1,4 +1,4 @@
|
||||
# KernelSU 模組與 Magisk 的差異 {#title}
|
||||
# KernelSU 與 Magisk 的差異 {#title}
|
||||
|
||||
儘管 KernelSU 模組和 Magisk 模組之間有許多相似之處,但由於它們完全不同的實作機制,不可避免地存在一些差異;如果您想讓您的模組同時在 Magisk 和 KernelSU 上運作,那麼您必須瞭解這些差異。
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
- 模組檔案格式:都以 Zip 的格式組織模組,並且模組的格式幾乎相同
|
||||
- 模組安裝目錄:都位於 `/data/adb/modules`
|
||||
- Systemless:都支援透過模組的形式以 systemless 修改 /system
|
||||
- `post-fs-data.sh`:執行時間和語義完全相同
|
||||
- `service.sh`:執行時間和語義完全相同
|
||||
- Systemless:都支援通過模組以無系統修改的方式來更改 `/system`
|
||||
- `post-fs-data.sh`:執行階段和語義完全相同
|
||||
- `service.sh`:執行階段和語義完全相同
|
||||
- `system.prop`:完全相同
|
||||
- `sepolicy.rule`:完全相同
|
||||
- BusyBox:指令碼在 BusyBox 中以「獨立模式」執行
|
||||
@@ -24,3 +24,5 @@
|
||||
3. KernelSU 模組取代或刪除檔案與 Magisk 完全不同。KernelSU 不支援 `.replace` 方法,相反,您需要透過 `mknod filename c 0 0` 建立相同名稱的資料夾以刪除對應檔案。
|
||||
4. BusyBox 的目錄不同;KernelSU 內建的 BusyBox 在 `/data/adb/ksu/bin/busybox` 而 Magisk 在 `/data/adb/magisk/busybox`;**注意此為 KernelSU 內部行為,未來可能會變更!**
|
||||
5. KernelSU 不支援 `.replace` 檔案;但 KernelSU 支援 `REPLACE` 和 `REMOVE` 變數以移除或取代檔案 (資料夾)。
|
||||
6. KernelSU 新增了 `boot-completed` 階段以在啟動完成時執行一些腳本。
|
||||
7. KernelSU 新增了 `post-mount` 階段,以便在掛載 overlayfs 後執行一些腳本
|
||||
|
||||
@@ -55,13 +55,26 @@ KernelSU 的模組系統與 Magisk 的 magic mount 存在衝突,如果在 Kern
|
||||
|
||||
核心版本與 Android 版本無關,如果您要刷新 KernelSU,請一律使用**核心版本**而非 Android 版本,如果你為 "android12-5.10" 的裝置刷新 Android 13 的核心,等候您的將會是開機迴圈。
|
||||
|
||||
## 我是 GKI1.0,能用 KernelSU 嗎?
|
||||
|
||||
GKI1 與 GKI2 完全不同,所以您需要自行編譯核心。
|
||||
|
||||
## KernelSU 支援 --mount-master/全域掛接命名空間嗎?
|
||||
|
||||
目前沒有 (未來可能會支援),但實際上有很多種方法手動進入全域命名空間,無需 Su 內建支援,比如:
|
||||
目前沒有 (未來可能會支援),但實際上有很多種方法手動進入全域命名空間,無需 `su` 內建支援,比如:
|
||||
|
||||
1. `nsenter -t 1 -m sh` 可以取得一個全域 mount namespace 的 shell.
|
||||
2. 在您要執行的命令前新增 `nsenter --mount=/proc/1/ns/mnt` 即可使此命令在全域 mount namespace 下執行。KernelSU 本身也使用了 [這種方法](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt#L115)
|
||||
|
||||
## 我是 GKI1.0,能用 KernelSU 嗎?
|
||||
## KernelSU 可以修改 Hosts 嗎? 我要怎麼使用 AdAway?
|
||||
當然。但是 KernelSU 沒有內建的 Hosts 支持,您可以安裝 [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) 來做到這一點。
|
||||
|
||||
GKI1 與 GKI2 完全不同,所以您需要自行編譯核心。
|
||||
## 為什麼會有 1TB 的龐大檔案?
|
||||
|
||||
1 TB 大小的 `modules.img` 是一個磁碟映像文件,不用擔心它的大小,它是一種特殊類型的文件,稱為稀疏檔案,它的實際大小只有你使用的模組的大小,並且在你刪除模組後會動態縮小;實際上並沒有佔用1TB的磁碟空間(實際上你的手機可能並沒有那麼多空間)。
|
||||
|
||||
如果你確實對檔案的大小不滿意,可以使用 `resize2fs -M` 指令將其調整為實際大小;但此時模組可能無法正常運作,我們不會這種情況提供任何支援。
|
||||
|
||||
## 為什麼我的設備顯示錯誤的儲存空間大小?
|
||||
|
||||
某些裝置使用非正規方法來計算裝置的儲存大小,可能會導致系統應用程式的儲存空間計算不準確,特別是在處理 1 TB 的稀疏檔案時。雖然這個問題似乎是三星設備特有的,僅影響三星應用程式和服務,但必須注意的是,差異主要在於總儲存大小,可用空間運算仍然準確。
|
||||
@@ -1,7 +1,7 @@
|
||||
# 隱藏功能
|
||||
|
||||
## ksurc
|
||||
## .ksurc
|
||||
|
||||
預設狀況下,`/system/bin/sh` 會載入 `/system/etc/mkshrc`。
|
||||
|
||||
可以透過建立 `/data/adb/ksu/.ksurc` 檔案來讓 Su 載入此檔案而非 `/system/etc/mkshrc`。
|
||||
可以透過建立 `/data/adb/ksu/.ksurc` 檔案來讓 `su` 載入此檔案而非 `/system/etc/mkshrc`。
|
||||
@@ -40,6 +40,14 @@ LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
|
||||
tools/bazel build --config=fast //common:kernel_aarch64_dist
|
||||
```
|
||||
|
||||
:::info
|
||||
對於某些 Android 14 內核,要使 Wi-Fi/藍牙正常工作,可能需要刪除所有受 GKI 保護的匯出:
|
||||
|
||||
```sh
|
||||
rm common/android/abi_gki_protected_exports_*
|
||||
```
|
||||
:::
|
||||
|
||||
## 使用 KernelSU 建置核心
|
||||
|
||||
如果您可以成功建置核心,那麼建置 KernelSU 就會非常輕鬆,依自己的需求在核心原始碼根目錄中執行以下任一命令:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user