diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 95e3839e..801c232c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -26,13 +26,3 @@ updates: maven: patterns: - "*" - - package-ecosystem: npm - directory: website - schedule: - interval: daily - allow: - - dependency-type: "all" - groups: - npm: - patterns: - - "*" diff --git a/.github/manifests/android-14-avd_x86_64.xml b/.github/manifests/android-14-avd_x86_64.xml deleted file mode 100644 index db2a6c06..00000000 --- a/.github/manifests/android-14-avd_x86_64.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.github/manifests/android-15-avd_aarch64.xml b/.github/manifests/android-15-avd_aarch64.xml deleted file mode 100644 index e3a5e086..00000000 --- a/.github/manifests/android-15-avd_aarch64.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.github/manifests/android-15-avd_x86_64.xml b/.github/manifests/android-15-avd_x86_64.xml deleted file mode 100644 index 0504d8a5..00000000 --- a/.github/manifests/android-15-avd_x86_64.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.github/workflows/avd-kernel.yml b/.github/workflows/avd-kernel.yml index a8ef7288..e211c1e0 100644 --- a/.github/workflows/avd-kernel.yml +++ b/.github/workflows/avd-kernel.yml @@ -1,137 +1,137 @@ -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-22.04 - 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 - DRIVER_KCONFIG=$GKI_ROOT/common/drivers/Kconfig - grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE" - grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" - 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=fast --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: 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-22.04 + 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 + DRIVER_KCONFIG=$GKI_ROOT/common/drivers/Kconfig + grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE" + grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" + 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=fast --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 }}" diff --git a/.github/workflows/build-debug-kernel.yml b/.github/workflows/build-debug-kernel.yml deleted file mode 100644 index fd9b3d9f..00000000 --- a/.github/workflows/build-debug-kernel.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Build debug kernel -on: - workflow_dispatch: - -jobs: - build-debug-kernel-a12: - uses: ./.github/workflows/gki-kernel.yml - with: - version: android12-5.10 - version_name: android12-5.10.226 - tag: android12-5.10-2024-11 - os_patch_level: 2024-11 - patch_path: "5.10" - debug: true - build-debug-kernel-a13: - strategy: - matrix: - include: - - version: "5.10" - sub_level: 223 - os_patch_level: 2024-11 - - version: "5.15" - sub_level: 167 - os_patch_level: 2024-11 - uses: ./.github/workflows/gki-kernel.yml - with: - version: android13-${{ matrix.version }} - version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }} - tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }} - patch_path: ${{ matrix.version }} - debug: true - build-debug-kernel-a14: - strategy: - matrix: - include: - - version: "5.15" - sub_level: 167 - os_patch_level: 2024-11 - - version: "6.1" - sub_level: 115 - os_patch_level: 2024-12 - 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 }} - patch_path: ${{ matrix.version }} - debug: true - build-debug-kernel-a15: - strategy: - matrix: - include: - - version: "6.6" - sub_level: 57 - os_patch_level: 2024-12 - uses: ./.github/workflows/gki-kernel.yml - with: - version: android15-${{ matrix.version }} - version_name: android15-${{ matrix.version }}.${{ matrix.sub_level }} - tag: android15-${{ matrix.version }}-${{ matrix.os_patch_level }} - patch_path: ${{ matrix.version }} - debug: true \ No newline at end of file diff --git a/.github/workflows/build-kernel-a12.yml b/.github/workflows/build-kernel-a12.yml deleted file mode 100644 index 4e1ea397..00000000 --- a/.github/workflows/build-kernel-a12.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: Build Kernel - Android 12 -on: - push: - branches: ["main", "ci", "checkci"] - paths: - - ".github/workflows/build-kernel-a12.yml" - - ".github/workflows/gki-kernel.yml" - - ".github/scripts/build_a12.sh" - - "kernel/**" - pull_request: - branches: ["main"] - paths: - - ".github/workflows/build-kernel-a12.yml" - - ".github/workflows/gki-kernel.yml" - - ".github/scripts/build-a12.sh" - - "kernel/**" - workflow_call: -jobs: - build-kernel: - if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci' - strategy: - matrix: - include: - - sub_level: 209 - os_patch_level: 2024-05 - - sub_level: 218 - os_patch_level: 2024-08 - - sub_level: 226 - os_patch_level: 2024-11 - uses: ./.github/workflows/gki-kernel.yml - secrets: inherit - with: - version: android12-5.10 - version_name: android12-5.10.${{ matrix.sub_level }} - tag: android12-5.10-${{ matrix.os_patch_level }} - os_patch_level: ${{ matrix.os_patch_level }} - patch_path: "5.10" - - upload-artifacts: - needs: build-kernel - runs-on: ubuntu-latest - if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }} - 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 }} - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - - - uses: actions/checkout@v4 - with: - path: KernelSU - fetch-depth: 0 - - - name: List artifacts - run: | - tree - - - name: Download prebuilt toolchain - run: | - AOSP_MIRROR=https://android.googlesource.com - BRANCH=main-kernel-build-2024 - git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools - git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools - git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1 - pip3 install telethon - - - name: Set boot sign key - env: - BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }} - run: | - if [ ! -z "$BOOT_SIGN_KEY" ]; then - echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem - fi - - - name: Bot session cache - id: bot_session_cache - uses: actions/cache@v4 - if: false - with: - path: scripts/ksubot.session - key: ${{ runner.os }}-bot-session - - - name: Build boot images - run: | - export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool - export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip - export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4 - export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py - export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py - cd $GITHUB_WORKSPACE/KernelSU - export VERSION=$(($(git rev-list --count HEAD) + 10200)) - echo "VERSION: $VERSION" - cd - - bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a12.sh - - - name: Display structure of boot files - run: ls -R - - - name: Upload images artifact - uses: actions/upload-artifact@v4 - with: - name: boot-images-android12 - path: Image-android12*/*.img.gz - - check-build-kernel: - if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci' - uses: ./.github/workflows/gki-kernel.yml - with: - version: android12-5.10 - version_name: android12-5.10.223 - tag: android12-5.10-2024-11 - os_patch_level: 2024-11 - patch_path: "5.10" \ No newline at end of file diff --git a/.github/workflows/build-kernel-a13.yml b/.github/workflows/build-kernel-a13.yml deleted file mode 100644 index d9e1c379..00000000 --- a/.github/workflows/build-kernel-a13.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: Build Kernel - Android 13 -on: - push: - branches: ["main", "ci", "checkci"] - paths: - - ".github/workflows/build-kernel-a13.yml" - - ".github/workflows/gki-kernel.yml" - - ".github/scripts/build_a13.sh" - - "kernel/**" - pull_request: - branches: ["main"] - paths: - - ".github/workflows/build-kernel-a13.yml" - - ".github/workflows/gki-kernel.yml" - - ".github/scripts/build-a13.sh" - - "kernel/**" - workflow_call: -jobs: - build-kernel: - if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci' - strategy: - matrix: - include: - - version: "5.10" - sub_level: 209 - os_patch_level: 2024-05 - - version: "5.10" - sub_level: 210 - os_patch_level: 2024-06 - - version: "5.10" - sub_level: 214 - os_patch_level: 2024-07 - - version: "5.10" - sub_level: 218 - os_patch_level: 2024-08 - - version: "5.10" - sub_level: 223 - os_patch_level: 2024-11 - - version: "5.15" - sub_level: 148 - os_patch_level: 2024-05 - - version: "5.15" - sub_level: 149 - os_patch_level: 2024-07 - - version: "5.15" - sub_level: 151 - os_patch_level: 2024-08 - - version: "5.15" - sub_level: 153 - os_patch_level: 2024-09 - - version: "5.15" - sub_level: 167 - os_patch_level: 2024-11 - uses: ./.github/workflows/gki-kernel.yml - secrets: inherit - with: - version: android13-${{ matrix.version }} - 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 }} - - upload-artifacts: - needs: build-kernel - runs-on: ubuntu-latest - if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }} - 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 }} - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - - - uses: actions/checkout@v4 - with: - path: KernelSU - fetch-depth: 0 - - - name: List artifacts - run: | - tree - - - name: Download prebuilt toolchain - run: | - AOSP_MIRROR=https://android.googlesource.com - BRANCH=main-kernel-build-2024 - git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools - git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools - git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1 - pip3 install telethon - - - name: Set boot sign key - env: - BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }} - run: | - if [ ! -z "$BOOT_SIGN_KEY" ]; then - echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem - fi - - - name: Bot session cache - id: bot_session_cache - uses: actions/cache@v4 - if: false - with: - path: scripts/ksubot.session - key: ${{ runner.os }}-bot-session - - - name: Build boot images - run: | - export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool - export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip - export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4 - export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py - export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py - cd $GITHUB_WORKSPACE/KernelSU - export VERSION=$(($(git rev-list --count HEAD) + 10200)) - echo "VERSION: $VERSION" - cd - - bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a13.sh - - - name: Display structure of boot files - run: ls -R - - - name: Upload images artifact - uses: actions/upload-artifact@v4 - with: - name: boot-images-android13 - path: Image-android13*/*.img.gz - - check-build-kernel: - if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci' - strategy: - matrix: - include: - - version: "5.10" - sub_level: 223 - os_patch_level: 2024-11 - - version: "5.15" - sub_level: 167 - os_patch_level: 2024-11 - uses: ./.github/workflows/gki-kernel.yml - with: - version: android13-${{ matrix.version }} - 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 }} \ No newline at end of file diff --git a/.github/workflows/build-kernel-a14.yml b/.github/workflows/build-kernel-a14.yml deleted file mode 100644 index a65873e2..00000000 --- a/.github/workflows/build-kernel-a14.yml +++ /dev/null @@ -1,163 +0,0 @@ -name: Build Kernel - Android 14 -on: - push: - branches: ["main", "ci", "checkci"] - paths: - - ".github/workflows/build-kernel-a14.yml" - - ".github/workflows/gki-kernel.yml" - - ".github/scripts/build_a13.sh" - - "kernel/**" - pull_request: - branches: ["main"] - paths: - - ".github/workflows/build-kernel-a14.yml" - - ".github/workflows/gki-kernel.yml" - - ".github/scripts/build-a13.sh" - - "kernel/**" - workflow_call: -jobs: - build-kernel: - if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci' - strategy: - matrix: - include: - - version: "5.15" - sub_level: 148 - os_patch_level: 2024-05 - - version: "5.15" - sub_level: 149 - os_patch_level: 2024-06 - - version: "5.15" - sub_level: 153 - os_patch_level: 2024-07 - - version: "5.15" - sub_level: 158 - os_patch_level: 2024-08 - - version: "5.15" - sub_level: 164 - os_patch_level: 2024-09 - - version: "5.15" - sub_level: 167 - os_patch_level: 2024-11 - - version: "6.1" - sub_level: 75 - os_patch_level: 2024-05 - - version: "6.1" - sub_level: 78 - os_patch_level: 2024-06 - - version: "6.1" - sub_level: 84 - os_patch_level: 2024-07 - - version: "6.1" - sub_level: 90 - os_patch_level: 2024-08 - - version: "6.1" - sub_level: 93 - os_patch_level: 2024-09 - - version: "6.1" - sub_level: 99 - os_patch_level: 2024-10 - - version: "6.1" - sub_level: 112 - os_patch_level: 2024-11 - - version: "6.1" - sub_level: 115 - os_patch_level: 2024-12 - uses: ./.github/workflows/gki-kernel.yml - secrets: inherit - 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 }} - - upload-artifacts: - needs: build-kernel - runs-on: ubuntu-latest - if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }} - 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 }} - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - - - uses: actions/checkout@v4 - with: - path: KernelSU - fetch-depth: 0 - - - name: List artifacts - run: | - tree - - - name: Download prebuilt toolchain - run: | - AOSP_MIRROR=https://android.googlesource.com - BRANCH=main-kernel-build-2024 - git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools - git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools - git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1 - pip3 install telethon - - - name: Set boot sign key - env: - BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }} - run: | - if [ ! -z "$BOOT_SIGN_KEY" ]; then - echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem - fi - - - name: Bot session cache - id: bot_session_cache - uses: actions/cache@v4 - if: false - with: - path: scripts/ksubot.session - key: ${{ runner.os }}-bot-session - - - name: Build boot images - run: | - export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool - export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip - export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4 - export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py - export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py - cd $GITHUB_WORKSPACE/KernelSU - export VERSION=$(($(git rev-list --count HEAD) + 10200)) - echo "VERSION: $VERSION" - cd - - bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a13.sh - - - name: Display structure of boot files - run: ls -R - - - name: Upload images artifact - uses: actions/upload-artifact@v4 - with: - name: boot-images-android14 - path: Image-android14*/*.img.gz - - check-build-kernel: - if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci' - strategy: - matrix: - include: - - version: "5.15" - sub_level: 167 - os_patch_level: 2024-11 - - version: "6.1" - sub_level: 115 - os_patch_level: 2024-12 - 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 }} \ No newline at end of file diff --git a/.github/workflows/build-kernel-a15.yml b/.github/workflows/build-kernel-a15.yml deleted file mode 100644 index 6c325395..00000000 --- a/.github/workflows/build-kernel-a15.yml +++ /dev/null @@ -1,133 +0,0 @@ -name: Build Kernel - Android 15 -on: - push: - branches: ["main", "ci", "checkci"] - paths: - - ".github/workflows/build-kernel-a15.yml" - - ".github/workflows/gki-kernel.yml" - - ".github/scripts/build_a13.sh" - - "kernel/**" - pull_request: - branches: ["main"] - paths: - - ".github/workflows/build-kernel-a15.yml" - - ".github/workflows/gki-kernel.yml" - - ".github/scripts/build-a13.sh" - - "kernel/**" - workflow_call: -jobs: - build-kernel: - if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci' - strategy: - matrix: - include: - - version: "6.6" - sub_level: 30 - os_patch_level: 2024-08 - - version: "6.6" - sub_level: 46 - os_patch_level: 2024-09 - - version: "6.6" - sub_level: 50 - os_patch_level: 2024-10 - - version: "6.6" - sub_level: 56 - os_patch_level: 2024-11 - - version: "6.6" - sub_level: 57 - os_patch_level: 2024-12 - uses: ./.github/workflows/gki-kernel.yml - secrets: inherit - with: - version: android15-${{ matrix.version }} - version_name: android15-${{ matrix.version }}.${{ matrix.sub_level }} - tag: android15-${{ matrix.version }}-${{ matrix.os_patch_level }} - os_patch_level: ${{ matrix.os_patch_level }} - patch_path: ${{ matrix.version }} - - upload-artifacts: - needs: build-kernel - runs-on: ubuntu-latest - if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }} - 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 }} - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - - - uses: actions/checkout@v4 - with: - path: KernelSU - fetch-depth: 0 - - - name: List artifacts - run: | - tree - - - name: Download prebuilt toolchain - run: | - AOSP_MIRROR=https://android.googlesource.com - BRANCH=main-kernel-build-2024 - git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools - git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools - git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1 - pip3 install telethon - - - name: Set boot sign key - env: - BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }} - run: | - if [ ! -z "$BOOT_SIGN_KEY" ]; then - echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem - fi - - - name: Bot session cache - id: bot_session_cache - uses: actions/cache@v4 - if: false - with: - path: scripts/ksubot.session - key: ${{ runner.os }}-bot-session - - - name: Build boot images - run: | - export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool - export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip - export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4 - export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py - export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py - cd $GITHUB_WORKSPACE/KernelSU - export VERSION=$(($(git rev-list --count HEAD) + 10200)) - echo "VERSION: $VERSION" - cd - - bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a13.sh - - - name: Display structure of boot files - run: ls -R - - - name: Upload images artifact - uses: actions/upload-artifact@v4 - with: - name: boot-images-android15 - path: Image-android15*/*.img.gz - - check-build-kernel: - if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci' - strategy: - matrix: - include: - - version: "6.6" - sub_level: 57 - os_patch_level: 2024-12 - uses: ./.github/workflows/gki-kernel.yml - with: - version: android15-${{ matrix.version }} - version_name: android15-${{ matrix.version }}.${{ matrix.sub_level }} - tag: android15-${{ matrix.version }}-${{ matrix.os_patch_level }} - os_patch_level: ${{ matrix.os_patch_level }} - patch_path: ${{ matrix.version }} \ No newline at end of file diff --git a/.github/workflows/build-kernel-arcvm.yml b/.github/workflows/build-kernel-arcvm.yml deleted file mode 100644 index 9dbe8de1..00000000 --- a/.github/workflows/build-kernel-arcvm.yml +++ /dev/null @@ -1,137 +0,0 @@ -name: Build Kernel - ChromeOS ARCVM -on: - push: - branches: ["main", "ci", "checkci"] - paths: - - ".github/workflows/build-kernel-arcvm.yml" - - "kernel/**" - pull_request: - branches: ["main"] - paths: - - ".github/workflows/build-kernel-arcvm.yml" - - "kernel/**" - workflow_call: - workflow_dispatch: - -env: - git_tag: chromeos-5.10-arcvm - -jobs: - build: - if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && !github.event.pull_request.draft) - strategy: - matrix: - include: - - arch: x86_64 - kernel_image_name: bzImage - build_config: build.config.gki.x86_64 - defconfig: x86_64_arcvm_defconfig - - arch: arm64 - kernel_image_name: Image - build_config: build.config.gki.aarch64 - defconfig: arm64_arcvm_defconfig - - name: Build ChromeOS ARCVM kernel - runs-on: ubuntu-20.04 - env: - LTO: thin - ROOT_DIR: / - KERNEL_DIR: ${{ github.workspace }}/kernel - - steps: - - name: Install Build Tools - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends bc \ - bison build-essential ca-certificates flex git gnupg \ - libelf-dev libssl-dev lsb-release software-properties-common wget \ - libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget gzip \ - rsync python3 device-tree-compiler - - sudo ln -s --force python3 /usr/bin/python - - export LLVM_VERSION=12 - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh $LLVM_VERSION - rm ./llvm.sh - sudo ln -s --force /usr/bin/clang-$LLVM_VERSION /usr/bin/clang - sudo ln -s --force /usr/bin/ld.lld-$LLVM_VERSION /usr/bin/ld.lld - sudo ln -s --force /usr/bin/llvm-objdump-$LLVM_VERSION /usr/bin/llvm-objdump - sudo ln -s --force /usr/bin/llvm-ar-$LLVM_VERSION /usr/bin/llvm-ar - sudo ln -s --force /usr/bin/llvm-nm-$LLVM_VERSION /usr/bin/llvm-nm - sudo ln -s --force /usr/bin/llvm-strip-$LLVM_VERSION /usr/bin/llvm-strip - sudo ln -s --force /usr/bin/llvm-objcopy-$LLVM_VERSION /usr/bin/llvm-objcopy - sudo ln -s --force /usr/bin/llvm-readelf-$LLVM_VERSION /usr/bin/llvm-readelf - sudo ln -s --force /usr/bin/clang++-$LLVM_VERSION /usr/bin/clang++ - - - name: Checkout KernelSU - uses: actions/checkout@v4 - with: - path: KernelSU - fetch-depth: 0 - - - name: Setup kernel source - run: git clone https://chromium.googlesource.com/chromiumos/third_party/kernel.git -b ${{ env.git_tag }} --depth=1 - - - name: Extract version from Makefile - working-directory: kernel - run: | - VERSION=$(grep -E '^VERSION = ' Makefile | awk '{print $3}') - PATCHLEVEL=$(grep -E '^PATCHLEVEL = ' Makefile | awk '{print $3}') - SUBLEVEL=$(grep -E '^SUBLEVEL = ' Makefile | awk '{print $3}') - echo "ChromeOS ARCVM Linux kernel version: $VERSION.$PATCHLEVEL.$SUBLEVEL" - echo "version=$VERSION.$PATCHLEVEL.$SUBLEVEL" >> $GITHUB_ENV - - - name: Setup KernelSU - working-directory: kernel - run: | - echo "[+] KernelSU setup" - KERNEL_ROOT=$GITHUB_WORKSPACE/kernel - echo "[+] KERNEL_ROOT: $KERNEL_ROOT" - echo "[+] Copy KernelSU driver to $KERNEL_ROOT/drivers" - ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $KERNEL_ROOT/drivers/kernelsu - - echo "[+] Add KernelSU driver to Makefile" - DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile - DRIVER_KCONFIG=$KERNEL_ROOT/drivers/Kconfig - grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE" - grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" - - echo "[+] Apply KernelSU patches" - cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.10/*.patch || echo "[-] No patch found" - - echo "[+] Patch script/setlocalversion" - sed -i 's/-dirty//g' $KERNEL_ROOT/scripts/setlocalversion - - echo "[+] KernelSU setup done." - cd $GITHUB_WORKSPACE/KernelSU - KSU_VERSION=$(($(git rev-list --count HEAD) + 10200)) - echo "KernelSU version: $KSU_VERSION" - echo "kernelsu_version=$KSU_VERSION" >> $GITHUB_ENV - - - name: Build Kernel - working-directory: kernel - env: - KERNEL_IMAGE_NAME: ${{ matrix.kernel_image_name }} - ARCH: ${{ matrix.arch }} - run: | - set -a && . ${{ matrix.build_config }}; set +a - export DEFCONFIG=${{ matrix.defconfig }} - 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 - - make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} mrproper - make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} ${DEFCONFIG} < /dev/null - scripts/config --file .config -e LTO_CLANG -d LTO_NONE -e LTO_CLANG_THIN -d LTO_CLANG_FULL -e THINLTO - make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} -j$(nproc) ${KERNEL_IMAGE_NAME} modules prepare-objtool - ls -l -h ${PWD}/arch/${ARCH}/boot - echo "file_path=${PWD}/arch/${ARCH}/boot/${KERNEL_IMAGE_NAME}" >> $GITHUB_ENV - - - name: Upload kernel-ARCVM-${{ matrix.arch }}-${{ env.version }} - uses: actions/upload-artifact@v4 - with: - name: kernel-ARCVM-${{ matrix.arch }}-${{ env.version }} - path: "${{ env.file_path }}" diff --git a/.github/workflows/build-kernel-avd.yml b/.github/workflows/build-kernel-avd.yml deleted file mode 100644 index ca3ef004..00000000 --- a/.github/workflows/build-kernel-avd.yml +++ /dev/null @@ -1,70 +0,0 @@ -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: - inputs: - upload: - required: false - type: boolean - default: false - description: "Whether to upload to branch" -jobs: - build-kernel: - if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci' - uses: ./.github/workflows/avd-kernel.yml - secrets: inherit - strategy: - fail-fast: false - 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" - - version: "android-15-avd_x86_64" - manifest: "android-15-avd_x86_64.xml" - arch: "x86_64" - with: - version_name: ${{ matrix.version }} - manifest_name: ${{ matrix.manifest }} - arch: ${{ matrix.arch }} - debug: true - - push-to-branch: - needs: [build-kernel] - runs-on: ubuntu-latest - if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || inputs.upload - steps: - - name: Download all workflow run artifacts - uses: actions/download-artifact@v4 - with: - path: bin/ - merge-multiple: true - - name: Push to branch LKM - run: | - cd bin - git config --global init.defaultBranch avd - git init - git remote add origin https://${{ secrets.TOKEN }}@github.com/${{ github.repository }} - git config --local user.name "github-actions[bot]" - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - find . -type f - git add . - git commit -m "Upload AVD Kernel from ${{ github.sha }}" -m "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - git push --force --set-upstream origin avd diff --git a/.github/workflows/build-kernel-wsa.yml b/.github/workflows/build-kernel-wsa.yml deleted file mode 100644 index 9a313de9..00000000 --- a/.github/workflows/build-kernel-wsa.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Build Kernel - WSA -on: - push: - branches: ["main", "ci", "checkci"] - paths: - - ".github/workflows/build-kernel-wsa.yml" - - ".github/workflows/wsa-kernel.yml" - - "kernel/**" - pull_request: - branches: ["main"] - paths: - - ".github/workflows/build-kernel-wsa.yml" - - ".github/workflows/wsa-kernel.yml" - - "kernel/**" - workflow_call: - workflow_dispatch: - -jobs: - build: - if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci' - strategy: - matrix: - arch: [x86_64, arm64] - version: ["5.15.94.2", "5.15.104.1", "5.15.104.2", "5.15.104.3", "5.15.104.4"] - uses: ./.github/workflows/wsa-kernel.yml - with: - arch: ${{ matrix.arch }} - version: ${{ matrix.version }} - - check_build: - if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci' - uses: ./.github/workflows/wsa-kernel.yml - strategy: - matrix: - arch: [x86_64, arm64] - with: - arch: ${{ matrix.arch }} - version: "5.15.104.4" \ No newline at end of file diff --git a/.github/workflows/build-lkm.yml b/.github/workflows/build-lkm.yml index bfe0e904..96a0ea91 100644 --- a/.github/workflows/build-lkm.yml +++ b/.github/workflows/build-lkm.yml @@ -24,23 +24,23 @@ jobs: matrix: include: - version: "android12-5.10" - sub_level: 226 - os_patch_level: 2024-11 + sub_level: 233 + os_patch_level: 2025-02 - version: "android13-5.10" - sub_level: 223 - os_patch_level: 2024-11 + sub_level: 228 + os_patch_level: 2025-01 - version: "android13-5.15" - sub_level: 167 - os_patch_level: 2024-11 + sub_level: 170 + os_patch_level: 2025-01 - version: "android14-5.15" - sub_level: 167 - os_patch_level: 2024-11 + sub_level: 170 + os_patch_level: 2025-01 - version: "android14-6.1" - sub_level: 115 - os_patch_level: 2024-12 + sub_level: 124 + os_patch_level: 2025-02 - version: "android15-6.6" - sub_level: 57 - os_patch_level: 2024-12 + sub_level: 66 + os_patch_level: 2025-02 # uses: ./.github/workflows/gki-kernel-mock.yml when debugging uses: ./.github/workflows/gki-kernel.yml with: diff --git a/.github/workflows/build-manager.yml b/.github/workflows/build-manager.yml index 672a8b7a..06c73f41 100644 --- a/.github/workflows/build-manager.yml +++ b/.github/workflows/build-manager.yml @@ -6,7 +6,9 @@ on: paths: - '.github/workflows/build-manager.yml' - 'manager/**' + - 'kernel/**' - 'userspace/ksud/**' + - 'userspace/zakomksd/**' pull_request: branches: [ "main" ] paths: @@ -28,7 +30,6 @@ on: type: boolean default: false description: "Whether to upload lkm" - jobs: check-build-lkm: runs-on: ubuntu-latest @@ -47,7 +48,7 @@ jobs: cd tmp git config --global init.defaultBranch bot git config --global user.name 'Bot' - git config --global user.email 'bot@github.5ec1cff.io' + git config --global user.email 'bot@github.shirkneko.io' git init . git remote add origin https://github.com/${{ github.repository }} CURRENT_COMMIT="${{ github.event.head_commit.id }}" @@ -62,7 +63,7 @@ jobs: cd .. rm -rf tmp fi - if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/main' ]; then + if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/susfs' ]; then need_upload=true elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then need_upload="${{ inputs.upload_lkm }}" @@ -81,7 +82,19 @@ jobs: with: upload: ${{ needs.check-build-lkm.outputs.upload_lkm == 'true' }} secrets: inherit - + build-zakomksd: + if: ${{ always() }} + needs: [ check-build-lkm, build-lkm ] + strategy: + matrix: + include: + - target: aarch64-linux-android + os: ubuntu-latest + uses: ./.github/workflows/zakomksd.yml + with: + target: ${{ matrix.target }} + os: ${{ matrix.os }} + build-ksud: if: ${{ always() }} needs: [ check-build-lkm, build-lkm ] @@ -123,7 +136,7 @@ jobs: fi - name: Write key - if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }} + if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref == 'refs/heads/susfs' || github.ref_type == 'tag' }} run: | if [ ! -z "${{ secrets.KEYSTORE }}" ]; then { @@ -147,6 +160,12 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 + - name: Download arm64 zakomksd + uses: actions/download-artifact@v4 + with: + name: zakomksd-aarch64-linux-android + path: . + - name: Download arm64 ksud uses: actions/download-artifact@v4 with: @@ -163,8 +182,13 @@ jobs: run: | mkdir -p app/src/main/jniLibs/arm64-v8a mkdir -p app/src/main/jniLibs/x86_64 - cp -f ../aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud.so - cp -f ../x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud.so + cp -f ../aarch64-linux-android/release/zakomk ../manager/app/src/main/jniLibs/arm64-v8a/libzakomk.so + cp -f ../x86_64-linux-android/release/zakomk ../manager/app/src/main/jniLibs/x86_64/libzakomk.so + + - name: Copy zakomksd to app jniLibs + run: | + mkdir -p app/src/main/jniLibs/arm64-v8a + cp -f ../arm64-v8a/zakomksd ../manager/app/src/main/jniLibs/arm64-v8a/libzakomksd.so - name: Build with Gradle run: | @@ -176,7 +200,6 @@ jobs: } >> gradle.properties sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties ./gradlew clean assembleRelease - - name: Upload build artifact uses: actions/upload-artifact@v4 if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }} @@ -190,7 +213,7 @@ jobs: with: name: "mappings" path: "manager/app/build/outputs/mapping/release/" - + - name: Bot session cache if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true' id: bot_session_cache diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index a59d1d1d..bafb41a1 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -34,4 +34,4 @@ jobs: - name: Run clippy run: | cross clippy --manifest-path userspace/ksud/Cargo.toml --target aarch64-linux-android --release - cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android --release \ No newline at end of file + cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android --release diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 4174daf6..34961052 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -64,4 +64,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/gki-kernel.yml b/.github/workflows/gki-kernel.yml index 88082738..a9516bdf 100644 --- a/.github/workflows/gki-kernel.yml +++ b/.github/workflows/gki-kernel.yml @@ -103,7 +103,7 @@ jobs: 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 -b common-${{ inputs.tag }} --repo-rev=v2.16 + repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }} --repo-rev=v2.35 REMOTE_BRANCH=$(git ls-remote https://android.googlesource.com/kernel/common ${{ inputs.tag }}) DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml if grep -q deprecated <<< $REMOTE_BRANCH; then @@ -255,4 +255,4 @@ jobs: if: ${{ inputs.build_lkm == true }} with: name: ${{ inputs.version }}-lkm - path: ./output/*_kernelsu.ko \ No newline at end of file + path: ./output/*_kernelsu.ko diff --git a/.github/workflows/ksud.yml b/.github/workflows/ksud.yml index 7d1ea16c..65f52599 100644 --- a/.github/workflows/ksud.yml +++ b/.github/workflows/ksud.yml @@ -71,4 +71,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: ksud-${{ inputs.target }} - path: userspace/ksud/target/**/release/ksud* + path: userspace/ksud/target/**/release/zakomk* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index a79128e7..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Release -on: - push: - tags: - - "v*" - workflow_dispatch: - -jobs: - build-manager: - uses: ./.github/workflows/build-manager.yml - secrets: inherit - build-a12-kernel: - uses: ./.github/workflows/build-kernel-a12.yml - secrets: inherit - build-a13-kernel: - uses: ./.github/workflows/build-kernel-a13.yml - secrets: inherit - build-a14-kernel: - uses: ./.github/workflows/build-kernel-a14.yml - secrets: inherit - build-a15-kernel: - uses: ./.github/workflows/build-kernel-a15.yml - secrets: inherit - build-wsa-kernel: - uses: ./.github/workflows/build-kernel-wsa.yml - secrets: inherit - build-arcvm-kernel: - uses: ./.github/workflows/build-kernel-arcvm.yml - secrets: inherit - release: - needs: - - build-manager - - build-a12-kernel - - build-a13-kernel - - build-a14-kernel - - build-wsa-kernel - - build-arcvm-kernel - runs-on: ubuntu-latest - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - - name: Rename ksud - run: | - mkdir -p ksud - for dir in ./ksud-*; do - if [ -d "$dir" ]; then - echo "----- Rename $dir -----" - ksud_platform_name=$(basename "$dir") - find "$dir" -type f -name "ksud" -path "*/release/*" | while read -r ksud_file; do - if [ -f "$ksud_file" ]; then - mv "$ksud_file" "ksud/$ksud_platform_name" - fi - done - fi - done - - name: Zip AnyKernel3 - run: | - for dir in AnyKernel3-*; do - if [ -d "$dir" ]; then - echo "----- Zip $dir -----" - (cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..) - fi - done - - - name: Zip WSA kernel - run: | - for dir in kernel-WSA-*; do - if [ -d "$dir" ]; then - echo "------ Zip $dir ----------" - (cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..) - fi - done - - - name: Zip ChromeOS ARCVM kernel - run: | - for dir in kernel-ARCVM-*; do - if [ -d "$dir" ]; then - echo "------ Zip $dir ----------" - (cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..) - fi - done - - - name: Display structure of downloaded files - run: ls -R - - - name: release - uses: softprops/action-gh-release@v2 - with: - files: | - manager/*.apk - android*-lkm/*_kernelsu.ko - AnyKernel3-*.zip - boot-images-*/Image-*/*.img.gz - kernel-WSA*.zip - kernel-ARCVM*.zip - ksud/ksud-* diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index f864e05f..e4287380 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -16,7 +16,7 @@ on: jobs: shellcheck: - runs-on: ubuntu-latest + runs-on: self-hosted steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/wsa-kernel.yml b/.github/workflows/wsa-kernel.yml deleted file mode 100644 index 1f87e497..00000000 --- a/.github/workflows/wsa-kernel.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: Build Kernel - WSA -on: - workflow_call: - inputs: - arch: - required: true - type: string - description: > - Build arch: x86_64 / arm64 - version: - required: true - type: string - description: > - Build version -jobs: - build: - name: Build WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }} - runs-on: ubuntu-22.04 - env: - CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion" - CCACHE_NOHASHDIR: "true" - CCACHE_HARDLINK: "true" - - steps: - - name: Install Build Tools - uses: awalsh128/cache-apt-pkgs-action@v1 - with: - packages: bc bison build-essential flex libelf-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu gzip ccache - version: 1.0 - - - name: Cache LLVM - id: cache-llvm - uses: actions/cache@v4 - with: - path: ./llvm - key: llvm-12.0.1 - - - name: Setup LLVM - uses: KyleMayes/install-llvm-action@v1 - with: - version: "12.0.1" - force-version: true - ubuntu-version: "16.04" - cached: ${{ steps.cache-llvm.outputs.cache-hit }} - - - name: Checkout KernelSU - uses: actions/checkout@v4 - with: - path: KernelSU - fetch-depth: 0 - - - name: Setup kernel source - uses: actions/checkout@v4 - with: - repository: microsoft/WSA-Linux-Kernel - ref: android-lts/latte-2/${{ inputs.version }} - path: WSA-Linux-Kernel - - - name: Setup Ccache - uses: hendrikmuhs/ccache-action@v1 - with: - key: WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }} - save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - max-size: 2G - - - name: Setup KernelSU - working-directory: WSA-Linux-Kernel - run: | - echo "[+] KernelSU setup" - KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel - echo "[+] KERNEL_ROOT: $KERNEL_ROOT" - echo "[+] Copy KernelSU driver to $KERNEL_ROOT/drivers" - ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $KERNEL_ROOT/drivers/kernelsu - echo "[+] Add KernelSU driver to Makefile" - DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile - DRIVER_KCONFIG=$KERNEL_ROOT/drivers/Kconfig - grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE" - grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" - echo "[+] Apply KernelSU patches" - cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.15/*.patch || echo "[-] No patch found" - 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: Build Kernel - working-directory: WSA-Linux-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 - declare -A ARCH_MAP=(["x86_64"]="x64" ["arm64"]="arm64") - cp configs/wsa/config-wsa-${ARCH_MAP[${{ inputs.arch }}]} .config - make olddefconfig - declare -A FILE_NAME=(["x86_64"]="bzImage" ["arm64"]="Image") - make -j`nproc` LLVM=1 ARCH=${{ inputs.arch }} $(if [ "${{ inputs.arch }}" == "arm64" ]; then echo CROSS_COMPILE=aarch64-linux-gnu; fi) ${FILE_NAME[${{ inputs.arch }}]} CCACHE="/usr/bin/ccache" - declare -A ARCH_MAP_FILE=(["x86_64"]="x86" ["arm64"]="arm64") - echo "file_path=WSA-Linux-Kernel/arch/${ARCH_MAP_FILE[${{ inputs.arch }}]}/boot/${FILE_NAME[${{ inputs.arch }}]}" >> $GITHUB_ENV - - - name: Upload kernel-${{ inputs.arch }}-${{ inputs.version }} - uses: actions/upload-artifact@v4 - with: - name: kernel-WSA-${{ inputs.arch }}-${{ inputs.version }} - path: "${{ env.file_path }}" \ No newline at end of file diff --git a/.github/workflows/zakomksd.yml b/.github/workflows/zakomksd.yml new file mode 100644 index 00000000..10fe0d85 --- /dev/null +++ b/.github/workflows/zakomksd.yml @@ -0,0 +1,40 @@ +name: Build zakomksd + +on: + push: + branches: [ "mian" ] + paths: + - '.github/workflows/zakomksd.yml' + - 'userspace/zakomksd/**' + workflow_dispatch: + workflow_call: + inputs: + target: + required: true + type: string + os: + required: false + type: string + default: self-hosted + +jobs: + build-susfs: + name: Build userspace zakomksd + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build zakomksd + working-directory: ./userspace/zakomksd + run: | + $ANDROID_NDK_HOME/ndk-build + + - name: Upload a Build Artifact + uses: actions/upload-artifact@v4 + with: + name: zakomksd-aarch64-linux-android + path: ./userspace/zakomksd/libs diff --git a/docs/README-en.md b/docs/README-en.md new file mode 100644 index 00000000..ea1700d2 --- /dev/null +++ b/docs/README-en.md @@ -0,0 +1,101 @@ +# SukiSU + +**Enlish** | [简体中文](README.md) + + +Android device root solution based on [KernelSU](https://github.com/KernelSU/KernelSU) + +**Experimental! Use at your own risk! **This solution is based on [KernelSU]() and is experimental! + +> +> This is an unofficial fork, all rights reserved [@tiann](https://github.com/tiann) +> + +- Fully adapted for non-GKI devices (susfs-dev and unsusfs-patched dev branches only) + +## How to add + +Using the susfs-dev branch (integrated susfs with support for non-GKI devices) +``` +curl -LSs “https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh” | bash -s susfs-dev +``` + +Use main branching (no longer with support for non-GKI devices) +``` +curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main +``` + +## How to use integrated susfs + +Use the susfs-dev branch directly without any patching + + +## More links +Projects compiled based on Sukisu and susfs +- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS) +- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS) + +## Hook method +- This method references the hook manual to (https://github.com/rsuntk/KernelSU) + +1. **KPROBES hook:** + - This fork only supports GKI (5.10 - 6.x) kernels, all non-GKI kernels must use manual hooks. + - For Loadable Kernel Modules (LKM) + - Default hooking method for GKI kernels + - Requires `CONFIG_KPROBES=y`. 2. +2. **Hooks manual:** + - For GKI (5.10 - 6.x) kernels, add `CONFIG_KSU_MANUAL_HOOK=y` to the kernel defconfig and make sure to protect KernelSU hooks by using `#ifdef CONFIG_KSU_MANUAL_HOOK` instead of `#ifdef CONFIG_KSU`. + - Standard KernelSU hooks: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source + - backslashxx syscall hooks: https://github.com/backslashxx/KernelSU/issues/5 + - Some non-GKI devices that manually integrate KPROBES do not require the manual VFS hook `new_hook.patch` patch + + +## Usage +[GKI] +1. such as millet redmi samsung and other devices (does not include the magic kernel manufacturers such as: meizu, a plus real me oppo) +2. find more links in the GKI build project to find the device kernel version directly download with TWRP or kernel flashing tool to brush into the zip with AnyKernel3 suffix can be +3. General without the suffix of the .zip compressed package is universal, gz suffix for the special TianGui models, lz4 suffix for Google models, general brush without the suffix can be! + +[OnePlus] +1. Find the Yiga project in the More link and fill in your own, then build it with cloud compilation, and finally brush in the zip with AnyKernel3 suffix. +Note: You only need to fill in the first two kernel versions, such as 5.10, 5.15, 6.1, 6.6. +- Please search for the processor codename by yourself, usually it is all English without numbers. +- Branching and configuration files, please fill in the kernel open source address. + + + +## Features + +1. Kernel-based `su` and root access management. +2. Not based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) module system. 3. +3. [Application Profiles](https://kernelsu.org/guide/app-profile.html): Lock root privileges in a cage. 4. +4. Bringing back non-GKI/GKI 1.0 support +5. More customization + + + +## License + +- The file in the “kernel” directory is [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). +- All other parts except the “kernel” directory are [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html). + +## Sponsorship list +- [Ktouls](https://github.com/Ktouls) Thanks so much for bringing me support +- [zaoqi123](https://github.com/zaoqi123) It's not a bad idea to buy me a milk tea +- [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project + + + + +How the above list does not have your name, I will keep you updated, thanks again for your support! + +## Contributions + +- [KernelSU](https://github.com/tiann/KernelSU): original project +- [MKSU](https://github.com/5ec1cff/KernelSU): Used project +- [RKSU](https://github.com/rsuntk/KernelsU):Re-support of non-GKI devices using the kernel of this project +- [susfs](https://gitlab.com/simonpunk/susfs4ksu):Used susfs file system +- [KernelSU](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU conceptualization +- [Magisk](https://github.com/topjohnwu/Magisk): Powerful root utility +- [genuine](https://github.com/brevent/genuine/): APK v2 Signature Verification +- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit skills. diff --git a/docs/README.md b/docs/README.md index 177c74ae..71dc9484 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,24 +1,102 @@ -# MKSU +# SukiSU -A [KernelSU](https://github.com/tiann/KernelSU/commit/eeffecbd1bd7d49672a1c6bd52d95d28a42acb21)-based root solution for Android devices. +**简体中文** | [English](README-en.md) -**Experimental. Use at your own risk.** +基于 [KernelSU](https://github.com/tiann/KernelSU) 的安卓设备 root 解决方案 -## Features +**实验性!使用风险自负!** -1. Kernel-based `su` and root access management. -2. Module system not based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS). -3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage. -## License +> +> 这是非官方分支,保留所有权利 [@tiann](https://github.com/tiann) +> -- Files under the `kernel` directory are [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). -- All other parts except the `kernel` directory are [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html). -## Credits +## 如何添加 +在内核源码的根目录下执行以下命令: -- [KernelSU](https://github.com/tiann/KernelSU): The original project. -- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): The KernelSU idea. -- [Magisk](https://github.com/topjohnwu/Magisk): The powerful root tool. -- [genuine](https://github.com/brevent/genuine/): APK v2 signature validation. -- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit skills. +使用 susfs-dev 分支(已集成susfs,带非GKI设备的支持) +``` +curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s susfs-dev +``` + +使用 main 分支(不再带非GKI设备的支持) +``` +curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main +``` + +## 如何集成 susfs + +1. 直接使用 susfs-dev 分支,不需要再集成 susfs + + +## 钩子方法 +- 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU) + +1. **KPROBES 钩子:** + - 此方法仅支持 GKI 2.0(5.10 - 6.x)内核,所有非 GKI 2.0 内核都必须使用手动钩子 + - 用于可加载内核模块 (LKM) + - GKI 2.0 内核的默认钩子方法 + - 需要 `CONFIG_KPROBES=y` +2. **手动钩子:** + - 对于 GKI 2.0(5.10 - 6.x)内核,需要在对应设备的 defconfig 文件中添加 `CONFIG_KSU_MANUAL_HOOK=y` 并确保使用 `#ifdef CONFIG_KSU_MANUAL_HOOK` 而不是 `#ifdef CONFIG_KSU` 来保护 KernelSU 钩子 + - 标准的 KernelSU 钩子:https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source + - backslashxx 的 syscall 手动钩子:https://github.com/backslashxx/KernelSU/issues/5 + - 部分手动集成KPROBES的非 GKI 2.0 设备不需要手动 VFS 钩子 `new_hook.patch` 补丁 + + +## 更多链接 +基于 SukiSU 和 susfs 编译的项目 +- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS) +- [一加](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS) + + +## 使用方法 +### GKI +1. 适用于如小米红米三星等的 GKI 2.0 的设备(不包含魔改内核的厂商如魅族、一加、真我和 oppo) +2. 找到更多链接里的 GKI 构建的项目找到设备内核版本直接下载用TWRP或者内核刷写工具刷入带 AnyKernel3 后缀的压缩包即可 +3. 一般不带后缀的 .zip 压缩包是通用,gz 后缀的为天玑机型专用,lz4 后缀的为谷歌系机型专用,一般刷不带后缀的即可 + +### 一加 +1.找到更多链接里的一加项目进行自行填写,然后云编译构建,最后刷入带 AnyKernel3 后缀的压缩包即可 + +注意事项: +- 内核版本只需要填写前两位即可,如 5.10,5.15,6.1,6.6 +- 处理器代号请自行搜索,一般为全英文不带数字的代号 +- 分支和配置文件请自行到一加内核开源地址进行填写 + + +## 特点 + +1. 基于内核的 `su` 和 root 访问管理 +2. 基于 5ec1cff 的 [Magic Mount](https://github.com/5ec1cff/KernelSU) 的模块系统 +3. [App Profile](https://kernelsu.org/guide/app-profile.html):将 root 权限锁在笼子里 +4. 恢复对非 GKI 2.0 内核的支持(仅限susfs-dev和未进行susfs补丁的dev分支) +5. 更多自定义功能 + + +## 许可证 + +- `kernel` 目录下的文件是 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)。 +- 除 `kernel` 目录外,所有其他部分均为 [GPL-3.0 或更高版本](https://www.gnu.org/licenses/gpl-3.0.html)。 + +## 赞助名单 +- [Ktouls](https://github.com/Ktouls) 非常感谢你给我带来的支持 +- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错 +- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持 + + + + +如何以上名单没有你的名称,我会及时更新,再次感谢大家的支持 + +## 贡献 + +- [KernelSU](https://github.com/tiann/KernelSU):原始项目 +- [MKSU](https://github.com/5ec1cff/KernelSU):使用的项目 +- [RKSU](https://github.com/rsuntk/KernelsU):使用该项目的 kernel 对非GKI设备重新进行支持 +- [susfs4ksu](https://gitlab.com/simonpunk/susfs4ksu):使用的 susfs 文件系统 +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的构想 +- [Magisk](https://github.com/topjohnwu/Magisk):强大的 root 工具 +- [genuine](https://github.com/brevent/genuine/):APK v2 签名验证 +- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技能 diff --git a/kernel/Kconfig b/kernel/Kconfig index 67f177f4..d95e4c84 100644 --- a/kernel/Kconfig +++ b/kernel/Kconfig @@ -16,4 +16,11 @@ config KSU_DEBUG help Enable KernelSU debug mode. +config KSU_HOOK + bool "Enable KernelSU Hook" + default n + help + This option enables the KernelSU Hook feature. If enabled, it will + override the kernel version check and enable the hook functionality. + endmenu diff --git a/kernel/Makefile b/kernel/Makefile index 732ba2d9..0661e7e1 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -20,8 +20,8 @@ obj-$(CONFIG_KSU) += kernelsu.o 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)) +# ksu_version: major * 10000 + git version + 600 for historical reasons +$(eval KSU_VERSION=$(shell expr 12000 + $(KSU_GIT_VERSION) + 500)) $(info -- KernelSU version: $(KSU_VERSION)) ccflags-y += -DKSU_VERSION=$(KSU_VERSION) else # If there is no .git file, the default version will be passed. @@ -30,11 +30,11 @@ ccflags-y += -DKSU_VERSION=16 endif ifndef KSU_EXPECTED_SIZE -KSU_EXPECTED_SIZE := 384 +KSU_EXPECTED_SIZE := 0x35c endif ifndef KSU_EXPECTED_HASH -KSU_EXPECTED_HASH := 7e0c6d7278a3bb8e364e0fcba95afaf3666cf5ff3c245a3b63c8833bd0445cc4 +KSU_EXPECTED_HASH := 947ae944f3de4ed4c21a7e4f7953ecf351bfa2b36239da37a34111ad29993eef endif ifdef KSU_MANAGER_PACKAGE @@ -44,6 +44,10 @@ endif $(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE)) $(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH)) +$(info -- Supported Unofficial Manager: ShirkNeko (GKI) (Non-GKI)) +KERNEL_VERSION := $(VERSION).$(PATCHLEVEL) +$(info -- KERNEL_VERSION: $(KERNEL_VERSION)) + ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE) ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\" diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 76282a94..9bc25bf3 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -236,12 +236,10 @@ static void nuke_ext4_sysfs() { const char* name = sb->s_type->name; if (strcmp(name, "ext4") != 0) { pr_info("nuke but module aren't mounted\n"); - path_put(&path); return; } ext4_unregister_sysfs(sb); - path_put(&path); } int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, @@ -886,7 +884,9 @@ void __init ksu_core_init(void) void ksu_core_exit(void) { +#ifdef CONFIG_KPROBE pr_info("ksu_core_kprobe_exit\n"); // we dont use this now // ksu_kprobe_exit(); +#endif } diff --git a/kernel/ksu.c b/kernel/ksu.c index c76ece70..21f88be5 100644 --- a/kernel/ksu.c +++ b/kernel/ksu.c @@ -56,9 +56,12 @@ int __init kernelsu_init(void) ksu_allowlist_init(); ksu_throne_tracker_init(); - +#ifdef CONFIG_KPROBES ksu_sucompat_init(); ksu_ksud_init(); +#else + pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html"); +#endif #ifdef MODULE #ifndef CONFIG_KSU_DEBUG @@ -76,8 +79,10 @@ void kernelsu_exit(void) destroy_workqueue(ksu_workqueue); +#ifdef CONFIG_KPROBES ksu_ksud_exit(); ksu_sucompat_exit(); +#endif ksu_core_exit(); } diff --git a/kernel/ksud.c b/kernel/ksud.c index d2ae017a..efb20b90 100644 --- a/kernel/ksud.c +++ b/kernel/ksud.c @@ -20,6 +20,7 @@ #include "kernel_compat.h" #include "selinux/selinux.h" + static const char KERNEL_SU_RC[] = "\n" @@ -47,12 +48,21 @@ static void stop_vfs_read_hook(); static void stop_execve_hook(); static void stop_input_hook(); +#ifdef CONFIG_KPROBES static struct work_struct stop_vfs_read_work; static struct work_struct stop_execve_hook_work; static struct work_struct stop_input_hook_work; +#else +bool ksu_vfs_read_hook __read_mostly = true; +bool ksu_execveat_hook __read_mostly = true; +bool ksu_input_hook __read_mostly = true; +#endif u32 ksu_devpts_sid; +// Detect whether it is on or not +static bool is_boot_phase = true; + void on_post_fs_data(void) { static bool done = false; @@ -68,6 +78,9 @@ void on_post_fs_data(void) ksu_devpts_sid = ksu_get_devpts_sid(); pr_info("devpts sid: %d\n", ksu_devpts_sid); + + // End of boot state + is_boot_phase = false; } #define MAX_ARG_STRINGS 0x7FFFFFFF @@ -144,6 +157,11 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, struct user_arg_ptr *argv, struct user_arg_ptr *envp, int *flags) { +#ifndef CONFIG_KPROBES + if (!ksu_execveat_hook) { + return 0; + } + #endif struct filename *filename; static const char app_process[] = "/system/bin/app_process"; @@ -295,6 +313,11 @@ static ssize_t read_iter_proxy(struct kiocb *iocb, struct iov_iter *to) int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, size_t *count_ptr, loff_t **pos) { +#ifndef CONFIG_KPROBES + if (!ksu_vfs_read_hook) { + return 0; + } +#endif struct file *file; char __user *buf; size_t count; @@ -403,10 +426,15 @@ static bool is_volumedown_enough(unsigned int count) int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int *value) { +#ifndef CONFIG_KPROBES + if (!ksu_input_hook) { + return 0; + } +#endif if (*type == EV_KEY && *code == KEY_VOLUMEDOWN) { int val = *value; pr_info("KEY_VOLUMEDOWN val: %d\n", val); - if (val) { + if (val && is_boot_phase) { // key pressed, count it volumedown_pressed_count += 1; if (is_volumedown_enough(volumedown_pressed_count)) { @@ -440,6 +468,7 @@ bool ksu_is_safe_mode() return false; } +#ifdef CONFIG_KPROBES static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs) { struct pt_regs *real_regs = PT_REAL_REGS(regs); @@ -511,17 +540,28 @@ static void do_stop_input_hook(struct work_struct *work) { unregister_kprobe(&input_event_kp); } +#endif static void stop_vfs_read_hook() { +#ifdef CONFIG_KPROBES bool ret = schedule_work(&stop_vfs_read_work); pr_info("unregister vfs_read kprobe: %d!\n", ret); +#else + ksu_vfs_read_hook = false; + pr_info("stop vfs_read_hook\n"); +#endif } static void stop_execve_hook() { +#ifdef CONFIG_KPROBES bool ret = schedule_work(&stop_execve_hook_work); pr_info("unregister execve kprobe: %d!\n", ret); +#else + ksu_execveat_hook = false; + pr_info("stop execve_hook\n"); +#endif } static void stop_input_hook() @@ -531,13 +571,19 @@ static void stop_input_hook() return; } input_hook_stopped = true; +#ifdef CONFIG_KPROBES bool ret = schedule_work(&stop_input_hook_work); pr_info("unregister input kprobe: %d!\n", ret); +#else + ksu_input_hook = false; + pr_info("stop input_hook\n"); +#endif } // ksud: module support void ksu_ksud_init() { +#ifdef CONFIG_KPROBES int ret; ret = register_kprobe(&execve_kp); @@ -552,12 +598,17 @@ void ksu_ksud_init() INIT_WORK(&stop_vfs_read_work, do_stop_vfs_read_hook); INIT_WORK(&stop_execve_hook_work, do_stop_execve_hook); INIT_WORK(&stop_input_hook_work, do_stop_input_hook); +#endif } void ksu_ksud_exit() { +#ifdef CONFIG_KPROBES unregister_kprobe(&execve_kp); // this should be done before unregister vfs_read_kp // unregister_kprobe(&vfs_read_kp); unregister_kprobe(&input_event_kp); + + is_boot_phase = false; +#endif } \ No newline at end of file diff --git a/kernel/ksud.h b/kernel/ksud.h index cc2df243..76181816 100644 --- a/kernel/ksud.h +++ b/kernel/ksud.h @@ -1,8 +1,6 @@ #ifndef __KSU_H_KSUD #define __KSU_H_KSUD -#include - #define KSUD_PATH "/data/adb/ksud" void on_post_fs_data(void); @@ -10,5 +8,4 @@ void on_post_fs_data(void); bool ksu_is_safe_mode(void); extern u32 ksu_devpts_sid; - #endif diff --git a/kernel/setup.sh b/kernel/setup.sh index b560d5d2..c4a3f597 100755 --- a/kernel/setup.sh +++ b/kernel/setup.sh @@ -39,7 +39,7 @@ perform_cleanup() { # Sets up or update KernelSU environment setup_kernelsu() { echo "[+] Setting up KernelSU..." - test -d "$GKI_ROOT/KernelSU" || git clone https://github.com/5ec1cff/KernelSU && echo "[+] Repository cloned." + test -d "$GKI_ROOT/KernelSU" || git clone https://github.com/ShirkNeko/KernelSU && echo "[+] Repository cloned." cd "$GKI_ROOT/KernelSU" git stash && echo "[-] Stashed current changes." if [ "$(git status | grep -Po 'v\d+(\.\d+)*' | head -n1)" ]; then diff --git a/kernel/sucompat.c b/kernel/sucompat.c index 54f0fda7..40699878 100644 --- a/kernel/sucompat.c +++ b/kernel/sucompat.c @@ -164,7 +164,7 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user, return 0; } - +#ifdef CONFIG_KPROBES static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) { struct pt_regs *real_regs = PT_REAL_REGS(regs); @@ -228,18 +228,23 @@ static void destroy_kprobe(struct kprobe **kp_ptr) } static struct kprobe *su_kps[3]; +#endif // sucompat: permited process can execute 'su' to gain root access. void ksu_sucompat_init() { +#ifdef CONFIG_KPROBES su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre); su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre); su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre); +#endif } void ksu_sucompat_exit() { +#ifdef CONFIG_KPROBES for (int i = 0; i < ARRAY_SIZE(su_kps); i++) { destroy_kprobe(&su_kps[i]); } +#endif } diff --git a/kernel/throne_tracker.c b/kernel/throne_tracker.c index d7c1dae1..721b6c6e 100644 --- a/kernel/throne_tracker.c +++ b/kernel/throne_tracker.c @@ -148,6 +148,13 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name, if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen)) return FILLDIR_ACTOR_CONTINUE; // Skip "." and ".." + if (d_type == DT_DIR && namelen >= 8 && !strncmp(name, "vmdl", 4) && + !strncmp(name + namelen - 4, ".tmp", 4)) { + pr_info("Skipping directory: %.*s\n", namelen, name); + return FILLDIR_ACTOR_CONTINUE; // Skip staging package + } + + if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir, namelen, name) >= DATA_PATH_LEN) { pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen, diff --git a/manager/app/build.gradle.kts b/manager/app/build.gradle.kts index 1f98e4a1..820d3c41 100644 --- a/manager/app/build.gradle.kts +++ b/manager/app/build.gradle.kts @@ -10,6 +10,8 @@ plugins { alias(libs.plugins.ksp) alias(libs.plugins.lsplugin.apksign) id("kotlin-parcelize") + + } val managerVersionCode: Int by rootProject.extra @@ -23,7 +25,7 @@ apksign { } android { - namespace = "me.weishu.kernelsu" + namespace = "shirkneko.zako.sukisu" buildTypes { release { @@ -68,7 +70,7 @@ android { applicationVariants.all { outputs.forEach { val output = it as BaseVariantOutputImpl - output.outputFileName = "KernelSU_${managerVersionName}_${managerVersionCode}-$name.apk" + output.outputFileName = "SukiSU_${managerVersionName}_${managerVersionCode}-$name.apk" } kotlin.sourceSets { getByName(name) { @@ -102,6 +104,8 @@ dependencies { implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.foundation) + implementation(libs.androidx.documentfile) debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.tooling) @@ -133,4 +137,7 @@ dependencies { implementation(libs.androidx.webkit) implementation(libs.lsposed.cxx) + + implementation(libs.com.github.topjohnwu.libsu.core) + } \ No newline at end of file diff --git a/manager/app/src/main/AndroidManifest.xml b/manager/app/src/main/AndroidManifest.xml index 11cda5f2..a893ed23 100644 --- a/manager/app/src/main/AndroidManifest.xml +++ b/manager/app/src/main/AndroidManifest.xml @@ -3,6 +3,9 @@ xmlns:tools="http://schemas.android.com/tools"> + + + GetStringUTFChars(pkg, nullptr); auto result = become_manager(cpkg); env->ReleaseStringUTFChars(pkg, cpkg); @@ -21,13 +21,13 @@ Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) extern "C" JNIEXPORT jint JNICALL -Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jobject) { +Java_shirkneko_zako_sukisu_Natives_getVersion(JNIEnv *env, jobject) { return get_version(); } extern "C" JNIEXPORT jintArray JNICALL -Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) { +Java_shirkneko_zako_sukisu_Natives_getAllowList(JNIEnv *env, jobject) { int uids[1024]; int size = 0; bool result = get_allow_list(uids, &size); @@ -42,13 +42,13 @@ Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) { extern "C" JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) { +Java_shirkneko_zako_sukisu_Natives_isSafeMode(JNIEnv *env, jclass clazz) { return is_safe_mode(); } extern "C" JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_isLkmMode(JNIEnv *env, jclass clazz) { +Java_shirkneko_zako_sukisu_Natives_isLkmMode(JNIEnv *env, jclass clazz) { return is_lkm_mode(); } @@ -111,7 +111,7 @@ static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) { extern "C" JNIEXPORT jobject JNICALL -Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) { +Java_shirkneko_zako_sukisu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) { if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) { return nullptr; } @@ -129,7 +129,7 @@ Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, bool useDefaultProfile = !get_app_profile(key, &profile); - auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile"); + auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile"); auto constructor = env->GetMethodID(cls, "", "()V"); auto obj = env->NewObject(cls, constructor); auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;"); @@ -207,8 +207,8 @@ Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, extern "C" JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) { - auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile"); +Java_shirkneko_zako_sukisu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) { + auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile"); auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;"); auto currentUidField = env->GetFieldID(cls, "currentUid", "I"); @@ -293,16 +293,16 @@ Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobjec } extern "C" JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) { +Java_shirkneko_zako_sukisu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) { return uid_should_umount(uid); } extern "C" JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_isSuEnabled(JNIEnv *env, jobject thiz) { +Java_shirkneko_zako_sukisu_Natives_isSuEnabled(JNIEnv *env, jobject thiz) { return is_su_enabled(); } extern "C" JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) { +Java_shirkneko_zako_sukisu_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) { return set_su_enabled(enabled); } \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt deleted file mode 100644 index fb12df6b..00000000 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt +++ /dev/null @@ -1,362 +0,0 @@ -package me.weishu.kernelsu.ui.screen - -import android.app.Activity -import android.content.Intent -import android.net.Uri -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.StringRes -import androidx.compose.foundation.LocalIndication -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.selection.toggleable -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.FileUpload -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.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.produceState -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.dropUnlessResumed -import com.maxkeppeker.sheets.core.models.base.Header -import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState -import com.maxkeppeler.sheets.list.ListDialog -import com.maxkeppeler.sheets.list.models.ListOption -import com.maxkeppeler.sheets.list.models.ListSelection -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.annotation.RootGraph -import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination -import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator -import me.weishu.kernelsu.R -import me.weishu.kernelsu.ui.component.DialogHandle -import me.weishu.kernelsu.ui.component.rememberConfirmDialog -import me.weishu.kernelsu.ui.component.rememberCustomDialog -import me.weishu.kernelsu.ui.util.LkmSelection -import me.weishu.kernelsu.ui.util.getCurrentKmi -import me.weishu.kernelsu.ui.util.getSupportedKmis -import me.weishu.kernelsu.ui.util.isAbDevice -import me.weishu.kernelsu.ui.util.isInitBoot -import me.weishu.kernelsu.ui.util.rootAvailable - -/** - * @author weishu - * @date 2024/3/12. - */ -@OptIn(ExperimentalMaterial3Api::class) -@Destination -@Composable -fun InstallScreen(navigator: DestinationsNavigator) { - var installMethod by remember { - mutableStateOf(null) - } - - var lkmSelection by remember { - mutableStateOf(LkmSelection.KmiNone) - } - - val onInstall = { - installMethod?.let { method -> - val flashIt = FlashIt.FlashBoot( - boot = if (method is InstallMethod.SelectFile) method.uri else null, - lkm = lkmSelection, - ota = method is InstallMethod.DirectInstallToInactiveSlot - ) - navigator.navigate(FlashScreenDestination(flashIt)) - } - } - - val currentKmi by produceState(initialValue = "") { value = getCurrentKmi() } - - val selectKmiDialog = rememberSelectKmiDialog { kmi -> - kmi?.let { - lkmSelection = LkmSelection.KmiString(it) - onInstall() - } - } - - val onClickNext = { - if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) { - // no lkm file selected and cannot get current kmi - selectKmiDialog.show() - } else { - onInstall() - } - } - - val selectLkmLauncher = - rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == Activity.RESULT_OK) { - it.data?.data?.let { uri -> - lkmSelection = LkmSelection.LkmUri(uri) - } - } - } - - val onLkmUpload = { - selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply { - type = "application/octet-stream" - }) - } - - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - - Scaffold( - topBar = { - TopBar( - onBack = dropUnlessResumed { navigator.popBackStack() }, - onLkmUpload = onLkmUpload, - scrollBehavior = scrollBehavior - ) - }, - contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) - ) { innerPadding -> - Column( - modifier = Modifier - .padding(innerPadding) - .nestedScroll(scrollBehavior.nestedScrollConnection) - .verticalScroll(rememberScrollState()) - ) { - SelectInstallMethod { method -> - installMethod = method - } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) { - (lkmSelection as? LkmSelection.LkmUri)?.let { - Text( - stringResource( - id = R.string.selected_lkm, - it.uri.lastPathSegment ?: "(file)" - ) - ) - } - Button(modifier = Modifier.fillMaxWidth(), - enabled = installMethod != null, - onClick = { - onClickNext() - }) { - Text( - stringResource(id = R.string.install_next), - fontSize = MaterialTheme.typography.bodyMedium.fontSize - ) - } - } - } - } -} - -sealed class InstallMethod { - data class SelectFile( - val uri: Uri? = null, - @StringRes override val label: Int = R.string.select_file, - override val summary: String? - ) : InstallMethod() - - data object DirectInstall : InstallMethod() { - override val label: Int - get() = R.string.direct_install - } - - data object DirectInstallToInactiveSlot : InstallMethod() { - override val label: Int - get() = R.string.install_inactive_slot - } - - abstract val label: Int - open val summary: String? = null -} - -@Composable -private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) { - val rootAvailable = rootAvailable() - val isAbDevice = isAbDevice() - val selectFileTip = stringResource( - id = R.string.select_file_tip, if (isInitBoot()) "init_boot" else "boot" - ) - val radioOptions = - mutableListOf(InstallMethod.SelectFile(summary = selectFileTip)) - if (rootAvailable) { - radioOptions.add(InstallMethod.DirectInstall) - - if (isAbDevice) { - radioOptions.add(InstallMethod.DirectInstallToInactiveSlot) - } - } - - var selectedOption by remember { mutableStateOf(null) } - val selectImageLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult() - ) { - if (it.resultCode == Activity.RESULT_OK) { - it.data?.data?.let { uri -> - val option = InstallMethod.SelectFile(uri, summary = selectFileTip) - 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) - } - } - } - - Column { - radioOptions.forEach { option -> - val interactionSource = remember { MutableInteractionSource() } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .toggleable( - value = option.javaClass == selectedOption?.javaClass, - onValueChange = { - onClick(option) - }, - role = Role.RadioButton, - indication = LocalIndication.current, - interactionSource = interactionSource - ) - ) { - RadioButton( - selected = option.javaClass == selectedOption?.javaClass, - onClick = { - onClick(option) - }, - interactionSource = interactionSource - ) - Column( - modifier = Modifier.padding(vertical = 12.dp) - ) { - Text( - text = stringResource(id = option.label), - fontSize = MaterialTheme.typography.titleMedium.fontSize, - fontFamily = MaterialTheme.typography.titleMedium.fontFamily, - fontStyle = MaterialTheme.typography.titleMedium.fontStyle - ) - option.summary?.let { - Text( - text = it, - fontSize = MaterialTheme.typography.bodySmall.fontSize, - fontFamily = MaterialTheme.typography.bodySmall.fontFamily, - fontStyle = MaterialTheme.typography.bodySmall.fontStyle - ) - } - } - } - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle { - return rememberCustomDialog { dismiss -> - val supportedKmi by produceState(initialValue = emptyList()) { - value = getSupportedKmis() - } - val options = supportedKmi.map { value -> - ListOption( - titleText = value - ) - } - - var selection by remember { mutableStateOf(null) } - ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = { - onSelected(selection) - }, onCloseRequest = { - dismiss() - }), header = Header.Default( - title = stringResource(R.string.select_kmi), - ), selection = ListSelection.Single( - showRadioButtons = true, - options = options, - ) { _, option -> - selection = option.titleText - }) - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun TopBar( - onBack: () -> Unit = {}, - onLkmUpload: () -> Unit = {}, - scrollBehavior: TopAppBarScrollBehavior? = null -) { - TopAppBar( - title = { Text(stringResource(R.string.install)) }, navigationIcon = { - IconButton( - onClick = onBack - ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } - }, actions = { - IconButton(onClick = onLkmUpload) { - Icon(Icons.Filled.FileUpload, contentDescription = null) - } - }, - windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), - scrollBehavior = scrollBehavior - ) -} - -@Composable -@Preview -fun SelectInstallPreview() { - InstallScreen(EmptyDestinationsNavigator) -} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt deleted file mode 100644 index a2510a7f..00000000 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt +++ /dev/null @@ -1,192 +0,0 @@ -package me.weishu.kernelsu.ui.screen - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material3.* -import androidx.compose.material3.pulltorefresh.PullToRefreshBox -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.lifecycle.viewmodel.compose.viewModel -import coil.compose.AsyncImage -import coil.request.ImageRequest -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.annotation.RootGraph -import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination -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.SearchAppBar -import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel - -@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) -@Destination -@Composable -fun SuperUserScreen(navigator: DestinationsNavigator) { - val viewModel = viewModel() - val scope = rememberCoroutineScope() - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - val listState = rememberLazyListState() - - LaunchedEffect(key1 = navigator) { - viewModel.search = "" - if (viewModel.appList.isEmpty()) { - viewModel.fetchAppList() - } - } - - LaunchedEffect(viewModel.search) { - if (viewModel.search.isEmpty()) { - listState.scrollToItem(0) - } - } - - Scaffold( - topBar = { - SearchAppBar( - title = { Text(stringResource(R.string.superuser)) }, - searchText = viewModel.search, - onSearchTextChange = { viewModel.search = it }, - onClearClick = { viewModel.search = "" }, - dropdownContent = { - var showDropdown by remember { mutableStateOf(false) } - - IconButton( - onClick = { showDropdown = true }, - ) { - Icon( - imageVector = Icons.Filled.MoreVert, - contentDescription = stringResource(id = R.string.settings) - ) - - DropdownMenu(expanded = showDropdown, onDismissRequest = { - showDropdown = false - }) { - DropdownMenuItem(text = { - Text(stringResource(R.string.refresh)) - }, onClick = { - scope.launch { - viewModel.fetchAppList() - } - showDropdown = false - }) - DropdownMenuItem(text = { - Text( - if (viewModel.showSystemApps) { - stringResource(R.string.hide_system_apps) - } else { - stringResource(R.string.show_system_apps) - } - ) - }, onClick = { - viewModel.showSystemApps = !viewModel.showSystemApps - showDropdown = false - }) - } - } - }, - scrollBehavior = scrollBehavior - ) - }, - contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) - ) { innerPadding -> - PullToRefreshBox( - modifier = Modifier.padding(innerPadding), - onRefresh = { - scope.launch { viewModel.fetchAppList() } - }, - isRefreshing = viewModel.isRefreshing - ) { - LazyColumn( - state = listState, - modifier = Modifier - .fillMaxSize() - .nestedScroll(scrollBehavior.nestedScrollConnection) - ) { - items(viewModel.appList, key = { it.packageName + it.uid }) { app -> - AppItem(app) { - navigator.navigate(AppProfileScreenDestination(app)) - } - } - } - } - } -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -private fun AppItem( - app: SuperUserViewModel.AppInfo, - onClickListener: () -> Unit, -) { - ListItem( - modifier = Modifier.clickable(onClick = onClickListener), - headlineContent = { Text(app.label) }, - supportingContent = { - Column { - Text(app.packageName) - FlowRow { - if (app.allowSu) { - LabelText(label = "ROOT") - } else { - if (Natives.uidShouldUmount(app.uid)) { - LabelText(label = "UMOUNT") - } - } - if (app.hasCustomProfile) { - LabelText(label = "CUSTOM") - } - } - } - }, - leadingContent = { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(app.packageInfo) - .crossfade(true) - .build(), - contentDescription = app.label, - modifier = Modifier - .padding(4.dp) - .width(48.dp) - .height(48.dp) - ) - }, - ) -} - -@Composable -fun LabelText(label: String) { - Box( - modifier = Modifier - .padding(top = 4.dp, end = 4.dp) - .background( - Color.Black, - shape = RoundedCornerShape(4.dp) - ) - ) { - Text( - text = label, - modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp), - style = TextStyle( - fontSize = 8.sp, - color = Color.White, - ) - ) - } -} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Color.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Color.kt deleted file mode 100644 index 155cf4e2..00000000 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Color.kt +++ /dev/null @@ -1,10 +0,0 @@ -package me.weishu.kernelsu.ui.theme - -import androidx.compose.ui.graphics.Color - -val YELLOW = Color(0xFFeed502) -val YELLOW_LIGHT = Color(0xFFffff52) -val SECONDARY_LIGHT = Color(0xffa9817f) - -val YELLOW_DARK = Color(0xFFb7a400) -val SECONDARY_DARK = Color(0xFF4c2b2b) \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Theme.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Theme.kt deleted file mode 100644 index 903ee94e..00000000 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Theme.kt +++ /dev/null @@ -1,46 +0,0 @@ -package me.weishu.kernelsu.ui.theme - -import android.os.Build -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme -import androidx.compose.material3.lightColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext - -private val DarkColorScheme = darkColorScheme( - primary = YELLOW, - secondary = YELLOW_DARK, - tertiary = SECONDARY_DARK -) - -private val LightColorScheme = lightColorScheme( - primary = YELLOW, - secondary = YELLOW_LIGHT, - tertiary = SECONDARY_LIGHT -) - -@Composable -fun KernelSUTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - // Dynamic color is available on Android 12+ - dynamicColor: Boolean = true, - content: @Composable () -> Unit -) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) - } - darkTheme -> DarkColorScheme - else -> LightColorScheme - } - - MaterialTheme( - colorScheme = colorScheme, - typography = Typography, - content = content - ) -} diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/module/LatestVersionInfo.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/module/LatestVersionInfo.kt deleted file mode 100644 index 374b3853..00000000 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/module/LatestVersionInfo.kt +++ /dev/null @@ -1,7 +0,0 @@ -package me.weishu.kernelsu.ui.util.module - -data class LatestVersionInfo( - val versionCode : Int = 0, - val downloadUrl : String = "", - val changelog : String = "" -) diff --git a/manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/KernelSUApplication.kt similarity index 53% rename from manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/KernelSUApplication.kt index e09cc135..bc6afc75 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/KernelSUApplication.kt @@ -1,22 +1,16 @@ -package me.weishu.kernelsu +package shirkneko.zako.sukisu import android.app.Application -import android.system.Os import coil.Coil import coil.ImageLoader import me.zhanghai.android.appiconloader.coil.AppIconFetcher import me.zhanghai.android.appiconloader.coil.AppIconKeyer -import okhttp3.Cache -import okhttp3.OkHttpClient import java.io.File -import java.util.Locale lateinit var ksuApp: KernelSUApplication class KernelSUApplication : Application() { - lateinit var okhttpClient: OkHttpClient - override fun onCreate() { super.onCreate() ksuApp = this @@ -36,20 +30,7 @@ class KernelSUApplication : Application() { if (!webroot.exists()) { webroot.mkdir() } - - // Provide working env for rust's temp_dir() - Os.setenv("TMPDIR", cacheDir.absolutePath, true) - - okhttpClient = - OkHttpClient.Builder().cache(Cache(File(cacheDir, "okhttp"), 10 * 1024 * 1024)) - .addInterceptor { block -> - block.proceed( - block.request().newBuilder() - .header("User-Agent", "KernelSU/${BuildConfig.VERSION_CODE}") - .header("Accept-Language", Locale.getDefault().toLanguageTag()).build() - ) - }.build() } -} +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/Kernels.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/Kernels.kt similarity index 96% rename from manager/app/src/main/java/me/weishu/kernelsu/Kernels.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/Kernels.kt index 860e46fc..d77e8419 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/Kernels.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/Kernels.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu +package shirkneko.zako.sukisu import android.system.Os diff --git a/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/Natives.kt similarity index 98% rename from manager/app/src/main/java/me/weishu/kernelsu/Natives.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/Natives.kt index 426cf37f..03b8e664 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/Natives.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu +package shirkneko.zako.sukisu import android.os.Parcelable import androidx.annotation.Keep @@ -30,7 +30,7 @@ object Natives { const val ROOT_GID = 0 init { - System.loadLibrary("kernelsu") + System.loadLibrary("zako") } // become root manager, return true if success. diff --git a/manager/app/src/main/java/me/weishu/kernelsu/profile/Capabilities.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/profile/Capabilities.kt similarity index 99% rename from manager/app/src/main/java/me/weishu/kernelsu/profile/Capabilities.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/profile/Capabilities.kt index d52518c2..7e2faafd 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/profile/Capabilities.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/profile/Capabilities.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.profile +package shirkneko.zako.sukisu.profile /** * @author weishu diff --git a/manager/app/src/main/java/me/weishu/kernelsu/profile/Groups.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/profile/Groups.kt similarity index 99% rename from manager/app/src/main/java/me/weishu/kernelsu/profile/Groups.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/profile/Groups.kt index 2ddb94dc..04dddb36 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/profile/Groups.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/profile/Groups.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.profile +package shirkneko.zako.sukisu.profile /** * https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/KsuService.java b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/KsuService.java similarity index 96% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/KsuService.java rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/KsuService.java index 2ebcc786..71d3ddb9 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/KsuService.java +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/KsuService.java @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui; +package shirkneko.zako.sukisu.ui; import android.content.Context; import android.content.Intent; @@ -17,7 +17,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import me.weishu.kernelsu.IKsuInterface; +import shirkneko.zako.sukisu.IKsuInterface; import rikka.parcelablelist.ParcelableListSlice; /** diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/MainActivity.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/MainActivity.kt similarity index 76% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/MainActivity.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/MainActivity.kt index 5737c524..8e2c437a 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/MainActivity.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/MainActivity.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui +package shirkneko.zako.sukisu.ui import android.os.Build import android.os.Bundle @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.union import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold @@ -29,8 +30,9 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.navigation.NavBackStackEntry import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController @@ -39,13 +41,16 @@ import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationSty import com.ramcosta.composedestinations.generated.NavGraphs import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator -import me.weishu.kernelsu.Natives -import me.weishu.kernelsu.ksuApp -import me.weishu.kernelsu.ui.screen.BottomBarDestination -import me.weishu.kernelsu.ui.theme.KernelSUTheme -import me.weishu.kernelsu.ui.util.LocalSnackbarHost -import me.weishu.kernelsu.ui.util.rootAvailable -import me.weishu.kernelsu.ui.util.install +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.ksuApp +import shirkneko.zako.sukisu.ui.screen.BottomBarDestination +import shirkneko.zako.sukisu.ui.theme.CardConfig +import shirkneko.zako.sukisu.ui.theme.KernelSUTheme +import shirkneko.zako.sukisu.ui.theme.loadCustomBackground +import shirkneko.zako.sukisu.ui.theme.loadThemeMode +import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost +import shirkneko.zako.sukisu.ui.util.rootAvailable +import shirkneko.zako.sukisu.ui.util.install class MainActivity : ComponentActivity() { @@ -59,8 +64,14 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) - val isManager = Natives.becomeManager(ksuApp.packageName) - if (isManager) install() + // 加载保存的背景设置 + loadCustomBackground() + loadThemeMode() + CardConfig.load(applicationContext) + + + val isManager = Natives.becomeManager(ksuApp.packageName) + if (isManager) install() setContent { KernelSUTheme { @@ -96,8 +107,16 @@ private fun BottomBar(navController: NavHostController) { val navigator = navController.rememberDestinationsNavigator() val isManager = Natives.becomeManager(ksuApp.packageName) val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() + + // 获取卡片颜色和透明度 + val cardColor = MaterialTheme.colorScheme.secondaryContainer + val cardAlpha = CardConfig.cardAlpha + val cardElevation = CardConfig.cardElevation + NavigationBar( - tonalElevation = 8.dp, + tonalElevation = cardElevation, // 动态设置阴影 + containerColor = cardColor.copy(alpha = cardAlpha), // 动态设置颜色和透明度 + contentColor = if (cardColor.luminance() > 0.5) Color.Black else Color.White, // 根据背景亮度设置文字颜色 windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only( WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom ) @@ -127,8 +146,12 @@ private fun BottomBar(navController: NavHostController) { } }, label = { Text(stringResource(destination.label)) }, - alwaysShowLabel = false + alwaysShowLabel = false, + colors = androidx.compose.material3.NavigationBarItemDefaults.colors( + selectedTextColor = Color.Black, + unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant + ) ) } } -} +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/AboutCard.kt similarity index 92% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/AboutCard.kt index 80344739..b3b1db7c 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/AboutCard.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.component +package shirkneko.zako.sukisu.ui.component import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column @@ -31,8 +31,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog -import me.weishu.kernelsu.BuildConfig -import me.weishu.kernelsu.R +import shirkneko.zako.sukisu.BuildConfig +import shirkneko.zako.sukisu.R @Preview @Composable @@ -72,7 +72,7 @@ private fun AboutCardContent() { shape = CircleShape ) { Image( - painter = painterResource(id = R.drawable.ic_launcher_foreground), + painter = painterResource(id = R.drawable.ic_launcher_monochrome), contentDescription = "icon", modifier = Modifier.scale(1.4f) ) @@ -98,8 +98,8 @@ private fun AboutCardContent() { val annotatedString = AnnotatedString.Companion.fromHtml( htmlString = stringResource( id = R.string.about_source_code, - "GitHub", - "Telegram" + "GitHub", + "Telegram" ), linkStyles = TextLinkStyles( style = SpanStyle( diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/Dialog.kt similarity index 99% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/Dialog.kt index 27adc3f0..3bc75030 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/Dialog.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.component +package shirkneko.zako.sukisu.ui.component import android.graphics.text.LineBreaker import android.os.Build @@ -88,6 +88,7 @@ interface ConfirmDialogHandle : DialogHandle { ) suspend fun awaitConfirm( + title: String, content: String, markdown: Boolean = false, diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/KeyEventBlocker.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/KeyEventBlocker.kt similarity index 94% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/component/KeyEventBlocker.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/KeyEventBlocker.kt index b3268131..325ed92d 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/KeyEventBlocker.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/KeyEventBlocker.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.component +package shirkneko.zako.sukisu.ui.component import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SearchBar.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/SearchBar.kt similarity index 90% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/component/SearchBar.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/SearchBar.kt index b388cb49..6dbbff20 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SearchBar.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/SearchBar.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.component +package shirkneko.zako.sukisu.ui.component import android.util.Log import androidx.compose.animation.AnimatedVisibility @@ -20,9 +20,11 @@ import androidx.compose.material.icons.filled.Search import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -40,6 +42,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import shirkneko.zako.sukisu.ui.theme.CardConfig private const val TAG = "SearchBar" @@ -59,6 +62,11 @@ fun SearchAppBar( val focusRequester = remember { FocusRequester() } var onSearch by remember { mutableStateOf(false) } + // 获取卡片颜色和透明度 + val cardColor = MaterialTheme.colorScheme.secondaryContainer + val cardAlpha = CardConfig.cardAlpha + val cardElevation = CardConfig.cardElevation + if (onSearch) { LaunchedEffect(Unit) { focusRequester.requestFocus() } } @@ -140,7 +148,11 @@ fun SearchAppBar( }, windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), - scrollBehavior = scrollBehavior + scrollBehavior = scrollBehavior, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = cardColor.copy(alpha = cardAlpha), + scrolledContainerColor = cardColor.copy(alpha = cardAlpha) + ) ) } @@ -155,4 +167,4 @@ private fun SearchAppBarPreview() { onSearchTextChange = { searchText = it }, onClearClick = { searchText = "" } ) -} +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SettingsItem.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/SettingsItem.kt similarity index 97% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/component/SettingsItem.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/SettingsItem.kt index e537175f..1c8ce6db 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SettingsItem.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/SettingsItem.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.component +package shirkneko.zako.sukisu.ui.component import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/profile/AppProfileConfig.kt similarity index 91% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/profile/AppProfileConfig.kt index 065ff6d0..0aa53b08 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/profile/AppProfileConfig.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.component.profile +package shirkneko.zako.sukisu.ui.component.profile import androidx.compose.foundation.layout.Column import androidx.compose.material3.OutlinedTextField @@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import me.weishu.kernelsu.Natives -import me.weishu.kernelsu.R -import me.weishu.kernelsu.ui.component.SwitchItem +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.SwitchItem @Composable fun AppProfileConfig( diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/profile/RootProfileConfig.kt similarity index 96% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/profile/RootProfileConfig.kt index a6a2a45e..8a6bdd3a 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/profile/RootProfileConfig.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.component.profile +package shirkneko.zako.sukisu.ui.component.profile import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -9,17 +9,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.material.icons.filled.ArrowDropUp import androidx.compose.material3.AssistChip -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults @@ -49,12 +42,12 @@ import com.maxkeppeler.sheets.input.models.ValidationResult import com.maxkeppeler.sheets.list.ListDialog import com.maxkeppeler.sheets.list.models.ListOption import com.maxkeppeler.sheets.list.models.ListSelection -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 +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.profile.Capabilities +import shirkneko.zako.sukisu.profile.Groups +import shirkneko.zako.sukisu.ui.component.rememberCustomDialog +import shirkneko.zako.sukisu.ui.util.isSepolicyValid @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/TemplateConfig.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/profile/TemplateConfig.kt similarity index 93% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/TemplateConfig.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/profile/TemplateConfig.kt index b60e8ea4..5100343e 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/TemplateConfig.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/component/profile/TemplateConfig.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.component.profile +package shirkneko.zako.sukisu.ui.component.profile import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons @@ -23,11 +23,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import me.weishu.kernelsu.Natives -import me.weishu.kernelsu.R -import me.weishu.kernelsu.ui.util.listAppProfileTemplates -import me.weishu.kernelsu.ui.util.setSepolicy -import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates +import shirkneko.zako.sukisu.ui.util.setSepolicy +import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById /** * @author weishu diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/AppProfile.kt similarity index 94% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/AppProfile.kt index 9cfc0686..6d7ee314 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/AppProfile.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.screen +package shirkneko.zako.sukisu.ui.screen import androidx.annotation.StringRes import androidx.compose.animation.Crossfade @@ -64,20 +64,20 @@ import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplat import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination 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.SwitchItem -import me.weishu.kernelsu.ui.component.profile.AppProfileConfig -import me.weishu.kernelsu.ui.component.profile.RootProfileConfig -import me.weishu.kernelsu.ui.component.profile.TemplateConfig -import me.weishu.kernelsu.ui.util.LocalSnackbarHost -import me.weishu.kernelsu.ui.util.forceStopApp -import me.weishu.kernelsu.ui.util.getSepolicy -import me.weishu.kernelsu.ui.util.launchApp -import me.weishu.kernelsu.ui.util.restartApp -import me.weishu.kernelsu.ui.util.setSepolicy -import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel -import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.SwitchItem +import shirkneko.zako.sukisu.ui.component.profile.AppProfileConfig +import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig +import shirkneko.zako.sukisu.ui.component.profile.TemplateConfig +import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost +import shirkneko.zako.sukisu.ui.util.forceStopApp +import shirkneko.zako.sukisu.ui.util.getSepolicy +import shirkneko.zako.sukisu.ui.util.launchApp +import shirkneko.zako.sukisu.ui.util.restartApp +import shirkneko.zako.sukisu.ui.util.setSepolicy +import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel +import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById /** * @author weishu diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/BottomBarDestination.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/BottomBarDestination.kt similarity index 77% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/screen/BottomBarDestination.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/BottomBarDestination.kt index c9637ed2..129411b2 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/BottomBarDestination.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/BottomBarDestination.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.screen +package shirkneko.zako.sukisu.ui.screen import androidx.annotation.StringRes import androidx.compose.material.icons.Icons @@ -8,8 +8,9 @@ import androidx.compose.ui.graphics.vector.ImageVector import com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination import com.ramcosta.composedestinations.generated.destinations.ModuleScreenDestination import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination +import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination import com.ramcosta.composedestinations.spec.DirectionDestinationSpec -import me.weishu.kernelsu.R +import shirkneko.zako.sukisu.R enum class BottomBarDestination( val direction: DirectionDestinationSpec, @@ -20,5 +21,6 @@ enum class BottomBarDestination( ) { Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false), SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true), - Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true) + Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true), + Settings(SettingScreenDestination, R.string.settings, Icons.Filled.Settings, Icons.Outlined.Settings, false), } diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/ExecuteModuleAction.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/ExecuteModuleAction.kt similarity index 95% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/screen/ExecuteModuleAction.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/ExecuteModuleAction.kt index c6be680e..79aae3f0 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/ExecuteModuleAction.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/ExecuteModuleAction.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.screen +package shirkneko.zako.sukisu.ui.screen import android.os.Environment import androidx.compose.foundation.layout.Column @@ -37,10 +37,10 @@ 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.runModuleAction +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.KeyEventBlocker +import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost +import shirkneko.zako.sukisu.ui.util.runModuleAction import java.io.File import java.text.SimpleDateFormat import java.util.Date diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Flash.kt similarity index 67% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Flash.kt index e61b88d3..cdf98cb1 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Flash.kt @@ -1,40 +1,18 @@ -package me.weishu.kernelsu.ui.screen +package shirkneko.zako.sukisu.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.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing +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.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Save -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.key @@ -52,25 +30,12 @@ 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.FlashResult -import me.weishu.kernelsu.ui.util.LkmSelection -import me.weishu.kernelsu.ui.util.LocalSnackbarHost -import me.weishu.kernelsu.ui.util.flashModule -import me.weishu.kernelsu.ui.util.installBoot -import me.weishu.kernelsu.ui.util.reboot -import me.weishu.kernelsu.ui.util.restoreBoot -import me.weishu.kernelsu.ui.util.uninstallPermanently +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.KeyEventBlocker +import shirkneko.zako.sukisu.ui.util.* import java.io.File import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -/** - * @author weishu - * @date 2023/1/1. - */ +import java.util.* enum class FlashingStatus { FLASHING, @@ -78,27 +43,20 @@ enum class FlashingStatus { FAILED } -// Lets you flash modules sequentially when mutiple zipUris are selected -fun flashModulesSequentially( - uris: List, - onStdout: (String) -> Unit, - onStderr: (String) -> Unit -): FlashResult { - for (uri in uris) { - flashModule(uri, onStdout, onStderr).apply { - if (code != 0) { - return FlashResult(code, err, showReboot) - } - } - } - return FlashResult(0, "", true) +private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING) + +fun getFlashingStatus(): FlashingStatus { + return currentFlashingStatus.value +} + +fun setFlashingStatus(status: FlashingStatus) { + currentFlashingStatus.value = status } @OptIn(ExperimentalMaterial3Api::class) @Composable @Destination fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { - var text by rememberSaveable { mutableStateOf("") } var tempText: String val logContent = rememberSaveable { StringBuilder() } @@ -108,18 +66,27 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { val scope = rememberCoroutineScope() val scrollState = rememberScrollState() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - var flashing by rememberSaveable { - mutableStateOf(FlashingStatus.FLASHING) - } LaunchedEffect(Unit) { if (text.isNotEmpty()) { return@LaunchedEffect } withContext(Dispatchers.IO) { - flashIt(flashIt, onStdout = { + setFlashingStatus(FlashingStatus.FLASHING) + flashIt(flashIt, onFinish = { showReboot, code -> + if (code != 0) { + text += "Error: exit code = $code.\nPlease save and check the log.\n" + setFlashingStatus(FlashingStatus.FAILED) + } else { + setFlashingStatus(FlashingStatus.SUCCESS) + } + if (showReboot) { + text += "\n\n\n" + showFloatAction = true + } + }, onStdout = { tempText = "$it\n" - if (tempText.startsWith("")) { // clear command + if (tempText.startsWith("[H[J")) { // clear command text = tempText.substring(6) } else { text += tempText @@ -127,24 +94,15 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { logContent.append(it).append("\n") }, onStderr = { logContent.append(it).append("\n") - }).apply { - if (code != 0) { - text += "Error code: $code.\n $err Please save and check the log.\n" - } - if (showReboot) { - text += "\n\n\n" - showFloatAction = true - } - flashing = if (code == 0) FlashingStatus.SUCCESS else FlashingStatus.FAILED - } + }) } } Scaffold( topBar = { TopBar( - flashing, - onBack = dropUnlessResumed { + currentFlashingStatus.value, + onBack = dropUnlessResumed { navigator.popBackStack() }, onSave = { @@ -207,37 +165,30 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { @Parcelize sealed class FlashIt : Parcelable { - data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : - FlashIt() - - data class FlashModules(val uris: List) : FlashIt() - + data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : FlashIt() + data class FlashModule(val uri: Uri) : FlashIt() data object FlashRestore : FlashIt() - data object FlashUninstall : FlashIt() } fun flashIt( flashIt: FlashIt, + onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit -): FlashResult { - return when (flashIt) { +) { + when (flashIt) { is FlashIt.FlashBoot -> installBoot( flashIt.boot, flashIt.lkm, flashIt.ota, + onFinish, onStdout, onStderr ) - - is FlashIt.FlashModules -> { - flashModulesSequentially(flashIt.uris, onStdout, onStderr) - } - - FlashIt.FlashRestore -> restoreBoot(onStdout, onStderr) - - FlashIt.FlashUninstall -> uninstallPermanently(onStdout, onStderr) + is FlashIt.FlashModule -> flashModule(flashIt.uri, onFinish, onStdout, onStderr) + FlashIt.FlashRestore -> restoreBoot(onFinish, onStdout, onStderr) + FlashIt.FlashUninstall -> uninstallPermanently(onFinish, onStdout, onStderr) } } @@ -281,6 +232,6 @@ private fun TopBar( @Preview @Composable -fun InstallPreview() { - InstallScreen(EmptyDestinationsNavigator) +fun FlashScreenPreview() { + FlashScreen(EmptyDestinationsNavigator, FlashIt.FlashUninstall) } \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Home.kt similarity index 62% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Home.kt index 4631733e..3ceccd85 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Home.kt @@ -1,9 +1,10 @@ -package me.weishu.kernelsu.ui.screen +package shirkneko.zako.sukisu.ui.screen import android.content.Context import android.os.Build import android.os.PowerManager import android.system.Os +import android.util.Log import androidx.annotation.StringRes import androidx.compose.animation.* import androidx.compose.foundation.clickable @@ -11,20 +12,15 @@ 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 -import androidx.compose.material.icons.outlined.CheckCircle -import androidx.compose.material.icons.outlined.Warning +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.platform.* import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -36,39 +32,59 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest import com.ramcosta.composedestinations.navigation.DestinationsNavigator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import me.weishu.kernelsu.* -import me.weishu.kernelsu.R -import me.weishu.kernelsu.ui.component.rememberConfirmDialog -import me.weishu.kernelsu.ui.util.* -import me.weishu.kernelsu.ui.util.module.LatestVersionInfo +import shirkneko.zako.sukisu.* +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog +import shirkneko.zako.sukisu.ui.util.* +import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import shirkneko.zako.sukisu.ui.theme.getCardColors +import shirkneko.zako.sukisu.ui.theme.getCardElevation +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.runtime.saveable.rememberSaveable +import shirkneko.zako.sukisu.ui.theme.CardConfig @OptIn(ExperimentalMaterial3Api::class) @Destination(start = true) @Composable fun HomeScreen(navigator: DestinationsNavigator) { + val context = LocalContext.current + var isSimpleMode by rememberSaveable { mutableStateOf(false) } + + // 从 SharedPreferences 加载简洁模式状态 + LaunchedEffect(Unit) { + isSimpleMode = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + .getBoolean("is_simple_mode", false) + } val kernelVersion = getKernelVersion() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + Scaffold( topBar = { TopBar( kernelVersion, - onSettingsClick = { - navigator.navigate(SettingScreenDestination) - }, - onInstallClick = { - navigator.navigate(InstallScreenDestination) - }, + onInstallClick = { navigator.navigate(InstallScreenDestination) }, + onSettingsClick = { navigator.navigate(SettingScreenDestination) }, scrollBehavior = scrollBehavior ) }, - contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) + contentWindowInsets = WindowInsets.safeDrawing.only( + WindowInsetsSides.Top + WindowInsetsSides.Horizontal + ) ) { innerPadding -> Column( modifier = Modifier .padding(innerPadding) .nestedScroll(scrollBehavior.nestedScrollConnection) .verticalScroll(rememberScrollState()) + .padding(top = 12.dp) .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { @@ -99,9 +115,42 @@ fun HomeScreen(navigator: DestinationsNavigator) { if (checkUpdate) { UpdateCard() } + val prefs = remember { context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) } + var clickCount by rememberSaveable { mutableStateOf(prefs.getInt("click_count", 0)) } + + if (!isSimpleMode && clickCount < 3) { + AnimatedVisibility( + visible = clickCount < 3, + exit = shrinkVertically() + fadeOut() + ) { + ElevatedCard( + colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), + elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + clickCount++ + prefs.edit().putInt("click_count", clickCount).apply() + } + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.using_mksu_manager), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + } InfoCard() - DonateCard() - LearnMoreCard() + if (!isSimpleMode) { + DonateCard() + LearnMoreCard() + } + Spacer(Modifier) } } @@ -122,6 +171,11 @@ fun UpdateCard() { val newVersionUrl = newVersion.downloadUrl val changelog = newVersion.changelog + Log.d("UpdateCard", "Current version code: $currentVersionCode") + Log.d("UpdateCard", "New version code: $newVersionCode") + + + val uriHandler = LocalUriHandler.current val title = stringResource(id = R.string.module_changelog) val updateText = stringResource(id = R.string.module_update) @@ -167,30 +221,27 @@ private fun TopBar( onSettingsClick: () -> Unit, scrollBehavior: TopAppBarScrollBehavior? = null ) { + val cardColor = MaterialTheme.colorScheme.secondaryContainer + val cardAlpha = CardConfig.cardAlpha + TopAppBar( title = { Text(stringResource(R.string.app_name)) }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = cardColor.copy(alpha = cardAlpha), + scrolledContainerColor = cardColor.copy(alpha = cardAlpha) + ), actions = { if (kernelVersion.isGKI()) { IconButton(onClick = onInstallClick) { - Icon( - imageVector = Icons.Filled.Archive, - contentDescription = stringResource(id = R.string.install) - ) + Icon(Icons.Filled.Archive, stringResource(R.string.install)) } } var showDropdown by remember { mutableStateOf(false) } - IconButton(onClick = { - showDropdown = true - }) { - Icon( - imageVector = Icons.Filled.Refresh, - contentDescription = stringResource(id = R.string.reboot) - ) - - DropdownMenu(expanded = showDropdown, onDismissRequest = { - showDropdown = false - }) { + IconButton(onClick = { showDropdown = true }) { + Icon(Icons.Filled.Refresh, stringResource(R.string.reboot)) + DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false } + ) { RebootDropdownItem(id = R.string.reboot) @@ -205,13 +256,6 @@ private fun TopBar( RebootDropdownItem(id = R.string.reboot_edl, reason = "edl") } } - - IconButton(onClick = onSettingsClick) { - Icon( - imageVector = Icons.Filled.Settings, - contentDescription = stringResource(id = R.string.settings) - ) - } }, windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), scrollBehavior = scrollBehavior @@ -226,10 +270,8 @@ private fun StatusCard( onClickInstall: () -> Unit = {} ) { ElevatedCard( - colors = CardDefaults.elevatedCardColors(containerColor = run { - if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer - else MaterialTheme.colorScheme.errorContainer - }) + colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), + elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) ) { Row(modifier = Modifier .fillMaxWidth() @@ -247,7 +289,7 @@ private fun StatusCard( } val workingMode = when (lkmMode) { - null -> "" + null -> " " true -> " " else -> " " } @@ -277,6 +319,19 @@ private fun StatusCard( text = stringResource(R.string.home_module_count, getModuleCount()), style = MaterialTheme.typography.bodyMedium ) + Spacer(modifier = Modifier.height(4.dp)) + + val suSFS = getSuSFS() + val translatedStatus = when (suSFS) { + "Supported" -> stringResource(R.string.status_supported) + "Not Supported" -> stringResource(R.string.status_not_supported) + else -> stringResource(R.string.status_unknown) + } + + Text( + text = stringResource(R.string.home_susfs, translatedStatus), + style = MaterialTheme.typography.bodyMedium + ) } } @@ -319,9 +374,8 @@ fun WarningCard( message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null ) { ElevatedCard( - colors = CardDefaults.elevatedCardColors( - containerColor = color - ) + colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), + elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) ) { Row( modifier = Modifier @@ -341,7 +395,10 @@ fun LearnMoreCard() { val uriHandler = LocalUriHandler.current val url = stringResource(R.string.home_learn_kernelsu_url) - ElevatedCard { + ElevatedCard( + colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), + elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + ) { Row(modifier = Modifier .fillMaxWidth() @@ -368,7 +425,10 @@ fun LearnMoreCard() { fun DonateCard() { val uriHandler = LocalUriHandler.current - ElevatedCard { + ElevatedCard( + colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), + elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + ) { Row(modifier = Modifier .fillMaxWidth() @@ -394,8 +454,13 @@ fun DonateCard() { @Composable private fun InfoCard() { val context = LocalContext.current + val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) + .getBoolean("is_simple_mode", false) - ElevatedCard { + ElevatedCard( + colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), + elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) + ) { Column( modifier = Modifier .fillMaxWidth() @@ -405,26 +470,77 @@ private fun InfoCard() { val uname = Os.uname() @Composable - fun InfoCardItem(label: String, content: String) { + fun InfoCardItem( + label: String, + content: String, + ) { contents.appendLine(label).appendLine(content).appendLine() Text(text = label, style = MaterialTheme.typography.bodyLarge) Text(text = content, style = MaterialTheme.typography.bodyMedium) } - InfoCardItem(stringResource(R.string.home_kernel), uname.release) + InfoCardItem(stringResource(R.string.home_kernel), uname.release) - Spacer(Modifier.height(16.dp)) - val managerVersion = getManagerVersion(context) - InfoCardItem( - stringResource(R.string.home_manager_version), - "${managerVersion.first} (${managerVersion.second})" - ) + if (!isSimpleMode) { + Spacer(Modifier.height(16.dp)) + val androidVersion = Build.VERSION.RELEASE + InfoCardItem(stringResource(R.string.home_android_version), androidVersion) + } - Spacer(Modifier.height(16.dp)) - InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT) - Spacer(Modifier.height(16.dp)) - InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus()) + Spacer(Modifier.height(16.dp)) + val deviceModel = Build.MODEL + InfoCardItem(stringResource(R.string.home_device_model), deviceModel) + + + + Spacer(Modifier.height(16.dp)) + val managerVersion = getManagerVersion(context) + InfoCardItem( + stringResource(R.string.home_manager_version), + "${managerVersion.first} (${managerVersion.second})" + ) + + + + Spacer(Modifier.height(16.dp)) + InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus()) + + + if (!isSimpleMode) { + Spacer(modifier = Modifier.height(16.dp)) + + val suSFS = getSuSFS() + if (suSFS == "Supported") { + InfoCardItem( + stringResource(R.string.home_susfs_version), + "${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})" + ) + } else { + val susSUMode = try { + susfsSUS_SU_Mode() + } catch (e: Exception) { + 0 + } + + if (susSUMode == 2 || susSUMode == 0) { + val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU" + val susSUModeLabel = stringResource(R.string.sus_su_mode) + val susSUModeValue = susSUMode.toString() + val susSUModeText = if (isSUS_SU) " $susSUModeLabel $susSUModeValue" else "" + + InfoCardItem( + stringResource(R.string.home_susfs_version), + "${getSuSFSVersion()} (${getSuSFSVariant()})$susSUModeText" + ) + } else { + InfoCardItem( + stringResource(R.string.home_susfs_version), + "${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})" + ) + } + } + } } } } diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Install.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Install.kt new file mode 100644 index 00000000..b4b2ab0c --- /dev/null +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Install.kt @@ -0,0 +1,571 @@ +package shirkneko.zako.sukisu.ui.screen + +import android.app.Activity +import android.app.AlertDialog +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.StringRes +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.selection.toggleable +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.FileUpload +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.documentfile.provider.DocumentFile +import com.maxkeppeker.sheets.core.models.base.Header +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.list.ListDialog +import com.maxkeppeler.sheets.list.models.ListOption +import com.maxkeppeler.sheets.list.models.ListSelection +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootGraph +import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.DialogHandle +import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog +import shirkneko.zako.sukisu.ui.component.rememberCustomDialog +import shirkneko.zako.sukisu.ui.util.* +import shirkneko.zako.sukisu.utils.AssetsUtil +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + + +/** + * @author weishu + * @date 2024/3/12. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Destination +@Composable +fun InstallScreen(navigator: DestinationsNavigator) { + var installMethod by remember { mutableStateOf(null) } + var lkmSelection by remember { mutableStateOf(LkmSelection.KmiNone) } + val context = LocalContext.current + + var showRebootDialog by remember { mutableStateOf(false) } + + val onFlashComplete = { + showRebootDialog = true + } + + if (showRebootDialog) { + RebootDialog( + show = true, + onDismiss = { showRebootDialog = false }, + onConfirm = { + showRebootDialog = false + try { + val process = Runtime.getRuntime().exec("su") + process.outputStream.bufferedWriter().use { writer -> + writer.write("svc power reboot\n") + writer.write("exit\n") + } + } catch (e: Exception) { + Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show() + } + } + ) + } + + val onInstall = { + installMethod?.let { method -> + when (method) { + is InstallMethod.HorizonKernel -> { + method.uri?.let { uri -> + val worker = HorizonKernelWorker(context) + worker.uri = uri + worker.setOnFlashCompleteListener(onFlashComplete) + worker.start() + } + } + else -> { + val flashIt = FlashIt.FlashBoot( + boot = if (method is InstallMethod.SelectFile) method.uri else null, + lkm = lkmSelection, + ota = method is InstallMethod.DirectInstallToInactiveSlot + ) + navigator.navigate(FlashScreenDestination(flashIt)) + } + } + } + Unit + } + + val currentKmi by produceState(initialValue = "") { + value = getCurrentKmi() + } + + val selectKmiDialog = rememberSelectKmiDialog { kmi -> + kmi?.let { + lkmSelection = LkmSelection.KmiString(it) + onInstall() + } + } + + val onClickNext = { + if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) { + selectKmiDialog.show() + } else { + onInstall() + } + Unit + } + + val selectLkmLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode == Activity.RESULT_OK) { + it.data?.data?.let { uri -> + lkmSelection = LkmSelection.LkmUri(uri) + } + } + } + + val onLkmUpload = { + selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply { + type = "application/octet-stream" + }) + } + + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + + Scaffold( + topBar = { + TopBar( + onBack = { navigator.popBackStack() }, + onLkmUpload = onLkmUpload, + scrollBehavior = scrollBehavior + ) + }, + contentWindowInsets = WindowInsets.safeDrawing.only( + WindowInsetsSides.Top + WindowInsetsSides.Horizontal + ) + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .nestedScroll(scrollBehavior.nestedScrollConnection) + .verticalScroll(rememberScrollState()) + ) { + SelectInstallMethod { method -> + installMethod = method + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + (lkmSelection as? LkmSelection.LkmUri)?.let { + Text( + stringResource( + id = R.string.selected_lkm, + it.uri.lastPathSegment ?: "(file)" + ) + ) + } + Button( + modifier = Modifier.fillMaxWidth(), + enabled = installMethod != null, + onClick = onClickNext + ) { + Text( + stringResource(id = R.string.install_next), + fontSize = MaterialTheme.typography.bodyMedium.fontSize + ) + } + } + } + } +} + +private fun launchHorizonKernelFlash(context: Context, uri: Uri) { + val worker = HorizonKernelWorker(context) + worker.uri = uri + worker.setOnFlashCompleteListener { + } + worker.start() +} + +@Composable +private fun RebootDialog( + show: Boolean, + onDismiss: () -> Unit, + onConfirm: () -> Unit +) { + if (show) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(stringResource(id = R.string.reboot_complete_title)) }, + text = { Text(stringResource(id = R.string.reboot_complete_msg)) }, + confirmButton = { + TextButton(onClick = onConfirm) { + Text(stringResource(id = R.string.yes)) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(id = R.string.no)) + } + } + ) + } +} + + +private class HorizonKernelWorker(private val context: Context) : Thread() { + var uri: Uri? = null + private lateinit var filePath: String + private lateinit var binaryPath: String + + + private var onFlashComplete: (() -> Unit)? = null + + fun setOnFlashCompleteListener(listener: () -> Unit) { + onFlashComplete = listener + } + + override fun run() { + filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}" + binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary" + + try { + cleanup() + if (!rootAvailable()) { + showError(context.getString(R.string.root_required)) + return + } + + copy() + if (!File(filePath).exists()) { + showError(context.getString(R.string.copy_failed)) + return + } + + getBinary() + patch() + flash() + + (context as? Activity)?.runOnUiThread { + onFlashComplete?.invoke() + } + } catch (e: Exception) { + showError(e.message ?: context.getString(R.string.unknown_error)) + } + } + + private fun cleanup() { + runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete") + } + + private fun copy() { + uri?.let { safeUri -> + context.contentResolver.openInputStream(safeUri)?.use { input -> + FileOutputStream(File(filePath)).use { output -> + input.copyTo(output) + } + } + } + } + + private fun getBinary() { + runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}") + if (!File(binaryPath).exists()) { + throw IOException("Failed to extract update-binary") + } + } + + private fun patch() { + val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs" + AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath) + runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath") + } + + private fun flash() { + val process = ProcessBuilder("su") + .redirectErrorStream(true) + .start() + + try { + process.outputStream.bufferedWriter().use { writer -> + writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n") + writer.write("sh $binaryPath 3 1 \"$filePath\" && touch ${context.filesDir.absolutePath}/done\nexit\n") + writer.flush() + } + + process.inputStream.bufferedReader().use { reader -> + reader.lineSequence().forEach { line -> + if (line.startsWith("ui_print")) { + showLog(line.removePrefix("ui_print")) + } + } + } + } finally { + process.destroy() + } + + if (!File("${context.filesDir.absolutePath}/done").exists()) { + throw IOException("Flash failed") + } + } + + private fun runCommand(su: Boolean, cmd: String): Int { + val process = ProcessBuilder(if (su) "su" else "sh") + .redirectErrorStream(true) + .start() + + return try { + process.outputStream.bufferedWriter().use { writer -> + writer.write("$cmd\n") + writer.write("exit\n") + writer.flush() + } + process.waitFor() + } finally { + process.destroy() + } + } + + private fun showError(message: String) { + (context as? Activity)?.runOnUiThread { + Toast.makeText(context, message, Toast.LENGTH_LONG).show() + } + } + + private fun showLog(message: String) { + (context as? Activity)?.runOnUiThread { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + } +} + +sealed class InstallMethod { + data class SelectFile( + val uri: Uri? = null, + @StringRes override val label: Int = R.string.select_file, + override val summary: String? + ) : InstallMethod() + + data object DirectInstall : InstallMethod() { + override val label: Int + get() = R.string.direct_install + } + + data object DirectInstallToInactiveSlot : InstallMethod() { + override val label: Int + get() = R.string.install_inactive_slot + } + + data class HorizonKernel( + val uri: Uri? = null, + @StringRes override val label: Int = R.string.horizon_kernel, + override val summary: String? = null + ) : InstallMethod() + + abstract val label: Int + open val summary: String? = null +} + +@Composable +private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) { + val rootAvailable = rootAvailable() + val isAbDevice = isAbDevice() + val selectFileTip = stringResource( + id = R.string.select_file_tip, + if (isInitBoot()) "init_boot" else "boot" + ) + + val radioOptions = mutableListOf( + InstallMethod.SelectFile(summary = selectFileTip) + ) + + if (rootAvailable) { + radioOptions.add(InstallMethod.DirectInstall) + if (isAbDevice) { + radioOptions.add(InstallMethod.DirectInstallToInactiveSlot) + } + radioOptions.add(InstallMethod.HorizonKernel(summary = "Flashing the Anykernel3 Kernel")) + } + + var selectedOption by remember { mutableStateOf(null) } + var currentSelectingMethod by remember { mutableStateOf(null) } + + val selectImageLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode == Activity.RESULT_OK) { + it.data?.data?.let { uri -> + val option = when (currentSelectingMethod) { + is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip) + is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = " Flashing the Anykernel3 Kernel") + else -> null + } + option?.let { + selectedOption = it + onSelected(it) + } + } + } + } + + 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 -> + currentSelectingMethod = option + when (option) { + is InstallMethod.SelectFile, is InstallMethod.HorizonKernel -> { + selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply { + type = "application/*" + putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/octet-stream", "application/zip")) + }) + } + is InstallMethod.DirectInstall -> { + selectedOption = option + onSelected(option) + } + is InstallMethod.DirectInstallToInactiveSlot -> { + confirmDialog.showConfirm(dialogTitle, dialogContent) + } + } + } + + Column { + radioOptions.forEach { option -> + val interactionSource = remember { MutableInteractionSource() } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .toggleable( + value = option.javaClass == selectedOption?.javaClass, + onValueChange = { onClick(option) }, + role = Role.RadioButton, + indication = LocalIndication.current, + interactionSource = interactionSource + ) + ) { + RadioButton( + selected = option.javaClass == selectedOption?.javaClass, + onClick = { onClick(option) }, + interactionSource = interactionSource + ) + Column( + modifier = Modifier.padding(vertical = 12.dp) + ) { + Text( + text = stringResource(id = option.label), + fontSize = MaterialTheme.typography.titleMedium.fontSize, + fontFamily = MaterialTheme.typography.titleMedium.fontFamily, + fontStyle = MaterialTheme.typography.titleMedium.fontStyle + ) + option.summary?.let { + Text( + text = it, + fontSize = MaterialTheme.typography.bodySmall.fontSize, + fontFamily = MaterialTheme.typography.bodySmall.fontFamily, + fontStyle = MaterialTheme.typography.bodySmall.fontStyle + ) + } + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle { + return rememberCustomDialog { dismiss -> + val supportedKmi by produceState(initialValue = emptyList()) { + value = getSupportedKmis() + } + + val options = supportedKmi.map { value -> + ListOption(titleText = value) + } + + var selection by remember { mutableStateOf(null) } + + ListDialog( + state = rememberUseCaseState( + visible = true, + onFinishedRequest = { + onSelected(selection) + }, + onCloseRequest = { + dismiss() + } + ), + header = Header.Default( + title = stringResource(R.string.select_kmi), + ), + selection = ListSelection.Single( + showRadioButtons = true, + options = options, + ) { _, option -> + selection = option.titleText + } + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar( + onBack: () -> Unit = {}, + onLkmUpload: () -> Unit = {}, + scrollBehavior: TopAppBarScrollBehavior? = null +) { + TopAppBar( + title = { Text(stringResource(R.string.install)) }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) + } + }, + actions = { + IconButton(onClick = onLkmUpload) { + Icon(Icons.Filled.FileUpload, contentDescription = null) + } + }, + windowInsets = WindowInsets.safeDrawing.only( + WindowInsetsSides.Top + WindowInsetsSides.Horizontal + ), + scrollBehavior = scrollBehavior + ) +} + +@Preview +@Composable +fun SelectInstallPreview() { + InstallScreen(EmptyDestinationsNavigator) +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Module.kt similarity index 69% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Module.kt index cbb8c656..75b37804 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Module.kt @@ -1,14 +1,13 @@ -package me.weishu.kernelsu.ui.screen +package shirkneko.zako.sukisu.ui.screen -import android.app.Activity.RESULT_OK +import android.app.Activity.* import android.content.Context import android.content.Intent import android.net.Uri -import android.util.Log import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -24,22 +23,18 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.selection.toggleable import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Wysiwyg -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material.icons.outlined.PlayArrow -import androidx.compose.material.icons.outlined.Download -import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material3.AlertDialog -import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.automirrored.outlined.* +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.* import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -58,7 +53,6 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Switch import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.rememberTopAppBarState @@ -74,7 +68,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.* import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight @@ -94,24 +88,30 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.weishu.kernelsu.Natives -import me.weishu.kernelsu.R -import me.weishu.kernelsu.ksuApp -import me.weishu.kernelsu.ui.component.ConfirmResult -import me.weishu.kernelsu.ui.component.SearchAppBar -import me.weishu.kernelsu.ui.component.rememberConfirmDialog -import me.weishu.kernelsu.ui.component.rememberLoadingDialog -import me.weishu.kernelsu.ui.util.DownloadListener -import me.weishu.kernelsu.ui.util.LocalSnackbarHost -import me.weishu.kernelsu.ui.util.download -import me.weishu.kernelsu.ui.util.hasMagisk -import me.weishu.kernelsu.ui.util.reboot -import me.weishu.kernelsu.ui.util.restoreModule -import me.weishu.kernelsu.ui.util.toggleModule -import me.weishu.kernelsu.ui.util.uninstallModule -import me.weishu.kernelsu.ui.util.getFileName -import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel -import me.weishu.kernelsu.ui.webui.WebUIActivity +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.ConfirmResult +import shirkneko.zako.sukisu.ui.component.SearchAppBar +import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog +import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog +import shirkneko.zako.sukisu.ui.util.DownloadListener +import shirkneko.zako.sukisu.ui.util.* +import shirkneko.zako.sukisu.ui.util.download +import shirkneko.zako.sukisu.ui.util.hasMagisk +import shirkneko.zako.sukisu.ui.util.reboot +import shirkneko.zako.sukisu.ui.util.restoreModule +import shirkneko.zako.sukisu.ui.util.toggleModule +import shirkneko.zako.sukisu.ui.util.uninstallModule +import shirkneko.zako.sukisu.ui.webui.WebUIActivity +import okhttp3.OkHttpClient +import shirkneko.zako.sukisu.ui.util.ModuleModify +import shirkneko.zako.sukisu.ui.theme.getCardColors +import shirkneko.zako.sukisu.ui.theme.getCardElevation +import shirkneko.zako.sukisu.ui.viewmodel.ModuleViewModel +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.zip.ZipInputStream +import androidx.compose.ui.graphics.Color @OptIn(ExperimentalMaterial3Api::class) @Destination @@ -121,6 +121,122 @@ fun ModuleScreen(navigator: DestinationsNavigator) { val context = LocalContext.current val snackBarHost = LocalSnackbarHost.current val scope = rememberCoroutineScope() + val confirmDialog = rememberConfirmDialog() + val buttonTextColor = androidx.compose.ui.graphics.Color.Black + + val selectZipLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode != RESULT_OK) { + return@rememberLauncherForActivityResult + } + val data = it.data ?: return@rememberLauncherForActivityResult + + scope.launch { + val clipData = data.clipData + if (clipData != null) { + // 处理多选结果 + val selectedModules = mutableSetOf() + val selectedModuleNames = mutableMapOf() + + suspend fun processUri(uri: Uri) { + val moduleName = withContext(Dispatchers.IO) { + try { + val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri)) + var entry = zipInputStream.nextEntry + var name = context.getString(R.string.unknown_module) + + while (entry != null) { + if (entry.name == "module.prop") { + val reader = BufferedReader(InputStreamReader(zipInputStream)) + var line: String? + while (reader.readLine().also { line = it } != null) { + if (line?.startsWith("name=") == true) { + name = line?.substringAfter("=") ?: name + break + } + } + break + } + entry = zipInputStream.nextEntry + } + name + } catch (e: Exception) { + context.getString(R.string.unknown_module) + } + } + selectedModules.add(uri) + selectedModuleNames[uri] = moduleName + } + + for (i in 0 until clipData.itemCount) { + val uri = clipData.getItemAt(i).uri + processUri(uri) + } + + // 显示确认对话框 + val modulesList = selectedModuleNames.values.joinToString("\n• ", "• ") + val confirmResult = confirmDialog.awaitConfirm( + title = context.getString(R.string.module_install), + content = context.getString(R.string.module_install_multiple_confirm_with_names, selectedModules.size, modulesList), + confirm = context.getString(R.string.install), + dismiss = context.getString(R.string.cancel) + ) + + if (confirmResult == ConfirmResult.Confirmed) { + // 批量安装模块 + selectedModules.forEach { uri -> + navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri))) + } + viewModel.markNeedRefresh() + } + } else { + // 单个文件安装逻辑 + val uri = data.data ?: return@launch + val moduleName = withContext(Dispatchers.IO) { + try { + val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri)) + var entry = zipInputStream.nextEntry + var name = context.getString(R.string.unknown_module) + + while (entry != null) { + if (entry.name == "module.prop") { + val reader = BufferedReader(InputStreamReader(zipInputStream)) + var line: String? + while (reader.readLine().also { line = it } != null) { + if (line?.startsWith("name=") == true) { + name = line?.substringAfter("=") ?: name + break + } + } + break + } + entry = zipInputStream.nextEntry + } + name + } catch (e: Exception) { + context.getString(R.string.unknown_module) + } + } + + val confirmResult = confirmDialog.awaitConfirm( + title = context.getString(R.string.module_install), + content = context.getString(R.string.module_install_confirm, moduleName), + confirm = context.getString(R.string.install), + dismiss = context.getString(R.string.cancel) + ) + + if (confirmResult == ConfirmResult.Confirmed) { + navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri))) + viewModel.markNeedRefresh() + } + } + } + } + + val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost) + val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost) + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) LaunchedEffect(Unit) { @@ -160,43 +276,62 @@ fun ModuleScreen(navigator: DestinationsNavigator) { contentDescription = stringResource(id = R.string.settings) ) - DropdownMenu(expanded = showDropdown, onDismissRequest = { - showDropdown = false - }) { - DropdownMenuItem(text = { - Text(stringResource(R.string.module_sort_action_first)) - }, trailingIcon = { - Checkbox(viewModel.sortActionFirst, null) - }, onClick = { - viewModel.sortActionFirst = - !viewModel.sortActionFirst - prefs.edit() - .putBoolean( - "module_sort_action_first", - viewModel.sortActionFirst - ) - .apply() - scope.launch { - viewModel.fetchModuleList() + DropdownMenu( + expanded = showDropdown, + onDismissRequest = { showDropdown = false } + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.module_sort_action_first)) }, + trailingIcon = { Checkbox(viewModel.sortActionFirst, null) }, + onClick = { + viewModel.sortActionFirst = !viewModel.sortActionFirst + prefs.edit() + .putBoolean("module_sort_action_first", viewModel.sortActionFirst) + .apply() + scope.launch { + viewModel.fetchModuleList() + } } - }) - DropdownMenuItem(text = { - Text(stringResource(R.string.module_sort_enabled_first)) - }, trailingIcon = { - Checkbox(viewModel.sortEnabledFirst, null) - }, onClick = { - viewModel.sortEnabledFirst = - !viewModel.sortEnabledFirst - prefs.edit() - .putBoolean( - "module_sort_enabled_first", - viewModel.sortEnabledFirst - ) - .apply() - scope.launch { - viewModel.fetchModuleList() + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.module_sort_enabled_first)) }, + trailingIcon = { Checkbox(viewModel.sortEnabledFirst, null) }, + onClick = { + viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst + prefs.edit() + .putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst) + .apply() + scope.launch { + viewModel.fetchModuleList() + } } - }) + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.backup_modules)) }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.Download, + contentDescription = "备份" + ) + }, + onClick = { + showDropdown = false + backupLauncher.launch(ModuleModify.createBackupIntent()) + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.restore_modules)) }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.Refresh, + contentDescription = "还原" + ) + }, + onClick = { + showDropdown = false + restoreLauncher.launch(ModuleModify.createRestoreIntent()) + } + ) } } }, @@ -206,63 +341,36 @@ fun ModuleScreen(navigator: DestinationsNavigator) { floatingActionButton = { if (!hideInstallButton) { val moduleInstall = stringResource(id = R.string.module_install) - val confirmTitle = stringResource(R.string.module) - var zipUris by remember { mutableStateOf>(emptyList()) } - val confirmDialog = rememberConfirmDialog(onConfirm = { - navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(zipUris))) - viewModel.markNeedRefresh() - }) - val selectZipLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult() - ) { - if (it.resultCode != RESULT_OK) { - return@rememberLauncherForActivityResult - } - val data = it.data ?: return@rememberLauncherForActivityResult - val clipData = data.clipData - - val uris = mutableListOf() - if (clipData != null) { - for (i in 0 until clipData.itemCount) { - clipData.getItemAt(i)?.uri?.let { uris.add(it) } - } - } else { - data.data?.let { uris.add(it) } - } - - if (uris.size == 1) { - navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(listOf(uris.first())))) - } else if (uris.size > 1) { - // multiple files selected - val moduleNames = uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }.joinToString("") - val confirmContent = context.getString(R.string.module_install_prompt_with_name, moduleNames) - zipUris = uris - confirmDialog.showConfirm( - title = confirmTitle, - content = confirmContent, - markdown = true - ) - } - } - ExtendedFloatingActionButton( onClick = { - // Select the zip files to install - val intent = Intent(Intent.ACTION_GET_CONTENT).apply { - type = "application/zip" - putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) - } - selectZipLauncher.launch(intent) + selectZipLauncher.launch( + Intent(Intent.ACTION_GET_CONTENT).apply { + type = "application/zip" + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) + } + ) }, - icon = { Icon(Icons.Filled.Add, moduleInstall) }, - text = { Text(text = moduleInstall) }, + icon = { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = moduleInstall, + tint = buttonTextColor + ) + }, + text = { + Text( + text = moduleInstall, + color = buttonTextColor + ) + } ) } }, - contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), + contentWindowInsets = WindowInsets.safeDrawing.only( + WindowInsetsSides.Top + WindowInsetsSides.Horizontal + ), snackbarHost = { SnackbarHost(hostState = snackBarHost) } ) { innerPadding -> - when { hasMagisk -> { Box( @@ -277,15 +385,14 @@ fun ModuleScreen(navigator: DestinationsNavigator) { ) } } - else -> { ModuleList( - navigator, + navigator = navigator, viewModel = viewModel, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), boxModifier = Modifier.padding(innerPadding), onInstallModule = { - navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(listOf(it)))) + navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it))) }, onClickModule = { id, name, hasWebUi -> if (hasWebUi) { @@ -345,7 +452,7 @@ private fun ModuleList( val changelogResult = loadingDialog.withLoading { withContext(Dispatchers.IO) { runCatching { - ksuApp.okhttpClient.newCall( + OkHttpClient().newCall( okhttp3.Request.Builder().url(changelogUrl).build() ).execute().body!!.string() } @@ -560,7 +667,8 @@ fun ModuleItem( onClick: (ModuleViewModel.ModuleInfo) -> Unit ) { ElevatedCard( - modifier = Modifier.fillMaxWidth() + colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer), + elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation()) ) { val textDecoration = if (!module.remove) null else TextDecoration.LineThrough val interactionSource = remember { MutableInteractionSource() } @@ -617,7 +725,7 @@ fun ModuleItem( fontSize = MaterialTheme.typography.bodySmall.fontSize, lineHeight = MaterialTheme.typography.bodySmall.lineHeight, fontFamily = MaterialTheme.typography.bodySmall.fontFamily, - textDecoration = textDecoration + textDecoration = textDecoration ) } @@ -668,7 +776,11 @@ fun ModuleItem( navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId)) viewModel.markNeedRefresh() }, - contentPadding = ButtonDefaults.TextButtonContentPadding + contentPadding = ButtonDefaults.TextButtonContentPadding, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = Color.White, + contentColor = Color.Black + ) ) { Icon( modifier = Modifier.size(20.dp), @@ -694,7 +806,11 @@ fun ModuleItem( enabled = !module.remove && module.enabled, onClick = { onClick(module) }, interactionSource = interactionSource, - contentPadding = ButtonDefaults.TextButtonContentPadding + contentPadding = ButtonDefaults.TextButtonContentPadding, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = Color.White, + contentColor = Color.Black + ) ) { Icon( modifier = Modifier.size(20.dp), @@ -720,7 +836,11 @@ fun ModuleItem( enabled = !module.remove, onClick = { onUpdate(module) }, shape = ButtonDefaults.textShape, - contentPadding = ButtonDefaults.TextButtonContentPadding + contentPadding = ButtonDefaults.TextButtonContentPadding, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = Color.White, + contentColor = Color.Black + ) ) { Icon( modifier = Modifier.size(20.dp), @@ -743,7 +863,11 @@ fun ModuleItem( FilledTonalButton( modifier = Modifier.defaultMinSize(52.dp, 32.dp), onClick = { onUninstallClicked(module) }, - contentPadding = ButtonDefaults.TextButtonContentPadding + contentPadding = ButtonDefaults.TextButtonContentPadding, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = Color.White, + contentColor = Color.Black + ) ) { if (!module.remove) { Icon( @@ -756,6 +880,7 @@ fun ModuleItem( modifier = Modifier.size(20.dp).rotate(180f), imageVector = Icons.Outlined.Refresh, contentDescription = null, + ) } if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) { @@ -792,3 +917,4 @@ fun ModuleItemPreview() { ) ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {}) } + diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/MoreSettings.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/MoreSettings.kt new file mode 100644 index 00000000..b112845c --- /dev/null +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/MoreSettings.kt @@ -0,0 +1,470 @@ +package shirkneko.zako.sukisu.ui.screen + +import android.content.Context +import android.net.Uri +import android.os.Build +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.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootGraph +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.SwitchItem +import shirkneko.zako.sukisu.ui.theme.CardConfig +import shirkneko.zako.sukisu.ui.theme.ThemeColors +import shirkneko.zako.sukisu.ui.theme.ThemeConfig +import shirkneko.zako.sukisu.ui.theme.saveCustomBackground +import shirkneko.zako.sukisu.ui.theme.saveThemeColors +import shirkneko.zako.sukisu.ui.theme.saveThemeMode +import shirkneko.zako.sukisu.ui.theme.saveDynamicColorState +import shirkneko.zako.sukisu.ui.util.getSuSFS +import shirkneko.zako.sukisu.ui.util.getSuSFSFeatures +import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_0 +import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_2 +import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_Mode + +fun saveCardConfig(context: Context) { + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + with(prefs.edit()) { + putFloat("card_alpha", CardConfig.cardAlpha) + putBoolean("custom_background_enabled", CardConfig.cardElevation == 0.dp) + apply() + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Destination +@Composable +fun MoreSettingsScreen(navigator: DestinationsNavigator) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + val context = LocalContext.current + val prefs = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) } + // 主题模式选择 + var themeMode by remember { + mutableStateOf( + when(ThemeConfig.forceDarkMode) { + true -> 2 // 深色 + false -> 1 // 浅色 + null -> 0 // 跟随系统 + } + ) + } + + // 动态颜色开关状态 + var useDynamicColor by remember { + mutableStateOf(ThemeConfig.useDynamicColor) + } + + var showThemeModeDialog by remember { mutableStateOf(false) } + // 主题模式选项 + val themeOptions = listOf( + stringResource(R.string.theme_follow_system), + stringResource(R.string.theme_light), + stringResource(R.string.theme_dark) + ) + + // 简洁模块开关状态 + var isSimpleMode by remember { + mutableStateOf(prefs.getBoolean("is_simple_mode", false)) + } + + // 更新简洁模块开关状态 + val onSimpleModeChange = { newValue: Boolean -> + prefs.edit().putBoolean("is_simple_mode", newValue).apply() + isSimpleMode = newValue + } + + // SELinux 状态 + var selinuxEnabled by remember { + mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing") + } + + // 卡片配置状态 + var cardAlpha by rememberSaveable { mutableStateOf(CardConfig.cardAlpha) } + var showCardSettings by remember { mutableStateOf(false) } + var isCustomBackgroundEnabled by rememberSaveable { + mutableStateOf(ThemeConfig.customBackgroundUri != null) + } + + // 初始化卡片配置 + LaunchedEffect(Unit) { + CardConfig.apply { + cardAlpha = prefs.getFloat("card_alpha", 0.85f) + cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else CardConfig.defaultElevation + } + } + + // 主题色选项 + val themeColorOptions = listOf( + stringResource(R.string.color_default) to ThemeColors.Default, + stringResource(R.string.color_blue) to ThemeColors.Blue, + stringResource(R.string.color_green) to ThemeColors.Green, + stringResource(R.string.color_purple) to ThemeColors.Purple, + stringResource(R.string.color_orange) to ThemeColors.Orange, + stringResource(R.string.color_pink) to ThemeColors.Pink, + stringResource(R.string.color_gray) to ThemeColors.Gray, + stringResource(R.string.color_ivory) to ThemeColors.Ivory + ) + + var showThemeColorDialog by remember { mutableStateOf(false) } + + // 图片选择器 + val pickImageLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.GetContent() + ) { uri: Uri? -> + uri?.let { + context.saveCustomBackground(it) + isCustomBackgroundEnabled = true + CardConfig.cardElevation = 0.dp + saveCardConfig(context) + } + } + + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.more_settings)) }, + navigationIcon = { + IconButton(onClick = { navigator.popBackStack() }) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, null) + } + }, + scrollBehavior = scrollBehavior + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + .padding(top = 12.dp) + ) { + // SELinux 开关 + SwitchItem( + icon = Icons.Filled.Security, + title = stringResource(R.string.selinux), + summary = if (selinuxEnabled) + stringResource(R.string.selinux_enabled) else + stringResource(R.string.selinux_disabled), + checked = selinuxEnabled + ) { enabled -> + val command = if (enabled) "setenforce 1" else "setenforce 0" + Shell.getShell().newJob().add(command).exec().let { result -> + if (result.isSuccess) selinuxEnabled = enabled + } + } + + // 添加简洁模块开关 + SwitchItem( + icon = Icons.Filled.FormatPaint, + title = stringResource(R.string.simple_mode), + summary = stringResource(R.string.simple_mode_summary), + checked = isSimpleMode + ) { + onSimpleModeChange(it) + } + + // region SUSFS 配置(仅在支持时显示) + val suSFS = getSuSFS() + val isSUS_SU = getSuSFSFeatures() + if (suSFS == "Supported") { + if (isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") { + // 初始化时,默认启用 + var isEnabled by rememberSaveable { + mutableStateOf(true) // 默认启用 + } + + // 在启动时检查状态 + LaunchedEffect(Unit) { + // 如果当前模式不是2就强制启用 + val currentMode = susfsSUS_SU_Mode() + val wasManuallyDisabled = prefs.getBoolean("enable_sus_su", true) + if (currentMode != "2" && wasManuallyDisabled) { + susfsSUS_SU_2() // 强制切换到模式2 + prefs.edit().putBoolean("enable_sus_su", true).apply() + } + isEnabled = currentMode == "2" + } + + SwitchItem( + icon = Icons.Filled.VisibilityOff, + title = stringResource(id = R.string.settings_susfs_toggle), + summary = stringResource(id = R.string.settings_susfs_toggle_summary), + checked = isEnabled + ) { + if (it) { + // 手动启用 + susfsSUS_SU_2() + prefs.edit().putBoolean("enable_sus_su", true).apply() + } else { + // 手动关闭 + susfsSUS_SU_0() + prefs.edit().putBoolean("enable_sus_su", false).apply() + } + isEnabled = it + } + } + } + // endregion + // 动态颜色开关 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + SwitchItem( + icon = Icons.Filled.ColorLens, + title = stringResource(R.string.dynamic_color_title), + summary = stringResource(R.string.dynamic_color_summary), + checked = useDynamicColor + ) { enabled -> + useDynamicColor = enabled + context.saveDynamicColorState(enabled) + } + } + // 只在未启用动态颜色时显示主题色选择 + if (!useDynamicColor) { + ListItem( + leadingContent = { Icon(Icons.Default.Palette, null) }, + headlineContent = { Text("主题颜色") }, + supportingContent = { + val currentThemeName = when (ThemeConfig.currentTheme) { + is ThemeColors.Default -> stringResource(R.string.color_default) + is ThemeColors.Blue -> stringResource(R.string.color_blue) + is ThemeColors.Green -> stringResource(R.string.color_green) + is ThemeColors.Purple -> stringResource(R.string.color_purple) + is ThemeColors.Orange -> stringResource(R.string.color_orange) + is ThemeColors.Pink -> stringResource(R.string.color_pink) + is ThemeColors.Gray -> stringResource(R.string.color_gray) + is ThemeColors.Ivory -> stringResource(R.string.color_ivory) + else -> stringResource(R.string.color_default) + } + Text(currentThemeName) + }, + modifier = Modifier.clickable { showThemeColorDialog = true } + ) + + if (showThemeColorDialog) { + AlertDialog( + onDismissRequest = { showThemeColorDialog = false }, + title = { Text(stringResource(R.string.choose_theme_color)) }, + text = { + Column { + themeColorOptions.forEach { (name, theme) -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + context.saveThemeColors(when (theme) { + ThemeColors.Default -> "default" + ThemeColors.Blue -> "blue" + ThemeColors.Green -> "green" + ThemeColors.Purple -> "purple" + ThemeColors.Orange -> "orange" + ThemeColors.Pink -> "pink" + ThemeColors.Gray -> "gray" + ThemeColors.Ivory -> "ivory" + else -> "default" + }) + showThemeColorDialog = false + } + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = ThemeConfig.currentTheme::class == theme::class, + onClick = null + ) + Spacer(modifier = Modifier.width(8.dp)) + Box( + modifier = Modifier + .size(24.dp) + .background(theme.Primary, shape = CircleShape) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(name) + } + } + } + }, + confirmButton = {} + ) + } + } + + // 自定义背景开关 + SwitchItem( + icon = Icons.Filled.Wallpaper, + title = stringResource(id = R.string.settings_custom_background), + summary = stringResource(id = R.string.settings_custom_background_summary), + checked = isCustomBackgroundEnabled + ) { isChecked -> + if (isChecked) { + pickImageLauncher.launch("image/*") + } else { + context.saveCustomBackground(null) + isCustomBackgroundEnabled = false + CardConfig.cardElevation = CardConfig.defaultElevation + CardConfig.cardAlpha = 1f + saveCardConfig(context) + } + } + + // 卡片管理展开控制 + if (ThemeConfig.customBackgroundUri != null) { + ListItem( + leadingContent = { Icon(Icons.Default.ExpandMore, null) }, + headlineContent = { Text(stringResource(R.string.settings_card_manage)) }, + modifier = Modifier.clickable { showCardSettings = !showCardSettings } + ) + + if (showCardSettings) { + // 透明度 Slider + ListItem( + leadingContent = { Icon(Icons.Filled.Opacity, null) }, + headlineContent = { Text(stringResource(R.string.settings_card_alpha)) }, + supportingContent = { + Slider( + value = cardAlpha, + onValueChange = { newValue -> + cardAlpha = newValue + CardConfig.cardAlpha = newValue + prefs.edit().putFloat("card_alpha", newValue).apply() + }, + onValueChangeFinished = { + CoroutineScope(Dispatchers.IO).launch { + saveCardConfig(context) + } + }, + valueRange = 0f..1f, + // 确保使用自定义颜色 + colors = getSliderColors(cardAlpha, useCustomColors = true), + thumb = { + SliderDefaults.Thumb( + interactionSource = remember { MutableInteractionSource() }, + thumbSize = DpSize(0.dp, 0.dp) + ) + } + ) + } + ) + + + ListItem( + leadingContent = { Icon(Icons.Filled.DarkMode, null) }, + headlineContent = { Text(stringResource(R.string.theme_mode)) }, + supportingContent = { Text(themeOptions[themeMode]) }, + modifier = Modifier.clickable { + showThemeModeDialog = true + } + ) + + // 主题模式选择对话框 + if (showThemeModeDialog) { + AlertDialog( + onDismissRequest = { showThemeModeDialog = false }, + title = { Text(stringResource(R.string.theme_mode)) }, + text = { + Column { + themeOptions.forEachIndexed { index, option -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + themeMode = index + val newThemeMode = when(index) { + 0 -> null // 跟随系统 + 1 -> false // 浅色 + 2 -> true // 深色 + else -> null + } + context.saveThemeMode(newThemeMode) + showThemeModeDialog = false + } + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = themeMode == index, + onClick = null + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(option) + } + } + } + }, + confirmButton = {} + ) + } + } + } + } + } +} + +@Composable +private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false): SliderColors { + val theme = ThemeConfig.currentTheme + val isDarkTheme = ThemeConfig.forceDarkMode ?: isSystemInDarkTheme() + val useDynamicColor = ThemeConfig.useDynamicColor + + return when { + // 使用动态颜色时 + useDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + SliderDefaults.colors( + activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f), + inactiveTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f), + thumbColor = MaterialTheme.colorScheme.primary + ) + } + // 使用自定义主题色时 + useCustomColors -> { + SliderDefaults.colors( + activeTrackColor = theme.getCustomSliderActiveColor(), + inactiveTrackColor = theme.getCustomSliderInactiveColor(), + thumbColor = theme.Primary + ) + } + else -> { + val activeColor = if (isDarkTheme) { + theme.Primary.copy(alpha = cardAlpha) + } else { + theme.Primary.copy(alpha = cardAlpha) + } + val inactiveColor = if (isDarkTheme) { + Color.DarkGray.copy(alpha = 0.3f) + } else { + Color.LightGray.copy(alpha = 0.3f) + } + SliderDefaults.colors( + activeTrackColor = activeColor, + inactiveTrackColor = inactiveColor, + thumbColor = activeColor + ) + } + } +} diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Settings.kt similarity index 88% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Settings.kt index 1d6fcdee..f82483c5 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Settings.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.screen +package shirkneko.zako.sukisu.ui.screen import android.content.Context import android.content.Intent @@ -18,23 +18,10 @@ import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.Undo -import androidx.compose.material.icons.filled.BugReport -import androidx.compose.material.icons.filled.Compress -import androidx.compose.material.icons.filled.ContactPage -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.DeleteForever -import androidx.compose.material.icons.filled.DeveloperMode -import androidx.compose.material.icons.filled.Fence -import androidx.compose.material.icons.filled.FolderDelete -import androidx.compose.material.icons.filled.RemoveModerator -import androidx.compose.material.icons.filled.Save -import androidx.compose.material.icons.filled.Share -import androidx.compose.material.icons.filled.Update +import androidx.compose.material.icons.filled.* import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold @@ -62,7 +49,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.content.FileProvider -import androidx.lifecycle.compose.dropUnlessResumed import com.maxkeppeker.sheets.core.models.base.Header import com.maxkeppeker.sheets.core.models.base.IconSource import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState @@ -73,25 +59,30 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination +import com.ramcosta.composedestinations.generated.destinations.MoreSettingsScreenDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -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.ConfirmResult -import me.weishu.kernelsu.ui.component.DialogHandle -import me.weishu.kernelsu.ui.component.SwitchItem -import me.weishu.kernelsu.ui.component.rememberConfirmDialog -import me.weishu.kernelsu.ui.component.rememberCustomDialog -import me.weishu.kernelsu.ui.component.rememberLoadingDialog -import me.weishu.kernelsu.ui.util.LocalSnackbarHost -import me.weishu.kernelsu.ui.util.getBugreportFile +import shirkneko.zako.sukisu.BuildConfig +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.AboutDialog +import shirkneko.zako.sukisu.ui.component.ConfirmResult +import shirkneko.zako.sukisu.ui.component.DialogHandle +import shirkneko.zako.sukisu.ui.component.SwitchItem +import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog +import shirkneko.zako.sukisu.ui.component.rememberCustomDialog +import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog +import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost +import shirkneko.zako.sukisu.ui.util.getBugreportFile import java.time.LocalDateTime import java.time.format.DateTimeFormatter +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material3.MaterialTheme +import shirkneko.zako.sukisu.ui.theme.CardConfig + /** * @author weishu @@ -101,15 +92,14 @@ import java.time.format.DateTimeFormatter @Destination @Composable fun SettingScreen(navigator: DestinationsNavigator) { + // region 界面基础设置 val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val snackBarHost = LocalSnackbarHost.current + // endregion Scaffold( topBar = { TopBar( - onBack = dropUnlessResumed { - navigator.popBackStack() - }, scrollBehavior = scrollBehavior ) }, @@ -121,6 +111,7 @@ fun SettingScreen(navigator: DestinationsNavigator) { } val loadingDialog = rememberLoadingDialog() val shrinkDialog = rememberConfirmDialog() + // endregion Column( modifier = Modifier @@ -128,10 +119,12 @@ fun SettingScreen(navigator: DestinationsNavigator) { .nestedScroll(scrollBehavior.nestedScrollConnection) .verticalScroll(rememberScrollState()) ) { - + // region 上下文与协程 val context = LocalContext.current val scope = rememberCoroutineScope() + // endregion + // region 日志导出功能 val exportBugreportLauncher = rememberLauncherForActivityResult( ActivityResultContracts.CreateDocument("application/gzip") ) { uri: Uri? -> @@ -146,8 +139,10 @@ fun SettingScreen(navigator: DestinationsNavigator) { loadingDialog.hide() snackBarHost.showSnackbar(context.getString(R.string.log_saved)) } + // endregion } - + // region 配置项列表 + // 配置文件模板入口 val profileTemplate = stringResource(id = R.string.settings_profile_template) ListItem( leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) }, @@ -157,7 +152,7 @@ fun SettingScreen(navigator: DestinationsNavigator) { navigator.navigate(AppProfileTemplateScreenDestination) } ) - + // 卸载模块开关 var umountChecked by rememberSaveable { mutableStateOf(Natives.isDefaultUmountModules()) } @@ -171,7 +166,7 @@ fun SettingScreen(navigator: DestinationsNavigator) { umountChecked = it } } - + // SU 禁用开关(仅在兼容版本显示) if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) { var isSuDisabled by rememberSaveable { mutableStateOf(!Natives.isSuEnabled()) @@ -190,6 +185,8 @@ fun SettingScreen(navigator: DestinationsNavigator) { } val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + + // 更新检查开关 var checkUpdate by rememberSaveable { mutableStateOf( prefs.getBoolean("check_update", true) @@ -205,6 +202,7 @@ fun SettingScreen(navigator: DestinationsNavigator) { checkUpdate = it } + // Web调试开关 var enableWebDebugging by rememberSaveable { mutableStateOf( prefs.getBoolean("enable_web_debugging", false) @@ -219,6 +217,21 @@ fun SettingScreen(navigator: DestinationsNavigator) { prefs.edit().putBoolean("enable_web_debugging", it).apply() enableWebDebugging = it } + // endregion + val newButtonTitle = stringResource(id = R.string.more_settings) + ListItem( + leadingContent = { + Icon( + Icons.Filled.ExpandMore, + contentDescription = newButtonTitle + ) + }, + headlineContent = { Text(newButtonTitle) }, + supportingContent = { Text(stringResource(id = R.string.more_settings)) }, + modifier = Modifier.clickable { + navigator.navigate(MoreSettingsScreenDestination) + } + ) var showBottomsheet by remember { mutableStateOf(false) } @@ -458,18 +471,17 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle { @OptIn(ExperimentalMaterial3Api::class) @Composable private fun TopBar( - onBack: () -> Unit = {}, scrollBehavior: TopAppBarScrollBehavior? = null ) { + val cardColor = MaterialTheme.colorScheme.secondaryContainer + val cardAlpha = CardConfig.cardAlpha TopAppBar( title = { Text(stringResource(R.string.settings)) }, - navigationIcon = { - IconButton( - onClick = onBack - ) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) - } - }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = cardColor.copy(alpha = cardAlpha), + scrolledContainerColor = cardColor.copy(alpha = cardAlpha) + ), + windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), scrollBehavior = scrollBehavior ) @@ -480,3 +492,5 @@ private fun TopBar( private fun SettingsPreview() { SettingScreen(EmptyDestinationsNavigator) } + + diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/SuperUser.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/SuperUser.kt new file mode 100644 index 00000000..8e21afae --- /dev/null +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/SuperUser.kt @@ -0,0 +1,403 @@ +package shirkneko.zako.sukisu.ui.screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootGraph +import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlinx.coroutines.launch +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.SearchAppBar +import shirkneko.zako.sukisu.ui.util.ModuleModify +import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel + +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) +@Destination +@Composable +fun SuperUserScreen(navigator: DestinationsNavigator) { + val viewModel = viewModel() + val scope = rememberCoroutineScope() + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + val listState = rememberLazyListState() + val context = LocalContext.current + val snackBarHostState = remember { SnackbarHostState() } + + // 添加备份和还原启动器 + val backupLauncher = ModuleModify.rememberAllowlistBackupLauncher(context, snackBarHostState) + val restoreLauncher = ModuleModify.rememberAllowlistRestoreLauncher(context, snackBarHostState) + + LaunchedEffect(key1 = navigator) { + viewModel.search = "" + if (viewModel.appList.isEmpty()) { + viewModel.fetchAppList() + } + } + + LaunchedEffect(viewModel.search) { + if (viewModel.search.isEmpty()) { + listState.scrollToItem(0) + } + } + + Scaffold( + topBar = { + SearchAppBar( + title = { Text(stringResource(R.string.superuser)) }, + searchText = viewModel.search, + onSearchTextChange = { viewModel.search = it }, + onClearClick = { viewModel.search = "" }, + dropdownContent = { + var showDropdown by remember { mutableStateOf(false) } + + IconButton( + onClick = { showDropdown = true }, + ) { + Icon( + imageVector = Icons.Filled.MoreVert, + contentDescription = stringResource(id = R.string.settings) + ) + + DropdownMenu(expanded = showDropdown, onDismissRequest = { + showDropdown = false + }) { + DropdownMenuItem(text = { + Text(stringResource(R.string.refresh)) + }, onClick = { + scope.launch { + viewModel.fetchAppList() + } + showDropdown = false + }) + DropdownMenuItem(text = { + Text( + if (viewModel.showSystemApps) { + stringResource(R.string.hide_system_apps) + } else { + stringResource(R.string.show_system_apps) + } + ) + }, onClick = { + viewModel.showSystemApps = !viewModel.showSystemApps + showDropdown = false + }) + // 批量操作菜单项已移除 + DropdownMenuItem(text = { + Text(stringResource(R.string.backup_allowlist)) + }, onClick = { + backupLauncher.launch(ModuleModify.createAllowlistBackupIntent()) + showDropdown = false + }) + DropdownMenuItem(text = { + Text(stringResource(R.string.restore_allowlist)) + }, onClick = { + restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent()) + showDropdown = false + }) + } + } + }, + scrollBehavior = scrollBehavior + ) + }, + snackbarHost = { SnackbarHost(snackBarHostState) }, + contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), + bottomBar = { + // 批量操作按钮,直接放在底部栏 + if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surface) + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Button( + onClick = { + scope.launch { + viewModel.updateBatchPermissions(true) + } + } + ) { + Text("批量授权") + } + + Button( + onClick = { + scope.launch { + viewModel.updateBatchPermissions(false) + } + } + ) { + Text("批量取消授权") + } + } + } + } + ) { innerPadding -> + PullToRefreshBox( + modifier = Modifier.padding(innerPadding), + onRefresh = { + scope.launch { viewModel.fetchAppList() } + }, + isRefreshing = viewModel.isRefreshing + ) { + LazyColumn( + state = listState, + modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection) + ) { + // 获取分组后的应用列表 - 修改分组逻辑,避免应用重复出现在多个分组中 + val rootApps = viewModel.appList.filter { it.allowSu } + val customApps = viewModel.appList.filter { !it.allowSu && it.hasCustomProfile } + val otherApps = viewModel.appList.filter { !it.allowSu && !it.hasCustomProfile } + + // 显示ROOT权限应用组 + if (rootApps.isNotEmpty()) { + item { + GroupHeader(title = "ROOT 权限应用") + } + items(rootApps, key = { "root_" + it.packageName + it.uid }) { app -> + AppItem( + app = app, + isSelected = viewModel.selectedApps.contains(app.packageName), + onToggleSelection = { viewModel.toggleAppSelection(app.packageName) }, + onSwitchChange = { allowSu -> + scope.launch { + val profile = Natives.getAppProfile(app.packageName, app.uid) + val updatedProfile = profile.copy(allowSu = allowSu) + if (Natives.setAppProfile(updatedProfile)) { + viewModel.fetchAppList() + } + } + }, + onClick = { + if (viewModel.showBatchActions) { + viewModel.toggleAppSelection(app.packageName) + } else { + navigator.navigate(AppProfileScreenDestination(app)) + } + }, + onLongClick = { + // 长按进入多选模式 + if (!viewModel.showBatchActions) { + viewModel.toggleBatchMode() + viewModel.toggleAppSelection(app.packageName) + } + }, + viewModel = viewModel + ) + } + } + + // 显示自定义配置应用组 + if (customApps.isNotEmpty()) { + item { + GroupHeader(title = "自定义配置应用") + } + items(customApps, key = { "custom_" + it.packageName + it.uid }) { app -> + AppItem( + app = app, + isSelected = viewModel.selectedApps.contains(app.packageName), + onToggleSelection = { viewModel.toggleAppSelection(app.packageName) }, + onSwitchChange = { allowSu -> + scope.launch { + val profile = Natives.getAppProfile(app.packageName, app.uid) + val updatedProfile = profile.copy(allowSu = allowSu) + if (Natives.setAppProfile(updatedProfile)) { + viewModel.fetchAppList() + } + } + }, + onClick = { + if (viewModel.showBatchActions) { + viewModel.toggleAppSelection(app.packageName) + } else { + navigator.navigate(AppProfileScreenDestination(app)) + } + }, + onLongClick = { + // 长按进入多选模式 + if (!viewModel.showBatchActions) { + viewModel.toggleBatchMode() + viewModel.toggleAppSelection(app.packageName) + } + }, + viewModel = viewModel + ) + } + } + + // 显示其他应用组 + if (otherApps.isNotEmpty()) { + item { + GroupHeader(title = "其他应用") + } + items(otherApps, key = { "other_" + it.packageName + it.uid }) { app -> + AppItem( + app = app, + isSelected = viewModel.selectedApps.contains(app.packageName), + onToggleSelection = { viewModel.toggleAppSelection(app.packageName) }, + onSwitchChange = { allowSu -> + scope.launch { + val profile = Natives.getAppProfile(app.packageName, app.uid) + val updatedProfile = profile.copy(allowSu = allowSu) + if (Natives.setAppProfile(updatedProfile)) { + viewModel.fetchAppList() + } + } + }, + onClick = { + if (viewModel.showBatchActions) { + viewModel.toggleAppSelection(app.packageName) + } else { + navigator.navigate(AppProfileScreenDestination(app)) + } + }, + onLongClick = { + // 长按进入多选模式 + if (!viewModel.showBatchActions) { + viewModel.toggleBatchMode() + viewModel.toggleAppSelection(app.packageName) + } + }, + viewModel = viewModel + ) + } + } + } + } + } +} + +@Composable +fun GroupHeader(title: String) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceVariant) + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Text( + text = title, + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + ) + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun AppItem( + app: SuperUserViewModel.AppInfo, + isSelected: Boolean, + onToggleSelection: () -> Unit, + onSwitchChange: (Boolean) -> Unit, + onClick: () -> Unit, + onLongClick: () -> Unit, + viewModel: SuperUserViewModel +) { + ListItem( + modifier = Modifier + .pointerInput(Unit) { + detectTapGestures( + onLongPress = { onLongClick() }, + onTap = { onClick() } + ) + }, + headlineContent = { Text(app.label) }, + supportingContent = { + Column { + Text(app.packageName) + FlowRow { + if (app.allowSu) { + LabelText(label = "ROOT") + } else { + if (Natives.uidShouldUmount(app.uid)) { + LabelText(label = "UMOUNT") + } + } + if (app.hasCustomProfile) { + LabelText(label = "CUSTOM") + } + } + } + }, + leadingContent = { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(app.packageInfo) + .crossfade(true) + .build(), + contentDescription = app.label, + modifier = Modifier + .padding(4.dp) + .width(48.dp) + .height(48.dp) + ) + }, + trailingContent = { + if (!viewModel.showBatchActions) { + Switch( + checked = app.allowSu, + onCheckedChange = onSwitchChange + ) + } else { + Checkbox( + checked = isSelected, + onCheckedChange = { onToggleSelection() } + ) + } + } + ) +} + +@Composable +fun LabelText(label: String) { + Box( + modifier = Modifier + .padding(top = 4.dp, end = 4.dp) + .background( + Color.Black, + shape = RoundedCornerShape(4.dp) + ) + ) { + Text( + text = label, + modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp), + style = TextStyle( + fontSize = 8.sp, + color = Color.White, + ) + ) + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Template.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Template.kt similarity index 98% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Template.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Template.kt index 4904f160..4333a287 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Template.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/Template.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.screen +package shirkneko.zako.sukisu.ui.screen import android.widget.Toast import androidx.compose.foundation.clickable @@ -49,7 +49,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.dropUnlessResumed import androidx.lifecycle.viewmodel.compose.viewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph @@ -59,8 +58,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient import com.ramcosta.composedestinations.result.getOr import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import me.weishu.kernelsu.R -import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel +import androidx.lifecycle.compose.dropUnlessResumed /** * @author weishu diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/TemplateEditor.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/TemplateEditor.kt similarity index 96% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/screen/TemplateEditor.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/TemplateEditor.kt index 535b5c30..3a7dee79 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/TemplateEditor.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/TemplateEditor.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.screen +package shirkneko.zako.sukisu.ui.screen import android.widget.Toast import androidx.activity.compose.BackHandler @@ -44,18 +44,18 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import androidx.lifecycle.compose.dropUnlessResumed import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.result.ResultBackNavigator -import me.weishu.kernelsu.Natives -import me.weishu.kernelsu.R -import me.weishu.kernelsu.ui.component.profile.RootProfileConfig -import me.weishu.kernelsu.ui.util.deleteAppProfileTemplate -import me.weishu.kernelsu.ui.util.getAppProfileTemplate -import me.weishu.kernelsu.ui.util.setAppProfileTemplate -import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel -import me.weishu.kernelsu.ui.viewmodel.toJSON +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.R +import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig +import shirkneko.zako.sukisu.ui.util.deleteAppProfileTemplate +import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate +import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate +import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel +import shirkneko.zako.sukisu.ui.viewmodel.toJSON +import androidx.lifecycle.compose.dropUnlessResumed /** * @author weishu diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/CardManage.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/CardManage.kt new file mode 100644 index 00000000..1e8cf91a --- /dev/null +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/CardManage.kt @@ -0,0 +1,42 @@ +package shirkneko.zako.sukisu.ui.theme + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.material3.CardDefaults + +object CardConfig { + val defaultElevation: Dp = 2.dp + + var cardAlpha by mutableStateOf(1f) + var cardElevation by mutableStateOf(defaultElevation) + + fun save(context: Context) { + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + prefs.edit().apply { + putFloat("card_alpha", cardAlpha) + putBoolean("custom_background_enabled", cardElevation == 0.dp) + apply() + } + } + + fun load(context: Context) { + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + cardAlpha = prefs.getFloat("card_alpha", 1f) + cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation + } +} + +@Composable +fun getCardColors(originalColor: Color) = CardDefaults.elevatedCardColors( + containerColor = originalColor.copy(alpha = CardConfig.cardAlpha), + contentColor = if (originalColor.luminance() > 0.5) Color.Black else Color.White +) + +fun getCardElevation() = CardConfig.cardElevation diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/Color.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/Color.kt new file mode 100644 index 00000000..1f97a2e9 --- /dev/null +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/Color.kt @@ -0,0 +1,162 @@ +package shirkneko.zako.sukisu.ui.theme + +import androidx.compose.ui.graphics.Color + +sealed class ThemeColors { + abstract val Primary: Color + abstract val Secondary: Color + abstract val Tertiary: Color + abstract val OnPrimary: Color + abstract val OnSecondary: Color + abstract val OnTertiary: Color + abstract val PrimaryContainer: Color + abstract val SecondaryContainer: Color + abstract val TertiaryContainer: Color + abstract val OnPrimaryContainer: Color + abstract val OnSecondaryContainer: Color + abstract val OnTertiaryContainer: Color + + open fun getCustomSliderActiveColor(): Color = Primary + open fun getCustomSliderInactiveColor(): Color = PrimaryContainer + + // Default Theme (Yellow) + object Default : ThemeColors() { + override val Primary = Color(0xFFFFD700) + override val Secondary = Color(0xFFFFBC52) + override val Tertiary = Color(0xFF795548) + override val OnPrimary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFFFFFFFF) + override val OnTertiary = Color(0xFFFFFFFF) + override val PrimaryContainer = Color(0xFFFFFBE9) + override val SecondaryContainer = Color(0xFFFFE6B3) + override val TertiaryContainer = Color(0xFFD7CCC8) + override val OnPrimaryContainer = Color(0xFF000000) + override val OnSecondaryContainer = Color(0xFF000000) + override val OnTertiaryContainer = Color(0xFF000000) + } + + // Blue Theme + object Blue : ThemeColors() { + override val Primary = Color(0xFF2196F3) + override val Secondary = Color(0xFF1E88E5) + override val Tertiary = Color(0xFF0D47A1) + override val OnPrimary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFFFFFFFF) + override val OnTertiary = Color(0xFFFFFFFF) + override val PrimaryContainer = Color(0xFFE3F2FD) + override val SecondaryContainer = Color(0xFFBBDEFB) + override val TertiaryContainer = Color(0xFF90CAF9) + override val OnPrimaryContainer = Color(0xFF000000) + override val OnSecondaryContainer = Color(0xFF000000) + override val OnTertiaryContainer = Color(0xFF000000) + } + + // Green Theme + object Green : ThemeColors() { + override val Primary = Color(0xFF4CAF50) + override val Secondary = Color(0xFF43A047) + override val Tertiary = Color(0xFF1B5E20) + override val OnPrimary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFFFFFFFF) + override val OnTertiary = Color(0xFFFFFFFF) + override val PrimaryContainer = Color(0xFFE8F5E9) + override val SecondaryContainer = Color(0xFFC8E6C9) + override val TertiaryContainer = Color(0xFFA5D6A7) + override val OnPrimaryContainer = Color(0xFF000000) + override val OnSecondaryContainer = Color(0xFF000000) + override val OnTertiaryContainer = Color(0xFF000000) + } + + // Purple Theme + object Purple : ThemeColors() { + override val Primary = Color(0xFF9C27B0) + override val Secondary = Color(0xFF8E24AA) + override val Tertiary = Color(0xFF4A148C) + override val OnPrimary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFFFFFFFF) + override val OnTertiary = Color(0xFFFFFFFF) + override val PrimaryContainer = Color(0xFFF3E5F5) + override val SecondaryContainer = Color(0xFFE1BEE7) + override val TertiaryContainer = Color(0xFFCE93D8) + override val OnPrimaryContainer = Color(0xFF000000) + override val OnSecondaryContainer = Color(0xFF000000) + override val OnTertiaryContainer = Color(0xFF000000) + } + + // Orange Theme + object Orange : ThemeColors() { + override val Primary = Color(0xFFFF9800) + override val Secondary = Color(0xFFFB8C00) + override val Tertiary = Color(0xFFE65100) + override val OnPrimary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFFFFFFFF) + override val OnTertiary = Color(0xFFFFFFFF) + override val PrimaryContainer = Color(0xFFFFF3E0) + override val SecondaryContainer = Color(0xFFFFE0B2) + override val TertiaryContainer = Color(0xFFFFCC80) + override val OnPrimaryContainer = Color(0xFF000000) + override val OnSecondaryContainer = Color(0xFF000000) + override val OnTertiaryContainer = Color(0xFF000000) + } + + // Pink Theme + object Pink : ThemeColors() { + override val Primary = Color(0xFFE91E63) + override val Secondary = Color(0xFFD81B60) + override val Tertiary = Color(0xFF880E4F) + override val OnPrimary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFFFFFFFF) + override val OnTertiary = Color(0xFFFFFFFF) + override val PrimaryContainer = Color(0xFFFCE4EC) + override val SecondaryContainer = Color(0xFFF8BBD0) + override val TertiaryContainer = Color(0xFFF48FB1) + override val OnPrimaryContainer = Color(0xFF000000) + override val OnSecondaryContainer = Color(0xFF000000) + override val OnTertiaryContainer = Color(0xFF000000) + } + + // Gray Theme + object Gray : ThemeColors() { + override val Primary = Color(0xFF9E9E9E) + override val Secondary = Color(0xFF757575) + override val Tertiary = Color(0xFF616161) + override val OnPrimary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFFFFFFFF) + override val OnTertiary = Color(0xFFFFFFFF) + override val PrimaryContainer = Color(0xFFEEEEEE) + override val SecondaryContainer = Color(0xFFE0E0E0) + override val TertiaryContainer = Color(0xFFBDBDBD) + override val OnPrimaryContainer = Color(0xFF000000) + override val OnSecondaryContainer = Color(0xFF000000) + override val OnTertiaryContainer = Color(0xFF000000) + } + + // Ivory Theme + object Ivory : ThemeColors() { + override val Primary = Color(0xFFFAF0E6) + override val Secondary = Color(0xFFFFF0E6) + override val Tertiary = Color(0xFFD7CCC8) + override val OnPrimary = Color(0xFFFFFFFF) + override val OnSecondary = Color(0xFFFFFFFF) + override val OnTertiary = Color(0xFFFFFFFF) + override val PrimaryContainer = Color(0xFFFFFAE3) + override val SecondaryContainer = Color(0xFFFFF0E6) + override val TertiaryContainer = Color(0xFFFFF0E6) + override val OnPrimaryContainer = Color(0xFF000000) + override val OnSecondaryContainer = Color(0xFF000000) + override val OnTertiaryContainer = Color(0xFF000000) + } + + companion object { + fun fromName(name: String): ThemeColors = when (name.lowercase()) { + "blue" -> Blue + "green" -> Green + "purple" -> Purple + "orange" -> Orange + "pink" -> Pink + "gray" -> Gray + "ivory" -> Ivory + else -> Default + } + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/Theme.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/Theme.kt new file mode 100644 index 00000000..7ae50a94 --- /dev/null +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/Theme.kt @@ -0,0 +1,289 @@ +package shirkneko.zako.sukisu.ui.theme + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.os.Build +import android.util.Log +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.paint +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.zIndex +import coil.compose.rememberAsyncImagePainter +import androidx.compose.foundation.background +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream + +object ThemeConfig { + var customBackgroundUri by mutableStateOf(null) + var forceDarkMode by mutableStateOf(null) + var currentTheme by mutableStateOf(ThemeColors.Default) + var useDynamicColor by mutableStateOf(false) +} + +@Composable +private fun getDarkColorScheme() = darkColorScheme( + primary = ThemeConfig.currentTheme.Primary, + onPrimary = ThemeConfig.currentTheme.OnPrimary, + primaryContainer = ThemeConfig.currentTheme.PrimaryContainer, + onPrimaryContainer = Color.White, + secondary = ThemeConfig.currentTheme.Secondary, + onSecondary = ThemeConfig.currentTheme.OnSecondary, + secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer, + onSecondaryContainer = Color.White, + tertiary = ThemeConfig.currentTheme.Tertiary, + onTertiary = ThemeConfig.currentTheme.OnTertiary, + tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer, + onTertiaryContainer = Color.White, + background = Color.Transparent, + surface = Color.Transparent, + onBackground = Color.White, + onSurface = Color.White +) + +@Composable +private fun getLightColorScheme() = lightColorScheme( + primary = ThemeConfig.currentTheme.Primary, + onPrimary = ThemeConfig.currentTheme.OnPrimary, + primaryContainer = ThemeConfig.currentTheme.PrimaryContainer, + onPrimaryContainer = ThemeConfig.currentTheme.OnPrimaryContainer, + secondary = ThemeConfig.currentTheme.Secondary, + onSecondary = ThemeConfig.currentTheme.OnSecondary, + secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer, + onSecondaryContainer = ThemeConfig.currentTheme.OnSecondaryContainer, + tertiary = ThemeConfig.currentTheme.Tertiary, + onTertiary = ThemeConfig.currentTheme.OnTertiary, + tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer, + onTertiaryContainer = ThemeConfig.currentTheme.OnTertiaryContainer, + background = Color.Transparent, + surface = Color.Transparent +) + +// 复制图片到应用内部存储 +fun Context.copyImageToInternalStorage(uri: Uri): Uri? { + try { + val contentResolver: ContentResolver = contentResolver + val inputStream: InputStream = contentResolver.openInputStream(uri)!! + val fileName = "custom_background.jpg" + val file = File(filesDir, fileName) + val outputStream = FileOutputStream(file) + val buffer = ByteArray(4 * 1024) + var read: Int + while (inputStream.read(buffer).also { read = it } != -1) { + outputStream.write(buffer, 0, read) + } + outputStream.flush() + outputStream.close() + inputStream.close() + return Uri.fromFile(file) + } catch (e: Exception) { + Log.e("ImageCopy", "Failed to copy image: ${e.message}") + return null + } +} + +@Composable +fun KernelSUTheme( + darkTheme: Boolean = when(ThemeConfig.forceDarkMode) { + true -> true + false -> false + null -> isSystemInDarkTheme() + }, + dynamicColor: Boolean = ThemeConfig.useDynamicColor, + content: @Composable () -> Unit +) { + val context = LocalContext.current + context.loadCustomBackground() + context.loadThemeColors() + context.loadDynamicColorState() + + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + if (darkTheme) dynamicDarkColorScheme(context).copy( + background = Color.Transparent, + surface = Color.Transparent, + onBackground = Color.White, + onSurface = Color.White + ) else dynamicLightColorScheme(context).copy( + background = Color.Transparent, + surface = Color.Transparent + ) + } + darkTheme -> getDarkColorScheme() + else -> getLightColorScheme() + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography + ) { + Box(modifier = Modifier.fillMaxSize()) { + // 背景图层 + ThemeConfig.customBackgroundUri?.let { uri -> + Box( + modifier = Modifier + .fillMaxSize() + .zIndex(-1f) + ) { + // 背景图片 + Box( + modifier = Modifier + .fillMaxSize() + .paint( + painter = rememberAsyncImagePainter( + model = uri, + onError = { + ThemeConfig.customBackgroundUri = null + context.saveCustomBackground(null) + } + ), + contentScale = ContentScale.Crop + ) + ) + + // 亮度调节层 + Box( + modifier = Modifier + .fillMaxSize() + .background( + if (darkTheme) { + Color.Black.copy(alpha = 0.4f) + } else { + Color.White.copy(alpha = 0.1f) + } + ) + ) + + // 边缘渐变遮罩层 + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.radialGradient( + colors = listOf( + Color.Transparent, + if (darkTheme) { + Color.Black.copy(alpha = 0.5f) + } else { + Color.Black.copy(alpha = 0.2f) + } + ), + radius = 1200f + ) + ) + ) + } + } + // 内容图层 + Box( + modifier = Modifier + .fillMaxSize() + .zIndex(1f) + ) { + content() + } + } + } +} + + +fun Context.saveCustomBackground(uri: Uri?) { + val newUri = uri?.let { copyImageToInternalStorage(it) } + getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) + .edit() + .putString("custom_background", newUri?.toString()) + .apply() + ThemeConfig.customBackgroundUri = newUri +} + +fun Context.loadCustomBackground() { + val uriString = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) + .getString("custom_background", null) + ThemeConfig.customBackgroundUri = uriString?.let { Uri.parse(it) } +} + +fun Context.saveThemeMode(forceDark: Boolean?) { + getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) + .edit() + .putString("theme_mode", when(forceDark) { + true -> "dark" + false -> "light" + null -> "system" + }) + .apply() + ThemeConfig.forceDarkMode = forceDark +} + +fun Context.loadThemeMode() { + val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) + .getString("theme_mode", "system") + ThemeConfig.forceDarkMode = when(mode) { + "dark" -> true + "light" -> false + else -> null + } +} + +fun Context.saveThemeColors(themeName: String) { + getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) + .edit() + .putString("theme_colors", themeName) + .apply() + + ThemeConfig.currentTheme = when(themeName) { + "blue" -> ThemeColors.Blue + "green" -> ThemeColors.Green + "purple" -> ThemeColors.Purple + "orange" -> ThemeColors.Orange + "pink" -> ThemeColors.Pink + "gray" -> ThemeColors.Gray + "ivory" -> ThemeColors.Ivory + else -> ThemeColors.Default + } +} + +fun Context.loadThemeColors() { + val themeName = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) + .getString("theme_colors", "default") + + ThemeConfig.currentTheme = when(themeName) { + "blue" -> ThemeColors.Blue + "green" -> ThemeColors.Green + "purple" -> ThemeColors.Purple + "orange" -> ThemeColors.Orange + "pink" -> ThemeColors.Pink + "gray" -> ThemeColors.Gray + "ivory" -> ThemeColors.Ivory + else -> ThemeColors.Default + } +} + +fun Context.saveDynamicColorState(enabled: Boolean) { + getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) + .edit() + .putBoolean("use_dynamic_color", enabled) + .apply() + ThemeConfig.useDynamicColor = enabled +} + +fun Context.loadDynamicColorState() { + val enabled = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) + .getBoolean("use_dynamic_color", true) + ThemeConfig.useDynamicColor = enabled +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Type.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/Type.kt similarity index 95% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Type.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/Type.kt index e8d73313..171c50ec 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Type.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/theme/Type.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.theme +package shirkneko.zako.sukisu.ui.theme import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/CompositionProvider.kt similarity index 86% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/CompositionProvider.kt index c1b57483..debf62b9 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/CompositionProvider.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.util +package shirkneko.zako.sukisu.ui.util import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.compositionLocalOf diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/Downloader.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/Downloader.kt similarity index 73% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/util/Downloader.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/Downloader.kt index 3b799c45..e3ff97b7 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/Downloader.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/Downloader.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.util +package shirkneko.zako.sukisu.ui.util import android.annotation.SuppressLint import android.app.DownloadManager @@ -8,11 +8,11 @@ import android.content.Intent import android.content.IntentFilter import android.net.Uri import android.os.Environment +import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.core.content.ContextCompat -import me.weishu.kernelsu.ksuApp -import me.weishu.kernelsu.ui.util.module.LatestVersionInfo +import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo /** * @author weishu @@ -63,45 +63,62 @@ fun download( } fun checkNewVersion(): LatestVersionInfo { - val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest" - // default null value if failed + // 改为新的 release 接口 + val url = "https://api.github.com/repos/ShirkNeko/KernelSU/releases/latest" val defaultValue = LatestVersionInfo() - runCatching { - ksuApp.okhttpClient.newCall(okhttp3.Request.Builder().url(url).build()).execute() + return runCatching { + okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute() .use { response -> if (!response.isSuccessful) { + Log.d("CheckUpdate", "Network request failed: ${response.message}") return defaultValue } - val body = response.body?.string() ?: return defaultValue + val body = response.body?.string() + if (body == null) { + Log.d("CheckUpdate", "Response body is null") + return defaultValue + } + Log.d("CheckUpdate", "Response body: $body") val json = org.json.JSONObject(body) + + // 直接从 tag_name 提取版本号(如 v1.1) + val tagName = json.optString("tag_name", "") + val versionName = tagName.removePrefix("v") // 移除前缀 "v" + + // 从 body 字段获取更新日志(保留换行符) val changelog = json.optString("body") + .replace("\\r\\n", "\n") // 转换换行符 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(".apk")) { + if (!name.endsWith(".apk")) continue + + // 修改正则表达式,只匹配 SukiSU 和版本号 + val regex = Regex("SukiSU.*_(\\d+)-release") + val matchResult = regex.find(name) + if (matchResult == null) { + Log.d("CheckUpdate", "No match found in $name, skipping") continue } + val versionCode = matchResult.groupValues[1].toInt() - val regex = Regex("v(.+?)_(\\d+)-") - val matchResult = regex.find(name) ?: continue - val versionName = matchResult.groupValues[1] - val versionCode = matchResult.groupValues[2].toInt() val downloadUrl = asset.getString("browser_download_url") - return LatestVersionInfo( versionCode, downloadUrl, - changelog + changelog, + versionName ) } - + Log.d("CheckUpdate", "No valid apk asset found, returning default value") + defaultValue } - } - return defaultValue + }.getOrDefault(defaultValue) } + @Composable fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) { DisposableEffect(context) { @@ -141,3 +158,4 @@ fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) { } } } + diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HanziToPinyin.java b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/HanziToPinyin.java similarity index 99% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/util/HanziToPinyin.java rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/HanziToPinyin.java index d3d57cef..6dcae8af 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HanziToPinyin.java +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/HanziToPinyin.java @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.util; +package shirkneko.zako.sukisu.ui.util; /* * Copyright (C) 2009 The Android Open Source Project * diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HyperlinkText.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/HyperlinkText.kt similarity index 98% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/util/HyperlinkText.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/HyperlinkText.kt index 4473b828..29a28c11 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HyperlinkText.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/HyperlinkText.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.util +package shirkneko.zako.sukisu.ui.util import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.material3.MaterialTheme diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/KsuCli.kt similarity index 84% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/KsuCli.kt index 49ecf06d..00321f60 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/KsuCli.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.util +package shirkneko.zako.sukisu.ui.util import android.content.ContentResolver import android.content.Context @@ -16,9 +16,9 @@ import com.topjohnwu.superuser.ShellUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize -import me.weishu.kernelsu.BuildConfig -import me.weishu.kernelsu.Natives -import me.weishu.kernelsu.ksuApp +import shirkneko.zako.sukisu.BuildConfig +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.ksuApp import org.json.JSONArray import java.io.File @@ -30,12 +30,7 @@ import java.io.File private const val TAG = "KsuCli" private fun getKsuDaemonPath(): String { - return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so" -} - -data class FlashResult(val code: Int, val err: String, val showReboot: Boolean) { - constructor(result: Shell.Result, showReboot: Boolean) : this(result.code, result.err.joinToString("\n"), showReboot) - constructor(result: Shell.Result) : this(result, result.isSuccess) + return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomk.so" } object KsuCli { @@ -104,7 +99,7 @@ fun execKsud(args: String, newShell: Boolean = false): Boolean { fun install() { val start = SystemClock.elapsedRealtime() - val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so").absolutePath + val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so").absolutePath val result = execKsud("install --magiskboot $magiskboot", true) Log.w(TAG, "install result: $result, cost: ${SystemClock.elapsedRealtime() - start}ms") } @@ -179,9 +174,10 @@ private fun flashWithIO( fun flashModule( uri: Uri, + onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit -): FlashResult { +): Boolean { val resolver = ksuApp.contentResolver with(resolver.openInputStream(uri)) { val file = File(ksuApp.cacheDir, "module.zip") @@ -194,7 +190,8 @@ fun flashModule( file.delete() - return FlashResult(result) + onFinish(result.isSuccess, result.code) + return result.isSuccess } } @@ -223,19 +220,21 @@ fun runModuleAction( } fun restoreBoot( - onStdout: (String) -> Unit, onStderr: (String) -> Unit -): FlashResult { - val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") + onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit +): Boolean { + val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so") val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr) - return FlashResult(result) + onFinish(result.isSuccess, result.code) + return result.isSuccess } fun uninstallPermanently( - onStdout: (String) -> Unit, onStderr: (String) -> Unit -): FlashResult { - val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") + onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit +): Boolean { + val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so") val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr) - return FlashResult(result) + onFinish(result.isSuccess, result.code) + return result.isSuccess } @Parcelize @@ -249,9 +248,10 @@ fun installBoot( bootUri: Uri?, lkm: LkmSelection, ota: Boolean, + onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit, -): FlashResult { +): Boolean { val resolver = ksuApp.contentResolver val bootFile = bootUri?.let { uri -> @@ -265,7 +265,7 @@ fun installBoot( } } - val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") + val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so") var cmd = "boot-patch --magiskboot ${magiskboot.absolutePath}" cmd += if (bootFile == null) { @@ -314,7 +314,8 @@ fun installBoot( lkmFile?.delete() // if boot uri is empty, it is direct install, when success, we should show reboot button - return FlashResult(result, bootUri == null && result.isSuccess) + onFinish(bootUri == null && result.isSuccess, result.code) + return result.isSuccess } fun reboot(reason: String = "") { @@ -434,3 +435,48 @@ fun restartApp(packageName: String) { forceStopApp(packageName) launchApp(packageName) } + +private fun getSuSFSDaemonPath(): String { + return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomksd.so" +} + +fun getSuSFS(): String { + val shell = getRootShell() + val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} support") + return result +} + +fun getSuSFSVersion(): String { + val shell = getRootShell() + val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} version") + return result +} + +fun getSuSFSVariant(): String { + val shell = getRootShell() + val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} variant") + return result +} +fun getSuSFSFeatures(): String { + val shell = getRootShell() + val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} features") + return result +} + +fun susfsSUS_SU_0(): String { + val shell = getRootShell() + val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 0") + return result +} + +fun susfsSUS_SU_2(): String { + val shell = getRootShell() + val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 2") + return result +} + +fun susfsSUS_SU_Mode(): String { + val shell = getRootShell() + val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su mode") + return result +} diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/LogEvent.kt similarity index 97% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/LogEvent.kt index a8363120..87d59443 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/LogEvent.kt @@ -1,11 +1,11 @@ -package me.weishu.kernelsu.ui.util +package shirkneko.zako.sukisu.ui.util import android.content.Context import android.os.Build import android.system.Os import com.topjohnwu.superuser.ShellUtils -import me.weishu.kernelsu.Natives -import me.weishu.kernelsu.ui.screen.getManagerVersion +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.ui.screen.getManagerVersion import java.io.File import java.io.FileWriter import java.io.PrintWriter diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/ModuleModify.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/ModuleModify.kt new file mode 100644 index 00000000..27f12882 --- /dev/null +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/ModuleModify.kt @@ -0,0 +1,330 @@ +package shirkneko.zako.sukisu.ui.util + +import android.app.AlertDialog +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.util.Log +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import shirkneko.zako.sukisu.R +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +object ModuleModify { + suspend fun showRestoreConfirmation(context: Context): Boolean { + val result = CompletableDeferred() + withContext(Dispatchers.Main) { + AlertDialog.Builder(context) + .setTitle(context.getString(R.string.restore_confirm_title)) + .setMessage(context.getString(R.string.restore_confirm_message)) + .setPositiveButton(context.getString(R.string.confirm)) { _, _ -> result.complete(true) } + .setNegativeButton(context.getString(R.string.cancel)) { _, _ -> result.complete(false) } + .setOnCancelListener { result.complete(false) } + .show() + } + return result.await() + } + + suspend fun backupModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) { + withContext(Dispatchers.IO) { + try { + val busyboxPath = "/data/adb/ksu/bin/busybox" + val moduleDir = "/data/adb/modules" + + // 直接将tar输出重定向到用户选择的文件 + val command = """ + cd "$moduleDir" && + $busyboxPath tar -cz ./* > /proc/self/fd/1 + """.trimIndent() + + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) + + // 直接将tar输出写入到用户选择的文件 + context.contentResolver.openOutputStream(uri)?.use { output -> + process.inputStream.copyTo(output) + } + + val error = BufferedReader(InputStreamReader(process.errorStream)).readText() + if (process.exitValue() != 0) { + throw IOException(context.getString(R.string.command_execution_failed, error)) + } + + withContext(Dispatchers.Main) { + snackBarHost.showSnackbar( + context.getString(R.string.backup_success), + duration = SnackbarDuration.Long + ) + } + + } catch (e: Exception) { + Log.e("Backup", context.getString(R.string.backup_failed, ""), e) + withContext(Dispatchers.Main) { + snackBarHost.showSnackbar( + context.getString(R.string.backup_failed, e.message), + duration = SnackbarDuration.Long + ) + } + } + } + } + + suspend fun restoreModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) { + val userConfirmed = showRestoreConfirmation(context) + if (!userConfirmed) return + + withContext(Dispatchers.IO) { + try { + val busyboxPath = "/data/adb/ksu/bin/busybox" + val moduleDir = "/data/adb/modules" + + // 直接从用户选择的文件读取并解压 + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "$busyboxPath tar -xz -C $moduleDir")) + + context.contentResolver.openInputStream(uri)?.use { input -> + input.copyTo(process.outputStream) + } + process.outputStream.close() + + process.waitFor() + + val error = BufferedReader(InputStreamReader(process.errorStream)).readText() + if (process.exitValue() != 0) { + throw IOException(context.getString(R.string.command_execution_failed, error)) + } + + withContext(Dispatchers.Main) { + val snackbarResult = snackBarHost.showSnackbar( + message = context.getString(R.string.restore_success), + actionLabel = context.getString(R.string.restart_now), + duration = SnackbarDuration.Long + ) + if (snackbarResult == SnackbarResult.ActionPerformed) { + reboot() + } + } + + } catch (e: Exception) { + Log.e("Restore", context.getString(R.string.restore_failed, ""), e) + withContext(Dispatchers.Main) { + snackBarHost.showSnackbar( + message = context.getString( + R.string.restore_failed, + e.message ?: context.getString(R.string.unknown_error) + ), + duration = SnackbarDuration.Long + ) + } + } + } + } + + suspend fun showAllowlistRestoreConfirmation(context: Context): Boolean { + val result = CompletableDeferred() + withContext(Dispatchers.Main) { + AlertDialog.Builder(context) + .setTitle(context.getString(R.string.allowlist_restore_confirm_title)) + .setMessage(context.getString(R.string.allowlist_restore_confirm_message)) + .setPositiveButton(context.getString(R.string.confirm)) { _, _ -> result.complete(true) } + .setNegativeButton(context.getString(R.string.cancel)) { _, _ -> result.complete(false) } + .setOnCancelListener { result.complete(false) } + .show() + } + return result.await() + } + + suspend fun backupAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) { + withContext(Dispatchers.IO) { + try { + val allowlistPath = "/data/adb/ksu/.allowlist" + + // 直接复制文件到用户选择的位置 + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat $allowlistPath")) + + context.contentResolver.openOutputStream(uri)?.use { output -> + process.inputStream.copyTo(output) + } + + val error = BufferedReader(InputStreamReader(process.errorStream)).readText() + if (process.exitValue() != 0) { + throw IOException(context.getString(R.string.command_execution_failed, error)) + } + + withContext(Dispatchers.Main) { + snackBarHost.showSnackbar( + context.getString(R.string.allowlist_backup_success), + duration = SnackbarDuration.Long + ) + } + + } catch (e: Exception) { + Log.e("AllowlistBackup", context.getString(R.string.allowlist_backup_failed, ""), e) + withContext(Dispatchers.Main) { + snackBarHost.showSnackbar( + context.getString(R.string.allowlist_backup_failed, e.message), + duration = SnackbarDuration.Long + ) + } + } + } + } + + suspend fun restoreAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) { + val userConfirmed = showAllowlistRestoreConfirmation(context) + if (!userConfirmed) return + + withContext(Dispatchers.IO) { + try { + val allowlistPath = "/data/adb/ksu/.allowlist" + + // 直接从用户选择的文件读取并写入到目标位置 + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat > $allowlistPath")) + + context.contentResolver.openInputStream(uri)?.use { input -> + input.copyTo(process.outputStream) + } + process.outputStream.close() + + process.waitFor() + + val error = BufferedReader(InputStreamReader(process.errorStream)).readText() + if (process.exitValue() != 0) { + throw IOException(context.getString(R.string.command_execution_failed, error)) + } + + withContext(Dispatchers.Main) { + snackBarHost.showSnackbar( + context.getString(R.string.allowlist_restore_success), + duration = SnackbarDuration.Long + ) + } + + } catch (e: Exception) { + Log.e("AllowlistRestore", context.getString(R.string.allowlist_restore_failed, ""), e) + withContext(Dispatchers.Main) { + snackBarHost.showSnackbar( + context.getString(R.string.allowlist_restore_failed, e.message), + duration = SnackbarDuration.Long + ) + } + } + } + } + + @Composable + fun rememberModuleBackupLauncher( + context: Context, + snackBarHost: SnackbarHostState, + scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope() + ) = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == android.app.Activity.RESULT_OK) { + result.data?.data?.let { uri -> + scope.launch { + backupModules(context, snackBarHost, uri) + } + } + } + } + + @Composable + fun rememberModuleRestoreLauncher( + context: Context, + snackBarHost: SnackbarHostState, + scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope() + ) = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == android.app.Activity.RESULT_OK) { + result.data?.data?.let { uri -> + scope.launch { + restoreModules(context, snackBarHost, uri) + } + } + } + } + + @Composable + fun rememberAllowlistBackupLauncher( + context: Context, + snackBarHost: SnackbarHostState, + scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope() + ) = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == android.app.Activity.RESULT_OK) { + result.data?.data?.let { uri -> + scope.launch { + backupAllowlist(context, snackBarHost, uri) + } + } + } + } + + @Composable + fun rememberAllowlistRestoreLauncher( + context: Context, + snackBarHost: SnackbarHostState, + scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope() + ) = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == android.app.Activity.RESULT_OK) { + result.data?.data?.let { uri -> + scope.launch { + restoreAllowlist(context, snackBarHost, uri) + } + } + } + } + + fun createBackupIntent(): Intent { + return Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/zip" + val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + putExtra(Intent.EXTRA_TITLE, "modules_backup_$timestamp.zip") + } + } + + fun createRestoreIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/zip" + } + } + + fun createAllowlistBackupIntent(): Intent { + return Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/octet-stream" + val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + putExtra(Intent.EXTRA_TITLE, "ksu_allowlist_backup_$timestamp.dat") + } + } + + fun createAllowlistRestoreIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/octet-stream" + } + } + + private fun reboot() { + Runtime.getRuntime().exec(arrayOf("su", "-c", "reboot")) + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/SELinuxChecker.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/SELinuxChecker.kt similarity index 58% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/util/SELinuxChecker.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/SELinuxChecker.kt index 6b0d704e..50f12188 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/SELinuxChecker.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/SELinuxChecker.kt @@ -1,34 +1,33 @@ -package me.weishu.kernelsu.ui.util +package shirkneko.zako.sukisu.ui.util import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.topjohnwu.superuser.Shell -import me.weishu.kernelsu.R +import shirkneko.zako.sukisu.R @Composable fun getSELinuxStatus(): String { - val shell = Shell.Builder.create() - .setFlags(Shell.FLAG_REDIRECT_STDERR) - .build("sh") - + val shell = Shell.Builder.create().build("sh") val list = ArrayList() + val result = shell.use { it.newJob().add("getenforce").to(list, list).exec() } - val output = result.out.joinToString("\n").trim() - if (result.isSuccess) { - return when (output) { + val output = list.joinToString("\n").trim() + + return if (result.isSuccess) { + when (output) { "Enforcing" -> stringResource(R.string.selinux_status_enforcing) "Permissive" -> stringResource(R.string.selinux_status_permissive) "Disabled" -> stringResource(R.string.selinux_status_disabled) else -> stringResource(R.string.selinux_status_unknown) } - } - - return if (output.endsWith("Permission denied")) { - stringResource(R.string.selinux_status_enforcing) } else { - stringResource(R.string.selinux_status_unknown) + if (output.contains("Permission denied")) { + stringResource(R.string.selinux_status_enforcing) + } else { + stringResource(R.string.selinux_status_unknown) + } } -} +} \ No newline at end of file diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/module/LatestVersionInfo.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/module/LatestVersionInfo.kt new file mode 100644 index 00000000..c55df63a --- /dev/null +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/util/module/LatestVersionInfo.kt @@ -0,0 +1,8 @@ +package shirkneko.zako.sukisu.ui.util.module + +data class LatestVersionInfo( + val versionCode : Int = 0, + val downloadUrl : String = "", + val changelog : String = "", + val versionName: String = "" +) diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/viewmodel/ModuleViewModel.kt similarity index 94% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/viewmodel/ModuleViewModel.kt index 98342694..e5dc70da 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/viewmodel/ModuleViewModel.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.viewmodel +package shirkneko.zako.sukisu.ui.viewmodel import android.os.SystemClock import android.util.Log @@ -10,9 +10,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import me.weishu.kernelsu.ksuApp -import me.weishu.kernelsu.ui.util.HanziToPinyin -import me.weishu.kernelsu.ui.util.listModules +import shirkneko.zako.sukisu.ui.util.HanziToPinyin +import shirkneko.zako.sukisu.ui.util.listModules import org.json.JSONArray import org.json.JSONObject import java.text.Collator @@ -134,8 +133,11 @@ class ModuleViewModel : ViewModel() { val result = kotlin.runCatching { val url = m.updateJson Log.i(TAG, "checkUpdate url: $url") - val response = ksuApp.okhttpClient.newCall( - okhttp3.Request.Builder().url(url).build() + val response = okhttp3.OkHttpClient() + .newCall( + okhttp3.Request.Builder() + .url(url) + .build() ).execute() Log.d(TAG, "checkUpdate code: ${response.code}") if (response.isSuccessful) { diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/viewmodel/SuperUserViewModel.kt similarity index 65% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/viewmodel/SuperUserViewModel.kt index ecb5024a..549612ce 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/viewmodel/SuperUserViewModel.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.viewmodel +package shirkneko.zako.sukisu.ui.viewmodel import android.content.ComponentName import android.content.Intent @@ -18,19 +18,18 @@ import com.topjohnwu.superuser.Shell import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize -import me.weishu.kernelsu.IKsuInterface -import me.weishu.kernelsu.Natives -import me.weishu.kernelsu.ksuApp -import me.weishu.kernelsu.ui.KsuService -import me.weishu.kernelsu.ui.util.HanziToPinyin -import me.weishu.kernelsu.ui.util.KsuCli +import shirkneko.zako.sukisu.IKsuInterface +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.ksuApp +import shirkneko.zako.sukisu.ui.KsuService +import shirkneko.zako.sukisu.ui.util.HanziToPinyin +import shirkneko.zako.sukisu.ui.util.KsuCli import java.text.Collator import java.util.* import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class SuperUserViewModel : ViewModel() { - companion object { private const val TAG = "SuperUserViewModel" private var apps by mutableStateOf>(emptyList()) @@ -54,7 +53,6 @@ class SuperUserViewModel : ViewModel() { if (profile == null) { return false } - return if (profile.allowSu) { !profile.rootUseDefault } else { @@ -68,6 +66,12 @@ class SuperUserViewModel : ViewModel() { var isRefreshing by mutableStateOf(false) private set + // 批量操作相关状态 + var showBatchActions by mutableStateOf(false) + private set + var selectedApps by mutableStateOf>(emptySet()) + private set + private val sortedList by derivedStateOf { val comparator = compareBy { when { @@ -89,21 +93,65 @@ class SuperUserViewModel : ViewModel() { ) || HanziToPinyin.getInstance() .toPinyinString(it.label).contains(search, true) }.filter { - it.uid == 2000 // Always show shell - || showSystemApps || it.packageInfo.applicationInfo!!.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0 + it.uid == 2000 || showSystemApps || it.packageInfo.applicationInfo!!.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0 } } - private suspend inline fun connectKsuService( - crossinline onDisconnect: () -> Unit = {} - ): Pair = suspendCoroutine { + // 切换批量操作模式 + fun toggleBatchMode() { + showBatchActions = !showBatchActions + if (!showBatchActions) { + clearSelection() + } + } + + // 切换应用选择状态 + fun toggleAppSelection(packageName: String) { + selectedApps = if (selectedApps.contains(packageName)) { + selectedApps - packageName + } else { + selectedApps + packageName + } + } + + // 清除所有选择 + fun clearSelection() { + selectedApps = emptySet() + } + + // 批量更新权限 + suspend fun updateBatchPermissions(allowSu: Boolean) { + selectedApps.forEach { packageName -> + val app = apps.find { it.packageName == packageName } + app?.let { + val profile = Natives.getAppProfile(packageName, it.uid) + val updatedProfile = profile.copy(allowSu = allowSu) + if (Natives.setAppProfile(updatedProfile)) { + apps = apps.map { app -> + if (app.packageName == packageName) { + app.copy(profile = updatedProfile) + } else { + app + } + } + } + } + } + clearSelection() + showBatchActions = false // 批量操作完成后退出批量模式 + fetchAppList() // 刷新列表以显示最新状态 + } + + private suspend fun connectKsuService( + onDisconnect: () -> Unit = {} + ): Pair = suspendCoroutine { continuation -> val connection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { onDisconnect() } override fun onServiceConnected(name: ComponentName?, binder: IBinder?) { - it.resume(binder as IBinder to this) + continuation.resume(binder as IBinder to this) } } @@ -124,7 +172,6 @@ class SuperUserViewModel : ViewModel() { } suspend fun fetchAppList() { - isRefreshing = true val result = connectKsuService { @@ -157,4 +204,4 @@ class SuperUserViewModel : ViewModel() { Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}") } } -} +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/TemplateViewModel.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/viewmodel/TemplateViewModel.kt similarity index 93% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/TemplateViewModel.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/viewmodel/TemplateViewModel.kt index e825d5e9..adc87522 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/TemplateViewModel.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/viewmodel/TemplateViewModel.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.viewmodel +package shirkneko.zako.sukisu.ui.viewmodel import android.os.Parcelable import android.util.Log @@ -10,18 +10,19 @@ import androidx.lifecycle.ViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize -import me.weishu.kernelsu.Natives -import me.weishu.kernelsu.ksuApp -import me.weishu.kernelsu.profile.Capabilities -import me.weishu.kernelsu.profile.Groups -import me.weishu.kernelsu.ui.util.getAppProfileTemplate -import me.weishu.kernelsu.ui.util.listAppProfileTemplates -import me.weishu.kernelsu.ui.util.setAppProfileTemplate +import shirkneko.zako.sukisu.Natives +import shirkneko.zako.sukisu.profile.Capabilities +import shirkneko.zako.sukisu.profile.Groups +import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate +import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates +import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate +import okhttp3.OkHttpClient import okhttp3.Request import org.json.JSONArray import org.json.JSONObject import java.text.Collator import java.util.Locale +import java.util.concurrent.TimeUnit /** @@ -137,7 +138,13 @@ class TemplateViewModel : ViewModel() { private fun fetchRemoteTemplates() { runCatching { - ksuApp.okhttpClient.newCall( + val client: OkHttpClient = OkHttpClient.Builder() + .connectTimeout(5, TimeUnit.SECONDS) + .writeTimeout(5, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .build() + + client.newCall( Request.Builder().url(TEMPLATE_INDEX_URL).build() ).execute().use { response -> if (!response.isSuccessful) { @@ -148,7 +155,7 @@ private fun fetchRemoteTemplates() { 0.until(remoteTemplateIds.length()).forEach { i -> val id = remoteTemplateIds.getString(i) Log.i(TAG, "fetch template: $id") - val templateJson = ksuApp.okhttpClient.newCall( + val templateJson = client.newCall( Request.Builder().url(TEMPLATE_URL.format(id)).build() ).runCatching { execute().use { response -> diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/MimeUtil.java b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/MimeUtil.java similarity index 98% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/webui/MimeUtil.java rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/MimeUtil.java index 1fc2f4a4..33af5fe8 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/MimeUtil.java +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/MimeUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package me.weishu.kernelsu.ui.webui; +package shirkneko.zako.sukisu.ui.webui; import java.net.URLConnection; diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/SuFilePathHandler.java b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/SuFilePathHandler.java similarity index 99% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/webui/SuFilePathHandler.java rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/SuFilePathHandler.java index ff450e8e..5ba98f2d 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/SuFilePathHandler.java +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/SuFilePathHandler.java @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.webui; +package shirkneko.zako.sukisu.ui.webui; import android.content.Context; import android.util.Log; diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebUIActivity.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/WebUIActivity.kt similarity index 97% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebUIActivity.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/WebUIActivity.kt index d926d7ea..b452d119 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebUIActivity.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/WebUIActivity.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.webui +package shirkneko.zako.sukisu.ui.webui import android.annotation.SuppressLint import android.app.ActivityManager @@ -16,7 +16,7 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.updateLayoutParams import androidx.webkit.WebViewAssetLoader import com.topjohnwu.superuser.Shell -import me.weishu.kernelsu.ui.util.createRootShell +import shirkneko.zako.sukisu.ui.util.createRootShell import java.io.File @SuppressLint("SetJavaScriptEnabled") diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebViewInterface.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/WebViewInterface.kt similarity index 97% rename from manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebViewInterface.kt rename to manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/WebViewInterface.kt index 00fcde65..fedae073 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebViewInterface.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/webui/WebViewInterface.kt @@ -1,4 +1,4 @@ -package me.weishu.kernelsu.ui.webui +package shirkneko.zako.sukisu.ui.webui import android.app.Activity import android.content.Context @@ -14,9 +14,9 @@ import androidx.core.view.WindowInsetsControllerCompat import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.internal.UiThreadHandler -import me.weishu.kernelsu.ui.util.createRootShell -import me.weishu.kernelsu.ui.util.listModules -import me.weishu.kernelsu.ui.util.withNewRootShell +import shirkneko.zako.sukisu.ui.util.createRootShell +import shirkneko.zako.sukisu.ui.util.listModules +import shirkneko.zako.sukisu.ui.util.withNewRootShell import org.json.JSONArray import org.json.JSONObject import java.io.File diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/utils/AssetsUtil.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/utils/AssetsUtil.kt new file mode 100644 index 00000000..080b057d --- /dev/null +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/utils/AssetsUtil.kt @@ -0,0 +1,26 @@ +package shirkneko.zako.sukisu.utils + +import android.content.Context +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + +object AssetsUtil { + @Throws(IOException::class) + fun exportFiles(context: Context, src: String, out: String) { + val fileNames = context.assets.list(src) + if (fileNames?.isNotEmpty() == true) { + val file = File(out) + file.mkdirs() + fileNames.forEach { fileName -> + exportFiles(context, "$src/$fileName", "$out/$fileName") + } + } else { + context.assets.open(src).use { inputStream -> + FileOutputStream(File(out)).use { outputStream -> + inputStream.copyTo(outputStream) + } + } + } + } +} \ No newline at end of file diff --git a/manager/app/src/main/jniLibs/.gitignore b/manager/app/src/main/jniLibs/.gitignore index 311f7299..00bfe245 100644 --- a/manager/app/src/main/jniLibs/.gitignore +++ b/manager/app/src/main/jniLibs/.gitignore @@ -1 +1,2 @@ -libksud.so \ No newline at end of file +libzakomk.so +libzakomksd.so \ No newline at end of file diff --git a/manager/app/src/main/jniLibs/arm64-v8a/libmagiskboot.so b/manager/app/src/main/jniLibs/arm64-v8a/libzakoboot.so similarity index 100% rename from manager/app/src/main/jniLibs/arm64-v8a/libmagiskboot.so rename to manager/app/src/main/jniLibs/arm64-v8a/libzakoboot.so diff --git a/manager/app/src/main/res/values-ar/strings.xml b/manager/app/src/main/res/values-ar/strings.xml index 9095106d..a9f57193 100644 --- a/manager/app/src/main/res/values-ar/strings.xml +++ b/manager/app/src/main/res/values-ar/strings.xml @@ -18,7 +18,7 @@ متساهل مجهول مستخدم خارق - فشل في تمكين الوحدة %s + لا يمكن تشغيل %s الوحدة فشل تعطيل الإضافة : %s لا توجد إضافات مثبتة الإضافات @@ -50,7 +50,6 @@ تعرف على كيفية تثبيت KernelSU واستخدام الإضافات إدعمنا KernelSU سيظل دائماً مجانياً ومفتوح المصدر. مع ذلك، يمكنك أن تظهر لنا أنك تهتم بالتبرع. - إنضم إلى قناتنا في %2$s ]]> القدرات تحديث تحميل الإضافة: %s @@ -73,11 +72,10 @@ القيمة الافتراضية العامة لـ\"إلغاء تحميل الإضافات\" في ملفات تعريف التطبيقات. إذا تم تمكينه، إزالة جميع تعديلات الإضافات على النظام للتطبيقات التي لا تحتوي على مجموعة ملف تعريف. سيسمح تمكين هذا الخيار لـKernelSU باستعادة أي ملفات معدلة بواسطة الإضافات لهذا التطبيق. المجال - القواعد + القواعد إعادة تشغيل التطبيق فشل تحديث قواعد SELinux لـ %s اسم الملف الشخصي - إصدار KernelSU الحالي %d منخفض جدًا بحيث لا يعمل المدير بشكل صحيح. الرجاء الترقية إلى الإصدار %d أو أعلى! سجل التغييرات تم الاستيراد بنجاح تصدير إلى الحافظة @@ -132,6 +130,4 @@ السجلات محفوظة فرز (الممكن أولاً) فرز (الإجراء أولاً) - تعطيل توافق su - قم بتعطيل أي تطبيقات مؤقتا من الحصول على امتيازات الجذر عبر الأمر su (لن تتأثر عمليات الجذر الحالية). diff --git a/manager/app/src/main/res/values-az/strings.xml b/manager/app/src/main/res/values-az/strings.xml index cd95c0b4..00a92ddc 100644 --- a/manager/app/src/main/res/values-az/strings.xml +++ b/manager/app/src/main/res/values-az/strings.xml @@ -53,7 +53,6 @@ Defolt Özəl KernelSU pulsuz və açıq mənbəlidir,həmişə belə olacaqdır. Bununla belə, ianə etməklə bizə qayğı göstərdiyinizi göstərə bilərsiniz. - Mənbə kodlarımıza baxın %1$s
Kanalımıza %2$s qoşulun
Profil adı Bacarıqlar Modulları umount et diff --git a/manager/app/src/main/res/values-bn/strings.xml b/manager/app/src/main/res/values-bn/strings.xml index 83983ef7..0fa6426c 100644 --- a/manager/app/src/main/res/values-bn/strings.xml +++ b/manager/app/src/main/res/values-bn/strings.xml @@ -41,7 +41,7 @@ রিফ্রেশ শো সিস্টেম অ্যাপস হাইড সিস্টেম অ্যাপস - সেন্ড লগ + সেন্ড লগ সেইফ মোড রিবুট এপ্লাই মডিউলগুলি অক্ষম কারণ তারা ম্যাজিস্কের সাথে বিরোধিতা করে! @@ -50,7 +50,6 @@ কিভাবে কার্নেলএসইউ ইনস্টল করতে হয় এবং মডিউল ব্যবহার করতে হয় তা শিখুন সাপোর্ট টাইটেল কার্নেলএসইউ বিনামূল্যে এবং ওপেন সোর্স, এবং সবসময় থাকবে। আপনি সবসময় একটি অনুদান দিয়ে আপনার কৃতজ্ঞতা প্রদর্শন করতে পারেন. - আমাদের %2$s চ্যানেল মার্জ করুন]]> প্রফাইলের নাম নেমস্পেস মাউন্ট গ্রুপস @@ -62,6 +61,5 @@ গ্লোবাল আলাদাভাবে আনমাউন্ট মোডিউল - ম্যানেজার সঠিকভাবে কাজ করার জন্য বর্তমান KernelSU সংস্করণ %d খুবই কম। অনুগ্রহ করে %d বা উচ্চতর সংস্করণে আপগ্রেড করুন! লগ সংরক্ষণ করুন diff --git a/manager/app/src/main/res/values-bs/strings.xml b/manager/app/src/main/res/values-bs/strings.xml index efca3bdb..cebe99d6 100644 --- a/manager/app/src/main/res/values-bs/strings.xml +++ b/manager/app/src/main/res/values-bs/strings.xml @@ -9,7 +9,6 @@ SELinux kontekst Umount module Ažuriranje Profila Aplikacije za %s nije uspjelo - Trenutna KernelSU verzija %d je preniska da bi upravitelj ispravno radio. Molimo vas da nadogradite na verziju %d ili noviju! Umount module po zadanom Globalna zadana vrijednost za \"Umount module\" u Profilima Aplikacije. Ako je omogućeno, uklonit će sve izmjene modula na sistemu za aplikacije koje nemaju postavljen Profil. Uključivanjem ove opcije omogućit će KernelSU-u da vrati sve izmjenute datoteke od strane modula za ovu aplikaciju. @@ -18,7 +17,7 @@ Započnite sa skidanjem: %s Nova verzija: %s je dostupna, kliknite da skinete Pokrenite - Prisilno Zaustavite + Prisilno Zaustavite Resetujte U Provođenju Početna @@ -48,7 +47,6 @@ Podržite Nas Pošaljite Izvještaj Naučite KernelSU - Pogledajte izvornu kodu na %1$s
Pridružite nam se na %2$s kanalu
Domena Pravila Neuspješno ažuriranje SELinux pravila za: %s diff --git a/manager/app/src/main/res/values-da/strings.xml b/manager/app/src/main/res/values-da/strings.xml index 0edc4aea..3ff7a8ce 100644 --- a/manager/app/src/main/res/values-da/strings.xml +++ b/manager/app/src/main/res/values-da/strings.xml @@ -32,7 +32,6 @@ Lær KernelSU https://kernelsu.org/guide/what-is-kernelsu.html Lær hvordan man installerer KernelSU og moduler - Se source koden ved %1$s
Deltage i vores %2$s kanal
Standard Skabelon Monter navnerum @@ -75,8 +74,7 @@ Opdatering af App Profil for %s fejlede Den globale standard værdi for \"Afmonter moduler\" i App Profiler. Hvis aktiveret vil den fjerne alle modulers modifikationer til system applikationerne der ikke har en sat Profil. Domæne - Regler + Regler Genstart - Den nuværende KernelSU version %d er for lav til manageren for at fungere ordentligt. Opgrader til version %d eller højere! Gem Logfiler diff --git a/manager/app/src/main/res/values-de/strings.xml b/manager/app/src/main/res/values-de/strings.xml index faee61d2..4f5cf9f5 100644 --- a/manager/app/src/main/res/values-de/strings.xml +++ b/manager/app/src/main/res/values-de/strings.xml @@ -38,7 +38,7 @@ Aktualisieren der SELinux-Regeln schlug fehl für: %s Starten Neue Version %s verfügbar, tippen zum Aktualisieren. - Stopp erzwingen + Stopp erzwingen Neustarten Module: %d Manager-Version @@ -62,7 +62,6 @@ KernelSU verstehen Sicherer Modus Neustarten, damit Änderungen wirksam werden - Unserem %2$s-Kanal beitreten]]> Profilname Namespace einhängen Gruppen @@ -77,7 +76,6 @@ Soft-Reboot Möchtest du wirklich Modul %s deinstallieren? Deinstallation fehlgeschlagen: %s - Die aktuelle KernelSU-Version %d ist zu alt für diese Manager-Version. Bitte auf Version %d oder höher aktualisieren! Änderungsprotokoll Erfolgreich importiert In Zwischenablage exportieren @@ -128,7 +126,6 @@ Schreiben erfolgreich Schreiben fehlgeschlagen Wähle LKM: %s - Spärliches Bild minimieren Aktion Protokolle gespeichert diff --git a/manager/app/src/main/res/values-es/strings.xml b/manager/app/src/main/res/values-es/strings.xml index dd3733c3..2eb4dedc 100644 --- a/manager/app/src/main/res/values-es/strings.xml +++ b/manager/app/src/main/res/values-es/strings.xml @@ -51,7 +51,6 @@ Aprende a instalar KernelSU y a utilizar módulos Apóyanos KernelSU es, y siempre será, gratuito y de código abierto. Sin embargo, puedes demostrarnos que te importamos haciendo una donación. - Ver código fuente en %1$s
Únete a nuestro canal %2$s
Predeterminado Plantilla Personalizado @@ -75,10 +74,9 @@ Iniciar descarga: %s La nueva versión %s está disponible, haga clic para actualizar. Iniciar - Forzar detención + Forzar detención Reiniciar Error al actualizar las reglas SELinux para: %s - La versión %d actual de KernelSU es demasiado baja para que el gestor funcione correctamente. Por favor, ¡actualice a la versión %d o superior! Registro de cambios Importado con éxito Exportar al portapapeles diff --git a/manager/app/src/main/res/values-et/strings.xml b/manager/app/src/main/res/values-et/strings.xml index 01b3534e..10d24f19 100644 --- a/manager/app/src/main/res/values-et/strings.xml +++ b/manager/app/src/main/res/values-et/strings.xml @@ -67,7 +67,6 @@ Grupid KernelSU on, ja alati jääb, tasuta ning avatud lähtekoodiga kättesaadavaks. Sellegipoolest võid sa näidata, et hoolid, ning teha annetuse. Mall - Vaata lähtekoodi %1$sis
Liitu meie %2$si kanaliga
Profiili nimi Kohandatud Päritud @@ -76,14 +75,13 @@ Võimekused Sobimatu malli ID SELinux kontekst - Praegune KernelSU versioon %d on liiga madal, haldur ei saa korrektselt töötada. Palun täienda versioonile %d või kõrgem! Domeen Käivita Sundpeata Reeglid Uuenda Mooduli allalaadimine: %s - Uus versioon %s on saadaval, klõpsa täiendamiseks. + Uus versioon %s on saadaval, klõpsa täiendamiseks. Taaskäivita Muudatuste logi Nimi diff --git a/manager/app/src/main/res/values-fa/strings.xml b/manager/app/src/main/res/values-fa/strings.xml index 37db04e4..fee65b7a 100644 --- a/manager/app/src/main/res/values-fa/strings.xml +++ b/manager/app/src/main/res/values-fa/strings.xml @@ -50,9 +50,6 @@ یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید از ما حمایت کنید KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است. - -Join our %2$s channel ]]> - پروفایل برنامه پیش‌فرض قالب diff --git a/manager/app/src/main/res/values-fil/strings.xml b/manager/app/src/main/res/values-fil/strings.xml index 259590a6..5c13391d 100644 --- a/manager/app/src/main/res/values-fil/strings.xml +++ b/manager/app/src/main/res/values-fil/strings.xml @@ -37,7 +37,6 @@ Matutunan kung paano mag-install ng KernelSU at gumamit ng mga modyul Suportahan Kami Ang KernelSU ay, at palaging magiging, libre, at open source. Gayunpaman, maaari mong ipakita sa amin na nagmamalasakit ka sa pamamagitan ng pagbibigay ng donasyon. - Tingnan ang source code sa %1$s
Sumali sa aming %2$s channel
I-mount ang namespace Indibidwal Mga Grupo @@ -45,14 +44,13 @@ Konteksto ng SELinux I-unmount ang mga modyul Nabigong i-update ang App Profile para sa %s - Ang kasalukuyang bersyon ng KernelSU %d ay masyadong mababa para gumana nang maayos ang manager. Mangyaring mag-upgrade sa bersyon %d o mas mataas! Ang pagpapagana sa opsyong ito ay magbibigay-daan sa KernelSU na ibalik ang anumang binagong file ng mga modyul para sa aplikasyon na ito. Mga Tuntunin Nagda-download ng modyul: %s Simulan ang pag-download: %s Bagong bersyon: Available ang %s, i-click upang i-download Ilunsad - Pilit na I-hinto + Pilit na I-hinto I-restart Nabigong i-update ang mga panuntunan ng SELinux para sa: %s Bersyon ng Manager diff --git a/manager/app/src/main/res/values-fr/strings.xml b/manager/app/src/main/res/values-fr/strings.xml index 5cdae119..886ce326 100644 --- a/manager/app/src/main/res/values-fr/strings.xml +++ b/manager/app/src/main/res/values-fr/strings.xml @@ -50,7 +50,6 @@ Soutenez-nous Découvrez comment installer KernelSU et utiliser les modules KernelSU est, et restera toujours, gratuit et open source. Vous pouvez cependant nous témoigner de votre soutien en nous faisant un don. - Rejoignez notre canal %2$s]]> Modèle Par défaut Personnalisé @@ -74,10 +73,9 @@ Lancer La nouvelle version %s est disponible, appuyez ici pour mettre à jour. Début du téléchargement de : %s - Forcer l\'arrêt + Forcer l\'arrêt Relancer l\'application - Échec de la mise à jour des règles SELinux pour %s - 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 ! + Échec de la mise à jour des règles SELinux pour : %s Importation réussie Exporter vers le presse-papiers Impossible de trouver un modèle local à exporter ! @@ -132,6 +130,4 @@ Trier par activé Action Journaux enregistrés - Désactiver la compatibilité avec su - Désactivez temporairement l\'accès des applications aux privilèges root par le biais de la commande su (les processus root existants ne seront pas affectés). diff --git a/manager/app/src/main/res/values-hi/strings.xml b/manager/app/src/main/res/values-hi/strings.xml index da541809..7684a9ac 100644 --- a/manager/app/src/main/res/values-hi/strings.xml +++ b/manager/app/src/main/res/values-hi/strings.xml @@ -19,7 +19,7 @@ Individual %s मॉड्यूल चालू करने में विफल जबर्दस्ती बंद करें - EDL मोड में रिबूट करें + EDL मोड में रिबूट करें फिर से चालू करें क्षमताएं सुपरयूजर : %d @@ -33,7 +33,6 @@ डिफॉल्ट लॉन्च करें सेफ मोड - मैनेजर के ठीक से काम करने के लिए वर्तमान KernelSU वर्जन %d बहुत कम है। कृपया वर्जन %d या उच्चतर में अपग्रेड करें! रिकवरी में रिबूट करें सॉफ्ट रिबूट प्रोफाइल का नाम @@ -76,7 +75,6 @@ https://kernelsu.org/guide/what-is-kernelsu.html %s के लिए SELinux नियमों को अपटेड करने में विफल बुटलोडर में रिबूट करें - %1$s पर स्रोत कोड देखें
हमारे %2$s चैनल से जुड़ें
मैनेजर वर्जन नया वर्जन: %s उपलब्ध है,अपग्रेड के लिए क्लिक करें लॉग सहेजें diff --git a/manager/app/src/main/res/values-hr/strings.xml b/manager/app/src/main/res/values-hr/strings.xml index 5242b758..a95c1f50 100644 --- a/manager/app/src/main/res/values-hr/strings.xml +++ b/manager/app/src/main/res/values-hr/strings.xml @@ -51,7 +51,6 @@ Naučite kako da instalirate KernelSU i da koristite module Podržite Nas KernelSU je, i uvijek če biti, besplatan, i otvorenog izvora. Možete nam međutim pokazati da vas je briga s time da napravite donaciju. - Pogledajte izvornu kodu na %1$s
Pridružite nam se na %2$s kanalu
Zadano Šablon Prilagođeno @@ -65,7 +64,6 @@ Grupe Sposobnosti SELinux kontekst - Trenutna KernelSU verzija %d je preniska da bi voditelj ispravno radio. Molimo vas da nadogradite na verziju %d ili noviju! Umount module po zadanom Globalna zadana vrijednost za \"Umount module\" u Profilima Aplikacije. Ako je omogućeno, uklonit će sve izmjene modula na sistemu za aplikacije koje nemaju postavljen Profil. Domena @@ -76,7 +74,7 @@ Započnite sa preuzimanjem: %s Nova verzija: %s je dostupna, kliknite da preuzmete Pokrenite - Prisilno Zaustavite + Prisilno Zaustavite Resetujte Spremi Zapise diff --git a/manager/app/src/main/res/values-hu/strings.xml b/manager/app/src/main/res/values-hu/strings.xml index 07d68d28..14250500 100644 --- a/manager/app/src/main/res/values-hu/strings.xml +++ b/manager/app/src/main/res/values-hu/strings.xml @@ -22,7 +22,6 @@ Tudjon meg többet a KernelSU-ról Ismerje meg a KernelSU telepítését és a modulok használatát Támogasson minket - Tekintse meg a forráskódot a %1$s-on
Csatlakozzon a %2$s csatornánkhoz
Alapértelmezett Sablon Egyedi @@ -42,7 +41,7 @@ Modul letöltése: %s Letöltés indítása: %s Indítás - Kényszerített leállítás + Kényszerített leállítás újraindítás Kezdőlap Nincs telepítve @@ -77,7 +76,6 @@ A \"Modulok leválasztása\" globális alapértelmezett értéke az App Profile-ban. Ha engedélyezve van, eltávolít minden modulmódosítást a rendszerből azon alkalmazások esetében, amelyeknek nincs profilja beállítva. Elérhető az új, %s verzió, kattintson a frissítéshez. Nem sikerült frissíteni az SELinux szabályokat a következőhöz: %s - A jelenlegi KernelSU verzió %d túlságosan elavult a megfelelő működéshez. Kérjük frissítsen a %d verzióra vagy újabbra! Sikeresen importálva Exportálás a vágólapról Nem található helyi sablon az exportáláshoz! @@ -124,10 +122,8 @@ Művelet Közvetlen telepítés (Ajánlott) Az eszköze **KÉNYSZERÍTETTEN** a jelenleg inaktív helyről fog indulni újraindítás után!\nCsak az OTA befejezése után használja.\nFolytatja? - Átméretezi a sparse képfájlt, ahol a modul található, a tényleges méretére. Vegye figyelembe, hogy ez a modul rendellenes működését okozhatja, ezért kérjük, hogy csak akkor használja, ha szükséges (például biztonsági mentéshez). Állítsa vissza a gyári képfájlt (ha létezik biztonsági mentés). Általában OTA előtt használják. Ha a KernelSU-t szeretné eltávolítani, használja a végleges eltávolítás opciót. Frissítés ellenőrzése Automatikusan keressen frissítéseket az alkalmazás megnyitásakor Mentett naplók - Sparse képfájl minimalizálása diff --git a/manager/app/src/main/res/values-in/strings.xml b/manager/app/src/main/res/values-in/strings.xml index db31fda7..afe50176 100644 --- a/manager/app/src/main/res/values-in/strings.xml +++ b/manager/app/src/main/res/values-in/strings.xml @@ -50,7 +50,6 @@ Pelajari cara instal KernelSU dan menggunakan modul Dukung Kami KernelSU akan selalu menjadi aplikasi gratis dan terbuka. Anda dapat memberikan donasi sebagai bentuk dukungan. - Gabung kanal %2$s kami]]> Profil Apl Bawaan Templat @@ -75,10 +74,9 @@ Mulai mengunduh: %s Tersedia versi terbaru %s, Klik untuk membarui. Jalankan - Paksa berhenti + Paksa berhenti Mulai ulang Gagal membarui aturan SELinux pada: %s - Versi KernelSU %d terlalu rendah agar manajer berfungsi normal. Harap membarui ke versi %d atau di atasnya! Catatan Perubahan Berhasil diimpor Ekspor ke papan klip diff --git a/manager/app/src/main/res/values-it/strings.xml b/manager/app/src/main/res/values-it/strings.xml index 6ed4692a..84da3092 100644 --- a/manager/app/src/main/res/values-it/strings.xml +++ b/manager/app/src/main/res/values-it/strings.xml @@ -50,7 +50,6 @@ Scopri come installare KernelSU e utilizzare i moduli Supportaci KernelSU è, e sempre sarà, gratuito e open source. Puoi comunque mostrarci il tuo apprezzamento facendo una donazione. - Unisciti al nostro canale %2$s]]> Nome profilo Spazio dei nomi del mount Globale @@ -71,13 +70,12 @@ Sto scaricando il modulo: %s Inizia a scaricare:%s Nuova versione: %s disponibile, tocca per aggiornare - Arresto forzato + Arresto forzato Riavvia Aggiornamento regole SELinux per %s fallito Attivando questa opzione permetterai a KernelSU di ripristinare ogni file modificato dai moduli per questa app. Dominio Il valore predefinito per \"Scollega moduli\" in App Profile. Se attivato, rimuoverà tutte le modifiche al sistema da parte dei moduli per le applicazioni che non hanno un profilo impostato. - La versione attualmente installata di KernelSU (%d) è troppo vecchia ed il gestore non può funzionare correttamente. Si prega di aggiornare alla versione %d o successiva! Registro aggiornamenti Crea modello Modifica modello diff --git a/manager/app/src/main/res/values-iw/strings.xml b/manager/app/src/main/res/values-iw/strings.xml index f0ff9fe8..fb97a716 100644 --- a/manager/app/src/main/res/values-iw/strings.xml +++ b/manager/app/src/main/res/values-iw/strings.xml @@ -19,7 +19,7 @@ אישי הפעלת המודל נכשלה: %s עצירה בכח - הפעלה מחדש למצב EDL + הפעלה מחדש למצב EDL איתחול יכולת משתמשי על: %d @@ -33,7 +33,6 @@ ברירת מחדל להשיק מצב בטוח - גרסת KernelSU הנוכחית %d נמוכה מדי כדי שהמנהל יפעל כראוי. אנא שדרג לגרסה %d ומעלה! הפעלה מחדש לריקברי רך Reboot שם פרופיל @@ -76,7 +75,6 @@ https://kernelsu.org/guide/what-is-kernelsu.html נכשל עדכון כללי SELinux עבור: %s הפעלה מחדש לבוטלאודר - ראה את קוד המקור ב%1$s
הצטרף אלינו %2$s בערוץ
גרסת מנהל גרסה חדשה עבור: %s זמינה, לחץ כדי לשדרג שמור יומנים diff --git a/manager/app/src/main/res/values-ja/strings.xml b/manager/app/src/main/res/values-ja/strings.xml index 417cd257..f024370d 100644 --- a/manager/app/src/main/res/values-ja/strings.xml +++ b/manager/app/src/main/res/values-ja/strings.xml @@ -18,8 +18,8 @@ Permissive 不明 スーパーユーザー - モジュールの有効化に失敗しました: %s - モジュールの無効化に失敗しました: %s + %s モジュールをオンにできませんでした + %s モジュールをオフにできませんでした モジュールがインストールされていません モジュール アンインストール @@ -35,7 +35,7 @@ アプリについて モジュール %s をアンインストールしますか? %s はアンインストールされました - アンインストールに失敗しました: %s + %s をアンインストールできませんでした バージョン 制作者 更新 @@ -50,7 +50,6 @@ KernelSU のインストール方法やモジュールの使い方はこちら 支援する KernelSU はこれからもずっと無料でオープンソースです。寄付をして頂くことで、開発を支援していただけます。 - %2$s チャンネルに参加]]> アプリのプロファイル 既定 テンプレート @@ -70,7 +69,7 @@ アップデート ダウンロードを開始: %s 起動 - 強制停止 + 強制停止 再起動 SELinux ルールの更新に失敗しました %s ケーパビリティ @@ -78,7 +77,6 @@ このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。 既定でモジュールのマウントを解除 アプリプロファイルの「モジュールのアンマウント」の共通のデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。 - 現在の KernelSU バージョン %d はマネージャーが適切に機能するには低すぎます。 バージョン %d 以降にアップグレードしてください! 変更履歴 インポート成功 クリップボードからエクスポート diff --git a/manager/app/src/main/res/values-kn/strings.xml b/manager/app/src/main/res/values-kn/strings.xml index 3964f203..d64d4e4d 100644 --- a/manager/app/src/main/res/values-kn/strings.xml +++ b/manager/app/src/main/res/values-kn/strings.xml @@ -26,9 +26,8 @@ ಮಾಡ್ಯೂಲ್‌ಗಳು: %d SELinux ಸಂದರ್ಭ ಡೀಫಾಲ್ಟ್ - ಲಾಂಚ್ + ಲಾಂಚ್ ಸುರಕ್ಷಿತ ಮೋಡ್ - ಪ್ರಸ್ತುತ KernelSU ಆವೃತ್ತಿ %d ಮ್ಯಾನೇಜರ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸಲು ತುಂಬಾ ಕಡಿಮೆಯಾಗಿದೆ. ದಯವಿಟ್ಟು ಆವೃತ್ತಿ %d ಅಥವಾ ಹೆಚ್ಚಿನದಕ್ಕೆ ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಿ! ಸಾಫ್ಟ್ ರೀಬೂಟ್ ಪ್ರೊಫೈಲ್ ಹೆಸರು KernelSU ಉಚಿತ ಮತ್ತು ಮುಕ್ತ ಮೂಲವಾಗಿದೆ ಮತ್ತು ಯಾವಾಗಲೂ ಇರುತ್ತದೆ. ಆದಾಗ್ಯೂ ನೀವು ದೇಣಿಗೆ ನೀಡುವ ಮೂಲಕ ನೀವು ಕಾಳಜಿ ವಹಿಸುತ್ತೀರಿ ಎಂದು ನಮಗೆ ತೋರಿಸಬಹುದು. @@ -62,7 +61,6 @@ ಕರ್ನಲ್ %s ಗಾಗಿ ಅಪ್ಲಿಕೇಶನ್ ಪ್ರೊಫೈಲ್ ಅನ್ನು ನವೀಕರಿಸಲು ವಿಫಲವಾಗಿದೆ https://kernelsu.org/guide/what-is-kernelsu.html - %1$s ನಲ್ಲಿ ಮೂಲ ಕೋಡ್ ಅನ್ನು ವೀಕ್ಷಿಸಿ
ನಮ್ಮ %2$s ಚಾನಲ್‌ಗೆ ಸೇರಿ
ಮ್ಯಾನೇಜರ್ ವರ್ಷನ್ ಹೊಸ ಆವೃತ್ತಿ: %s ಲಭ್ಯವಿದೆ, ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ ಲಾಗ್ಗಳನ್ನು ಉಳಿಸಿ diff --git a/manager/app/src/main/res/values-ko/strings.xml b/manager/app/src/main/res/values-ko/strings.xml index 3ae208fe..b67c858e 100644 --- a/manager/app/src/main/res/values-ko/strings.xml +++ b/manager/app/src/main/res/values-ko/strings.xml @@ -49,9 +49,8 @@ KernelSU 설치 방법과 모듈 사용 방법을 확인합니다 지원이 필요합니다 KernelSU는 지금도, 앞으로도 항상 무료이며 오픈 소스로 유지됩니다. 기부를 통해 여러분의 관심을 보여주세요. - %2$s 채널 참가하기]]> https://kernelsu.org/guide/what-is-kernelsu.html - 앱 프로필 메뉴의 \"모듈 마운트 해제\" 설정에 대한 전역 기본값을 설정합니다. 활성화 시, 개별 프로필이 설정되지 않은 앱은 시스템에 대한 모듈의 모든 수정사항이 적용되지 않습니다. + 앱 프로필 메뉴의 \"모듈 마운트 해제\" 설정에 대한 전역 기본값을 설정합니다. 활성화 시, 개별 프로필이 설정되지 않은 앱은 시스템에 대한 모듈의 모든 수정사항이 적용되지 않습니다. 다시 시작 규칙 새 버전: %s이 사용 가능합니다, 여기를 눌러 업그레이드하세요. @@ -80,7 +79,6 @@ 로그 저장 업데이트 내역 WebUI 디버깅에 사용 가능, 필요할 때만 활성화해주세요. - 스파스 이미지 최소화 플래시 중 선택된 LKM: %s %1$s 파티션 이미지 권장됨 @@ -88,8 +86,6 @@ 다음 완전히, 그리고 영구히 KernelSU (루트 및 모든 모듈)를 삭제합니다. WebView 디버깅 활성화 - 현재 KernelSU 버전 %d는 매니저가 올바르게 작동하기에 너무 낮습니다. 버전 %d 이상으로 업그레이드해 주세요! - 모듈이 위치한 스파스 이미지의 크기를 실제 크기로 조정합니다. 모듈이 비정상적으로 작동할 수 있으니, 필요할 때만 (예: 백업) 사용해 주세요. 동작 임시적 삭제 업데이트 내역 가져오기 실패: %s diff --git a/manager/app/src/main/res/values-lt/strings.xml b/manager/app/src/main/res/values-lt/strings.xml index 5b879cdb..8a071fc4 100644 --- a/manager/app/src/main/res/values-lt/strings.xml +++ b/manager/app/src/main/res/values-lt/strings.xml @@ -30,7 +30,6 @@ https://kernelsu.org/guide/what-is-kernelsu.html Sužinokite apie KernelSU Sužinokite, kaip įdiegti KernelSU ir naudoti modulius - Peržiūrėkite šaltinio kodą %1$s
Prisijunkite prie mūsų %2$s kanalo
Numatytas Šablonas Pasirinktinis @@ -52,7 +51,7 @@ Pradedamas atsisiuntimas: %s Nauja versija: %s pasiekiama, spustelėkite norėdami atsinaujinti Paleisti - Priversti sustoti + Priversti sustoti Perkrauti Nepavyko atnaujinti SELinux taisyklių: %s Namai @@ -78,6 +77,5 @@ Nepavyko atnaujinti programos profilio %s Visuotinė numatytoji „Modulių atjungimo“ reikšmė programų profiliuose. Jei įjungta, ji pašalins visus sistemos modulio pakeitimus programoms, kurios neturi profilio. Keitimų žurnalas - Ši KernelSU versija %d yra per žema, kad šis vadybininkas galėtų tinkamai funkcionuoti. Prašome atsinaujinti į versiją %d ar aukščiau! Saglabāt Žurnālus diff --git a/manager/app/src/main/res/values-lv/strings.xml b/manager/app/src/main/res/values-lv/strings.xml index 6b504866..55dec619 100644 --- a/manager/app/src/main/res/values-lv/strings.xml +++ b/manager/app/src/main/res/values-lv/strings.xml @@ -51,7 +51,6 @@ https://kernelsu.org/guide/what-is-kernelsu.html Uzzināt, kā instalēt KernelSU un izmantot moduļus Atbalsti mūs - Skatiet avota kodu vietnē %1$s
Pievienojies mūsu %2$s kanālam
Noklusējums Veidne Pielāgots @@ -71,7 +70,7 @@ Sākt lejupielādi: %s Jaunā versija: %s ir pieejama, noklikšķiniet, lai atjauninātu Palaist - Piespiedu apstāšana + Piespiedu apstāšana Restartēt aplikāciju Izmaiņu žurnāls Lietotnes profila veidne @@ -99,7 +98,6 @@ KernelSU ir un vienmēr būs bezmaksas un atvērtā koda. Tomēr jūs varat parādīt mums, ka jums rūp, veicot ziedojumu. Grupas Globāli - Pašreizējā KernelSU versija %d ir pārāk zema, lai pārvaldnieks darbotos pareizi. Lūdzu, atjauniniet uz versiju %d vai jaunāku! Iespējot WebView atkļūdošanu Ieteicams %1$s nodalījuma attēls Nākamais diff --git a/manager/app/src/main/res/values-mr/strings.xml b/manager/app/src/main/res/values-mr/strings.xml index 43bd5878..64457bb8 100644 --- a/manager/app/src/main/res/values-mr/strings.xml +++ b/manager/app/src/main/res/values-mr/strings.xml @@ -56,7 +56,6 @@ साचा वैयक्तिक क्षमता - %1$s वर स्रोत कोड पहा
आमच्या %2$s चॅनेलमध्ये सामील व्हा
प्रोफाइल नाव इनहेरीटेड जागतिक diff --git a/manager/app/src/main/res/values-nl/strings.xml b/manager/app/src/main/res/values-nl/strings.xml index 8675d1b7..ac9fdc69 100644 --- a/manager/app/src/main/res/values-nl/strings.xml +++ b/manager/app/src/main/res/values-nl/strings.xml @@ -50,8 +50,6 @@ Leer hoe KernelSU te installeren en modules te gebruiken Ondersteun ons KernelSU is, en zal altijd, vrij en open source zijn. Je kan altijd je appreciatie tonen met een donatie. - Vervoeg ons %2$s kanaal]]> - App profiel Standaard Sjabloon Aangepast @@ -74,11 +72,10 @@ Downloaden van module: %s Nieuwe versie %s is beschikbaar,klik om te upgraden. Start - Forceer stop + Forceer stop Herstart Begin met downloaden: %s Kan SELinux-regels niet bijwerken voor: %s - De huidige KernelSU-versie %d is te laag voor de manager om goed te werken. Upgrade naar versie %d of hoger! wijzigings logboek App-profiel Sjabloon Maken sjabloon diff --git a/manager/app/src/main/res/values-pl/strings.xml b/manager/app/src/main/res/values-pl/strings.xml index 7163ac7e..33cb6bd3 100644 --- a/manager/app/src/main/res/values-pl/strings.xml +++ b/manager/app/src/main/res/values-pl/strings.xml @@ -51,7 +51,6 @@ Dowiedz się jak zainstalować KernelSU i jak korzystać z modułów Wesprzyj nas KernelSU jest i zawsze będzie darmowy oraz otwarty. Niemniej jednak możesz nam pokazać, że Ci zależy, wysyłając darowiznę. - Dołącz do kanału %2$s]]> Profil aplikacji Domyślny Szablon @@ -76,10 +75,9 @@ Rozpocznij pobieranie: %s Nowa wersja %s jest dostępna. Kliknij, aby zaktualizować. Uruchom - Wymuś zatrzymanie + Wymuś zatrzymanie Restartuj Nie udało się zaktualizować reguł SELinux dla %s - Obecna wersja KernelSU %d jest zbyt stara, aby menedżer działał poprawnie. Prosimy o aktualizację do wersji %d lub nowszej! Dziennik zmian Włącz debugowanie WebView Może być użyte do debugowania WebUI. Włącz tylko w razie potrzeby. diff --git a/manager/app/src/main/res/values-pt-rBR/strings.xml b/manager/app/src/main/res/values-pt-rBR/strings.xml index c6535f93..906276d8 100644 --- a/manager/app/src/main/res/values-pt-rBR/strings.xml +++ b/manager/app/src/main/res/values-pt-rBR/strings.xml @@ -50,7 +50,6 @@ Saiba como instalar o KernelSU e usar os módulos Apoie-nos KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação. - Junte-se ao nosso canal do %2$s]]> Perfil do Aplicativo Padrão Modelo @@ -75,10 +74,9 @@ Começando a baixar %s Nova versão %s está disponível, clique para atualizar. Iniciar - Forçar parada + Forçar parada Reiniciar Falha ao atualizar as regras do SELinux para %s - A versão atual do KernelSU %d é muito baixa para o gerenciador funcionar corretamente. Por favor, atualize para a versão %d ou superior! Registro de alterações Importado com sucesso Exportar para a área de transferência @@ -133,6 +131,4 @@ Registros salvos Ordenar (Ação primeiro) Ordenar (Ativado primeiro) - Desative temporariamente a capacidade de qualquer app obter privilégios root por meio do comando ⁠su (processos root existentes não serão afetados). - Desativar compatibilidade su diff --git a/manager/app/src/main/res/values-pt/strings.xml b/manager/app/src/main/res/values-pt/strings.xml index 0a3c19ef..5f6436f6 100644 --- a/manager/app/src/main/res/values-pt/strings.xml +++ b/manager/app/src/main/res/values-pt/strings.xml @@ -44,7 +44,6 @@ Mostrar aplicativos do sistema Aprenda a instalar o KernelSU e usar os módulos O KernelSU é, e sempre será, gratuito e de código aberto. No entanto, você pode nos mostrar que se importa fazendo uma doação. - Veja o código-fonte em %1$s
Junte-se ao nosso canal %2$s
Individual Global Herdado @@ -72,11 +71,10 @@ Iniciar o download: %s Baixando módulo: %s Falha ao atualizar as regras do SELinux para: %s - https://kernelsu.org/guide/what-is-kernelsu.html + https://kernelsu.org/guide/what-is-kernelsu.html Reiniciar Lançar Forçar parada Nova versão: %s está disponível, clique para baixar - A versão atual do KernelSU %d é muito baixa para o gerenciador funcionar corretamente. Atualize para a versão %d ou superior! Salvar Registros diff --git a/manager/app/src/main/res/values-ro/strings.xml b/manager/app/src/main/res/values-ro/strings.xml index 42e0b439..0332ea3d 100644 --- a/manager/app/src/main/res/values-ro/strings.xml +++ b/manager/app/src/main/res/values-ro/strings.xml @@ -50,7 +50,6 @@ Află cum să instalezi KernelSU și să utilizezi module Suport KernelSU este, și va fi întotdeauna, gratuit și cu codul sursă deschis. Cu toate acestea, ne poți arăta că îți pasă făcând o donație. - Alătură-te canalului nostru %2$s]]> Implicit Șablon Personalizat @@ -75,9 +74,8 @@ Versiune nouă: %s este disponibilă, clic pentru a actualiza Nu s-au reușit actualizările regulilor SELinux pentru: %s Lansare - Oprire forțată + Oprire forțată Repornește - Versiunea actuală a KernelSU %d este prea mică pentru ca managerul să funcționeze corect. Actualizează la versiunea %d sau o versiune superioară! Jurnalul modificărilor Importat cu succes Export în clipboard diff --git a/manager/app/src/main/res/values-ru/strings.xml b/manager/app/src/main/res/values-ru/strings.xml index bc958015..739a949b 100644 --- a/manager/app/src/main/res/values-ru/strings.xml +++ b/manager/app/src/main/res/values-ru/strings.xml @@ -52,7 +52,6 @@ Узнайте, как установить KernelSU и использовать модули Поддержите нас KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование. - Присоединяйтесь к нашему %2$s каналу]]> App Profile По умолчанию @@ -79,9 +78,8 @@ Новая версия: %s доступна, нажмите чтобы скачать. Остановить принудительно Не удалось обновить правила SELinux для %s - Запустить + Запустить Перезапустить - Текущая версия KernelSU %d слишком низкая для правильной работы менеджера. Пожалуйста, обновите до версии %d или выше! Список изменений Успешный импорт Экспортировать в буфер обмена @@ -136,6 +134,4 @@ Логи сохранены Сортировать (Сначала с действием) Сортировать (Сначала включённые) - Отключить su совместимость - Временно отключить возможность получения root привилегий любым приложениям с помощью команды su (существующие root процессы не будут затронуты). diff --git a/manager/app/src/main/res/values-sl/strings.xml b/manager/app/src/main/res/values-sl/strings.xml index 2486a425..b74e0fe7 100644 --- a/manager/app/src/main/res/values-sl/strings.xml +++ b/manager/app/src/main/res/values-sl/strings.xml @@ -29,7 +29,6 @@ Naučite se KernelSU https://kernelsu.org/guide/what-is-kernelsu.html Naučite se, kako namestiti KernelSU in uporabiti module - Glej odprto kodo na %1$s
Pridružite se našem %2$s kanalu
Privzeto Predloga Imenski prostor vmestitve @@ -42,7 +41,7 @@ Domena Posodobitev Nalaganje modula: %s - Zaženi + Zaženi Ponovni zagon Dnevnik sprememb Predloga za aplikacijski profil @@ -69,7 +68,6 @@ SELinux kontekst KernelSU je, in bo vedno brezplačen in odprtokoden. Kljub temu, nam lahko z donacijo pokažete, da vam je mar. Napaka pri posodobitvi aplikacijskega profila za %s - Za pravilno funkionalnost upravitelja je trenutna KernelSU verzija %d prenizka. Potrebna je nadgradnja na verzijo %d ali več! Globalno privzeta vrednost za \"Izvrzi module\" v aplikacijskih profilih. Če je omogočena, bo to odstranilo vse sistemske modifikacije modulov za aplikacije, ki nimajo nastavljenega profila. Omogočanje te opcije bo dovolilo KernelSU, da obnovi vse zaradi modulov spremenjene datoteke za to aplikacijo. Prisilna ustavitev diff --git a/manager/app/src/main/res/values-th/strings.xml b/manager/app/src/main/res/values-th/strings.xml index e8b3e143..afa495d8 100644 --- a/manager/app/src/main/res/values-th/strings.xml +++ b/manager/app/src/main/res/values-th/strings.xml @@ -20,8 +20,8 @@ Permissive ไม่ทราบ สิทธิ์ผู้ใช้ขั้นสูง - ล้มเหลวในการเปิดใช้งานโมดูล %s - ล้มเหลวในการปิดใช้งานโมดูล: %s + ไม่สามารถเปิดใช้งานโมดูล %s + ไม่สามารถปิดใช้งานโมดูล: %s ไม่มีโมดูลที่ติดตั้ง โมดูล ถอนการติดตั้ง @@ -34,7 +34,7 @@ รีบูตเข้าสู่โหมด Download รีบูตเข้าสู่โหมด EDL %s ถอนการติดตั้งสำเร็จ - ถอนการติดตั้ง %s ล้มเหลว + ไม่สามารถถอนการติดตั้ง %s คุณแน่ใจว่าจะถอนการติดตั้งโมดูล %s หรือไม่\? ผู้สร้าง เวอร์ชัน @@ -50,7 +50,6 @@ เรียนรู้วิธีการติดตั้ง KernelSU และวิธีใช้งานโมดูลต่าง ๆ สนับสนุนพวกเรา KernelSU เป็นโอเพ่นซอร์สฟรีทั้งจากนี้และตลอดไป อย่างไรก็ตาม คุณสามารถแสดงความห่วงใยได้ด้วยการบริจาค - และเข้าร่วมช่อง %2$s channel]]> กำหนดเอง ค่าเริ่มต้น เทมเพลต @@ -72,12 +71,11 @@ กำลังดาวน์โหลดโมดูล: %s กำลังเริ่มดาวน์โหลด: %s เวอร์ชันใหม่: %s พร้อมใช้งาน คลิกเพื่ออัปเกรด - บังคับหยุด + บังคับหยุด รีสตาร์ท หากเปิดใช้งานค่าเริ่มต้นโดยทั่วไปสำหรับ \"Umount โมดูล\" ในโปรไฟล์แอป จะเป็นการลบการแก้ไขโมดูลทั้งหมดในระบบสำหรับแอปพลิเคชันที่ไม่มีการตั้งค่าโปรไฟล์ เปิด ไม่สามารถอัปเดตกฎ SElinux สำหรับ %s - KernelSU เวอร์ชัน %d ต่ำเกินไป ทำให้ตัวจัดการไม่สามารถทำงานได้อย่างถูกต้อง โปรดอัปเกรดเป็นเวอร์ชัน %d หรือสูงกว่า! บันทึกการเปลี่ยนแปลง นำเข้าเสร็จสิ้น ส่งออกไปยังคลิปบอร์ด diff --git a/manager/app/src/main/res/values-tr/strings.xml b/manager/app/src/main/res/values-tr/strings.xml index 4331ee14..a0a4e471 100644 --- a/manager/app/src/main/res/values-tr/strings.xml +++ b/manager/app/src/main/res/values-tr/strings.xml @@ -51,7 +51,6 @@ KernelSU\'nun nasıl kurulacağını ve modüllerin nasıl kullanılacağını öğrenin Bizi destekleyin 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. - %2$s kanalımıza katılın.]]> Uygulama profili Varsayılan Şablon @@ -66,7 +65,6 @@ SELinux içeriği Modüllerin bağlantısını kes %s için uygulama profili güncellenemedi. - Mevcut KernelSU sürümü %d, yöneticinin düzgün çalışabilmesi için çok düşük. Lütfen %d sürümüne veya daha yüksek bir sürüme güncelleyin! Varsayılan olarak modüllerin bağlantısını kes Uygulama profilindeki \"Modüllerin bağlantısını kes\" seçeneği için varsayılan değer. Etkinleştirilirse, profil ayarı yapılmamış uygulamalar için modüllerin sistemde yaptığı tüm değişiklikler kaldırılacaktır. Bu seçeneği etkinleştirmek, KernelSU\'nun bu uygulama için modüller tarafından değiştirilen dosyaları geri yüklemesine izin verir. @@ -77,7 +75,7 @@ İndirme başladı: %s Yeni sürüm: %s var, güncellemek için tıklayın. Uygulamayı başlat - Uygulamayı durmaya zorla + Uygulamayı durmaya zorla Uygulamayı yeniden başlat %s için SELinux kuralları güncellenemedi. Değişiklik geçmişi diff --git a/manager/app/src/main/res/values-uk/strings.xml b/manager/app/src/main/res/values-uk/strings.xml index 58485bbd..a318f474 100644 --- a/manager/app/src/main/res/values-uk/strings.xml +++ b/manager/app/src/main/res/values-uk/strings.xml @@ -50,7 +50,6 @@ Дізнайтеся, як інсталювати KernelSU і використовувати модулі Підтримати нас KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування. - Приєднуйтесь до нашого каналу %2$s]]> Профіль додатка Типовий Шаблон @@ -74,12 +73,11 @@ Завантаження модуля: %s Початок завантаження: %s Запустити - Примусово зупинити + Примусово зупинити Перезапустити Нова версія: %s доступна, натисніть, щоб завантажити Не вдалося оновити правила SELinux для: %s Журнал змін - Поточна версія KernelSU %d занадто низька, щоб менеджер міг працювати належним чином. Будь ласка, оновіть до версії %d або вище! Успішно імпортовано Експортувати в буфер обміну Неможливо знайти локальні шаблони для експорту! diff --git a/manager/app/src/main/res/values-vi/strings.xml b/manager/app/src/main/res/values-vi/strings.xml index 300677ec..3ea04e70 100644 --- a/manager/app/src/main/res/values-vi/strings.xml +++ b/manager/app/src/main/res/values-vi/strings.xml @@ -1,132 +1,2 @@ - - Hồ sơ ứng dụng - Mặc định - Mẫu - Tuỳ chỉnh - Tên hồ sơ - Nhóm - Không thể cập nhật Hồ sơ ứng dụng cho %s - Ngắt mô-đun theo mặc định - Giá trị mặc định của \"Ngắt mô-đun\" trong Cấu hình ứng dụng. Nếu bật, KernelSU sẽ khôi phục mọi tệp hệ thống đã sửa đổi bởi mô-đun cho các ứng dụng chưa thiết lập Cấu hình. - Bật tùy chọn này sẽ khôi phục mọi tệp đã sửa đổi bởi các mô-đun cho ứng dụng này. - Cập nhật - Đang tải xuống mô-đun: %s - Bắt đầu tải xuống: %s - Phiên bản mới: %s đã có, bấm để nâng cấp - Tìm hiểu KernelSU - Tìm hiểu cách cài đặt KernelSU và sử dụng các mô-đun - Hỗ trợ chúng tôi - KernelSU sẽ luôn luôn miễn phí và mã nguồn mở. Tuy nhiên bạn có thể ủng hộ chúng tôi bằng một khoản đóng góp nhỏ. - Tham gia kênh %2$s của chúng tôi]]> - Các mô-đun bị vô hiệu hóa vì chúng xung đột với Magisk! - Bạn có muốn gỡ cài đặt mô-đun %s không\? - Nhật ký báo cáo - Trang chủ - Chưa cài đặt - Nhấn để cài đặt - Đang hoạt động - Phiên bản: %d - Không được hỗ trợ - KernelSU hiện tại chỉ hỗ trợ kernel GKI - Kernel - Phiên bản Manager - Fingerprint - Trạng thái SELinux - Vô hiệu hóa - Thực thi - Cho phép - Không rõ - SuperUser - Không thể kích hoạt mô-đun: %s - Không thể vô hiệu hóa mô-đun: %s - Chưa cài đặt mô-đun nào - Mô-đun - Gỡ cài đặt - Cài đặt - Cài đặt - Khởi động lại - Thiết đặt - Khởi động mềm - Khởi động lại vào Recovery - Khởi động lại vào Bootloader - Khởi động lại vào Download Mode - Khởi động lại vào EDL - Giới thiệu - %s được gỡ cài đặt - Lỗi khi gỡ cài đặt: %s - Phiên bản - Tác giả - Làm mới - Hiển thị ứng dụng hệ thống - Ẩn ứng dụng hệ thống - Chế độ an toàn - Khởi động lại để có hiệu lực - https://kernelsu.org/vi_VN/guide/what-is-kernelsu.html - Số superuser: %d - Số mô-đun: %d - Phạm vi - Quy định - Khởi chạy - Khởi động lại - Gắn namespace - Quyền - Không thể cập nhật quy định SELinux cho: %s - Buộc dừng - Thừa hưởng - Chung - Riêng - Bối cảnh SELinux - Ngắt mô-đun - 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! - Đã nhập thành công - Xuất từ khay nhớ tạm - Không thể tìm thấy mẫu cục bộ để xuất! - id bản mẫu đã tồn tại! - Nhật ký thay đổi - Nhập từ khay nhớ tạm - Không nạp được nhật ký thay đổi: %s - Tên - Id mẫu không hợp lệ - Đồng bộ hóa các mẫu trực tuyến - Tạo Bản Mẫu - Nhập/Xuất - Không lưu được mẫu - Sửa Bản Mẫu - Mẫu Hồ Sơ Ứng Dụng - Mô tả - Lưu - Quản lý mẫu Hồ sơ Ứng dụng cục bộ và trực tuyến - Xóa - Clipboard trống! - Xem Bản Mẫu - chỉ đọc - id - Bật gỡ lỗi WebView - Có thể được sử dụng để gỡ lỗi WebUI, vui lòng chỉ bật khi cần. - Không cấp được quyền root! - Kiểm tra cập nhật - Tự động kiểm tra cập nhật khi mở ứng dụng - Mở - Cài đặt vào khe không hoạt động (Sau OTA) - Thiết bị của bạn sẽ **BẮT BUỘC** khởi động vào khe không hoạt động hiện tại sau khi khởi động lại! -\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất. -\nTiếp tục? - Tạm thời gỡ cài đặt KernelSU, khôi phục về trạng thái ban đầu sau lần khởi động lại tiếp theo. - Chọn KMI - Kế tiếp - Cài đặt trực tiếp (Được khuyến nghị) - Chọn một tệp - Gỡ cài đặt - Gỡ cài đặt tạm thời - Gỡ cài đặt vĩnh viễn - Khôi phục hình ảnh gốc - Gỡ cài đặt KernelSU (Root và tất cả các mô-đun) hoàn toàn và vĩnh viễn. - Khôi phục hình ảnh gốc của nhà máy (nếu có bản sao lưu), thường được sử dụng trước OTA; nếu bạn cần gỡ cài đặt KernelSU, vui lòng sử dụng \"Gỡ cài đặt vĩnh viễn\". - Đang cài - Cài thành công - Cài thất bại - Đã chọn lkm: %s - Nên sử dụng hình ảnh phân vùng %1$s - Lưu Nhật Ký - + diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index d5ec58c0..25ac8d04 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -7,8 +7,11 @@ 版本:%d 超级用户数:%d 不支持 - KernelSU 现在只支持 GKI 内核 + 内核上未检测到 KernelSU 驱动程序,内核错误? 内核版本 + SuSFS:%s + SuSFS 版本 + SuS SU 管理器版本 系统指纹 SELinux 状态 @@ -53,7 +56,7 @@ 了解如何安装 KernelSU 以及如何开发模块 支持开发 KernelSU 将保持免费开源,向开发者捐赠以表示支持。 - 加入我们的 %2$s 频道
加入我们的 QQ 频道]]>
+ 加入我们的 %2$s 频道
加入我们的 QQ 群聊]]>
默认 模版 自定义 @@ -67,9 +70,11 @@ SELinux 卸载模块 为 %s 更新 App Profile 失败 - 当前 KernelSU 版本 %d 过低,管理器无法正常工作,请将内核 KernelSU 版本升级至 %d 或以上! + 当前 KernelSU 版本 %d 过低,管理器无法正常工作,请将内核 KernelSU 版本升级至 %d 或以上! 默认卸载模块 App Profile 中“卸载模块”的全局默认值,如果启用,将会为没有设置 Profile 的应用移除所有模块针对系统的修改。 + 隐藏 Kprobe 钩子 + 禁用由 KSU 创建的 Kprobe 钩子,并使用非 Kprobe 内联钩子代替,实现方式类似于不支持 Kprobe 的非 GKI 内核。 启用该选项后将允许 KernelSU 为本应用还原被模块修改过的文件。 规则 @@ -78,10 +83,9 @@ 开始下载:%s 发现新版本:%s,点击升级。 启动 - 强制停止 + 强制停止 重新启动 - 为 %s 更新 SELinux 策略失败 - 不允许授予:%s 超级用户权限 + 为:%s 更新翻译失败 更新日志 App Profile 模版 管理本地和在线的 App Profile 模版 @@ -132,6 +136,82 @@ 选择的 LKM:%s 保存日志 日志已保存 - 关闭 su 兼容 - 临时禁止任何应用通过 su 命令获取 root 权限(已运行的 root 进程不受影响) - + 支持 + 不支持 + 未知 + SuS SU 模式: + + 确认安装模块 %1$s? + 未知模块 + + 确认还原模块 + 此操作将覆盖所有现有模块,是否继续? + 确定 + 取消 + + 备份成功 (tar.gz) + 备份失败:%1$s + 备份模块 + 恢复模块 + + 模块已成功还原,需重启生效 + 还原失败:%1$s + 立即重启 + 未知错误 + + 命令执行失败:%1$s + + 应用列表备份成功 + 应用列表备份失败:%1$s + 确认还原应用列表 + 此操作将覆盖当前的应用列表,是否继续? + 应用列表还原成功 + 应用列表还原失败:%1$s + 备份应用列表 + 还原应用列表 + 自定义背景 + 选择一张图片作为应用背景 + 卡片管理 + 卡片透明度 + 恢复默认 + Android 版本 + 设备 + 不允许授予 %s 超级用户权限 + 禁用 su 兼容性 + 临时禁止任何应用程序通过 su 命令获取 Root 权限(现有的 Root 进程不受影响) + 你正在使用的是MKSU第三方管理器 + 确定要安装选择的 %d 个模块吗? + 确定要安装以下 %1$d 个模块吗?\n\n%2$s + 更多设置 + SELinux + 强制执行 + 兼容模式 + 简洁模式 + 开启后将隐藏不必要的卡片 + 主题模式 + 跟随系统 + 浅色 + 深色 + Manual Hook + 动态颜色 + 使用系统主题的动态颜色 + 选择主题色 + 黄色 + 蓝色 + 绿色 + 紫色 + 橙色 + 粉色 + 高级灰 + 象牙白 + 刷入选项 + 选择要刷入的文件 + Anykernel3 刷写 + 需要 root 权限 + 文件复制失败 + 刷写完成 + 是否立即重启? + + + 重启失败 + \ No newline at end of file diff --git a/manager/app/src/main/res/values-zh-rHK/strings.xml b/manager/app/src/main/res/values-zh-rHK/strings.xml index 920ecaa4..7b2e2399 100644 --- a/manager/app/src/main/res/values-zh-rHK/strings.xml +++ b/manager/app/src/main/res/values-zh-rHK/strings.xml @@ -50,7 +50,6 @@ 瞭解如何安裝 KernelSU 以及如何開發模組 支援開發 KernelSU 將保持免費和開源,您可以考慮向開發人員贊助以表示支持。 - 加入我們的 %2$s 頻道]]> 預設 設定檔名稱 範本 @@ -60,7 +59,7 @@ 卸載模組 無法更新 %s 應用程式設定檔 規則 - 目前 KernelSU 版本 %d 過低,管理器無法正常運作。請升級至 %d 或更高版本! + 目前 KernelSU 版本 %d 過低,管理器無法正常運作。請升級至 %d 或更高版本! 應用程式設定檔中「解除安裝模組」的全域預設值,如果啟用,將會為沒有設定檔的應用程式移除所有模組針對系統的修改。 啟用此選項將允許 KernelSU 為這個應用程式還原任何被模組修改過的檔案。 網域 @@ -75,7 +74,7 @@ 開始下載:%s 新版本:%s 已可供使用,按一下以升級 啟動 - 強制停止 + 強制停止 重新啟動 無法為 %s 更新 SELinux 規則 變更記錄 diff --git a/manager/app/src/main/res/values-zh-rTW/strings.xml b/manager/app/src/main/res/values-zh-rTW/strings.xml index 400fa5cf..c2770a44 100644 --- a/manager/app/src/main/res/values-zh-rTW/strings.xml +++ b/manager/app/src/main/res/values-zh-rTW/strings.xml @@ -50,10 +50,9 @@ 知曉安裝、使用 KernelSU 本體與其模組功能的方法 協助發展 KernelSU 一向以免費製品與開放原始碼自居,矢志不渝。若想協助我們,請以小額捐款表達你對專案發展的大力支持。 - 前往 %2$s 加入頻道]]> 解除掛載模組功能 無法更新「%s」App Profile - 管理工具無法以老舊的 KernelSU %d 版本正常運作。請升級至 %d 以上的版本! + 管理工具無法以老舊的 KernelSU %d 版本正常運作。請升級至 %d 以上的版本! 預設解除掛載模組功能 將 App Profile 的全域預設行為設作「解除掛載模組功能」。啟用後,將向未指派 Profile 的應用程式移除模組功能。 啟用選項後,KernelSU 會將應用程式內遭模組修改的檔案恢復原狀。 @@ -61,7 +60,7 @@ 自訂 權限 規則 - 正在下載模組:%s + 正在下載模組:%s 重新執行 範本 Profile 名稱 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 2d568411..8dadf34d 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - KernelSU + SukiSU Home Not installed Click to install @@ -9,8 +9,11 @@ Superusers: %d Modules: %d Unsupported - KernelSU only supports GKI kernels now + No KernelSU driver detected on your kernel, wrong kernel?. Kernel + SuSFS: %s + Version de SuSFS + SuS SU Manager version Fingerprint SELinux status @@ -23,10 +26,8 @@ Failed to disable module: %s No module installed Module - The following modules will be installed: %1$s Sort (Action first) Sort (Enabled first) - Confirm Uninstall Restore Install @@ -71,9 +72,11 @@ SELinux context Umount modules Failed to update App Profile for %s - The current KernelSU version %d is too low for the manager to work properly. Please upgrade to version %d or higher! + The current KernelSU version %d is too low for the manager to work properly. Please upgrade to version %d or higher! Umount modules by default The global default value for \"Umount modules\" in App Profile. If enabled, it will remove all module modifications to the system for apps that don\'t have a profile set. + Cacher les hooks kprobe + Désactive les hooks kprobe créés par KSU et, à la place, active les hooks non-kprobe intégrés, implémentant les mêmes fonctionnalités qui seraient appliquées à un kernel non-GKI, qui ne supportent krpobe. Enabling this option will allow KernelSU to restore any modified files by the modules for this app. Domain Rules @@ -82,10 +85,9 @@ Start downloading: %s New version %s is available, click to upgrade. Launch - Force stop + Force stop Restart Failed to update SELinux rules for %s - Granting superuser to %s is not allowed Changelog App Profile Template Manage local and online template of App Profile @@ -136,6 +138,82 @@ Selected LKM: %s Save logs Logs saved + Supported + Not Supported + Unknown + SuS SU mode: + + confirm install module %1$s? + unknown module + + Confirm Module Restoration + This operation will overwrite all existing modules. Continue? + Confirm + Cancel + + Backup successful (tar.gz) + Backup failed: %1$s + backup modules + restore modules + + Modules restored successfully, restart required + Restore failed: %1$s + Restart Now + Unknown error + + Command execution failed: %1$s + + Allowlist backup successful + Allowlist backup failed: %1$s + Confirm Allowlist Restoration + This operation will overwrite the current allowlist. Continue? + Allowlist restored successfully + Allowlist restore failed: %1$s + Backup Allowlist + Restore Allowlist + settings custom background + settings custom background summary + card manage + card alpha + restore default + Android version + device model + Granting superuser to %s is not allowed Disable su compatibility - Temporarily disable the ability of any app to gain root privileges via the ⁠su command (existing root processes won\'t be affected). + Temporarily disable any applications from obtaining root privileges via the ⁠su command (existing root processes will not be affected). + You are using the MKSU third-party manager + Are you sure you want to install the selected %d modules? + Sure you want to install the following %1$d modules? \n\n%2$s + more settings + SELinux + Enabled + Disabled + simplicity mode + Hides unnecessary cards when turned on + Theme Mode + follow-up system + light color + dark colored + Manual Hook + Dynamic colours + Dynamic colours using system themes + Choose a theme colour + yellow + blue + green + purple + orange + pink + gray + ivory + Brush Options + Select the file to be flashed + Anykernel3 Flush + Requires root privileges + File Copy Failure + Scrubbing complete + Whether to reboot immediately? + yes + no + Reboot Failed diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts index 5e806524..ac0616ed 100644 --- a/manager/build.gradle.kts +++ b/manager/build.gradle.kts @@ -33,31 +33,27 @@ val androidCompileSdkVersion = 35 val androidCompileNdkVersion = "28.0.13004108" val androidSourceCompatibility = JavaVersion.VERSION_21 val androidTargetCompatibility = JavaVersion.VERSION_21 -val managerVersionCode by extra(getVersionCode()) -val managerVersionName by extra(getVersionName()) +val managerVersionCode by extra(1 * 12000 + getGitCommitCount() + 500) +val managerVersionName by extra(getGitDescribe()) fun getGitCommitCount(): Int { - val out = ByteArrayOutputStream() - exec { + return providers.exec { commandLine("git", "rev-list", "--count", "HEAD") - standardOutput = out - } - return out.toString().trim().toInt() + }.standardOutput.asText.get().trim().toInt() } fun getGitDescribe(): String { - val out = ByteArrayOutputStream() - exec { - commandLine("git", "describe", "--tags", "--always") - standardOutput = out - } - return out.toString().trim() + return providers.exec { + commandLine("git", "describe", "--tags", "--always", "--abbrev=0") + }.standardOutput.asText.get().trim() } + + fun getVersionCode(): Int { val commitCount = getGitCommitCount() val major = 1 - return major * 10000 + commitCount + 200 + return major * 12000 + commitCount + 500 } fun getVersionName(): String { diff --git a/manager/gradle.properties b/manager/gradle.properties index 387da396..62d1c743 100644 --- a/manager/gradle.properties +++ b/manager/gradle.properties @@ -1,3 +1,4 @@ android.experimental.enableNewResourceShrinker.preciseShrinking=true android.enableAppCompileTimeRClass=true android.useAndroidX=true +org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError diff --git a/manager/gradle/libs.versions.toml b/manager/gradle/libs.versions.toml index 53171730..b2416367 100644 --- a/manager/gradle/libs.versions.toml +++ b/manager/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] -agp = "8.8.2" +agp = "8.8.1" kotlin = "2.1.10" -ksp = "2.1.10-1.0.31" +ksp = "2.1.10-1.0.30" compose-bom = "2025.02.00" lifecycle = "2.8.7" -navigation = "2.8.8" -activity-compose = "1.10.1" +navigation = "2.8.7" +activity-compose = "1.10.0" kotlinx-coroutines = "1.10.1" coil-compose = "2.7.0" compose-destination = "2.1.0-beta16" @@ -17,6 +17,11 @@ parcelablelist = "2.0.1" libsu = "6.0.0" apksign = "1.4" cmaker = "1.2" +compose-material = "1.7.8" +compose-material3 = "1.3.1" +compose-ui = "1.7.8" +compose-foundation = "1.7.8" +documentfile = "1.0.1" [plugins] agp-app = { id = "com.android.application", version.ref = "agp" } @@ -33,21 +38,25 @@ lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version.ref = "cmaker" } [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } +androidx-foundation = { module = "androidx.compose.foundation:foundation" } +androidx-material3 = { module = "androidx.compose.material3:material3" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } -androidx-compose-material = { group = "androidx.compose.material", name = "material" } -androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } -androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-compose-material = { group = "androidx.compose.material", name = "material", version.ref = "compose-material" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "compose-material3" } +androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose-ui" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "compose-ui" } +androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose-foundation" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } +androidx-ui = { module = "androidx.compose.ui:ui" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } @@ -71,4 +80,5 @@ sheet-compose-dialogs-input = { group = "com.maxkeppeler.sheets-compose-dialogs" markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" } -lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" } \ No newline at end of file +lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" } +androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } \ No newline at end of file diff --git a/manager/gradle/wrapper/gradle-wrapper.properties b/manager/gradle/wrapper/gradle-wrapper.properties index c1048ec0..e18bc253 100644 --- a/manager/gradle/wrapper/gradle-wrapper.properties +++ b/manager/gradle/wrapper/gradle-wrapper.properties @@ -4,4 +4,4 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists \ No newline at end of file +zipStorePath=wrapper/dists diff --git a/scripts/ksubot.py b/scripts/ksubot.py index 832f86a0..3d254488 100644 --- a/scripts/ksubot.py +++ b/scripts/ksubot.py @@ -1,106 +1,106 @@ -import asyncio -import os -import sys -from telethon import TelegramClient - -API_ID = 611335 -API_HASH = "d524b414d21f4d37f08684c1df41ac9c" - - -BOT_TOKEN = os.environ.get("BOT_TOKEN") -CHAT_ID = os.environ.get("CHAT_ID") -MESSAGE_THREAD_ID = os.environ.get("MESSAGE_THREAD_ID") -COMMIT_URL = os.environ.get("COMMIT_URL") -COMMIT_MESSAGE = os.environ.get("COMMIT_MESSAGE") -RUN_URL = os.environ.get("RUN_URL") -TITLE = os.environ.get("TITLE") -VERSION = os.environ.get("VERSION") -MSG_TEMPLATE = """ -**{title}** -#ci_{version} -``` -{commit_message} -``` -[Commit]({commit_url}) -[Workflow run]({run_url}) -""".strip() - - -def get_caption(): - msg = MSG_TEMPLATE.format( - title=TITLE, - version=VERSION, - commit_message=COMMIT_MESSAGE, - commit_url=COMMIT_URL, - run_url=RUN_URL, - ) - if len(msg) > 1024: - return COMMIT_URL - return msg - - -def check_environ(): - global CHAT_ID, MESSAGE_THREAD_ID - if BOT_TOKEN is None: - print("[-] Invalid BOT_TOKEN") - exit(1) - if CHAT_ID is None: - print("[-] Invalid CHAT_ID") - exit(1) - else: - try: - CHAT_ID = int(CHAT_ID) - except: - pass - if COMMIT_URL is None: - print("[-] Invalid COMMIT_URL") - exit(1) - if COMMIT_MESSAGE is None: - print("[-] Invalid COMMIT_MESSAGE") - exit(1) - if RUN_URL is None: - print("[-] Invalid RUN_URL") - exit(1) - if TITLE is None: - print("[-] Invalid TITLE") - exit(1) - if VERSION is None: - print("[-] Invalid VERSION") - exit(1) - if MESSAGE_THREAD_ID is not None and MESSAGE_THREAD_ID != "": - try: - MESSAGE_THREAD_ID = int(MESSAGE_THREAD_ID) - except: - print("[-] Invaild MESSAGE_THREAD_ID") - exit(1) - else: - MESSAGE_THREAD_ID = None - - -async def main(): - print("[+] Uploading to telegram") - check_environ() - files = sys.argv[1:] - print("[+] Files:", files) - if len(files) <= 0: - print("[-] No files to upload") - exit(1) - print("[+] Logging in Telegram with bot") - script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) - session_dir = os.path.join(script_dir, "ksubot") - async with await TelegramClient(session=session_dir, api_id=API_ID, api_hash=API_HASH).start(bot_token=BOT_TOKEN) as bot: - caption = [""] * len(files) - caption[-1] = get_caption() - print("[+] Caption: ") - print("---") - print(caption) - print("---") - print("[+] Sending") - await bot.send_file(entity=CHAT_ID, file=files, caption=caption, reply_to=MESSAGE_THREAD_ID, parse_mode="markdown") - print("[+] Done!") - -if __name__ == "__main__": - try: - asyncio.run(main()) - except Exception as e: - print(f"[-] An error occurred: {e}") +import asyncio +import os +import sys +from telethon import TelegramClient + +API_ID = 611335 +API_HASH = "d524b414d21f4d37f08684c1df41ac9c" + + +BOT_TOKEN = os.environ.get("BOT_TOKEN") +CHAT_ID = os.environ.get("CHAT_ID") +MESSAGE_THREAD_ID = os.environ.get("MESSAGE_THREAD_ID") +COMMIT_URL = os.environ.get("COMMIT_URL") +COMMIT_MESSAGE = os.environ.get("COMMIT_MESSAGE") +RUN_URL = os.environ.get("RUN_URL") +TITLE = os.environ.get("TITLE") +VERSION = os.environ.get("VERSION") +MSG_TEMPLATE = """ +**{title}** +#ci_{version} +``` +{commit_message} +``` +[Commit]({commit_url}) +[Workflow run]({run_url}) +""".strip() + + +def get_caption(): + msg = MSG_TEMPLATE.format( + title=TITLE, + version=VERSION, + commit_message=COMMIT_MESSAGE, + commit_url=COMMIT_URL, + run_url=RUN_URL, + ) + if len(msg) > 1024: + return COMMIT_URL + return msg + + +def check_environ(): + global CHAT_ID, MESSAGE_THREAD_ID + if BOT_TOKEN is None: + print("[-] Invalid BOT_TOKEN") + exit(1) + if CHAT_ID is None: + print("[-] Invalid CHAT_ID") + exit(1) + else: + try: + CHAT_ID = int(CHAT_ID) + except: + pass + if COMMIT_URL is None: + print("[-] Invalid COMMIT_URL") + exit(1) + if COMMIT_MESSAGE is None: + print("[-] Invalid COMMIT_MESSAGE") + exit(1) + if RUN_URL is None: + print("[-] Invalid RUN_URL") + exit(1) + if TITLE is None: + print("[-] Invalid TITLE") + exit(1) + if VERSION is None: + print("[-] Invalid VERSION") + exit(1) + if MESSAGE_THREAD_ID is not None and MESSAGE_THREAD_ID != "": + try: + MESSAGE_THREAD_ID = int(MESSAGE_THREAD_ID) + except: + print("[-] Invaild MESSAGE_THREAD_ID") + exit(1) + else: + MESSAGE_THREAD_ID = None + + +async def main(): + print("[+] Uploading to telegram") + check_environ() + files = sys.argv[1:] + print("[+] Files:", files) + if len(files) <= 0: + print("[-] No files to upload") + exit(1) + print("[+] Logging in Telegram with bot") + script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) + session_dir = os.path.join(script_dir, "ksubot") + async with await TelegramClient(session=session_dir, api_id=API_ID, api_hash=API_HASH).start(bot_token=BOT_TOKEN) as bot: + caption = [""] * len(files) + caption[-1] = get_caption() + print("[+] Caption: ") + print("---") + print(caption) + print("---") + print("[+] Sending") + await bot.send_file(entity=CHAT_ID, file=files, caption=caption, reply_to=MESSAGE_THREAD_ID, parse_mode="markdown") + print("[+] Done!") + +if __name__ == "__main__": + try: + asyncio.run(main()) + except Exception as e: + print(f"[-] An error occurred: {e}") diff --git a/userspace/ksud/Cargo.lock b/userspace/ksud/Cargo.lock index 6121fa02..733ada44 100644 --- a/userspace/ksud/Cargo.lock +++ b/userspace/ksud/Cargo.lock @@ -784,7 +784,7 @@ dependencies = [ ] [[package]] -name = "ksud" +name = "zakomk" version = "0.1.0" dependencies = [ "android-properties", diff --git a/userspace/ksud/Cargo.toml b/userspace/ksud/Cargo.toml index 7c1a2d17..5c1d1ab2 100644 --- a/userspace/ksud/Cargo.toml +++ b/userspace/ksud/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ksud" +name = "zakomk" version = "0.1.0" edition = "2024" diff --git a/userspace/ksud/build.rs b/userspace/ksud/build.rs index 021418ac..77e881dc 100644 --- a/userspace/ksud/build.rs +++ b/userspace/ksud/build.rs @@ -15,7 +15,7 @@ fn get_git_version() -> Result<(u32, String), std::io::Error> { .trim() .parse() .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to parse git count"))?; - let version_code = 10000 + 200 + version_code; // For historical reasons + let version_code = 12000 + 500 + version_code; // For historical reasons let version_name = String::from_utf8( Command::new("git") diff --git a/userspace/ksud/src/banner b/userspace/ksud/src/banner index 6087569c..268d35d3 100644 --- a/userspace/ksud/src/banner +++ b/userspace/ksud/src/banner @@ -1,5 +1,5 @@ - _ __ _ ____ _ _ - | |/ /___ _ __ _ __ ___| / ___|| | | | - | ' // _ \ '__| '_ \ / _ \ \___ \| | | | - | . \ __/ | | | | | __/ |___) | |_| | - |_|\_\___|_| |_| |_|\___|_|____/ \___/ + ____ _ _ _ ____ _____ _____ + / ___|| |__(_) |__ | ___|| ____|_ _| + \___ \| '_ \ | '_ \|___ \| _| | | + ___) | | | | | | | ___) | |___ | | + |____/|_| |_|_| |_| |____/|_____| |_| \ No newline at end of file diff --git a/userspace/zakomksd/.gitignore b/userspace/zakomksd/.gitignore new file mode 100644 index 00000000..720289cc --- /dev/null +++ b/userspace/zakomksd/.gitignore @@ -0,0 +1,2 @@ +/obj +/libs diff --git a/userspace/zakomksd/jni/Android.mk b/userspace/zakomksd/jni/Android.mk new file mode 100644 index 00000000..92ecd077 --- /dev/null +++ b/userspace/zakomksd/jni/Android.mk @@ -0,0 +1,6 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := zakomksd +LOCAL_SRC_FILES := zakomksd.c +include $(BUILD_EXECUTABLE) diff --git a/userspace/zakomksd/jni/Application.mk b/userspace/zakomksd/jni/Application.mk new file mode 100644 index 00000000..61d31236 --- /dev/null +++ b/userspace/zakomksd/jni/Application.mk @@ -0,0 +1,3 @@ +APP_ABI := arm64-v8a +APP_PLATFORM := android-24 +APP_STL := none diff --git a/userspace/zakomksd/jni/zakomksd.c b/userspace/zakomksd/jni/zakomksd.c new file mode 100644 index 00000000..6779bb3e --- /dev/null +++ b/userspace/zakomksd/jni/zakomksd.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include +#include + +#define KERNEL_SU_OPTION 0xDEADBEEF + +// Command definitions +#define CMD_SUSFS_SHOW_VERSION 0x555e1 +#define CMD_SUSFS_SHOW_ENABLED_FEATURES 0x555e2 +#define CMD_SUSFS_SHOW_VARIANT 0x555e3 +#define CMD_SUSFS_SHOW_SUS_SU_WORKING_MODE 0x555e4 +#define CMD_SUSFS_IS_SUS_SU_READY 0x555f0 +#define CMD_SUSFS_SUS_SU 0x60000 + +// SUS_SU modes +#define SUS_SU_DISABLED 0 +#define SUS_SU_WITH_HOOKS 2 + +// Feature flags +#define FEATURE_SUS_PATH (1 << 0) +#define FEATURE_SUS_MOUNT (1 << 1) +#define FEATURE_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT (1 << 2) +#define FEATURE_AUTO_ADD_SUS_BIND_MOUNT (1 << 3) +#define FEATURE_SUS_KSTAT (1 << 4) +#define FEATURE_SUS_OVERLAYFS (1 << 5) +#define FEATURE_TRY_UMOUNT (1 << 6) +#define FEATURE_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT (1 << 7) +#define FEATURE_SPOOF_UNAME (1 << 8) +#define FEATURE_ENABLE_LOG (1 << 9) +#define FEATURE_HIDE_KSU_SUSFS_SYMBOLS (1 << 10) +#define FEATURE_SPOOF_BOOTCONFIG (1 << 11) +#define FEATURE_OPEN_REDIRECT (1 << 12) +#define FEATURE_SUS_SU (1 << 13) + +struct st_sus_su { + int mode; +}; + +// Function prototypes +int enable_sus_su(int last_working_mode, int target_working_mode); +void print_features(unsigned long enabled_features); +bool is_feature_enabled(unsigned long enabled_features, int feature); +int get_sus_su_working_mode(int* mode); + +int main(int argc, char* argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s >\n", argv[0]); + return 1; + } + + int error = -1; + + if (strcmp(argv[1], "version") == 0) { + char version[16]; + prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_VERSION, version, NULL, &error); + printf("%s\n", error ? "Invalid" : version); + } else if (strcmp(argv[1], "variant") == 0) { + char variant[16]; + prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_VARIANT, variant, NULL, &error); + printf("%s\n", error ? "Invalid" : variant); + } else if (strcmp(argv[1], "features") == 0) { + unsigned long enabled_features; + prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_ENABLED_FEATURES, &enabled_features, NULL, &error); + if (!error) { + print_features(enabled_features); + } else { + printf("Invalid\n"); + } + } else if (strcmp(argv[1], "support") == 0) { + unsigned long enabled_features; + prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_ENABLED_FEATURES, &enabled_features, NULL, &error); + printf("%s\n", error || !enabled_features ? "Unsupported" : "Supported"); + } else if (argc == 3 && strcmp(argv[1], "sus_su") == 0) { + int last_working_mode, target_working_mode; + char* endptr; + + if (get_sus_su_working_mode(&last_working_mode)) { + return 1; + } + + if (strcmp(argv[2], "mode") == 0) { + printf("%d\n", last_working_mode); + return 0; + } + + target_working_mode = strtol(argv[2], &endptr, 10); + if (*endptr != '\0') { + fprintf(stderr, "Invalid argument: %s\n", argv[2]); + return 1; + } + + if (target_working_mode == SUS_SU_WITH_HOOKS) { + bool is_sus_su_ready; + prctl(KERNEL_SU_OPTION, CMD_SUSFS_IS_SUS_SU_READY, &is_sus_su_ready, NULL, &error); + if (error || !is_sus_su_ready) { + printf("[-] sus_su mode %d must be run during or after service stage\n", SUS_SU_WITH_HOOKS); + return 1; + } + if (last_working_mode == SUS_SU_WITH_HOOKS) { + printf("[-] sus_su is already in mode %d\n", last_working_mode); + return 1; + } + enable_sus_su(last_working_mode, SUS_SU_WITH_HOOKS); + } else if (target_working_mode == SUS_SU_DISABLED) { + if (last_working_mode == SUS_SU_DISABLED) { + printf("[-] sus_su is already in mode %d\n", last_working_mode); + return 1; + } + enable_sus_su(last_working_mode, SUS_SU_DISABLED); + } else { + fprintf(stderr, "Invalid mode: %d\n", target_working_mode); + return 1; + } + } else { + fprintf(stderr, "Invalid argument: %s\n", argv[1]); + return 1; + } + + return 0; +} + +// Helper functions +int enable_sus_su(int last_working_mode, int target_working_mode) { + struct st_sus_su info = {target_working_mode}; + int error = -1; + prctl(KERNEL_SU_OPTION, CMD_SUSFS_SUS_SU, &info, NULL, &error); + if (!error) { + printf("[+] sus_su mode %d is enabled\n", target_working_mode); + } + return error; +} + +void print_features(unsigned long enabled_features) { + if (is_feature_enabled(enabled_features, FEATURE_SUS_PATH)) { + printf("CONFIG_KSU_SUSFS_SUS_PATH\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_SUS_MOUNT)) { + printf("CONFIG_KSU_SUSFS_SUS_MOUNT\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT)) { + printf("CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_AUTO_ADD_SUS_BIND_MOUNT)) { + printf("CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_SUS_KSTAT)) { + printf("CONFIG_KSU_SUSFS_SUS_KSTAT\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_SUS_OVERLAYFS)) { + printf("CONFIG_KSU_SUSFS_SUS_OVERLAYFS\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_TRY_UMOUNT)) { + printf("CONFIG_KSU_SUSFS_TRY_UMOUNT\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT)) { + printf("CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_SPOOF_UNAME)) { + printf("CONFIG_KSU_SUSFS_SPOOF_UNAME\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_ENABLE_LOG)) { + printf("CONFIG_KSU_SUSFS_ENABLE_LOG\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_HIDE_KSU_SUSFS_SYMBOLS)) { + printf("CONFIG_KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_SPOOF_BOOTCONFIG)) { + printf("CONFIG_KSU_SUSFS_SPOOF_BOOTCONFIG\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_OPEN_REDIRECT)) { + printf("CONFIG_KSU_SUSFS_OPEN_REDIRECT\n"); + } + if (is_feature_enabled(enabled_features, FEATURE_SUS_SU)) { + printf("CONFIG_KSU_SUSFS_SUS_SU\n"); + } +} + +bool is_feature_enabled(unsigned long enabled_features, int feature) { + return (enabled_features & feature) != 0; +} + +int get_sus_su_working_mode(int* mode) { + int error = -1; + prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_SUS_SU_WORKING_MODE, mode, NULL, &error); + return error; +} \ No newline at end of file diff --git a/website/docs/guide/difference-with-magisk.md b/website/docs/guide/difference-with-magisk.md index 1625634b..1e1cc08b 100644 --- a/website/docs/guide/difference-with-magisk.md +++ b/website/docs/guide/difference-with-magisk.md @@ -20,7 +20,7 @@ Before understanding the differences, it's important to know how to identify whe Here are some differences: - KernelSU modules cannot be installed in Recovery mode. -- KernelSU modules don't have built-in support for Zygisk, but you can use Zygisk modules through [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext). +- KernelSU modules don't have built-in support for Zygisk, but you can use Zygisk modules through [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext)). - The method for replacing or deleting files in KernelSU modules is completely different from Magisk. KernelSU doesn't support the `.replace` method. Instead, you need to create a same-named file with `mknod filename c 0 0` to delete the corresponding file. - The directories for BusyBox are different. The built-in BusyBox in KernelSU is located at `/data/adb/ksu/bin/busybox`, while in Magisk it is at `/data/adb/magisk/busybox`. **Note that this is an internal behavior of KernelSU and may change in the future!** - KernelSU doesn't support `.replace` files, but it supports the `REMOVE` and `REPLACE` variables to remove or replace files and folders. diff --git a/website/docs/guide/faq.md b/website/docs/guide/faq.md index 066bc0d0..5f68397c 100644 --- a/website/docs/guide/faq.md +++ b/website/docs/guide/faq.md @@ -2,13 +2,9 @@ ## Does KernelSU support my device? -KernelSU supports devices running Android with an unlocked bootloader. However, official support is only for GKI Linux Kernels 5.10+ (in practice, this means your device needs to have Android 12 out-of-the-box to be supported). +First, your devices should be able to unlock the bootloader. If not, then there is unsupported. -You can easily check the support for your device through the KernelSU manager, which is available [here](https://github.com/tiann/KernelSU/releases). - -If the app shows `Not installed`, it means your device is officially supported by KernelSU. - -If the app shows `Unsupported`, it means your device isn't officially supported at present. However, you can build kernel source code and integrate KernelSU to make it work, or use [Unofficially supported devices](unofficially-support-devices). +Next, install the KernelSU manager on your device and open it. If it shows `Unsupported`, then your device cannot be supported immediately. However, you can build kernel source and integrate KernelSU to make it work, or use [Unofficially supported devices](unofficially-support-devices). ## Does KernelSU need to unlock bootloader? @@ -16,15 +12,15 @@ Certainly, yes. ## Does KernelSU support modules? -Yes, most Magisk modules work on KernelSU. Check [Module guide](module.md) for more info. +Yes, check [Module guide](module.md). ## Does KernelSU support Xposed? -Yes, you can use LSPosed (or other modern Xposed derivative) with [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext). +Yes, you can use LSPosed with [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext). ## Does KernelSU support Zygisk? -KernelSU has no built-in Zygisk support, but you can use a module like [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext) to support it. +KernelSU has no built-in Zygisk support, but you can use [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext). ## Is KernelSU compatible with Magisk? @@ -69,7 +65,7 @@ We don't recommend that you modify the system partition directly. Please check [ ## Can KernelSU modify hosts? How can I use AdAway? -Of course. But KernelSU doesn't have built-in hosts support, you can install a module like [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) to do it. +Of course. But KernelSU doesn't have built-in hosts support, you can install [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) to do it. ## Why is there a huge 1 TB file? diff --git a/website/docs/guide/how-to-build.md b/website/docs/guide/how-to-build.md index 4a11b22f..0260ce2e 100644 --- a/website/docs/guide/how-to-build.md +++ b/website/docs/guide/how-to-build.md @@ -58,7 +58,7 @@ If you can successfully build the kernel, adding support for KernelSU will be re curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash - ``` -```sh[main branch (dev)] +```sh[ main branch (dev)] curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s main ``` diff --git a/website/docs/guide/module.md b/website/docs/guide/module.md index fa231939..9fcff84f 100644 --- a/website/docs/guide/module.md +++ b/website/docs/guide/module.md @@ -99,7 +99,7 @@ author= description= ``` -- `id` has to match this regular expression: `^[a-zA-Z][a-zA-Z0-9._-]+$`
+- `id` has to match this regular expression: `^[a-zA-Z][a-zA-Z0-9._-]+$` .
Example: ✓ `a_module`, ✓ `a.module`, ✓ `module-101`, ✗ `a module`, ✗ `1_module`, ✗ `-a-module`
This is the **unique identifier** of your module. You should not change it once published. - `versionCode` has to be an **integer**. This is used to compare versions. @@ -150,7 +150,7 @@ REPLACE=" This list will automatically create the directories `$MODPATH/system/app/YouTube` and `$MODPATH/system/app/Bloatware`, and then execute `setfattr -n trusted.overlay.opaque -v y $MODPATH/system/app/YouTube` and `setfattr -n trusted.overlay.opaque -v y $MODPATH/system/app/Bloatware`. After the module takes effect, `/system/app/YouTube` and `/system/app/Bloatware` will be replaced with empty directories. ::: tip DIFFERENCE WITH MAGISK -KernelSU's systemless mechanism is implemented through the kernel's OverlayFS, while Magisk currently uses magic mount (bind mount). These two implementation methods have significant differences, but the ultimate goal is the same: modifying `/system` files without physically modifying the `/system` partition. +KernelSU's systemless mechanism is implemented through the kernel's OverlayFS, while Magisk currently uses magic mount (bind mount). These two implementation methods have significant differences, but the ultimate goal is the same: modifying /system files without physically modifying the /system partition. ::: If you're interested in OverlayFS, it's recommended to read the Linux Kernel's [documentation on OverlayFS](https://docs.kernel.org/filesystems/overlayfs.html). @@ -250,8 +250,8 @@ In KernelSU, scripts are divided into two types based on their running mode: pos In KernelSU, startup scripts are divided into two types based on their storage location: general scripts and module scripts. - General scripts - - Placed in `/data/adb/post-fs-data.d`, `/data/adb/service.d`, `/data/adb/post-mount.d` or `/data/adb/boot-completed.d`. - - Only executed if the script is set as executable (`chmod +x script.sh`). + - Placed in `/data/adb/post-fs-data.d`, `/data/adb/service.d`, `/data/adb/post-mount.d` or `/data/adb/boot-completed.d.` + - Only executed if the script is set as executable (`chmod +x script.sh`) - Scripts in `post-fs-data.d` runs in post-fs-data mode, and scripts in `service.d` runs in late_start service mode. - Modules should **NOT** add general scripts during installation. - Module scripts diff --git a/website/docs/ja_JP/guide/faq.md b/website/docs/ja_JP/guide/faq.md index c3995db5..397a322d 100644 --- a/website/docs/ja_JP/guide/faq.md +++ b/website/docs/ja_JP/guide/faq.md @@ -60,7 +60,7 @@ KernelSU は現在カーネル4.14にバックポートされていますが、 今はまだありませんが(将来的にはあるかもしれません)、グローバルマウントの名前空間に手動で切り替える方法は、以下のようにたくさんあります: 1. `nsenter -t 1 -m sh` でシェルをグローバル名前空間にします。 -2. `nsenter --mount=/proc/1/ns/mnt` を実行したいコマンドに追加すればグローバル名前空間で実行されます。 KernelSU は [このような使い方](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt#L115) もできます。 +2. `nsenter --mount=/proc/1/ns/mnt` を実行したいコマンドに追加すればグローバル名前空間で実行されます。 KernelSU は [このような使い方](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/shirkneko/zako/mksu/ui/util/KsuCli.kt#L115) もできます。 ## GKI 1.0 なのですが、使えますか? diff --git a/website/docs/pt_BR/guide/app-profile.md b/website/docs/pt_BR/guide/app-profile.md index e21af3e3..1c2b68d2 100644 --- a/website/docs/pt_BR/guide/app-profile.md +++ b/website/docs/pt_BR/guide/app-profile.md @@ -1,118 +1,118 @@ -# Perfil do Aplicativo - -O Perfil do Aplicativo é um mecanismo fornecido pelo KernelSU para personalizar a configuração de vários apps. - -Para apps com privilégios root (ou seja, capazes de usar `su`), o Perfil do Aplicativo também pode ser chamado de Perfil root. Ele permite a customização das regras `uid`, `gid`, `grupos`, `capacidades` e `SELinux` do comando `su`, restringindo assim os privilégios do usuário root. Por exemplo, ele pode conceder permissões de rede apenas para apps de firewall enquanto nega permissões de acesso a arquivos, ou pode conceder permissões de shell em vez de acesso root completo para apps congelados: **mantendo o poder confinado com o princípio do menor privilégio.** - -Para apps comuns sem privilégios root, o Perfil do Aplicativo pode controlar o comportamento do kernel e do sistema de módulos em relação a esses apps. Por exemplo, pode determinar se as modificações resultantes dos módulos devem ser abordadas. O kernel e o sistema de módulos podem tomar decisões com base nesta configuração, como realizar operações semelhantes a "ocultar". - -## Perfil root - -### UID, GID e Grupos - -Os sistemas Linux possuem dois conceitos: usuários e grupos. Cada usuário possui um ID de usuário (UID) e pode pertencer a vários grupos, cada um com seu próprio ID de grupo (GID). Esses IDs são usados ​​para identificar usuários no sistema e determinar quais recursos do sistema eles podem acessar. - -Os usuários com UID 0 são conhecidos como usuários root, e grupos com GID 0 são chamados de grupos root. O grupo de usuários root geralmente tem os privilégios mais altos no sistema. - -No caso do sistema Android, cada app funciona como um usuário separado (exceto em casos de UID compartilhado) e recebe um UID exclusivo. Por exemplo, `0` representa o usuário root, `1000` representa `system`, `2000` ao ADB shell e os UIDs de `10000` a `19999` são atribuídos a apps comuns. - -::: info INFORMAÇÕES -Aqui, o UID mencionado não é o mesmo que o conceito de múltiplos usuários ou perfis de trabalho no sistema Android. Os perfis de trabalho são, na verdade, implementados particionando o intervalo UID. Por exemplo, 10000-19999 representa o usuário principal, enquanto 110000-119999 representa um perfil de trabalho. Cada app comum entre eles possui seu próprio UID exclusivo. -::: - -Cada app pode ter vários grupos, com o GID representando o grupo principal, que geralmente corresponde ao UID. Outros grupos são conhecidos como grupos suplementares. Certas permissões são controladas por meio de grupos, como permissões de acesso à rede ou acesso Bluetooth. - -Por exemplo, se executarmos o comando `id` no ADB shell, a saída pode ser semelhante a esta: - -```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_rw),1079(ext_obb_rw),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid),3012(readtracefs) context=u:r:shell:s0 -``` - -Aqui, o UID é `2000` e o GID (ID do grupo primário) também é `2000`. Além disso, pertence a vários grupos suplementares, como `inet` (indicando a capacidade de criar soquetes `AF_INET` e `AF_INET6`) e `sdcard_rw` (indicando permissões de leitura/gravação para o cartão SD). - -O Perfil root do KernelSU permite personalizar o UID, GID e grupos para o processo root após a execução de `su`. Por exemplo, o Perfil root de um app root pode definir seu UID como `2000`, o que significa que, ao usar `su`, as permissões reais do app estão no nível do ADB shell. Além disso, o grupo `inet` pode ser removido, evitando que o comando `su` tenha acesso à rede. - -::: tip OBSERVAÇÃO -O Perfil do Aplicativo controla apenas as permissões do processo root após usar `su` e não afeta as permissões do próprio app. Se um app solicitou permissão para acessar a rede, ele ainda poderá acessar a rede mesmo sem usar `su`. Remover o grupo `inet` de `su` apenas impede que `su` acesse a rede. -::: - -O Perfil root é aplicado no kernel e não depende do comportamento voluntário de apps root, ao contrário da troca de usuários ou grupos por meio de `su`. A concessão da permissão `su` depende inteiramente do usuário e não do desenvolvedor. - -### Capacidades - -As capacidades são um mecanismo para separação de privilégios no Linux. - -Para realizar verificações de permissão, as implementações tradicionais do `UNIX` distinguem duas categorias de processos: processos privilegiados (cujo ID de usuário efetivo é `0`, referido como superusuário ou root) e processos sem privilégios (cujo UID efetivo é diferente de zero). Os processos privilegiados ignoram todas as verificações de permissão do kernel, enquanto os processos não privilegiados estão sujeitos à verificação completa de permissão com base nas credenciais do processo (geralmente: UID efetivo, GID efetivo e lista de grupos suplementares). - -A partir do Linux 2.2, o Linux divide os privilégios tradicionalmente associados ao superusuário em unidades distintas, conhecidas como capacidades, que podem ser ativadas e desativadas de forma independente. - -Cada capacidade representa um ou mais privilégios. Por exemplo, `CAP_DAC_READ_SEARCH` representa a capacidade de ignorar verificações de permissão para leitura de arquivos, bem como permissões de leitura e execução de diretório. Se um usuário com um UID efetivo `0` (usuário root) não tiver a capacidade `CAP_DAC_READ_SEARCH` ou superiores, isso significa que mesmo sendo root, ele não pode ler arquivos à vontade. - -O Perfil root do KernelSU permite a personalização das capacidades do processo root após a execução de `su`, concedendo assim "privilégios root" de forma parcial. Ao contrário do UID e GID mencionados acima, certos apps root exigem um UID de `0` após usar `su`. Nesses casos, limitar as capacidades deste usuário root com UID `0` pode restringir as operações que ele pode realizar. - -::: tip FORTE RECOMENDAÇÃO -A [documentação oficial](https://man7.org/linux/man-pages/man7/capabilities.7.html) da capacidade do Linux fornece explicações detalhadas das habilidades representadas por cada capacidade. Se você pretende customizar as capacidade, é altamente recomendável que você leia este documento primeiro. -::: - -### SELinux - -SELinux é um poderoso mecanismo do Controle de Acesso Obrigatório (MAC). Ele opera com base no princípio de **negação padrão**. Qualquer ação não explicitamente permitida é negada. - -O SELinux pode ser executado em dois modos globais: - -1. Modo permissivo (Permissive): Os eventos de negação são registrados, mas não aplicados. -2. Modo impondo (Enforcing): Os eventos de negação são registrados e aplicados. - -::: warning AVISO -Os sistemas Android modernos dependem fortemente do SELinux para garantir a segurança geral do sistema. É altamente recomendável não usar nenhum sistema personalizado executado em "Modo permissivo", pois ele não oferece vantagens significativas em relação a um sistema completamente aberto. -::: - -Explicar o conceito completo do SELinux é complexo e está além do objetivo deste documento. Recomenda-se primeiro entender seu funcionamento através dos seguintes recursos: - -1. [Wikipédia](https://en.wikipedia.org/wiki/Security-Enhanced_Linux) -2. [Red Hat: O que é SELinux?](https://www.redhat.com/pt-br/topics/linux/what-is-selinux) -3. [ArchLinux: SELinux](https://wiki.archlinux.org/title/SELinux) - -O Perfil root do KernelSU permite a personalização do contexto SELinux do processo root após a execução de `su`. Regras específicas de controle de acesso podem ser definidas para este contexto, possibilitando um controle refinado sobre os privilégios root. - -Em cenários típicos, quando um app executa `su`, ele alterna o processo para um domínio SELinux com **acesso irrestrito**, como `u:r:su:s0`. Através do Perfil root, esse domínio pode ser mudado para um domínio personalizado, como `u:r:app1:s0`, e uma série de regras podem ser definidas para esse domínio: - -```sh -type app1 -enforce app1 -typeattribute app1 mlstrustedsubject -allow app1 * * * -``` - -Observe que a regra `allow app1 * * *` é usada apenas para fins de demonstração. Na prática, esta regra não deve ser usada extensivamente, pois não difere muito do Modo permissivo. - -### Escalação - -Se a configuração do Perfil root não estiver definida corretamente, poderá ocorrer um cenário de escalação. As restrições impostas pelo Perfil root poderão falhar involuntariamente. - -Por exemplo, se você conceder permissão root a um usuário ADB shell (que é um caso comum) e, em seguida, conceder permissão root a um app normal, mas configurar seu Perfil root com o UID 2000 (o UID do usuário ADB shell), o app pode obter acesso root completo ao executar o comando `su` duas vezes: - -1. A primeira execução de `su` será sujeita ao Perfil do Aplicativo, e mudará para o UID `2000` (ADB shell) em vez de `0` (root). -2. A segunda execução de `su`, como o UID é `2000` e você concedeu acesso root ao UID `2000` (ADB shell) na configuração, o app obterá privilégios root completo. - -::: warning OBSERVAÇÃO -Este comportamento é totalmente esperado e não é um bug. Portanto, recomendamos o seguinte: - -Se você realmente precisa conceder privilégios root ao ADB (por exemplo, como desenvolvedor), não é aconselhável alterar o UID para `2000` ao configurar o Perfil root. Usar `1000` (sistema) seria uma melhor escolha. -::: - -## Perfil não root - -### Desmontar módulos - -O KernelSU fornece um mecanismo sem sistema para modificar partições do sistema, obtido através da montagem do OverlayFS. No entanto, alguns apps podem ser sensíveis a esse comportamento. Nesse caso, podemos descarregar módulos montados nesses apps configurando a opção "Desmontar módulos". - -Além disso, a interface de configurações do gerenciador do KernelSU oferece a opção "Desmontar módulos por padrão". Por padrão, essa opção está **ativada**, o que significa que o KernelSU ou alguns módulos descarregarão módulos para este app, a menos que configurações adicionais sejam aplicadas. Se você não preferir esta configuração ou se ela afetar determinados apps, você terá as seguintes opções: - -1. Manter a opção "Desmontar módulos por padrão" ativada e desative individualmente a opção "Desmontar módulos" no Perfil do Aplicativo para apps que exigem o carregamento do módulo (agindo como uma "lista de permissões"). -2. Desativar a opção "Desmontar módulos por padrão" e ativar individualmente a opção "Desmontar módulos" no Perfil do Aplicativo para apps que exigem o descarregamento do módulo (agindo como uma "lista negra"). - -::: info INFORMAÇÕES -Em dispositivos que utilizam a versão do kernel 5.10 ou superior, o kernel realiza qualquer ação adicional do descarregamento de módulos. No entanto, para dispositivos que executam versões do kernel abaixo de 5.10, essa opção é apenas uma opção de configuração e o próprio KernelSU não executa nenhuma ação. Se você quiser usar a opção "Desmontar módulos" em versões do kernel anteriores a 5.10, é necessário portar a função `path_umount` em `fs/namespace.c`. Você pode obter mais informações no final da página [Integração para dispositivos não-GKI](https://kernelsu.org/pt_BR/guide/how-to-integrate-for-non-gki.html). Alguns módulos, como ZygiskNext, também podem usar essa opção para determinar se o descarregamento do módulo é necessário. -::: +# Perfil do Aplicativo + +O Perfil do Aplicativo é um mecanismo fornecido pelo KernelSU para personalizar a configuração de vários apps. + +Para apps com privilégios root (ou seja, capazes de usar `su`), o Perfil do Aplicativo também pode ser chamado de Perfil root. Ele permite a customização das regras `uid`, `gid`, `grupos`, `capacidades` e `SELinux` do comando `su`, restringindo assim os privilégios do usuário root. Por exemplo, ele pode conceder permissões de rede apenas para apps de firewall enquanto nega permissões de acesso a arquivos, ou pode conceder permissões de shell em vez de acesso root completo para apps congelados: **mantendo o poder confinado com o princípio do menor privilégio.** + +Para apps comuns sem privilégios root, o Perfil do Aplicativo pode controlar o comportamento do kernel e do sistema de módulos em relação a esses apps. Por exemplo, pode determinar se as modificações resultantes dos módulos devem ser abordadas. O kernel e o sistema de módulos podem tomar decisões com base nesta configuração, como realizar operações semelhantes a "ocultar". + +## Perfil root + +### UID, GID e Grupos + +Os sistemas Linux possuem dois conceitos: usuários e grupos. Cada usuário possui um ID de usuário (UID) e pode pertencer a vários grupos, cada um com seu próprio ID de grupo (GID). Esses IDs são usados ​​para identificar usuários no sistema e determinar quais recursos do sistema eles podem acessar. + +Os usuários com UID 0 são conhecidos como usuários root, e grupos com GID 0 são chamados de grupos root. O grupo de usuários root geralmente tem os privilégios mais altos no sistema. + +No caso do sistema Android, cada app funciona como um usuário separado (exceto em casos de UID compartilhado) e recebe um UID exclusivo. Por exemplo, `0` representa o usuário root, `1000` representa `system`, `2000` ao ADB shell e os UIDs de `10000` a `19999` são atribuídos a apps comuns. + +::: info INFORMAÇÕES +Aqui, o UID mencionado não é o mesmo que o conceito de múltiplos usuários ou perfis de trabalho no sistema Android. Os perfis de trabalho são, na verdade, implementados particionando o intervalo UID. Por exemplo, 10000-19999 representa o usuário principal, enquanto 110000-119999 representa um perfil de trabalho. Cada app comum entre eles possui seu próprio UID exclusivo. +::: + +Cada app pode ter vários grupos, com o GID representando o grupo principal, que geralmente corresponde ao UID. Outros grupos são conhecidos como grupos suplementares. Certas permissões são controladas por meio de grupos, como permissões de acesso à rede ou acesso Bluetooth. + +Por exemplo, se executarmos o comando `id` no ADB shell, a saída pode ser semelhante a esta: + +```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_rw),1079(ext_obb_rw),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid),3012(readtracefs) context=u:r:shell:s0 +``` + +Aqui, o UID é `2000` e o GID (ID do grupo primário) também é `2000`. Além disso, pertence a vários grupos suplementares, como `inet` (indicando a capacidade de criar soquetes `AF_INET` e `AF_INET6`) e `sdcard_rw` (indicando permissões de leitura/gravação para o cartão SD). + +O Perfil root do KernelSU permite personalizar o UID, GID e grupos para o processo root após a execução de `su`. Por exemplo, o Perfil root de um app root pode definir seu UID como `2000`, o que significa que, ao usar `su`, as permissões reais do app estão no nível do ADB shell. Além disso, o grupo `inet` pode ser removido, evitando que o comando `su` tenha acesso à rede. + +::: tip OBSERVAÇÃO +O Perfil do Aplicativo controla apenas as permissões do processo root após usar `su` e não afeta as permissões do próprio app. Se um app solicitou permissão para acessar a rede, ele ainda poderá acessar a rede mesmo sem usar `su`. Remover o grupo `inet` de `su` apenas impede que `su` acesse a rede. +::: + +O Perfil root é aplicado no kernel e não depende do comportamento voluntário de apps root, ao contrário da troca de usuários ou grupos por meio de `su`. A concessão da permissão `su` depende inteiramente do usuário e não do desenvolvedor. + +### Capacidades + +As capacidades são um mecanismo para separação de privilégios no Linux. + +Para realizar verificações de permissão, as implementações tradicionais do `UNIX` distinguem duas categorias de processos: processos privilegiados (cujo ID de usuário efetivo é `0`, referido como superusuário ou root) e processos sem privilégios (cujo UID efetivo é diferente de zero). Os processos privilegiados ignoram todas as verificações de permissão do kernel, enquanto os processos não privilegiados estão sujeitos à verificação completa de permissão com base nas credenciais do processo (geralmente: UID efetivo, GID efetivo e lista de grupos suplementares). + +A partir do Linux 2.2, o Linux divide os privilégios tradicionalmente associados ao superusuário em unidades distintas, conhecidas como capacidades, que podem ser ativadas e desativadas de forma independente. + +Cada capacidade representa um ou mais privilégios. Por exemplo, `CAP_DAC_READ_SEARCH` representa a capacidade de ignorar verificações de permissão para leitura de arquivos, bem como permissões de leitura e execução de diretório. Se um usuário com um UID efetivo `0` (usuário root) não tiver a capacidade `CAP_DAC_READ_SEARCH` ou superiores, isso significa que mesmo sendo root, ele não pode ler arquivos à vontade. + +O Perfil root do KernelSU permite a personalização das capacidades do processo root após a execução de `su`, concedendo assim "privilégios root" de forma parcial. Ao contrário do UID e GID mencionados acima, certos apps root exigem um UID de `0` após usar `su`. Nesses casos, limitar as capacidades deste usuário root com UID `0` pode restringir as operações que ele pode realizar. + +::: tip FORTE RECOMENDAÇÃO +A [documentação oficial](https://man7.org/linux/man-pages/man7/capabilities.7.html) da capacidade do Linux fornece explicações detalhadas das habilidades representadas por cada capacidade. Se você pretende customizar as capacidade, é altamente recomendável que você leia este documento primeiro. +::: + +### SELinux + +SELinux é um poderoso mecanismo do Controle de Acesso Obrigatório (MAC). Ele opera com base no princípio de **negação padrão**. Qualquer ação não explicitamente permitida é negada. + +O SELinux pode ser executado em dois modos globais: + +1. Modo permissivo: Os eventos de negação são registrados, mas não aplicados. +2. Modo de aplicação: Os eventos de negação são registrados e aplicados. + +::: warning AVISO +Os sistemas Android modernos dependem fortemente do SELinux para garantir a segurança geral do sistema. É altamente recomendável não usar nenhum sistema personalizado executado em "Modo permissivo", pois ele não oferece vantagens significativas em relação a um sistema completamente aberto. +::: + +Explicar o conceito completo do SELinux é complexo e está além do objetivo deste documento. Recomenda-se primeiro entender seu funcionamento através dos seguintes recursos: + +1. [Wikipédia](https://en.wikipedia.org/wiki/Security-Enhanced_Linux) +2. [Red Hat: O que é SELinux?](https://www.redhat.com/pt-br/topics/linux/what-is-selinux) +3. [ArchLinux: SELinux](https://wiki.archlinux.org/title/SELinux) + +O Perfil root do KernelSU permite a personalização do contexto SELinux do processo root após a execução de `su`. Regras específicas de controle de acesso podem ser definidas para este contexto, possibilitando um controle refinado sobre os privilégios root. + +Em cenários típicos, quando um app executa `su`, ele alterna o processo para um domínio SELinux com **acesso irrestrito**, como `u:r:su:s0`. Através do Perfil root, esse domínio pode ser mudado para um domínio personalizado, como `u:r:app1:s0`, e uma série de regras podem ser definidas para esse domínio: + +```sh +type app1 +enforce app1 +typeattribute app1 mlstrustedsubject +allow app1 * * * +``` + +Observe que a regra `allow app1 * * *` é usada apenas para fins de demonstração. Na prática, esta regra não deve ser utilizada extensivamente, pois não difere muito do Modo permissivo. + +### Escalação + +Se a configuração do Perfil root não estiver definida corretamente, poderá ocorrer um cenário de escalação. As restrições impostas pelo Perfil root poderão falhar involuntariamente. + +Por exemplo, se você conceder permissão root a um usuário ADB shell (que é um caso comum) e, em seguida, conceder permissão root a um app normal, mas configurar seu Perfil root com o UID 2000 (o UID do usuário ADB shell), o app pode obter acesso root completo ao executar o comando `su` duas vezes: + +1. A primeira execução de `su` será sujeita ao Perfil do Aplicativo, e mudará para o UID `2000` (ADB shell) em vez de `0` (root). +2. A segunda execução de `su`, como o UID é `2000` e você concedeu acesso root ao UID `2000` (ADB shell) na configuração, o app obterá privilégios root completo. + +::: warning OBSERVAÇÃO +Este comportamento é totalmente esperado e não é um bug. Portanto, recomendamos o seguinte: + +Se você realmente precisa conceder privilégios root ao ADB (por exemplo, como desenvolvedor), não é aconselhável alterar o UID para `2000` ao configurar o Perfil root. Usar `1000` (system) seria uma melhor escolha. +::: + +## Perfil não root + +### Desmontar módulos + +O KernelSU fornece um mecanismo sem sistema para modificar partições do sistema, obtido através da montagem do OverlayFS. No entanto, alguns apps podem ser sensíveis a esse comportamento. Nesse caso, podemos descarregar módulos montados nesses apps configurando a opção "Desmontar módulos". + +Além disso, a interface de configurações do gerenciador do KernelSU oferece a opção "Desmontar módulos por padrão". Por padrão, essa opção está **ativada**, o que significa que o KernelSU ou alguns módulos descarregarão módulos para este app, a menos que configurações adicionais sejam aplicadas. Se você não preferir esta configuração ou se ela afetar determinados apps, você terá as seguintes opções: + +1. Manter a opção "Desmontar módulos por padrão" ativada e desative individualmente a opção "Desmontar módulos" no Perfil do Aplicativo para apps que exigem o carregamento do módulo (agindo como uma "lista de permissões"). +2. Desativar a opção "Desmontar módulos por padrão" e ativar individualmente a opção "Desmontar módulos" no Perfil do Aplicativo para apps que exigem o descarregamento do módulo (agindo como uma "lista negra"). + +::: info INFORMAÇÕES +Em dispositivos que utilizam a versão do kernel 5.10 ou superior, o kernel realiza qualquer ação adicional do descarregamento de módulos. No entanto, para dispositivos que executam versões do kernel abaixo de 5.10, essa opção é apenas uma opção de configuração e o próprio KernelSU não executa nenhuma ação. Se você quiser usar a opção "Desmontar módulos" em versões do kernel anteriores a 5.10, é necessário portar a função `path_umount` em `fs/namespace.c`. Você pode obter mais informações no final da página [Integração para dispositivos não-GKI](https://kernelsu.org/pt_BR/guide/how-to-integrate-for-non-gki.html). Alguns módulos, como ZygiskNext, também podem usar essa opção para determinar se o descarregamento do módulo é necessário. +::: diff --git a/website/docs/pt_BR/guide/difference-with-magisk.md b/website/docs/pt_BR/guide/difference-with-magisk.md index deac8833..41cb6511 100644 --- a/website/docs/pt_BR/guide/difference-with-magisk.md +++ b/website/docs/pt_BR/guide/difference-with-magisk.md @@ -1,28 +1,28 @@ -# Diferenças com Magisk - -Embora os módulos do KernelSU e do Magisk tenham muitas semelhanças, existem inevitavelmente algumas diferenças devido aos seus mecanismos de implementação completamente diferentes. Se você deseja que seu módulo funcione tanto no Magisk quanto no KernelSU, é essencial compreender essas diferenças. - -## Semelhanças - -- Formato de arquivo do módulo: Ambos usam o formato ZIP para organizar os módulos, e o formato dos módulos é praticamente o mesmo. -- Diretório de instalação do módulo: Ambos estão localizados em `/data/adb/modules`. -- Sem sistema: Ambos suportam a modificação de `/system` de forma sem sistema por meio de módulos. -- post-fs-data.sh: O tempo de execução e a semântica são exatamente os mesmos. -- service.sh: O tempo de execução e a semântica são exatamente os mesmos. -- system.prop: Completamente o mesmo. -- sepolicy.rule: Completamente o mesmo. -- BusyBox: Os scripts são executados no BusyBox com o "Modo Autônomo" ativado em ambos os casos. - -## Diferenças - -Antes de entender as diferenças, é importante saber como identificar se o seu módulo está sendo executado no KernelSU ou no Magisk. Você pode usar a variável de ambiente `KSU` para diferenciá-lo em todos os locais onde você pode executar os scripts do módulo (`customize.sh`, `post-fs-data.sh`, `service.sh`). No KernelSU, essa variável de ambiente será definida como `true`. - -Aqui estão algumas diferenças: - -- Os módulos KernelSU não podem ser instalados no modo Recovery. -- Os módulos KernelSU não oferece suporte nativo ao Zygisk, mas você pode usar módulos Zygisk através do [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext). -- O método para substituir ou excluir arquivos nos módulos do KernelSU é completamente diferente do Magisk. O KernelSU não suporta o método `.replace`. Em vez disso, você deve criar um arquivo com o comando `mknod filename c 0 0` para excluir o arquivo correspondente. -- Os diretórios do BusyBox são diferentes. O BusyBox integrado no KernelSU está localizado em `/data/adb/ksu/bin/busybox`, enquanto no Magisk está em `/data/adb/magisk/busybox`. **Observe que este é um comportamento interno do KernelSU e pode mudar no futuro!** -- O KernelSU não suporta arquivos `.replace`, mas oferece suporte às variáveis ​​`REMOVE` e `REPLACE` para remover ou substituir arquivos e pastas. -- O KernelSU adiciona o estágio `boot-completed` para executar scripts após a inicialização ser concluída. -- O KernelSU adiciona o estágio `post-mount` para executar scripts após o OverlayFS ser montado. +# Diferenças com Magisk + +Embora os módulos do KernelSU e do Magisk tenham muitas semelhanças, existem inevitavelmente algumas diferenças devido aos seus mecanismos de implementação completamente diferentes. Se você deseja que seu módulo funcione tanto no Magisk quanto no KernelSU, é essencial compreender essas diferenças. + +## Semelhanças + +- Formato de arquivo do módulo: Ambos usam o formato ZIP para organizar os módulos, e o formato dos módulos é praticamente o mesmo. +- Diretório de instalação do módulo: Ambos estão localizados em `/data/adb/modules`. +- Sem sistema: Ambos suportam a modificação de `/system` de forma sem sistema por meio de módulos. +- post-fs-data.sh: O tempo de execução e a semântica são exatamente os mesmos. +- service.sh: O tempo de execução e a semântica são exatamente os mesmos. +- system.prop: Completamente o mesmo. +- sepolicy.rule: Completamente o mesmo. +- BusyBox: Os scripts são executados no BusyBox com o "Modo Autônomo" ativado em ambos os casos. + +## Diferenças + +Antes de entender as diferenças, é importante saber como identificar se o seu módulo está sendo executado no KernelSU ou no Magisk. Você pode usar a variável de ambiente `KSU` para diferenciá-lo em todos os locais onde você pode executar os scripts do módulo (`customize.sh`, `post-fs-data.sh`, `service.sh`). No KernelSU, essa variável de ambiente será definida como `true`. + +Aqui estão algumas diferenças: + +- Os módulos KernelSU não podem ser instalados no modo Recovery. +- Os módulos KernelSU não oferece suporte nativo ao Zygisk, mas você pode usar módulos Zygisk através do [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext). +- O método para substituir ou excluir arquivos nos módulos do KernelSU é completamente diferente do Magisk. O KernelSU não suporta o método `.replace`. Em vez disso, você deve criar um arquivo com o comando `mknod filename c 0 0` para excluir o arquivo correspondente. +- Os diretórios do BusyBox são diferentes. O BusyBox integrado no KernelSU está localizado em `/data/adb/ksu/bin/busybox`, enquanto no Magisk está em `/data/adb/magisk/busybox`. **Observe que este é um comportamento interno do KernelSU e pode mudar no futuro!** +- O KernelSU não suporta arquivos `.replace`, mas oferece suporte às variáveis ​​`REMOVE` e `REPLACE` para remover ou substituir arquivos e pastas. +- O KernelSU adiciona o estágio `boot-completed` para executar scripts após a inicialização ser concluída. +- O KernelSU adiciona o estágio `post-mount` para executar scripts após o OverlayFS ser montado. diff --git a/website/docs/pt_BR/guide/faq.md b/website/docs/pt_BR/guide/faq.md index 776b9bb2..dbe5f472 100644 --- a/website/docs/pt_BR/guide/faq.md +++ b/website/docs/pt_BR/guide/faq.md @@ -1,82 +1,78 @@ -# Perguntas frequentes - -## KernelSU oferece suporte ao meu dispositivo? - -O KernelSU suporta dispositivos rodando Android com bootloader desbloqueado. No entanto, o suporte oficial é apenas para kernels Linux GKI 5.10+ (na prática isso significa que seu dispositivo precisa ter Android 12 de fábrica para ser compatível). - -Você pode verificar facilmente o suporte para o seu dispositivo através do gerenciador do KernelSU, que está disponível [aqui](https://github.com/tiann/KernelSU/releases). - -Se o app mostrar `Não instalado`, significa que seu dispositivo é oficialmente suportado pelo KernelSU. - -Se o app mostrar `Sem suporte`, significa que seu dispositivo não é oficialmente suportado no momento. No entanto, você pode compilar o código-fonte do kernel e integrar o KernelSU para fazê-lo funcionar, ou usar [Dispositivos com suporte não oficial](unofficially-support-devices). - -## Para usar o KernelSU precisa desbloquear o bootloader? - -Certamente, sim. - -## KernelSU suporta módulos? - -Sim, a maioria dos módulos Magisk funcionam no KernelSU. Verifique [Guias de módulo](module.md) para mais informações. - -## KernelSU suporta Xposed? - -Sim, você pode usar LSPosed (ou outro derivado moderno do Xposed) com [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext). - -## KernelSU suporta Zygisk? - -KernelSU não tem suporte integrado ao Zygisk, mas você pode usar um módulo como [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext) para suportá-lo. - -## KernelSU é compatível com o Magisk? - -O sistema de módulos do KernelSU está em conflito com a montagem mágica do Magisk. Se houver algum módulo ativado no KernelSU, todo o Magisk deixará de funcionar. - -No entanto, se você usar apenas o `su` do KernelSU, ele funcionará bem com o Magisk. O KernelSU modifica o `kernel`, enquanto o Magisk modifica o `ramdisk`, permitindo que ambos trabalhem juntos. - -## KernelSU substituirá o Magisk? - -Acreditamos que não, e esse não é o nosso objetivo. O Magisk é bom o suficiente para solução root do espaço do usuário e terá uma longa vida. O objetivo do KernelSU é fornecer uma interface de kernel aos usuários, não substituindo o Magisk. - -## KernelSU oferece suporte a dispositivos não-GKI? - -É possível. Mas você deve baixar o código-fonte do kernel e integrar o KernelSU à árvore do código-fonte e compilar o kernel você mesmo. - -## KernelSU oferece suporte a dispositivos abaixo do Android 12? - -É o kernel do dispositivo que afeta a compatibilidade do KernelSU e não tem nada a ver com a versão do Android. A única restrição é que os dispositivos lançados com Android 12 devem ser kernel 5.10+ (dispositivos GKI). Então: - -1. Os dispositivos lançados com Android 12 devem ser compatíveis. -2. Dispositivos com kernel antigo (alguns dispositivos com Android 12 também têm o kernel antigo) são compatíveis (você mesmo deve compilar o kernel). - -## KernelSU suporta kernel antigo? - -É possível, o KernelSU é portado para o kernel 4.14 agora. Para kernels mais antigo, você precisa portar manualmente e PRs são sempre bem-vindas! - -## Como integrar o KernelSU para um kernel antigo? - -Por favor, verifique o guia [Integração para dispositivos não-GKI](how-to-integrate-for-non-gki). - -## Por que a minha versão do Android é 13 e o kernel mostra "android12-5.10"? - -A versão do Kernel não tem nada a ver com a versão do Android. Se você precisar fazer o flash do kernel, use sempre a versão do kernel, a versão do Android não é tão importante. - -## Eu sou GKI 1.0, posso usar isso? - -GKI 1.0 é completamente diferente do GKI 2.0, você deve compilar o kernel sozinho. - -## Como posso fazer `/system` RW? - -Não recomendamos que você modifique a partição do sistema diretamente. Por favor, verifique [Guias de módulo](module.md) para modificá-lo sem sistema. Se você insiste em fazer isso, verifique [magisk_overlayfs](https://github.com/HuskyDG/magic_overlayfs). - -## KernelSU pode modificar hosts? Como posso usar AdAway? - -Claro. Mas o KernelSU não tem suporte a hosts integrados, você pode instalar um módulo como [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) para fazer isso. - -## Por que existe um enorme arquivo de 1 TB? - -O arquivo `modules.img` de 1 TB é um arquivo de imagem de disco. **Não se preocupe com seu tamanho**; ele é 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 a exclusão do módulo. Na verdade, ele não ocupa 1 TB de espaço em disco (seu celular pode não ter tanto espaço). - -Se você realmente se incomodar com o tamanho desse arquivo, você pode usar o comando `resize2fs -M` para ajustá-lo ao tamanho real. Porém, o módulo pode não funcionar corretamente nesse caso, e não forneceremos suporte para isso. - -## Por que meu dispositivo mostra o tamanho de armazenamento errado? - -Certos dispositivos usam métodos não padrão para calcular o tamanho de armazenamento do dispositivo, o que pode levar a cálculos imprecisos nos 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 principalmente os apps e serviços da Samsung, é importante observar que a discrepância está principalmente no tamanho total do armazenamento, enquanto o cálculo do espaço livre permanece preciso. +# Perguntas frequentes + +## KernelSU oferece suporte ao meu dispositivo? + +Primeiro, seu dispositivo deve ser capaz de desbloquear o bootloader. Se não, então não há suporte. + +Em seguida, instale o gerenciador do KernelSU no seu dispositivo e abra-o. Se aparecer `Sem suporte` então seu dispositivo não pode ser suportado imediatamente. No entanto, você pode compilar a fonte do kernel e integrar o KernelSU para fazê-lo funcionar ou usar [Dispositivos com suporte não oficial](unofficially-support-devices). + +## Para usar o KernelSU precisa desbloquear o bootloader? + +Certamente, sim. + +## KernelSU suporta módulos? + +Sim, verifique [Guias de módulo](module.md). + +## KernelSU suporta Xposed? + +Sim, você pode usar LSPosed com [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext). + +## KernelSU suporta Zygisk? + +KernelSU não tem suporte integrado ao Zygisk, mas você pode usar [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext). + +## KernelSU é compatível com o Magisk? + +O sistema de módulos do KernelSU está em conflito com a montagem mágica do Magisk. Se houver algum módulo ativado no KernelSU, todo o Magisk deixará de funcionar. + +No entanto, se você usar apenas o `su` do KernelSU, ele funcionará bem com o Magisk. O KernelSU modifica o `kernel`, enquanto o Magisk modifica o `ramdisk`, permitindo que ambos trabalhem juntos. + +## KernelSU substituirá o Magisk? + +Acreditamos que não, e esse não é o nosso objetivo. O Magisk é bom o suficiente para solução root do espaço do usuário e terá uma longa vida. O objetivo do KernelSU é fornecer uma interface de kernel aos usuários, não substituindo o Magisk. + +## KernelSU oferece suporte a dispositivos não-GKI? + +É possível. Mas você deve baixar o código-fonte do kernel e integrar o KernelSU à árvore do código-fonte e compilar o kernel você mesmo. + +## KernelSU oferece suporte a dispositivos abaixo do Android 12? + +É o kernel do dispositivo que afeta a compatibilidade do KernelSU e não tem nada a ver com a versão do Android. A única restrição é que os dispositivos lançados com Android 12 devem ser kernel 5.10+ (dispositivos GKI). Então: + +1. Os dispositivos lançados com Android 12 devem ser compatíveis. +2. Dispositivos com kernel antigo (alguns dispositivos com Android 12 também têm o kernel antigo) são compatíveis (você mesmo deve compilar o kernel). + +## KernelSU suporta kernel antigo? + +É possível, o KernelSU é portado para o kernel 4.14 agora. Para kernels mais antigo, você precisa portar manualmente e PRs são sempre bem-vindas! + +## Como integrar o KernelSU para um kernel antigo? + +Por favor, verifique o guia [Integração para dispositivos não-GKI](how-to-integrate-for-non-gki). + +## Por que a minha versão do Android é 13 e o kernel mostra "android12-5.10"? + +A versão do Kernel não tem nada a ver com a versão do Android. Se você precisar fazer o flash do kernel, use sempre a versão do kernel, a versão do Android não é tão importante. + +## Eu sou GKI 1.0, posso usar isso? + +GKI 1.0 é completamente diferente do GKI 2.0, você deve compilar o kernel sozinho. + +## Como posso fazer `/system` RW? + +Não recomendamos que você modifique a partição do sistema diretamente. Por favor, verifique [Guias de módulo](module.md) para modificá-lo sem sistema. Se você insiste em fazer isso, verifique [magisk_overlayfs](https://github.com/HuskyDG/magic_overlayfs). + +## KernelSU pode modificar hosts? Como posso usar AdAway? + +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 1 TB? + +O arquivo `modules.img` de 1 TB é um arquivo de imagem de disco. **Não se preocupe com seu tamanho**; ele é 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 a exclusão do módulo. Na verdade, ele não ocupa 1 TB de espaço em disco (seu celular pode não ter tanto espaço). + +Se você realmente se incomodar com o tamanho desse arquivo, você pode usar o comando `resize2fs -M` para ajustá-lo ao tamanho real. Porém, o módulo pode não funcionar corretamente nesse caso, e não forneceremos suporte para isso. + +## Por que meu dispositivo mostra o tamanho de armazenamento errado? + +Certos dispositivos usam métodos não padrão para calcular o tamanho de armazenamento do dispositivo, o que pode levar a cálculos imprecisos nos 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 principalmente os apps e serviços da Samsung, é importante observar que a discrepância está principalmente no tamanho total do armazenamento, enquanto o cálculo do espaço livre permanece preciso. diff --git a/website/docs/pt_BR/guide/how-to-build.md b/website/docs/pt_BR/guide/how-to-build.md index 982c21b1..8fc71bff 100644 --- a/website/docs/pt_BR/guide/how-to-build.md +++ b/website/docs/pt_BR/guide/how-to-build.md @@ -1,71 +1,71 @@ -# Como compilar - -Primeiro, você deve ler a documentação oficial do Android para compilação do kernel: - -1. [Como criar kernels](https://source.android.com/docs/setup/build/building-kernels) -2. [Builds de versão de imagem genérica do kernel (GKI)](https://source.android.com/docs/core/architecture/kernel/gki-release-builds) - -::: warning AVISO -Esta página é para dispositivos GKI, se você usa um kernel antigo, consulte [Integração para dispositivos não-GKI](how-to-integrate-for-non-gki). -::: - -## Compilar o kernel - -### Sincronize o código-fonte do kernel - -```sh -repo init -u https://android.googlesource.com/kernel/manifest -mv .repo/manifests -repo init -m manifest.xml -repo sync -``` - -O arquivo `` é um manifesto que pode determinar exclusivamente uma compilação, permitindo que você a torne reprodutível. Para isso, você deve baixar o arquivo de manifesto em [Builds de versão de imagem genérica do kernel (GKI)](https://source.android.com/docs/core/architecture/kernel/gki-release-builds). - -### Construir - -Por favor, verifique [Como criar kernels](https://source.android.com/docs/setup/build/building-kernels) primeiro. - -Por exemplo, para compilar uma imagem de kernel `aarch64`: - -```sh -LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh -``` - -Não se esqueça de adicionar o sinalizador `LTO=thin`, caso contrário a compilação poderá falhar se a memória do seu computador for inferior a 24 GB. - -A partir do Android 13, o kernel é compilado pelo `bazel`: - -```sh -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 pelo GKI: - -```sh -rm common/android/abi_gki_protected_exports_* -``` -::: - -## Compilar o kernel com KernelSU - -Se você conseguir compilar o kernel com sucesso, adicionar suporte ao KernelSU será relativamente simples. Na raiz do diretório de origem do kernel, execute qualquer uma das opções listadas abaixo: - -::: code-group - -```sh[Tag mais recente (estável)] -curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash - -``` - -```sh[Branch main (dev)] -curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s main -``` - -```sh[Selecionar tag (como v0.5.2)] -curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.5.2 -``` - -::: - -Então, reconstrua o kernel e você obterá uma imagem do kernel com o KernelSU! +# Como compilar + +Primeiro, você deve ler a documentação oficial do Android para compilação do kernel: + +1. [Como criar kernels](https://source.android.com/docs/setup/build/building-kernels) +2. [Builds de versão de imagem genérica do kernel (GKI)](https://source.android.com/docs/core/architecture/kernel/gki-release-builds) + +::: warning AVISO +Esta página é para dispositivos GKI, se você usa um kernel antigo, consulte [Integração para dispositivos não-GKI](how-to-integrate-for-non-gki). +::: + +## Compilar o kernel + +### Sincronize o código-fonte do kernel + +```sh +repo init -u https://android.googlesource.com/kernel/manifest +mv .repo/manifests +repo init -m manifest.xml +repo sync +``` + +O arquivo `` é um manifesto que pode determinar exclusivamente uma compilação, permitindo que você a torne reprodutível. Para isso, você deve baixar o arquivo de manifesto em [Builds de versão de imagem genérica do kernel (GKI)](https://source.android.com/docs/core/architecture/kernel/gki-release-builds). + +### Construir + +Por favor, verifique [Como criar kernels](https://source.android.com/docs/setup/build/building-kernels) primeiro. + +Por exemplo, para compilar uma imagem de kernel `aarch64`: + +```sh +LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh +``` + +Não se esqueça de adicionar o sinalizador `LTO=thin`, caso contrário a compilação poderá falhar se a memória do seu computador for inferior a 24 GB. + +A partir do Android 13, o kernel é compilado pelo `bazel`: + +```sh +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 pelo GKI: + +```sh +rm common/android/abi_gki_protected_exports_* +``` +::: + +## Compilar o kernel com KernelSU + +Se você conseguir compilar o kernel com sucesso, adicionar suporte ao KernelSU será relativamente simples. Na raiz do diretório de origem do kernel, execute qualquer uma das opções listadas abaixo: + +::: code-group + +```sh[Tag mais recente (estável)] +curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash - +``` + +```sh[Branch principal (dev)] +curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s main +``` + +```sh[Selecionar tag (como v0.5.2)] +curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.5.2 +``` + +::: + +Então, reconstrua o kernel e você obterá uma imagem do kernel com o KernelSU! diff --git a/website/docs/pt_BR/guide/how-to-integrate-for-non-gki.md b/website/docs/pt_BR/guide/how-to-integrate-for-non-gki.md index aee8e36f..6cda5213 100644 --- a/website/docs/pt_BR/guide/how-to-integrate-for-non-gki.md +++ b/website/docs/pt_BR/guide/how-to-integrate-for-non-gki.md @@ -1,377 +1,377 @@ -# Integração para dispositivos não-GKI - -O KernelSU pode ser integrado a kernels não-GKI e foi portado para 4.14 e versões anteriores. - -Devido à fragmentação dos kernels não-GKI, não temos um método universal para construí-lo, portanto, não podemos fornecer o boot.img não-GKI. No entanto, você pode compilar o kernel com o KernelSU integrado por conta própria. - -Primeiro, você deve ser capaz de compilar um kernel inicializável a partir do código-fonte do kernel. Se o kernel não for de código aberto, será difícil executar o KernelSU para o seu dispositivo. - -Se você puder compilar um kernel inicializável, existem duas maneiras de integrar o KernelSU ao código-fonte do kernel: - -1. Automaticamente com `kprobe` -2. Manualmente - -## Integrar com kprobe - -O KernelSU usa kprobe para fazer ganchos do kernel, se o kprobe funcionar bem em seu kernel, é recomendado usar desta forma. - -Primeiro, adicione o KernelSU à árvore de origem do kernel: - -```sh -curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.9.5 -``` - -::: info INFORMAÇÕES -[KernelSU 1.0 e versões posteriores não suportam mais kernels não-GKI](https://github.com/tiann/KernelSU/issues/1705). A última versão suportada é a `v0.9.5`, portanto, certifique-se de usar a versão correta. -::: - -Então, você deve verificar se o kprobe está ativado na configuração do seu kernel. Caso não esteja, adicione estas configurações a ele: - -```txt -CONFIG_KPROBES=y -CONFIG_HAVE_KPROBES=y -CONFIG_KPROBE_EVENTS=y -``` - -Agora, ao recompilar seu kernel, o KernelSU deve funcionar corretamente. - -Se você descobrir que o KPROBES ainda não está ativado, pode tentar ativar `CONFIG_MODULES`. Se isso não resolver, use `make menuconfig` para procurar outras dependências do KPROBES. - -Porém, se você entrar em um bootloop após integrar o KernelSU, isso pode indicar que o **kprobe está quebrado no seu kernel**, o que significa que você precisará corrigir o bug do kprobe ou usar outra maneira. - -::: tip COMO VERIFICAR SE O KPROBE ESTÁ QUEBRADO? -Comente `ksu_enable_sucompat()` e `ksu_enable_ksud()` em `KernelSU/kernel/ksu.c`, se o dispositivo inicializar normalmente, então o kprobe pode estar quebrado. -::: - -::: info COMO FAZER COM QUE O RECURSO DE DESMONTAR MÓDULOS FUNCIONE NO PRÉ-GKI? -Se o seu kernel for inferior a 5.9, você deve portar `path_umount` para `fs/namespace.c`. Isso é necessário para que o recurso "Desmontar módulos" funcione corretamente. Caso você não porte `path_umount`, o recurso "Desmontar módulos" não funcionará. Você pode obter mais informações sobre como conseguir isso no final desta página. -::: - -## Modifique manualmente a fonte do kernel - -Se o kprobe não funcionar no seu kernel (isso pode ser causado por um bug no upstream ou do kernel abaixo de 4.8), então você pode tentar o seguinte: - -Primeiro, adicione o KernelSU à árvore de origem do kernel: - -```sh -curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.9.5 -``` - -Tenha em mente que, em alguns dispositivos, seu defconfig pode estar localizado em `arch/arm64/configs` ou em outros casos pode estar em `arch/arm64/configs/vendor/your_defconfig`. Independentemente do defconfig que você estiver usando, certifique-se de ativar `CONFIG_KSU` com `y` para ativa-lo ou `n` para desativa-lo. Por exemplo, se optar por ativá-lo, seu defconfig deverá conter a seguinte linha: - -```txt -# KernelSU -CONFIG_KSU=y -``` - -Em seguida, adicione chamadas do KernelSU à fonte do kernel. Abaixo estão alguns patches para referência: - -::: 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() precisa usar o uid/gid real, não o uid/gid efetivo. - * Fazemos isso limpando temporariamente todos os recursos relacionados ao FS e -@@ -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 - Obtenha atributos básicos e extras por filename - * @dfd: Um descritor de arquivo que representa o diretório base para um filename relativo -@@ -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; -``` - -::: - -Você deve encontrar as quatro funções no código-fonte do kernel: - -1. `do_faccessat`, geralmente em `fs/open.c` -2. `do_execveat_common`, geralmente em `fs/exec.c` -3. `vfs_read`, geralmente em `fs/read_write.c` -4. `vfs_statx`, geralmente em `fs/stat.c` - -Se o seu kernel não tiver a função `vfs_statx`, use `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; -``` - -Para kernels anteriores ao 4.17, se você não conseguir encontrar `do_faccessat`, basta ir até a definição do syscall `faccessat` e fazer a chamada lá: - -```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() precisa usar o uid/gid real, não o uid/gid efetivo. - * Fazemos isso limpando temporariamente todos os recursos relacionados ao FS e -@@ -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; -``` - -### Modo de Segurança - -Para ativar o Modo de Segurança integrado do KernelSU, você deve modificar a função `input_handle_event` em `drivers/input/input.c`: - -::: tip DICA -É altamente recomendável ativar este recurso, é muito útil para evitar bootloops! -::: - -```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); -``` - -::: info ENTRANDO NO MODO DE SEGURANÇA ACIDENTALMENTE? -Se você estiver usando a integração manual e não desativar `CONFIG_KPROBES`, o usuário poderá acionar o Modo de Segurança pressionando o botão de diminuir volume após a inicialização! Portanto, se estiver usando a integração manual, é necessário desativar `CONFIG_KPROBES`! -::: - -### Falha ao executar `pm` no terminal? - -Você deve modificar `fs/devpts/inode.c`. Referência: - -```diff -diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c -index 32f6f1c68..d69d8eca2 100644 ---- a/fs/devpts/inode.c -+++ b/fs/devpts/inode.c -@@ -602,6 +602,8 @@ struct dentry *devpts_pty_new(struct pts_fs_info *fsi, int index, void *priv) - return dentry; - } - -+#ifdef CONFIG_KSU -+extern int ksu_handle_devpts(struct inode*); -+#endif -+ - /** - * devpts_get_priv -- get private data for a slave - * @pts_inode: inode of the slave -@@ -610,6 +612,7 @@ struct dentry *devpts_pty_new(struct pts_fs_info *fsi, int index, void *priv) - */ - void *devpts_get_priv(struct dentry *dentry) - { -+ #ifdef CONFIG_KSU -+ ksu_handle_devpts(dentry->d_inode); -+ #ifdef CONFIG_KSU - if (dentry->d_sb->s_magic != DEVPTS_SUPER_MAGIC) - return NULL; - return dentry->d_fsdata; -``` - -### Como portar path_umount - -Você pode fazer com que o recurso "Desmontar módulos" funcione em kernels pré-GKI portando manualmente `path_umount` da versão 5.9. Você pode usar este patch como referência: - -```diff ---- a/fs/namespace.c -+++ b/fs/namespace.c -@@ -1739,6 +1739,39 @@ static inline bool may_mandlock(void) - } - #endif - -+static int can_umount(const struct path *path, int flags) -+{ -+ struct mount *mnt = real_mount(path->mnt); -+ -+ if (flags & ~(MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW)) -+ return -EINVAL; -+ if (!may_mount()) -+ return -EPERM; -+ if (path->dentry != path->mnt->mnt_root) -+ return -EINVAL; -+ if (!check_mnt(mnt)) -+ return -EINVAL; -+ if (mnt->mnt.mnt_flags & MNT_LOCKED) /* Check optimistically */ -+ return -EINVAL; -+ if (flags & MNT_FORCE && !capable(CAP_SYS_ADMIN)) -+ return -EPERM; -+ return 0; -+} -+ -+int path_umount(struct path *path, int flags) -+{ -+ struct mount *mnt = real_mount(path->mnt); -+ int ret; -+ -+ ret = can_umount(path, flags); -+ if (!ret) -+ ret = do_umount(mnt, flags); -+ -+ /* não devemos chamar path_put() pois isso limparia mnt_expiry_mark */ -+ dput(path->dentry); -+ mntput_no_expire(mnt); -+ return ret; -+} - /* - * Agora o umount pode lidar com pontos de montagem e também com dispositivos bloqueados. - * Isto é importante para filesystems que usam dispositivos bloqueados sem nome. -``` - -Finalmente, compile seu kernel novamente e o KernelSU deverá funcionar corretamente. +# Integração para dispositivos não-GKI + +O KernelSU pode ser integrado a kernels não-GKI e foi portado para 4.14 e versões anteriores. + +Devido à fragmentação dos kernels não-GKI, não temos um método universal para construí-lo, portanto, não podemos fornecer o boot.img não-GKI. No entanto, você pode compilar o kernel com o KernelSU integrado por conta própria. + +Primeiro, você deve ser capaz de compilar um kernel inicializável a partir do código-fonte do kernel. Se o kernel não for de código aberto, será difícil executar o KernelSU para o seu dispositivo. + +Se você puder compilar um kernel inicializável, existem duas maneiras de integrar o KernelSU ao código-fonte do kernel: + +1. Automaticamente com `kprobe` +2. Manualmente + +## Integrar com kprobe + +O KernelSU usa kprobe para fazer ganchos do kernel, se o kprobe funcionar bem em seu kernel, é recomendado usar desta forma. + +Primeiro, adicione o KernelSU à árvore de origem do kernel: + +```sh +curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.9.5 +``` + +::: info INFORMAÇÕES +[KernelSU 1.0 e versões posteriores não suportam mais kernels não-GKI](https://github.com/tiann/KernelSU/issues/1705). A última versão suportada é a `v0.9.5`, portanto, certifique-se de usar a versão correta. +::: + +Então, você deve verificar se o kprobe está ativado na configuração do seu kernel. Caso não esteja, adicione estas configurações a ele: + +```txt +CONFIG_KPROBES=y +CONFIG_HAVE_KPROBES=y +CONFIG_KPROBE_EVENTS=y +``` + +Agora, ao recompilar seu kernel, o KernelSU deve funcionar corretamente. + +Se você descobrir que o KPROBES ainda não está ativado, pode tentar ativar `CONFIG_MODULES`. Se isso não resolver, use `make menuconfig` para procurar outras dependências do KPROBES. + +Porém, se você entrar em um bootloop após integrar o KernelSU, isso pode indicar que o **kprobe está quebrado no seu kernel**, o que significa que você precisará corrigir o bug do kprobe ou usar outra maneira. + +::: tip COMO VERIFICAR SE O KPROBE ESTÁ QUEBRADO? +Comente `ksu_enable_sucompat()` e `ksu_enable_ksud()` em `KernelSU/kernel/ksu.c`, se o dispositivo inicializar normalmente, então o kprobe pode estar quebrado. +::: + +::: info COMO FAZER COM QUE O RECURSO DE DESMONTAR MÓDULOS FUNCIONE NO PRÉ-GKI? +Se o seu kernel for inferior a 5.9, você deve portar `path_umount` para `fs/namespace.c`. Isso é necessário para que o recurso "Desmontar módulos" funcione corretamente. Caso você não porte `path_umount`, o recurso "Desmontar módulos" não funcionará. Você pode obter mais informações sobre como conseguir isso no final desta página. +::: + +## Modifique manualmente a fonte do kernel + +Se o kprobe não funcionar no seu kernel (isso pode ser causado por um bug no upstream ou do kernel abaixo de 4.8), então você pode tentar o seguinte: + +Primeiro, adicione o KernelSU à árvore de origem do kernel: + +```sh +curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.9.5 +``` + +Tenha em mente que, em alguns dispositivos, seu defconfig pode estar localizado em `arch/arm64/configs` ou em outros casos pode estar em `arch/arm64/configs/vendor/your_defconfig`. Independentemente do defconfig que você estiver usando, certifique-se de ativar `CONFIG_KSU` com `y` para ativa-lo ou `n` para desativa-lo. Por exemplo, se optar por ativá-lo, seu defconfig deverá conter a seguinte linha: + +```txt +# KernelSU +CONFIG_KSU=y +``` + +Em seguida, adicione chamadas do KernelSU à fonte do kernel. Abaixo estão alguns patches para referência: + +::: 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() precisa usar o uid/gid real, não o uid/gid efetivo. + * Fazemos isso limpando temporariamente todos os recursos relacionados ao FS e +@@ -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 - Obtenha atributos básicos e extras por filename + * @dfd: Um descritor de arquivo que representa o diretório base para um filename relativo +@@ -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; +``` + +::: + +Você deve encontrar as quatro funções no código-fonte do kernel: + +1. `do_faccessat`, geralmente em `fs/open.c` +2. `do_execveat_common`, geralmente em `fs/exec.c` +3. `vfs_read`, geralmente em `fs/read_write.c` +4. `vfs_statx`, geralmente em `fs/stat.c` + +Se o seu kernel não tiver a função `vfs_statx`, use `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; +``` + +Para kernels anteriores ao 4.17, se você não conseguir encontrar `do_faccessat`, basta ir até a definição do syscall `faccessat` e fazer a chamada lá: + +```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() precisa usar o uid/gid real, não o uid/gid efetivo. + * Fazemos isso limpando temporariamente todos os recursos relacionados ao FS e +@@ -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; +``` + +### Modo de Segurança + +Para ativar o Modo de Segurança integrado do KernelSU, você deve modificar a função `input_handle_event` em `drivers/input/input.c`: + +::: tip DICA +É altamente recomendável ativar este recurso, é muito útil para evitar bootloops! +::: + +```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); +``` + +::: info ENTRANDO NO MODO DE SEGURANÇA ACIDENTALMENTE? +Se você estiver usando a integração manual e não desativar `CONFIG_KPROBES`, o usuário poderá acionar o Modo de Segurança pressionando o botão de diminuir volume após a inicialização! Portanto, se estiver usando a integração manual, é necessário desativar `CONFIG_KPROBES`! +::: + +### Falha ao executar `pm` no terminal? + +Você deve modificar `fs/devpts/inode.c`. Referência: + +```diff +diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c +index 32f6f1c68..d69d8eca2 100644 +--- a/fs/devpts/inode.c ++++ b/fs/devpts/inode.c +@@ -602,6 +602,8 @@ struct dentry *devpts_pty_new(struct pts_fs_info *fsi, int index, void *priv) + return dentry; + } + ++#ifdef CONFIG_KSU ++extern int ksu_handle_devpts(struct inode*); ++#endif ++ + /** + * devpts_get_priv -- get private data for a slave + * @pts_inode: inode of the slave +@@ -610,6 +612,7 @@ struct dentry *devpts_pty_new(struct pts_fs_info *fsi, int index, void *priv) + */ + void *devpts_get_priv(struct dentry *dentry) + { ++ #ifdef CONFIG_KSU ++ ksu_handle_devpts(dentry->d_inode); ++ #ifdef CONFIG_KSU + if (dentry->d_sb->s_magic != DEVPTS_SUPER_MAGIC) + return NULL; + return dentry->d_fsdata; +``` + +### Como portar path_umount + +Você pode fazer com que o recurso "Desmontar módulos" funcione em kernels pré-GKI portando manualmente `path_umount` da versão 5.9. Você pode usar este patch como referência: + +```diff +--- a/fs/namespace.c ++++ b/fs/namespace.c +@@ -1739,6 +1739,39 @@ static inline bool may_mandlock(void) + } + #endif + ++static int can_umount(const struct path *path, int flags) ++{ ++ struct mount *mnt = real_mount(path->mnt); ++ ++ if (flags & ~(MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW)) ++ return -EINVAL; ++ if (!may_mount()) ++ return -EPERM; ++ if (path->dentry != path->mnt->mnt_root) ++ return -EINVAL; ++ if (!check_mnt(mnt)) ++ return -EINVAL; ++ if (mnt->mnt.mnt_flags & MNT_LOCKED) /* Check optimistically */ ++ return -EINVAL; ++ if (flags & MNT_FORCE && !capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ return 0; ++} ++ ++int path_umount(struct path *path, int flags) ++{ ++ struct mount *mnt = real_mount(path->mnt); ++ int ret; ++ ++ ret = can_umount(path, flags); ++ if (!ret) ++ ret = do_umount(mnt, flags); ++ ++ /* não devemos chamar path_put() pois isso limparia mnt_expiry_mark */ ++ dput(path->dentry); ++ mntput_no_expire(mnt); ++ return ret; ++} + /* + * Agora o umount pode lidar com pontos de montagem e também com dispositivos bloqueados. + * Isto é importante para filesystems que usam dispositivos bloqueados sem nome. +``` + +Finalmente, compile seu kernel novamente e o KernelSU deverá funcionar corretamente. diff --git a/website/docs/pt_BR/guide/installation.md b/website/docs/pt_BR/guide/installation.md index eda5d6e5..367209e2 100644 --- a/website/docs/pt_BR/guide/installation.md +++ b/website/docs/pt_BR/guide/installation.md @@ -1,280 +1,280 @@ -# Instalação - -## Verifique se o seu dispositivo é compatível - -Baixe o gerenciador do KernelSU em [GitHub Releases](https://github.com/tiann/KernelSU/releases) e instale-o no seu dispositivo: - -- Se o app mostrar `Sem suporte`, significa que **você precisará compilar o kernel por conta própria**. O KernelSU não fornecerá e nunca fornecerá um arquivo boot.img para você instalar. -- Se o app mostrar `Não instalado`, então seu dispositivo é oficialmente suportado pelo KernelSU. - -::: info INFORMAÇÕES -Para dispositivos que mostram `Sem suporte`, você pode conferir a lista de [Dispositivos com suporte não oficial](unofficially-support-devices.md). Você mesmo pode compilar o kernel. -::: - -## Backup padrão do boot.img - -Antes de fazer o flash, é essencial que você faça o backup do seu boot.img padrão. Se encontrar algum bootloop, você sempre pode restaurar o sistema voltando ao boot padrão de fábrica usando o fastboot. - -::: warning AVISO -O flash pode causar perda de dados. Certifique-se de executar esta etapa bem antes de prosseguir para a próxima! Se necessário, também é recomendável fazer backup de todos os dados do seu dispositivo. -::: - -## Conhecimento necessário - -### ADB e fastboot - -Por padrão, você usará as ferramentas ADB e fastboot neste tutorial, portanto, se você não as conhece, recomendamos pesquisar para aprender sobre elas primeiro. - -### KMI - -Kernel Module Interface (KMI), versões de kernel com o mesmo KMI são **compatíveis**, isso é o que "geral" significa no GKI. Por outro lado, se o KMI for diferente, então esses kernels não são compatíveis entre si, e atualizar uma imagem do kernel com um KMI diferente do seu dispositivo pode causar um bootloop. - -Especificamente, para dispositivos GKI, o formato da versão do kernel deve ser a seguinte: - -```txt -KernelRelease := -Version.PatchLevel.SubLevel-AndroidRelease-KmiGeneration-suffix -w .x .y -zzz -k -alguma coisa -``` - -`w.x-zzz-k` é a versão KMI. Por exemplo, se a versão do kernel de um dispositivo for `5.10.101-android12-9-g30979850fc20`, então seu KMI será `5.10-android12-9`. Teoricamente, ele pode inicializar normalmente com outros kernels KMI. - -::: tip DICA -Observe que o SubLevel na versão do kernel não faz parte do KMI! Isso significa que `5.10.101-android12-9-g30979850fc20` tem o mesmo KMI que `5.10.137-android12-9-g30979850fc20`! -::: - -### Nível do patch de segurança {#security-patch-level} - -Dispositivos Android mais recentes podem ter mecanismos anti-rollback que impedem o flash de um boot.img com um nível de patch de segurança antigo. Por exemplo, se o kernel do seu dispositivo for `5.10.101-android12-9-g30979850fc20`, o patch de segurança será `2023-11`, mesmo se você atualizar o kernel correspondente ao KMI do kernel, se o nível do patch de segurança for anterior a `2023-11` (como `2023-06`), isso pode causar um bootloop. - -Portanto, kernels com os níveis de patch de segurança mais recentes são preferidos para manter a compatibilidade com o KMI. - -### Versão do kernel vs Versão do Android - -Por favor, observe: **A versão do kernel e a versão do Android não são necessariamente iguais!** - -Se você descobrir que a versão do seu kernel é `android12-5.10.101`, mas a versão do seu sistema Android é Android 13 ou outra, não se surpreenda, pois o número da versão do sistema Android não é necessariamente igual ao número da versão do kernel Linux. O número da versão do kernel Linux geralmente é correspondente à versão do sistema Android que acompanha o **dispositivo quando ele é enviado**. Se o sistema Android for atualizado posteriormente, a versão do kernel geralmente não será alterada. Então, antes de flashar qualquer coisa, **consulte sempre a versão do kernel!** - -## Introdução - -Desde a versão [0.9.0](https://github.com/tiann/KernelSU/releases/tag/v0.9.0), o KernelSU suporta dois modos de execução em dispositivos GKI: - -1. `GKI`: Substitue o kernel original do dispositivo pelo **Generic Kernel Image** (GKI) fornecido pelo KernelSU. -2. `LKM`: Carregue o **Loadable Kernel Module** (LKM) no kernel do dispositivo sem substituir o kernel original. - -Esses dois modos são adequados para diferentes cenários, e você pode escolher o mais adequado conforme suas necessidades. - -### Modo GKI {#gki-mode} - -No modo GKI, o kernel original do dispositivo será substituído pela imagem genérica do kernel fornecida pelo KernelSU. As vantagens do modo GKI são: - -1. Forte universalidade, adequada para a maioria dos dispositivos. Por exemplo, a Samsung ativou dispositivos KNOX, e o modo LKM não pode funcionar. Existem também alguns dispositivos modificados de nicho que só podem usar o modo GKI. -2. Pode ser usado sem depender de firmware oficial, e não há necessidade de esperar por atualizações oficiais de firmware, desde que o KMI seja consistente, ele pode ser usado. - -### Modo LKM {#lkm-mode} - -No modo LKM, o kernel original do dispositivo não será substituído, mas o módulo do kernel carregável será carregado no kernel do dispositivo. As vantagens do modo LKM são: - -1. Não substituirá o kernel original do dispositivo. Se você tiver os requisitos especiais para o kernel original do dispositivo ou quiser usar o KernelSU enquanto usa um kernel de terceiros, poderá usar o modo LKM. -2. É mais conveniente atualizar o OTA. Ao atualizar o KernelSU, você pode instalá-lo diretamente no gerenciador sem flashar manualmente. Após o sistema OTA, você pode instalá-lo diretamente no segundo slot sem flashar manualmente. -3. Adequado para alguns cenários especiais. Por exemplo, o LKM também pode ser carregado com privilégios root temporários. Como não é necessário substituir a partição boot, ele não acionará o AVB e não causará o bloqueio do dispositivo. -4. O LKM pode ser desinstalado temporariamente. Se você deseja desativar temporariamente o acesso root, você pode desinstalar o LKM. Este processo não requer o flash de partições, nem mesmo a reinicialização do dispositivo. Se quiser ativar o root novamente, basta reiniciar o dispositivo. - -::: tip COEXISTÊNCIA DE DOIS MODOS -Após abrir o gerenciador, você pode ver o modo atual do dispositivo na página inicial. Observe que a prioridade do modo GKI é maior que a do LKM. Por exemplo, se você usar o kernel GKI para substituir o kernel original e usar LKM para corrigir o kernel GKI, o LKM será ignorado e o dispositivo sempre será executado no modo GKI. -::: - -### Qual escolher? {#which-one} - -Se o seu aparelho for um celular, recomendamos que você priorize o modo LKM. Se o seu dispositivo for um emulador, WSA ou Waydroid, recomendamos que você priorize o modo GKI. - -## Instalação do LKM - -### Obtenha o firmware oficial - -Para usar o modo LKM, você precisa obter o firmware oficial e corrigi-lo com base no firmware oficial. Se você usar um kernel de terceiros, poderá usar o `boot.img` do kernel de terceiros como firmware oficial. - -Existem muitas maneiras de obter o firmware oficial. Se o seu dispositivo suportar `fastboot boot`, então recomendamos **o método mais simples e indicado**, que consiste em usar `fastboot boot` para inicializar temporariamente o kernel GKI fornecido pelo KernelSU, depois instalar o gerenciador e, finalmente, instalá-lo diretamente pelo gerenciador. Este método não exige o download manual do firmware oficial nem a extração manual do boot. - -Se o seu dispositivo não suportar `fastboot boot`, pode ser necessário baixar manualmente o pacote de firmware oficial e extrair o boot dele. - -Ao contrário do modo GKI, o modo LKM modifica o `ramdisk`. Portanto, em dispositivos com Android 13, ele precisa corrigir a partição `init_boot` em vez da partição `boot`, enquanto o modo GKI sempre opera sobre a partição `boot`. - -### Use o gerenciador - -Abra o gerenciador, clique no ícone de instalação no canto superior direito e diversas opções aparecerão: - -1. Selecione um arquivo. Se o seu dispositivo não tiver privilégios root, você pode escolher esta opção e, em seguida, selecionar o seu firmware oficial. O gerenciador corrigirá automaticamente o firmware. Após isso, basta fazer o flash deste arquivo corrigido para obter privilégios root permanentemente. -2. Instalação direta. Se o seu dispositivo já estiver rooteado, você pode escolher esta opção. O gerenciador obterá automaticamente as informações do seu dispositivo, corrigirá o firmware oficial e realizará o flash automaticamente. Você também pode usar o comando `fastboot boot` junto com o kernel GKI do KernelSU para obter root temporário e instalar o gerenciador, e então usar esta opção. Esta também é a principal forma de atualizar o KernelSU. -3. Instalar no slot inativo. Se o seu dispositivo suportar partição A/B, você pode escolher esta opção. O gerenciador corrigirá automaticamente o firmware oficial e o instalará em outra partição. Esse método é adequado para dispositivos após o OTA, você pode instalá-lo diretamente em outra partição após o OTA e, em seguida, reiniciar o dispositivo. - -### Use a linha de comando - -Se não quiser usar o gerenciador, você também pode usar a linha de comando para instalar o LKM. A ferramenta `ksud` fornecida pelo KernelSU pode ajudá-lo a corrigir rapidamente o firmware oficial e depois fazer o flash. - -Esta ferramenta oferece suporte ao macOS, Linux e Windows. Você pode baixar a versão correspondente em [GitHub Release](https://github.com/tiann/KernelSU/releases). - -Uso: `ksud boot-patch` você pode verificar a ajuda da linha de comando para opções específicas. - -```sh -oriole:/ # ksud boot-patch -h -Patch boot ou imagens init_boot para aplicar o KernelSU - -Uso: ksud boot-patch [OPTIONS] - -Opções: - -b, --boot Caminho da imagem boot. Se não especificado, tentará encontrar a imagem boot automaticamente - -k, --kernel Caminho da imagem do kernel a ser substituída - -m, --module Caminho do módulo LKM a ser substituído. Se não especificado, usará o módulo integrado - -i, --init init a ser substituído - -u, --ota Usará outro slot se a imagem boot não for especificada - -f, --flash Flash para a partição boot após o patch - -o, --out Caminho de saída. Se não especificado, usará o diretório atual - --magiskboot Caminho do magiskboot. Se não especificado, usará a versão integrada - --kmi Versão do KMI. Se especificada, usará o KMI indicado - -h, --help Imprimir ajuda -``` - -Algumas opções que precisam ser explicadas: - -1. A opção `--magiskboot` pode especificar o caminho do magiskboot. Se não for especificado, o ksud irá procurá-lo nas variáveis ​​de ambiente. Se você não souber como obter o magiskboot, você pode verificar [aqui](#patch-boot-image). -2. A opção `--kmi` pode especificar a versão do `KMI`. Se o nome do kernel do seu dispositivo não seguir a especificação KMI, você poderá especificá-lo através desta opção. - -O uso mais comum é: - -```sh -ksud boot-patch -b --kmi android13-5.10 -``` - -## Instalação no modo GKI - -Existem vários métodos de instalação para o modo GKI, cada um adequado para um cenário diferente, portanto escolha conforme necessário. - -1. Instalar com fastboot usando o boot.img fornecido pelo KernelSU. -2. Instalar com um app kernel flash, como o [Kernel Flasher](https://github.com/capntrips/KernelFlasher/releases). -3. Corrigir manualmente o boot.img e instalá-lo. -4. Instalar com Recovery personalizado (por exemplo, TWRP). - -## Instalar com o boot.img fornecido pelo KernelSU - -Se o `boot.img` do seu dispositivo usa um formato de compactação comumente usado, você pode usar as imagens GKI fornecidas pelo KernelSU para atualizá-lo diretamente. Não requer TWRP ou autocorreção da imagem. - -### Encontre o boot.img adequado - -O KernelSU fornece um boot.img genérico para dispositivos GKI, e você deve fazer o flash do boot.img na 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 do [KMI](#kmi) e [Nível do patch de segurança](#security-patch-level) neste documento. - -Normalmente, existem três arquivos de inicialização em formatos diferentes para o mesmo KMI e nível de patch de segurança. Eles são idênticos, exceto pelo formato de compactação do kernel. Por favor, verifique o formato de compactação do kernel de seu boot.img original. Você deve usar o formato correto, como `lz4` ou `gz`. Se você usar um formato de compactação incorreto, poderá encontrar bootloop após o flash do boot.img. - -::: info FORMATO DE COMPACTAÇÃO DO BOOT.IMG -1. Você pode usar o magiskboot para obter o formato de compactação do seu boot.img original. Alternativamente, você também pode perguntar a membros ou desenvolvedores da comunidade que possuam o mesmo modelo de dispositivo. Além disso, o formato de compactação do kernel geralmente não muda, portanto, se você inicializar com êxito com um determinado formato de compactação, poderá tentar esse formato mais tarde. -2. Dispositivos Xiaomi geralmente usam `gz` ou `uncompressed`. -3. Para dispositivos Pixel, siga as instruções abaixo: -::: - -### Flash o boot.img para o dispositivo - -Use o `adb` para conectar seu dispositivo, execute `adb reboot bootloader` para entrar no modo fastboot e use este comando para flashar o KernelSU: - -```sh -fastboot flash boot boot.img -``` - -::: info INFORMAÇÕES -Se o seu dispositivo suportar `fastboot boot`, você pode usar primeiro `fastboot boot boot.img` para tentar usar o boot.img para inicializar o sistema primeiro. Se algo inesperado acontecer, reinicie-o novamente para inicializar. -::: - -### Reiniciar - -Após a conclusão do flash, você deve reiniciar o dispositivo: - -```sh -fastboot reboot -``` - -## Instalar com Kernel Flasher - -Etapa: - -1. Baixe o ZIP AnyKernel3. Se você não sabe qual arquivo baixar, leia atentamente a descrição do [KMI](#kmi) e [Nível do patch de segurança](#security-patch-level) neste documento. -2. Abra o app Kernel Flasher, conceda as permissões de root necessárias e use o ZIP AnyKernel3 fornecido para fazer o flash. - -Dessa forma, é necessário que o app Kernel Flasher tenha privilégios root. Você pode usar os seguintes métodos para conseguir isso: - -1. Seu dispositivo está rooteado. Por exemplo, você instalou o KernelSU e deseja atualizar para a versão mais recente ou fez o root por meio de outros métodos (como Magisk). -2. Se o seu dispositivo não estiver rooteado, mas suportar o método de inicialização temporária como `fastboot boot boot.img`, você pode usar a imagem GKI fornecida pelo KernelSU para inicializar temporariamente o seu dispositivo, obter privilégios root temporário e, em seguida, usar o Kernel Flasher para obter privilégios root permanente. - -Aqui estão alguns apps que podem ser usados para realizar o flash do kernel: - -1. [Kernel Flasher](https://github.com/capntrips/KernelFlasher/releases) -2. [Franco Kernel Manager](https://play.google.com/store/apps/details?id=com.franco.kernel) -3. [Ex Kernel Manager](https://play.google.com/store/apps/details?id=flar2.exkernelmanager) - -Observação: Este método é mais conveniente ao atualizar o KernelSU e pode ser feito sem um computador (faça um backup primeiro). - -## Corrigir boot.img manualmente {#patch-boot-image} - -Para alguns dispositivos, o formato boot.img não é tão comum como `lz4`, `gz` e `uncompressed`. Um exemplo típico é o Pixel, cujo boot.img é compactado no formato `lz4_legacy`, enquanto o ramdisk pode estar em `gz` ou também comprimido em `lz4_legacy`. Atualmente, se você flashar diretamente o boot.img fornecido pelo KernelSU, o dispositivo pode não conseguir inicializar. Nesse caso, é necessário corrigir manualmente o boot.img para conseguir isso. - -É sempre recomendado usar `magiskboot` para corrigir imagens, existem duas maneiras: - -1. [magiskboot](https://github.com/topjohnwu/Magisk/releases) -2. [magiskboot_build](https://github.com/ookiineko/magiskboot_build/releases/tag/last-ci) - -A versão oficial do `magiskboot` só pode ser executada em dispositivos Android, se você quiser rodar no PC, você pode tentar a segunda opção. - -::: tip DICA -Android-Image-Kitchen não é recomendado por enquanto, porque ele não lida corretamente com os metadados de inicialização (como o nível do patch de segurança). Portanto, pode não funcionar em alguns dispositivos. -::: - -### Preparação - -1. Obtenha o boot.img padrão do dispositivo. Você pode obtê-lo com os fabricantes do seu dispositivo. Talvez você precise do [payload-dumper-go](https://github.com/ssut/payload-dumper-go). -2. Baixe o arquivo ZIP AnyKernel3 fornecido pelo KernelSU que corresponde à versão KMI do seu dispositivo. Você pode consultar [Instalar com Recovery personalizado](#install-with-custom-recovery). -3. Descompacte o pacote AnyKernel3 e obtenha o arquivo `Image`, que é o arquivo do kernel do KernelSU. - -### Usando o magiskboot em dispositivos Android {#using-magiskboot-on-Android-devices} - -1. Baixe o Magisk mais recente em [GitHub Releases](https://github.com/topjohnwu/Magisk/releases). -2. Renomeie o `Magisk-*(versão).apk` para `Magisk-*.zip` e descompacte-o. -3. Envie `Magisk-*/lib/arm64-v8a/libmagiskboot.so` para o seu dispositivo por ADB: `adb push Magisk-*/lib/arm64-v8a/libmagiskboot.so /data/local/tmp/magiskboot`. -4. Envie o boot.img padrão e Image em AnyKernel3 para o seu dispositivo. -5. Entre no ADB shell e execute o diretório `cd /data/local/tmp/`, em seguida, `chmod +x magiskboot`. -6. Entre no ADB shell e execute o diretório `cd /data/local/tmp/`, execute `./magiskboot unpack boot.img` para descompactar `boot.img`, você obterá um arquivo `kernel`, este é o seu kernel padrão. -7. Substitua `kernel` por `Image` executando o comando: `mv -f Image kernel`. -8. Execute `./magiskboot repack boot.img` para reembalar o boot.img, e você obterá um arquivo `new-boot.img`, faça o flash deste arquivo para o dispositivo por fastboot. - -### Usando o magiskboot no PC Windows/macOS/Linux {#using-magiskboot-on-PC} - -1. Baixe o `magiskboot` adequado para o seu sistema operacional em [magiskboot_build](https://github.com/ookiineko/magiskboot_build/releases/tag/last-ci). -2. Prepare o `boot.img` padrão e `Image` em seu PC. -3. Execute `chmod +x magiskboot`. -4. Entre no diretório apropriado, execute `./magiskboot unpack boot.img` para descompactar `boot.img`. Você obterá um arquivo `kernel`, este é o seu kernel padrão. -5. Substitua `kernel` por `Image` executando o comando: `mv -f Image kernel`. -6. Execute `./magiskboot repack boot.img` para reembalar o boot.img, e você obterá um arquivo `new-boot.img`, faça o flash deste arquivo para o dispositivo por fastboot. - -::: info INFORMAÇÕES -O `magiskboot` oficial pode executar o dispositivo `Linux` normalmente. Se você for um usuário Linux, você pode usar a versão oficial. -::: - -## Instalar com Recovery personalizado {#install-with-custom-recovery} - -Pré-requisito: Seu dispositivo deve ter um Recovery personalizado, como TWRP. Se não houver Recovery personalizado disponível para o seu dispositivo, use outro método. - -Etapas: - -1. Em [GitHub Releases](https://github.com/tiann/KernelSU/releases), baixe o pacote ZIP começando com AnyKernel3 que corresponde à versão do seu dispositivo. Por exemplo, a versão do kernel do dispositivo é `android12-5.10.66`, então você deve baixar o arquivo `AnyKernel3-android12-5.10.66_yyyy-MM.zip` (onde `yyyy` é o ano e `MM` é o mês). -2. Reinicie o dispositivo no TWRP. -3. Use o ADB para colocar AnyKernel3-*.zip no dispositivo em `/sdcard` e escolha instalá-lo na interface do TWRP, ou você pode diretamente executar `adb sideload AnyKernel-*.zip` para instalar. - -Observação: Este método é adequado para qualquer instalação (não limitado à instalação inicial ou atualizações subsequentes), desde que você use o TWRP. - -## Outros métodos - -Na verdade, todos esses métodos de instalação têm apenas uma ideia principal, que é **substituir o kernel original pelo fornecido pelo KernelSU**, desde que isso possa ser alcançado, ele pode ser instalado. A seguir estão outros métodos possíveis: - -1. Primeiro instale o Magisk, obtenha privilégios root através do Magisk e então use o Kernel Flasher para fazer o flash no ZIP AnyKernel3 do KernelSU. -2. Use algum kit de ferramentas de flash em PC para flashar no kernel fornecido pelo KernelSU. - -No entanto, se não funcionar, por favor, tente o método `magiskboot`. +# Instalação + +## Verifique se o seu dispositivo é compatível + +Baixe o gerenciador do KernelSU em [GitHub Releases](https://github.com/tiann/KernelSU/releases) e instale-o no seu dispositivo: + +- Se o app mostrar `Sem suporte`, significa que **você precisará compilar o kernel por conta própria**. O KernelSU não fornecerá e nunca fornecerá um arquivo boot.img para você instalar. +- Se o app mostrar `Não instalado`, então seu dispositivo é oficialmente suportado pelo KernelSU. + +::: info INFORMAÇÕES +Para dispositivos que mostram `Sem suporte`, você pode conferir a lista de [Dispositivos com suporte não oficial](unofficially-support-devices.md). Você mesmo pode compilar o kernel. +::: + +## Backup padrão do boot.img + +Antes de fazer o flash, é essencial que você faça o backup do seu boot.img padrão. Se encontrar algum bootloop, você sempre pode restaurar o sistema voltando ao boot padrão de fábrica usando o fastboot. + +::: warning AVISO +O flash pode causar perda de dados. Certifique-se de executar esta etapa bem antes de prosseguir para a próxima! Se necessário, também é recomendável fazer backup de todos os dados do seu dispositivo. +::: + +## Conhecimento necessário + +### ADB e fastboot + +Por padrão, você usará as ferramentas ADB e fastboot neste tutorial, portanto, se você não as conhece, recomendamos pesquisar para aprender sobre elas primeiro. + +### KMI + +Kernel Module Interface (KMI), versões de kernel com o mesmo KMI são **compatíveis**, isso é o que "geral" significa no GKI. Por outro lado, se o KMI for diferente, então esses kernels não são compatíveis entre si, e atualizar uma imagem do kernel com um KMI diferente do seu dispositivo pode causar um bootloop. + +Especificamente, para dispositivos GKI, o formato da versão do kernel deve ser a seguinte: + +```txt +KernelRelease := +Version.PatchLevel.SubLevel-AndroidRelease-KmiGeneration-suffix +w .x .y -zzz -k -alguma coisa +``` + +`w.x-zzz-k` é a versão KMI. Por exemplo, se a versão do kernel de um dispositivo for `5.10.101-android12-9-g30979850fc20`, então seu KMI será `5.10-android12-9`. Teoricamente, ele pode inicializar normalmente com outros kernels KMI. + +::: tip DICA +Observe que o SubLevel na versão do kernel não faz parte do KMI! Isso significa que `5.10.101-android12-9-g30979850fc20` tem o mesmo KMI que `5.10.137-android12-9-g30979850fc20`! +::: + +### Nível do patch de segurança {#security-patch-level} + +Dispositivos Android mais recentes podem ter mecanismos anti-rollback que impedem o flash de um boot.img com um nível de patch de segurança antigo. Por exemplo, se o kernel do seu dispositivo for `5.10.101-android12-9-g30979850fc20`, o patch de segurança será `2023-11`, mesmo se você atualizar o kernel correspondente ao KMI do kernel, se o nível do patch de segurança for anterior a `2023-11` (como `2023-06`), isso pode causar um bootloop. + +Portanto, kernels com os níveis de patch de segurança mais recentes são preferidos para manter a compatibilidade com o KMI. + +### Versão do kernel vs Versão do Android + +Por favor, observe: **A versão do kernel e a versão do Android não são necessariamente iguais!** + +Se você descobrir que a versão do seu kernel é `android12-5.10.101`, mas a versão do seu sistema Android é Android 13 ou outra, não se surpreenda, pois o número da versão do sistema Android não é necessariamente igual ao número da versão do kernel Linux. O número da versão do kernel Linux geralmente é correspondente à versão do sistema Android que acompanha o **dispositivo quando ele é enviado**. Se o sistema Android for atualizado posteriormente, a versão do kernel geralmente não será alterada. Então, antes de flashar qualquer coisa, **consulte sempre a versão do kernel!** + +## Introdução + +Desde a versão [0.9.0](https://github.com/tiann/KernelSU/releases/tag/v0.9.0), o KernelSU suporta dois modos de execução em dispositivos GKI: + +1. `GKI`: Substitue o kernel original do dispositivo pelo **Generic Kernel Image** (GKI) fornecido pelo KernelSU. +2. `LKM`: Carregue o **Loadable Kernel Module** (LKM) no kernel do dispositivo sem substituir o kernel original. + +Esses dois modos são adequados para diferentes cenários, e você pode escolher o mais adequado conforme suas necessidades. + +### Modo GKI {#gki-mode} + +No modo GKI, o kernel original do dispositivo será substituído pela imagem genérica do kernel fornecida pelo KernelSU. As vantagens do modo GKI são: + +1. Forte universalidade, adequada para a maioria dos dispositivos. Por exemplo, a Samsung ativou dispositivos KNOX, e o modo LKM não pode funcionar. Existem também alguns dispositivos modificados de nicho que só podem usar o modo GKI. +2. Pode ser usado sem depender de firmware oficial, e não há necessidade de esperar por atualizações oficiais de firmware, desde que o KMI seja consistente, ele pode ser usado. + +### Modo LKM {#lkm-mode} + +No modo LKM, o kernel original do dispositivo não será substituído, mas o módulo do kernel carregável será carregado no kernel do dispositivo. As vantagens do modo LKM são: + +1. Não substituirá o kernel original do dispositivo. Se você tiver os requisitos especiais para o kernel original do dispositivo ou quiser usar o KernelSU enquanto usa um kernel de terceiros, poderá usar o modo LKM. +2. É mais conveniente atualizar o OTA. Ao atualizar o KernelSU, você pode instalá-lo diretamente no gerenciador sem flashar manualmente. Após o sistema OTA, você pode instalá-lo diretamente no segundo slot sem flashar manualmente. +3. Adequado para alguns cenários especiais. Por exemplo, o LKM também pode ser carregado com privilégios root temporários. Como não é necessário substituir a partição boot, ele não acionará o AVB e não causará o bloqueio do dispositivo. +4. O LKM pode ser desinstalado temporariamente. Se você deseja desativar temporariamente o acesso root, você pode desinstalar o LKM. Este processo não requer o flash de partições, nem mesmo a reinicialização do dispositivo. Se quiser ativar o root novamente, basta reiniciar o dispositivo. + +::: tip COEXISTÊNCIA DE DOIS MODOS +Após abrir o gerenciador, você pode ver o modo atual do dispositivo na página inicial. Observe que a prioridade do modo GKI é maior que a do LKM. Por exemplo, se você usar o kernel GKI para substituir o kernel original e usar LKM para corrigir o kernel GKI, o LKM será ignorado e o dispositivo sempre será executado no modo GKI. +::: + +### Qual escolher? {#which-one} + +Se o seu aparelho for um celular, recomendamos que você priorize o modo LKM. Se o seu dispositivo for um emulador, WSA ou Waydroid, recomendamos que você priorize o modo GKI. + +## Instalação do LKM + +### Obtenha o firmware oficial + +Para usar o modo LKM, você precisa obter o firmware oficial e corrigi-lo com base no firmware oficial. Se você usar um kernel de terceiros, poderá usar o `boot.img` do kernel de terceiros como firmware oficial. + +Existem muitas maneiras de obter o firmware oficial. Se o seu dispositivo suportar `fastboot boot`, então recomendamos **o método mais simples e indicado**, que consiste em usar `fastboot boot` para inicializar temporariamente o kernel GKI fornecido pelo KernelSU, depois instalar o gerenciador e, finalmente, instalá-lo diretamente pelo gerenciador. Este método não exige o download manual do firmware oficial nem a extração manual do boot. + +Se o seu dispositivo não suportar `fastboot boot`, pode ser necessário baixar manualmente o pacote de firmware oficial e extrair o boot dele. + +Ao contrário do modo GKI, o modo LKM modifica o `ramdisk`. Portanto, em dispositivos com Android 13, ele precisa corrigir a partição `init_boot` em vez da partição `boot`, enquanto o modo GKI sempre opera sobre a partição `boot`. + +### Use o gerenciador + +Abra o gerenciador, clique no ícone de instalação no canto superior direito e diversas opções aparecerão: + +1. Selecione um arquivo. Se o seu dispositivo não tiver privilégios root, você pode escolher esta opção e, em seguida, selecionar o seu firmware oficial. O gerenciador corrigirá automaticamente o firmware. Após isso, basta fazer o flash deste arquivo corrigido para obter privilégios root permanentemente. +2. Instalação direta. Se o seu dispositivo já estiver rooteado, você pode escolher esta opção. O gerenciador obterá automaticamente as informações do seu dispositivo, corrigirá o firmware oficial e realizará o flash automaticamente. Você também pode usar o comando `fastboot boot` junto com o kernel GKI do KernelSU para obter root temporário e instalar o gerenciador, e então usar esta opção. Esta também é a principal forma de atualizar o KernelSU. +3. Instalar no slot inativo. Se o seu dispositivo suportar partição A/B, você pode escolher esta opção. O gerenciador corrigirá automaticamente o firmware oficial e o instalará em outra partição. Esse método é adequado para dispositivos após o OTA, você pode instalá-lo diretamente em outra partição após o OTA e, em seguida, reiniciar o dispositivo. + +### Use a linha de comando + +Se não quiser usar o gerenciador, você também pode usar a linha de comando para instalar o LKM. A ferramenta `ksud` fornecida pelo KernelSU pode ajudá-lo a corrigir rapidamente o firmware oficial e depois fazer o flash. + +Esta ferramenta oferece suporte ao macOS, Linux e Windows. Você pode baixar a versão correspondente em [GitHub Release](https://github.com/tiann/KernelSU/releases). + +Uso: `ksud boot-patch` você pode verificar a ajuda da linha de comando para opções específicas. + +```sh +oriole:/ # ksud boot-patch -h +Patch boot ou imagens init_boot para aplicar o KernelSU + +Uso: ksud boot-patch [OPTIONS] + +Opções: + -b, --boot Caminho da imagem boot. Se não especificado, tentará encontrar a imagem boot automaticamente + -k, --kernel Caminho da imagem do kernel a ser substituída + -m, --module Caminho do módulo LKM a ser substituído. Se não especificado, usará o módulo integrado + -i, --init init a ser substituído + -u, --ota Usará outro slot se a imagem boot não for especificada + -f, --flash Flash para a partição boot após o patch + -o, --out Caminho de saída. Se não especificado, usará o diretório atual + --magiskboot Caminho do magiskboot. Se não especificado, usará a versão integrada + --kmi Versão do KMI. Se especificada, usará o KMI indicado + -h, --help Imprimir ajuda +``` + +Algumas opções que precisam ser explicadas: + +1. A opção `--magiskboot` pode especificar o caminho do magiskboot. Se não for especificado, o ksud irá procurá-lo nas variáveis ​​de ambiente. Se você não souber como obter o magiskboot, você pode verificar [aqui](#patch-boot-image). +2. A opção `--kmi` pode especificar a versão do `KMI`. Se o nome do kernel do seu dispositivo não seguir a especificação KMI, você poderá especificá-lo através desta opção. + +O uso mais comum é: + +```sh +ksud boot-patch -b --kmi android13-5.10 +``` + +## Instalação no modo GKI + +Existem vários métodos de instalação para o modo GKI, cada um adequado para um cenário diferente, portanto escolha conforme necessário. + +1. Instalar com fastboot usando o boot.img fornecido pelo KernelSU. +2. Instalar com um app kernel flash, como o [Kernel Flasher](https://github.com/capntrips/KernelFlasher/releases). +3. Corrigir manualmente o boot.img e instalá-lo. +4. Instalar com Recovery personalizado (por exemplo, TWRP). + +## Instalar com o boot.img fornecido pelo KernelSU + +Se o `boot.img` do seu dispositivo usa um formato de compactação comumente usado, você pode usar as imagens GKI fornecidas pelo KernelSU para atualizá-lo diretamente. Não requer TWRP ou autocorreção da imagem. + +### Encontre o boot.img adequado + +O KernelSU fornece um boot.img genérico para dispositivos GKI, e você deve fazer o flash do boot.img na 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 do [KMI](#kmi) e [Nível do patch de segurança](#security-patch-level) neste documento. + +Normalmente, existem três arquivos de inicialização em formatos diferentes para o mesmo KMI e nível de patch de segurança. Eles são idênticos, exceto pelo formato de compactação do kernel. Por favor, verifique o formato de compactação do kernel de seu boot.img original. Você deve usar o formato correto, como `lz4` ou `gz`. Se você usar um formato de compactação incorreto, poderá encontrar bootloop após o flash do boot.img. + +::: info FORMATO DE COMPACTAÇÃO DO BOOT.IMG +1. Você pode usar o magiskboot para obter o formato de compactação do seu boot.img original. Alternativamente, você também pode perguntar a membros ou desenvolvedores da comunidade que possuam o mesmo modelo de dispositivo. Além disso, o formato de compactação do kernel geralmente não muda, portanto, se você inicializar com êxito com um determinado formato de compactação, poderá tentar esse formato mais tarde. +2. Dispositivos Xiaomi geralmente usam `gz` ou `uncompressed`. +3. Para dispositivos Pixel, siga as instruções abaixo: +::: + +### Flash o boot.img para o dispositivo + +Use o `adb` para conectar seu dispositivo, execute `adb reboot bootloader` para entrar no modo fastboot e use este comando para flashar o KernelSU: + +```sh +fastboot flash boot boot.img +``` + +::: info INFORMAÇÕES +Se o seu dispositivo suportar `fastboot boot`, você pode usar primeiro `fastboot boot boot.img` para tentar usar o boot.img para inicializar o sistema primeiro. Se algo inesperado acontecer, reinicie-o novamente para inicializar. +::: + +### Reiniciar + +Após a conclusão do flash, você deve reiniciar o dispositivo: + +```sh +fastboot reboot +``` + +## Instalar com Kernel Flasher + +Etapa: + +1. Baixe o ZIP AnyKernel3. Se você não sabe qual arquivo baixar, leia atentamente a descrição do [KMI](#kmi) e [Nível do patch de segurança](#security-patch-level) neste documento. +2. Abra o app Kernel Flasher, conceda as permissões de root necessárias e use o ZIP AnyKernel3 fornecido para fazer o flash. + +Dessa forma, é necessário que o app Kernel Flasher tenha privilégios root. Você pode usar os seguintes métodos para conseguir isso: + +1. Seu dispositivo está rooteado. Por exemplo, você instalou o KernelSU e deseja atualizar para a versão mais recente ou fez o root por meio de outros métodos (como Magisk). +2. Se o seu dispositivo não estiver rooteado, mas suportar o método de inicialização temporária como `fastboot boot boot.img`, você pode usar a imagem GKI fornecida pelo KernelSU para inicializar temporariamente o seu dispositivo, obter privilégios root temporário e, em seguida, usar o Kernel Flasher para obter privilégios root permanente. + +Aqui estão alguns apps que podem ser usados para realizar o flash do kernel: + +1. [Kernel Flasher](https://github.com/capntrips/KernelFlasher/releases) +2. [Franco Kernel Manager](https://play.google.com/store/apps/details?id=com.franco.kernel) +3. [Ex Kernel Manager](https://play.google.com/store/apps/details?id=flar2.exkernelmanager) + +Observação: Este método é mais conveniente ao atualizar o KernelSU e pode ser feito sem um computador (faça um backup primeiro). + +## Corrigir boot.img manualmente {#patch-boot-image} + +Para alguns dispositivos, o formato boot.img não é tão comum como `lz4`, `gz` e `uncompressed`. Um exemplo típico é o Pixel, cujo boot.img é compactado no formato `lz4_legacy`, enquanto o ramdisk pode estar em `gz` ou também comprimido em `lz4_legacy`. Atualmente, se você flashar diretamente o boot.img fornecido pelo KernelSU, o dispositivo pode não conseguir inicializar. Nesse caso, é necessário corrigir manualmente o boot.img para conseguir isso. + +É sempre recomendado usar `magiskboot` para corrigir imagens, existem duas maneiras: + +1. [magiskboot](https://github.com/topjohnwu/Magisk/releases) +2. [magiskboot_build](https://github.com/ookiineko/magiskboot_build/releases/tag/last-ci) + +A versão oficial do `magiskboot` só pode ser executada em dispositivos Android, se você quiser rodar no PC, você pode tentar a segunda opção. + +::: tip DICA +Android-Image-Kitchen não é recomendado por enquanto, porque ele não lida corretamente com os metadados de inicialização (como o nível do patch de segurança). Portanto, pode não funcionar em alguns dispositivos. +::: + +### Preparação + +1. Obtenha o boot.img padrão do dispositivo. Você pode obtê-lo com os fabricantes do seu dispositivo. Talvez você precise do [payload-dumper-go](https://github.com/ssut/payload-dumper-go). +2. Baixe o arquivo ZIP AnyKernel3 fornecido pelo KernelSU que corresponde à versão KMI do seu dispositivo. Você pode consultar [Instalar com Recovery personalizado](#install-with-custom-recovery). +3. Descompacte o pacote AnyKernel3 e obtenha o arquivo `Image`, que é o arquivo do kernel do KernelSU. + +### Usando o magiskboot em dispositivos Android {#using-magiskboot-on-Android-devices} + +1. Baixe o Magisk mais recente em [GitHub Releases](https://github.com/topjohnwu/Magisk/releases). +2. Renomeie o `Magisk-*(versão).apk` para `Magisk-*.zip` e descompacte-o. +3. Envie `Magisk-*/lib/arm64-v8a/libmagiskboot.so` para o seu dispositivo por ADB: `adb push Magisk-*/lib/arm64-v8a/libmagiskboot.so /data/local/tmp/magiskboot`. +4. Envie o boot.img padrão e Image em AnyKernel3 para o seu dispositivo. +5. Entre no ADB shell e execute o diretório `cd /data/local/tmp/`, em seguida, `chmod +x magiskboot`. +6. Entre no ADB shell e execute o diretório `cd /data/local/tmp/`, execute `./magiskboot unpack boot.img` para descompactar `boot.img`, você obterá um arquivo `kernel`, este é o seu kernel padrão. +7. Substitua `kernel` por `Image` executando o comando: `mv -f Image kernel`. +8. Execute `./magiskboot repack boot.img` para reembalar o boot.img, e você obterá um arquivo `new-boot.img`, faça o flash deste arquivo para o dispositivo por fastboot. + +### Usando o magiskboot no PC Windows/macOS/Linux {#using-magiskboot-on-PC} + +1. Baixe o `magiskboot` adequado para o seu sistema operacional em [magiskboot_build](https://github.com/ookiineko/magiskboot_build/releases/tag/last-ci). +2. Prepare o `boot.img` padrão e `Image` em seu PC. +3. Execute `chmod +x magiskboot`. +4. Entre no diretório apropriado, execute `./magiskboot unpack boot.img` para descompactar `boot.img`. Você obterá um arquivo `kernel`, este é o seu kernel padrão. +5. Substitua `kernel` por `Image` executando o comando: `mv -f Image kernel`. +6. Execute `./magiskboot repack boot.img` para reembalar o boot.img, e você obterá um arquivo `new-boot.img`, faça o flash deste arquivo para o dispositivo por fastboot. + +::: info INFORMAÇÕES +O `magiskboot` oficial pode executar o dispositivo `Linux` normalmente. Se você for um usuário Linux, você pode usar a versão oficial. +::: + +## Instalar com Recovery personalizado {#install-with-custom-recovery} + +Pré-requisito: Seu dispositivo deve ter um Recovery personalizado, como TWRP. Se não houver Recovery personalizado disponível para o seu dispositivo, use outro método. + +Etapas: + +1. Em [GitHub Releases](https://github.com/tiann/KernelSU/releases), baixe o pacote ZIP começando com AnyKernel3 que corresponde à versão do seu dispositivo. Por exemplo, a versão do kernel do dispositivo é `android12-5.10.66`, então você deve baixar o arquivo `AnyKernel3-android12-5.10.66_yyyy-MM.zip` (onde `yyyy` é o ano e `MM` é o mês). +2. Reinicie o dispositivo no TWRP. +3. Use o ADB para colocar AnyKernel3-*.zip no dispositivo em `/sdcard` e escolha instalá-lo na interface do TWRP, ou você pode diretamente executar `adb sideload AnyKernel-*.zip` para instalar. + +Observação: Este método é adequado para qualquer instalação (não limitado à instalação inicial ou atualizações subsequentes), desde que você use o TWRP. + +## Outros métodos + +Na verdade, todos esses métodos de instalação têm apenas uma ideia principal, que é **substituir o kernel original pelo fornecido pelo KernelSU**, desde que isso possa ser alcançado, ele pode ser instalado. A seguir estão outros métodos possíveis: + +1. Primeiro instale o Magisk, obtenha privilégios root através do Magisk e então use o Kernel Flasher para fazer o flash no ZIP AnyKernel3 do KernelSU. +2. Use algum kit de ferramentas de flash em PC para flashar no kernel fornecido pelo KernelSU. + +No entanto, se não funcionar, por favor, tente o método `magiskboot`. diff --git a/website/docs/pt_BR/guide/module.md b/website/docs/pt_BR/guide/module.md index 70950a4d..b7f4e359 100644 --- a/website/docs/pt_BR/guide/module.md +++ b/website/docs/pt_BR/guide/module.md @@ -1,326 +1,326 @@ -# Guias de módulo - -O KernelSU fornece um mecanismo de módulo que consegue modificar o diretório do sistema enquanto mantém a integridade da partição do sistema. Esse mecanismo é conhecido como "sem sistema". - -O mecanismo de módulos do KernelSU é quase o mesmo do Magisk. Se você já está familiarizado com o desenvolvimento de módulos Magisk, o desenvolvimento de módulos KernelSU é muito semelhante. Você pode pular a introdução dos módulos abaixo e só precisa ler [Diferenças com Magisk](difference-with-magisk.md). - -## WebUI - -Os módulos do KernelSU suportam a exibição de interfaces e a interação com os usuários. Para mais detalhes, consulte a [documentação do WebUI](module-webui.md). - -## BusyBox - -O KernelSU vem com um recurso binário BusyBox completo (incluindo suporte completo ao SELinux). O executável está localizado em `/data/adb/ksu/bin/busybox`. O BusyBox do KernelSU suporta "ASH Standalone Shell Mode" alternável em tempo de execução. O que este Modo Autônomo significa é que ao executar no shell `ash` do BusyBox, cada comando usará diretamente o miniaplicativo dentro do BusyBox, independentemente do que estiver definido em `PATH`. Por exemplo, comandos como `ls`, `rm`, `chmod` **NÃO** usarão o que está em `PATH` (no caso do Android, por padrão será `/system/bin/ls`, `/system/bin/rm` e `/system/bin/chmod` respectivamente), mas em vez disso chamará diretamente os miniaplicativos internos do BusyBox. Isso garante que os scripts sempre sejam executados em um ambiente previsível e sempre tenham o conjunto completo de comandos, independentemente da versão do Android em que estão sendo executados. Para forçar um comando a **NÃO** usar o BusyBox, você deve chamar o executável com caminhos completos. - -Cada script shell executado no contexto do KernelSU será executado no shell `ash` do BusyBox com o Modo Autônomo ativado. Para o que é relevante para desenvolvedores terceirizados, isso inclui todos os scripts de inicialização e scripts de instalação de módulos. - -Para aqueles que desejam usar o recurso Modo Autônomo fora do KernelSU, existem 2 maneiras de ativá-los: - -1. Definir a variável de ambiente `ASH_STANDALONE` como `1`.
Exemplo: `ASH_STANDALONE=1 /data/adb/ksu/bin/busybox sh - - - - - - - - - - - - - - - - -
MantenedorRepositórioDispositivos suportados
{{ repo.maintainer }}{{ repo.kernel_name }}{{ repo.devices }}
+# Dispositivos com suporte não oficial + +::: warning AVISO +Nesta página, existem kernels para dispositivos não-GKI que suportam o KernelSU mantidos por outros desenvolvedores. +::: + +::: warning AVISO +Esta página é destinada apenas para ajudá-lo a encontrar o código-fonte correspondente ao seu dispositivo. **NÃO** significa que o código-fonte foi revisado pelos desenvolvedores do KernelSU. Você deve usá-lo por sua própria conta e risco. +::: + + + + + + + + + + + + + + + + + + +
MantenedorRepositórioDispositivos suportados
{{ repo.maintainer }}{{ repo.kernel_name }}{{ repo.devices }}
diff --git a/website/docs/pt_BR/guide/what-is-kernelsu.md b/website/docs/pt_BR/guide/what-is-kernelsu.md index 541c780a..6fd30023 100644 --- a/website/docs/pt_BR/guide/what-is-kernelsu.md +++ b/website/docs/pt_BR/guide/what-is-kernelsu.md @@ -1,21 +1,21 @@ -# O que é KernelSU? - -O KernelSU é uma solução root para dispositivos Android GKI, funciona no modo kernel e concede privilégios root para apps do espaço do usuário diretamente no espaço do kernel. - -## Características - -A principal característica do KernelSU é que ele é **baseado em kernel**. O KernelSU funciona no modo kernel, portanto pode fornecer uma interface de kernel que nunca tivemos antes. Por exemplo, é possível adicionar pontos de interrupção de hardware a qualquer processo no modo kernel, acessar a memória física de qualquer processo de forma invisível, interceptar qualquer chamada de sistema (syscall) no espaço do kernel, entre outras funcionalidades. - -E também, o KernelSU fornece um sistema de módulos via OverlayFS, que permite carregar seu plugin personalizado no sistema. Ele também fornece um mecanismo para modificar arquivos na partição `/system`. - -## Como usar o KernelSU? - -Por favor, consulte: [Instalação](installation) - -## Como compilar o KernelSU? - -Por favor, consulte: [Como compilar](how-to-build) - -## Discussão - -- Telegram: [@KernelSU](https://t.me/KernelSU) +# O que é KernelSU? + +O KernelSU é uma solução root para dispositivos Android GKI, funciona no modo kernel e concede privilégios root para apps do espaço do usuário diretamente no espaço do kernel. + +## Características + +A principal característica do KernelSU é que ele é baseado em kernel. O KernelSU funciona no modo kernel, portanto pode fornecer uma interface de kernel que nunca tivemos antes. Por exemplo, é possível adicionar pontos de interrupção de hardware a qualquer processo no modo kernel, acessar a memória física de qualquer processo de forma invisível, interceptar qualquer chamada de sistema (syscall) no espaço do kernel, entre outras funcionalidades. + +E também, o KernelSU fornece um sistema de módulos via OverlayFS, que permite carregar seu plugin personalizado no sistema. Ele também fornece um mecanismo para modificar arquivos na partição `/system`. + +## Como usar o KernelSU? + +Por favor, consulte: [Instalação](installation) + +## Como compilar o KernelSU? + +Por favor, consulte: [Como compilar](how-to-build) + +## Discussão + +- Telegram: [@KernelSU](https://t.me/KernelSU) diff --git a/website/docs/pt_BR/index.md b/website/docs/pt_BR/index.md index 113cd73f..b3730f25 100644 --- a/website/docs/pt_BR/index.md +++ b/website/docs/pt_BR/index.md @@ -1,28 +1,28 @@ ---- -layout: home -title: Início - -hero: - name: KernelSU - text: Uma solução root baseada em kernel para Android - tagline: "" - image: - src: /logo.png - alt: KernelSU - actions: - - theme: brand - text: Iniciar - link: /pt_BR/guide/what-is-kernelsu - - theme: alt - text: Ver no GitHub - link: https://github.com/tiann/KernelSU - -features: - - title: Baseado em kernel - details: Como o nome sugere, KernelSU funciona no kernel Linux, dando-lhe mais controle sobre os apps do espaço do usuário. - - 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 root personalizáveis - details: KernelSU permite a personalização de su, uid, gid, grupos, capacidades e regras do SELinux, bloqueando privilégios root. - - title: Módulos - details: Os módulos podem modificar /system sem sistema usando OverlayFS proporcionando flexibilidade significativa. +--- +layout: home +title: Início + +hero: + name: KernelSU + text: Uma solução root baseada em kernel para Android + tagline: "" + image: + src: /logo.png + alt: KernelSU + actions: + - theme: brand + text: Iniciar + link: /pt_BR/guide/what-is-kernelsu + - theme: alt + text: Ver no GitHub + link: https://github.com/tiann/KernelSU + +features: + - title: Baseado em kernel + details: Como o nome sugere, KernelSU funciona no kernel Linux, dando-lhe mais controle sobre os apps do espaço do usuário. + - 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 root personalizáveis + details: KernelSU permite a personalização de su, uid, gid, grupos, capacidades e regras do SELinux, bloqueando privilégios root. + - title: Módulos + details: Os módulos podem modificar /system sem sistema usando OverlayFS proporcionando flexibilidade significativa. diff --git a/website/docs/ru_RU/guide/faq.md b/website/docs/ru_RU/guide/faq.md index 29182819..f28dbc61 100644 --- a/website/docs/ru_RU/guide/faq.md +++ b/website/docs/ru_RU/guide/faq.md @@ -60,7 +60,7 @@ KernelSU не имеет встроенной поддержки Zygisk, но в Сейчас нет (возможно, в будущем), но есть много способов переключиться на глобальное пространство имен монтирования вручную, например: 1. `nsenter -t 1 -m sh` для получения оболочки в глобальном пространстве имен монтирования. -2. Добавить `nsenter --mount=/proc/1/ns/mnt` к команде, которую вы хотите выполнить, тогда команда будет выполнена в глобальном пространстве имен монтирования. KernelSU также [использует этот способ](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt#L115) +2. Добавить `nsenter --mount=/proc/1/ns/mnt` к команде, которую вы хотите выполнить, тогда команда будет выполнена в глобальном пространстве имен монтирования. KernelSU также [использует этот способ](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/shirkneko/zako/mksu/ui/util/KsuCli.kt#L115) ## Я GKI1.0, могу ли я использовать это? diff --git a/website/docs/vi_VN/guide/faq.md b/website/docs/vi_VN/guide/faq.md index fb20f907..1c794bd3 100644 --- a/website/docs/vi_VN/guide/faq.md +++ b/website/docs/vi_VN/guide/faq.md @@ -60,7 +60,7 @@ Phiên bản Kernel không liên quan gì đến phiên bản Android, nếu b Hiện tại thì không (có thể có trong tương lai), nhưng có nhiều cách để chuyển sang global mount namespace một cách thủ công, chẳng hạn như: 1. `nsenter -t 1 -m sh` để lấy shell trong global mount namespace. -2. Thêm `nsenter --mount=/proc/1/ns/mnt` vào lệnh bạn muốn thực thi, sau đó lệnh được thực thi trong global mount namespace. KernelSU cũng [sử dụng cách này](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt#L115) +2. Thêm `nsenter --mount=/proc/1/ns/mnt` vào lệnh bạn muốn thực thi, sau đó lệnh được thực thi trong global mount namespace. KernelSU cũng [sử dụng cách này](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/shirkneko/zako/mksu/ui/util/KsuCli.kt#L115) ## Tôi là GKI1.0, tôi có thể sử dụng cái này không? diff --git a/website/docs/zh_TW/guide/faq.md b/website/docs/zh_TW/guide/faq.md index 57166b68..b3666a9e 100644 --- a/website/docs/zh_TW/guide/faq.md +++ b/website/docs/zh_TW/guide/faq.md @@ -64,7 +64,7 @@ GKI1 與 GKI2 完全不同,所以您需要自行編譯核心。 目前沒有 (未來可能會支援),但實際上有很多種方法手動進入全域命名空間,無需 `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) +2. 在您要執行的命令前新增 `nsenter --mount=/proc/1/ns/mnt` 即可使此命令在全域 mount namespace 下執行。KernelSU 本身也使用了 [這種方法](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/shirkneko/zako/mksu/ui/util/KsuCli.kt#L115) ## KernelSU 可以修改 Hosts 嗎? 我要怎麼使用 AdAway? 當然。但是 KernelSU 沒有內建的 Hosts 支持,您可以安裝 [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) 來做到這一點。 diff --git a/website/yarn.lock b/website/yarn.lock index d16447d4..384a4013 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -29,121 +29,121 @@ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz#105e84ad9d1a31d3fb86ba20dc890eefe1a313a0" integrity sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg== -"@algolia/client-abtesting@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.20.3.tgz#e4120919292c57270546cdf8f7030ac8f035c9a2" - integrity sha512-wPOzHYSsW+H97JkBLmnlOdJSpbb9mIiuNPycUCV5DgzSkJFaI/OFxXfZXAh1gqxK+hf0miKue1C9bltjWljrNA== +"@algolia/client-abtesting@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.20.2.tgz#e0d950735cbe4e00960418749839820a333f9df0" + integrity sha512-IS8JSFsDD33haaKIIFaL7qj3bEIG9GldZfb3ILW0QF3at7TcrIJYy58hrDvFee5T3p3E2aH/+wqIr0eha8jB/w== dependencies: - "@algolia/client-common" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-common" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" -"@algolia/client-analytics@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.20.3.tgz#242d448ee34667a52bf1efe5c39b58546d71201f" - integrity sha512-XE3iduH9lA7iTQacDGofBQyIyIgaX8qbTRRdj1bOCmfzc9b98CoiMwhNwdTifmmMewmN0EhVF3hP8KjKWwX7Yw== +"@algolia/client-analytics@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.20.2.tgz#b8bc4ff50c3968e58b11a56ce50e8732c056ca19" + integrity sha512-k0KxCfcX/HZySqPasKy6GkiiDuebaMh2v/nE0HHg1PbsyeyagLapDi6Ktjkxhz8NlUq6eTJR+ddGJegippKQtQ== dependencies: - "@algolia/client-common" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-common" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" -"@algolia/client-common@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.20.3.tgz#7eec522ca18ae446b775092d17d47eecf97c6af9" - integrity sha512-IYRd/A/R3BXeaQVT2805lZEdWo54v39Lqa7ABOxIYnUvX2vvOMW1AyzCuT0U7Q+uPdD4UW48zksUKRixShcWxA== +"@algolia/client-common@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.20.2.tgz#4a80baedbe739ae84fde300a1f539508e5ae38a7" + integrity sha512-xoZcL/Uu49KYDb3feu2n06gALD17p5CslO8Zk3mZ7+uTurK3lgjLws7LNetZ172Ap/GpzPCRXI83d2iDoYQD6Q== -"@algolia/client-insights@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.20.3.tgz#1bcd9e3090512d3f32e64c2c0b839c7ebfd40574" - integrity sha512-QGc/bmDUBgzB71rDL6kihI2e1Mx6G6PxYO5Ks84iL3tDcIel1aFuxtRF14P8saGgdIe1B6I6QkpkeIddZ6vWQw== +"@algolia/client-insights@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.20.2.tgz#cf1c21e2c40c3751276c27048c72a3b164b0a4f2" + integrity sha512-fy7aCbo9y7WHt/9G03EYc471Dd5kIaM8PNP4z6AEQYr9a9X8c4inwNs6tePxAEfRHwVQi0CZ7kuVdn6/MjWx1A== dependencies: - "@algolia/client-common" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-common" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" -"@algolia/client-personalization@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.20.3.tgz#ca1fe559112bd9aedefa38ca712d69b0bc2bfddf" - integrity sha512-zuM31VNPDJ1LBIwKbYGz/7+CSm+M8EhlljDamTg8AnDilnCpKjBebWZR5Tftv/FdWSro4tnYGOIz1AURQgZ+tQ== +"@algolia/client-personalization@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.20.2.tgz#ab8342c1e5f1a4ae71383dffdb25910c5df9d06e" + integrity sha512-ocL1ZXulfuXzJAwsKw2kMscKMD0rs/f4CFYu6Gjh4mK4um6rGfa1a6u1MSc4swFqRQer0wNP9Pi+kVfKhuKt5A== dependencies: - "@algolia/client-common" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-common" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" -"@algolia/client-query-suggestions@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.20.3.tgz#fed613ab3c7e0be1cb5dcca09fdab65de17e2800" - integrity sha512-Nn872PuOI8qzi1bxMMhJ0t2AzVBqN01jbymBQOkypvZHrrjZPso3iTpuuLLo9gi3yc/08vaaWTAwJfPhxPwJUw== +"@algolia/client-query-suggestions@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.20.2.tgz#fc2d5836aaf90fba60fc347e0f1b1e8e4ab15592" + integrity sha512-Xjs4Tj1zkLCnmq1ys8RRhLQPy002I6GuT/nbHVdSQmQu4yKCI0gOFbwxHdM6yYPEuE3cJx7A4wSQjCH21mUKsg== dependencies: - "@algolia/client-common" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-common" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" -"@algolia/client-search@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.20.3.tgz#d8ce41dea173112d4a971d697f34145a1354befd" - integrity sha512-9+Fm1ahV8/2goSIPIqZnVitV5yHW5E5xTdKy33xnqGd45A9yVv5tTkudWzEXsbfBB47j9Xb3uYPZjAvV5RHbKA== +"@algolia/client-search@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.20.2.tgz#941788be5c238197d30a21237e0b3f28d80db874" + integrity sha512-2cD3RGB5byusLS0DAX1Nvl5MLiv7OoGgQrRs+94dTalqjvK8lGKzxxJhXoVojgx2qcROyIUAIDXFdTqv6NIHaA== dependencies: - "@algolia/client-common" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-common" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" -"@algolia/ingestion@1.20.3": - version "1.20.3" - resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.20.3.tgz#32283c2cde45d47b6731bbaaa170703ddf9c3874" - integrity sha512-5GHNTiZ3saLjTNyr6WkP5hzDg2eFFAYWomvPcm9eHWskjzXt8R0IOiW9kkTS6I6hXBwN5H9Zna5mZDSqqJdg+g== +"@algolia/ingestion@1.20.2": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.20.2.tgz#f506af644979974a6842fb75ac90df8782da4b0d" + integrity sha512-S593Kmhc98+5zdzGet4GrZEBEBGl4vVtqg/MPfW8dCRf9qDRNYSkhBsIzlhQe9JWiohe9oB9LW5meibwOgRmww== dependencies: - "@algolia/client-common" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-common" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" -"@algolia/monitoring@1.20.3": - version "1.20.3" - resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.20.3.tgz#8165d8fbb89134876e9055f3432a3de72dc150bc" - integrity sha512-KUWQbTPoRjP37ivXSQ1+lWMfaifCCMzTnEcEnXwAmherS5Tp7us6BAqQDMGOD4E7xyaS2I8pto6WlOzxH+CxmA== +"@algolia/monitoring@1.20.2": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.20.2.tgz#920ce38c5d2250bc34692bc256b622e72c3f8169" + integrity sha512-bW41aWLYgBv/coJUIT85mkN3kk1VBKsM8tlwB5S/s446Mgc7r8t5TX7kA8kCR2UbwDedOK51i/85/x/rM0ZXbg== dependencies: - "@algolia/client-common" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-common" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" -"@algolia/recommend@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.20.3.tgz#bfe36b49287cdf07ad5369640dd65f4b12117e30" - integrity sha512-oo/gG77xTTTclkrdFem0Kmx5+iSRFiwuRRdxZETDjwzCI7svutdbwBgV/Vy4D4QpYaX4nhY/P43k84uEowCE4Q== +"@algolia/recommend@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.20.2.tgz#423ebaddf13de43a8eb28b0642b60e85f3393e34" + integrity sha512-wBMf3J1L5ogvU8p8ifHkknDXWn1zdZ2epkqpt2MkUaZynE3G77rrFU9frcO+Pu1FQJQ5xCDTKcYUUcJCDD00rg== dependencies: - "@algolia/client-common" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-common" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" -"@algolia/requester-browser-xhr@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.20.3.tgz#7bc054eb70669e601e0b1ab034d360eb44c900b6" - integrity sha512-BkkW7otbiI/Er1AiEPZs1h7lxbtSO9p09jFhv3/iT8/0Yz0CY79VJ9iq+Wv1+dq/l0OxnMpBy8mozrieGA3mXQ== +"@algolia/requester-browser-xhr@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.20.2.tgz#ec674ae20e9fecde1c1a73c5cb0fcfacd3803bac" + integrity sha512-w+VMzOkIq2XDGg6Ybzr74RlBZvJQnuIdKpVusQSXCXknvxwAwbO457LmoavhZWl06Lcsk9YDx1X2k0zb+iJQmw== dependencies: - "@algolia/client-common" "5.20.3" + "@algolia/client-common" "5.20.2" -"@algolia/requester-fetch@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.20.3.tgz#e280a3bd142984a31b172743119f21a3ccef576b" - integrity sha512-eAVlXz7UNzTsA1EDr+p0nlIH7WFxo7k3NMxYe8p38DH8YVWLgm2MgOVFUMNg9HCi6ZNOi/A2w/id2ZZ4sKgUOw== +"@algolia/requester-fetch@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.20.2.tgz#13d59d9c946d9cd3de319865d70db64e255028e4" + integrity sha512-wpjnbvbi3A13b0DvijE45DRYDvwcP5Ttz7RTMkPWTkF1s6AHuo6O2UcwGyaogMAGa1QOOzFYfp5u4YQwMOQx5g== dependencies: - "@algolia/client-common" "5.20.3" + "@algolia/client-common" "5.20.2" -"@algolia/requester-node-http@5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.20.3.tgz#cf06a8568efd7f3ad02414e13f2fa94c15edfc37" - integrity sha512-FqR3pQPfHfQyX1wgcdK6iyqu86yP76MZd4Pzj1y/YLMj9rRmRCY0E0AffKr//nrOFEwv6uY8BQY4fd9/6b0ZCg== +"@algolia/requester-node-http@5.20.2": + version "5.20.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.20.2.tgz#29bd7333dbec0d24dfecd2e1d8276fe40e67ea9d" + integrity sha512-YuSSdtgUt1dFBTNYUb+2TA5j0Hd0eDXE0bVISjUvTCqmoaGsGLwW+rKI7p1eLQ1r7RESwBAvUwcY1qP2Wl3Lyw== dependencies: - "@algolia/client-common" "5.20.3" + "@algolia/client-common" "5.20.2" "@babel/helper-string-parser@^7.25.9": version "7.25.9" @@ -156,16 +156,16 @@ integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== "@babel/parser@^7.25.3": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.9.tgz#d9e78bee6dc80f9efd8f2349dcfbbcdace280fd5" - integrity sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A== + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.8.tgz#deca2b4d99e5e1b1553843b99823f118da6107c2" + integrity sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw== dependencies: - "@babel/types" "^7.26.9" + "@babel/types" "^7.26.8" -"@babel/types@^7.26.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce" - integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw== +"@babel/types@^7.26.8": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.8.tgz#97dcdc190fab45be7f3dc073e3c11160d677c127" + integrity sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA== dependencies: "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" @@ -309,9 +309,9 @@ integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== "@iconify-json/simple-icons@^1.2.21": - version "1.2.25" - resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.2.25.tgz#b984e1851baac5cddbe82ecc15f3a5806c8879bb" - integrity sha512-2E1/gOCO97rF6usfhhiXxwzCb+UhdEsxW3lW1Sew+xZY0COY6dp82Z/r1rUt2fWKneWjuoGcNeJHHXQyG8mIuw== + version "1.2.24" + resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.2.24.tgz#1cc703e731f979f76b0586236b402372169062d5" + integrity sha512-06ZWXZx3PHCE+02zn+iIGOKKNgE3kyPd0Yh7IUEIa0bCYI6UmGlsYYghRx8As9TnTNYMCEiy5V0zI4Jb6EY6XA== dependencies: "@iconify/types" "*" @@ -325,114 +325,102 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@rollup/rollup-android-arm-eabi@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz#731df27dfdb77189547bcef96ada7bf166bbb2fb" - integrity sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw== +"@rollup/rollup-android-arm-eabi@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz#9b726b4dcafb9332991e9ca49d54bafc71d9d87f" + integrity sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg== -"@rollup/rollup-android-arm64@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz#4bea6db78e1f6927405df7fe0faf2f5095e01343" - integrity sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q== +"@rollup/rollup-android-arm64@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz#88326ff46168a47851077ca0bf0c442689ec088f" + integrity sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA== -"@rollup/rollup-darwin-arm64@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz#a7aab77d44be3c44a20f946e10160f84e5450e7f" - integrity sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q== +"@rollup/rollup-darwin-arm64@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz#b8fbcc9389bc6fad3334a1d16dbeaaa5637c5772" + integrity sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg== -"@rollup/rollup-darwin-x64@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz#c572c024b57ee8ddd1b0851703ace9eb6cc0dd82" - integrity sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw== +"@rollup/rollup-darwin-x64@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz#1aa2bcad84c0fb5902e945d88822e17a4f661d51" + integrity sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg== -"@rollup/rollup-freebsd-arm64@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz#cf74f8113b5a83098a5c026c165742277cbfb88b" - integrity sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA== +"@rollup/rollup-freebsd-arm64@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz#29c54617e0929264dcb6416597d6d7481696e49f" + integrity sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ== -"@rollup/rollup-freebsd-x64@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz#39561f3a2f201a4ad6a01425b1ff5928154ecd7c" - integrity sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q== +"@rollup/rollup-freebsd-x64@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz#a8b58ab7d31882559d93f2d1b5863d9e4b4b2678" + integrity sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ== -"@rollup/rollup-linux-arm-gnueabihf@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz#980d6061e373bfdaeb67925c46d2f8f9b3de537f" - integrity sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g== +"@rollup/rollup-linux-arm-gnueabihf@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz#a844e1978c8b9766b169ecb1cb5cc0d8a3f05930" + integrity sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg== -"@rollup/rollup-linux-arm-musleabihf@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz#f91a90f30dc00d5a64ac2d9bbedc829cd3cfaa78" - integrity sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA== +"@rollup/rollup-linux-arm-musleabihf@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz#6b44c3b7257985d71b087fcb4ef01325e2fff201" + integrity sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg== -"@rollup/rollup-linux-arm64-gnu@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz#fac700fa5c38bc13a0d5d34463133093da4c92a0" - integrity sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A== +"@rollup/rollup-linux-arm64-gnu@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz#ebb499cf1720115256d0c9ae7598c90cc2251bc5" + integrity sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA== -"@rollup/rollup-linux-arm64-musl@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz#f50ecccf8c78841ff6df1706bc4782d7f62bf9c3" - integrity sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q== +"@rollup/rollup-linux-arm64-musl@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz#9658221b59d9e5643348f9a52fa5ef35b4dc07b1" + integrity sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q== -"@rollup/rollup-linux-loongarch64-gnu@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz#5869dc0b28242da6553e2b52af41374f4038cd6e" - integrity sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ== +"@rollup/rollup-linux-loongarch64-gnu@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz#19418cc57579a5655af2d850a89d74b3f7e9aa92" + integrity sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw== -"@rollup/rollup-linux-powerpc64le-gnu@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz#5cdd9f851ce1bea33d6844a69f9574de335f20b1" - integrity sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw== +"@rollup/rollup-linux-powerpc64le-gnu@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz#fe0bce7778cb6ce86898c781f3f11369d1a4952c" + integrity sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ== -"@rollup/rollup-linux-riscv64-gnu@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz#ef5dc37f4388f5253f0def43e1440ec012af204d" - integrity sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw== +"@rollup/rollup-linux-riscv64-gnu@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz#9c158360abf6e6f7794285642ba0898c580291f6" + integrity sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg== -"@rollup/rollup-linux-s390x-gnu@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz#7dbc3ccbcbcfb3e65be74538dfb6e8dd16178fde" - integrity sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA== +"@rollup/rollup-linux-s390x-gnu@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz#f9113498d22962baacdda008b5587d568b05aa34" + integrity sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw== -"@rollup/rollup-linux-x64-gnu@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz#5783fc0adcab7dc069692056e8ca8d83709855ce" - integrity sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA== +"@rollup/rollup-linux-x64-gnu@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz#aec8d4cdf911cd869a72b8bd00833cb426664e0c" + integrity sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw== -"@rollup/rollup-linux-x64-musl@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz#00b6c29b298197a384e3c659910b47943003a678" - integrity sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ== +"@rollup/rollup-linux-x64-musl@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz#61c0a146bdd1b5e0dcda33690dd909b321d8f20f" + integrity sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A== -"@rollup/rollup-win32-arm64-msvc@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz#cbfee01f1fe73791c35191a05397838520ca3cdd" - integrity sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ== +"@rollup/rollup-win32-arm64-msvc@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz#c6c5bf290a3a459c18871110bc2e7009ce35b15a" + integrity sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA== -"@rollup/rollup-win32-ia32-msvc@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz#95cdbdff48fe6c948abcf6a1d500b2bd5ce33f62" - integrity sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w== +"@rollup/rollup-win32-ia32-msvc@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz#16ca6bdadc9e054818b9c51f8dac82f6b8afab81" + integrity sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA== -"@rollup/rollup-win32-x64-msvc@4.34.8": - version "4.34.8" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz#4cdb2cfae69cdb7b1a3cc58778e820408075e928" - integrity sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g== +"@rollup/rollup-win32-x64-msvc@4.34.6": + version "4.34.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz#f3d03ce2d82723eb089188ea1494a719b09e1561" + integrity sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w== -"@shikijs/core@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-2.5.0.tgz#e14d33961dfa3141393d4a76fc8923d0d1c4b62f" - integrity sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg== - dependencies: - "@shikijs/engine-javascript" "2.5.0" - "@shikijs/engine-oniguruma" "2.5.0" - "@shikijs/types" "2.5.0" - "@shikijs/vscode-textmate" "^10.0.2" - "@types/hast" "^3.0.4" - hast-util-to-html "^9.0.4" - -"@shikijs/core@^2.1.0": +"@shikijs/core@2.3.2", "@shikijs/core@^2.1.0": version "2.3.2" resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-2.3.2.tgz#dcdc851e6963fe4196e2f1051302dcecce1a8706" integrity sha512-s7vyL3LzUKm3Qwf36zRWlavX9BQMZTIq9B1almM63M5xBuSldnsTHCmsXzoF/Kyw4k7Xgas7yAyJz9VR/vcP1A== @@ -453,15 +441,6 @@ "@shikijs/vscode-textmate" "^10.0.1" oniguruma-to-es "^3.1.0" -"@shikijs/engine-javascript@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz#e045c6ecfbda6c99137547b0a482e0b87f1053fc" - integrity sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w== - dependencies: - "@shikijs/types" "2.5.0" - "@shikijs/vscode-textmate" "^10.0.2" - oniguruma-to-es "^3.1.0" - "@shikijs/engine-oniguruma@2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-2.3.2.tgz#42e64b7bbbaec5e903b12718dde1f1e1738a9c66" @@ -470,35 +449,27 @@ "@shikijs/types" "2.3.2" "@shikijs/vscode-textmate" "^10.0.1" -"@shikijs/engine-oniguruma@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz#230de5693cc1da6c9d59c7ad83593c2027274817" - integrity sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw== +"@shikijs/langs@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-2.3.2.tgz#a716ac528dea9e927d7088102a132c153f8a161b" + integrity sha512-UqI6bSxFzhexIJficZLKeB1L2Sc3xoNiAV0yHpfbg5meck93du+EKQtsGbBv66Ki53XZPhnR/kYkOr85elIuFw== dependencies: - "@shikijs/types" "2.5.0" - "@shikijs/vscode-textmate" "^10.0.2" + "@shikijs/types" "2.3.2" -"@shikijs/langs@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-2.5.0.tgz#97ab50c495922cc1ca06e192985b28dc73de5d50" - integrity sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w== +"@shikijs/themes@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-2.3.2.tgz#c117d826ce22899dea802499a7ad5a99c2de0446" + integrity sha512-QAh7D/hhfYKHibkG2tti8vxNt3ekAH5EqkXJeJbTh7FGvTCWEI7BHqNCtMdjFvZ0vav5nvUgdvA7/HI7pfsB4w== dependencies: - "@shikijs/types" "2.5.0" - -"@shikijs/themes@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-2.5.0.tgz#8c6aecf73f5455681c8bec15797cf678162896cb" - integrity sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw== - dependencies: - "@shikijs/types" "2.5.0" + "@shikijs/types" "2.3.2" "@shikijs/transformers@^2.1.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@shikijs/transformers/-/transformers-2.5.0.tgz#190c84786ff06c417580ab79177338a947168c55" - integrity sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg== + version "2.3.2" + resolved "https://registry.yarnpkg.com/@shikijs/transformers/-/transformers-2.3.2.tgz#a62183c40cb004851312428d8681962ed487b37e" + integrity sha512-2HDnJumw8A/9GecRpTgvfqSbPjEbJ4DPWq5J++OVP1gNMLvbV0MqFsP4canqRNM1LqB7VmWY45Stipb0ZIJ+0A== dependencies: - "@shikijs/core" "2.5.0" - "@shikijs/types" "2.5.0" + "@shikijs/core" "2.3.2" + "@shikijs/types" "2.3.2" "@shikijs/types@2.3.2", "@shikijs/types@^2.1.0": version "2.3.2" @@ -508,18 +479,10 @@ "@shikijs/vscode-textmate" "^10.0.1" "@types/hast" "^3.0.4" -"@shikijs/types@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-2.5.0.tgz#e949c7384802703a48b9d6425dd41673c164df69" - integrity sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw== - dependencies: - "@shikijs/vscode-textmate" "^10.0.2" - "@types/hast" "^3.0.4" - -"@shikijs/vscode-textmate@^10.0.1", "@shikijs/vscode-textmate@^10.0.2": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224" - integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg== +"@shikijs/vscode-textmate@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz#d06d45b67ac5e9b0088e3f67ebd3f25c6c3d711a" + integrity sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg== "@types/estree@1.0.6": version "1.0.6" @@ -621,18 +584,18 @@ "@vue/shared" "3.5.13" "@vue/devtools-api@^7.7.0": - version "7.7.2" - resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-7.7.2.tgz#49837eae6f61fc43a09f5d6c2d3210f9f73a0d09" - integrity sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA== + version "7.7.1" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-7.7.1.tgz#6a44bf03ce27ba0230461171812d9ae98aeb2458" + integrity sha512-Cexc8GimowoDkJ6eNelOPdYIzsu2mgNyp0scOQ3tiaYSb9iok6LOESSsJvHaI+ib3joRfqRJNLkHFjhNuWA5dg== dependencies: - "@vue/devtools-kit" "^7.7.2" + "@vue/devtools-kit" "^7.7.1" -"@vue/devtools-kit@^7.7.2": - version "7.7.2" - resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz#3315bd5b144f98c7b84c2f44270b445644ec8f10" - integrity sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ== +"@vue/devtools-kit@^7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.7.1.tgz#9b4cdef7111ffd8673c14e9a16a433c65ebb8a8e" + integrity sha512-yhZ4NPnK/tmxGtLNQxmll90jIIXdb2jAhPF76anvn5M/UkZCiLJy28bYgPIACKZ7FCosyKoaope89/RsFJll1w== dependencies: - "@vue/devtools-shared" "^7.7.2" + "@vue/devtools-shared" "^7.7.1" birpc "^0.2.19" hookable "^5.5.3" mitt "^3.0.1" @@ -640,10 +603,10 @@ speakingurl "^14.0.1" superjson "^2.2.1" -"@vue/devtools-shared@^7.7.2": - version "7.7.2" - resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz#b11b143820130a32d8ce5737e264d06ab6d62f40" - integrity sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA== +"@vue/devtools-shared@^7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.7.1.tgz#3a92d7cc268c15fa639797c45b0aff79eae9b8d7" + integrity sha512-BtgF7kHq4BHG23Lezc/3W2UhK2ga7a8ohAIAGJMBr4BkxUFzhqntQtCiuL1ijo2ztWnmusymkirgqUrXoQKumA== dependencies: rfdc "^1.4.1" @@ -685,17 +648,7 @@ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== -"@vueuse/core@12.7.0": - version "12.7.0" - resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-12.7.0.tgz#b9c3880e9c01d9db86029c6a58412f1b1922497e" - integrity sha512-jtK5B7YjZXmkGNHjviyGO4s3ZtEhbzSgrbX+s5o+Lr8i2nYqNyHuPVOeTdM1/hZ5Tkxg/KktAuAVDDiHMraMVA== - dependencies: - "@types/web-bluetooth" "^0.0.20" - "@vueuse/metadata" "12.7.0" - "@vueuse/shared" "12.7.0" - vue "^3.5.13" - -"@vueuse/core@^12.4.0": +"@vueuse/core@12.5.0", "@vueuse/core@^12.4.0": version "12.5.0" resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-12.5.0.tgz#1321c75132c4f20f223e6313587ebeeec79957f2" integrity sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg== @@ -706,12 +659,12 @@ vue "^3.5.13" "@vueuse/integrations@^12.4.0": - version "12.7.0" - resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-12.7.0.tgz#d9ba676a6643def3f8dcc99580162fbaf33de05e" - integrity sha512-IEq7K4bCl7mn3uKJaWtNXnd1CAPaHLUMuyj5K1/k/pVcItt0VONZW8xiGxdIovJcQjkzOHjImhX5t6gija+0/g== + version "12.5.0" + resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-12.5.0.tgz#6496ea24772d087c8fec3973a471a6ab50f9e7c0" + integrity sha512-HYLt8M6mjUfcoUOzyBcX2RjpfapIwHPBmQJtTmXOQW845Y/Osu9VuTJ5kPvnmWJ6IUa05WpblfOwZ+P0G4iZsQ== dependencies: - "@vueuse/core" "12.7.0" - "@vueuse/shared" "12.7.0" + "@vueuse/core" "12.5.0" + "@vueuse/shared" "12.5.0" vue "^3.5.13" "@vueuse/metadata@12.5.0": @@ -719,11 +672,6 @@ resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-12.5.0.tgz#8f1778a2894bdda2bf458054377a379d40276306" integrity sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg== -"@vueuse/metadata@12.7.0": - version "12.7.0" - resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-12.7.0.tgz#17a263927204962ec045095c83f62c81db085a46" - integrity sha512-4VvTH9mrjXqFN5LYa5YfqHVRI6j7R00Vy4995Rw7PQxyCL3z0Lli86iN4UemWqixxEvYfRjG+hF9wL8oLOn+3g== - "@vueuse/shared@12.5.0": version "12.5.0" resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-12.5.0.tgz#b93af7ab0fd6a8d879808c9bf37d540dac01da13" @@ -731,31 +679,24 @@ dependencies: vue "^3.5.13" -"@vueuse/shared@12.7.0": - version "12.7.0" - resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-12.7.0.tgz#0c573789069818a2e25ddae3ab64b536c614537b" - integrity sha512-coLlUw2HHKsm7rPN6WqHJQr18WymN4wkA/3ThFaJ4v4gWGWAQQGK+MJxLuJTBs4mojQiazlVWAKNJNpUWGRkNw== - dependencies: - vue "^3.5.13" - algoliasearch@^5.14.2: - version "5.20.3" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.20.3.tgz#32d79b9ffaf5a085943fab304698f46c5a3faed9" - integrity sha512-iNC6BGvipaalFfDfDnXUje8GUlW5asj0cTMsZJwO/0rhsyLx1L7GZFAY8wW+eQ6AM4Yge2p5GSE5hrBlfSD90Q== + version "5.20.2" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.20.2.tgz#e1df37b02a4cf0f7031ff729ee9b35a7b43eeea6" + integrity sha512-8evxG++iWyWnhng3g5RP+kwn6j+2vKLfew8pVoekn87FcfsDm92zJXKwSrU6pl+m5eAbGFhFF/gCYEQiRdbPlA== dependencies: - "@algolia/client-abtesting" "5.20.3" - "@algolia/client-analytics" "5.20.3" - "@algolia/client-common" "5.20.3" - "@algolia/client-insights" "5.20.3" - "@algolia/client-personalization" "5.20.3" - "@algolia/client-query-suggestions" "5.20.3" - "@algolia/client-search" "5.20.3" - "@algolia/ingestion" "1.20.3" - "@algolia/monitoring" "1.20.3" - "@algolia/recommend" "5.20.3" - "@algolia/requester-browser-xhr" "5.20.3" - "@algolia/requester-fetch" "5.20.3" - "@algolia/requester-node-http" "5.20.3" + "@algolia/client-abtesting" "5.20.2" + "@algolia/client-analytics" "5.20.2" + "@algolia/client-common" "5.20.2" + "@algolia/client-insights" "5.20.2" + "@algolia/client-personalization" "5.20.2" + "@algolia/client-query-suggestions" "5.20.2" + "@algolia/client-search" "5.20.2" + "@algolia/ingestion" "1.20.2" + "@algolia/monitoring" "1.20.2" + "@algolia/recommend" "5.20.2" + "@algolia/requester-browser-xhr" "5.20.2" + "@algolia/requester-fetch" "5.20.2" + "@algolia/requester-node-http" "5.20.2" birpc@^0.2.19: version "0.2.19" @@ -863,9 +804,9 @@ fsevents@~2.3.2, fsevents@~2.3.3: integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== hast-util-to-html@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005" - integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw== + version "9.0.4" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz#d689c118c875aab1def692c58603e34335a0f5c5" + integrity sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA== dependencies: "@types/hast" "^3.0.0" "@types/unist" "^3.0.0" @@ -874,7 +815,7 @@ hast-util-to-html@^9.0.4: hast-util-whitespace "^3.0.0" html-void-elements "^3.0.0" mdast-util-to-hast "^13.0.0" - property-information "^7.0.0" + property-information "^6.0.0" space-separated-tokens "^2.0.0" stringify-entities "^4.0.0" zwitch "^2.0.4" @@ -961,9 +902,9 @@ micromark-util-types@^2.0.0: integrity sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ== minisearch@^7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-7.1.2.tgz#296ee8d1906cc378f7e57a3a71f07e5205a75df5" - integrity sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA== + version "7.1.1" + resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-7.1.1.tgz#901d0367f078233cdc7a10be7264e09c6248cf5f" + integrity sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw== mitt@^3.0.1: version "3.0.1" @@ -976,9 +917,9 @@ nanoid@^3.3.8: integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== oniguruma-to-es@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz#480e4bac4d3bc9439ac0d2124f0725e7a0d76d17" - integrity sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ== + version "3.1.0" + resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-3.1.0.tgz#6e74d9ef4e8e33a61ad28c1f564b7d0ce1d0b0d9" + integrity sha512-BJ3Jy22YlgejHSO7Fvmz1kKazlaPmRSUH+4adTDUS/dKQ4wLxI+gALZ8updbaux7/m7fIlpgOZ5fp/Inq5jUAw== dependencies: emoji-regex-xs "^1.0.0" regex "^6.0.1" @@ -995,23 +936,23 @@ picocolors@^1.1.1: integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== postcss@^8.4.43, postcss@^8.4.48: - version "8.5.3" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" - integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== + version "8.5.2" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.2.tgz#e7b99cb9d2ec3e8dd424002e7c16517cb2b846bd" + integrity sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA== dependencies: nanoid "^3.3.8" picocolors "^1.1.1" source-map-js "^1.2.1" preact@^10.0.0: - version "10.26.2" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.26.2.tgz#d737055584a4d8004ec273e425fb4c30960aa512" - integrity sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg== + version "10.25.4" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.25.4.tgz#c1d00bee9d7b9dcd06a2311d9951973b506ae8ac" + integrity sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA== -property-information@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.0.0.tgz#3508a6d6b0b8eb3ca6eb2c6623b164d2ed2ab112" - integrity sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg== +property-information@^6.0.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" + integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== regex-recursion@^6.0.2: version "6.0.2" @@ -1038,45 +979,45 @@ rfdc@^1.4.1: integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== rollup@^4.20.0: - version "4.34.8" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.8.tgz#e859c1a51d899aba9bcf451d4eed1d11fb8e2a6e" - integrity sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ== + version "4.34.6" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.6.tgz#a07e4d2621759e29034d909655e7a32eee9195c9" + integrity sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ== dependencies: "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.34.8" - "@rollup/rollup-android-arm64" "4.34.8" - "@rollup/rollup-darwin-arm64" "4.34.8" - "@rollup/rollup-darwin-x64" "4.34.8" - "@rollup/rollup-freebsd-arm64" "4.34.8" - "@rollup/rollup-freebsd-x64" "4.34.8" - "@rollup/rollup-linux-arm-gnueabihf" "4.34.8" - "@rollup/rollup-linux-arm-musleabihf" "4.34.8" - "@rollup/rollup-linux-arm64-gnu" "4.34.8" - "@rollup/rollup-linux-arm64-musl" "4.34.8" - "@rollup/rollup-linux-loongarch64-gnu" "4.34.8" - "@rollup/rollup-linux-powerpc64le-gnu" "4.34.8" - "@rollup/rollup-linux-riscv64-gnu" "4.34.8" - "@rollup/rollup-linux-s390x-gnu" "4.34.8" - "@rollup/rollup-linux-x64-gnu" "4.34.8" - "@rollup/rollup-linux-x64-musl" "4.34.8" - "@rollup/rollup-win32-arm64-msvc" "4.34.8" - "@rollup/rollup-win32-ia32-msvc" "4.34.8" - "@rollup/rollup-win32-x64-msvc" "4.34.8" + "@rollup/rollup-android-arm-eabi" "4.34.6" + "@rollup/rollup-android-arm64" "4.34.6" + "@rollup/rollup-darwin-arm64" "4.34.6" + "@rollup/rollup-darwin-x64" "4.34.6" + "@rollup/rollup-freebsd-arm64" "4.34.6" + "@rollup/rollup-freebsd-x64" "4.34.6" + "@rollup/rollup-linux-arm-gnueabihf" "4.34.6" + "@rollup/rollup-linux-arm-musleabihf" "4.34.6" + "@rollup/rollup-linux-arm64-gnu" "4.34.6" + "@rollup/rollup-linux-arm64-musl" "4.34.6" + "@rollup/rollup-linux-loongarch64-gnu" "4.34.6" + "@rollup/rollup-linux-powerpc64le-gnu" "4.34.6" + "@rollup/rollup-linux-riscv64-gnu" "4.34.6" + "@rollup/rollup-linux-s390x-gnu" "4.34.6" + "@rollup/rollup-linux-x64-gnu" "4.34.6" + "@rollup/rollup-linux-x64-musl" "4.34.6" + "@rollup/rollup-win32-arm64-msvc" "4.34.6" + "@rollup/rollup-win32-ia32-msvc" "4.34.6" + "@rollup/rollup-win32-x64-msvc" "4.34.6" fsevents "~2.3.2" shiki@^2.1.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/shiki/-/shiki-2.5.0.tgz#09d01ebf3b0b06580431ce3ddc023320442cf223" - integrity sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ== + version "2.3.2" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-2.3.2.tgz#d13bae8403c8aec11907185b4d96746b317cf539" + integrity sha512-UZhz/gsUz7DHFbQBOJP7eXqvKyYvMGramxQiSDc83M/7OkWm6OdVHAReEc3vMLh6L6TRhgL9dvhXz9XDkCDaaw== dependencies: - "@shikijs/core" "2.5.0" - "@shikijs/engine-javascript" "2.5.0" - "@shikijs/engine-oniguruma" "2.5.0" - "@shikijs/langs" "2.5.0" - "@shikijs/themes" "2.5.0" - "@shikijs/types" "2.5.0" - "@shikijs/vscode-textmate" "^10.0.2" + "@shikijs/core" "2.3.2" + "@shikijs/engine-javascript" "2.3.2" + "@shikijs/engine-oniguruma" "2.3.2" + "@shikijs/langs" "2.3.2" + "@shikijs/themes" "2.3.2" + "@shikijs/types" "2.3.2" + "@shikijs/vscode-textmate" "^10.0.1" "@types/hast" "^3.0.4" source-map-js@^1.2.0, source-map-js@^1.2.1: