1 Commits

Author SHA1 Message Date
Der_Googler
c5ed6e1e8c manager: Update WebUI X to the latest version (#345)
* manager: bump WebUI X

* manager: forgot to add interface name back

* manager: use compose set content for webuix

* manager: fix :jna library and reflections in prod
2025-08-21 07:21:43 +08:00
474 changed files with 27073 additions and 31496 deletions

137
.github/workflows/avd-kernel.yml vendored Normal file
View File

@@ -0,0 +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 }}"

View File

@@ -1,20 +0,0 @@
name: Build Android GKI Image
on:
workflow_call:
jobs:
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-a16-kernel:
uses: ./.github/workflows/build-kernel-a16.yml
secrets: inherit

View File

@@ -1,111 +0,0 @@
name: Build Kernel - Android 12
on:
# push:
# branches: ["main", "ci", "checkci"]
# paths:
# - ".github/workflows/deps/gki/build-kernel-a12.yml"
# - ".github/workflows/deps/gki/gki-kernel.yml"
# - ".github/scripts/build_a12.sh"
# - "kernel/**"
workflow_call:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
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
- sub_level: 233
os_patch_level: 2025-02
- sub_level: 236
os_patch_level: 2025-05
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"
debug: ${{ inputs.debug || false }}
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: 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

View File

@@ -1,146 +0,0 @@
name: Build Kernel - Android 13
on:
# push:
# branches: ["main", "ci", "checkci"]
# paths:
# - ".github/workflows/deps/gki/build-kernel-a13.yml"
# - ".github/workflows/deps/gki/gki-kernel.yml"
# - ".github/scripts/build_a13.sh"
# - "kernel/**"
workflow_call:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
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.10"
sub_level: 228
os_patch_level: 2025-01
- version: "5.10"
sub_level: 234
os_patch_level: 2025-03
- 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
- version: "5.15"
sub_level: 178
os_patch_level: 2024-11
- version: "5.15"
sub_level: 170
os_patch_level: 2025-01
- version: "5.15"
sub_level: 178
os_patch_level: 2025-03
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 }}
debug: ${{ inputs.debug || false }}
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: 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

View File

@@ -1,158 +0,0 @@
name: Build Kernel - Android 14
on:
# push:
# branches: ["main", "ci", "checkci"]
# paths:
# - ".github/workflows/deps/gki/build-kernel-a14.yml"
# - ".github/workflows/deps/gki/gki-kernel.yml"
# - ".github/scripts/build_a13.sh"
# - "kernel/**"
workflow_call:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
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: "5.15"
sub_level: 170
os_patch_level: 2025-01
- version: "5.15"
sub_level: 178
os_patch_level: 2025-03
- 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
- version: "6.1"
sub_level: 118
os_patch_level: 2025-01
- version: "6.1"
sub_level: 128
os_patch_level: 2025-03
- version: "6.1"
sub_level: 134
os_patch_level: 2025-05
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 }}
debug: ${{ inputs.debug || false }}
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: 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

View File

@@ -1,131 +0,0 @@
name: Build Kernel - Android 15
on:
# push:
# branches: ["main", "ci", "checkci"]
# paths:
# - ".github/workflows/deps/gki/build-kernel-a15.yml"
# - ".github/workflows/deps/gki/gki-kernel.yml"
# - ".github/scripts/build_a13.sh"
# - "kernel/**"
workflow_call:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
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
- version: "6.6"
sub_level: 58
os_patch_level: 2025-01
- version: "6.6"
sub_level: 66
os_patch_level: 2025-02
- version: "6.6"
sub_level: 77
os_patch_level: 2025-03
- version: "6.6"
sub_level: 82
os_patch_level: 2025-04
- version: "6.6"
sub_level: 87
os_patch_level: 2025-05
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 }}
debug: ${{ inputs.debug || false }}
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: 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

View File

@@ -1,104 +0,0 @@
name: Build Kernel - Android 16
on:
# push:
# branches: ["main", "ci", "checkci"]
# paths:
# - ".github/workflows/deps/gki/build-kernel-a16.yml"
# - ".github/workflows/deps/gki/gki-kernel.yml"
# - ".github/scripts/build_a13.sh"
# - "kernel/**"
workflow_call:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
debug:
description: 'Build debug kernel'
required: false
type: boolean
default: false
jobs:
build-kernel:
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
strategy:
matrix:
include:
- version: "6.12"
sub_level: 38
os_patch_level: 2025-08
uses: ./.github/workflows/gki-kernel.yml
secrets: inherit
with:
version: android16-${{ matrix.version }}
version_name: android16-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android16-${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}
debug: ${{ inputs.debug || false }}
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@v6
- uses: actions/checkout@v5
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: 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@v5
with:
name: boot-images-android16
path: Image-android16*/*.img.gz

74
.github/workflows/build-lkm-local.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: Build LKM for KernelSU Local
on:
workflow_call:
inputs:
upload:
required: true
type: boolean
default: true
description: "Whether to upload to branch"
secrets:
# username:github_pat
TOKEN:
required: true
workflow_dispatch:
inputs:
upload:
required: true
type: boolean
default: true
description: "Whether to upload to branch"
jobs:
build-lkm:
strategy:
matrix:
include:
- version: "android12-5.10"
sub_level: 236
os_patch_level: 2025-05
- version: "android13-5.10"
sub_level: 234
os_patch_level: 2025-03
- version: "android13-5.15"
sub_level: 178
os_patch_level: 2025-03
- version: "android14-5.15"
sub_level: 178
os_patch_level: 2025-03
- version: "android14-6.1"
sub_level: 134
os_patch_level: 2025-05
- version: "android15-6.6"
sub_level: 87
os_patch_level: 2025-05
# uses: ./.github/workflows/gki-kernel-mock.yml when debugging
uses: ./.github/workflows/gki-kernel-local.yml
with:
version: ${{ matrix.version }}
version_name: ${{ matrix.version }}.${{ matrix.sub_level }}
tag: ${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
build_lkm: true
push-to-branch:
needs: [build-lkm]
runs-on: self-hosted
if: ${{ 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 lkm
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 LKM from ${{ github.sha }}" -m "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
git push --force --set-upstream origin lkm

View File

@@ -1,21 +1,74 @@
name: Build LKM for KernelSU
on:
workflow_call:
inputs:
upload:
required: true
type: boolean
default: true
description: "Whether to upload to branch"
secrets:
# username:github_pat
TOKEN:
required: true
workflow_dispatch:
inputs:
upload:
required: true
type: boolean
default: true
description: "Whether to upload to branch"
jobs:
build-lkm:
strategy:
fail-fast: false
matrix:
kmi:
- android12-5.10
- android13-5.10
- android13-5.15
- android14-5.15
- android14-6.1
- android15-6.6
- android16-6.12
uses: ./.github/workflows/ddk-lkm.yml
include:
- version: "android12-5.10"
sub_level: 237
os_patch_level: 2025-06
- version: "android13-5.10"
sub_level: 236
os_patch_level: 2025-05
- version: "android13-5.15"
sub_level: 180
os_patch_level: 2025-05
- version: "android14-5.15"
sub_level: 180
os_patch_level: 2025-05
- version: "android14-6.1"
sub_level: 138
os_patch_level: 2025-06
- version: "android15-6.6"
sub_level: 89
os_patch_level: 2025-06
# uses: ./.github/workflows/gki-kernel-mock.yml when debugging
uses: ./.github/workflows/gki-kernel.yml
with:
kmi: ${{ matrix.kmi }}
ddk_release: '20251104'
version: ${{ matrix.version }}
version_name: ${{ matrix.version }}.${{ matrix.sub_level }}
tag: ${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
build_lkm: true
push-to-branch:
needs: [build-lkm]
runs-on: ubuntu-latest
if: ${{ 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 lkm
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 LKM from ${{ github.sha }}" -m "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
git push --force --set-upstream origin lkm

View File

@@ -0,0 +1,252 @@
name: Build Manager Manual
on:
workflow_dispatch:
inputs:
build_lkm:
required: true
type: choice
default: "auto"
options:
- "true"
- "false"
- "auto"
description: "Whether to build lkm"
upload_lkm:
required: true
type: boolean
default: true
description: "Whether to upload lkm"
jobs:
check-build-lkm:
runs-on: self-hosted
outputs:
build_lkm: ${{ steps.check-build.outputs.build_lkm }}
upload_lkm: ${{ steps.check-build.outputs.upload_lkm }}
steps:
- name: check build
id: check-build
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${{ inputs.build_lkm }}" != "auto" ]; then
kernel_changed="${{ inputs.build_lkm }}"
else
kernel_changed=true
mkdir tmp
cd tmp
git config --global init.defaultBranch bot
git config --global user.name 'Bot'
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 }}"
git fetch origin $CURRENT_COMMIT --depth=1
git fetch origin lkm --depth=1
LKM_COMMIT="$(git log --format=%B -n 1 origin/lkm | head -n 1)"
LKM_COMMIT="${LKM_COMMIT#Upload LKM from }"
LKM_COMMIT=$(echo "$LKM_COMMIT" | tr -d '[:space:]')
echo "LKM_COMMIT=$LKM_COMMIT"
git fetch origin "$LKM_COMMIT" --depth=1
git diff --quiet "$LKM_COMMIT" "$CURRENT_COMMIT" -- kernel :!kernel/setup.sh .github/workflows/build-lkm-local.yml .github/workflows/build-kernel-*.yml && kernel_changed=false
cd ..
rm -rf tmp
fi
if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/main' ]; then
need_upload=true
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
need_upload="${{ inputs.upload_lkm }}"
else
need_upload=false
fi
echo "kernel changed: $kernel_changed"
echo "need upload: $need_upload"
echo "build_lkm=$kernel_changed" >> "$GITHUB_OUTPUT"
echo "upload_lkm=$need_upload" >> "$GITHUB_OUTPUT"
build-lkm:
needs: check-build-lkm
uses: ./.github/workflows/build-lkm-local.yml
if: ${{ needs.check-build-lkm.outputs.build_lkm == 'true' }}
with:
upload: ${{ needs.check-build-lkm.outputs.upload_lkm == 'true' }}
secrets: inherit
build-susfs:
if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ]
strategy:
matrix:
include:
- target: aarch64-linux-android
os: ubuntu-latest
uses: ./.github/workflows/susfs.yml
with:
target: ${{ matrix.target }}
os: ${{ matrix.os }}
build-kpmmgr:
if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ]
strategy:
matrix:
include:
- target: aarch64-linux-android
os: ubuntu-latest
uses: ./.github/workflows/kpmmgr.yml
with:
target: ${{ matrix.target }}
os: ${{ matrix.os }}
build-ksud:
if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ]
strategy:
matrix:
include:
- target: aarch64-linux-android
os: ubuntu-latest
- target: x86_64-linux-android
os: ubuntu-latest
- target: armv7-linux-androideabi
os: ubuntu-latest
uses: ./.github/workflows/ksud.yml
with:
target: ${{ matrix.target }}
os: ${{ matrix.os }}
pack_lkm: true
pull_lkm: ${{ needs.check-build-lkm.outputs.build_lkm != 'true' }}
build-manager:
if: ${{ always() }}
needs: build-ksud
runs-on: self-hosted
defaults:
run:
working-directory: ./manager
steps:
- name: Checkout
uses: actions/checkout@v4
with:
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: Write key
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
{
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}'
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}'
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}'
echo KEYSTORE_FILE='key.jks'
} >> gradle.properties
echo "${{ secrets.KEYSTORE }}" | base64 -d > key.jks
fi
- name: Download arm64 susfs
uses: actions/download-artifact@v4
with:
name: susfs-aarch64-linux-android
path: .
- name: Download arm64 kpmmgr
uses: actions/download-artifact@v4
with:
name: kpmmgr-aarch64-linux-android
path: .
- name: Download arm64 ksud
uses: actions/download-artifact@v4
with:
name: ksud-aarch64-linux-android
path: .
- name: Download x86_64 ksud
uses: actions/download-artifact@v4
with:
name: ksud-x86_64-linux-android
path: .
- name: Download arm ksud
uses: actions/download-artifact@v4
with:
name: ksud-armv7-linux-androideabi
path: .
- name: Copy ksud to app jniLibs
run: |
mkdir -p app/src/main/jniLibs/arm64-v8a
mkdir -p app/src/main/jniLibs/x86_64
mkdir -p app/src/main/jniLibs/armeabi-v7a
cp -f ../aarch64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozako.so
cp -f ../x86_64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/x86_64/libzakozako.so
cp -f ../armv7-linux-androideabi/release/zakozako ../manager/app/src/main/jniLibs/armeabi-v7a/libzakozako.so
- name: Copy kpmmgr to app jniLibs
run: |
mkdir -p app/src/main/jniLibs/arm64-v8a
cp -f ../arm64-v8a/kpmmgr ../manager/app/src/main/jniLibs/arm64-v8a/libkpmmgr.so
- name: Copy susfs to app jniLibs
run: |
mkdir -p app/src/main/jniLibs/arm64-v8a
cp -f ../arm64-v8a/zakozakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozakozako.so
- name: Build with Gradle
run: |
export ANDROID_HOME=/root/.android/sdk
export PATH=$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$PATH
{
echo 'org.gradle.parallel=true'
echo 'org.gradle.vfs.watch=true'
echo 'org.gradle.jvmargs=-Xmx2048m'
echo 'android.native.buildOutput=verbose'
} >> 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' }}
with:
name: manager
path: manager/app/build/outputs/apk/release/*.apk
- name: Upload mappings
uses: actions/upload-artifact@v4
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
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
uses: actions/cache@v4
with:
path: scripts/ksubot.session
key: ${{ runner.os }}-bot-session
- name: Upload to telegram
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
env:
CHAT_ID: ${{ vars.CHAT_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
MESSAGE_THREAD_ID: ${{ vars.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 }}
TITLE: Manager
run: |
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
export VERSION=$(git rev-list --count HEAD)
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
python3 $GITHUB_WORKSPACE/scripts/ksubot.py $APK
fi

View File

@@ -2,42 +2,116 @@ name: Build Manager
on:
push:
branches: [ "main", "dev", "ci" ]
branches: [ "main", "ci" ]
paths:
- '.github/workflows/build-manager.yml'
- '.github/workflows/build-lkm.yml'
- 'manager/**'
- 'kernel/**'
- 'userspace/ksud/**'
- 'userspace/user_scanner/**'
- 'userspace/susfs/**'
- 'userspace/kpmmgr/**'
pull_request:
branches: [ "main", "dev" ]
branches: [ "main" ]
paths:
- '.github/workflows/build-manager.yml'
- '.github/workflows/build-lkm.yml'
- 'manager/**'
- 'kernel/**'
- 'userspace/ksud/**'
workflow_call:
workflow_dispatch:
inputs:
build_lkm:
required: true
type: choice
default: "auto"
options:
- "true"
- "false"
- "auto"
description: "Whether to build lkm"
upload_lkm:
required: true
type: boolean
default: true
description: "Whether to upload lkm"
jobs:
check-build-lkm:
runs-on: ubuntu-latest
outputs:
build_lkm: ${{ steps.check-build.outputs.build_lkm }}
upload_lkm: ${{ steps.check-build.outputs.upload_lkm }}
steps:
- name: check build
id: check-build
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${{ inputs.build_lkm }}" != "auto" ]; then
kernel_changed="${{ inputs.build_lkm }}"
else
kernel_changed=true
mkdir tmp
cd tmp
git config --global init.defaultBranch bot
git config --global user.name 'Bot'
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 }}"
git fetch origin $CURRENT_COMMIT --depth=1
git fetch origin lkm --depth=1
LKM_COMMIT="$(git log --format=%B -n 1 origin/lkm | head -n 1)"
LKM_COMMIT="${LKM_COMMIT#Upload LKM from }"
LKM_COMMIT=$(echo "$LKM_COMMIT" | tr -d '[:space:]')
echo "LKM_COMMIT=$LKM_COMMIT"
git fetch origin "$LKM_COMMIT" --depth=1
git diff --quiet "$LKM_COMMIT" "$CURRENT_COMMIT" -- kernel :!kernel/setup.sh .github/workflows/build-lkm.yml .github/workflows/build-kernel-*.yml && kernel_changed=false
cd ..
rm -rf tmp
fi
if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/main' ]; then
need_upload=true
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
need_upload="${{ inputs.upload_lkm }}"
else
need_upload=false
fi
echo "kernel changed: $kernel_changed"
echo "need upload: $need_upload"
echo "build_lkm=$kernel_changed" >> "$GITHUB_OUTPUT"
echo "upload_lkm=$need_upload" >> "$GITHUB_OUTPUT"
build-lkm:
needs: check-build-lkm
uses: ./.github/workflows/build-lkm.yml
if: ${{ needs.check-build-lkm.outputs.build_lkm == 'true' }}
with:
upload: ${{ needs.check-build-lkm.outputs.upload_lkm == 'true' }}
secrets: inherit
build-user_scanner:
needs: build-lkm
build-susfs:
if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ]
strategy:
matrix:
include:
- target: All-linux-android
- target: aarch64-linux-android
os: ubuntu-latest
uses: ./.github/workflows/user_scanner.yml
uses: ./.github/workflows/susfs.yml
with:
target: ${{ matrix.target }}
os: ${{ matrix.os }}
build-kpmmgr:
if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ]
strategy:
matrix:
include:
- target: aarch64-linux-android
os: ubuntu-latest
uses: ./.github/workflows/kpmmgr.yml
with:
target: ${{ matrix.target }}
os: ${{ matrix.os }}
build-ksud:
needs: build-lkm
if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ]
strategy:
matrix:
include:
@@ -51,13 +125,13 @@ jobs:
with:
target: ${{ matrix.target }}
os: ${{ matrix.os }}
pack_lkm: true
pull_lkm: ${{ needs.check-build-lkm.outputs.build_lkm != 'true' }}
build-manager:
if: ${{ always() }}
needs: build-ksud
runs-on: ubuntu-latest
strategy:
matrix:
spoofed: ["true","false"]
defaults:
run:
working-directory: ./manager
@@ -77,27 +151,8 @@ jobs:
echo "UPLOAD=false" >> $GITHUB_OUTPUT
fi
- name: Determine manager variant for telegram bot
id: determine
run: |
if [ "${{ matrix.spoofed }}" == "true" ]; then
echo "title=Spoofed-Manager" >> $GITHUB_OUTPUT
# maybe need a new var
echo "topicid=${{ vars.MESSAGE_SPOOFED_THREAD_ID }}" >> $GITHUB_OUTPUT
else
echo "title=Manager" >> $GITHUB_OUTPUT
echo "topicid=${{ vars.MESSAGE_THREAD_ID }}" >> $GITHUB_OUTPUT
fi
- name: Run randomizer
if: ${{ matrix.spoofed == 'true' }}
run: |
chmod +x randomizer
./randomizer
- name: Write key
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' )) || 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
{
@@ -121,10 +176,16 @@ jobs:
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Download all userscanner artifacts
- name: Download arm64 susfs
uses: actions/download-artifact@v4
with:
name: userscanner-all-linux-android
name: susfs-aarch64-linux-android
path: .
- name: Download arm64 kpmmgr
uses: actions/download-artifact@v4
with:
name: kpmmgr-aarch64-linux-android
path: .
- name: Download arm64 ksud
@@ -150,47 +211,62 @@ jobs:
mkdir -p app/src/main/jniLibs/arm64-v8a
mkdir -p app/src/main/jniLibs/x86_64
mkdir -p app/src/main/jniLibs/armeabi-v7a
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 ../armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud.so
cp -f ../aarch64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozako.so
cp -f ../x86_64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/x86_64/libzakozako.so
cp -f ../armv7-linux-androideabi/release/zakozako ../manager/app/src/main/jniLibs/armeabi-v7a/libzakozako.so
- name: Copy user_scanner to app jniLibs
- name: Copy kpmmgr to app jniLibs
run: |
mkdir -p app/src/main/jniLibs/arm64-v8a
mkdir -p app/src/main/jniLibs/x86_64
mkdir -p app/src/main/jniLibs/armeabi-v7a
cp -f ../arm64-v8a/uid_scanner ../manager/app/src/main/jniLibs/arm64-v8a/libuid_scanner.so
cp -f ../x86_64/uid_scanner ../manager/app/src/main/jniLibs/x86_64/libuid_scanner.so
cp -f ../armeabi-v7a/uid_scanner ../manager/app/src/main/jniLibs/armeabi-v7a/libuid_scanner.so
cp -f ../arm64-v8a/kpmmgr ../manager/app/src/main/jniLibs/arm64-v8a/libkpmmgr.so
- name: Copy susfs to app jniLibs
run: |
mkdir -p app/src/main/jniLibs/arm64-v8a
cp -f ../arm64-v8a/zakozakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozakozako.so
- name: Build with Gradle
run: ./gradlew clean assembleRelease
run: |
{
echo 'org.gradle.parallel=true'
echo 'org.gradle.vfs.watch=true'
echo 'org.gradle.jvmargs=-Xmx2048m'
echo 'android.native.buildOutput=verbose'
} >> 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 == 'refs/heads/dev' )) || github.ref_type == 'tag' }}
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
with:
name: ${{ steps.determine.outputs.title }}
name: manager
path: manager/app/build/outputs/apk/release/*.apk
- name: Upload mappings
uses: actions/upload-artifact@v4
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' )) || github.ref_type == 'tag' }}
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
with:
name: "${{ steps.determine.outputs.title }}-mappings"
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
uses: actions/cache@v4
with:
path: scripts/ksubot.session
key: ${{ runner.os }}-bot-session
- name: Upload to telegram
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
env:
CHAT_ID: ${{ vars.CHAT_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
MESSAGE_THREAD_ID: ${{ steps.determine.outputs.topicid }}
MESSAGE_THREAD_ID: ${{ vars.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 }}
TITLE: ${{ steps.determine.outputs.title }}
BRANCH: ${{ github.ref_name }}
TITLE: Manager
run: |
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
export VERSION=$(git rev-list --count HEAD)

View File

@@ -1,53 +0,0 @@
name: Build KernelSU Kernel Module
on:
workflow_call:
inputs:
kmi:
description: 'KMI version'
required: true
type: string
ddk_release:
description: 'DDK release version'
required: false
default: '20251104'
type: string
jobs:
build-kernelsu-ko:
name: Build kernelsu.ko for ${{ inputs.kmi }}
runs-on: ubuntu-latest
container:
image: ghcr.io/ylarod/ddk:${{ inputs.kmi }}-${{ inputs.ddk_release }}
options: --privileged
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Build kernelsu.ko
run: |
git config --global --add safe.directory /__w/SukiSU-Ultra/SukiSU-Ultra
cd kernel
echo "=== Building kernelsu.ko for KMI: ${{ inputs.kmi }} ==="
CONFIG_KSU=m CONFIG_KSU_MANUAL_SU=y make
echo "=== Build completed ==="
# Create output directory in GitHub workspace
mkdir -p /github/workspace/out
# Copy with KMI-specific naming
OUTPUT_NAME="${{ inputs.kmi }}_kernelsu.ko"
cp kernelsu.ko "/github/workspace/out/$OUTPUT_NAME"
echo "Copied to: /github/workspace/out/$OUTPUT_NAME"
ls -la "/github/workspace/out/$OUTPUT_NAME"
echo "Size: $(du -h "/github/workspace/out/$OUTPUT_NAME" | cut -f1)"
llvm-strip -d "/github/workspace/out/$OUTPUT_NAME"
echo "Size after stripping: $(du -h "/github/workspace/out/$OUTPUT_NAME" | cut -f1)"
- name: Upload kernelsu.ko artifact
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.kmi }}-lkm
path: /github/workspace/out/${{ inputs.kmi }}_kernelsu.ko

252
.github/workflows/gki-kernel-local.yml vendored Normal file
View File

@@ -0,0 +1,252 @@
name: GKI Kernel Build Local
on:
workflow_call:
inputs:
version:
required: true
type: string
description: >
Output directory of gki,
for example: android12-5.10
version_name:
required: true
type: string
description: >
With SUBLEVEL of kernel,
for example: android12-5.10.66
tag:
required: true
type: string
description: >
Part of branch name of common kernel manifest,
for example: android12-5.10-2021-11
os_patch_level:
required: false
type: string
description: >
Patch level of common kernel manifest,
for example: 2021-11
default: 2022-05
patch_path:
required: false
type: string
description: >
Directory name of .github/patches/<patch_path>
for example: 5.10
use_cache:
required: false
type: boolean
default: true
embed_ksud:
required: false
type: string
default: ksud-aarch64-linux-android
description: >
Artifact name of prebuilt ksud to be embedded
for example: ksud-aarch64-linux-android
debug:
required: false
type: boolean
default: false
build_lkm:
required: false
type: boolean
default: false
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: self-hosted
env:
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
CCACHE_NOHASHDIR: "true"
CCACHE_HARDLINK: "true"
steps:
- 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
export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo'
mkdir android-kernel && cd android-kernel
repo init --depth=1 --u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/kernel/manifest -b common-${{ inputs.tag }} --repo-rev=v2.35
REMOTE_BRANCH=$(git ls-remote https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/kernel/common ${{ inputs.tag }})
DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml
if grep -q deprecated <<< $REMOTE_BRANCH; then
echo "Found deprecated branch: ${{ inputs.tag }}"
sed -i 's/"${{ inputs.tag }}"/"deprecated\/${{ inputs.tag }}"/g' $DEFAULT_MANIFEST_PATH
cat $DEFAULT_MANIFEST_PATH
fi
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 Compilation Patches"
if [ ! -e build/build.sh ]; then
GLIBC_VERSION=$(ldd --version 2>/dev/null | head -n 1 | awk '{print $NF}')
echo "GLIBC_VERSION: $GLIBC_VERSION"
if [ "$(printf '%s\n' "2.38" "$GLIBC_VERSION" | sort -V | head -n1)" = "2.38" ]; then
echo "Patching resolve_btfids/Makefile"
cd $GKI_ROOT/common/ && sed -i '/\$(Q)\$(MAKE) -C \$(SUBCMD_SRC) OUTPUT=\$(abspath \$(dir \$@))\/ \$(abspath \$@)/s//$(Q)$(MAKE) -C $(SUBCMD_SRC) EXTRA_CFLAGS="$(CFLAGS)" OUTPUT=$(abspath $(dir $@))\/ $(abspath $@)/' tools/bpf/resolve_btfids/Makefile || echo "No patch needed."
fi
fi
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."
- name: Symbol magic
run: |
echo "[+] Export all symbol from abi_gki_aarch64.xml"
COMMON_ROOT=$GITHUB_WORKSPACE/android-kernel/common
KSU_ROOT=$GITHUB_WORKSPACE/KernelSU
ABI_XML=$COMMON_ROOT/android/abi_gki_aarch64.xml
SYMBOL_LIST=$COMMON_ROOT/android/abi_gki_aarch64
# python3 $KSU_ROOT/scripts/abi_gki_all.py $ABI_XML > $SYMBOL_LIST
echo "[+] Add KernelSU symbols"
cat $KSU_ROOT/kernel/export_symbol.txt | awk '{sub("[ \t]+","");print " "$0}' >> $SYMBOL_LIST
- name: Setup ccache
if: inputs.use_cache == true
uses: hendrikmuhs/ccache-action@v1
with:
key: gki-kernel-aarch64-${{ inputs.version_name }}
max-size: 2G
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
- name: Setup for LKM
if: ${{ inputs.build_lkm == true }}
working-directory: android-kernel
run: |
pip install ast-grep-cli
sudo apt-get install llvm-15 -y
ast-grep -U -p '$$$ check_exports($$$) {$$$}' -r '' common/scripts/mod/modpost.c
ast-grep -U -p 'check_exports($$$);' -r '' common/scripts/mod/modpost.c
sed -i '/config KSU/,/help/{s/default y/default m/}' common/drivers/kernelsu/Kconfig
echo "drivers/kernelsu/kernelsu.ko" >> common/android/gki_aarch64_modules
# bazel build, android14-5.15, android14-6.1 use bazel
if [ ! -e build/build.sh ]; then
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
if [ -e common/modules.bzl ]; then
sed -i 's/_COMMON_GKI_MODULES_LIST = \[/_COMMON_GKI_MODULES_LIST = \[ "drivers\/kernelsu\/kernelsu.ko",/g' common/modules.bzl
fi
else
TARGET_FILE="build/kernel/build.sh"
if [ ! -e "$TARGET_FILE" ]; then
TARGET_FILE="build/build.sh"
fi
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' $TARGET_FILE || echo "No unknown symbol in $TARGET_FILE"
sed -i 's/if ! diff -u "\${KERNEL_DIR}\/\${MODULES_ORDER}" "\${OUT_DIR}\/modules\.order"; then/if false; then/g' $TARGET_FILE
sed -i 's@${ROOT_DIR}/build/abi/compare_to_symbol_list@echo@g' $TARGET_FILE
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
fi
- name: Make working directory clean to avoid dirty
working-directory: android-kernel
run: |
if [ -e common/BUILD.bazel ]; then
sed -i '/^[[:space:]]*"protected_exports_list"[[:space:]]*:[[:space:]]*"android\/abi_gki_protected_exports_aarch64",$/d' common/BUILD.bazel
fi
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/LKM
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
if [ -e build/build.sh ]; then
LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh CC="/usr/bin/ccache clang"
else
tools/bazel run --disk_cache=/home/runner/.cache/bazel --config=fast --config=stamp --lto=thin //common:kernel_aarch64_dist -- --dist_dir=dist
fi
- name: Prepare artifacts
id: prepareArtifacts
run: |
OUTDIR=android-kernel/out/${{ inputs.version }}/dist
if [ ! -e $OUTDIR ]; then
OUTDIR=android-kernel/dist
fi
mkdir output
if [ "${{ inputs.build_lkm}}" = "true" ]; then
llvm-strip-15 -d $OUTDIR/kernelsu.ko
mv $OUTDIR/kernelsu.ko ./output/${{ inputs.version }}_kernelsu.ko
else
cp $OUTDIR/Image ./output/
cp $OUTDIR/Image.lz4 ./output/
git clone https://github.com/Kernel-SU/AnyKernel3
rm -rf ./AnyKernel3/.git
cp $OUTDIR/Image ./AnyKernel3/
fi
- name: Upload Image and Image.gz
uses: actions/upload-artifact@v4
if: ${{ inputs.build_lkm == false }}
with:
name: Image-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
path: ./output/*
- name: Upload AnyKernel3
if: ${{ inputs.build_lkm == false }}
uses: actions/upload-artifact@v4
with:
name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
path: ./AnyKernel3/*
- name: Upload LKM
uses: actions/upload-artifact@v4
if: ${{ inputs.build_lkm == true }}
with:
name: ${{ inputs.version }}-lkm
path: ./output/*_kernelsu.ko

79
.github/workflows/gki-kernel-mock.yml vendored Normal file
View File

@@ -0,0 +1,79 @@
name: GKI Kernel Build
on:
workflow_call:
inputs:
version:
required: true
type: string
description: >
Output directory of gki,
for example: android12-5.10
version_name:
required: true
type: string
description: >
With SUBLEVEL of kernel,
for example: android12-5.10.66
tag:
required: true
type: string
description: >
Part of branch name of common kernel manifest,
for example: android12-5.10-2021-11
os_patch_level:
required: false
type: string
description: >
Patch level of common kernel manifest,
for example: 2021-11
default: 2022-05
patch_path:
required: false
type: string
description: >
Directory name of .github/patches/<patch_path>
for example: 5.10
use_cache:
required: false
type: boolean
default: true
embed_ksud:
required: false
type: string
default: ksud-aarch64-linux-android
description: >
Artifact name of prebuilt ksud to be embedded
for example: ksud-aarch64-linux-android
debug:
required: false
type: boolean
default: false
build_lkm:
required: false
type: boolean
default: false
secrets:
BOOT_SIGN_KEY:
required: false
CHAT_ID:
required: false
BOT_TOKEN:
required: false
MESSAGE_THREAD_ID:
required: false
jobs:
mock_build:
name: Mock build ${{ inputs.version_name }}
runs-on: ubuntu-latest
steps:
- name: Create mocking ko
run: |
echo "${{ inputs.version }}_kernelsu.ko" > ${{ inputs.version }}_kernelsu.ko
- name: Upload LKM
uses: actions/upload-artifact@v4
if: ${{ inputs.build_lkm == true }}
with:
name: ${{ inputs.version }}-lkm
path: ./*_kernelsu.ko

View File

@@ -77,10 +77,10 @@ jobs:
with:
root-reserve-mb: 8192
temp-reserve-mb: 2048
remove-dotnet: "true"
remove-android: "true"
remove-haskell: "true"
remove-codeql: "true"
remove-dotnet: 'true'
remove-android: 'true'
remove-haskell: 'true'
remove-codeql: 'true'
- uses: actions/checkout@v4
with:
@@ -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
@@ -195,35 +195,12 @@ jobs:
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
fi
- name: Append ashmem exports if missing
if: startsWith(inputs.version, 'android16-6.12')
working-directory: android-kernel
run: |
FILE=common/drivers/staging/android/ashmem.c
if [[ -f "$FILE" ]] && ! grep -q 'is_ashmem_file' "$FILE"; then
cat >>"$FILE" <<'EOF'
bool is_ashmem_file(struct file *file) { return false; }
int ashmem_area_name(struct file *file, char *name) { return 0; }
long ashmem_area_size(struct file *file) { return 0; }
struct file *ashmem_area_vmfile(struct file *file) { return NULL; }
EXPORT_SYMBOL_GPL(is_ashmem_file);
EXPORT_SYMBOL_GPL(ashmem_area_name);
EXPORT_SYMBOL_GPL(ashmem_area_size);
EXPORT_SYMBOL_GPL(ashmem_area_vmfile);
EOF
fi
sed -i -E 's/\$\(CONFIG_ANDROID_BINDER_IPC_RUST\)/m/g' common/drivers/android/Makefile
- name: Make working directory clean to avoid dirty
working-directory: android-kernel
run: |
# Fix bazel build error
if [ -f common/BUILD.bazel ]; then
[ -f android/abi_gki_protected_exports_aarch64 ] || sed -i '/^[[:space:]]*"protected_exports_list"[[:space:]]*:[[:space:]]*"android\/abi_gki_protected_exports_aarch64",$/d' common/BUILD.bazel
if [ -e common/BUILD.bazel ]; then
sed -i '/^[[:space:]]*"protected_exports_list"[[:space:]]*:[[:space:]]*"android\/abi_gki_protected_exports_aarch64",$/d' common/BUILD.bazel
fi
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"
@@ -240,11 +217,7 @@ jobs:
if [ -e build/build.sh ]; then
LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh CC="/usr/bin/ccache clang"
else
if [ "${{ inputs.version }}" == "android16-6.12" ]; then
tools/bazel run --disk_cache=/home/runner/.cache/bazel --config=fast --config=stamp --lto=thin //common:kernel_aarch64_dist -- --destdir=dist
else
tools/bazel run --disk_cache=/home/runner/.cache/bazel --config=fast --config=stamp --lto=thin //common:kernel_aarch64_dist -- --dist_dir=dist
fi
tools/bazel run --disk_cache=/home/runner/.cache/bazel --config=fast --config=stamp --lto=thin //common:kernel_aarch64_dist -- --dist_dir=dist
fi
- name: Prepare artifacts

View File

@@ -1,11 +1,11 @@
name: Build user_scanner
name: Build kpmmgr
on:
push:
branches: [ "mian" ]
paths:
- '.github/workflows/user_scanner.yml'
- 'userspace/user_scanner/**'
- '.github/workflows/kpmmgr.yml'
- 'userspace/kpmmgr/**'
workflow_dispatch:
workflow_call:
inputs:
@@ -18,8 +18,8 @@ on:
default: self-hosted
jobs:
build-user_scanner:
name: Build userspace user_scanner
build-susfs:
name: Build userspace kpmmgr
runs-on: ubuntu-latest
steps:
@@ -28,13 +28,13 @@ jobs:
with:
fetch-depth: 0
- name: Build user_scanner
working-directory: ./userspace/user_scanner
- name: Build kpmmgr
working-directory: ./userspace/kpmmgr
run: |
$ANDROID_NDK_HOME/ndk-build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
with:
name: userscanner-all-linux-android
path: ./userspace/user_scanner/libs
name: kpmmgr-aarch64-linux-android
path: ./userspace/kpmmgr/libs

View File

@@ -9,6 +9,10 @@ on:
required: false
type: string
default: ubuntu-latest
pull_lkm:
required: false
type: boolean
default: true
pack_lkm:
required: false
type: boolean
@@ -17,8 +21,6 @@ on:
required: false
type: boolean
default: true
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ${{ inputs.os }}
@@ -27,11 +29,24 @@ jobs:
with:
fetch-depth: 0
- name: Download artifacts
- name: Pull lkms from branch
if: ${{ inputs.pack_lkm && inputs.pull_lkm }}
uses: actions/checkout@v4
with:
ref: lkm
path: lkm
- name: Download lkms from artifacts
if: ${{ inputs.pack_lkm && !inputs.pull_lkm }}
uses: actions/download-artifact@v4
- name: Prepare LKM fies
if: ${{ inputs.pack_lkm }}
- name: Prepare LKM files
if: ${{ inputs.pack_lkm && inputs.pull_lkm }}
run: |
cp lkm/*_kernelsu.ko ./userspace/ksud/bin/aarch64/
- name: Prepare LKM files
if: ${{ inputs.pack_lkm && !inputs.pull_lkm }}
run: |
cp android*-lkm/*_kernelsu.ko ./userspace/ksud/bin/aarch64/
@@ -40,6 +55,10 @@ jobs:
rustup update stable
rustup target add x86_64-apple-darwin
rustup target add aarch64-apple-darwin
- uses: Swatinem/rust-cache@v2
with:
workspaces: userspace/ksud
cache-targets: false
- name: Install cross
run: |
@@ -52,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/zakozako*

View File

@@ -16,7 +16,7 @@ on:
jobs:
shellcheck:
runs-on: ubuntu-latest
runs-on: self-hosted
steps:
- uses: actions/checkout@v4

40
.github/workflows/susfs.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Build susfs
on:
push:
branches: [ "mian" ]
paths:
- '.github/workflows/susfs.yml'
- 'userspace/susfs/**'
workflow_dispatch:
workflow_call:
inputs:
target:
required: true
type: string
os:
required: false
type: string
default: self-hosted
jobs:
build-susfs:
name: Build userspace susfs
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build susfs
working-directory: ./userspace/susfs
run: |
$ANDROID_NDK_HOME/ndk-build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
with:
name: susfs-aarch64-linux-android
path: ./userspace/susfs/libs

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
.idea
CLAUDE.md
.vscode
.DS_Store

89
Website/.gitignore vendored
View File

@@ -1,89 +0,0 @@
node_modules
pnpm-lock.yaml
package-lock.json
yarn.lock
.wrangler
.DS_Store
.vscode
.idea
.vite_opt_cache
# Build artifacts
docs/.vitepress/dist/
docs/.vitepress/cache/
docs/.vitepress/.temp/
dist/
# Generated files
docs/public/sw.js
docs/public/favicon*.png
docs/public/favicon.ico
docs/public/android-chrome-*.png
docs/public/apple-touch-icon*.png
docs/public/safari-pinned-tab.svg
docs/public/browserconfig.xml
docs/public/site.webmanifest
docs/public/manifest.webmanifest
docs/public/mstile-*.png
docs/public/favicons.html
favicon-data.json
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# Dependency directories
.pnpm-store/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Stores VSCode versions used for testing VSCode extensions
.vscode-test

View File

@@ -1,14 +0,0 @@
# Build artifacts
node_modules/
dist/
docs/.vitepress/cache/
docs/.vitepress/dist/
# Logs
*.log
# Lockfiles backup
pnpm-debug.log*
# Generated
.DS_Store

View File

@@ -1,10 +0,0 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"singleQuote": true,
"semi": false,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "always",
"endOfLine": "lf"
}

View File

@@ -1,7 +0,0 @@
# SukiSU-Ultra Documentation
[![Website](https://img.shields.io/website?url=https%3A%2F%2Fsukisu.org&logo=Cloudflare&label=Cloudflare%20Pages&style=flat-square)](https://sukisu.org)
[![VitePress](https://img.shields.io/badge/VitePress-2.0.0--alpha.12-646cff?logo=vite&logoColor=white&style=flat-square)](https://vitepress.dev)
[![License](https://img.shields.io/badge/License-Multiple-blue?style=flat-square)](https://sukisu.org/guide/license)
[![Last Commit](https://img.shields.io/github/last-commit/sukisu-ultra/sukisu-ultra?style=flat-square)](https://github.com/sukisu-ultra/sukisu-ultra/commits/main)
[![GitHub Stars](https://img.shields.io/github/stars/sukisu-ultra/sukisu-ultra?style=flat-square)](https://github.com/sukisu-ultra/sukisu-ultra/stargazers)

View File

@@ -1,18 +0,0 @@
/coverage
/src/client/shared.ts
/src/node/shared.ts
*.log
*.tgz
.DS_Store
.idea
.temp
.vite_opt_cache
.vscode
dist
cache
temp
examples-temp
node_modules
pnpm-global
TODOs.md
*.timestamp-*.mjs

View File

@@ -1,248 +0,0 @@
import { defineConfig } from 'vitepress'
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons'
import {
GitChangelog,
GitChangelogMarkdownSection,
} from '@nolebase/vitepress-plugin-git-changelog/vite'
import footnote from 'markdown-it-footnote'
import mark from 'markdown-it-mark'
import sub from 'markdown-it-sub'
import taskLists from 'markdown-it-task-lists'
export default defineConfig({
title: 'SukiSU-Ultra',
description: 'Next-Generation Android root solution.',
lastUpdated: true,
cleanUrls: true,
metaChunk: true,
// Global performance optimizations
cacheDir: './.vitepress/cache',
ignoreDeadLinks: false,
// Enhanced markdown with performance focus
markdown: {
math: true,
config(md) {
md.use(groupIconMdPlugin)
md.use(footnote)
md.use(mark)
md.use(sub)
md.use(taskLists)
},
linkify: true,
typographer: true,
lineNumbers: true,
image: {
lazyLoading: true,
},
toc: {
level: [1, 2, 3],
},
theme: {
light: 'github-light',
dark: 'github-dark',
},
},
sitemap: {
hostname: 'https://sukisu.org',
transformItems(items) {
return items
.filter((item) => !item.url.includes('404'))
.map((item) => ({
...item,
changefreq:
item.url === '/' ? 'daily' : item.url.includes('/guide/') ? 'weekly' : 'monthly',
priority: item.url === '/' ? 1.0 : item.url.includes('/guide/') ? 0.9 : 0.7,
}))
},
},
// Critical performance transformations
transformPageData(pageData) {
const canonicalUrl = `https://sukisu.org${pageData.relativePath}`
.replace(/index\.md$/, '')
.replace(/\.md$/, '')
pageData.frontmatter.head ??= []
pageData.frontmatter.head.push(
['link', { rel: 'canonical', href: canonicalUrl }],
['meta', { property: 'og:url', content: canonicalUrl }],
['link', { rel: 'preload', href: '/logo.svg', as: 'image' }]
)
return pageData
},
head: [
// Critical resource hints for global performance
['link', { rel: 'dns-prefetch', href: '//github.com' }],
['link', { rel: 'dns-prefetch', href: '//t.me' }],
['link', { rel: 'dns-prefetch', href: '//sukisu.org' }],
// Essential favicon setup - synced from /favicon during build/dev
['link', { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }],
['link', { rel: 'icon', type: 'image/png', sizes: '96x96', href: '/favicon-96x96.png' }],
['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }],
['link', { rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#64edff' }],
// (Removed msapplication meta to avoid referencing non-existent files)
// Web App Manifest
['link', { rel: 'manifest', href: '/site.webmanifest' }],
// Theme and app configuration
['meta', { name: 'theme-color', content: '#64edff' }],
['meta', { name: 'application-name', content: 'SukiSU-Ultra' }],
['meta', { name: 'apple-mobile-web-app-title', content: 'SukiSU-Ultra' }],
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'default' }],
// Viewport and mobile optimization
[
'meta',
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, viewport-fit=cover' },
],
['meta', { name: 'format-detection', content: 'telephone=no' }],
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:site_name', content: 'SukiSU-Ultra' }],
['meta', { property: 'og:url', content: 'https://sukisu.org/' }],
['meta', { property: 'og:locale', content: 'en_US' }],
['meta', { property: 'og:locale:alternate', content: 'zh_CN' }],
// Twitter optimization for global audience
['meta', { property: 'twitter:card', content: 'summary_large_image' }],
['meta', { property: 'twitter:site', content: '@sukisu_ultra' }],
['meta', { property: 'twitter:creator', content: '@sukisu_ultra' }],
// (Removed Twitter image as no PNG social image is provided)
// Additional SEO optimizations
[
'meta',
{
name: 'robots',
content: 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
},
],
['meta', { name: 'bingbot', content: 'index, follow' }],
['meta', { name: 'referrer', content: 'strict-origin-when-cross-origin' }],
// Global SEO optimization
[
'meta',
{
name: 'keywords',
content:
'Android root, KernelSU, SukiSU-Ultra, Android kernel, root management, 安卓 root, カーネル, рут',
},
],
['meta', { name: 'author', content: 'SukiSU-Ultra Team' }],
// Enhanced structured data for global search engines
[
'script',
{ type: 'application/ld+json' },
JSON.stringify({
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'SukiSU-Ultra',
description: 'Next-Generation Android Root Solution',
applicationCategory: 'SystemApplication',
operatingSystem: 'Android',
url: 'https://sukisu.org',
downloadUrl: 'https://github.com/sukisu-ultra/sukisu-ultra/releases',
supportingData: {
'@type': 'DataCatalog',
name: 'Compatibility Database',
},
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
author: {
'@type': 'Organization',
name: 'SukiSU-Ultra Team',
url: 'https://github.com/sukisu-ultra',
},
}),
],
// PWA optimization for global mobile users (manifest declared above)
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' }],
['meta', { name: 'apple-mobile-web-app-title', content: 'SukiSU-Ultra' }],
// Cloudflare Web Analytics
[
'script',
{
defer: '',
src: 'https://static.cloudflareinsights.com/beacon.min.js',
'data-cf-beacon': '{"token": "dcc5feef58bf4c56a170a99f4cec4798"}',
},
],
],
themeConfig: {
logo: { src: '/logo.svg', width: 24, height: 24 },
socialLinks: [
{ icon: 'github', link: 'https://github.com/sukisu-ultra/sukisu-ultra' },
{
icon: {
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-telegram"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 10l-4 4l6 6l4 -16l-18 7l4 2l2 6l3 -4" /></svg>',
},
link: 'https://t.me/sukiksu',
},
],
search: {
provider: 'local',
},
},
rewrites: {
'en/:rest*': ':rest*',
},
locales: {
root: {
label: 'English',
},
zh: {
label: '简体中文',
link: '/zh/',
},
},
vite: {
plugins: [
groupIconVitePlugin({
customIcon: {
bash: '<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"><g fill="none"><rect width="256" height="256" fill="#242938" rx="60"/><path fill="#242938" fill-rule="evenodd" d="m203.819 68.835l-63.14-37.48a23.79 23.79 0 0 0-24.361 0l-63.14 37.48C45.642 73.31 41 81.575 41 90.522v74.961c0 8.945 4.643 17.215 12.18 21.689l63.14 37.473a23.8 23.8 0 0 0 12.179 3.354a23.8 23.8 0 0 0 12.178-3.354l63.14-37.473c7.536-4.474 12.182-12.744 12.182-21.689v-74.96c0-8.948-4.646-17.214-12.18-21.688" clip-rule="evenodd"/><path fill="#fff" fill-rule="evenodd" d="m118.527 220.808l-63.14-37.474c-6.176-3.666-10.013-10.506-10.013-17.852V90.523c0-7.346 3.837-14.186 10.01-17.85l63.143-37.48a19.55 19.55 0 0 1 9.972-2.747c3.495 0 6.943.95 9.973 2.747l63.14 37.48c5.204 3.089 8.714 8.438 9.701 14.437c-2.094-4.469-6.817-5.684-12.32-2.47l-59.734 36.897c-7.448 4.354-12.94 9.24-12.945 18.221v73.604c-.004 5.378 2.168 8.861 5.504 9.871c-1.096.19-2.201.322-3.319.322a19.55 19.55 0 0 1-9.972-2.747m85.292-151.974l-63.14-37.478A23.8 23.8 0 0 0 128.499 28a23.8 23.8 0 0 0-12.181 3.356l-63.14 37.478C45.642 73.308 41 81.576 41 90.524v74.958c0 8.945 4.643 17.215 12.18 21.689l63.14 37.475A23.84 23.84 0 0 0 128.499 228a23.83 23.83 0 0 0 12.178-3.354l63.142-37.475c7.536-4.474 12.18-12.744 12.18-21.689V90.523c0-8.947-4.644-17.215-12.18-21.689" clip-rule="evenodd"/><path fill="#47b353" fill-rule="evenodd" d="m187.267 172.729l-15.722 9.41c-.417.243-.723.516-.726 1.017v4.114c0 .503.338.712.754.467l15.966-9.703c.416-.243.48-.708.483-1.209v-3.629c0-.5-.338-.71-.755-.467" clip-rule="evenodd"/><path fill="#242938" fill-rule="evenodd" d="M153.788 138.098c.509-.258.928.059.935.725l.053 5.439c2.277-.906 4.255-1.148 6.047-.734c.389.104.561.633.402 1.261l-1.197 4.82c-.093.364-.298.732-.545.961a1.3 1.3 0 0 1-.315.234a.7.7 0 0 1-.472.077c-.818-.185-2.763-.61-5.823.94c-3.21 1.625-4.333 4.414-4.311 6.484c.027 2.472 1.295 3.221 5.673 3.296c5.834.097 8.355 2.646 8.416 8.522c.06 5.77-3.02 11.966-7.732 15.763l.104 5.384c.006.648-.415 1.391-.924 1.649l-3.189 1.837c-.511.258-.93-.06-.937-.708l-.055-5.296c-2.731 1.135-5.499 1.409-7.267.699c-.333-.13-.476-.622-.344-1.182l1.156-4.868c.092-.384.295-.768.571-1.012q.147-.142.299-.219c.183-.092.362-.112.514-.055c1.905.642 4.342.342 6.685-.844c2.977-1.506 4.968-4.542 4.937-7.558c-.029-2.737-1.51-3.874-5.113-3.901c-4.586.013-8.861-.891-8.932-7.642c-.057-5.558 2.833-11.342 7.408-14.999l-.057-5.435c-.007-.668.401-1.403.926-1.667z" clip-rule="evenodd"/></g></svg>',
},
}),
GitChangelog({ repoURL: () => 'https://github.com/SukiSU-Ultra/Website' }),
GitChangelogMarkdownSection({
exclude: (id) => id.endsWith('index.md'),
sections: { disableContributors: true },
}),
],
build: {
minify: 'terser',
chunkSizeWarningLimit: 800,
assetsInlineLimit: 8192,
target: 'esnext',
cssCodeSplit: true,
sourcemap: false,
},
server: {
fs: {
allow: ['..'],
},
},
},
})

View File

@@ -1,195 +0,0 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useData } from 'vitepress'
const props = defineProps({
shareText: {
type: String,
default: undefined,
},
copiedText: {
type: String,
default: undefined,
},
includeQuery: {
type: Boolean,
default: false,
},
includeHash: {
type: Boolean,
default: false,
},
copiedTimeout: {
type: Number,
default: 2000,
},
})
defineOptions({ name: 'ArticleShare' })
const copied = ref(false)
const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'
const { theme, lang } = useData()
const defaultShareText = computed(() =>
lang.value?.toLowerCase().startsWith('zh') ? '分享链接' : 'Share link'
)
const defaultCopiedText = computed(() =>
lang.value?.toLowerCase().startsWith('zh') ? '已复制!' : 'Copied!'
)
const defaultCopyFailedText = computed(() =>
lang.value?.toLowerCase().startsWith('zh') ? '复制链接失败:' : 'Failed to copy link:'
)
const i18nShareText = computed(
() => props.shareText ?? (theme.value as any)?.articleShare?.shareText ?? defaultShareText.value
)
const i18nCopiedText = computed(
() =>
props.copiedText ?? (theme.value as any)?.articleShare?.copiedText ?? defaultCopiedText.value
)
const i18nCopyFailedText = computed(
() => (theme.value as any)?.articleShare?.copyFailed ?? defaultCopyFailedText.value
)
const shareLink = computed(() => {
if (!isClient) return ''
const { origin, pathname, search, hash } = window.location
const finalSearch = props.includeQuery ? search : ''
const finalHash = props.includeHash ? hash : ''
return `${origin}${pathname}${finalSearch}${finalHash}`
})
async function copyToClipboard() {
if (copied.value || !isClient) return
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(shareLink.value)
} else {
const input = document.createElement('input')
input.setAttribute('readonly', 'readonly')
input.setAttribute('value', shareLink.value)
document.body.appendChild(input)
input.select()
document.execCommand('copy')
document.body.removeChild(input)
}
copied.value = true
setTimeout(() => {
copied.value = false
}, props.copiedTimeout)
} catch (error) {
console.error(i18nCopyFailedText.value, error)
}
}
const shareIconSvg = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path>
<polyline points="16 6 12 2 8 6"></polyline>
<line x1="12" y1="2" x2="12" y2="15"></line>
</svg>
`
const copiedIconSvg = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 6 9 17l-5-5"></path>
</svg>
`
</script>
<template>
<div class="article-share">
<button
:class="['article-share__button', { copied: copied }]"
:aria-label="copied ? i18nCopiedText : i18nShareText"
aria-live="polite"
@click="copyToClipboard"
>
<div v-if="!copied" class="content-wrapper">
<span class="icon" v-html="shareIconSvg"></span>
{{ i18nShareText }}
</div>
<div v-else class="content-wrapper">
<span class="icon" v-html="copiedIconSvg"></span>
{{ i18nCopiedText }}
</div>
</button>
</div>
</template>
<style scoped>
.article-share {
padding: 14px 0;
}
.article-share__button {
display: flex;
justify-content: center;
align-items: center;
font-weight: 500;
font-size: 14px;
position: relative;
z-index: 1;
transition: all 0.4s var(--ease-out-cubic, cubic-bezier(0.33, 1, 0.68, 1));
cursor: pointer;
border: 1px solid transparent;
border-radius: 14px;
padding: 7px 14px;
width: 100%;
overflow: hidden;
color: var(--vp-c-text-1, #333);
background-color: var(--vp-c-bg-alt, #f6f6f7);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.02);
will-change: transform, box-shadow;
}
.article-share__button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
z-index: -1;
transition: left 0.6s ease;
background-color: var(--vp-c-brand-soft, #ddf4ff);
width: 100%;
height: 100%;
}
.article-share__button:hover {
transform: translateY(-1px);
border-color: var(--vp-c-brand-soft, #ddf4ff);
background-color: var(--vp-c-brand-soft, #ddf4ff);
}
.article-share__button:active {
transform: scale(0.9);
}
.article-share__button.copied {
color: var(--vp-c-brand-1, #007acc);
background-color: var(--vp-c-brand-soft, #ddf4ff);
}
.article-share__button.copied::before {
left: 0;
background-color: var(--vp-c-brand-soft, #ddf4ff);
}
.content-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.icon {
display: inline-flex;
align-items: center;
margin-right: 6px;
}
</style>

View File

@@ -1,12 +0,0 @@
<script setup lang="ts">
import confetti from 'canvas-confetti'
const media = window.matchMedia('(prefers-reduced-motion: reduce)')
if (!media.matches) {
confetti({
particleCount: 100,
spread: 170,
origin: { y: 0.6 },
})
}
</script>

View File

@@ -1,150 +0,0 @@
<script setup>
import { onBeforeUnmount, onMounted, ref, computed } from 'vue'
const showBackTop = ref(false) // 初始状态设为false
const scrollProgress = ref(0)
// 圆形进度条计算
const radius = 42
const circumference = computed(() => 2 * Math.PI * radius)
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}
// 使用更高效的节流函数
function throttle(fn, delay = 50) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
}
}
const updateScrollProgress = () => {
const { scrollY, innerHeight } = window
const { scrollHeight } = document.documentElement
const totalScroll = scrollHeight - innerHeight
scrollProgress.value = totalScroll > 0 ? Math.min(scrollY / totalScroll, 1) : 0
}
const handleScroll = throttle(() => {
// 当滚动超过100px时显示否则隐藏
const shouldShow = window.scrollY > 100
showBackTop.value = shouldShow
updateScrollProgress()
})
onMounted(() => {
window.addEventListener('scroll', handleScroll)
updateScrollProgress()
})
onBeforeUnmount(() => {
window.removeEventListener('scroll', handleScroll)
})
</script>
<template>
<Transition name="fade">
<div class="back-top-container" v-show="showBackTop">
<svg class="progress-ring" viewBox="0 0 100 100">
<circle class="progress-ring-background" cx="50" cy="50" r="42" />
<circle
class="progress-ring-circle"
cx="50"
cy="50"
r="42"
:style="{ 'stroke-dashoffset': circumference - scrollProgress * circumference }"
/>
</svg>
<div class="vitepress-backTop-main" title="返回顶部" @click="scrollToTop()">
<svg class="icon" viewBox="0 0 1024 1024">
<path
d="M752.736 431.063C757.159 140.575 520.41 8.97 504.518 0.41V0l-0.45 0.205-0.41-0.205v0.41c-15.934 8.56-252.723 140.165-248.259 430.653-48.21 31.457-98.713 87.368-90.685 184.074 8.028 96.666 101.007 160.768 136.601 157.287 35.595-3.482 25.232-30.31 25.232-30.31l12.206-50.095s52.47 80.569 69.304 80.528c15.114-1.23 87-0.123 95.6 0h0.82c8.602-0.123 80.486-1.23 95.6 0 16.794 0 69.305-80.528 69.305-80.528l12.165 50.094s-10.322 26.83 25.272 30.31c35.595 3.482 128.574-60.62 136.602-157.286 8.028-96.665-42.475-152.617-90.685-184.074z m-248.669-4.26c-6.758-0.123-94.781-3.359-102.891-107.192 2.95-98.714 95.97-107.438 102.891-107.93 6.964 0.492 99.943 9.216 102.892 107.93-8.11 103.833-96.174 107.07-102.892 107.192z m-52.019 500.531c0 11.838-9.42 21.382-21.012 21.382a21.217 21.217 0 0 1-21.054-21.34V821.74c0-11.797 9.421-21.382 21.054-21.382 11.591 0 21.012 9.585 21.012 21.382v105.635z m77.333 57.222a21.504 21.504 0 0 1-21.34 21.626 21.504 21.504 0 0 1-21.34-21.626V827.474c0-11.96 9.543-21.668 21.299-21.668 11.796 0 21.38 9.708 21.38 21.668v157.082z m71.147-82.043c0 11.796-9.42 21.34-21.053 21.34a21.217 21.217 0 0 1-21.013-21.34v-75.367c0-11.755 9.421-21.299 21.013-21.299 11.632 0 21.053 9.544 21.053 21.3v75.366z"
fill="#FFF"
/>
</svg>
</div>
</div>
</Transition>
</template>
<style scoped>
.back-top-container {
position: fixed;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
z-index: 999;
}
.vitepress-backTop-main {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
width: 44px;
height: 44px;
border-radius: 50%;
background-color: #3eaf7c;
padding: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
transition: background-color 0.2s ease;
}
.vitepress-backTop-main:hover {
background-color: #71cda3;
}
.progress-ring {
position: absolute;
width: 100%;
height: 100%;
transform: rotate(-90deg);
z-index: 1;
}
.progress-ring-background {
fill: none;
stroke: rgba(62, 175, 124, 0.15);
stroke-width: 3;
}
.progress-ring-circle {
fill: none;
stroke: #3eaf7c;
stroke-width: 3;
stroke-dasharray: 264; /* 2 * π * 42 */
stroke-linecap: round;
transition: stroke-dashoffset 0.15s ease-out;
}
.icon {
width: 24px;
height: 24px;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,33 +0,0 @@
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import { NolebaseGitChangelogPlugin } from '@nolebase/vitepress-plugin-git-changelog/client'
import 'virtual:group-icons.css'
import { h, onMounted } from 'vue'
import './style/style.css'
import ArticleShare from './components/ArticleShare.vue'
import backtotop from './components/backtotop.vue'
import '@nolebase/vitepress-plugin-git-changelog/client/style.css'
export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
'aside-outline-before': () => h(ArticleShare),
'doc-footer-before': () => h(backtotop),
})
},
enhanceApp({ app }) {
app.use(NolebaseGitChangelogPlugin)
// Register service worker in production for offline support and caching
if (
typeof window !== 'undefined' &&
'serviceWorker' in navigator &&
(import.meta as any).env?.PROD
) {
onMounted(() => {
navigator.serviceWorker.register('/sw.js').catch(() => {})
})
}
},
}

View File

@@ -1,192 +0,0 @@
/* .vitepress/theme/style/custom-block.css */
/* 深浅色卡 */
:root {
--custom-block-info-left: #cccccc;
--custom-block-info-bg: #fafafa;
--custom-block-tip-left: #009400;
--custom-block-tip-bg: #e6f6e6;
--custom-block-warning-left: #e6a700;
--custom-block-warning-bg: #fff8e6;
--custom-block-danger-left: #e13238;
--custom-block-danger-bg: #ffebec;
--custom-block-note-left: #4cb3d4;
--custom-block-note-bg: #eef9fd;
--custom-block-important-left: #a371f7;
--custom-block-important-bg: #f4eefe;
--custom-block-caution-left: #e0575b;
--custom-block-caution-bg: #fde4e8;
}
.dark {
--custom-block-info-left: #cccccc;
--custom-block-info-bg: #474748;
--custom-block-tip-left: #009400;
--custom-block-tip-bg: #003100;
--custom-block-warning-left: #e6a700;
--custom-block-warning-bg: #4d3800;
--custom-block-danger-left: #e13238;
--custom-block-danger-bg: #4b1113;
--custom-block-note-left: #4cb3d4;
--custom-block-note-bg: #193c47;
--custom-block-important-left: #a371f7;
--custom-block-important-bg: #230555;
--custom-block-caution-left: #e0575b;
--custom-block-caution-bg: #391c22;
}
/* 标题字体大小 */
.custom-block-title {
font-size: 16px;
}
/* info容器:背景色、左侧 */
.custom-block.info {
border-left: 5px solid var(--custom-block-info-left);
background-color: var(--custom-block-info-bg);
}
/* info容器:svg图 */
.custom-block.info [class*='custom-block-title']::before {
content: '';
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-11v6h2v-6h-2zm0-4v2h2V7h-2z' fill='%23ccc'/%3E%3C/svg%3E");
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
position: relative;
margin-right: 4px;
left: -5px;
top: -1px;
}
/* 提示容器:边框色、背景色、左侧 */
.custom-block.tip {
/* border-color: var(--custom-block-tip); */
border-left: 5px solid var(--custom-block-tip-left);
background-color: var(--custom-block-tip-bg);
}
/* 提示容器:svg图 */
.custom-block.tip [class*='custom-block-title']::before {
content: '';
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23009400' d='M7.941 18c-.297-1.273-1.637-2.314-2.187-3a8 8 0 1 1 12.49.002c-.55.685-1.888 1.726-2.185 2.998H7.94zM16 20v1a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-1h8zm-3-9.995V6l-4.5 6.005H11v4l4.5-6H13z'/%3E%3C/svg%3E");
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
position: relative;
margin-right: 4px;
left: -5px;
top: -2px;
}
/* 警告容器:背景色、左侧 */
.custom-block.warning {
border-left: 5px solid var(--custom-block-warning-left);
background-color: var(--custom-block-warning-bg);
}
/* 警告容器:svg图 */
.custom-block.warning [class*='custom-block-title']::before {
content: '';
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M576.286 752.57v-95.425q0-7.031-4.771-11.802t-11.3-4.772h-96.43q-6.528 0-11.3 4.772t-4.77 11.802v95.424q0 7.031 4.77 11.803t11.3 4.77h96.43q6.528 0 11.3-4.77t4.77-11.803zm-1.005-187.836 9.04-230.524q0-6.027-5.022-9.543-6.529-5.524-12.053-5.524H456.754q-5.524 0-12.053 5.524-5.022 3.516-5.022 10.547l8.538 229.52q0 5.023 5.022 8.287t12.053 3.265h92.913q7.032 0 11.803-3.265t5.273-8.287zM568.25 95.65l385.714 707.142q17.578 31.641-1.004 63.282-8.538 14.564-23.354 23.102t-31.892 8.538H126.286q-17.076 0-31.892-8.538T71.04 866.074q-18.582-31.641-1.004-63.282L455.75 95.65q8.538-15.57 23.605-24.61T512 62t32.645 9.04 23.605 24.61z' fill='%23e6a700'/%3E%3C/svg%3E");
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
position: relative;
margin-right: 4px;
left: -5px;
}
/* 危险容器:背景色、左侧 */
.custom-block.danger {
border-left: 5px solid var(--custom-block-danger-left);
background-color: var(--custom-block-danger-bg);
}
/* 危险容器:svg图 */
.custom-block.danger [class*='custom-block-title']::before {
content: '';
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2c5.523 0 10 4.477 10 10v3.764a2 2 0 0 1-1.106 1.789L18 19v1a3 3 0 0 1-2.824 2.995L14.95 23a2.5 2.5 0 0 0 .044-.33L15 22.5V22a2 2 0 0 0-1.85-1.995L13 20h-2a2 2 0 0 0-1.995 1.85L9 22v.5c0 .171.017.339.05.5H9a3 3 0 0 1-3-3v-1l-2.894-1.447A2 2 0 0 1 2 15.763V12C2 6.477 6.477 2 12 2zm-4 9a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm8 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4z' fill='%23e13238'/%3E%3C/svg%3E");
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
position: relative;
margin-right: 4px;
left: -5px;
top: -1px;
}
/* 提醒容器:背景色、左侧 */
.custom-block.note {
border-left: 5px solid var(--custom-block-note-left);
background-color: var(--custom-block-note-bg);
}
/* 提醒容器:svg图 */
.custom-block.note [class*='custom-block-title']::before {
content: '';
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-11v6h2v-6h-2zm0-4v2h2V7h-2z' fill='%234cb3d4'/%3E%3C/svg%3E");
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
position: relative;
margin-right: 4px;
left: -5px;
top: -1px;
}
/* 重要容器:背景色、左侧 */
.custom-block.important {
border-left: 5px solid var(--custom-block-important-left);
background-color: var(--custom-block-important-bg);
}
/* 重要容器:svg图 */
.custom-block.important [class*='custom-block-title']::before {
content: '';
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M512 981.333a84.992 84.992 0 0 1-84.907-84.906h169.814A84.992 84.992 0 0 1 512 981.333zm384-128H128v-42.666l85.333-85.334v-256A298.325 298.325 0 0 1 448 177.92V128a64 64 0 0 1 128 0v49.92a298.325 298.325 0 0 1 234.667 291.413v256L896 810.667v42.666zm-426.667-256v85.334h85.334v-85.334h-85.334zm0-256V512h85.334V341.333h-85.334z' fill='%23a371f7'/%3E%3C/svg%3E");
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
position: relative;
margin-right: 4px;
left: -5px;
top: -1px;
}
/* 注意容器:背景色、左侧 */
.custom-block.caution {
border-left: 5px solid var(--custom-block-caution-left);
background-color: var(--custom-block-caution-bg);
}
/* 注意容器:svg图 */
.custom-block.caution [class*='custom-block-title']::before {
content: '';
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2c5.523 0 10 4.477 10 10v3.764a2 2 0 0 1-1.106 1.789L18 19v1a3 3 0 0 1-2.824 2.995L14.95 23a2.5 2.5 0 0 0 .044-.33L15 22.5V22a2 2 0 0 0-1.85-1.995L13 20h-2a2 2 0 0 0-1.995 1.85L9 22v.5c0 .171.017.339.05.5H9a3 3 0 0 1-3-3v-1l-2.894-1.447A2 2 0 0 1 2 15.763V12C2 6.477 6.477 2 12 2zm-4 9a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm8 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4z' fill='%23e13238'/%3E%3C/svg%3E");
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
position: relative;
margin-right: 4px;
left: -5px;
top: -1px;
}

View File

@@ -1,125 +0,0 @@
#app a:focus-visible,
#app button:focus-visible,
#app input[type='checkbox']:focus-visible {
--at-apply: outline-1 outline-primary ring-2 ring-primary;
}
.VPSidebar::-webkit-scrollbar {
background: transparent;
height: 8px;
width: 8px;
margin-right: 8px;
}
#app > div > div.VPLocalNav > div > div > div.outline {
outline-style: none !important;
}
.vp-doc .color-swatch {
display: inline-block;
width: 0.85em;
height: 0.85em;
min-width: 12px;
min-height: 12px;
font-size: inherit;
border: 0;
border-radius: 2px;
margin: 0 3px 0 6px;
cursor: pointer;
}
.medium-zoom-overlay {
z-index: 20;
}
.medium-zoom-image {
z-index: 21;
}
@keyframes slide-enter {
0% {
transform: translateY(10px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 100;
}
}
@media (prefers-reduced-motion: no-preference) {
html:not(.no-sliding) [slide-enter],
html:not(.no-sliding) .slide-enter,
html:not(.no-sliding) .main > div > *,
html:not(.no-sliding) #VPContent > div > div.VPFeatures.VPHomeFeatures > *,
html:not(.no-sliding) .TeamPage > *,
html:not(.no-sliding) .VPHomeHero > * {
--enter-stage: 0;
--enter-step: 90ms;
--enter-initial: 0ms;
animation: slide-enter 1s both 1;
animation-delay: calc(var(--enter-initial) + var(--enter-stage) * var(--enter-step));
}
.main > div > *:nth-child(1) {
--enter-stage: 1;
}
.main > div > *:nth-child(2) {
--enter-stage: 2;
}
.main > div > *:nth-child(3) {
--enter-stage: 3;
}
.main > div > *:nth-child(4) {
--enter-stage: 4;
}
.main > div > *:nth-child(5) {
--enter-stage: 5;
}
.main > div > *:nth-child(6) {
--enter-stage: 6;
}
.main > div > *:nth-child(7) {
--enter-stage: 7;
}
.main > div > *:nth-child(8) {
--enter-stage: 8;
}
.main > div > *:nth-child(9) {
--enter-stage: 9;
}
.main > div > *:nth-child(10) {
--enter-stage: 10;
}
.main > div > *:nth-child(11) {
--enter-stage: 11;
}
.main > div > *:nth-child(12) {
--enter-stage: 12;
}
.main > div > *:nth-child(13) {
--enter-stage: 13;
}
.main > div > *:nth-child(14) {
--enter-stage: 14;
}
.main > div > *:nth-child(15) {
--enter-stage: 15;
}
.main > div > *:nth-child(16) {
--enter-stage: 16;
}
.main > div > *:nth-child(17) {
--enter-stage: 17;
}
.main > div > *:nth-child(18) {
--enter-stage: 18;
}
.main > div > *:nth-child(19) {
--enter-stage: 19;
}
.main > div > *:nth-child(20) {
--enter-stage: 20;
}
}

View File

@@ -1,158 +0,0 @@
@import './custom-block.css';
@import './doc-fade-in.css';
:root:where(:lang(fa)) {
--vp-font-family-base:
'Vazirmatn', 'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe 30%, #41d1ff);
--vp-home-hero-image-background-image: linear-gradient(-45deg, #bd34fe 50%, #47caff 50%);
--vp-home-hero-image-filter: blur(44px);
/* Enhanced brand color for better contrast */
--vp-c-brand-1: #1e40af;
--vp-c-brand-2: #2563eb;
--vp-c-brand-3: #3b82f6;
--vp-c-brand-soft: rgba(30, 64, 175, 0.14);
/* Button contrast improvements */
--vp-button-brand-bg: #1e40af;
--vp-button-brand-text: #ffffff;
--vp-button-brand-hover-bg: #1d4ed8;
--vp-button-brand-hover-text: #ffffff;
--vp-button-brand-active-bg: #1e3a8a;
--vp-button-brand-active-text: #ffffff;
}
/* Dark mode color overrides for better contrast */
.dark:root {
--vp-c-brand-1: #60a5fa;
--vp-c-brand-2: #3b82f6;
--vp-c-brand-3: #2563eb;
--vp-c-brand-soft: rgba(96, 165, 250, 0.16);
--vp-button-brand-bg: #3b82f6;
--vp-button-brand-text: #000000;
--vp-button-brand-hover-bg: #60a5fa;
--vp-button-brand-hover-text: #000000;
--vp-button-brand-active-bg: #2563eb;
--vp-button-brand-active-text: #000000;
}
@media (min-width: 640px) {
:root {
--vp-home-hero-image-filter: blur(56px);
}
}
@media (min-width: 960px) {
:root {
--vp-home-hero-image-filter: blur(68px);
}
}
.VPHero .VPImage {
filter: drop-shadow(-2px 4px 6px rgba(0, 0, 0, 0.2));
padding: 18px;
}
/* used in reference/default-theme-search */
img[src='/search.png'] {
width: 100%;
aspect-ratio: 1 / 1;
}
/* Enhanced button contrast for accessibility */
.VPButton.brand {
background-color: var(--vp-button-brand-bg) !important;
color: var(--vp-button-brand-text) !important;
border: none;
font-weight: 600;
text-shadow: none;
}
.VPButton.brand:hover {
background-color: var(--vp-button-brand-hover-bg) !important;
color: var(--vp-button-brand-hover-text) !important;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(30, 64, 175, 0.4);
}
.VPButton.brand:active {
background-color: var(--vp-button-brand-active-bg) !important;
color: var(--vp-button-brand-active-text) !important;
transform: translateY(0);
}
/* Dark mode support for buttons */
.dark .VPButton.brand {
background-color: #3b82f6 !important;
color: #000000 !important;
}
.dark .VPButton.brand:hover {
background-color: #60a5fa !important;
color: #000000 !important;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
.dark .VPButton.brand:active {
background-color: #2563eb !important;
color: #000000 !important;
}
/* Ensure proper contrast for all text elements */
.VPButton.brand .text {
color: inherit !important;
}
/* Focus states for accessibility */
.VPButton.brand:focus-visible {
outline: 2px solid #ffffff;
outline-offset: 2px;
}
.dark .VPButton.brand:focus-visible {
outline: 2px solid #000000;
outline-offset: 2px;
}
@font-face {
font-family: 'HarmonyOS Sans SC';
src: url('/HarmonyOS_Sans_SC.ttf') format('truetype');
}
body {
font-family: 'HarmonyOS Sans SC', sans-serif;
line-height: 1.8;
letter-spacing: 0.05em;
word-spacing: 0.05em;
}
p,
li,
a,
span,
div,
h1,
h2,
h3,
h4,
h5,
h6 {
word-break: break-word;
text-justify: inter-ideograph;
-ms-text-autospace: ideograph-alpha;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
pre,
code {
letter-spacing: normal;
word-spacing: normal;
}

View File

@@ -1,101 +0,0 @@
import { defineConfig, type DefaultTheme } from 'vitepress'
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons'
export default defineConfig({
lang: 'en-US',
description:
'Next-Generation Android Root Solution - Advanced kernel-based root management for Android devices with KernelSU integration',
themeConfig: {
nav: nav(),
sidebar: {
'/': { base: '/', items: sidebar() },
},
search: { options: searchOptions() },
editLink: {
pattern: 'https://github.com/sukisu-ultra/sukisu-ultra/edit/main/docs/:path',
text: 'Edit this page on GitHub',
},
docFooter: {
prev: 'Previous',
next: 'Next',
},
outline: {
label: 'On this page',
},
lastUpdated: {
text: 'Last updated',
},
notFound: {
title: 'Page Not Found',
quote: "Sorry, we couldn't find what you're looking for.",
linkLabel: 'Go to home',
linkText: 'Take me home',
},
langMenuLabel: 'Languages',
returnToTopLabel: 'Return to top',
sidebarMenuLabel: 'Menu',
darkModeSwitchLabel: 'Theme',
lightModeSwitchTitle: 'Switch to light theme',
darkModeSwitchTitle: 'Switch to dark theme',
skipToContentLabel: 'Skip to content',
},
})
function nav(): DefaultTheme.NavItem[] {
return [
{ text: 'Home', link: '/' },
{
text: 'Getting Started',
items: [
{ text: 'Introduction', link: '/guide/' },
{ text: 'Installation', link: '/guide/installation' },
{ text: 'Compatibility', link: '/guide/compatibility' },
{ text: 'Links', link: '/guide/links' },
{ text: 'license', link: '/guide/license' },
],
},
]
}
function sidebar(): DefaultTheme.SidebarItem[] {
return [
{
text: 'Getting Started',
items: [
{ text: 'Introduction', link: '/guide/' },
{ text: 'Installation', link: '/guide/installation' },
{ text: 'Compatibility', link: '/guide/compatibility' },
{ text: 'Links', link: '/guide/links' },
{ text: 'license', link: '/guide/license' },
],
},
]
}
function searchOptions(): Partial<DefaultTheme.LocalSearchOptions> {
return {
translations: {
button: {
buttonText: 'Search docs',
buttonAriaLabel: 'Search docs',
},
modal: {
noResultsText: 'No results found',
resetButtonTitle: 'Clear query',
footer: {
selectText: 'Select',
navigateText: 'Navigate',
closeText: 'Close',
},
},
},
}
}

View File

@@ -1,23 +0,0 @@
# Compatibility Status
::: info KernelSU
KernelSU (versions prior to v0.9.5) officially supports Android GKI 2.0 devices (kernel 5.10+)
:::
::: warning Legacy Kernel Support
Older kernels (4.4+) are also compatible, but the kernel must be built manually
:::
::: tip Extended Compatibility
SukiSu-Ultra can support 3.x kernels (3.4-3.18) through additional back ports
:::
## Architecture Support
Currently supports the following processor architectures:
| Architecture | Support Level | Notes |
| --------------- | ------------------ | --------------------------- |
| **arm64-v8a** | ✅ Full Support | Primary target architecture |
| **armeabi-v7a** | ✅ Basic Support | Bare minimum functionality |
| **X86_64** | 🟡 Partial Support | Some devices supported |

View File

@@ -1,85 +0,0 @@
# Introduction
Welcome to SukiSU-Ultra, the next-generation Android root solution that provides advanced kernel-based root management for Android devices.
## What is SukiSU-Ultra?
SukiSU-Ultra is a modern, secure, and powerful root solution designed specifically for Android devices. It offers kernel-level root access management with enhanced security features and improved compatibility.
## Key Features
### 🔒 Kernel-based su and root access management
Secure root access management at the kernel level, providing better security and performance compared to traditional solutions.
### 🚫 Not based on OverlayFS module system
Built on Magic Mount technology from 5ec1cff, offering a more stable and reliable foundation.
### 📱 App Profile
Advanced application profiling system that allows you to lock root privileges in a controlled environment.
### 🔧 Enhanced Device Support
Bringing back support for non-GKI/GKI 1.0 devices, ensuring compatibility with older Android devices.
### ⚙️ Extensive Customization
Comprehensive customization options to tailor the root experience to your specific needs.
### 🔌 KPM Kernel Module Support
Full KernelPatch Module (KPM) functionality for advanced kernel modifications and enhancements.
## Why Choose SukiSU-Ultra?
- **Security First**: Advanced security features protect your device and data
- **Modern Architecture**: Built with modern Android security models in mind
- **Wide Compatibility**: Supports both GKI and non-GKI devices
- **Active Development**: Continuously updated with latest Android versions
- **Community Driven**: Open source with active community support
## Getting Started
Ready to get started with SukiSU-Ultra? Follow our step-by-step guide:
1. **[Installation](./installation)** - Learn how to install SukiSU-Ultra on your device
2. **[Compatibility](./compatibility)** - Check device compatibility requirements
3. **[Links](./links)** - Find additional resources and downloads
## System Requirements
Before installing SukiSU-Ultra, ensure your device meets these requirements:
- **Android Version**: Android 8.0 (API 26) or higher
- **Bootloader**: Unlocked bootloader
- **Recovery**: Custom recovery (TWRP recommended)
- **Storage**: At least 100MB free space
- **Knowledge**: Basic understanding of Android modding
## Safety Notice
::: danger Important
⚠️ **Rooting your device can void your warranty and may cause permanent damage if done incorrectly.**
Always:
- Create a full backup before proceeding
- Ensure your device is compatible
- Follow instructions carefully
- Have a recovery plan ready
:::
## Support
Need help? We're here to assist:
- **📖 Documentation**: Comprehensive guides and tutorials
- **💬 Community**: Active community forums and chat
- **🐛 Bug Reports**: GitHub issue tracker
- **📧 Direct Support**: Contact developers for critical issues
---
**Ready to unlock the full potential of your Android device?** Start with our [installation guide](./installation) and join thousands of users who trust SukiSU-Ultra for their root needs.

View File

@@ -1,121 +0,0 @@
# Installation Guide
This guide provides comprehensive instructions for installing SukiSU-Ultra on your Android device. Please follow the steps carefully.
## Prerequisites
Before you begin, ensure you have the following:
- [ ] A compatible device. Check the [Compatibility Guide](./compatibility.md) for details.
- [ ] Unlocked bootloader.
- [ ] Custom recovery installed, such as TWRP.
- [ ] Basic knowledge of flashing custom ROMs and kernels.
- [ ] Your device's kernel source or a compatible pre-built kernel.
## Installation Methods
There are several ways to install SukiSU-Ultra, depending on your device and preference.
### Method 1: Using Pre-built GKI Packages
This is the recommended method for devices with Generic Kernel Image (GKI) 2.0, such as many Xiaomi, Redmi, and Samsung models.[^1]
[^1]: This method is not suitable for devices from manufacturers that heavily modify the kernel, like Meizu, OnePlus, Realme, and Oppo.
#### Steps:
1. **Download GKI Build**: Visit our [resources section](./links.md) to find the appropriate GKI build for your device's kernel version. Download the `.zip` file that includes `AnyKernel3` in its name.
2. **Flash via Recovery**:
- [ ] Boot your device into TWRP recovery.
- [ ] Select "Install".
- [ ] Navigate to the downloaded `AnyKernel3` zip file and select it.
- [ ] Swipe to confirm the flash.
- [ ] Once flashing is complete, reboot your system.
3. **Verify Installation**:
- [ ] Install the SukiSU-Ultra Manager app.
- [ ] Open the app and check if root access is granted and working correctly.
- [ ] You can also verify the new kernel version in your device's settings.
::: details File Format Guide
The `.zip` archive without a suffix is uncompressed. The `.gz` suffix indicates compression used for specific models.
:::
### Method 2: Custom Build for OnePlus Devices
For OnePlus devices, you'll need to create a custom build.
#### Steps:
1. **Gather Device Information**: You will need:
- Your kernel version (e.g., `5.10`, `5.15`).
- Your processor's codename.
- The branch and configuration files from the OnePlus open-source kernel repository.
2. **Create Custom Build**: Use the link in our [resources section](./links.md) to generate a custom build with your device's information.
3. **Flash the Build**:
- [ ] Download the generated `AnyKernel3` zip file.
- [ ] Boot into recovery.
- [ ] Flash the zip file.
- [ ] Reboot and verify the installation.
### Method 3: Manual Kernel Integration (Advanced)
This method is for advanced users who are building a kernel from source.
#### Integration Scripts:
- **Main Branch (GKI)**:
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
```
- **Non-GKI Branch**:
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s nongki
```
- **SUSFS-Dev Branch (Recommended)**:
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-main
```
::: warning Required Kernel Configs
For KPM support, you must enable `CONFIG_KPM=y`.
For non-GKI devices, you also need to enable `CONFIG_KALLSYMS=y` and `CONFIG_KALLSYMS_ALL=y`.
:::
## Post-Installation
### Maintaining Root After OTA Updates
To keep root access after an Over-the-Air (OTA) update, follow these steps ==before rebooting==.
1. **Flash to Inactive Slot**:
- [ ] After the OTA update is downloaded and installed, **do not reboot**.
- [ ] Open the SukiSU-Ultra Manager.
- [ ] Go to the flashing/patching interface.
- [ ] Select your `AnyKernel3` kernel zip file.
- [ ] Choose to install it to the inactive slot.
- [ ] Once flashed, you can safely reboot.
2. **Alternative: LKM Mode**: You can also use LKM mode to install to the unused slot after an OTA.
::: tip
For non-GKI devices, the safest method to retain root after an OTA is to use TWRP to flash the kernel again.
:::
## Verification Checklist
After installation, please verify the following:
- [ ] **Manager App**: The SukiSU-Ultra Manager app opens and shows a successful root status.
- [ ] **Root Access**: Root checker apps confirm that root access is working.
- [ ] **Kernel Version**: The kernel version in `Settings > About Phone` reflects the SukiSU-Ultra kernel.
## Troubleshooting
If you encounter any issues:
1. Double-check the [Compatibility Guide](./compatibility.md).
2. Visit our [GitHub repository](https://github.com/sukisu-ultra/sukisu-ultra) for issues and solutions.
3. Join our [Telegram community](https://t.me/sukiksu) for live support.
::: danger Safety Reminder
⚠️ **Always have a backup!** Keep a copy of your original `boot.img` and be prepared to restore your device if something goes wrong.
:::

View File

@@ -1,77 +0,0 @@
# License
## 📄 Software Licensing
### Kernel Components
::: info GPL-2.0 License
The files in the "kernel" directory are under GPL-2.0-only license
:::
**License:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
### Application Core
::: tip GPL-3.0 License
All other parts (except mentioned below) are under GPL-3.0 or later license
:::
**License:** [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html)
## Artwork & Brand Assets
### Launcher Icons & Character Art
::: warning Copyright Notice
Special licensing requirements for anime character artwork
:::
The images of the files `ic_launcher(?!.*alt.*).*` with anime character emoticons have specific copyright terms:
**Copyright Holders:**
- **Anime Character Art:** [五十根大虾仁](https://space.bilibili.com/370927)
- **Brand Intellectual Property:** [明风OuO](https://space.bilibili.com/274939213)
- **Vectorization:** @MiRinChan
**License Requirements:**
1. **Creative Commons License:** [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.txt)
2. **Author Authorization:** Required from both copyright holders
3. **Attribution:** Must credit all contributors listed above
::: details Usage Requirements
Before using these artistic assets, you must:
- Comply with Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license
- Obtain authorization from both original authors for use of artistic content
- Provide proper attribution to all contributors
:::
## 📋 License Summary
| Component | License | Notes |
| -------------------- | ------------------------------------------------------------------------- | ------------------------------------- |
| **Kernel Files** | [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) | Files in `/kernel/` directory |
| **Application Code** | [GPL-3.0+](https://www.gnu.org/licenses/gpl-3.0.html) | Main application components |
| **Character Art** | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) | + Author authorization required |
| **Brand Assets** | Mixed Licensing | See specific attribution requirements |
## 🔗 License Links
- **GPL-2.0:** [Full License Text](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
- **GPL-3.0:** [Full License Text](https://www.gnu.org/licenses/gpl-3.0.html)
- **CC BY-NC-SA 4.0:** [Full License Text](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.txt)
## 📞 Licensing Questions
For questions about licensing or usage permissions:
1. **Code Licensing:** Refer to respective GPL license terms
2. **Artwork Usage:** Contact original authors for authorization
3. **Commercial Use:** Review CC BY-NC-SA 4.0 restrictions
4. **Distribution:** Ensure compliance with all applicable licenses
::: tip Compliance Note
When redistributing or modifying SukiSU-Ultra, ensure you comply with all applicable licenses and attribution requirements for each component.
:::

View File

@@ -1,77 +0,0 @@
# More Links
## 🌐 Translation & Localization
::: info Contribute Translations
If you need to submit a translation for the manager, please visit our Crowdin project
:::
**Translation Platform:** [Crowdin - SukiSU-Ultra](https://crowdin.com/project/SukiSU-Ultra)
## 🔧 Projects & Builds
Projects compiled based on Sukisu and susfs:
### GKI Builds
::: tip Universal GKI Support
Generic Kernel Image builds with KernelSU and SUSFS integration
:::
**Repository:** [GKI_KernelSU_SUSFS](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
### OnePlus Builds
::: tip Device-Specific Builds
Automated OnePlus kernel builds with MKSU and SUSFS
:::
**Repository:** [Action_OnePlus_MKSU_SUSFS](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
## 📱 Community & Support
### Telegram Community
::: info Join Our Community
Connect with other users, get support, and stay updated
:::
**Main Group:** [Tg Group](https://t.me/sukiksu)
### Test Builds
::: warning Experimental Builds
Test builds are experimental and may be unstable
:::
**Test Builds Channel:** [Latest Test Build](https://t.me/Sukiksu/7114)
## Downloads & Releases
### Official Releases
::: tip Stable Releases
Download the latest stable versions from our GitHub releases
:::
**GitHub Releases:** [SukiSU-Ultra Releases](https://github.com/sukisu-ultra/sukisu-ultra/releases)
### Issue Reporting
::: info Bug Reports & Feature Requests
Report bugs or request new features on our GitHub repository
:::
**GitHub Issues:** [Report Issues](https://github.com/sukisu-ultra/sukisu-ultra/issues)
## 🔗 Quick Links Summary
| Resource | Link | Description |
| ------------------ | ---------------------------------------------------------------------------- | ---------------------- |
| **Translations** | [Crowdin](https://crowdin.com/project/SukiSU-Ultra) | Submit translations |
| **Telegram Group** | [t.me/sukiksu](https://t.me/sukiksu) | Community support |
| **Test Builds** | [Test Channel](https://t.me/Sukiksu/7114) | Experimental builds |
| **Releases** | [GitHub Releases](https://github.com/sukisu-ultra/sukisu-ultra/releases) | Stable downloads |
| **Issues** | [GitHub Issues](https://github.com/sukisu-ultra/sukisu-ultra/issues) | Bug reports |
| **GKI Builds** | [GKI Repository](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS) | Universal builds |
| **OnePlus Builds** | [OnePlus Repository](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS) | Device-specific builds |

View File

@@ -1,37 +0,0 @@
---
layout: home
hero:
name: 'SukiSU-Ultra'
text: 'Next-Generation Android Root Solution'
tagline: Advanced kernel-based root management for Android devices
image:
src: /logo.svg
alt: SukiSU-Ultra
actions:
- theme: brand
text: Get Started
link: /guide/
- theme: alt
text: View on GitHub
link: https://github.com/sukisu-ultra/sukisu-ultra
features:
- title: Kernel-based su and root access management
details: Secure root access management at the kernel level.
- title: Not based on OverlayFS module system
details: Based on Magic Mount from 5ec1cff.
- title: App Profile
details: Lock root privileges in a cage.
- title: Bringing back non-GKI/GKI 1.0 support
details: Enhanced compatibility for older devices.
- title: More customization
details: Extensive customization options available.
- title: Support for KPM kernel modules
details: Full KernelPatch Module functionality.
---

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 200 KiB

View File

@@ -1,60 +0,0 @@
# Global headers for security and performance
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: "max-age=31536000; includeSubDomains; preload"
Permissions-Policy: "camera=(), microphone=(), geolocation=()"
Content-Security-Policy: "default-src 'self'; script-src 'self' https://static.cloudflareinsights.com; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
# Cache static assets for maximum performance
/assets/*
Cache-Control: public, max-age=31536000, immutable
/*.js
Cache-Control: public, max-age=31536000, immutable
/*.css
Cache-Control: public, max-age=31536000, immutable
/*.woff2
Cache-Control: public, max-age=31536000, immutable
/*.woff
Cache-Control: public, max-age=31536000, immutable
# Images - 30 days cache
/*.svg
Cache-Control: public, max-age=2592000
/*.png
Cache-Control: public, max-age=2592000
/*.jpg
Cache-Control: public, max-age=2592000
/*.jpeg
Cache-Control: public, max-age=2592000
/*.webp
Cache-Control: public, max-age=2592000
/*.avif
Cache-Control: public, max-age=2592000
/*.ico
Cache-Control: public, max-age=2592000
# Manifest and service worker
/site.webmanifest
Cache-Control: public, max-age=86400
/sw.js
Cache-Control: public, max-age=0, must-revalidate
# Offline page and HTML caching
/*.html
Cache-Control: public, max-age=60, must-revalidate
/offline.html
Cache-Control: public, max-age=3600

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,535 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="512"
viewBox="0 0 512 512"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
sodipodi:docname="zako.optizmism.svg"
inkscape:export-filename="zako.plain.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showguides="false"
inkscape:zoom="2"
inkscape:cx="219"
inkscape:cy="370"
inkscape:window-width="1280"
inkscape:window-height="702"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="false"><inkscape:page
x="0"
y="0"
width="512"
height="512"
id="page2"
margin="0"
bleed="0" /></sodipodi:namedview><defs
id="defs1"><inkscape:path-effect
effect="simplify"
id="path-effect79"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect78"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect77"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect76"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect75"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect61"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect42"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /></defs><g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1"><path
style="display:inline;opacity:1;fill:#fff9f6;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 164.23888,424.64081 -31.07097,26.05282 c 0,0 -5.19317,18.17613 25.96589,28.93342 31.15907,10.7573 213.29123,1.48377 213.29123,1.48377 0,0 52.30272,11.87012 80.86519,-37.09413 -0.37094,0.74188 -8.90259,19.28895 -8.90259,19.28895 l 1.48377,19.28894 c 0,0 57.73516,-9.41425 30.45647,-148.95524 4.19672,-9.44262 8.39344,-15.21311 8.39344,-15.21311 l -3.14754,-21.77049 c 0,0 5.65758,-40.33533 7.88323,-45.52851 2.22564,-5.19317 9.64447,-11.12823 9.64447,-11.12823 0,0 -2.78206,-20.40177 -6.67694,-26.15136 -0.74189,-3.33847 6.86241,-35.05395 2.04017,-58.05231 -7.04788,-11.87012 -4.82223,-11.87012 -18.918,-25.59495 0,0.37094 8.90259,-44.88389 -0.74188,-73.446367 -2.96753,0 -32.27189,-10.757296 -96.81567,23.740241 -0.37094,1.112824 -26.33683,-30.046242 -153.56969,-15.950474 -0.74188,0 -43.40012,-37.836009 -66.76942,-40.432598 -1.11283,-0.370941 -22.62742,-4.080354 -25.22401,48.964247 0,1.112824 -44.14201,15.950474 -56.01213,64.914721 -11.87012,48.96425 -11.128238,80.86519 -11.128238,80.86519 0,0 -18.904912,27.6632 -9.798121,25.03308 5.346826,-1.54421 4.975885,23.56023 4.975885,23.56023 l -2.225648,39.31977 c 0,0 -14.466709,35.23942 -14.83765,59.72155 -0.370942,24.48212 -0.741883,34.12659 4.822236,48.5933 5.564119,14.46671 20.772711,46.7386 42.658245,63.06002 8.902591,0.37094 18.918001,1.8547 18.918001,1.8547 l 3.70942,-20.77271 c 0,0 18.918,15.95048 32.27189,14.83765 13.35388,-1.11282 -0.74189,-0.74188 -0.74189,-0.74188 l -13.72482,-17.80518 z"
id="path2"
sodipodi:nodetypes="ccscccccccscccccccccscsccsscccsccc"
inkscape:label="整个人的边框" /><path
id="path91"
style="opacity:1;fill:#93d4fa;fill-opacity:1;stroke:#4c4f59;stroke-width:6.9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 133.91016,110.16992 -69.738285,47.48047 18.263976,30.75433 -31.617492,55.67536 28.5625,17.0625 14.837891,-1.48438 2.226562,-55.64062 2.577791,-4.39109 18.193697,-21.94485 23.74023,-49.70703 z"
sodipodi:nodetypes="ccccccccccc" /><path
style="display:inline;opacity:1;fill:#ace0fe;fill-opacity:1;stroke:#4c4f59;stroke-width:6.54357;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 217.01397,405.82758 c 0,0 7.41728,-25.94462 41.16595,-27.07265 33.74866,-1.12802 53.77534,4.1361 53.77534,4.1361 0,0 11.12594,6.39215 17.80149,14.66435 6.67557,8.2722 28.92743,40.60895 41.53682,48.88115 12.60939,8.27219 15.20545,15.41636 15.20545,15.41636 l -1.85433,11.28027 c 0,0 -14.83458,10.15224 -30.78175,11.65627 -15.94717,1.50404 -147.60404,0 -147.60404,0 l -30.04002,-3.00807"
id="path71"
inkscape:label="衣服底色" /><path
style="fill:#ffffff;fill-opacity:1;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 363.54127,444.68178 c 0,0 -6.89429,4.06586 -4.86135,6.8059 2.03293,2.74004 5.92201,3.44715 5.92201,3.44715 0,0 4.5962,0.26516 5.12653,-3.53554 0.53033,-3.8007 -3.00521,-6.8059 -3.00521,-6.8059"
id="path60" /><path
style="fill:#ffffff;fill-opacity:1;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 349.67678,459.82583 c 0,0 4.5,-7.25 7,-5.5 2.5,1.75 -4.25,7.25 -4.25,7.25"
id="path59" /><path
id="path73-8"
style="fill:#ffffff;stroke-width:8.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 228.96985,465.10941 a 8.0341015,5.6995392 0 0 1 -8.0341,5.69953 8.0341015,5.6995392 0 0 1 -8.03411,-5.69953 8.0341015,5.6995392 0 0 1 8.03411,-5.69954 8.0341015,5.6995392 0 0 1 8.0341,5.69954 z" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 220.85246,408.91803 c -0.22678,0.7374 0.77024,1.47887 0.20184,0.3709 -0.55918,-1.08999 0.45864,1.0858 0.58075,1.39596 2.10089,5.33666 1.96407,11.16153 3.60742,16.6426 0.30205,1.00744 0.58829,2.039 0.5936,3.09874"
id="path75"
inkscape:path-effect="#path-effect75"
inkscape:original-d="m 220.85246,408.91803 c 0.0874,0.34973 -0.0874,1.13662 0.26229,1.04918 0.34973,-0.0874 -0.42351,-1.37161 -0.26229,-1.04918 2.70315,5.4063 2.32215,10.60009 3.67213,16 0.44415,1.77662 1.31148,3.74741 1.31148,5.5082" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:6.9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 225.57377,436.98361 c -1.22213,4.37272 0.0774,9.22771 2.41807,12.85195 2.34183,3.626 0.85866,8.30929 2.56554,12.06608 1.71568,3.77616 2.21068,8.39349 -0.30117,12.16042 -1.9802,2.96963 -5.23922,5.13567 -6.38674,8.61546 0,0 -0.0736,0.66095 -0.0736,0.66095"
id="path76"
inkscape:path-effect="#path-effect76"
inkscape:original-d="m 225.57377,436.98361 c -1.27879,3.14891 0.64852,9.42818 3.40984,14.95082 0.92316,1.84632 0.76302,8.34571 1.57377,9.96721 1.05661,2.11323 2.18032,6.57378 1.31147,9.18033 -1.55473,4.66419 -8.07296,8.77008 -8.07296,12.2565"
sodipodi:nodetypes="csssc" /><path
style="opacity:1;fill:#fbf3ef;fill-opacity:1;stroke:none;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 259.14754,381.90164 c 0,0 8.91803,15.47541 9.18033,20.98361 0.26229,5.50819 32.26229,-1.83607 32.26229,-1.83607 0,0 -4.45901,-16.78688 -7.34426,-19.40983 -2.88524,-2.62296 -34.09836,0.26229 -34.09836,0.26229 z"
id="path89" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:4.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 247.86885,386.36066 c 0.62439,0.76152 1.31219,1.32242 0.35251,0.10604 -0.98535,-1.24891 -0.63523,-0.56343 0.0935,0.24171 1.40155,1.54849 2.30926,3.40606 3.10338,5.34166 1.48771,3.62621 2.15649,7.53104 4.02075,11.02472 1.24886,2.3404 2.74915,4.53757 3.97084,6.89242"
id="path77"
inkscape:path-effect="#path-effect77"
inkscape:original-d="m 247.86885,386.36066 c 3.34102,3.56326 -3.68025,-4.79705 1.83607,2.09836 1.23825,1.54782 3.18087,7.73986 3.67213,9.70492 1.00623,4.02493 4.53306,8.80383 6.03279,11.80327" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:4.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 306.62295,386.88525 c 1.62958,6.07854 4.04229,11.93568 6.81967,17.57377"
id="path78"
inkscape:path-effect="#path-effect78"
inkscape:original-d="m 306.62295,386.88525 c 1.57569,6.03983 4.44925,12.83291 6.81967,17.57377" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 335.73771,431.21312 c 0.7696,3.76393 3.24862,7.26774 5.48726,10.89168 2.09754,3.39553 3.33574,7.13199 4.87926,10.7955 2.06975,4.91248 6.75146,8.7424 7.699,13.95386 0.92029,5.06165 5.24206,8.89669 5.27874,14.19502"
id="path79"
inkscape:path-effect="#path-effect79"
inkscape:original-d="m 335.73771,431.21312 c -1.15649,1.03916 3.66917,4.97179 4.45901,8.13114 0.96754,3.87016 3.8848,6.88344 4.72131,10.22951 1.09547,4.38186 5.88097,12.49359 8.39345,16.2623 0.80744,1.21116 0.89968,3.11084 1.57377,4.45901 1.7539,3.50782 4.19672,6.9619 4.19672,10.7541" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 258.62295,384.2623 c 0,0 8.13115,6.81967 11.27869,27.01639"
id="path80" /><path
style="opacity:1;fill:#fbf3ef;fill-opacity:1;stroke:#4c4f59;stroke-width:6.9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 127.23285,329.0249 c 0,0 -2.59659,60.46343 13.72483,75.30108 8.16071,-8.16071 10.01541,-10.38636 10.01541,-10.38636 l 12.24107,8.53165 5.19317,-4.45129 2.22565,-19.65989 5.19318,-10.01542 5.93506,-12.24106 -8.53165,-8.53165 -15.95047,-19.28894 -3.33848,-4.82224 -4.45129,9.27353 c 0,0 -12.24106,0 -15.95048,-2.59659 -3.70941,-2.59658 -6.306,-1.11282 -6.306,-1.11282 z"
id="path90" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 291.67213,381.37705 c 0,0 7.08197,6.55738 10.22951,26.4918"
id="path81" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 270.16393,404.19672 c 0,0 16.00001,-3.93442 27.80328,-2.62295"
id="path82" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 253.90164,417.83607 c 0,0 37.2459,-13.11476 69.77049,-9.18033 2.09836,3.67213 5.77049,16.78688 -5.77049,20.98361 -4.72131,0 -19.14754,-1.57378 -22.55738,-0.5246 -3.40983,1.04919 -12.59016,0.5246 -18.36065,4.45902 -5.77049,3.93443 -14.68853,8.91803 -18.36066,3.14754 -3.67213,-5.77049 -4.72131,-12.32787 -4.72131,-12.32787 z"
id="path83" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 276.45902,482.62295 c 0,0 -9.70492,-1.57377 1.04918,-46.42623"
id="path84" /><path
style="opacity:1;fill:#fcf6fa;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 287.21312,432.78689 6.29508,50.88524 13.90164,-0.78688 -6.81968,-54.03279 z"
id="path88" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 287.73771,430.95082 5.77049,50.88525"
id="path85" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 300.32787,428.06557 c 0,0 2.09836,37.7705 7.34426,52.98361"
id="path86" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 313.18033,431.21312 c 0,0 7.60656,27.54098 10.22951,35.93442 2.62295,8.39344 -8.13115,15.73771 -8.13115,15.73771"
id="path87" /><path
style="opacity:1;fill:#bce4fd;fill-opacity:1;stroke:none;stroke-width:8.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 164.23888,424.64081 -31.07097,26.05282 5.00771,13.72483 20.95818,15.20859 17.08508,2.15431 -11.1287,-20.55238 -1.54637,-22.78395 z"
id="path74" /><path
id="path73"
style="opacity:1;fill:#ffffff;stroke-width:8.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 163.81755,451.10144 a 8.0341015,5.6995392 0 0 1 -8.0341,5.69954 8.0341015,5.6995392 0 0 1 -8.0341,-5.69954 8.0341015,5.6995392 0 0 1 8.0341,-5.69954 8.0341015,5.6995392 0 0 1 8.0341,5.69954 z" /><path
style="opacity:1;fill:#fff9f6;fill-opacity:1;stroke:none;stroke-width:8.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 338.36066,376.13115 c 0,0 1.83605,30.95081 -6.29509,51.40983 2.88525,1.04918 14.48107,0.26606 26.91267,-7.34238 12.4316,-7.60844 11.38242,-22.03467 11.38242,-22.03467 l 2.09836,28.85246 c 0,0 24.31867,-28.09873 24.2249,-52.60674 -0.0938,-24.50801 -1.66753,-26.08178 -1.66753,-26.08178 l -11.83407,-1.12685 z"
id="path72" /><path
style="opacity:1;fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:2.845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 339.7822,389.85927 c 0,0 38.20695,-7.78977 56.38307,-25.96589 5.56412,8.53165 8.16071,32.64283 8.16071,32.64283 l 2.59659,12.98294 16.32141,-62.68907 -6.67694,-5.19318 -5.93506,-17.43424 c 0,0 -15.20859,15.2086 -27.44966,22.99836 -12.24106,7.78977 -34.12659,19.28895 -34.12659,19.28895 l -10.7573,3.70941 z"
id="path70" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#c3b4b0;stroke-width:2.845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 347.01555,369.64297 c 0,0 2.22565,10.01541 -1.8547,19.84536 -4.08036,9.82994 -5.00771,9.459 -5.00771,9.459"
id="path69" /><path
style="opacity:1;fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.945;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 380.85246,480 c 0,0 44.59016,-17.31147 59.27869,-87.08197 6.81967,-2.62295 34.09836,-14.68852 39.34426,-25.70491 2.62295,-9.96722 -3.67213,-32 -3.67213,-32 l -24.65574,0.52459 -26.7541,4.72131 -4.72131,11.54098 -8.39344,52.45902 -13.11475,28.32787 -15.73771,36.19672 z"
id="path68" /><path
style="fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:8.74203;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 147.6556,277.03379 c 0,0 -11.12655,104.13607 127.58449,103.38689 138.71104,-0.74917 149.83759,-100.39017 149.83759,-100.39017"
id="path41"
inkscape:label="脸蛋" /><path
style="opacity:1;fill:#fff9f6;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 123.27869,147.93443 304.78688,21.50819 -24.65573,-40.39344 c 0,0 44.06557,-58.754098 50.88524,-60.327868 6.81967,-1.573771 15.73771,-9.967214 15.73771,-9.967214 l -13.11476,-3.672131 -77.11475,22.032787 c 0,0 -122.7541,-33.57377 -168.39344,-6.295082 -45.63935,27.278689 -69.7705,46.163938 -69.7705,46.163938 z"
id="path33"
inkscape:label="头发底色2" /><path
style="fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 331.44501,66.651771 16.22882,30.84274 47.82991,31.367399 6.30629,1.07795 26.88325,-35.335399 -32.10312,-22.211018 -18.88524,5.245901 -30.42623,-11.540983 z"
id="path35"
inkscape:label="头发部件111" /><path
style="opacity:1;fill:#ace0fe;fill-opacity:1;stroke:none;stroke-width:14.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 431.03375,175.82616 17.06329,-17.0633 50.44802,71.96261 -5.93506,17.80518 -33.38472,18.54706 -12.612,-16.32142 17.80518,-11.12823 z"
id="path29"
inkscape:label="蓝色发卡上" /><path
style="opacity:1;fill:#ace0fe;fill-opacity:1;stroke:#4c4f59;stroke-width:6.945;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 465.90223,261.88453 c 0,0 35.61036,54.52837 2.96753,73.44637 -25.22401,4.08036 -48.22237,2.22565 -48.22237,2.22565 l 10.38636,-26.33683 21.51459,-41.91636 z"
id="path66"
inkscape:label="蓝色发卡下" /><path
style="opacity:1;fill:#febdc7;fill-opacity:1;stroke:none;stroke-width:4.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 471.86885,57.704918 c 0,0 17.04918,68.983602 -11.01639,110.426232 -7.34426,0.26229 -9.18033,-10.49181 -9.18033,-10.49181 l 2.09836,-6.55737 -1.04918,-9.18033 2.36066,-10.7541 1.83606,-5.5082 -6.29508,-6.03278 -6.55738,-5.2459 -2.88524,-5.24591 2.88524,-5.2459 -2.62295,-5.245899 -4.19672,-2.885246 -3.14754,-1.57377 5.2459,-11.540984 11.80328,-7.344262 z"
id="path64" /><path
id="path57"
style="opacity:1;fill:#ff1c1c;fill-opacity:0.0509804;stroke-width:3.92397;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 244.26691,326.81357 a 35.587568,21.480417 0 0 1 -35.58757,21.48042 35.587568,21.480417 0 0 1 -35.58757,-21.48042 35.587568,21.480417 0 0 1 35.58757,-21.48042 35.587568,21.480417 0 0 1 35.58757,21.48042 z" /><path
id="path57-1"
style="fill:#ff1c1c;fill-opacity:0.05044398;stroke-width:3.65169;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 355.20429,336.75188 a 31.611719,20.942554 0 0 1 -31.61171,20.94255 31.611719,20.942554 0 0 1 -31.61172,-20.94255 31.611719,20.942554 0 0 1 31.61172,-20.94255 31.611719,20.942554 0 0 1 31.61171,20.94255 z" /><path
style="display:inline;opacity:1;fill:#fbf3ef;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 188.32787,343.60656 c 0,0 12.06557,-28.32787 15.7377,6.29508 5.24591,-8.91803 9.96722,-17.31148 17.31148,-8.39344 6.81967,2.09836 15.21311,7.34426 4.72131,22.03278 -10.4918,14.68853 16.33814,-18.37136 15.445,-1.63289 -0.72099,13.51206 -24.52214,46.81129 -24.52214,46.81129 0,0 -6.33782,64.18547 -33.41466,70.23144 -36.5485,8.16089 -12.06558,-103.86885 -12.06558,-103.86885 z"
id="path1"
sodipodi:nodetypes="cccsscscc"
inkscape:label="右手" /><path
id="path5"
style="fill:#fbe7e5;stroke-width:10.4;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke;fill-opacity:0.40581462"
inkscape:label="右手踝阴影"
d="m 193.63133,383.92419 a 12.612,12.241061 0 0 1 -12.612,12.24107 12.612,12.241061 0 0 1 -12.612,-12.24107 12.612,12.241061 0 0 1 12.612,-12.24106 12.612,12.241061 0 0 1 12.612,12.24106 z" /><path
style="fill:#fee4e0;fill-opacity:0.42411327;stroke:none;stroke-width:10.4;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 179.09741,356.52608 14.09576,-20.77271 7.04789,1.48377 0.74188,13.72482 11.49918,-11.49918 9.27353,4.08036 8.53165,5.93506 -5.56412,14.83765 13.72483,-4.82224 -3.33847,17.80518 -47.85143,-27.44965 z"
id="path9"
inkscape:label="右手手指阴影" /><path
style="display:inline;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 188.32787,343.60656 c 0,0 12.06557,-28.32787 15.7377,6.29508 5.24591,-8.91803 9.96722,-17.31148 17.31148,-8.39344 6.81967,2.09836 15.21311,7.34426 4.72131,22.03278 -10.4918,14.68853 16.33814,-18.37136 15.445,-1.63289 -0.72099,13.51206 -24.52214,46.81129 -24.52214,46.81129 0,0 -6.33782,64.18547 -33.41466,70.23144 -36.5485,8.16089 -12.06558,-103.86885 -12.06558,-103.86885 z"
id="path1-6"
sodipodi:nodetypes="cccsscscc"
inkscape:label="右手遮挡" /><path
style="opacity:1;fill:#fff9f6;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 137.44262,123.2787 c 0,0 49.33852,-48.649769 86.97684,-58.734919 0.10513,0.76769 -120.16719,-122.15824 -86.97684,58.734919 z"
id="path3"
inkscape:label="头发部件19 左猫耳" /><path
style="fill:#fde9e7;fill-opacity:1;stroke-width:1.18016;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 198.85246,42 -63.6342,39.016573 3.84123,40.939237 L 184.82787,87 224,64.5 Z"
id="path58"
sodipodi:nodetypes="cccccc" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0.465732;stroke:#a18f90;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
id="path4"
d="m 404.96447,131.7729 c 0.52512,-0.76931 1.0357,-1.54875 1.57536,-2.30793 2.82418,-3.97291 5.82035,-7.82036 8.67758,-11.76948 2.39309,-3.30762 3.93978,-5.54326 6.29133,-8.89998 5.32612,-7.58044 11.18023,-14.760796 17.20894,-21.78699 3.02173,-3.55064 6.06655,-7.098864 9.47662,-10.289082 1.96198,-1.835492 4.21906,-3.672787 6.27415,-5.392122 5.10823,-4.357455 10.66931,-8.109574 16.22169,-11.862072 0,0 -2.92504,-1.864176 -2.92504,-1.864176 v 0 c -5.41592,3.900149 -10.90348,7.705144 -15.99184,12.040062 -1.99887,1.645368 -4.44389,3.611151 -6.36286,5.369248 -3.45237,3.162941 -6.48944,6.731577 -9.48752,10.316639 -5.95526,7.087894 -11.63254,14.399231 -17.00034,21.944173 -5.47138,7.72885 -10.96322,15.45542 -17.11251,22.66867 z"
inkscape:label="头发部件18" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0.465732;stroke:#a18f90;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
id="path6"
d="m 431.7055,96.885136 c 0.68345,-0.01683 1.36993,0.008 2.05351,0.03239 1.26616,0.04274 2.52519,0.175792 3.77564,0.374299 1.34943,0.219358 2.67703,0.547853 3.99756,0.89747 1.30448,0.342355 2.58658,0.760225 3.8587,1.206247 0.34787,0.132122 0.88919,0.332358 1.23164,0.483411 0.1893,0.0835 0.73774,0.373687 0.5597,0.268307 -4.92017,-2.912189 -2.83776,-1.86942 -2.01609,-0.911603 0.4665,0.942833 -0.25414,1.810973 -0.83258,2.515753 -1.05305,1.11546 -2.37988,1.92349 -3.65077,2.76486 -0.70455,0.52564 -1.84263,0.99551 -2.02707,1.9792 -0.13577,0.72412 0.14689,1.00706 0.45808,1.64499 0.25805,0.31484 0.49646,0.64686 0.77415,0.94453 0.63085,0.67625 1.54885,1.45348 2.25243,2.03623 1.28413,1.06359 2.61277,2.07518 3.92585,3.10243 2.22765,1.73231 4.46676,3.44711 6.4408,5.4696 1.24472,1.36548 2.33589,2.86124 3.10757,4.54226 0.57402,1.31239 0.61894,2.68201 0.46471,4.08008 -0.15236,1.542 -1.01177,2.74753 -1.96982,3.90485 -1.25092,1.39253 -2.70297,2.57966 -3.98322,3.94302 -0.61776,0.76458 -1.19371,1.60995 -1.3359,2.60746 -0.0756,0.53059 -0.0152,0.87319 0.031,1.40055 0.18192,1.34638 0.59785,2.64226 1.03422,3.92358 0.46172,1.36123 1.10075,2.65093 1.71056,3.94949 0.57934,1.12566 0.88081,2.32829 0.97901,3.58284 0.0285,1.01273 -0.14083,2.01608 -0.35002,3.00302 -0.18321,0.8594 -0.39147,1.62037 -0.92358,2.32955 -0.69384,0.72591 -1.52468,1.30006 -2.31082,1.91913 0,0 3.13155,1.96912 3.13155,1.96912 v 0 c 0.80747,-0.64335 1.66352,-1.23803 2.37418,-1.9921 0.58364,-0.77755 0.80933,-1.52536 1.00495,-2.4688 0.21825,-1.01967 0.38801,-2.05558 0.38421,-3.10149 -0.0761,-1.29512 -0.34601,-2.55043 -0.93895,-3.71574 -0.60728,-1.2882 -1.24849,-2.56502 -1.72185,-3.91075 -0.43768,-1.25837 -0.85635,-2.529 -1.07055,-3.84806 -0.053,-0.43231 -0.12968,-0.85315 -0.0855,-1.29165 0.0948,-0.94059 0.64999,-1.73394 1.21835,-2.45227 1.27656,-1.3752 2.72864,-2.57131 3.98645,-3.96542 1.00742,-1.20382 1.92485,-2.46496 2.104,-4.07351 0.17452,-1.45227 0.1673,-2.88462 -0.39573,-4.26425 -0.74413,-1.73945 -1.85028,-3.26977 -3.10544,-4.6809 -0.76822,-0.80351 -0.91233,-0.98548 -1.76144,-1.74089 -1.5106,-1.3439 -3.15419,-2.52947 -4.72414,-3.80078 -1.30934,-1.02359 -2.63319,-2.03185 -3.9193,-3.08473 -0.71006,-0.5813 -1.57278,-1.30278 -2.22387,-1.96185 -0.27049,-0.2738 -0.51217,-0.57463 -0.76826,-0.86194 -0.2502,-0.39692 -0.62512,-0.79772 -0.57512,-1.31981 0.0792,-0.82671 1.33713,-1.38331 1.899,-1.81756 1.29884,-0.86671 2.65474,-1.69887 3.73107,-2.84377 0.6873,-0.83746 1.4042,-1.79633 1.075,-2.93575 -0.0816,-0.13585 -0.14456,-0.28482 -0.24481,-0.40755 -0.1097,-0.13429 -0.23095,-0.26613 -0.3791,-0.356228 -1.82458,-1.109627 -3.48552,-2.166773 -5.4739,-2.822822 -1.27815,-0.438878 -2.56689,-0.847162 -3.87638,-1.183261 -1.32827,-0.341807 -2.66392,-0.6605 -4.021,-0.867076 -1.26646,-0.185139 -2.54065,-0.306124 -3.82011,-0.354118 -0.68835,-0.02484 -1.39159,-0.0088 -2.07079,-0.120664 z"
inkscape:label="头发部件17" /><path
style="display:inline;opacity:1;fill:#1a1a1a;fill-opacity:0.465732;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
id="path7"
d="m 472.56515,132.89174 c 0.0148,0.77317 -0.10076,1.54619 -0.19578,2.31209 -0.22663,1.66469 -0.65036,3.29152 -1.09324,4.90909 -0.61155,2.16519 -1.31643,4.30298 -2.07388,6.4211 -0.85906,2.40245 -1.88628,4.73722 -3.04362,7.0095 -1.12519,2.18837 -2.40662,4.28829 -3.77255,6.3331 -1.20044,1.78353 -2.51232,3.48695 -3.85961,5.16088 -0.77567,0.94691 -1.56273,1.88443 -2.34905,2.82249 0,0 3.09439,1.85764 3.09439,1.85764 v 0 c 0.78058,-0.94434 1.56325,-1.88703 2.331,-2.84187 1.33972,-1.69155 2.6509,-3.40675 3.84535,-5.20534 1.36483,-2.06191 2.64248,-4.17929 3.77385,-6.37958 1.16439,-2.28454 2.19746,-4.6314 3.07763,-7.04092 0.77266,-2.11878 1.49327,-4.25705 2.12518,-6.4224 0.46749,-1.62041 0.91079,-3.25003 1.1854,-4.9163 0.11547,-0.76691 0.21504,-1.53597 0.32143,-2.30416 z"
inkscape:label="头发部件16" /><path
style="display:inline;opacity:1;fill:#fff9f6;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 322.62295,86.032787 c 0,0 75.43341,26.120553 110.16394,89.180323 33.44261,60.72132 33.04918,62.42623 33.04918,62.42623 0,0 0.52459,8.39345 -19.93443,11.54099 -2.62295,0 14.68852,30.95082 19.93443,37.2459 -14.68853,7.34426 -16.78689,8.91803 -16.78689,8.91803 0,0 6.29508,7.86885 4.72131,15.73771 -17.31147,-0.52459 -22.55738,-1.04918 -22.55738,-1.04918 0,0 -4.19672,17.31147 -15.7377,33.04918 C 410.22951,333.11475 410.22951,320 410.22951,320 c 0,0 -24.13115,36.72131 -72.39344,41.44262 -1.57377,-2.62295 12.59016,-14.16393 17.83606,-28.32787 -11.54098,1.57377 -14.68852,2.09836 -14.68852,2.09836 l 10.4918,-16.78688 c 0,0 11.01639,-24.13115 9.44262,-28.32787 -1.04918,-2.62295 -32,-57.70492 -32,-57.70492 0,0 -24.65573,33.04918 -43.54098,49.83607 -6.81967,-4.19672 -13.63934,-14.68853 -13.63934,-14.68853 l -12.59017,11.54099 c 0,0 -48.43778,-50.76436 -42.0945,-90.18318 -3.14754,12.06557 0.12729,56.60941 -5.6432,62.3799 -5.24591,-2.09836 -16.2623,-14.68853 -14.16394,-40.91803 -0.52459,-0.52459 -29.90164,41.44262 -29.90164,41.44262 0,0 -4.19672,48.78688 16.2623,73.44262 1.57377,2.62295 -1.57377,16.2623 -31.47541,-2.62295 1.04918,-1.57377 -1.04918,9.96721 -1.04918,9.96721 0,0 -46.68853,2.09836 -45.63935,-60.85245 -2.62295,-2.62296 -13.639341,34.09836 -13.639341,34.09836 0,0 -24.131151,-32 -0.52459,-92.85246 11.803281,-30.42623 23.344261,-52.85246 31.934421,-67.67213 8.59017,-14.81968 14.22951,-22.03279 14.22951,-22.03279"
id="path8"
sodipodi:nodetypes="csccccccccccccccccccccccccccssc"
inkscape:label="头发部件15 头发底色" /><path
id="path36"
style="fill:#fffdfc;stroke-width:7.4278;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
inkscape:label="头发打光"
d="m 355.67214,146.88524 a 91.803276,41.967213 0 0 1 -91.80327,41.96721 91.803276,41.967213 0 0 1 -91.80328,-41.96721 91.803276,41.967213 0 0 1 91.80328,-41.96721 91.803276,41.967213 0 0 1 91.80327,41.96721 z" /><path
style="fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 349.37705,329.44262 c 0,0 71.34426,-41.96721 78.68852,-91.27868 0,1.04918 26.22951,28.32786 26.22951,28.32786 l 11.54099,24.13115 -15.73771,7.34426 2.09836,13.63935 -22.03279,-1.04918 -12.59016,30.42623 -12.59016,-17.83607 -23.08197,26.22951 -45.11475,14.68852 z"
id="path37"
inkscape:label="头发阴影"
sodipodi:nodetypes="cccccccccccc" /><path
style="fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 325.2459,144.78689 c 0,0 10.49181,44.06557 2.09836,78.68852 0,10.4918 33.57377,60.85246 33.57377,60.85246 0,0 14.68853,-71.34426 -25.18032,-132.19672 -8.39345,-5.2459 -10.49181,-7.34426 -10.49181,-7.34426 z"
id="path38"
inkscape:label="头发阴影2" /><path
style="fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 186.7541,166.81967 23.08197,1.04918 c 0,0 -14.68853,26.22951 -13.63935,46.16394 -2.09836,-3.14754 -22.03279,31.47541 -22.03279,31.47541 0,0 -9.44262,-14.68853 -3.14754,-50.36066 9.44263,-16.78688 15.73771,-28.32787 15.73771,-28.32787 z"
id="path39"
inkscape:label="头发阴影" /><path
id="path36-5"
style="fill:#fffdfc;stroke-width:6.94619;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
inkscape:label="头发打光"
transform="matrix(0.99965843,-0.02613486,0,1,0,0)"
d="m 340.28017,112.23744 a 80.289719,41.967213 0 0 1 -80.28972,41.96721 80.289719,41.967213 0 0 1 -80.28972,-41.96721 80.289719,41.967213 0 0 1 80.28972,-41.967211 80.289719,41.967213 0 0 1 80.28972,41.967211 z" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#a18f90;stroke-width:7.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 138.62295,122.4918 c 0,0 53.63935,-52.983604 92.98361,-58.229505"
id="path65"
sodipodi:nodetypes="cc" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 436.45902,268.06557 c 0,0 3.67213,18.36066 -2.62295,42.49181"
id="path17"
inkscape:label="头发部件7" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 418.09836,305.83607 -8.91803,17.83606"
id="path16"
inkscape:label="头发部件8" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 377.70492,300.59016 c 0,0 2.62295,12.06558 -22.03279,32"
id="path15"
inkscape:label="头发部件9" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:4.2;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 284.32787,97.573771 c 0,0 12.06557,39.344259 -27.80328,119.081969 0.52459,0 53.5082,-38.81967 56.13115,-89.18033"
id="path10"
inkscape:label="头发部件14" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:5.1;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 272.78689,87.081967 c 0,0 13.63934,-8.393442 18.88524,-0.52459 17.17267,10.839383 13.03281,30.236103 24.19672,44.393443 1.79263,1.85342 7.1718,-0.78131 7.80328,1.77049 4.88126,19.72513 14.81582,50.80617 4.32401,100.11764"
id="path11"
sodipodi:nodetypes="csssc"
inkscape:label="头发部件13" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:4.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 171.00392,241.8537 c 0,0 -28.56248,-60.09248 92.36438,-147.634619 2.22564,0.741882 -59.35061,54.157419 -66.02755,111.282379"
id="path12"
inkscape:label="头发部件12" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:3;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 327.34426,143.7377 c 0,0 25.18033,20.45902 32,65.57378"
id="path13"
inkscape:label="头发部件11" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 362.4918,268.59016 -3.14754,19.93443"
id="path14"
inkscape:label="头发部件10" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 421.77049,344.13115 c 0,0 -2.62295,58.22951 -33.57377,111.7377 -30.95082,53.5082 38.29508,15.73771 38.29508,15.73771"
id="path18"
inkscape:label="头发部件6" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 395.01639,348.32787 12.06558,60.32787"
id="path19"
inkscape:label="头发部件5" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 371.93443,423.86885 c 0,0 30.95082,-25.70492 24.65573,-73.96721"
id="path20"
inkscape:label="头发部件4" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 332.06557,427.54098 c 0,0 36.72132,1.57377 38.81968,-29.37705"
id="path21"
inkscape:label="头发部件3" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 372.45902,427.01639 -2.09836,-28.85246"
id="path22"
inkscape:label="头发部件2" /><path
style="fill:#93cefc;fill-opacity:1;stroke:#4d4e59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 377.50929,228.61046 c 0,0 24.13115,1.57377 24.13115,10.49181 -0.52459,3.67213 -11.54099,17.83606 -23.60656,18.88524 -2.09836,-1.57377 -0.52459,-29.37705 -0.52459,-29.37705 z"
id="path34"
sodipodi:nodetypes="cccc"
inkscape:label="兔子发卡后" /><path
style="opacity:1;fill:#fffdfe;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 350.95082,235.54098 c 0,0 -6.55738,-22.03278 1.04918,-22.03278 7.60656,0 13.37705,13.90164 13.37705,13.90164 0,0 5.05941,-17.72738 9.96722,-15.21311 11.79758,6.04389 3.40983,21.77048 3.40983,21.77048 0,0 18.62295,25.96722 -12.06558,27.27869 -30.16717,1.28919 -15.7377,-25.70492 -15.7377,-25.70492 z"
id="path24"
sodipodi:nodetypes="cscscsc"
inkscape:label="兔子发卡" /><path
style="display:inline;opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 332.06557,425.96721 c 0,0 10.49181,-25.18032 6.29509,-49.83606"
id="path23"
inkscape:label="头发部件" /><path
style="display:inline;opacity:1;fill:#e85240;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 192.2623,268.85246 c 0,0 -15.47541,22.03279 -2.62296,35.40984 42.7541,0.26229 43.27869,0.52459 43.27869,0.52459 0,0 11.27869,-8.39345 5.5082,-32 -25.44262,-9.96722 -46.16393,-3.93443 -46.16393,-3.93443 z"
id="path25"
inkscape:label="左眼白" /><path
style="opacity:1;fill:#fbb579;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 188.06557,301.11475 c -0.44151,-8.78192 13.4528,-14.01636 22.29509,-14.42623 8.67244,-0.402 23.2398,3.38526 23.08196,12.06558 -0.0775,4.26198 -11.54098,5.5082 -11.54098,5.5082 0,0 -33.2673,8.16555 -33.83607,-3.14755 z"
id="path27"
sodipodi:nodetypes="saacs"
inkscape:label="左眼瞳" /><path
id="path26"
style="display:inline;opacity:1;fill:#ffffff;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
inkscape:label="眼光"
d="m 217.96721,273.04919 a 7.8688526,7.0819674 0 0 1 -7.86885,7.08197 7.8688526,7.0819674 0 0 1 -7.86885,-7.08197 7.8688526,7.0819674 0 0 1 7.86885,-7.08196 7.8688526,7.0819674 0 0 1 7.86885,7.08196 z" /><path
style="fill:none;fill-opacity:1;stroke:#ebc2bf;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 174.16393,245.5082 c 15.08644,-4.76649 31.53934,-7.45293 47.09358,-3.51527 3.29779,0.83486 6.51821,1.99257 9.56216,3.51527"
id="path42"
inkscape:path-effect="#path-effect42"
inkscape:original-d="m 174.16393,245.5082 c 11.60252,-3.79212 36.99244,-9.83166 56.65574,0"
transform="translate(0,4)" /><path
style="opacity:1;fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.945;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 477.77235,132.79697 c 0,0 13.35388,10.01542 14.83765,17.43424 1.48376,7.41883 1.48376,57.4959 1.48376,57.4959 l -4.45129,1.11282 -30.78813,-37.46507 c 0,0 8.90259,-14.46671 11.12824,-22.62741 2.22565,-8.16071 7.78977,-15.95048 7.78977,-15.95048 z"
id="path67" /><path
style="fill:#e85240;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 192.2623,268.85246 c 0,0 -15.47541,22.03279 -2.62296,35.40984 42.7541,0.26229 43.27869,0.52459 43.27869,0.52459 0,0 11.27869,-8.39345 5.5082,-32 -25.44262,-9.96722 -46.16393,-3.93443 -46.16393,-3.93443 z"
id="path25-9"
inkscape:label="左眼遮挡上" /><path
style="display:inline;fill:#e85240;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 325.52138,272.81871 c 17.02505,1.06627 38.81967,23.08196 17.83607,48.26229 -1.57377,0.52459 -48.98481,-5.30599 -49.57378,-9.18033 -0.80561,-5.29953 -7.95666,-41.56799 31.73771,-39.08196 z"
id="path40"
sodipodi:nodetypes="scss"
inkscape:label="右眼白" /><path
style="fill:#fbb579;fill-opacity:1;stroke:none;stroke-width:7.14054;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 293.04592,306.32136 c 2.55323,-8.53469 20.43105,-9.52962 30.78942,-7.36618 10.1594,2.12188 25.66784,9.9581 22.45763,18.22228 -1.5762,4.05766 -15.25453,1.93723 -15.25453,1.93723 0,0 -41.28165,-1.79873 -37.99252,-12.79333 z"
id="path27-0"
sodipodi:nodetypes="saacs"
inkscape:label="左眼瞳" /><path
style="display:inline;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 325.52138,272.81871 c 17.02505,1.06627 38.81967,23.08196 17.83607,48.26229 -1.57377,0.52459 -48.98481,-5.30599 -49.57378,-9.18033 -0.80561,-5.29953 -7.95666,-41.56799 31.73771,-39.08196 z"
id="path40-2"
sodipodi:nodetypes="scss"
inkscape:label="右眼遮挡上" /><path
style="fill:none;fill-opacity:1;stroke:#ebc2bf;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 309.32819,258.43459 c 0,0 26.2295,-5.2459 36.72131,4.19672"
id="path43" /><path
id="path26-9"
style="display:inline;fill:#ffffff;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
inkscape:label="眼光"
d="m 329.6342,284.99301 a 7.8688526,7.0819674 0 0 1 -7.86885,7.08197 7.8688526,7.0819674 0 0 1 -7.86885,-7.08197 7.8688526,7.0819674 0 0 1 7.86885,-7.08197 7.8688526,7.0819674 0 0 1 7.86885,7.08197 z" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:14.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 170.4918,266.4918 c 0,0 32,-19.93442 70.29509,-2.09836"
id="path28"
inkscape:label="眉毛" /><path
style="fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:14.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 292.9971,274.17683 c 0,0 35.55098,-12.55026 69.08832,13.13797"
id="path28-2"
inkscape:label="眉毛" /><path
style="display:inline;opacity:1;fill:none;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 431.03375,176.1971 18.17612,-17.43424 41.54542,54.15742"
id="path30"
inkscape:label="蓝色发卡遮挡上" /><path
style="opacity:1;fill:none;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 454.0321,267.44865 43.21466,-23.92571"
id="path31"
sodipodi:nodetypes="cc"
inkscape:label="蓝色发卡遮挡下" /><path
style="display:inline;fill:#fff9f6;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 164.23888,424.64081 -31.07097,26.05282 c 0,0 -5.19317,18.17613 25.96589,28.93342 31.15907,10.7573 213.29123,1.48377 213.29123,1.48377 0,0 52.30272,11.87012 80.86519,-37.09413 -0.37094,0.74188 -8.90259,19.28895 -8.90259,19.28895 l 1.48377,19.28894 c 0,0 57.73516,-9.41425 30.45647,-148.95524 4.19672,-9.44262 8.39344,-15.21311 8.39344,-15.21311 l -3.14754,-21.77049 c 0,0 5.65758,-40.33533 7.88323,-45.52851 2.22564,-5.19317 8.51558,-6.35116 9.64447,-11.12823 2.06907,-8.75561 -2.78206,-20.40177 -6.67694,-26.15136 -0.74189,-3.33847 6.86241,-35.05395 2.04017,-58.05231 -7.04788,-11.87012 -4.82223,-11.87012 -18.918,-25.59495 0,0.37094 8.90259,-44.88389 -0.74188,-73.446367 -2.96753,0 -32.27189,-10.757296 -96.81567,23.740241 -0.37094,1.112824 -26.33683,-30.046242 -153.56969,-15.950474 -0.74188,0 -43.40012,-37.836009 -66.76942,-40.432598 -1.11283,-0.370941 -22.62742,-4.080354 -25.22401,48.964247 0,1.112824 -44.14201,15.950474 -56.01213,64.914721 -11.87012,48.96425 -11.128238,80.86519 -11.128238,80.86519 0,0 -18.904912,27.6632 -9.798121,25.03308 5.346826,-1.54421 4.975885,23.56023 4.975885,23.56023 l -2.225648,39.31977 c 0,0 -14.466709,35.23942 -14.83765,59.72155 -0.370942,24.48212 -0.741883,34.12659 4.822236,48.5933 5.564119,14.46671 20.772711,46.7386 42.658245,63.06002 8.902591,0.37094 18.918001,1.8547 18.918001,1.8547 l 3.70942,-20.77271 c 0,0 18.918,15.95048 32.27189,14.83765 13.35388,-1.11282 -0.74189,-0.74188 -0.74189,-0.74188 l -13.72482,-17.80518 z"
id="path2-4"
sodipodi:nodetypes="ccscccccccsaccccccccscsccsscccsccc"
inkscape:label="上发层" /><path
style="fill:none;fill-opacity:1;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 197.5082,220.06557 18.36065,-12.32786"
id="path46" /><path
style="fill:none;fill-opacity:1;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 327.54114,234.24941 c 0,0 -6.27192,4.90452 -15.85821,-0.991"
id="path47"
sodipodi:nodetypes="cc" /><path
style="display:inline;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 322.62295,86.03279 c 0,0 75.43341,26.12055 110.16394,89.18032 33.44261,60.72132 33.04918,62.42623 33.04918,62.42623 0,0 0.52459,8.39345 -19.93443,11.54099 -2.62295,0 14.68852,30.95082 19.93443,37.2459 -14.68853,7.34426 -16.78689,8.91803 -16.78689,8.91803 0,0 6.29508,7.86885 4.72131,15.73771 -17.31147,-0.52459 -22.55738,-1.04918 -22.55738,-1.04918 0,0 -4.19672,17.31147 -15.7377,33.04918 C 410.22951,333.11475 410.22951,320 410.22951,320 c 0,0 -24.13115,36.72131 -72.39344,41.44262 -1.57377,-2.62295 12.59016,-14.16393 17.83606,-28.32787 -11.54098,1.57377 -14.68852,2.09836 -14.68852,2.09836 l 10.4918,-16.78688 c 0,0 11.01639,-24.13115 9.44262,-28.32787 -1.04918,-2.62295 -32,-57.70492 -32,-57.70492 0,0 -24.65573,33.04918 -43.54098,49.83607 -6.81967,-4.19672 -13.63934,-14.68853 -13.63934,-14.68853 l -12.59017,11.54099 c 0,0 -48.43778,-50.76436 -42.0945,-90.18318 -3.14754,12.06557 0.12729,56.60941 -5.6432,62.3799 -5.24591,-2.09836 -16.2623,-14.68853 -14.16394,-40.91803 -0.52459,-0.52459 -29.90164,41.44262 -29.90164,41.44262 0,0 -4.19672,48.78688 16.2623,73.44262 1.57377,2.62295 -1.57377,16.2623 -31.47541,-2.62295 1.04918,-1.57377 -1.04918,9.96721 -1.04918,9.96721 0,0 -46.68853,2.09836 -45.63935,-60.85245 -2.62295,-2.62296 -13.63934,34.09836 -13.63934,34.09836 0,0 -24.13115,-32 -0.52459,-92.85246 11.80328,-30.42623 23.34426,-52.85246 31.93442,-67.67213 8.59017,-14.81968 14.22951,-22.03279 14.22951,-22.03279"
id="path8-0"
sodipodi:nodetypes="csccccccccccccccccccccccccccssc"
inkscape:label="头发部件15 头发底色" /><path
id="path44-3"
style="fill:#a88a8f;stroke:#4c4f59;stroke-width:2.8623;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 216.29183,255.53888 a 8.8904896,7.4928694 0 0 1 -8.89049,7.49287 8.8904896,7.4928694 0 0 1 -8.89049,-7.49287 8.8904896,7.4928694 0 0 1 8.89049,-7.49287 8.8904896,7.4928694 0 0 1 8.89049,7.49287 z" /><path
id="path44"
style="fill:#a88a8f;stroke:#4c4f59;stroke-width:2.8623;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 336.16933,270.5473 a 8.8904896,7.4928694 0 0 1 -8.89049,7.49287 8.8904896,7.4928694 0 0 1 -8.89049,-7.49287 8.8904896,7.4928694 0 0 1 8.89049,-7.49287 8.8904896,7.4928694 0 0 1 8.89049,7.49287 z" /><path
style="opacity:1;fill:none;fill-opacity:0;stroke:#a18f90;stroke-width:10.4;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 318.7189,85.461784 c 0,0 50.44801,17.063296 76.78484,43.400126 26.33683,26.33683 32.64283,39.31978 32.64283,39.31978"
id="path32"
inkscape:label="上方头发边缘加厚" /><path
style="fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 186.7541,224.52459 10.4918,1.04918"
id="path45" /><path
style="opacity:1;fill:#fcbeb5;fill-opacity:1;stroke:#e99c9b;stroke-width:3.4;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 250.94176,349.98308 c 0,0 -13.91029,-8.90259 -10.0154,-16.69235 3.89488,-7.78977 17.80517,-4.4513 17.80517,-4.4513 0,0 12.98295,-1.29829 20.03083,-3.89488 7.04788,-2.59659 9.64447,5.0077 9.64447,5.0077 0,0 4.76461,6.06544 -4.26583,16.50689 -5.93505,6.86241 -21.14364,7.04788 -21.14364,7.04788"
id="path48"
sodipodi:nodetypes="cscscsc" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e99c9b;stroke-width:1.8;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 276.20884,325.76493 c 0,0 4.35031,3.01904 5.37456,7.95235 0.57119,0.10629 3.33355,-3.08729 2.97647,-6.87263 -0.67968,-1.48531 -8.35103,-1.07972 -8.35103,-1.07972 z"
id="path49" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e99c9b;stroke-width:1.8;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 245.0067,329.39584 -4.17308,2.78206 -0.0927,6.0278 -0.0956,-0.0355 7.07328,-8.8174 z"
id="path50"
sodipodi:nodetypes="cccccc" /><path
style="fill:none;fill-opacity:1;stroke:#e99c9b;stroke-width:3.4;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 250.94176,349.98308 c 0,0 -13.91029,-8.90259 -10.0154,-16.69235 3.89488,-7.78977 17.80517,-4.4513 17.80517,-4.4513 0,0 12.98295,-1.29829 20.03083,-3.89488 7.04788,-2.59659 9.64447,5.0077 9.64447,5.0077 0,0 4.76461,6.06544 -4.26583,16.50689 -5.93505,6.86241 -21.14364,7.04788 -21.14364,7.04788"
id="path48-2"
sodipodi:nodetypes="cscscsc" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 190.84928,314.92913 6.12053,15.02312"
id="path51" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 204.94505,316.96931 5.56412,14.28124"
id="path52" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 217.92799,317.34025 7.41883,16.50689"
id="path53" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 300.64789,328.46849 6.86242,18.17612"
id="path54" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 312.70348,329.21037 6.67695,17.0633"
id="path55" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 326.24284,328.65396 7.04788,17.99065"
id="path56" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#e49589;stroke-width:4.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 218.4918,348.98361 c -1.87307,1.30472 -3.33608,3.11401 -4.70333,4.91603 -1.02497,1.35089 -2.00187,2.75003 -2.77208,4.2643"
id="path61"
inkscape:path-effect="#path-effect61"
inkscape:original-d="m 218.4918,348.98361 c -1.70592,0.99645 -5.82749,5.88449 -7.47541,9.18033"
transform="rotate(-3.3120949,219.55256,344.63259)" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:3.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 127.47541,375.08197 c 0,0 -14.42623,-19.14754 -12.06557,-59.54099"
id="path92" /><path
style="opacity:1;fill:#ffffff;fill-opacity:0.734285;stroke:#c1545a;stroke-width:7.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 136.39344,322.09836 c 0,0 9.44263,-18.88525 -4.19672,-39.86885 -3.14754,-0.52459 27.80328,-27.80328 5.2459,-77.11476 -1.57377,0.52459 23.60656,-55.60655 -24.13115,-82.88524 -4.19672,-8.39344 -45.639336,-69.245904 -89.180316,13.11475 0,11.54099 -1.04918,36.19672 -1.04918,36.19672 0,0 -14.6885299,38.81968 -6.29509,71.86886 2.09837,3.14754 -20.9835999,49.83606 11.0164,106.4918 0.52459,0.52459 -16.78689,57.70492 12.59016,82.88525 1.04918,4.72131 13.63935,33.57377 42.49181,39.34426 12.59016,0.52459 19.409826,-0.52459 19.409826,-0.52459 0,0 31.47541,-4.72131 47.73771,-67.14754 -2.09836,-27.80328 -4.19672,-50.36066 -11.0164,-70.81968"
id="path93" /><path
id="path101"
style="opacity:1;fill:#ffffff;fill-opacity:0.734285;stroke:#000000;stroke-width:9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 109.69769,219.48859 c 0,0 -15.208592,-11.87012 -21.143652,-19.65989 m -37.09413,20.03083 22.25648,-21.88553 m 0.37094,-14.09577 5.93506,41.91636 m -35.23942,-24.11118 56.012132,-4.82224 m 11.12824,-17.43424 -19.288952,0.37094 m -0.37094,-0.74188 -1.48376,-29.6753 m -38.94883,3.33847 37.836,-2.96753 m -24.85306,-11.49918 0.74188,39.69072"
inkscape:label="杂" /><path
id="path111"
style="opacity:1;fill:#ffffff;fill-opacity:0.734285;stroke:#000000;stroke-width:9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 58.507798,352.28556 54.157422,-3.33847 m -46.367662,-13.72483 35.610372,-2.96753 m -21.514602,-32.27189 2.59659,33.38472 m -18.918,-16.32142 37.094122,-3.33847 m 1.48377,18.54706 0.74188,-33.75565 m -0.37094,0.37094 -40.803542,2.22565 m 0,-0.37094 4.4513,33.38471 m 27.44965,-48.22237 3.33847,-14.4667 m 0,-1.48377 -27.44965,1.48377 m -1.48377,14.83765 0.74188,-20.03083"
inkscape:label="鱼" /><path
d="m 69.453989,376.46984 c -4.220665,0.22086 -8.079406,1.8518 -10.783191,4.54882 -2.980547,2.98155 -4.241957,7.10139 -3.534076,11.54824 0.904812,5.67433 4.763552,11.7394 11.384623,17.90216 1.405115,1.30393 2.39508,2.14912 4.390984,3.74184 3.507462,2.79468 6.679618,5.01174 11.04931,7.70876 1.559468,0.96838 4.635815,2.76496 5.796096,3.38931 l 0.335314,0.18263 0.48966,-0.26758 c 1.783005,-0.98111 4.960482,-2.86264 6.844609,-4.05189 7.126702,-4.49782 12.832312,-9.01266 17.063632,-13.50625 6.86057,-7.28827 9.77725,-14.38543 8.43068,-20.49298 -0.93142,-4.20475 -4.04502,-7.68327 -8.46261,-9.45012 -1.90011,-0.76026 -3.74699,-1.15525 -5.83869,-1.24869 -2.7091,-0.12316 -5.37562,0.33977 -7.91973,1.37185 -4.332435,1.75413 -7.951675,5.17315 -10.229661,9.66674 -0.159681,0.3143 -0.308701,0.59038 -0.329985,0.60737 -0.0692,0.0553 -0.143707,-0.051 -0.431122,-0.62859 -0.681263,-1.35913 -2.01719,-3.3341 -3.065699,-4.52331 -1.07513,-1.22747 -2.804909,-2.72251 -4.092931,-3.54224 -3.326503,-2.11937 -7.222503,-3.15569 -11.097213,-2.95607 z"
id="path1-4"
style="fill:#d96477;fill-opacity:1;stroke:none;stroke-width:0.00475452" /></g></svg>

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -1,80 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Offline | SukiSU-Ultra</title>
<meta name="theme-color" content="#64edff" />
<style>
html,
body {
height: 100%;
margin: 0;
font-family:
system-ui,
-apple-system,
Segoe UI,
Roboto,
Ubuntu,
Cantarell,
'Noto Sans',
sans-serif;
}
.wrap {
min-height: 100%;
display: grid;
place-items: center;
padding: 2rem;
}
.card {
max-width: 560px;
background: #fff;
color: #111;
border-radius: 12px;
box-shadow: 0 6px 30px rgba(0, 0, 0, 0.06);
padding: 24px;
}
.card h1 {
margin: 0 0 8px;
font-size: 1.4rem;
}
.card p {
margin: 0.5rem 0;
line-height: 1.5;
}
.muted {
color: #666;
font-size: 0.9rem;
}
.actions {
margin-top: 1rem;
display: flex;
gap: 0.75rem;
}
.btn {
padding: 0.6rem 0.9rem;
border-radius: 8px;
border: 1px solid #d0d0d0;
background: #f7f7f7;
color: #111;
text-decoration: none;
}
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<h1>You're offline</h1>
<p>We couldn't load this page because your device has no internet connection.</p>
<p class="muted">
When you're back online, try again. Some pages you visited before may still work from
cache.
</p>
<div class="actions">
<a class="btn" href="/">Go to home</a>
<a class="btn" href="/guide/">Docs guide</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,76 +0,0 @@
User-agent: *
Allow: /
Disallow: /admin/
Disallow: /.git/
Disallow: /node_modules/
Disallow: /api/
Disallow: /.vitepress/
# Sitemap
Sitemap: https://sukisu.org/sitemap.xml
# Crawl-delay for high-traffic optimization
Crawl-delay: 0.5
# Major search engines (global optimization)
User-agent: Googlebot
Allow: /
Crawl-delay: 0.5
User-agent: Bingbot
Allow: /
Crawl-delay: 1
User-agent: Slurp
Allow: /
Crawl-delay: 1
User-agent: DuckDuckBot
Allow: /
Crawl-delay: 0.5
User-agent: Baiduspider
Allow: /
Crawl-delay: 2
# Asian search engines (for China, Japan, etc.)
User-agent: YandexBot
Allow: /
Crawl-delay: 1
User-agent: NaverBot
Allow: /
Crawl-delay: 1
User-agent: SogouSpider
Allow: /
Crawl-delay: 2
# Block resource-intensive bots for performance
User-agent: AhrefsBot
Disallow: /
User-agent: MJ12bot
Disallow: /
User-agent: SemrushBot
Disallow: /
User-agent: DotBot
Disallow: /
# Block AI training crawlers to save bandwidth
User-agent: GPTBot
Disallow: /
User-agent: ChatGPT-User
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: anthropic-ai
Disallow: /
User-agent: Claude-Web
Disallow: /

View File

@@ -1,535 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="512"
viewBox="0 0 512 512"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
sodipodi:docname="zako.optizmism.svg"
inkscape:export-filename="zako.plain.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="1"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showguides="false"
inkscape:zoom="2"
inkscape:cx="219"
inkscape:cy="370"
inkscape:window-width="1280"
inkscape:window-height="702"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="false"><inkscape:page
x="0"
y="0"
width="512"
height="512"
id="page2"
margin="0"
bleed="0" /></sodipodi:namedview><defs
id="defs1"><inkscape:path-effect
effect="simplify"
id="path-effect79"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect78"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect77"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect76"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect75"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect61"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /><inkscape:path-effect
effect="simplify"
id="path-effect42"
is_visible="true"
lpeversion="1.3"
threshold="32.467532"
steps="1"
smooth_angles="360"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false" /></defs><g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1"><path
style="display:inline;opacity:1;fill:#fff9f6;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 164.23888,424.64081 -31.07097,26.05282 c 0,0 -5.19317,18.17613 25.96589,28.93342 31.15907,10.7573 213.29123,1.48377 213.29123,1.48377 0,0 52.30272,11.87012 80.86519,-37.09413 -0.37094,0.74188 -8.90259,19.28895 -8.90259,19.28895 l 1.48377,19.28894 c 0,0 57.73516,-9.41425 30.45647,-148.95524 4.19672,-9.44262 8.39344,-15.21311 8.39344,-15.21311 l -3.14754,-21.77049 c 0,0 5.65758,-40.33533 7.88323,-45.52851 2.22564,-5.19317 9.64447,-11.12823 9.64447,-11.12823 0,0 -2.78206,-20.40177 -6.67694,-26.15136 -0.74189,-3.33847 6.86241,-35.05395 2.04017,-58.05231 -7.04788,-11.87012 -4.82223,-11.87012 -18.918,-25.59495 0,0.37094 8.90259,-44.88389 -0.74188,-73.446367 -2.96753,0 -32.27189,-10.757296 -96.81567,23.740241 -0.37094,1.112824 -26.33683,-30.046242 -153.56969,-15.950474 -0.74188,0 -43.40012,-37.836009 -66.76942,-40.432598 -1.11283,-0.370941 -22.62742,-4.080354 -25.22401,48.964247 0,1.112824 -44.14201,15.950474 -56.01213,64.914721 -11.87012,48.96425 -11.128238,80.86519 -11.128238,80.86519 0,0 -18.904912,27.6632 -9.798121,25.03308 5.346826,-1.54421 4.975885,23.56023 4.975885,23.56023 l -2.225648,39.31977 c 0,0 -14.466709,35.23942 -14.83765,59.72155 -0.370942,24.48212 -0.741883,34.12659 4.822236,48.5933 5.564119,14.46671 20.772711,46.7386 42.658245,63.06002 8.902591,0.37094 18.918001,1.8547 18.918001,1.8547 l 3.70942,-20.77271 c 0,0 18.918,15.95048 32.27189,14.83765 13.35388,-1.11282 -0.74189,-0.74188 -0.74189,-0.74188 l -13.72482,-17.80518 z"
id="path2"
sodipodi:nodetypes="ccscccccccscccccccccscsccsscccsccc"
inkscape:label="整个人的边框" /><path
id="path91"
style="opacity:1;fill:#93d4fa;fill-opacity:1;stroke:#4c4f59;stroke-width:6.9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 133.91016,110.16992 -69.738285,47.48047 18.263976,30.75433 -31.617492,55.67536 28.5625,17.0625 14.837891,-1.48438 2.226562,-55.64062 2.577791,-4.39109 18.193697,-21.94485 23.74023,-49.70703 z"
sodipodi:nodetypes="ccccccccccc" /><path
style="display:inline;opacity:1;fill:#ace0fe;fill-opacity:1;stroke:#4c4f59;stroke-width:6.54357;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 217.01397,405.82758 c 0,0 7.41728,-25.94462 41.16595,-27.07265 33.74866,-1.12802 53.77534,4.1361 53.77534,4.1361 0,0 11.12594,6.39215 17.80149,14.66435 6.67557,8.2722 28.92743,40.60895 41.53682,48.88115 12.60939,8.27219 15.20545,15.41636 15.20545,15.41636 l -1.85433,11.28027 c 0,0 -14.83458,10.15224 -30.78175,11.65627 -15.94717,1.50404 -147.60404,0 -147.60404,0 l -30.04002,-3.00807"
id="path71"
inkscape:label="衣服底色" /><path
style="fill:#ffffff;fill-opacity:1;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 363.54127,444.68178 c 0,0 -6.89429,4.06586 -4.86135,6.8059 2.03293,2.74004 5.92201,3.44715 5.92201,3.44715 0,0 4.5962,0.26516 5.12653,-3.53554 0.53033,-3.8007 -3.00521,-6.8059 -3.00521,-6.8059"
id="path60" /><path
style="fill:#ffffff;fill-opacity:1;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 349.67678,459.82583 c 0,0 4.5,-7.25 7,-5.5 2.5,1.75 -4.25,7.25 -4.25,7.25"
id="path59" /><path
id="path73-8"
style="fill:#ffffff;stroke-width:8.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 228.96985,465.10941 a 8.0341015,5.6995392 0 0 1 -8.0341,5.69953 8.0341015,5.6995392 0 0 1 -8.03411,-5.69953 8.0341015,5.6995392 0 0 1 8.03411,-5.69954 8.0341015,5.6995392 0 0 1 8.0341,5.69954 z" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 220.85246,408.91803 c -0.22678,0.7374 0.77024,1.47887 0.20184,0.3709 -0.55918,-1.08999 0.45864,1.0858 0.58075,1.39596 2.10089,5.33666 1.96407,11.16153 3.60742,16.6426 0.30205,1.00744 0.58829,2.039 0.5936,3.09874"
id="path75"
inkscape:path-effect="#path-effect75"
inkscape:original-d="m 220.85246,408.91803 c 0.0874,0.34973 -0.0874,1.13662 0.26229,1.04918 0.34973,-0.0874 -0.42351,-1.37161 -0.26229,-1.04918 2.70315,5.4063 2.32215,10.60009 3.67213,16 0.44415,1.77662 1.31148,3.74741 1.31148,5.5082" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:6.9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 225.57377,436.98361 c -1.22213,4.37272 0.0774,9.22771 2.41807,12.85195 2.34183,3.626 0.85866,8.30929 2.56554,12.06608 1.71568,3.77616 2.21068,8.39349 -0.30117,12.16042 -1.9802,2.96963 -5.23922,5.13567 -6.38674,8.61546 0,0 -0.0736,0.66095 -0.0736,0.66095"
id="path76"
inkscape:path-effect="#path-effect76"
inkscape:original-d="m 225.57377,436.98361 c -1.27879,3.14891 0.64852,9.42818 3.40984,14.95082 0.92316,1.84632 0.76302,8.34571 1.57377,9.96721 1.05661,2.11323 2.18032,6.57378 1.31147,9.18033 -1.55473,4.66419 -8.07296,8.77008 -8.07296,12.2565"
sodipodi:nodetypes="csssc" /><path
style="opacity:1;fill:#fbf3ef;fill-opacity:1;stroke:none;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 259.14754,381.90164 c 0,0 8.91803,15.47541 9.18033,20.98361 0.26229,5.50819 32.26229,-1.83607 32.26229,-1.83607 0,0 -4.45901,-16.78688 -7.34426,-19.40983 -2.88524,-2.62296 -34.09836,0.26229 -34.09836,0.26229 z"
id="path89" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:4.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 247.86885,386.36066 c 0.62439,0.76152 1.31219,1.32242 0.35251,0.10604 -0.98535,-1.24891 -0.63523,-0.56343 0.0935,0.24171 1.40155,1.54849 2.30926,3.40606 3.10338,5.34166 1.48771,3.62621 2.15649,7.53104 4.02075,11.02472 1.24886,2.3404 2.74915,4.53757 3.97084,6.89242"
id="path77"
inkscape:path-effect="#path-effect77"
inkscape:original-d="m 247.86885,386.36066 c 3.34102,3.56326 -3.68025,-4.79705 1.83607,2.09836 1.23825,1.54782 3.18087,7.73986 3.67213,9.70492 1.00623,4.02493 4.53306,8.80383 6.03279,11.80327" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:4.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 306.62295,386.88525 c 1.62958,6.07854 4.04229,11.93568 6.81967,17.57377"
id="path78"
inkscape:path-effect="#path-effect78"
inkscape:original-d="m 306.62295,386.88525 c 1.57569,6.03983 4.44925,12.83291 6.81967,17.57377" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#6c9cb2;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 335.73771,431.21312 c 0.7696,3.76393 3.24862,7.26774 5.48726,10.89168 2.09754,3.39553 3.33574,7.13199 4.87926,10.7955 2.06975,4.91248 6.75146,8.7424 7.699,13.95386 0.92029,5.06165 5.24206,8.89669 5.27874,14.19502"
id="path79"
inkscape:path-effect="#path-effect79"
inkscape:original-d="m 335.73771,431.21312 c -1.15649,1.03916 3.66917,4.97179 4.45901,8.13114 0.96754,3.87016 3.8848,6.88344 4.72131,10.22951 1.09547,4.38186 5.88097,12.49359 8.39345,16.2623 0.80744,1.21116 0.89968,3.11084 1.57377,4.45901 1.7539,3.50782 4.19672,6.9619 4.19672,10.7541" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 258.62295,384.2623 c 0,0 8.13115,6.81967 11.27869,27.01639"
id="path80" /><path
style="opacity:1;fill:#fbf3ef;fill-opacity:1;stroke:#4c4f59;stroke-width:6.9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 127.23285,329.0249 c 0,0 -2.59659,60.46343 13.72483,75.30108 8.16071,-8.16071 10.01541,-10.38636 10.01541,-10.38636 l 12.24107,8.53165 5.19317,-4.45129 2.22565,-19.65989 5.19318,-10.01542 5.93506,-12.24106 -8.53165,-8.53165 -15.95047,-19.28894 -3.33848,-4.82224 -4.45129,9.27353 c 0,0 -12.24106,0 -15.95048,-2.59659 -3.70941,-2.59658 -6.306,-1.11282 -6.306,-1.11282 z"
id="path90" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 291.67213,381.37705 c 0,0 7.08197,6.55738 10.22951,26.4918"
id="path81" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 270.16393,404.19672 c 0,0 16.00001,-3.93442 27.80328,-2.62295"
id="path82" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 253.90164,417.83607 c 0,0 37.2459,-13.11476 69.77049,-9.18033 2.09836,3.67213 5.77049,16.78688 -5.77049,20.98361 -4.72131,0 -19.14754,-1.57378 -22.55738,-0.5246 -3.40983,1.04919 -12.59016,0.5246 -18.36065,4.45902 -5.77049,3.93443 -14.68853,8.91803 -18.36066,3.14754 -3.67213,-5.77049 -4.72131,-12.32787 -4.72131,-12.32787 z"
id="path83" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 276.45902,482.62295 c 0,0 -9.70492,-1.57377 1.04918,-46.42623"
id="path84" /><path
style="opacity:1;fill:#fcf6fa;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 287.21312,432.78689 6.29508,50.88524 13.90164,-0.78688 -6.81968,-54.03279 z"
id="path88" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 287.73771,430.95082 5.77049,50.88525"
id="path85" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 300.32787,428.06557 c 0,0 2.09836,37.7705 7.34426,52.98361"
id="path86" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#494d55;stroke-width:5.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 313.18033,431.21312 c 0,0 7.60656,27.54098 10.22951,35.93442 2.62295,8.39344 -8.13115,15.73771 -8.13115,15.73771"
id="path87" /><path
style="opacity:1;fill:#bce4fd;fill-opacity:1;stroke:none;stroke-width:8.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 164.23888,424.64081 -31.07097,26.05282 5.00771,13.72483 20.95818,15.20859 17.08508,2.15431 -11.1287,-20.55238 -1.54637,-22.78395 z"
id="path74" /><path
id="path73"
style="opacity:1;fill:#ffffff;stroke-width:8.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 163.81755,451.10144 a 8.0341015,5.6995392 0 0 1 -8.0341,5.69954 8.0341015,5.6995392 0 0 1 -8.0341,-5.69954 8.0341015,5.6995392 0 0 1 8.0341,-5.69954 8.0341015,5.6995392 0 0 1 8.0341,5.69954 z" /><path
style="opacity:1;fill:#fff9f6;fill-opacity:1;stroke:none;stroke-width:8.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 338.36066,376.13115 c 0,0 1.83605,30.95081 -6.29509,51.40983 2.88525,1.04918 14.48107,0.26606 26.91267,-7.34238 12.4316,-7.60844 11.38242,-22.03467 11.38242,-22.03467 l 2.09836,28.85246 c 0,0 24.31867,-28.09873 24.2249,-52.60674 -0.0938,-24.50801 -1.66753,-26.08178 -1.66753,-26.08178 l -11.83407,-1.12685 z"
id="path72" /><path
style="opacity:1;fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:2.845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 339.7822,389.85927 c 0,0 38.20695,-7.78977 56.38307,-25.96589 5.56412,8.53165 8.16071,32.64283 8.16071,32.64283 l 2.59659,12.98294 16.32141,-62.68907 -6.67694,-5.19318 -5.93506,-17.43424 c 0,0 -15.20859,15.2086 -27.44966,22.99836 -12.24106,7.78977 -34.12659,19.28895 -34.12659,19.28895 l -10.7573,3.70941 z"
id="path70" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#c3b4b0;stroke-width:2.845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 347.01555,369.64297 c 0,0 2.22565,10.01541 -1.8547,19.84536 -4.08036,9.82994 -5.00771,9.459 -5.00771,9.459"
id="path69" /><path
style="opacity:1;fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.945;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 380.85246,480 c 0,0 44.59016,-17.31147 59.27869,-87.08197 6.81967,-2.62295 34.09836,-14.68852 39.34426,-25.70491 2.62295,-9.96722 -3.67213,-32 -3.67213,-32 l -24.65574,0.52459 -26.7541,4.72131 -4.72131,11.54098 -8.39344,52.45902 -13.11475,28.32787 -15.73771,36.19672 z"
id="path68" /><path
style="fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:8.74203;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 147.6556,277.03379 c 0,0 -11.12655,104.13607 127.58449,103.38689 138.71104,-0.74917 149.83759,-100.39017 149.83759,-100.39017"
id="path41"
inkscape:label="脸蛋" /><path
style="opacity:1;fill:#fff9f6;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 123.27869,147.93443 304.78688,21.50819 -24.65573,-40.39344 c 0,0 44.06557,-58.754098 50.88524,-60.327868 6.81967,-1.573771 15.73771,-9.967214 15.73771,-9.967214 l -13.11476,-3.672131 -77.11475,22.032787 c 0,0 -122.7541,-33.57377 -168.39344,-6.295082 -45.63935,27.278689 -69.7705,46.163938 -69.7705,46.163938 z"
id="path33"
inkscape:label="头发底色2" /><path
style="fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 331.44501,66.651771 16.22882,30.84274 47.82991,31.367399 6.30629,1.07795 26.88325,-35.335399 -32.10312,-22.211018 -18.88524,5.245901 -30.42623,-11.540983 z"
id="path35"
inkscape:label="头发部件111" /><path
style="opacity:1;fill:#ace0fe;fill-opacity:1;stroke:none;stroke-width:14.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 431.03375,175.82616 17.06329,-17.0633 50.44802,71.96261 -5.93506,17.80518 -33.38472,18.54706 -12.612,-16.32142 17.80518,-11.12823 z"
id="path29"
inkscape:label="蓝色发卡上" /><path
style="opacity:1;fill:#ace0fe;fill-opacity:1;stroke:#4c4f59;stroke-width:6.945;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 465.90223,261.88453 c 0,0 35.61036,54.52837 2.96753,73.44637 -25.22401,4.08036 -48.22237,2.22565 -48.22237,2.22565 l 10.38636,-26.33683 21.51459,-41.91636 z"
id="path66"
inkscape:label="蓝色发卡下" /><path
style="opacity:1;fill:#febdc7;fill-opacity:1;stroke:none;stroke-width:4.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 471.86885,57.704918 c 0,0 17.04918,68.983602 -11.01639,110.426232 -7.34426,0.26229 -9.18033,-10.49181 -9.18033,-10.49181 l 2.09836,-6.55737 -1.04918,-9.18033 2.36066,-10.7541 1.83606,-5.5082 -6.29508,-6.03278 -6.55738,-5.2459 -2.88524,-5.24591 2.88524,-5.2459 -2.62295,-5.245899 -4.19672,-2.885246 -3.14754,-1.57377 5.2459,-11.540984 11.80328,-7.344262 z"
id="path64" /><path
id="path57"
style="opacity:1;fill:#ff1c1c;fill-opacity:0.0509804;stroke-width:3.92397;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 244.26691,326.81357 a 35.587568,21.480417 0 0 1 -35.58757,21.48042 35.587568,21.480417 0 0 1 -35.58757,-21.48042 35.587568,21.480417 0 0 1 35.58757,-21.48042 35.587568,21.480417 0 0 1 35.58757,21.48042 z" /><path
id="path57-1"
style="fill:#ff1c1c;fill-opacity:0.05044398;stroke-width:3.65169;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 355.20429,336.75188 a 31.611719,20.942554 0 0 1 -31.61171,20.94255 31.611719,20.942554 0 0 1 -31.61172,-20.94255 31.611719,20.942554 0 0 1 31.61172,-20.94255 31.611719,20.942554 0 0 1 31.61171,20.94255 z" /><path
style="display:inline;opacity:1;fill:#fbf3ef;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 188.32787,343.60656 c 0,0 12.06557,-28.32787 15.7377,6.29508 5.24591,-8.91803 9.96722,-17.31148 17.31148,-8.39344 6.81967,2.09836 15.21311,7.34426 4.72131,22.03278 -10.4918,14.68853 16.33814,-18.37136 15.445,-1.63289 -0.72099,13.51206 -24.52214,46.81129 -24.52214,46.81129 0,0 -6.33782,64.18547 -33.41466,70.23144 -36.5485,8.16089 -12.06558,-103.86885 -12.06558,-103.86885 z"
id="path1"
sodipodi:nodetypes="cccsscscc"
inkscape:label="右手" /><path
id="path5"
style="fill:#fbe7e5;stroke-width:10.4;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke;fill-opacity:0.40581462"
inkscape:label="右手踝阴影"
d="m 193.63133,383.92419 a 12.612,12.241061 0 0 1 -12.612,12.24107 12.612,12.241061 0 0 1 -12.612,-12.24107 12.612,12.241061 0 0 1 12.612,-12.24106 12.612,12.241061 0 0 1 12.612,12.24106 z" /><path
style="fill:#fee4e0;fill-opacity:0.42411327;stroke:none;stroke-width:10.4;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 179.09741,356.52608 14.09576,-20.77271 7.04789,1.48377 0.74188,13.72482 11.49918,-11.49918 9.27353,4.08036 8.53165,5.93506 -5.56412,14.83765 13.72483,-4.82224 -3.33847,17.80518 -47.85143,-27.44965 z"
id="path9"
inkscape:label="右手手指阴影" /><path
style="display:inline;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 188.32787,343.60656 c 0,0 12.06557,-28.32787 15.7377,6.29508 5.24591,-8.91803 9.96722,-17.31148 17.31148,-8.39344 6.81967,2.09836 15.21311,7.34426 4.72131,22.03278 -10.4918,14.68853 16.33814,-18.37136 15.445,-1.63289 -0.72099,13.51206 -24.52214,46.81129 -24.52214,46.81129 0,0 -6.33782,64.18547 -33.41466,70.23144 -36.5485,8.16089 -12.06558,-103.86885 -12.06558,-103.86885 z"
id="path1-6"
sodipodi:nodetypes="cccsscscc"
inkscape:label="右手遮挡" /><path
style="opacity:1;fill:#fff9f6;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 137.44262,123.2787 c 0,0 49.33852,-48.649769 86.97684,-58.734919 0.10513,0.76769 -120.16719,-122.15824 -86.97684,58.734919 z"
id="path3"
inkscape:label="头发部件19 左猫耳" /><path
style="fill:#fde9e7;fill-opacity:1;stroke-width:1.18016;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 198.85246,42 -63.6342,39.016573 3.84123,40.939237 L 184.82787,87 224,64.5 Z"
id="path58"
sodipodi:nodetypes="cccccc" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0.465732;stroke:#a18f90;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
id="path4"
d="m 404.96447,131.7729 c 0.52512,-0.76931 1.0357,-1.54875 1.57536,-2.30793 2.82418,-3.97291 5.82035,-7.82036 8.67758,-11.76948 2.39309,-3.30762 3.93978,-5.54326 6.29133,-8.89998 5.32612,-7.58044 11.18023,-14.760796 17.20894,-21.78699 3.02173,-3.55064 6.06655,-7.098864 9.47662,-10.289082 1.96198,-1.835492 4.21906,-3.672787 6.27415,-5.392122 5.10823,-4.357455 10.66931,-8.109574 16.22169,-11.862072 0,0 -2.92504,-1.864176 -2.92504,-1.864176 v 0 c -5.41592,3.900149 -10.90348,7.705144 -15.99184,12.040062 -1.99887,1.645368 -4.44389,3.611151 -6.36286,5.369248 -3.45237,3.162941 -6.48944,6.731577 -9.48752,10.316639 -5.95526,7.087894 -11.63254,14.399231 -17.00034,21.944173 -5.47138,7.72885 -10.96322,15.45542 -17.11251,22.66867 z"
inkscape:label="头发部件18" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0.465732;stroke:#a18f90;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
id="path6"
d="m 431.7055,96.885136 c 0.68345,-0.01683 1.36993,0.008 2.05351,0.03239 1.26616,0.04274 2.52519,0.175792 3.77564,0.374299 1.34943,0.219358 2.67703,0.547853 3.99756,0.89747 1.30448,0.342355 2.58658,0.760225 3.8587,1.206247 0.34787,0.132122 0.88919,0.332358 1.23164,0.483411 0.1893,0.0835 0.73774,0.373687 0.5597,0.268307 -4.92017,-2.912189 -2.83776,-1.86942 -2.01609,-0.911603 0.4665,0.942833 -0.25414,1.810973 -0.83258,2.515753 -1.05305,1.11546 -2.37988,1.92349 -3.65077,2.76486 -0.70455,0.52564 -1.84263,0.99551 -2.02707,1.9792 -0.13577,0.72412 0.14689,1.00706 0.45808,1.64499 0.25805,0.31484 0.49646,0.64686 0.77415,0.94453 0.63085,0.67625 1.54885,1.45348 2.25243,2.03623 1.28413,1.06359 2.61277,2.07518 3.92585,3.10243 2.22765,1.73231 4.46676,3.44711 6.4408,5.4696 1.24472,1.36548 2.33589,2.86124 3.10757,4.54226 0.57402,1.31239 0.61894,2.68201 0.46471,4.08008 -0.15236,1.542 -1.01177,2.74753 -1.96982,3.90485 -1.25092,1.39253 -2.70297,2.57966 -3.98322,3.94302 -0.61776,0.76458 -1.19371,1.60995 -1.3359,2.60746 -0.0756,0.53059 -0.0152,0.87319 0.031,1.40055 0.18192,1.34638 0.59785,2.64226 1.03422,3.92358 0.46172,1.36123 1.10075,2.65093 1.71056,3.94949 0.57934,1.12566 0.88081,2.32829 0.97901,3.58284 0.0285,1.01273 -0.14083,2.01608 -0.35002,3.00302 -0.18321,0.8594 -0.39147,1.62037 -0.92358,2.32955 -0.69384,0.72591 -1.52468,1.30006 -2.31082,1.91913 0,0 3.13155,1.96912 3.13155,1.96912 v 0 c 0.80747,-0.64335 1.66352,-1.23803 2.37418,-1.9921 0.58364,-0.77755 0.80933,-1.52536 1.00495,-2.4688 0.21825,-1.01967 0.38801,-2.05558 0.38421,-3.10149 -0.0761,-1.29512 -0.34601,-2.55043 -0.93895,-3.71574 -0.60728,-1.2882 -1.24849,-2.56502 -1.72185,-3.91075 -0.43768,-1.25837 -0.85635,-2.529 -1.07055,-3.84806 -0.053,-0.43231 -0.12968,-0.85315 -0.0855,-1.29165 0.0948,-0.94059 0.64999,-1.73394 1.21835,-2.45227 1.27656,-1.3752 2.72864,-2.57131 3.98645,-3.96542 1.00742,-1.20382 1.92485,-2.46496 2.104,-4.07351 0.17452,-1.45227 0.1673,-2.88462 -0.39573,-4.26425 -0.74413,-1.73945 -1.85028,-3.26977 -3.10544,-4.6809 -0.76822,-0.80351 -0.91233,-0.98548 -1.76144,-1.74089 -1.5106,-1.3439 -3.15419,-2.52947 -4.72414,-3.80078 -1.30934,-1.02359 -2.63319,-2.03185 -3.9193,-3.08473 -0.71006,-0.5813 -1.57278,-1.30278 -2.22387,-1.96185 -0.27049,-0.2738 -0.51217,-0.57463 -0.76826,-0.86194 -0.2502,-0.39692 -0.62512,-0.79772 -0.57512,-1.31981 0.0792,-0.82671 1.33713,-1.38331 1.899,-1.81756 1.29884,-0.86671 2.65474,-1.69887 3.73107,-2.84377 0.6873,-0.83746 1.4042,-1.79633 1.075,-2.93575 -0.0816,-0.13585 -0.14456,-0.28482 -0.24481,-0.40755 -0.1097,-0.13429 -0.23095,-0.26613 -0.3791,-0.356228 -1.82458,-1.109627 -3.48552,-2.166773 -5.4739,-2.822822 -1.27815,-0.438878 -2.56689,-0.847162 -3.87638,-1.183261 -1.32827,-0.341807 -2.66392,-0.6605 -4.021,-0.867076 -1.26646,-0.185139 -2.54065,-0.306124 -3.82011,-0.354118 -0.68835,-0.02484 -1.39159,-0.0088 -2.07079,-0.120664 z"
inkscape:label="头发部件17" /><path
style="display:inline;opacity:1;fill:#1a1a1a;fill-opacity:0.465732;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
id="path7"
d="m 472.56515,132.89174 c 0.0148,0.77317 -0.10076,1.54619 -0.19578,2.31209 -0.22663,1.66469 -0.65036,3.29152 -1.09324,4.90909 -0.61155,2.16519 -1.31643,4.30298 -2.07388,6.4211 -0.85906,2.40245 -1.88628,4.73722 -3.04362,7.0095 -1.12519,2.18837 -2.40662,4.28829 -3.77255,6.3331 -1.20044,1.78353 -2.51232,3.48695 -3.85961,5.16088 -0.77567,0.94691 -1.56273,1.88443 -2.34905,2.82249 0,0 3.09439,1.85764 3.09439,1.85764 v 0 c 0.78058,-0.94434 1.56325,-1.88703 2.331,-2.84187 1.33972,-1.69155 2.6509,-3.40675 3.84535,-5.20534 1.36483,-2.06191 2.64248,-4.17929 3.77385,-6.37958 1.16439,-2.28454 2.19746,-4.6314 3.07763,-7.04092 0.77266,-2.11878 1.49327,-4.25705 2.12518,-6.4224 0.46749,-1.62041 0.91079,-3.25003 1.1854,-4.9163 0.11547,-0.76691 0.21504,-1.53597 0.32143,-2.30416 z"
inkscape:label="头发部件16" /><path
style="display:inline;opacity:1;fill:#fff9f6;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 322.62295,86.032787 c 0,0 75.43341,26.120553 110.16394,89.180323 33.44261,60.72132 33.04918,62.42623 33.04918,62.42623 0,0 0.52459,8.39345 -19.93443,11.54099 -2.62295,0 14.68852,30.95082 19.93443,37.2459 -14.68853,7.34426 -16.78689,8.91803 -16.78689,8.91803 0,0 6.29508,7.86885 4.72131,15.73771 -17.31147,-0.52459 -22.55738,-1.04918 -22.55738,-1.04918 0,0 -4.19672,17.31147 -15.7377,33.04918 C 410.22951,333.11475 410.22951,320 410.22951,320 c 0,0 -24.13115,36.72131 -72.39344,41.44262 -1.57377,-2.62295 12.59016,-14.16393 17.83606,-28.32787 -11.54098,1.57377 -14.68852,2.09836 -14.68852,2.09836 l 10.4918,-16.78688 c 0,0 11.01639,-24.13115 9.44262,-28.32787 -1.04918,-2.62295 -32,-57.70492 -32,-57.70492 0,0 -24.65573,33.04918 -43.54098,49.83607 -6.81967,-4.19672 -13.63934,-14.68853 -13.63934,-14.68853 l -12.59017,11.54099 c 0,0 -48.43778,-50.76436 -42.0945,-90.18318 -3.14754,12.06557 0.12729,56.60941 -5.6432,62.3799 -5.24591,-2.09836 -16.2623,-14.68853 -14.16394,-40.91803 -0.52459,-0.52459 -29.90164,41.44262 -29.90164,41.44262 0,0 -4.19672,48.78688 16.2623,73.44262 1.57377,2.62295 -1.57377,16.2623 -31.47541,-2.62295 1.04918,-1.57377 -1.04918,9.96721 -1.04918,9.96721 0,0 -46.68853,2.09836 -45.63935,-60.85245 -2.62295,-2.62296 -13.639341,34.09836 -13.639341,34.09836 0,0 -24.131151,-32 -0.52459,-92.85246 11.803281,-30.42623 23.344261,-52.85246 31.934421,-67.67213 8.59017,-14.81968 14.22951,-22.03279 14.22951,-22.03279"
id="path8"
sodipodi:nodetypes="csccccccccccccccccccccccccccssc"
inkscape:label="头发部件15 头发底色" /><path
id="path36"
style="fill:#fffdfc;stroke-width:7.4278;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
inkscape:label="头发打光"
d="m 355.67214,146.88524 a 91.803276,41.967213 0 0 1 -91.80327,41.96721 91.803276,41.967213 0 0 1 -91.80328,-41.96721 91.803276,41.967213 0 0 1 91.80328,-41.96721 91.803276,41.967213 0 0 1 91.80327,41.96721 z" /><path
style="fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 349.37705,329.44262 c 0,0 71.34426,-41.96721 78.68852,-91.27868 0,1.04918 26.22951,28.32786 26.22951,28.32786 l 11.54099,24.13115 -15.73771,7.34426 2.09836,13.63935 -22.03279,-1.04918 -12.59016,30.42623 -12.59016,-17.83607 -23.08197,26.22951 -45.11475,14.68852 z"
id="path37"
inkscape:label="头发阴影"
sodipodi:nodetypes="cccccccccccc" /><path
style="fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 325.2459,144.78689 c 0,0 10.49181,44.06557 2.09836,78.68852 0,10.4918 33.57377,60.85246 33.57377,60.85246 0,0 14.68853,-71.34426 -25.18032,-132.19672 -8.39345,-5.2459 -10.49181,-7.34426 -10.49181,-7.34426 z"
id="path38"
inkscape:label="头发阴影2" /><path
style="fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 186.7541,166.81967 23.08197,1.04918 c 0,0 -14.68853,26.22951 -13.63935,46.16394 -2.09836,-3.14754 -22.03279,31.47541 -22.03279,31.47541 0,0 -9.44262,-14.68853 -3.14754,-50.36066 9.44263,-16.78688 15.73771,-28.32787 15.73771,-28.32787 z"
id="path39"
inkscape:label="头发阴影" /><path
id="path36-5"
style="fill:#fffdfc;stroke-width:6.94619;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
inkscape:label="头发打光"
transform="matrix(0.99965843,-0.02613486,0,1,0,0)"
d="m 340.28017,112.23744 a 80.289719,41.967213 0 0 1 -80.28972,41.96721 80.289719,41.967213 0 0 1 -80.28972,-41.96721 80.289719,41.967213 0 0 1 80.28972,-41.967211 80.289719,41.967213 0 0 1 80.28972,41.967211 z" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#a18f90;stroke-width:7.7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 138.62295,122.4918 c 0,0 53.63935,-52.983604 92.98361,-58.229505"
id="path65"
sodipodi:nodetypes="cc" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 436.45902,268.06557 c 0,0 3.67213,18.36066 -2.62295,42.49181"
id="path17"
inkscape:label="头发部件7" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 418.09836,305.83607 -8.91803,17.83606"
id="path16"
inkscape:label="头发部件8" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 377.70492,300.59016 c 0,0 2.62295,12.06558 -22.03279,32"
id="path15"
inkscape:label="头发部件9" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:4.2;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 284.32787,97.573771 c 0,0 12.06557,39.344259 -27.80328,119.081969 0.52459,0 53.5082,-38.81967 56.13115,-89.18033"
id="path10"
inkscape:label="头发部件14" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:5.1;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 272.78689,87.081967 c 0,0 13.63934,-8.393442 18.88524,-0.52459 17.17267,10.839383 13.03281,30.236103 24.19672,44.393443 1.79263,1.85342 7.1718,-0.78131 7.80328,1.77049 4.88126,19.72513 14.81582,50.80617 4.32401,100.11764"
id="path11"
sodipodi:nodetypes="csssc"
inkscape:label="头发部件13" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:4.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 171.00392,241.8537 c 0,0 -28.56248,-60.09248 92.36438,-147.634619 2.22564,0.741882 -59.35061,54.157419 -66.02755,111.282379"
id="path12"
inkscape:label="头发部件12" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#c3b4b0;stroke-width:3;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 327.34426,143.7377 c 0,0 25.18033,20.45902 32,65.57378"
id="path13"
inkscape:label="头发部件11" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 362.4918,268.59016 -3.14754,19.93443"
id="path14"
inkscape:label="头发部件10" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 421.77049,344.13115 c 0,0 -2.62295,58.22951 -33.57377,111.7377 -30.95082,53.5082 38.29508,15.73771 38.29508,15.73771"
id="path18"
inkscape:label="头发部件6" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 395.01639,348.32787 12.06558,60.32787"
id="path19"
inkscape:label="头发部件5" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 371.93443,423.86885 c 0,0 30.95082,-25.70492 24.65573,-73.96721"
id="path20"
inkscape:label="头发部件4" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 332.06557,427.54098 c 0,0 36.72132,1.57377 38.81968,-29.37705"
id="path21"
inkscape:label="头发部件3" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 372.45902,427.01639 -2.09836,-28.85246"
id="path22"
inkscape:label="头发部件2" /><path
style="fill:#93cefc;fill-opacity:1;stroke:#4d4e59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 377.50929,228.61046 c 0,0 24.13115,1.57377 24.13115,10.49181 -0.52459,3.67213 -11.54099,17.83606 -23.60656,18.88524 -2.09836,-1.57377 -0.52459,-29.37705 -0.52459,-29.37705 z"
id="path34"
sodipodi:nodetypes="cccc"
inkscape:label="兔子发卡后" /><path
style="opacity:1;fill:#fffdfe;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 350.95082,235.54098 c 0,0 -6.55738,-22.03278 1.04918,-22.03278 7.60656,0 13.37705,13.90164 13.37705,13.90164 0,0 5.05941,-17.72738 9.96722,-15.21311 11.79758,6.04389 3.40983,21.77048 3.40983,21.77048 0,0 18.62295,25.96722 -12.06558,27.27869 -30.16717,1.28919 -15.7377,-25.70492 -15.7377,-25.70492 z"
id="path24"
sodipodi:nodetypes="cscscsc"
inkscape:label="兔子发卡" /><path
style="display:inline;opacity:1;fill:#1a1a1a;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 332.06557,425.96721 c 0,0 10.49181,-25.18032 6.29509,-49.83606"
id="path23"
inkscape:label="头发部件" /><path
style="display:inline;opacity:1;fill:#e85240;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 192.2623,268.85246 c 0,0 -15.47541,22.03279 -2.62296,35.40984 42.7541,0.26229 43.27869,0.52459 43.27869,0.52459 0,0 11.27869,-8.39345 5.5082,-32 -25.44262,-9.96722 -46.16393,-3.93443 -46.16393,-3.93443 z"
id="path25"
inkscape:label="左眼白" /><path
style="opacity:1;fill:#fbb579;fill-opacity:1;stroke:none;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 188.06557,301.11475 c -0.44151,-8.78192 13.4528,-14.01636 22.29509,-14.42623 8.67244,-0.402 23.2398,3.38526 23.08196,12.06558 -0.0775,4.26198 -11.54098,5.5082 -11.54098,5.5082 0,0 -33.2673,8.16555 -33.83607,-3.14755 z"
id="path27"
sodipodi:nodetypes="saacs"
inkscape:label="左眼瞳" /><path
id="path26"
style="display:inline;opacity:1;fill:#ffffff;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
inkscape:label="眼光"
d="m 217.96721,273.04919 a 7.8688526,7.0819674 0 0 1 -7.86885,7.08197 7.8688526,7.0819674 0 0 1 -7.86885,-7.08197 7.8688526,7.0819674 0 0 1 7.86885,-7.08196 7.8688526,7.0819674 0 0 1 7.86885,7.08196 z" /><path
style="fill:none;fill-opacity:1;stroke:#ebc2bf;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 174.16393,245.5082 c 15.08644,-4.76649 31.53934,-7.45293 47.09358,-3.51527 3.29779,0.83486 6.51821,1.99257 9.56216,3.51527"
id="path42"
inkscape:path-effect="#path-effect42"
inkscape:original-d="m 174.16393,245.5082 c 11.60252,-3.79212 36.99244,-9.83166 56.65574,0"
transform="translate(0,4)" /><path
style="opacity:1;fill:#fde9e7;fill-opacity:1;stroke:none;stroke-width:6.945;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 477.77235,132.79697 c 0,0 13.35388,10.01542 14.83765,17.43424 1.48376,7.41883 1.48376,57.4959 1.48376,57.4959 l -4.45129,1.11282 -30.78813,-37.46507 c 0,0 8.90259,-14.46671 11.12824,-22.62741 2.22565,-8.16071 7.78977,-15.95048 7.78977,-15.95048 z"
id="path67" /><path
style="fill:#e85240;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 192.2623,268.85246 c 0,0 -15.47541,22.03279 -2.62296,35.40984 42.7541,0.26229 43.27869,0.52459 43.27869,0.52459 0,0 11.27869,-8.39345 5.5082,-32 -25.44262,-9.96722 -46.16393,-3.93443 -46.16393,-3.93443 z"
id="path25-9"
inkscape:label="左眼遮挡上" /><path
style="display:inline;fill:#e85240;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 325.52138,272.81871 c 17.02505,1.06627 38.81967,23.08196 17.83607,48.26229 -1.57377,0.52459 -48.98481,-5.30599 -49.57378,-9.18033 -0.80561,-5.29953 -7.95666,-41.56799 31.73771,-39.08196 z"
id="path40"
sodipodi:nodetypes="scss"
inkscape:label="右眼白" /><path
style="fill:#fbb579;fill-opacity:1;stroke:none;stroke-width:7.14054;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 293.04592,306.32136 c 2.55323,-8.53469 20.43105,-9.52962 30.78942,-7.36618 10.1594,2.12188 25.66784,9.9581 22.45763,18.22228 -1.5762,4.05766 -15.25453,1.93723 -15.25453,1.93723 0,0 -41.28165,-1.79873 -37.99252,-12.79333 z"
id="path27-0"
sodipodi:nodetypes="saacs"
inkscape:label="左眼瞳" /><path
style="display:inline;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 325.52138,272.81871 c 17.02505,1.06627 38.81967,23.08196 17.83607,48.26229 -1.57377,0.52459 -48.98481,-5.30599 -49.57378,-9.18033 -0.80561,-5.29953 -7.95666,-41.56799 31.73771,-39.08196 z"
id="path40-2"
sodipodi:nodetypes="scss"
inkscape:label="右眼遮挡上" /><path
style="fill:none;fill-opacity:1;stroke:#ebc2bf;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 309.32819,258.43459 c 0,0 26.2295,-5.2459 36.72131,4.19672"
id="path43" /><path
id="path26-9"
style="display:inline;fill:#ffffff;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
inkscape:label="眼光"
d="m 329.6342,284.99301 a 7.8688526,7.0819674 0 0 1 -7.86885,7.08197 7.8688526,7.0819674 0 0 1 -7.86885,-7.08197 7.8688526,7.0819674 0 0 1 7.86885,-7.08197 7.8688526,7.0819674 0 0 1 7.86885,7.08197 z" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:14.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 170.4918,266.4918 c 0,0 32,-19.93442 70.29509,-2.09836"
id="path28"
inkscape:label="眉毛" /><path
style="fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:14.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 292.9971,274.17683 c 0,0 35.55098,-12.55026 69.08832,13.13797"
id="path28-2"
inkscape:label="眉毛" /><path
style="display:inline;opacity:1;fill:none;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 431.03375,176.1971 18.17612,-17.43424 41.54542,54.15742"
id="path30"
inkscape:label="蓝色发卡遮挡上" /><path
style="opacity:1;fill:none;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 454.0321,267.44865 43.21466,-23.92571"
id="path31"
sodipodi:nodetypes="cc"
inkscape:label="蓝色发卡遮挡下" /><path
style="display:inline;fill:#fff9f6;fill-opacity:0;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 164.23888,424.64081 -31.07097,26.05282 c 0,0 -5.19317,18.17613 25.96589,28.93342 31.15907,10.7573 213.29123,1.48377 213.29123,1.48377 0,0 52.30272,11.87012 80.86519,-37.09413 -0.37094,0.74188 -8.90259,19.28895 -8.90259,19.28895 l 1.48377,19.28894 c 0,0 57.73516,-9.41425 30.45647,-148.95524 4.19672,-9.44262 8.39344,-15.21311 8.39344,-15.21311 l -3.14754,-21.77049 c 0,0 5.65758,-40.33533 7.88323,-45.52851 2.22564,-5.19317 8.51558,-6.35116 9.64447,-11.12823 2.06907,-8.75561 -2.78206,-20.40177 -6.67694,-26.15136 -0.74189,-3.33847 6.86241,-35.05395 2.04017,-58.05231 -7.04788,-11.87012 -4.82223,-11.87012 -18.918,-25.59495 0,0.37094 8.90259,-44.88389 -0.74188,-73.446367 -2.96753,0 -32.27189,-10.757296 -96.81567,23.740241 -0.37094,1.112824 -26.33683,-30.046242 -153.56969,-15.950474 -0.74188,0 -43.40012,-37.836009 -66.76942,-40.432598 -1.11283,-0.370941 -22.62742,-4.080354 -25.22401,48.964247 0,1.112824 -44.14201,15.950474 -56.01213,64.914721 -11.87012,48.96425 -11.128238,80.86519 -11.128238,80.86519 0,0 -18.904912,27.6632 -9.798121,25.03308 5.346826,-1.54421 4.975885,23.56023 4.975885,23.56023 l -2.225648,39.31977 c 0,0 -14.466709,35.23942 -14.83765,59.72155 -0.370942,24.48212 -0.741883,34.12659 4.822236,48.5933 5.564119,14.46671 20.772711,46.7386 42.658245,63.06002 8.902591,0.37094 18.918001,1.8547 18.918001,1.8547 l 3.70942,-20.77271 c 0,0 18.918,15.95048 32.27189,14.83765 13.35388,-1.11282 -0.74189,-0.74188 -0.74189,-0.74188 l -13.72482,-17.80518 z"
id="path2-4"
sodipodi:nodetypes="ccscccccccsaccccccccscsccsscccsccc"
inkscape:label="上发层" /><path
style="fill:none;fill-opacity:1;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 197.5082,220.06557 18.36065,-12.32786"
id="path46" /><path
style="fill:none;fill-opacity:1;stroke:#c3b4b0;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 327.54114,234.24941 c 0,0 -6.27192,4.90452 -15.85821,-0.991"
id="path47"
sodipodi:nodetypes="cc" /><path
style="display:inline;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 322.62295,86.03279 c 0,0 75.43341,26.12055 110.16394,89.18032 33.44261,60.72132 33.04918,62.42623 33.04918,62.42623 0,0 0.52459,8.39345 -19.93443,11.54099 -2.62295,0 14.68852,30.95082 19.93443,37.2459 -14.68853,7.34426 -16.78689,8.91803 -16.78689,8.91803 0,0 6.29508,7.86885 4.72131,15.73771 -17.31147,-0.52459 -22.55738,-1.04918 -22.55738,-1.04918 0,0 -4.19672,17.31147 -15.7377,33.04918 C 410.22951,333.11475 410.22951,320 410.22951,320 c 0,0 -24.13115,36.72131 -72.39344,41.44262 -1.57377,-2.62295 12.59016,-14.16393 17.83606,-28.32787 -11.54098,1.57377 -14.68852,2.09836 -14.68852,2.09836 l 10.4918,-16.78688 c 0,0 11.01639,-24.13115 9.44262,-28.32787 -1.04918,-2.62295 -32,-57.70492 -32,-57.70492 0,0 -24.65573,33.04918 -43.54098,49.83607 -6.81967,-4.19672 -13.63934,-14.68853 -13.63934,-14.68853 l -12.59017,11.54099 c 0,0 -48.43778,-50.76436 -42.0945,-90.18318 -3.14754,12.06557 0.12729,56.60941 -5.6432,62.3799 -5.24591,-2.09836 -16.2623,-14.68853 -14.16394,-40.91803 -0.52459,-0.52459 -29.90164,41.44262 -29.90164,41.44262 0,0 -4.19672,48.78688 16.2623,73.44262 1.57377,2.62295 -1.57377,16.2623 -31.47541,-2.62295 1.04918,-1.57377 -1.04918,9.96721 -1.04918,9.96721 0,0 -46.68853,2.09836 -45.63935,-60.85245 -2.62295,-2.62296 -13.63934,34.09836 -13.63934,34.09836 0,0 -24.13115,-32 -0.52459,-92.85246 11.80328,-30.42623 23.34426,-52.85246 31.93442,-67.67213 8.59017,-14.81968 14.22951,-22.03279 14.22951,-22.03279"
id="path8-0"
sodipodi:nodetypes="csccccccccccccccccccccccccccssc"
inkscape:label="头发部件15 头发底色" /><path
id="path44-3"
style="fill:#a88a8f;stroke:#4c4f59;stroke-width:2.8623;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 216.29183,255.53888 a 8.8904896,7.4928694 0 0 1 -8.89049,7.49287 8.8904896,7.4928694 0 0 1 -8.89049,-7.49287 8.8904896,7.4928694 0 0 1 8.89049,-7.49287 8.8904896,7.4928694 0 0 1 8.89049,7.49287 z" /><path
id="path44"
style="fill:#a88a8f;stroke:#4c4f59;stroke-width:2.8623;stroke-linecap:round;stroke-miterlimit:1.3;paint-order:fill markers stroke"
d="m 336.16933,270.5473 a 8.8904896,7.4928694 0 0 1 -8.89049,7.49287 8.8904896,7.4928694 0 0 1 -8.89049,-7.49287 8.8904896,7.4928694 0 0 1 8.89049,-7.49287 8.8904896,7.4928694 0 0 1 8.89049,7.49287 z" /><path
style="opacity:1;fill:none;fill-opacity:0;stroke:#a18f90;stroke-width:10.4;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 318.7189,85.461784 c 0,0 50.44801,17.063296 76.78484,43.400126 26.33683,26.33683 32.64283,39.31978 32.64283,39.31978"
id="path32"
inkscape:label="上方头发边缘加厚" /><path
style="fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:6.5;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 186.7541,224.52459 10.4918,1.04918"
id="path45" /><path
style="opacity:1;fill:#fcbeb5;fill-opacity:1;stroke:#e99c9b;stroke-width:3.4;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 250.94176,349.98308 c 0,0 -13.91029,-8.90259 -10.0154,-16.69235 3.89488,-7.78977 17.80517,-4.4513 17.80517,-4.4513 0,0 12.98295,-1.29829 20.03083,-3.89488 7.04788,-2.59659 9.64447,5.0077 9.64447,5.0077 0,0 4.76461,6.06544 -4.26583,16.50689 -5.93505,6.86241 -21.14364,7.04788 -21.14364,7.04788"
id="path48"
sodipodi:nodetypes="cscscsc" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e99c9b;stroke-width:1.8;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 276.20884,325.76493 c 0,0 4.35031,3.01904 5.37456,7.95235 0.57119,0.10629 3.33355,-3.08729 2.97647,-6.87263 -0.67968,-1.48531 -8.35103,-1.07972 -8.35103,-1.07972 z"
id="path49" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e99c9b;stroke-width:1.8;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 245.0067,329.39584 -4.17308,2.78206 -0.0927,6.0278 -0.0956,-0.0355 7.07328,-8.8174 z"
id="path50"
sodipodi:nodetypes="cccccc" /><path
style="fill:none;fill-opacity:1;stroke:#e99c9b;stroke-width:3.4;stroke-linecap:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 250.94176,349.98308 c 0,0 -13.91029,-8.90259 -10.0154,-16.69235 3.89488,-7.78977 17.80517,-4.4513 17.80517,-4.4513 0,0 12.98295,-1.29829 20.03083,-3.89488 7.04788,-2.59659 9.64447,5.0077 9.64447,5.0077 0,0 4.76461,6.06544 -4.26583,16.50689 -5.93505,6.86241 -21.14364,7.04788 -21.14364,7.04788"
id="path48-2"
sodipodi:nodetypes="cscscsc" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 190.84928,314.92913 6.12053,15.02312"
id="path51" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 204.94505,316.96931 5.56412,14.28124"
id="path52" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 217.92799,317.34025 7.41883,16.50689"
id="path53" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 300.64789,328.46849 6.86242,18.17612"
id="path54" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 312.70348,329.21037 6.67695,17.0633"
id="path55" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#e89493;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 326.24284,328.65396 7.04788,17.99065"
id="path56" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#e49589;stroke-width:4.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 218.4918,348.98361 c -1.87307,1.30472 -3.33608,3.11401 -4.70333,4.91603 -1.02497,1.35089 -2.00187,2.75003 -2.77208,4.2643"
id="path61"
inkscape:path-effect="#path-effect61"
inkscape:original-d="m 218.4918,348.98361 c -1.70592,0.99645 -5.82749,5.88449 -7.47541,9.18033"
transform="rotate(-3.3120949,219.55256,344.63259)" /><path
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4f59;stroke-width:3.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 127.47541,375.08197 c 0,0 -14.42623,-19.14754 -12.06557,-59.54099"
id="path92" /><path
style="opacity:1;fill:#ffffff;fill-opacity:0.734285;stroke:#c1545a;stroke-width:7.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 136.39344,322.09836 c 0,0 9.44263,-18.88525 -4.19672,-39.86885 -3.14754,-0.52459 27.80328,-27.80328 5.2459,-77.11476 -1.57377,0.52459 23.60656,-55.60655 -24.13115,-82.88524 -4.19672,-8.39344 -45.639336,-69.245904 -89.180316,13.11475 0,11.54099 -1.04918,36.19672 -1.04918,36.19672 0,0 -14.6885299,38.81968 -6.29509,71.86886 2.09837,3.14754 -20.9835999,49.83606 11.0164,106.4918 0.52459,0.52459 -16.78689,57.70492 12.59016,82.88525 1.04918,4.72131 13.63935,33.57377 42.49181,39.34426 12.59016,0.52459 19.409826,-0.52459 19.409826,-0.52459 0,0 31.47541,-4.72131 47.73771,-67.14754 -2.09836,-27.80328 -4.19672,-50.36066 -11.0164,-70.81968"
id="path93" /><path
id="path101"
style="opacity:1;fill:#ffffff;fill-opacity:0.734285;stroke:#000000;stroke-width:9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 109.69769,219.48859 c 0,0 -15.208592,-11.87012 -21.143652,-19.65989 m -37.09413,20.03083 22.25648,-21.88553 m 0.37094,-14.09577 5.93506,41.91636 m -35.23942,-24.11118 56.012132,-4.82224 m 11.12824,-17.43424 -19.288952,0.37094 m -0.37094,-0.74188 -1.48376,-29.6753 m -38.94883,3.33847 37.836,-2.96753 m -24.85306,-11.49918 0.74188,39.69072"
inkscape:label="杂" /><path
id="path111"
style="opacity:1;fill:#ffffff;fill-opacity:0.734285;stroke:#000000;stroke-width:9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 58.507798,352.28556 54.157422,-3.33847 m -46.367662,-13.72483 35.610372,-2.96753 m -21.514602,-32.27189 2.59659,33.38472 m -18.918,-16.32142 37.094122,-3.33847 m 1.48377,18.54706 0.74188,-33.75565 m -0.37094,0.37094 -40.803542,2.22565 m 0,-0.37094 4.4513,33.38471 m 27.44965,-48.22237 3.33847,-14.4667 m 0,-1.48377 -27.44965,1.48377 m -1.48377,14.83765 0.74188,-20.03083"
inkscape:label="鱼" /><path
d="m 69.453989,376.46984 c -4.220665,0.22086 -8.079406,1.8518 -10.783191,4.54882 -2.980547,2.98155 -4.241957,7.10139 -3.534076,11.54824 0.904812,5.67433 4.763552,11.7394 11.384623,17.90216 1.405115,1.30393 2.39508,2.14912 4.390984,3.74184 3.507462,2.79468 6.679618,5.01174 11.04931,7.70876 1.559468,0.96838 4.635815,2.76496 5.796096,3.38931 l 0.335314,0.18263 0.48966,-0.26758 c 1.783005,-0.98111 4.960482,-2.86264 6.844609,-4.05189 7.126702,-4.49782 12.832312,-9.01266 17.063632,-13.50625 6.86057,-7.28827 9.77725,-14.38543 8.43068,-20.49298 -0.93142,-4.20475 -4.04502,-7.68327 -8.46261,-9.45012 -1.90011,-0.76026 -3.74699,-1.15525 -5.83869,-1.24869 -2.7091,-0.12316 -5.37562,0.33977 -7.91973,1.37185 -4.332435,1.75413 -7.951675,5.17315 -10.229661,9.66674 -0.159681,0.3143 -0.308701,0.59038 -0.329985,0.60737 -0.0692,0.0553 -0.143707,-0.051 -0.431122,-0.62859 -0.681263,-1.35913 -2.01719,-3.3341 -3.065699,-4.52331 -1.07513,-1.22747 -2.804909,-2.72251 -4.092931,-3.54224 -3.326503,-2.11937 -7.222503,-3.15569 -11.097213,-2.95607 z"
id="path1-4"
style="fill:#d96477;fill-opacity:1;stroke:none;stroke-width:0.00475452" /></g></svg>

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -1,101 +0,0 @@
import { defineConfig, type DefaultTheme } from 'vitepress'
export default defineConfig({
lang: 'zh-Hans',
description: '下一代 Android Root 解决方案 - Android 上的内核级的高级 root 方案',
themeConfig: {
nav: nav(),
sidebar: {
'/zh/': { base: '/zh/', items: sidebar() },
},
search: { options: searchOptions() },
editLink: {
pattern: 'https://github.com/sukisu-ultra/sukisu-ultra/edit/main/docs/:path',
text: '在 GitHub 上编辑此页面',
},
docFooter: {
prev: '上一页',
next: '下一页',
},
outline: {
label: '页面导航',
},
lastUpdated: {
text: '最后更新于',
},
notFound: {
title: '页面未找到',
quote: '抱歉,我们无法找到您要查找的页面。',
linkLabel: '前往首页',
linkText: '带我回首页',
},
langMenuLabel: '多语言',
returnToTopLabel: '回到顶部',
sidebarMenuLabel: '菜单',
darkModeSwitchLabel: '主题',
lightModeSwitchTitle: '切换到浅色模式',
darkModeSwitchTitle: '切换到深色模式',
skipToContentLabel: '跳转到内容',
},
})
function nav(): DefaultTheme.NavItem[] {
return [
{ text: '首页', link: '/zh/' },
{
text: '开始使用',
items: [
{ text: '介绍', link: '/zh/guide/' },
{ text: '安装', link: '/zh/guide/installation' },
{ text: '集成', link: '/zh/guide/how-to-integrate' },
{ text: '兼容性', link: '/zh/guide/compatibility' },
{ text: '链接', link: '/zh/guide/links' },
{ text: '许可', link: '/zh/guide/license' },
],
},
]
}
function sidebar(): DefaultTheme.SidebarItem[] {
return [
{
text: '开始使用',
items: [
{ text: '介绍', link: '/guide/' },
{ text: '安装', link: '/guide/installation' },
{ text: '集成', link: '/guide/how-to-integrate' },
{ text: '兼容性', link: '/guide/compatibility' },
{ text: '链接', link: '/guide/links' },
{ text: '许可', link: '/guide/license' },
],
},
]
}
function searchOptions(): Partial<DefaultTheme.LocalSearchOptions> {
return {
translations: {
button: {
buttonText: '搜索文档',
buttonAriaLabel: '搜索文档',
},
modal: {
noResultsText: '无法找到相关结果',
resetButtonTitle: '清除查询条件',
footer: {
selectText: '选择',
navigateText: '切换',
closeText: '关闭',
},
},
},
}
}

View File

@@ -1,23 +0,0 @@
# 兼容性状态
::: info KernelSU
KernelSUv0.9.5 之前的版本)官方支持 Android GKI 2.0 设备(内核 5.10+
:::
::: warning 传统内核支持
较旧的内核4.4+)也兼容,但内核必须手动构建
:::
::: tip 扩展兼容性
SukiSU-Ultra 可以通过额外的反向移植支持 3.x 内核3.4-3.18
:::
## 架构支持
目前支持以下处理器架构:
| 架构 | 支持级别 | 备注 |
| --------------- | :---------: | -----------: |
| **arm64-v8a** | ✅ 完全支持 | 主要目标架构 |
| **armeabi-v7a** | ✅ 基础支持 | 最低功能要求 |
| **X86_64** | 🟡 部分支持 | 支持部分设备 |

View File

@@ -1,94 +0,0 @@
# 集成指导
SukiSU 可以集成到 GKI 和 non-GKI 内核中,并且已反向移植到 4.14 版本。
<!-- 应该是 3.4 版本,但 backslashxx 的 syscall manual hook 无法在 SukiSU 中使用-->
有些 OEM 定制可能导致多达 50% 的内核代码超出内核树代码,而非来自上游 Linux 内核或 ACK。因此non-GKI 内核的定制特性导致了严重的内核碎片化,而且我们缺乏构建它们的通用方法。因此,我们无法提供 non-GKI 内核的启动映像。
前提条件:开源的、可启动的内核。
## Hook 方法
1. **KPROBES hook:**
- GKI kernels 的默认 hook 方法。
- 需要 `# CONFIG_KSU_MANUAL_HOOK is not set`(未设定) & `CONFIG_KPROBES=y`
- 用作可加载的内核模块 (LKM).
2. **Manual hook:**
<!-- - backslashxx's syscall manual hook: https://github.com/backslashxx/KernelSU/issues/5 (v1.5 version is not available at the moment, if you want to use it, please use v1.4 version, or standard KernelSU hooks)-->
- 需要 `CONFIG_KSU_MANUAL_HOOK=y`
- 需要 [`guide/how-to-integrate.md`](how-to-integrate.md)
- 需要 [https://github.com/~](https://github.com/tiann/KernelSU/blob/main/website/docs/guide/how-to-integrate-for-non-gki.md#manually-modify-the-kernel-source)
3. **Tracepoint Hook:**
- 自 SukiSU commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124) 引入的 hook 方法
- 需要 `CONFIG_KSU_TRACEPOINT_HOOK=y`
- 需要 [`guide/tracepoint-hook.md`](tracepoint-hook.md)
<!-- This part refer to [rsuntk/KernelSU](https://github.com/rsuntk/KernelSU). -->
如果您能够构建可启动内核,有两种方法可以将 KernelSU 集成到内核源代码中:
1. 使用 `kprobe` 自动集成
2. 手动集成
## 与 kprobe 集成
适用:
- GKI 内核
不适用:
- non-GKI 内核
KernelSU 使用 kprobe 机制来做内核的相关 hook如果 _kprobe_ 可以在你编译的内核中正常运行,那么推荐用这个方法来集成。
请参阅此文档 [https://github.com/~](https://github.com/tiann/KernelSU/blob/main/website/docs/guide/how-to-integrate-for-non-gki.md#integrate-with-kprobe)。虽然标题为“适用于 non-GKI”但仅适用于 GKI。
替换 KernelSU 添加到内核源代码树的步骤的执行命令为:
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
```
## 手动修改内核源代码
适用:
- GKI 内核
- non-GKI 内核
请参考此文档 [https://github.com/~ (non-GKI 内核集成)](https://github.com/tiann/KernelSU/blob/main/website/docs/guide/how-to-integrate-for-non-gki.md#manually-modify-the-kernel-source) 和 [https://github.com/~ (GKI 内核构建)](https://kernelsu.org/zh_CN/guide/how-to-build.html) 进行手动集成。虽然第一个链接的标题是“适用于 non-GKI”但它也适用于 GKI。两者都可以正常工作。
还有另一种集成方法,但是仍在开发中。
<!-- 这是 backslashxx 的syscall manual hook但目前无法使用。 -->
将 KernelSUSukiSU添加到内核源代码树的步骤的运行命令将被替换为
### GKI 内核
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
```
### non-GKI 内核
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s nongki
```
### 带有 susfs 的 GKI / non-GKI 内核(实验)
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-{{branch}}
```
分支:
- `main` (susfs-main)
- `test` (susfs-test)
- 版本号 (例如: susfs-1.5.7, 你需要在 [分支](https://github.com/SukiSU-Ultra/SukiSU-Ultra/branches) 里找到它)

View File

@@ -1,35 +0,0 @@
# 什么是 SukiSU-Ultra
一个 Android 上基于内核的 root 方案,由 [`tiann/KernelSU`](https://github.com/tiann/KernelSU) 分叉而来,添加了一些有趣的变更。
## 特性
1. 基于内核的 su 和权限管理。
2. 基于 Magic Mount 的模块系统。
3. App Profile: 把 Root 权限关进笼子里。
4. 支持 non-GKI 与 GKI 1.0。
5. KPM 支持
6. 可调整管理器外观,可自定义 susfs 配置。
## 如何安装
了解如何在您的设备上 **[安装](./installation)** SukiSU-Ultra
## 如何集成
阅读 **[集成](./how-to-integrate.md)** 将 SukiSU 集成到你的内核中
## 了解兼容性
检查设备 **[兼容性](./compatibility)** 要求
## 资源获取
查找其他资源和 **[下载](./links)**
## 获取支持
需要帮助?我们在这里为您提供帮助:
- **错误报告**: [GitHub Issues](https://github.com/SukiSU-Ultra/SukiSU-Ultra/issues)
- **直接支持**: 联系开发者处理关键问题

View File

@@ -1,149 +0,0 @@
# 安装参考
::: details 将会了解 SukiSU-Ultra 安装过程
[[toc]]
:::
::: danger 警告
**Root 您的设备可能会使保修失效,如果操作不当可能会造成永久性损坏。**
请务必在继续之前创建完整备份,阅读文档确保与您的设备兼容,遵循文档参考,准备好恢复计划
:::
## 通用 GKI
请**全部**参考 [KernelSU 安装指南](https://kernelsu.org/zh_CN/guide/installation.html)
1. 适用于 GKI 2.0 设备,如小米、红米、三星等(不包括内核修改的制造商,如魅族、一加、真我和 OPPO
2. 在[更多链接](./links)中查找 GKI 构建。找到设备内核版本,然后下载并使用 TWRP 或内核刷写工具刷入带有 `AnyKernel3` 后缀的
zip 文件。
3. 无后缀的 `.zip` 档案是未压缩的,`gz` 后缀是特定型号使用的压缩格式。
## 一加设备
使用[更多链接](./links)部分提到的链接,用您的设备信息创建自定义构建,然后刷入带有 AnyKernel3 后缀的 zip 文件。
::: details 展开
- 只需要填写内核版本的前两部分,如 `5.10``5.15``6.1``6.6`
- 请自行搜索处理器代号,通常是不含数字的英文字母。
- 可以从一加开源内核仓库中找到分支和配置文件。
- 第三方 Recovery推荐 TWRP
:::
## 开始安装
### 通用 GKI 安装
::: tip
适用于 GKI 2.0 设备,如小米、红米、三星等(不包括内核修改的制造商,如魅族、一加、真我和 OPPO
:::
#### 步骤:
1. 下载 GKI 构建文件
从我们的[资源部分](./links)查找 GKI 构建。查找您设备的内核版本并下载带有 `AnyKernel3` 后缀的 `zip` 文件。
2. 通过 Recovery 刷入
启动到 `TWRP recovery`,选择 **Install**,导航到下载的 `AnyKernel3 zip` 文件,滑动刷入后重启系统
3. [验证安装](#验证)
::: details 文件格式说明
无后缀的 `.zip` 档案是未压缩的,`gz` 后缀是特定型号使用的压缩格式。
:::
### 一加设备安装
1. 获取设备信息
- 内核版本(前两部分,例如 `5.10``5.15``6.1``6.6`
- 处理器代号(通常是不含数字的英文)
- 来自一加开源内核仓库的分支和配置文件
2. 创建自定义构建
使用我们[资源部分](./links)中提到的链接,用您的设备信息创建自定义构建。
3. 刷入构建
下载生成的带有 AnyKernel3 后缀的 zip 文件,启动到 `TWRP recovery`,选择 **Install**,导航到下载的 `AnyKernel3 zip` 文件,滑动刷入后重启系统
::: tip
您只需要填写内核版本的前两部分。自行搜索处理器代号
:::
### 手动内核集成
面向希望将 SukiSU Ultra 集成到自己内核构建中的高级用户
#### 主分支GKI
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
```
#### 非 GKI 分支
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s nongki
```
#### SUSFS-Dev 分支(推荐)
```sh [bash]
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
```bash
curl -LSs "https://raw.githubusercontent.com/SukiSU-Ultra/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-main
```
::: warning 必需的内核配置
为了支持 KPM添加 `CONFIG_KPM=y`
对于非 GKI 设备,还要添加 `CONFIG_KALLSYMS=y` 和 `CONFIG_KALLSYMS_ALL=y`
:::
## 安装后设置
### 系统更新时保持 Root
> [!IMPORTANT]
> 如何在 OTA 更新后保持 root 访问权限:
#### 步骤:
1. 系统更新后重启前
- OTA 安装后不要立即重启,将 SukiSU Ultra 安装到第二插槽
- 打开 SukiSU Ultra 管理器,在**刷入/修补内核**界面选择 **GKI/non_GKI install**,选择您的 `AnyKernel3` 内核 `zip` 文件,选择与当前运行插槽相对的插槽然后刷入重启。
2. 替代方案LKM 模式
使用 [LKM 模式](#通用-gki) 在 OTA 后安装到未使用的插槽。
::: warning 警告
**非 GKI 设备注意事项:** 此方法不支持所有非 GKI 设备。对于非 GKI 设备,使用 TWRP 是最安全的方法。
:::
## 验证
安装后,验证一切是否正常工作,
- 打开 SukiSU Ultra 管理器检查 root 工作状态
- 使用 root 权限的应用程序来验证 root 访问是否工作正常
- 在设置 -> 关于手机中检查内核版本
## 需要帮助?
如果在安装过程中遇到问题:
1. 查看我们的[兼容性指南](./compatibility)了解设备要求
2. 访问我们的 [GitHub 仓库](https://github.com/sukisu-ultra/sukisu-ultra)获取支持
3. 加入我们的 [Telegram 社区](https://t.me/sukiksu)获取帮助
::: danger 安全提醒
**始终有备用计划!** 保留您的原始 `boot.img/init_boot.img` 并知道如何在出现问题时恢复设备。
:::

View File

@@ -1,77 +0,0 @@
# 许可证
## 软件许可
### 内核组件
::: info GPL-2.0 许可证
"kernel" 目录中的文件采用 GPL-2.0-only 许可证
:::
**许可证:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
### 应用程序核心
::: tip GPL-3.0 许可证
所有其他部分(除下述特别说明外)采用 GPL-3.0 或更高版本许可证
:::
**许可证:** [GPL-3.0 或更高版本](https://www.gnu.org/licenses/gpl-3.0.html)
## 艺术作品与品牌资产
### 启动器图标与角色艺术
::: warning 版权声明
动画角色艺术作品有特殊许可要求
:::
包含动画角色表情的文件 `ic_launcher(?!.*alt.*).*` 图像有特定的版权条款:
**版权持有者:**
- **动画角色艺术:** [怡子曰曰](https://space.bilibili.com/10545509)
- **品牌知识产权:** [明风OuO](https://space.bilibili.com/274939213)
- **矢量化制作:** @MiRinChan
**许可要求:**
1. **知识共享许可证:** [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.txt)
2. **作者授权:** 需要获得两位版权持有者的授权
3. **署名要求:** 必须署名上述所有贡献者
::: details 使用要求
在使用这些艺术资产之前,您必须:
- 遵守知识共享署名-非商业性使用-相同方式共享 4.0 国际许可证
- 获得两位原作者对艺术内容使用的授权
- 为所有贡献者提供适当的署名
:::
## 摘要
| 组件 | 许可证 | 备注 |
| ---------------- | ------------------------------------------------------------------------- | ----------------------- |
| **内核文件** | [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) | 主要应用程序组件 |
| **角色艺术** | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) | + 需要作者授权 |
| **品牌资产** | 混合许可 | 查看具体署名要求 |
## 链接
- **GPL-2.0** [完整许可证文本](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
- **GPL-3.0** [完整许可证文本](https://www.gnu.org/licenses/gpl-3.0.html)
- **CC BY-NC-SA 4.0** [完整许可证文本](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.txt)
## 问题
关于许可证或使用权限的问题:
1. **代码许可:** 参考相应的 GPL 许可证条款
2. **艺术作品使用:** 联系原作者获取授权
3. **商业使用:** 查看 CC BY-NC-SA 4.0 限制条款
4. **分发:** 确保遵守所有适用的许可证
::: tip 合规提示
在重新分发或修改 SukiSU-Ultra 时,请确保遵守各个组件的所有适用许可证和署名要求。
:::

View File

@@ -1,71 +0,0 @@
# 更多链接
## 参与翻译
::: info 贡献翻译
如果您需要为管理器提交翻译,请访问我们的 Crowdin 项目
:::
**翻译平台:** [Crowdin - SukiSU-Ultra](https://crowdin.com/project/SukiSU-Ultra)
## 项目与构建
基于 Sukisu 和 susfs 编译的项目:
### GKI 构建
::: tip 通用 GKI 支持
集成 KernelSU 和 SUSFS 的通用内核映像构建
:::
**仓库:** [GKI_KernelSU_SUSFS](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
### OnePlus 构建
::: tip 设备特定构建
带有 MKSU 和 SUSFS 的自动化 OnePlus 内核构建
:::
**仓库:** [Action_OnePlus_MKSU_SUSFS](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
## 社区与支持
### Telegram 社区
> 与其他用户联系,获取支持并保持更新
**主群组:** [Tg Group](https://t.me/sukiksu)
### 测试构建
::: warning 实验性构建
测试构建是实验性的,可能不稳定
:::
**测试构建频道:** [最新测试构建](https://t.me/Sukiksu/7114)
## 下载与发布
### 官方发布
> 从我们的 GitHub 发布页面下载最新稳定版本
**GitHub 发布:** [SukiSU-Ultra 发布](https://github.com/sukisu-ultra/sukisu-ultra/releases)
### 问题报告
> 在我们的 GitHub 仓库上报告错误或请求新功能
**GitHub 问题:** [报告问题](https://github.com/sukisu-ultra/sukisu-ultra/issues)
## 快速链接汇总
| 资源 | 链接 | 描述 |
| ----------------- | :--------------------------------------------------------------------: | -----------: |
| **翻译** | [Crowdin](https://crowdin.com/project/SukiSU-Ultra) | 提交翻译 |
| **Telegram 群组** | [t.me/sukiksu](https://t.me/sukiksu) | 社区支持 |
| **测试构建** | [测试频道](https://t.me/Sukiksu/7114) | 实验性构建 |
| **发布** | [GitHub 发布](https://github.com/sukisu-ultra/sukisu-ultra/releases) | 稳定下载 |
| **问题** | [GitHub 问题](https://github.com/sukisu-ultra/sukisu-ultra/issues) | 错误报告 |
| **GKI 构建** | [GKI 仓库](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS) | 通用构建 |
| **OnePlus 构建** | [OnePlus 仓库](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS) | 设备特定构建 |

View File

@@ -1,266 +0,0 @@
# Tracepoint Hook 集成
## 介绍
自 commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124) 起SukiSU 引入了 Tracepoint Hook
**该 Hook 理论上相比于 Kprobes Hook性能开销更小但次于 Manual Hook / Syscall Hook**
::: warning
目前 Tracepoint Hook 在 6.x 设备上并不稳定,请勿使用,否则可能出现无法开机或无法获取 `ROOT` 权限等问题。
:::
> [!NOTE]
> 本教程参考了 [backslashxx/KernelSU#5](https://github.com/backslashxx/KernelSU/issues/5) 的 syscall hook v1.4 版本钩子,以及原版 KernelSU 的 [Manual Hook](https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source)
## 在内核中放置 TP 钩子
::: code-group
```diff[exec.c]
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -78,6 +78,10 @@
#include <trace/hooks/sched.h>
#endif
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
EXPORT_TRACEPOINT_SYMBOL_GPL(task_rename);
static int bprm_creds_from_file(struct linux_binprm *bprm);
@@ -2037,6 +2041,9 @@ static int do_execve(struct filename *filename,
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
@@ -2064,6 +2071,9 @@ static int compat_do_execve(struct filename *filename,
.is_compat = true,
.ptr.compat = __envp,
};
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_sucompat_hook((int *)AT_FDCWD, &filename, NULL, NULL, NULL); /* 32-bit su */
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
```
```diff[open.c]
--- a/fs/open.c
+++ b/fs/open.c
@@ -37,6 +37,10 @@
#include "internal.h"
#include <trace/hooks/syscall_check.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry,
loff_t length, unsigned int time_attrs, struct file *filp)
{
@@ -468,6 +472,9 @@ static long do_faccessat(int dfd, const char __user *filename, int mode, int fla
SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
return do_faccessat(dfd, filename, mode, 0);
}
```
```diff[read_write.c]
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,10 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -630,6 +634,9 @@ ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
return ksys_read(fd, buf, count);
}
```
```diff[stat.c]
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -24,6 +24,10 @@
#include "internal.h"
#include "mount.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
/**
* generic_fillattr - Fill in the basic attributes from the inode struct
* @mnt_userns: user namespace of the mount the inode was found from
@@ -408,6 +412,10 @@ SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag);
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
@@ -559,6 +567,10 @@ SYSCALL_DEFINE4(fstatat64, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag); /* 32-bit su support */
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
```
:::
通常是要改四个地方:
1. compat_do_execve通常位于 `fs/exec.c`
2. do_faccessat通常位于 `/fs/open.c`
3. sys_read通常位于 `fs/read_write.c`
4. newfstatat SYSCALL通常位于 `fs/stat.c`
如果没有 do_faccessat 方法,可以找 faccessat 的 SYSCALL 定义(对于早于 4.17 的内核)
```diff
--- a/fs/open.c
+++ b/fs/open.c
@@ -31,6 +31,9 @@
#include <linux/ima.h>
#include <linux/dnotify.h>
#include <linux/compat.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
#include "internal.h"
@@ -369,6 +372,9 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
int res;
unsigned int lookup_flags = LOOKUP_FOLLOW;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
return -EINVAL;
```
如果没有 sys_read 方法,并且 4.14 及以下需要修改 read 的 SYSCALL 定义
```diff
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,11 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -575,6 +580,9 @@ SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
if (f.file) {
loff_t pos = file_pos_read(f.file);
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
ret = vfs_read(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
```
## 安全模式
要使用 KernelSU 内置的安全模式,你还需要修改 `drivers/input/input.c` 中的 input_handle_event 方法:
```diff
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -26,6 +26,10 @@
#include "input-compat.h"
#include "input-poller.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../../drivers/kernelsu/ksu_trace.h>
+#endif
+
MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
MODULE_DESCRIPTION("Input core");
MODULE_LICENSE("GPL");
@@ -451,6 +455,10 @@ void input_event(struct input_dev *dev,
{
unsigned long flags;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_input_hook(&type, &code, &value);
+#endif
+
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
```
## pm 命令执行失败?
你需要修改 `drivers/tty/pty.c`
```diff
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -31,6 +31,10 @@
#include <linux/compat.h>
#include "tty.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../../drivers/kernelsu/ksu_trace.h>
+#endif
+
#undef TTY_DEBUG_HANGUP
#ifdef TTY_DEBUG_HANGUP
# define tty_debug_hangup(tty, f, args...) tty_debug(tty, f, ##args)
@@ -707,6 +711,10 @@ static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
{
struct tty_struct *tty;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_devpts_hook((struct inode *)file->f_path.dentry->d_inode);
+#endif
+
mutex_lock(&devpts_mutex);
tty = devpts_get_priv(file->f_path.dentry);
mutex_unlock(&devpts_mutex);
```

View File

@@ -1,37 +0,0 @@
---
layout: home
hero:
name: 'SukiSU-Ultra'
text: '下一代 Android Root 解决方案'
tagline: Android 上的内核级的高级 root 方案
image:
src: /logo.svg
alt: SukiSU-Ultra
actions:
- theme: brand
text: 开始使用
link: /zh/guide/
- theme: alt
text: 在 GitHub 上查看
link: https://github.com/sukisu-ultra/sukisu-ultra
features:
- title: 内核级的 su 和 root 权限管理
details: 在内核进行安全的 root 权限管理。
- title: 不基于 OverlayFS 的模块系统
details: 模块系统基于来自 5ec1cff 的 Magic Mount。
- title: App Profile
details: 把 root 权限关进笼子里。
- title: 重新支持非 GKI 与 GKI 1.0 内核
details: 增强对旧设备的兼容性。
- title: 更多自定义选项
details: 提供广泛的自定义选项。
- title: 支持 KPM
details: 完整的基于 KernelPatch 的 KPM 功能。
---

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,26 +0,0 @@
{
"name": "SukiSU",
"short_name": "SukiSU",
"icons": [
{
"src": "/favicon.svg",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"theme_color": "#000000",
"background_color": "#000000",
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -1,57 +0,0 @@
{
"name": "sukisu-ultra-docs",
"version": "2.0.0",
"description": "SukiSU-Ultra Documentation - Next-Generation Android Root Solution",
"type": "module",
"scripts": {
"dev": "tsx scripts/rebuild-ico.ts && tsx scripts/sync-favicons.ts && vitepress dev docs --host",
"build": "tsx scripts/sync-favicons.ts && vitepress build docs && pnpm build:sw:dist",
"preview": "vitepress preview docs",
"build:sw": "tsc scripts/sw.ts --target ES2022 --lib ES2022,WebWorker --outDir docs/public --skipLibCheck",
"build:sw:dist": "tsc scripts/sw.ts --target ES2022 --lib ES2022,WebWorker --outDir docs/.vitepress/dist --skipLibCheck",
"build:scripts": "pnpm build:sw",
"type-check": "tsc --noEmit",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"keywords": [
"android",
"root",
"kernelsu",
"sukisu",
"documentation",
"vitepress"
],
"repository": {
"type": "git",
"url": "https://github.com/sukisu-ultra/sukisu-ultra.git"
},
"homepage": "https://sukisu.org",
"bugs": {
"url": "https://github.com/sukisu-ultra/sukisu-ultra/issues"
},
"devDependencies": {
"@types/node": "^24.3.0",
"@types/serviceworker": "^0.0.152",
"markdown-it-footnote": "^4.0.0",
"markdown-it-mark": "^4.0.0",
"markdown-it-mathjax3": "^4.3.2",
"markdown-it-sub": "^2.0.0",
"markdown-it-task-lists": "^2.1.1",
"prettier": "^3.6.2",
"terser": "^5.44.0",
"ts-node": "^10.9.2",
"tsx": "^4.20.5",
"typescript": "^5.9.2",
"vitepress": "1.6.4",
"vue": "^3.5.21"
},
"engines": {
"node": ">=22"
},
"packageManager": "pnpm@10.17.1",
"dependencies": {
"@nolebase/vitepress-plugin-git-changelog": "^2.18.2",
"vitepress-plugin-group-icons": "^1.6.3"
}
}

View File

@@ -1,6 +0,0 @@
packages:
- docs
- .
onlyBuiltDependencies:
- esbuild

View File

@@ -1,207 +0,0 @@
/// <reference lib="webworker" />
const CACHE_NAME: string = 'sukisu-ultra-v2.0'
const STATIC_CACHE: string = 'sukisu-static-v2.0'
const RUNTIME_CACHE: string = 'sukisu-runtime-v2.0'
const IMAGE_CACHE: string = 'sukisu-images-v2.0'
const CRITICAL_ASSETS: string[] = [
'/',
'/guide/',
'/guide/installation',
'/guide/compatibility',
'/zh/',
'/zh/guide/',
'/logo.svg',
'/favicon.ico',
'/offline.html',
]
const CACHE_STRATEGIES: {
static: string[]
images: string[]
documents: string[]
} = {
static: ['.css', '.js', '.woff2', '.woff', '.ttf', '.otf'],
images: ['.png', '.jpg', '.jpeg', '.svg', '.webp', '.avif', '.gif', '.ico'],
documents: ['.html', '.json', '.xml'],
}
self.addEventListener('install', (event: ExtendableEvent) => {
event.waitUntil(
(async (): Promise<void> => {
const cache: Cache = await caches.open(STATIC_CACHE)
try {
await cache.addAll(CRITICAL_ASSETS)
} catch (error) {
console.warn('Failed to cache some assets:', error)
}
;(self as any).skipWaiting()
})()
)
})
self.addEventListener('activate', (event: ExtendableEvent) => {
event.waitUntil(
(async (): Promise<void> => {
const cacheNames: string[] = await caches.keys()
const validCaches: string[] = [STATIC_CACHE, RUNTIME_CACHE, IMAGE_CACHE, CACHE_NAME]
await Promise.all(
cacheNames
.filter((name: string) => !validCaches.includes(name))
.map((name: string) => caches.delete(name))
)
;(self as any).clients.claim()
})()
)
})
self.addEventListener('fetch', (event: FetchEvent) => {
const { request } = event
const url: URL = new URL(request.url)
if (request.method !== 'GET' || url.origin !== location.origin) return
if (url.pathname.startsWith('/api/')) return
event.respondWith(handleRequest(request, url))
})
async function handleRequest(request: Request, url: URL): Promise<Response> {
const isStatic: boolean = CACHE_STRATEGIES.static.some((ext: string) =>
url.pathname.endsWith(ext)
)
const isImage: boolean = CACHE_STRATEGIES.images.some((ext: string) => url.pathname.endsWith(ext))
const isDocument: boolean =
CACHE_STRATEGIES.documents.some((ext: string) => url.pathname.endsWith(ext)) ||
url.pathname.endsWith('/')
try {
if (isStatic) {
return await handleStatic(request)
} else if (isImage) {
return await handleImage(request)
} else if (isDocument) {
return await handleDocument(request)
} else {
return await handleDefault(request)
}
} catch (error) {
return await handleOffline(request)
}
}
async function handleStatic(request: Request): Promise<Response> {
const cache: Cache = await caches.open(STATIC_CACHE)
const cached: Response | undefined = await cache.match(request)
if (cached) {
fetch(request)
.then((response: Response) => {
if (response.ok) cache.put(request, response.clone())
})
.catch(() => {})
return cached
}
const response: Response = await fetch(request)
if (response.ok) {
cache.put(request, response.clone())
}
return response
}
async function handleImage(request: Request): Promise<Response> {
const cache: Cache = await caches.open(IMAGE_CACHE)
const cached: Response | undefined = await cache.match(request)
if (cached) return cached
const response: Response = await fetch(request)
if (response.ok) {
cache.put(request, response.clone())
}
return response
}
async function handleDocument(request: Request): Promise<Response> {
const cache: Cache = await caches.open(RUNTIME_CACHE)
try {
const response: Response = await fetch(request)
if (response.ok) {
cache.put(request, response.clone())
}
return response
} catch (error) {
const cached: Response | undefined = await cache.match(request)
if (cached) return cached
if (request.mode === 'navigate') {
const offlinePage: Response | undefined = await caches.match('/offline.html')
if (offlinePage) return offlinePage
}
throw error
}
}
async function handleDefault(request: Request): Promise<Response> {
const cache: Cache = await caches.open(RUNTIME_CACHE)
try {
const response: Response = await fetch(request)
if (response.ok) {
cache.put(request, response.clone())
}
return response
} catch (error) {
const cached: Response | undefined = await cache.match(request)
if (cached) return cached
throw error
}
}
async function handleOffline(request: Request): Promise<Response> {
const cache: Cache = await caches.open(STATIC_CACHE)
if (request.mode === 'navigate') {
const offlinePage: Response | undefined = await caches.match('/offline.html')
if (offlinePage) return offlinePage
}
return new Response('Offline', {
status: 503,
statusText: 'Service Unavailable',
headers: new Headers({
'Content-Type': 'text/plain',
}),
})
}
self.addEventListener('sync', (event: any) => {
if (event.tag === 'data-sync') {
event.waitUntil(syncData())
}
})
async function syncData(): Promise<void> {
try {
console.log('Syncing application data...')
} catch (error) {
console.error('Data sync failed:', error)
}
}
interface PerformanceMessage {
type: string
name: string
}
self.addEventListener('message', (event: MessageEvent) => {
const data = event.data as PerformanceMessage
if (data && data.type === 'PERFORMANCE_MARK') {
performance.mark(data.name)
}
})

View File

@@ -1,38 +0,0 @@
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const srcDir = path.join(__dirname, '../favicon')
const dstDir = path.join(__dirname, '../docs/public')
function ensureDir(dir: string) {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
}
function copyAll(src: string, dst: string) {
ensureDir(dst)
const entries = fs.readdirSync(src, { withFileTypes: true })
let count = 0
for (const entry of entries) {
const s = path.join(src, entry.name)
const d = path.join(dst, entry.name)
if (entry.isDirectory()) {
copyAll(s, d)
} else if (entry.isFile()) {
fs.copyFileSync(s, d)
count++
}
}
return count
}
if (!fs.existsSync(srcDir)) {
console.warn('Favicons source folder not found:', srcDir)
process.exit(0)
}
const copied = copyAll(srcDir, dstDir)
console.log(`✅ Synced ${copied} favicon file(s) from \'favicon\' to docs/public`)

View File

@@ -1,25 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./",
"types": ["node", "serviceworker"],
"lib": ["ES2022", "WebWorker"],
"ignoreDeprecations": "5.0"
},
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node"
},
"include": ["scripts/**/*"],
"exclude": ["node_modules", "dist", "docs/.vitepress/dist"]
}

View File

@@ -1,11 +0,0 @@
# Cloudflare Pages Configuration for SukiSU-Ultra
name = "sukisu-ultra-docs"
compatibility_date = "2025-08-04"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = "docs/.vitepress/dist"
[env.production]
name = "sukisu-ultra-docs"
[env.preview]
name = "sukisu-ultra-docs-preview"

View File

@@ -1,6 +1,6 @@
# SukiSU Ultra
<img align='right' src='SukiSU-mini.svg' width='220px' alt="sukisu logo">
<img align='right' src='zakomonochrome-128.svg' width='100px' alt="logo">
# SukiSU Ultra
**English** | [简体中文](./zh/README.md) | [日本語](./ja/README.md) | [Türkçe](./tr/README.md)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 185 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 200 KiB

View File

@@ -24,12 +24,6 @@ Prerequisites: open source bootable kernel.
- Requires [`guide/how-to-integrate.md`](guide/how-to-integrate.md)
- Requires [https://github.com/~](https://github.com/tiann/KernelSU/blob/main/website/docs/guide/how-to-integrate-for-non-gki.md#manually-modify-the-kernel-source)
3. **Tracepoint Hook:**
- Hook method introduced since SukiSU commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124)
- Requires `CONFIG_KSU_TRACEPOINT_HOOK=y`
- Requires [`guide/tracepoint-hook.md`](tracepoint-hook.md)
<!-- This part refer to [rsuntk/KernelSU](https://github.com/rsuntk/KernelSU). -->
If you're able to build a bootable kernel, there are two ways to integrate KernelSU into the kernel source code:

View File

@@ -1,239 +0,0 @@
# Tracepoint Hook Integration
## Introduction
Since commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124), SukiSU has introduced Tracepoint Hook
This Hook theoretically has lower performance overhead compared to Kprobes Hook, but is inferior to Manual Hook / Syscall Hook
> [!NOTE]
> This tutorial references the syscall hook v1.4 version from [backslashxx/KernelSU#5](https://github.com/backslashxx/KernelSU/issues/5), as well as the original KernelSU's [Manual Hook](https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source)
## Guide
### execve Hook (`exec.c`)
Generally need to modify the `do_execve` and `compat_do_execve` methods in `fs/exec.c`
```patch
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -78,6 +78,10 @@
#include <trace/hooks/sched.h>
#endif
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
EXPORT_TRACEPOINT_SYMBOL_GPL(task_rename);
static int bprm_creds_from_file(struct linux_binprm *bprm);
@@ -2037,6 +2041,9 @@ static int do_execve(struct filename *filename,
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
@@ -2064,6 +2071,9 @@ static int compat_do_execve(struct filename *filename,
.is_compat = true,
.ptr.compat = __envp,
};
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0); // 32-bit su and 32-on-64 support
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
```
### faccessat Hook (`open.c`)
Generally need to modify the `do_faccessat` method in `/fs/open.c`
```patch
--- a/fs/open.c
+++ b/fs/open.c
@@ -37,6 +37,10 @@
#include "internal.h"
#include <trace/hooks/syscall_check.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry,
loff_t length, unsigned int time_attrs, struct file *filp)
{
@@ -468,6 +472,9 @@ static long do_faccessat(int dfd, const char __user *filename, int mode, int fla
SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
return do_faccessat(dfd, filename, mode, 0);
}
```
If there's no `do_faccessat` method, you can find the `faccessat` SYSCALL definition (for kernels earlier than 4.17)
```patch
--- a/fs/open.c
+++ b/fs/open.c
@@ -31,6 +31,9 @@
#include <linux/ima.h>
#include <linux/dnotify.h>
#include <linux/compat.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
#include "internal.h"
@@ -369,6 +372,9 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
int res;
unsigned int lookup_flags = LOOKUP_FOLLOW;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
return -EINVAL;
```
### sys_read Hook (`read_write.c`)
Need to modify the `sys_read` method in `fs/read_write.c` (4.19 and above)
```patch
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,10 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -630,6 +634,9 @@ ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
return ksys_read(fd, buf, count);
}
```
Or the `read` SYSCALL definition (4.14 and below)
```patch
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,11 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -575,6 +580,9 @@ SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
if (f.file) {
loff_t pos = file_pos_read(f.file);
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
ret = vfs_read(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
```
### fstatat Hook (`stat.c`)
Need to modify the `newfstatat` SYSCALL definition in `stat.c`
If 32-bit support is needed, also need to modify the `statat64` SYSCALL definition
```patch
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -24,6 +24,10 @@
#include "internal.h"
#include "mount.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
/**
* generic_fillattr - Fill in the basic attributes from the inode struct
* @mnt_userns: user namespace of the mount the inode was found from
@@ -408,6 +412,10 @@ SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag);
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
@@ -559,6 +567,10 @@ SYSCALL_DEFINE4(fstatat64, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag); /* 32-bit su support */
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
```
### input Hook (`input.c`, for entering KSU built-in security mode)
Need to modify the `input_event` method in `drivers/input/input.c`, not `input_handle_event`
```patch
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -26,6 +26,10 @@
#include "input-compat.h"
#include "input-poller.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../../drivers/kernelsu/ksu_trace.h>
+#endif
+
MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
MODULE_DESCRIPTION("Input core");
MODULE_LICENSE("GPL");
@@ -451,6 +455,10 @@ void input_event(struct input_dev *dev,
{
unsigned long flags;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_input_hook(&type, &code, &value);
+#endif
+
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
```

View File

@@ -1,6 +1,4 @@
# SukiSU Ultra
<img align='right' src='SukiSU-mini.svg' width='220px' alt="sukisu logo">
[English](../README.md) | [简体中文](../zh/README.md) | **日本語** | [Türkçe](../tr/README.md)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 185 KiB

View File

@@ -1,6 +1,4 @@
# SukiSU Ultra
<img align='right' src='SukiSU-mini.svg' width='250px' alt="sukisu logo">
[English](../README.md) | [简体中文](../zh/README.md) | [日本語](../ja/README.md) | **Türkçe**

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 185 KiB

View File

@@ -1,6 +1,6 @@
# SukiSU Ultra
<img align='right' src='SukiSU-mini.svg' width='220px' alt="sukisu logo">
<img align='right' src='zakomonochrome-128.svg' width='100px' alt="logo">
# SukiSU Ultra
[English](../README.md) | **简体中文** | [日本語](../ja/README.md) | [Türkçe](../tr/README.md)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 185 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 200 KiB

View File

@@ -21,15 +21,9 @@ SukiSU 可以集成到 GKI 和 non-GKI 内核中,并且已反向移植到 4.14
<!-- - backslashxx's syscall manual hook: https://github.com/backslashxx/KernelSU/issues/5 (v1.5 version is not available at the moment, if you want to use it, please use v1.4 version, or standard KernelSU hooks)-->
- 需要 `CONFIG_KSU_MANUAL_HOOK=y`
- 需要 [`guide/how-to-integrate.md`](how-to-integrate.md)
- 需要 [`guide/how-to-integrate.md`](guide/how-to-integrate.md)
- 需要 [https://github.com/~](https://github.com/tiann/KernelSU/blob/main/website/docs/guide/how-to-integrate-for-non-gki.md#manually-modify-the-kernel-source)
3. **Tracepoint Hook:**
- 自 SukiSU commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124) 引入的 hook 方法
- 需要 `CONFIG_KSU_TRACEPOINT_HOOK=y`
- 需要 [`guide/tracepoint-hook.md`](tracepoint-hook.md)
<!-- This part refer to [rsuntk/KernelSU](https://github.com/rsuntk/KernelSU). -->
如果您能够构建可启动内核,有两种方法可以将 KernelSU 集成到内核源代码中:

View File

@@ -1,239 +0,0 @@
# Tracepoint Hook 集成
## 介绍
自 commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124) 起SukiSU 引入了 Tracepoint Hook
该 Hook 理论上相比于 Kprobes Hook性能开销更小但次于 Manual Hook / Syscall Hook
> [!NOTE]
> 本教程参考了 [backslashxx/KernelSU#5](https://github.com/backslashxx/KernelSU/issues/5) 的 syscall hook v1.4 版本钩子,以及原版 KernelSU 的 [Manual Hook](https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source)
## Guide
### execve 钩子(`exec.c`
一般需要修改 `fs/exec.c``do_execve``compat_do_execve` 方法
```patch
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -78,6 +78,10 @@
#include <trace/hooks/sched.h>
#endif
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
EXPORT_TRACEPOINT_SYMBOL_GPL(task_rename);
static int bprm_creds_from_file(struct linux_binprm *bprm);
@@ -2037,6 +2041,9 @@ static int do_execve(struct filename *filename,
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
@@ -2064,6 +2071,9 @@ static int compat_do_execve(struct filename *filename,
.is_compat = true,
.ptr.compat = __envp,
};
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0)); // 32-bit su and 32-on-64 support
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
```
### faccessat 钩子 (`open.c`)
一般需要修改 `/fs/open.c``do_faccessat` 方法
```patch
--- a/fs/open.c
+++ b/fs/open.c
@@ -37,6 +37,10 @@
#include "internal.h"
#include <trace/hooks/syscall_check.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry,
loff_t length, unsigned int time_attrs, struct file *filp)
{
@@ -468,6 +472,9 @@ static long do_faccessat(int dfd, const char __user *filename, int mode, int fla
SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
return do_faccessat(dfd, filename, mode, 0);
}
```
如果没有 `do_faccessat` 方法,可以找 `faccessat` 的 SYSCALL 定义(对于早于 4.17 的内核)
```patch
--- a/fs/open.c
+++ b/fs/open.c
@@ -31,6 +31,9 @@
#include <linux/ima.h>
#include <linux/dnotify.h>
#include <linux/compat.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
#include "internal.h"
@@ -369,6 +372,9 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
int res;
unsigned int lookup_flags = LOOKUP_FOLLOW;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
return -EINVAL;
```
### sys_read 钩子 ( `read_write.c` )
需要修改 `fs/read_write.c``sys_read` 方法4.19 及以上)
```patch
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,10 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -630,6 +634,9 @@ ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
return ksys_read(fd, buf, count);
}
```
或者是 `read` 的 SYSCALL 定义4.14 及以下)
```patch
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,11 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -575,6 +580,9 @@ SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
if (f.file) {
loff_t pos = file_pos_read(f.file);
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
ret = vfs_read(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
```
### fstatat 钩子 ( `stat.c` )
需要修改 `stat.c``newfstatat` SYSCALL 定义
如果需要 32 位支持,还需要修改 `statat64` SYSCALL 定义
```patch
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -24,6 +24,10 @@
#include "internal.h"
#include "mount.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
/**
* generic_fillattr - Fill in the basic attributes from the inode struct
* @mnt_userns: user namespace of the mount the inode was found from
@@ -408,6 +412,10 @@ SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag);
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
@@ -559,6 +567,10 @@ SYSCALL_DEFINE4(fstatat64, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag); /* 32-bit su support */
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
```
### input 钩子 (`input.c` ,用于进入KSU系的内置安全模式)
需要修改 `drivers/input/input.c``input_event` 方法,而不是 `input_handle_event`
```patch
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -26,6 +26,10 @@
#include "input-compat.h"
#include "input-poller.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../../drivers/kernelsu/ksu_trace.h>
+#endif
+
MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
MODULE_DESCRIPTION("Input core");
MODULE_LICENSE("GPL");
@@ -451,6 +455,10 @@ void input_event(struct input_dev *dev,
{
unsigned long flags;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_input_hook(&type, &code, &value);
+#endif
+
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
```

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="128"
height="128"
viewBox="0 0 128 128"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="zakomonochrome-128.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="2.6185048"
inkscape:cx="59.957881"
inkscape:cy="71.032903"
inkscape:window-width="1280"
inkscape:window-height="696"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="图层 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#ffffff;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;paint-order:fill markers stroke;fill-opacity:1"
id="rect1"
width="128"
height="128"
x="0"
y="0"
rx="7.772471"
ry="7.772471" />
<path
id="path101"
style="fill:#ffffff;fill-opacity:0.734285;stroke:#000000;stroke-width:4.27504;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 42.510282,81.796052 c 0,0 -7.224141,-5.638356 -10.043315,-9.338525 M 14.847106,81.97224 25.41902,71.576535 m 0.17619,-6.695549 2.819179,19.910444 M 11.675534,73.338532 38.281518,71.047931 M 43.567475,62.7666 34.40515,62.942814 M 34.22896,62.590425 33.524162,48.494537 m -18.500855,1.58577 17.972249,-1.409582 m -11.8053,-5.462154 0.352397,18.853251"
inkscape:label="杂" />
<path
id="path111"
style="fill:#ffffff;fill-opacity:0.734285;stroke:#000000;stroke-width:3.94824;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.3;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="M 55.912937,82.876745 79.671596,81.412163 M 59.330273,75.391135 74.952411,74.089291 m -9.43837,-14.157553 1.139102,14.645756 m -8.299247,-7.160159 16.273048,-1.464569 m 0.650926,8.136525 0.325472,-14.808482 m -0.162747,0.162739 -17.900363,0.976379 m 0,-0.162738 1.952774,14.645756 m 12.042061,-21.154974 1.464576,-6.346492 m 0,-0.650928 -12.042063,0.650928 m -0.650918,6.509218 0.325459,-8.787441"
inkscape:label="鱼" />
<path
d="m 95.08569,51.121163 c -1.90515,0.116064 -3.64694,0.97349 -4.86738,2.391307 -1.34538,1.56738 -1.91476,3.733159 -1.59523,6.070852 0.40842,2.982962 2.1502,6.17135 5.13887,9.411078 0.63424,0.68546 1.08109,1.129773 1.98202,1.967071 1.58321,1.469144 3.01507,2.634638 4.9875,4.052454 0.70392,0.50905 2.09253,1.453525 2.61627,1.781734 l 0.15133,0.09594 0.22103,-0.140663 c 0.80481,-0.515755 2.23909,-1.504852 3.08956,-2.130057 3.21689,-2.364488 5.79232,-4.737902 7.70228,-7.100167 3.09676,-3.831409 4.4133,-7.562359 3.80549,-10.773058 -0.42043,-2.210414 -1.82588,-4.039057 -3.81992,-4.967887 -0.85767,-0.399664 -1.69132,-0.607312 -2.6355,-0.656431 -1.22285,-0.0647 -2.42648,0.178619 -3.57485,0.721182 -1.95561,0.922124 -3.58927,2.719503 -4.61752,5.081755 -0.072,0.165235 -0.1394,0.310355 -0.14895,0.319295 -0.0312,0.02902 -0.0648,-0.02679 -0.19458,-0.330457 -0.30752,-0.714476 -0.91055,-1.752718 -1.38382,-2.377871 -0.4853,-0.645282 -1.2661,-1.431214 -1.84749,-1.862143 -1.50155,-1.114153 -3.26013,-1.658924 -5.00914,-1.553996 z"
id="path1-4"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.00231605" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -56,8 +56,8 @@ ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
#CompactNamespaces: false # Unknown to clang-format-4.0
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
@@ -501,7 +501,7 @@ IncludeCategories:
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
#IndentPPDirectives: None # Unknown to clang-format-5.0
IndentWidth: 4
IndentWidth: 8
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
@@ -511,7 +511,7 @@ MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
ObjCBlockIndentWidth: 4
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
@@ -543,6 +543,6 @@ SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp03
TabWidth: 4
UseTab: Never
TabWidth: 8
UseTab: Always
...

22
kernel/.gitignore vendored
View File

@@ -1,22 +0,0 @@
.cache/
.thinlto-cache/
compile_commands.json
*.ko
*.o
*.mod
*.lds
*.mod.o
.*.o*
.*.mod*
*.ko*
*.mod.c
*.symvers*
*.order
.*.ko.cmd
.tmp_versions/
libs/
obj/
CLAUDE.md
.ddk-version
.vscode/settings.json

View File

@@ -1,11 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"cStandard": "c11",
"intelliSenseMode": "gcc-arm64",
"compileCommands": "${workspaceFolder}/compile_commands.json"
}
],
"version": 4
}

View File

@@ -1,92 +0,0 @@
#!/usr/bin/env python3
from __future__ import print_function, division
import argparse
import fnmatch
import functools
import json
import math
import multiprocessing
import os
import re
import sys
CMD_VAR_RE = re.compile(r'^\s*(?:saved)?cmd_(\S+)\s*:=\s*(.+)\s*$', re.MULTILINE)
SOURCE_VAR_RE = re.compile(r'^\s*source_(\S+)\s*:=\s*(.+)\s*$', re.MULTILINE)
def print_progress_bar(progress):
progress_bar = '[' + '|' * int(50 * progress) + '-' * int(50 * (1.0 - progress)) + ']'
print('\r', progress_bar, "{0:.1%}".format(progress), end='\r', file=sys.stderr)
def parse_cmd_file(out_dir, cmdfile_path):
with open(cmdfile_path, 'r') as cmdfile:
cmdfile_content = cmdfile.read()
commands = { match.group(1): match.group(2) for match in CMD_VAR_RE.finditer(cmdfile_content) }
sources = { match.group(1): match.group(2) for match in SOURCE_VAR_RE.finditer(cmdfile_content) }
return [{
'directory': out_dir,
'command': commands[o_file_name],
'file': source,
'output': o_file_name
} for o_file_name, source in sources.items()]
def gen_compile_commands(cmd_file_search_path, out_dir):
print("Building *.o.cmd file list...", file=sys.stderr)
out_dir = os.path.abspath(out_dir)
if not cmd_file_search_path:
cmd_file_search_path = [out_dir]
cmd_files = []
for search_path in cmd_file_search_path:
if (os.path.isdir(search_path)):
for cur_dir, subdir, files in os.walk(search_path):
cmd_files.extend(os.path.join(cur_dir, cmdfile_name) for cmdfile_name in fnmatch.filter(files, '*.o.cmd'))
else:
cmd_files.extend(search_path)
if not cmd_files:
print("No *.o.cmd files found in", ", ".join(cmd_file_search_path), file=sys.stderr)
return
print("Parsing *.o.cmd files...", file=sys.stderr)
n_processed = 0
print_progress_bar(0)
compdb = []
pool = multiprocessing.Pool()
try:
for compdb_chunk in pool.imap_unordered(functools.partial(parse_cmd_file, out_dir), cmd_files, chunksize=int(math.sqrt(len(cmd_files)))):
compdb.extend(compdb_chunk)
n_processed += 1
print_progress_bar(n_processed / len(cmd_files))
finally:
pool.terminate()
pool.join()
print(file=sys.stderr)
print("Writing compile_commands.json...", file=sys.stderr)
with open('compile_commands.json', 'w') as compdb_file:
json.dump(compdb, compdb_file, indent=1)
def main():
cmd_parser = argparse.ArgumentParser()
cmd_parser.add_argument('-O', '--out-dir', type=str, default=os.getcwd(), help="Build output directory")
cmd_parser.add_argument('cmd_file_search_path', nargs='*', help="*.cmd file search path")
gen_compile_commands(**vars(cmd_parser.parse_args()))
if __name__ == '__main__':
main()

View File

@@ -1,16 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Generate compile_commands.json",
"type": "process",
"command": "python",
"args": [
"${workspaceRoot}/.vscode/generate_compdb.py"
],
"problemMatcher": []
}
]
}

View File

@@ -1,42 +1,57 @@
menu "KernelSU"
config KSU
tristate "KernelSU function support"
default y
help
Enable kernel-level root privileges on Android System.
To compile as a module, choose M here: the
module will be called kernelsu.
tristate "KernelSU function support"
depends on OVERLAY_FS
default y
help
Enable kernel-level root privileges on Android System.
To compile as a module, choose M here: the
module will be called kernelsu.
config KSU_DEBUG
bool "KernelSU debug mode"
depends on KSU
default n
help
Enable KernelSU debug mode.
config KSU_MANUAL_SU
bool "Use manual su"
depends on KSU
default y
help
Use manual su and authorize the corresponding command line and application via prctl
bool "KernelSU debug mode"
depends on KSU
default n
help
Enable KernelSU debug mode.
config KPM
bool "Enable SukiSU KPM"
depends on KSU && 64BIT
default n
bool "Enable SukiSU KPM"
depends on KSU && 64BIT
default n
help
Enabling this option will activate the KPM feature of SukiSU.
This option is suitable for scenarios where you need to force KPM to be enabled.
but it may affect system stability.
select KALLSYMS
select KALLSYMS_ALL
choice
prompt "KernelSU hook type"
depends on KSU
default KSU_KPROBES_HOOK
help
Enabling this option will activate the KPM feature of SukiSU.
This option is suitable for scenarios where you need to force KPM to be enabled.
but it may affect system stability.
select KALLSYMS
select KALLSYMS_ALL
Hook type for KernelSU
config KSU_KPROBES_HOOK
bool "Hook KernelSU with Kprobes"
depends on KPROBES
help
If enabled, Hook required KernelSU syscalls with Kernel-probe.
config KSU_TRACEPOINT_HOOK
bool "Hook KernelSU with Tracepoint"
depends on TRACEPOINTS
help
If enabled, Hook required KernelSU syscalls with Tracepoint.
config KSU_MANUAL_HOOK
bool "Hook KernelSU manually"
depends on KSU != m
help
If enabled, Hook required KernelSU syscalls with manually-patched function.
depends on KSU != m
help
If enabled, Hook required KernelSU syscalls with manually-patched function.
endchoice
endmenu

View File

@@ -1,28 +1,16 @@
kernelsu-objs := ksu.o
kernelsu-objs += allowlist.o
kernelsu-objs += app_profile.o
kernelsu-objs += dynamic_manager.o
kernelsu-objs += dynamic_sign.o
kernelsu-objs += apk_sign.o
kernelsu-objs += sucompat.o
kernelsu-objs += syscall_hook_manager.o
kernelsu-objs += throne_tracker.o
kernelsu-objs += pkg_observer.o
kernelsu-objs += throne_tracker.o
kernelsu-objs += umount_manager.o
kernelsu-objs += setuid_hook.o
kernelsu-objs += kernel_umount.o
kernelsu-objs += supercalls.o
kernelsu-objs += feature.o
kernelsu-objs += core_hook.o
kernelsu-objs += ksud.o
kernelsu-objs += embed_ksud.o
kernelsu-objs += seccomp_cache.o
kernelsu-objs += file_wrapper.o
kernelsu-objs += throne_comm.o
kernelsu-objs += sulog.o
kernelsu-objs += kernel_compat.o
ifeq ($(CONFIG_KSU_MANUAL_SU), y)
ccflags-y += -DCONFIG_KSU_MANUAL_SU
kernelsu-objs += manual_su.o
ifeq ($(strip $(CONFIG_KSU_TRACEPOINT_HOOK)),y)
kernelsu-objs += ksu_trace.o
endif
kernelsu-objs += selinux/selinux.o
@@ -32,62 +20,46 @@ ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
obj-$(CONFIG_KSU) += kernelsu.o
obj-$(CONFIG_KSU_TRACEPOINT_HOOK) += ksu_trace_export.o
obj-$(CONFIG_KPM) += kpm/
REPO_OWNER := SukiSU-Ultra
REPO_NAME := SukiSU-Ultra
REPO_BRANCH := main
KSU_VERSION_API := 4.0.0
KSU_VERSION_API := 3.1.8
GIT_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git
CURL_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin curl
KDIR := $(KDIR)
MDIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
ifneq ($(KDIR),)
$(info -- KDIR: $(KDIR))
$(info -- MDIR: $(MDIR))
endif
KSU_GITHUB_VERSION := $(shell $(CURL_BIN) -s "https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
KSU_GITHUB_VERSION_COMMIT := $(shell $(CURL_BIN) -sI "https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/commits?sha=$(REPO_BRANCH)&per_page=1" | grep -i "link:" | sed -n 's/.*page=\([0-9]*\)>; rel="last".*/\1/p')
ifeq ($(findstring $(srctree),$(src)),$(srctree))
KSU_SRC := $(src)
else
KSU_SRC := $(srctree)/$(src)
endif
ifneq ($(shell test -e $(KSU_SRC)/../.git && echo "in-tree"),in-tree)
KSU_SRC := $(MDIR)
endif
LOCAL_GIT_EXISTS := $(shell test -e $(KSU_SRC)/../.git && echo 1 || echo 0)
LOCAL_GIT_EXISTS := $(shell test -e $(srctree)/$(src)/../.git && echo 1 || echo 0)
define get_ksu_version_full
v$1-$(shell cd $(KSU_SRC); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(KSU_SRC); $(GIT_BIN) rev-parse --abbrev-ref HEAD)
v$1-$(shell cd $(srctree)/$(src); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(srctree)/$(src); $(GIT_BIN) rev-parse --abbrev-ref HEAD)
endef
ifeq ($(KSU_GITHUB_VERSION_COMMIT),)
ifeq ($(LOCAL_GIT_EXISTS),1)
$(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
KSU_LOCAL_VERSION := $(shell cd $(KSU_SRC); $(GIT_BIN) rev-list --count $(REPO_BRANCH))
KSU_VERSION := $(shell expr 40000 + $(KSU_LOCAL_VERSION) - 2815)
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
KSU_LOCAL_VERSION := $(shell cd $(srctree)/$(src); $(GIT_BIN) rev-list --count $(REPO_BRANCH))
KSU_VERSION := $(shell expr 10000 + $(KSU_LOCAL_VERSION) + 700)
$(info -- $(REPO_NAME) version (local .git): $(KSU_VERSION))
else
KSU_VERSION := 13000
$(warning -- Could not fetch version online or via local .git! Using fallback version: $(KSU_VERSION))
endif
else
KSU_VERSION := $(shell expr 40000 + $(KSU_GITHUB_VERSION_COMMIT) - 2815)
KSU_VERSION := $(shell expr 10000 + $(KSU_GITHUB_VERSION_COMMIT) + 700)
$(info -- $(REPO_NAME) version (GitHub): $(KSU_VERSION))
endif
ifeq ($(KSU_GITHUB_VERSION),)
ifeq ($(LOCAL_GIT_EXISTS),1)
$(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
KSU_VERSION_FULL := $(call get_ksu_version_full,$(KSU_VERSION_API))
$(info -- $(REPO_NAME) version (local .git): $(KSU_VERSION_FULL))
$(info -- $(REPO_NAME) Formatted version (local .git): $(KSU_VERSION))
@@ -96,7 +68,7 @@ ifeq ($(KSU_GITHUB_VERSION),)
$(warning -- $(REPO_NAME) version: $(KSU_VERSION_FULL))
endif
else
$(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
KSU_VERSION_FULL := $(call get_ksu_version_full,$(KSU_GITHUB_VERSION))
$(info -- $(REPO_NAME) version (Github): $(KSU_VERSION_FULL))
endif
@@ -120,13 +92,14 @@ ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE))
endif
ifeq ($(CONFIG_KSU_MANUAL_HOOK), y)
ccflags-y += -DKSU_MANUAL_HOOK
$(info -- SukiSU: KSU_MANUAL_HOOK Temporarily discontinued)
else
ccflags-y += -DKSU_KPROBES_HOOK
ccflags-y += -DKSU_TP_HOOK
$(info -- SukiSU: KSU_TRACEPOINT_HOOK)
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
ifeq ($(strip $(CONFIG_KSU_KPROBES_HOOK)),y)
$(info -- SukiSU: CONFIG_KSU_KPROBES_HOOK)
else ifeq ($(strip $(CONFIG_KSU_TRACEPOINT_HOOK)),y)
$(info -- SukiSU: CONFIG_KSU_TRACEPOINT_HOOK)
else ifeq ($(strip $(CONFIG_KSU_MANUAL_HOOK)),y)
$(info -- SukiSU: CONFIG_KSU_MANUAL_HOOK)
endif
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
@@ -143,30 +116,14 @@ endif
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
$(info -- KERNEL_TYPE: $(KERNEL_TYPE))
ifeq ($(CONFIG_KPM), y)
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
ifeq ($(CONFIG_KPM),y)
$(info -- KPM is enabled)
else
$(info -- KPM is disabled)
endif
# Check new vfs_getattr()
ifeq ($(shell grep -A1 "^int vfs_getattr" $(srctree)/fs/stat.c | grep -q "query_flags" ; echo $$?),0)
ccflags-y += -DKSU_HAS_NEW_VFS_GETATTR
endif
# Function proc_ops check
ifeq ($(shell grep -q "struct proc_ops " $(srctree)/include/linux/proc_fs.h; echo $$?),0)
ccflags-y += -DKSU_COMPAT_HAS_PROC_OPS
endif
ccflags-y += -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat -Wno-missing-prototypes
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function -Wno-unused-variable
all:
make -C $(KDIR) M=$(MDIR) modules
compdb:
python3 $(MDIR)/.vscode/generate_compdb.py -O $(KDIR) $(MDIR)
clean:
make -C $(KDIR) M=$(MDIR) clean
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
# Keep a new line here!! Because someone may append config

View File

@@ -1,5 +1,3 @@
#include <linux/mutex.h>
#include <linux/task_work.h>
#include <linux/capability.h>
#include <linux/compiler.h>
#include <linux/fs.h>
@@ -10,16 +8,14 @@
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
#include <linux/compiler_types.h>
#endif
#include "ksu.h"
#include "klog.h" // IWYU pragma: keep
#include "ksud.h"
#include "selinux/selinux.h"
#include "kernel_compat.h"
#include "allowlist.h"
#include "manager.h"
#include "syscall_hook_manager.h"
#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32
#define FILE_FORMAT_VERSION 3 // u32
@@ -33,61 +29,58 @@ static DEFINE_MUTEX(allowlist_mutex);
static struct root_profile default_root_profile;
static struct non_root_profile default_non_root_profile;
void persistent_allow_list(void);
static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly
__aligned(PAGE_SIZE);
static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly __aligned(PAGE_SIZE);
static int allow_list_pointer __read_mostly = 0;
static void remove_uid_from_arr(uid_t uid)
{
int *temp_arr;
int i, j;
int *temp_arr;
int i, j;
if (allow_list_pointer == 0)
return;
if (allow_list_pointer == 0)
return;
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
if (temp_arr == NULL) {
pr_err("%s: unable to allocate memory\n", __func__);
return;
}
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
if (temp_arr == NULL) {
pr_err("%s: unable to allocate memory\n", __func__);
return;
}
for (i = j = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid)
continue;
temp_arr[j++] = allow_list_arr[i];
}
for (i = j = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid)
continue;
temp_arr[j++] = allow_list_arr[i];
}
allow_list_pointer = j;
allow_list_pointer = j;
for (; j < ARRAY_SIZE(allow_list_arr); j++)
temp_arr[j] = -1;
for (; j < ARRAY_SIZE(allow_list_arr); j++)
temp_arr[j] = -1;
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
kfree(temp_arr);
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
kfree(temp_arr);
}
static void init_default_profiles(void)
static void init_default_profiles()
{
kernel_cap_t full_cap = CAP_FULL_SET;
kernel_cap_t full_cap = CAP_FULL_SET;
default_root_profile.uid = 0;
default_root_profile.gid = 0;
default_root_profile.groups_count = 1;
default_root_profile.groups[0] = 0;
memcpy(&default_root_profile.capabilities.effective, &full_cap,
sizeof(default_root_profile.capabilities.effective));
default_root_profile.namespaces = 0;
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
default_root_profile.uid = 0;
default_root_profile.gid = 0;
default_root_profile.groups_count = 1;
default_root_profile.groups[0] = 0;
memcpy(&default_root_profile.capabilities.effective, &full_cap,
sizeof(default_root_profile.capabilities.effective));
default_root_profile.namespaces = 0;
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
// This means that we will umount modules by default!
default_non_root_profile.umount_modules = true;
// This means that we will umount modules by default!
default_non_root_profile.umount_modules = true;
}
struct perm_data {
struct list_head list;
struct app_profile profile;
struct list_head list;
struct app_profile profile;
};
static struct list_head allow_list;
@@ -97,535 +90,437 @@ static uint8_t allow_list_bitmap[PAGE_SIZE] __read_mostly __aligned(PAGE_SIZE);
#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist"
static struct work_struct ksu_save_work;
static struct work_struct ksu_load_work;
bool persistent_allow_list(void);
void ksu_show_allow_list(void)
{
struct perm_data *p = NULL;
struct list_head *pos = NULL;
pr_info("ksu_show_allow_list\n");
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
pr_info("uid :%d, allow: %d\n", p->profile.current_uid,
p->profile.allow_su);
}
struct perm_data *p = NULL;
struct list_head *pos = NULL;
pr_info("ksu_show_allow_list\n");
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
pr_info("uid :%d, allow: %d\n", p->profile.current_uid,
p->profile.allow_su);
}
}
#ifdef CONFIG_KSU_DEBUG
static void ksu_grant_root_to_shell(void)
static void ksu_grant_root_to_shell()
{
struct app_profile profile = {
.version = KSU_APP_PROFILE_VER,
.allow_su = true,
.current_uid = 2000,
};
strcpy(profile.key, "com.android.shell");
strcpy(profile.rp_config.profile.selinux_domain,
KSU_DEFAULT_SELINUX_DOMAIN);
ksu_set_app_profile(&profile, false);
struct app_profile profile = {
.version = KSU_APP_PROFILE_VER,
.allow_su = true,
.current_uid = 2000,
};
strcpy(profile.key, "com.android.shell");
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
ksu_set_app_profile(&profile, false);
}
#endif
bool ksu_get_app_profile(struct app_profile *profile)
{
struct perm_data *p = NULL;
struct list_head *pos = NULL;
bool found = false;
struct perm_data *p = NULL;
struct list_head *pos = NULL;
bool found = false;
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
bool uid_match = profile->current_uid == p->profile.current_uid;
if (uid_match) {
// found it, override it with ours
memcpy(profile, &p->profile, sizeof(*profile));
found = true;
goto exit;
}
}
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
bool uid_match = profile->current_uid == p->profile.current_uid;
if (uid_match) {
// found it, override it with ours
memcpy(profile, &p->profile, sizeof(*profile));
found = true;
goto exit;
}
}
exit:
return found;
return found;
}
static inline bool forbid_system_uid(uid_t uid)
{
#define SHELL_UID 2000
#define SYSTEM_UID 1000
return uid < SHELL_UID && uid != SYSTEM_UID;
static inline bool forbid_system_uid(uid_t uid) {
#define SHELL_UID 2000
#define SYSTEM_UID 1000
return uid < SHELL_UID && uid != SYSTEM_UID;
}
static bool profile_valid(struct app_profile *profile)
{
if (!profile) {
return false;
}
if (!profile) {
return false;
}
if (profile->version < KSU_APP_PROFILE_VER) {
pr_info("Unsupported profile version: %d\n", profile->version);
return false;
}
if (profile->version < KSU_APP_PROFILE_VER) {
pr_info("Unsupported profile version: %d\n", profile->version);
return false;
}
if (profile->allow_su) {
if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) {
return false;
}
if (profile->allow_su) {
if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) {
return false;
}
if (strlen(profile->rp_config.profile.selinux_domain) == 0) {
return false;
}
}
if (strlen(profile->rp_config.profile.selinux_domain) == 0) {
return false;
}
}
return true;
return true;
}
bool ksu_set_app_profile(struct app_profile *profile, bool persist)
{
struct perm_data *p = NULL;
struct list_head *pos = NULL;
bool result = false;
struct perm_data *p = NULL;
struct list_head *pos = NULL;
bool result = false;
if (!profile_valid(profile)) {
pr_err("Failed to set app profile: invalid profile!\n");
return false;
}
if (!profile_valid(profile)) {
pr_err("Failed to set app profile: invalid profile!\n");
return false;
}
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
// both uid and package must match, otherwise it will break multiple package with different user id
if (profile->current_uid == p->profile.current_uid &&
!strcmp(profile->key, p->profile.key)) {
// found it, just override it all!
memcpy(&p->profile, profile, sizeof(*profile));
result = true;
goto out;
}
}
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
// both uid and package must match, otherwise it will break multiple package with different user id
if (profile->current_uid == p->profile.current_uid &&
!strcmp(profile->key, p->profile.key)) {
// found it, just override it all!
memcpy(&p->profile, profile, sizeof(*profile));
result = true;
goto out;
}
}
// not found, alloc a new node!
p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
if (!p) {
pr_err("ksu_set_app_profile alloc failed\n");
return false;
}
// not found, alloc a new node!
p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
if (!p) {
pr_err("ksu_set_app_profile alloc failed\n");
return false;
}
memcpy(&p->profile, profile, sizeof(*profile));
if (profile->allow_su) {
pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n",
profile->key, profile->current_uid,
profile->rp_config.profile.gid,
profile->rp_config.profile.selinux_domain);
} else {
pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n",
profile->key, profile->current_uid,
profile->nrp_config.profile.umount_modules);
}
list_add_tail(&p->list, &allow_list);
memcpy(&p->profile, profile, sizeof(*profile));
if (profile->allow_su) {
pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n",
profile->key, profile->current_uid,
profile->rp_config.profile.gid,
profile->rp_config.profile.selinux_domain);
} else {
pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n",
profile->key, profile->current_uid,
profile->nrp_config.profile.umount_modules);
}
list_add_tail(&p->list, &allow_list);
out:
if (profile->current_uid <= BITMAP_UID_MAX) {
if (profile->allow_su)
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |=
1 << (profile->current_uid % BITS_PER_BYTE);
else
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &=
~(1 << (profile->current_uid % BITS_PER_BYTE));
} else {
if (profile->allow_su) {
/*
* 1024 apps with uid higher than BITMAP_UID_MAX
* registered to request superuser?
*/
if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) {
pr_err("too many apps registered\n");
WARN_ON(1);
return false;
}
allow_list_arr[allow_list_pointer++] = profile->current_uid;
} else {
remove_uid_from_arr(profile->current_uid);
}
}
result = true;
if (profile->current_uid <= BITMAP_UID_MAX) {
if (profile->allow_su)
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE);
else
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE));
} else {
if (profile->allow_su) {
/*
* 1024 apps with uid higher than BITMAP_UID_MAX
* registered to request superuser?
*/
if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) {
pr_err("too many apps registered\n");
WARN_ON(1);
return false;
}
allow_list_arr[allow_list_pointer++] = profile->current_uid;
} else {
remove_uid_from_arr(profile->current_uid);
}
}
result = true;
// check if the default profiles is changed, cache it to a single struct to accelerate access.
if (unlikely(!strcmp(profile->key, "$"))) {
// set default non root profile
memcpy(&default_non_root_profile, &profile->nrp_config.profile,
sizeof(default_non_root_profile));
}
// check if the default profiles is changed, cache it to a single struct to accelerate access.
if (unlikely(!strcmp(profile->key, "$"))) {
// set default non root profile
memcpy(&default_non_root_profile, &profile->nrp_config.profile,
sizeof(default_non_root_profile));
}
if (unlikely(!strcmp(profile->key, "#"))) {
// set default root profile
memcpy(&default_root_profile, &profile->rp_config.profile,
sizeof(default_root_profile));
}
if (unlikely(!strcmp(profile->key, "#"))) {
// set default root profile
memcpy(&default_root_profile, &profile->rp_config.profile,
sizeof(default_root_profile));
}
if (persist) {
persistent_allow_list();
// FIXME: use a new flag
ksu_mark_running_process();
}
if (persist)
persistent_allow_list();
return result;
return result;
}
bool __ksu_is_allow_uid(uid_t uid)
{
int i;
int i;
if (forbid_system_uid(uid)) {
// do not bother going through the list if it's system
return false;
}
if (unlikely(uid == 0)) {
// already root, but only allow our domain.
return is_ksu_domain();
}
if (likely(ksu_is_manager_uid_valid()) &&
unlikely(ksu_get_manager_uid() == uid)) {
// manager is always allowed!
return true;
}
if (forbid_system_uid(uid)) {
// do not bother going through the list if it's system
return false;
}
if (likely(uid <= BITMAP_UID_MAX)) {
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] &
(1 << (uid % BITS_PER_BYTE)));
} else {
for (i = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid)
return true;
}
}
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
// manager is always allowed!
return true;
}
return false;
}
if (likely(uid <= BITMAP_UID_MAX)) {
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE)));
} else {
for (i = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid)
return true;
}
}
bool __ksu_is_allow_uid_for_current(uid_t uid)
{
if (unlikely(uid == 0)) {
// already root, but only allow our domain.
return is_ksu_domain();
}
return __ksu_is_allow_uid(uid);
return false;
}
bool ksu_uid_should_umount(uid_t uid)
{
struct app_profile profile = { .current_uid = uid };
if (likely(ksu_is_manager_uid_valid()) &&
unlikely(ksu_get_manager_uid() == uid)) {
// we should not umount on manager!
return false;
}
bool found = ksu_get_app_profile(&profile);
if (!found) {
// no app profile found, it must be non root app
return default_non_root_profile.umount_modules;
}
if (profile.allow_su) {
// if found and it is granted to su, we shouldn't umount for it
return false;
} else {
// found an app profile
if (profile.nrp_config.use_default) {
return default_non_root_profile.umount_modules;
} else {
return profile.nrp_config.profile.umount_modules;
}
}
struct app_profile profile = { .current_uid = uid };
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
// we should not umount on manager!
return false;
}
bool found = ksu_get_app_profile(&profile);
if (!found) {
// no app profile found, it must be non root app
return default_non_root_profile.umount_modules;
}
if (profile.allow_su) {
// if found and it is granted to su, we shouldn't umount for it
return false;
} else {
// found an app profile
if (profile.nrp_config.use_default) {
return default_non_root_profile.umount_modules;
} else {
return profile.nrp_config.profile.umount_modules;
}
}
}
struct root_profile *ksu_get_root_profile(uid_t uid)
{
struct perm_data *p = NULL;
struct list_head *pos = NULL;
struct perm_data *p = NULL;
struct list_head *pos = NULL;
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
if (uid == p->profile.current_uid && p->profile.allow_su) {
if (!p->profile.rp_config.use_default) {
return &p->profile.rp_config.profile;
}
}
}
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
if (uid == p->profile.current_uid && p->profile.allow_su) {
if (!p->profile.rp_config.use_default) {
return &p->profile.rp_config.profile;
}
}
}
// use default profile
return &default_root_profile;
// use default profile
return &default_root_profile;
}
bool ksu_get_allow_list(int *array, int *length, bool allow)
{
struct perm_data *p = NULL;
struct list_head *pos = NULL;
int i = 0;
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
// pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow);
if (p->profile.allow_su == allow) {
array[i++] = p->profile.current_uid;
}
}
*length = i;
struct perm_data *p = NULL;
struct list_head *pos = NULL;
int i = 0;
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
// pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow);
if (p->profile.allow_su == allow) {
array[i++] = p->profile.current_uid;
}
}
*length = i;
return true;
return true;
}
static void do_persistent_allow_list(struct callback_head *_cb)
void do_save_allow_list(struct work_struct *work)
{
u32 magic = FILE_MAGIC;
u32 version = FILE_FORMAT_VERSION;
struct perm_data *p = NULL;
struct list_head *pos = NULL;
loff_t off = 0;
u32 magic = FILE_MAGIC;
u32 version = FILE_FORMAT_VERSION;
struct perm_data *p = NULL;
struct list_head *pos = NULL;
loff_t off = 0;
mutex_lock(&allowlist_mutex);
struct file *fp =
filp_open(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) {
pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
goto unlock;
}
struct file *fp =
ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) {
pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
return;
}
// store magic and version
if (kernel_write(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
pr_err("save_allow_list write magic failed.\n");
goto close_file;
}
// store magic and version
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) !=
sizeof(magic)) {
pr_err("save_allow_list write magic failed.\n");
goto exit;
}
if (kernel_write(fp, &version, sizeof(version), &off) != sizeof(version)) {
pr_err("save_allow_list write version failed.\n");
goto close_file;
}
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) !=
sizeof(version)) {
pr_err("save_allow_list write version failed.\n");
goto exit;
}
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
pr_info("save allow list, name: %s uid :%d, allow: %d\n",
p->profile.key, p->profile.current_uid, p->profile.allow_su);
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
pr_info("save allow list, name: %s uid :%d, allow: %d\n",
p->profile.key, p->profile.current_uid,
p->profile.allow_su);
kernel_write(fp, &p->profile, sizeof(p->profile), &off);
}
close_file:
filp_close(fp, 0);
unlock:
mutex_unlock(&allowlist_mutex);
kfree(_cb);
}
void persistent_allow_list()
{
struct task_struct *tsk;
tsk = get_pid_task(find_vpid(1), PIDTYPE_PID);
if (!tsk) {
pr_err("save_allow_list find init task err\n");
return;
}
struct callback_head *cb =
kzalloc(sizeof(struct callback_head), GFP_KERNEL);
if (!cb) {
pr_err("save_allow_list alloc cb err\b");
goto put_task;
}
cb->func = do_persistent_allow_list;
task_work_add(tsk, cb, TWA_RESUME);
put_task:
put_task_struct(tsk);
}
void ksu_load_allow_list()
{
loff_t off = 0;
ssize_t ret = 0;
struct file *fp = NULL;
u32 magic;
u32 version;
#ifdef CONFIG_KSU_DEBUG
// always allow adb shell by default
ksu_grant_root_to_shell();
#endif
// load allowlist now!
fp = filp_open(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
return;
}
// verify magic
if (kernel_read(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
magic != FILE_MAGIC) {
pr_err("allowlist file invalid: %d!\n", magic);
goto exit;
}
if (kernel_read(fp, &version, sizeof(version), &off) != sizeof(version)) {
pr_err("allowlist read version: %d failed\n", version);
goto exit;
}
pr_info("allowlist version: %d\n", version);
while (true) {
struct app_profile profile;
ret = kernel_read(fp, &profile, sizeof(profile), &off);
if (ret <= 0) {
pr_info("load_allow_list read err: %zd\n", ret);
break;
}
pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n", profile.key,
profile.current_uid, profile.allow_su);
ksu_set_app_profile(&profile, false);
}
ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile),
&off);
}
exit:
ksu_show_allow_list();
filp_close(fp, 0);
filp_close(fp, 0);
}
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *),
void *data)
void do_load_allow_list(struct work_struct *work)
{
struct perm_data *np = NULL;
struct perm_data *n = NULL;
loff_t off = 0;
ssize_t ret = 0;
struct file *fp = NULL;
u32 magic;
u32 version;
if (!ksu_boot_completed) {
pr_info("boot not completed, skip prune\n");
return;
}
#ifdef CONFIG_KSU_DEBUG
// always allow adb shell by default
ksu_grant_root_to_shell();
#endif
bool modified = false;
// TODO: use RCU!
mutex_lock(&allowlist_mutex);
list_for_each_entry_safe (np, n, &allow_list, list) {
uid_t uid = np->profile.current_uid;
char *package = np->profile.key;
// we use this uid for special cases, don't prune it!
bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID;
if (!is_preserved_uid && !is_uid_valid(uid, package, data)) {
modified = true;
pr_info("prune uid: %d, package: %s\n", uid, package);
list_del(&np->list);
if (likely(uid <= BITMAP_UID_MAX)) {
allow_list_bitmap[uid / BITS_PER_BYTE] &=
~(1 << (uid % BITS_PER_BYTE));
}
remove_uid_from_arr(uid);
smp_mb();
kfree(np);
}
}
mutex_unlock(&allowlist_mutex);
// load allowlist now!
fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
return;
}
if (modified) {
persistent_allow_list();
}
// verify magic
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) !=
sizeof(magic) ||
magic != FILE_MAGIC) {
pr_err("allowlist file invalid: %d!\n", magic);
goto exit;
}
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) !=
sizeof(version)) {
pr_err("allowlist read version: %d failed\n", version);
goto exit;
}
pr_info("allowlist version: %d\n", version);
while (true) {
struct app_profile profile;
ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile),
&off);
if (ret <= 0) {
pr_info("load_allow_list read err: %zd\n", ret);
break;
}
pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n",
profile.key, profile.current_uid, profile.allow_su);
ksu_set_app_profile(&profile, false);
}
exit:
ksu_show_allow_list();
filp_close(fp, 0);
}
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data)
{
struct perm_data *np = NULL;
struct perm_data *n = NULL;
bool modified = false;
// TODO: use RCU!
mutex_lock(&allowlist_mutex);
list_for_each_entry_safe (np, n, &allow_list, list) {
uid_t uid = np->profile.current_uid;
char *package = np->profile.key;
// we use this uid for special cases, don't prune it!
bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID;
if (!is_preserved_uid && !is_uid_valid(uid, package, data)) {
modified = true;
pr_info("prune uid: %d, package: %s\n", uid, package);
list_del(&np->list);
if (likely(uid <= BITMAP_UID_MAX)) {
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
}
remove_uid_from_arr(uid);
smp_mb();
kfree(np);
}
}
mutex_unlock(&allowlist_mutex);
if (modified) {
persistent_allow_list();
}
}
// make sure allow list works cross boot
bool persistent_allow_list(void)
{
return ksu_queue_work(&ksu_save_work);
}
bool ksu_load_allow_list(void)
{
return ksu_queue_work(&ksu_load_work);
}
void ksu_allowlist_init(void)
{
int i;
int i;
BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
allow_list_arr[i] = -1;
for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
allow_list_arr[i] = -1;
INIT_LIST_HEAD(&allow_list);
INIT_LIST_HEAD(&allow_list);
init_default_profiles();
INIT_WORK(&ksu_save_work, do_save_allow_list);
INIT_WORK(&ksu_load_work, do_load_allow_list);
init_default_profiles();
}
void ksu_allowlist_exit(void)
{
struct perm_data *np = NULL;
struct perm_data *n = NULL;
struct perm_data *np = NULL;
struct perm_data *n = NULL;
// free allowlist
mutex_lock(&allowlist_mutex);
list_for_each_entry_safe (np, n, &allow_list, list) {
list_del(&np->list);
kfree(np);
}
mutex_unlock(&allowlist_mutex);
do_save_allow_list(NULL);
// free allowlist
mutex_lock(&allowlist_mutex);
list_for_each_entry_safe (np, n, &allow_list, list) {
list_del(&np->list);
kfree(np);
}
mutex_unlock(&allowlist_mutex);
}
#ifdef CONFIG_KSU_MANUAL_SU
bool ksu_temp_grant_root_once(uid_t uid)
{
struct app_profile profile = {
.version = KSU_APP_PROFILE_VER,
.allow_su = true,
.current_uid = uid,
};
const char *default_key = "com.temp.once";
struct perm_data *p = NULL;
struct list_head *pos = NULL;
bool found = false;
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
if (p->profile.current_uid == uid) {
strcpy(profile.key, p->profile.key);
found = true;
break;
}
}
if (!found) {
strcpy(profile.key, default_key);
}
profile.rp_config.profile.uid = default_root_profile.uid;
profile.rp_config.profile.gid = default_root_profile.gid;
profile.rp_config.profile.groups_count = default_root_profile.groups_count;
memcpy(profile.rp_config.profile.groups, default_root_profile.groups, sizeof(default_root_profile.groups));
memcpy(&profile.rp_config.profile.capabilities, &default_root_profile.capabilities, sizeof(default_root_profile.capabilities));
profile.rp_config.profile.namespaces = default_root_profile.namespaces;
strcpy(profile.rp_config.profile.selinux_domain, default_root_profile.selinux_domain);
bool ok = ksu_set_app_profile(&profile, false);
if (ok)
pr_info("pending_root: UID=%d granted and persisted\n", uid);
return ok;
}
void ksu_temp_revoke_root_once(uid_t uid)
{
struct app_profile profile = {
.version = KSU_APP_PROFILE_VER,
.allow_su = false,
.current_uid = uid,
};
const char *default_key = "com.temp.once";
struct perm_data *p = NULL;
struct list_head *pos = NULL;
bool found = false;
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
if (p->profile.current_uid == uid) {
strcpy(profile.key, p->profile.key);
found = true;
break;
}
}
if (!found) {
strcpy(profile.key, default_key);
}
profile.nrp_config.profile.umount_modules = default_non_root_profile.umount_modules;
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
ksu_set_app_profile(&profile, false);
persistent_allow_list();
pr_info("pending_root: UID=%d removed and persist updated\n", uid);
}
#endif

View File

@@ -2,29 +2,19 @@
#define __KSU_H_ALLOWLIST
#include <linux/types.h>
#include <linux/uidgid.h>
#include "app_profile.h"
#define PER_USER_RANGE 100000
#define FIRST_APPLICATION_UID 10000
#define LAST_APPLICATION_UID 19999
#include "ksu.h"
void ksu_allowlist_init(void);
void ksu_allowlist_exit(void);
void ksu_load_allow_list(void);
bool ksu_load_allow_list(void);
void ksu_show_allow_list(void);
// Check if the uid is in allow list
bool __ksu_is_allow_uid(uid_t uid);
#define ksu_is_allow_uid(uid) unlikely(__ksu_is_allow_uid(uid))
// Check if the uid is in allow list, or current is ksu domain root
bool __ksu_is_allow_uid_for_current(uid_t uid);
#define ksu_is_allow_uid_for_current(uid) unlikely(__ksu_is_allow_uid_for_current(uid))
bool ksu_get_allow_list(int *array, int *length, bool allow);
void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, char *, void *), void *data);
@@ -34,16 +24,4 @@ bool ksu_set_app_profile(struct app_profile *, bool persist);
bool ksu_uid_should_umount(uid_t uid);
struct root_profile *ksu_get_root_profile(uid_t uid);
static inline bool is_appuid(uid_t uid)
{
uid_t appid = uid % PER_USER_RANGE;
return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID;
}
#ifdef CONFIG_KSU_MANUAL_SU
bool ksu_temp_grant_root_once(uid_t uid);
void ksu_temp_revoke_root_once(uid_t uid);
#endif
#endif

View File

@@ -15,375 +15,329 @@
#endif
#include "apk_sign.h"
#include "dynamic_manager.h"
#include "dynamic_sign.h"
#include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h"
#include "manager_sign.h"
struct sdesc {
struct shash_desc shash;
char ctx[];
struct shash_desc shash;
char ctx[];
};
static apk_sign_key_t apk_sign_keys[] = {
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
static struct apk_sign_key {
unsigned size;
const char *sha256;
} apk_sign_keys[] = {
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
{EXPECTED_SIZE_OTHER, EXPECTED_HASH_OTHER}, // Dynamic Sign
#ifdef EXPECTED_SIZE
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom
#endif
};
static struct sdesc *init_sdesc(struct crypto_shash *alg)
{
struct sdesc *sdesc;
int size;
struct sdesc *sdesc;
int size;
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
sdesc = kmalloc(size, GFP_KERNEL);
if (!sdesc)
return ERR_PTR(-ENOMEM);
sdesc->shash.tfm = alg;
return sdesc;
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
sdesc = kmalloc(size, GFP_KERNEL);
if (!sdesc)
return ERR_PTR(-ENOMEM);
sdesc->shash.tfm = alg;
return sdesc;
}
static int calc_hash(struct crypto_shash *alg, const unsigned char *data,
unsigned int datalen, unsigned char *digest)
unsigned int datalen, unsigned char *digest)
{
struct sdesc *sdesc;
int ret;
struct sdesc *sdesc;
int ret;
sdesc = init_sdesc(alg);
if (IS_ERR(sdesc)) {
pr_info("can't alloc sdesc\n");
return PTR_ERR(sdesc);
}
sdesc = init_sdesc(alg);
if (IS_ERR(sdesc)) {
pr_info("can't alloc sdesc\n");
return PTR_ERR(sdesc);
}
ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
kfree(sdesc);
return ret;
ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
kfree(sdesc);
return ret;
}
static int ksu_sha256(const unsigned char *data, unsigned int datalen,
unsigned char *digest)
unsigned char *digest)
{
struct crypto_shash *alg;
char *hash_alg_name = "sha256";
int ret;
struct crypto_shash *alg;
char *hash_alg_name = "sha256";
int ret;
alg = crypto_alloc_shash(hash_alg_name, 0, 0);
if (IS_ERR(alg)) {
pr_info("can't alloc alg %s\n", hash_alg_name);
return PTR_ERR(alg);
}
ret = calc_hash(alg, data, datalen, digest);
crypto_free_shash(alg);
return ret;
alg = crypto_alloc_shash(hash_alg_name, 0, 0);
if (IS_ERR(alg)) {
pr_info("can't alloc alg %s\n", hash_alg_name);
return PTR_ERR(alg);
}
ret = calc_hash(alg, data, datalen, digest);
crypto_free_shash(alg);
return ret;
}
static struct dynamic_sign_key dynamic_sign = DYNAMIC_SIGN_DEFAULT_CONFIG;
static bool check_dynamic_sign(struct file *fp, u32 size4, loff_t *pos, int *matched_index)
{
struct dynamic_sign_key current_dynamic_key = dynamic_sign;
if (ksu_get_dynamic_manager_config(&current_dynamic_key.size, &current_dynamic_key.hash)) {
pr_debug("Using dynamic manager config: size=0x%x, hash=%.16s...\n",
current_dynamic_key.size, current_dynamic_key.hash);
}
if (size4 != current_dynamic_key.size) {
return false;
}
#define CERT_MAX_LENGTH 1024
char cert[CERT_MAX_LENGTH];
if (size4 > CERT_MAX_LENGTH) {
pr_info("cert length overlimit\n");
return false;
}
kernel_read(fp, cert, size4, pos);
unsigned char digest[SHA256_DIGEST_SIZE];
if (ksu_sha256(cert, size4, digest) < 0) {
pr_info("sha256 error\n");
return false;
}
char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
pr_info("sha256: %s, expected: %s, index: dynamic\n", hash_str, current_dynamic_key.hash);
if (strcmp(current_dynamic_key.hash, hash_str) == 0) {
if (matched_index) {
*matched_index = DYNAMIC_SIGN_INDEX;
}
return true;
}
return false;
}
static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, int *matched_index)
{
int i;
apk_sign_key_t sign_key;
bool signature_valid = false;
int i;
struct apk_sign_key sign_key;
bool signature_valid = false;
kernel_read(fp, size4, 0x4, pos); // signer-sequence length
kernel_read(fp, size4, 0x4, pos); // signer length
kernel_read(fp, size4, 0x4, pos); // signed data length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length
*offset += 0x4 * 3;
*offset += 0x4 * 3;
kernel_read(fp, size4, 0x4, pos); // digests-sequence length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length
*pos += *size4;
*offset += 0x4 + *size4;
*pos += *size4;
*offset += 0x4 + *size4;
kernel_read(fp, size4, 0x4, pos); // certificates length
kernel_read(fp, size4, 0x4, pos); // certificate length
*offset += 0x4 * 2;
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length
*offset += 0x4 * 2;
if (ksu_is_dynamic_manager_enabled()) {
loff_t temp_pos = *pos;
if (check_dynamic_sign(fp, *size4, &temp_pos, matched_index)) {
*pos = temp_pos;
*offset += *size4;
return true;
}
}
for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) {
sign_key = apk_sign_keys[i];
for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) {
sign_key = apk_sign_keys[i];
if (i == 1) { // Dynamic Sign indexing
unsigned int size;
const char *hash;
if (ksu_get_dynamic_sign_config(&size, &hash)) {
sign_key.size = size;
sign_key.sha256 = hash;
}
}
if (*size4 != sign_key.size)
continue;
*offset += *size4;
if (*size4 != sign_key.size)
continue;
*offset += *size4;
#define CERT_MAX_LENGTH 1024
char cert[CERT_MAX_LENGTH];
if (*size4 > CERT_MAX_LENGTH) {
pr_info("cert length overlimit\n");
return false;
}
kernel_read(fp, cert, *size4, pos);
unsigned char digest[SHA256_DIGEST_SIZE];
if (ksu_sha256(cert, *size4, digest) < 0 ) {
pr_info("sha256 error\n");
return false;
}
char cert[CERT_MAX_LENGTH];
if (*size4 > CERT_MAX_LENGTH) {
pr_info("cert length overlimit\n");
return false;
}
ksu_kernel_read_compat(fp, cert, *size4, pos);
unsigned char digest[SHA256_DIGEST_SIZE];
if (IS_ERR(ksu_sha256(cert, *size4, digest))) {
pr_info("sha256 error\n");
return false;
}
char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
pr_info("sha256: %s, expected: %s, index: %d\n", hash_str, sign_key.sha256, i);
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
pr_info("sha256: %s, expected: %s, index: %d\n", hash_str, sign_key.sha256, i);
if (strcmp(sign_key.sha256, hash_str) == 0) {
signature_valid = true;
if (matched_index) {
*matched_index = i;
}
break;
}
}
return signature_valid;
if (strcmp(sign_key.sha256, hash_str) == 0) {
signature_valid = true;
if (matched_index) {
*matched_index = i;
}
break;
}
}
return signature_valid;
}
struct zip_entry_header {
uint32_t signature;
uint16_t version;
uint16_t flags;
uint16_t compression;
uint16_t mod_time;
uint16_t mod_date;
uint32_t crc32;
uint32_t compressed_size;
uint32_t uncompressed_size;
uint16_t file_name_length;
uint16_t extra_field_length;
uint32_t signature;
uint16_t version;
uint16_t flags;
uint16_t compression;
uint16_t mod_time;
uint16_t mod_date;
uint32_t crc32;
uint32_t compressed_size;
uint32_t uncompressed_size;
uint16_t file_name_length;
uint16_t extra_field_length;
} __attribute__((packed));
// This is a necessary but not sufficient condition, but it is enough for us
static bool has_v1_signature_file(struct file *fp)
{
struct zip_entry_header header;
const char MANIFEST[] = "META-INF/MANIFEST.MF";
struct zip_entry_header header;
const char MANIFEST[] = "META-INF/MANIFEST.MF";
loff_t pos = 0;
loff_t pos = 0;
while (kernel_read(fp, &header,
sizeof(struct zip_entry_header), &pos) ==
sizeof(struct zip_entry_header)) {
if (header.signature != 0x04034b50) {
// ZIP magic: 'PK'
return false;
}
// Read the entry file name
if (header.file_name_length == sizeof(MANIFEST) - 1) {
char fileName[sizeof(MANIFEST)];
kernel_read(fp, fileName,
header.file_name_length, &pos);
fileName[header.file_name_length] = '\0';
while (ksu_kernel_read_compat(fp, &header,
sizeof(struct zip_entry_header), &pos) ==
sizeof(struct zip_entry_header)) {
if (header.signature != 0x04034b50) {
// ZIP magic: 'PK'
return false;
}
// Read the entry file name
if (header.file_name_length == sizeof(MANIFEST) - 1) {
char fileName[sizeof(MANIFEST)];
ksu_kernel_read_compat(fp, fileName,
header.file_name_length, &pos);
fileName[header.file_name_length] = '\0';
// Check if the entry matches META-INF/MANIFEST.MF
if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) ==
0) {
return true;
}
} else {
// Skip the entry file name
pos += header.file_name_length;
}
// Check if the entry matches META-INF/MANIFEST.MF
if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) == 0) {
return true;
}
} else {
// Skip the entry file name
pos += header.file_name_length;
}
// Skip to the next entry
pos += header.extra_field_length + header.compressed_size;
}
// Skip to the next entry
pos += header.extra_field_length + header.compressed_size;
}
return false;
return false;
}
static __always_inline bool check_v2_signature(char *path, bool check_multi_manager, int *signature_index)
{
unsigned char buffer[0x11] = { 0 };
u32 size4;
u64 size8, size_of_block;
unsigned char buffer[0x11] = { 0 };
u32 size4;
u64 size8, size_of_block;
loff_t pos;
bool v2_signing_valid = false;
int v2_signing_blocks = 0;
bool v3_signing_exist = false;
bool v3_1_signing_exist = false;
int matched_index = -1;
int i;
struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("open %s error.\n", path);
return false;
}
loff_t pos;
// If you want to check for multi-manager APK signing, but dynamic signing is not enabled, skip
if (check_multi_manager && !ksu_is_dynamic_sign_enabled()) {
filp_close(fp, 0);
return 0;
}
bool v2_signing_valid = false;
int v2_signing_blocks = 0;
bool v3_signing_exist = false;
bool v3_1_signing_exist = false;
int matched_index = -1;
int i;
struct file *fp = filp_open(path, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("open %s error.\n", path);
return false;
}
// disable inotify for this file
fp->f_mode |= FMODE_NONOTIFY;
// If you want to check for multi-manager APK signing, but dynamic managering is not enabled, skip
if (check_multi_manager && !ksu_is_dynamic_manager_enabled()) {
filp_close(fp, 0);
return 0;
}
// https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
for (i = 0;; ++i) {
unsigned short n;
pos = generic_file_llseek(fp, -i - 2, SEEK_END);
ksu_kernel_read_compat(fp, &n, 2, &pos);
if (n == i) {
pos -= 22;
ksu_kernel_read_compat(fp, &size4, 4, &pos);
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
break;
}
}
if (i == 0xffff) {
pr_info("error: cannot find eocd\n");
goto clean;
}
}
// disable inotify for this file
fp->f_mode |= FMODE_NONOTIFY;
pos += 12;
// offset
ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
pos = size4 - 0x18;
// https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
for (i = 0;; ++i) {
unsigned short n;
pos = generic_file_llseek(fp, -i - 2, SEEK_END);
kernel_read(fp, &n, 2, &pos);
if (n == i) {
pos -= 22;
kernel_read(fp, &size4, 4, &pos);
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
break;
}
}
if (i == 0xffff) {
pr_info("error: cannot find eocd\n");
goto clean;
}
}
ksu_kernel_read_compat(fp, &size8, 0x8, &pos);
ksu_kernel_read_compat(fp, buffer, 0x10, &pos);
if (strcmp((char *)buffer, "APK Sig Block 42")) {
goto clean;
}
pos += 12;
// offset
kernel_read(fp, &size4, 0x4, &pos);
pos = size4 - 0x18;
pos = size4 - (size8 + 0x8);
ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos);
if (size_of_block != size8) {
goto clean;
}
kernel_read(fp, &size8, 0x8, &pos);
kernel_read(fp, buffer, 0x10, &pos);
if (strcmp((char *)buffer, "APK Sig Block 42")) {
goto clean;
}
pos = size4 - (size8 + 0x8);
kernel_read(fp, &size_of_block, 0x8, &pos);
if (size_of_block != size8) {
goto clean;
}
int loop_count = 0;
while (loop_count++ < 10) {
uint32_t id;
uint32_t offset;
kernel_read(fp, &size8, 0x8,
&pos); // sequence length
if (size8 == size_of_block) {
break;
}
kernel_read(fp, &id, 0x4, &pos); // id
offset = 4;
if (id == 0x7109871au) {
v2_signing_blocks++;
bool result = check_block(fp, &size4, &pos, &offset, &matched_index);
if (result) {
v2_signing_valid = true;
}
} else if (id == 0xf05368c0u) {
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#73
v3_signing_exist = true;
} else if (id == 0x1b93ad61u) {
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#74
v3_1_signing_exist = true;
} else {
int loop_count = 0;
while (loop_count++ < 10) {
uint32_t id;
uint32_t offset;
ksu_kernel_read_compat(fp, &size8, 0x8,
&pos); // sequence length
if (size8 == size_of_block) {
break;
}
ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id
offset = 4;
if (id == 0x7109871au) {
v2_signing_blocks++;
bool result = check_block(fp, &size4, &pos, &offset, &matched_index);
if (result) {
v2_signing_valid = true;
}
} else if (id == 0xf05368c0u) {
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#73
v3_signing_exist = true;
} else if (id == 0x1b93ad61u) {
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#74
v3_1_signing_exist = true;
} else {
#ifdef CONFIG_KSU_DEBUG
pr_info("Unknown id: 0x%08x\n", id);
pr_info("Unknown id: 0x%08x\n", id);
#endif
}
pos += (size8 - offset);
}
}
pos += (size8 - offset);
}
if (v2_signing_blocks != 1) {
if (v2_signing_blocks != 1) {
#ifdef CONFIG_KSU_DEBUG
pr_err("Unexpected v2 signature count: %d\n",
v2_signing_blocks);
pr_err("Unexpected v2 signature count: %d\n",
v2_signing_blocks);
#endif
v2_signing_valid = false;
}
v2_signing_valid = false;
}
if (v2_signing_valid) {
int has_v1_signing = has_v1_signature_file(fp);
if (has_v1_signing) {
pr_err("Unexpected v1 signature scheme found!\n");
filp_close(fp, 0);
return false;
}
}
if (v2_signing_valid) {
int has_v1_signing = has_v1_signature_file(fp);
if (has_v1_signing) {
pr_err("Unexpected v1 signature scheme found!\n");
filp_close(fp, 0);
return false;
}
}
clean:
filp_close(fp, 0);
filp_close(fp, 0);
if (v3_signing_exist || v3_1_signing_exist) {
if (v3_signing_exist || v3_1_signing_exist) {
#ifdef CONFIG_KSU_DEBUG
pr_err("Unexpected v3 signature scheme found!\n");
pr_err("Unexpected v3 signature scheme found!\n");
#endif
return false;
}
return false;
}
if (v2_signing_valid) {
if (signature_index) {
*signature_index = matched_index;
}
if (v2_signing_valid) {
if (signature_index) {
*signature_index = matched_index;
}
if (check_multi_manager) {
// 0: ShirkNeko/SukiSU, DYNAMIC_SIGN_INDEX : Dynamic Sign
if (matched_index == 0 || matched_index == DYNAMIC_SIGN_INDEX) {
pr_info("Multi-manager APK detected (dynamic_manager enabled): signature_index=%d\n", matched_index);
return true;
}
return false;
} else {
// Common manager check: any valid signature will do
return true;
}
}
return false;
if (check_multi_manager) {
// 0: ShirkNeko/SukiSU, 1: Dynamic Sign
if (matched_index == 0 || matched_index == 1) {
pr_info("Multi-manager APK detected (dynamic_sign enabled): signature_index=%d\n", matched_index);
return true;
}
return false;
} else {
// Common manager check: any valid signature will do
return true;
}
}
return false;
}
#ifdef CONFIG_KSU_DEBUG
@@ -394,19 +348,19 @@ int ksu_debug_manager_uid = -1;
static int set_expected_size(const char *val, const struct kernel_param *kp)
{
int rv = param_set_uint(val, kp);
ksu_set_manager_uid(ksu_debug_manager_uid);
pr_info("ksu_manager_uid set to %d\n", ksu_debug_manager_uid);
return rv;
int rv = param_set_uint(val, kp);
ksu_set_manager_uid(ksu_debug_manager_uid);
pr_info("ksu_manager_uid set to %d\n", ksu_debug_manager_uid);
return rv;
}
static struct kernel_param_ops expected_size_ops = {
.set = set_expected_size,
.get = param_get_uint,
.set = set_expected_size,
.get = param_get_uint,
};
module_param_cb(ksu_debug_manager_uid, &expected_size_ops,
&ksu_debug_manager_uid, S_IRUSR | S_IWUSR);
&ksu_debug_manager_uid, S_IRUSR | S_IWUSR);
#endif
@@ -415,7 +369,7 @@ bool is_manager_apk(char *path)
return check_v2_signature(path, false, NULL);
}
bool is_dynamic_manager_apk(char *path, int *signature_index)
bool ksu_is_multi_manager_apk(char *path, int *signature_index)
{
return check_v2_signature(path, true, signature_index);
}

View File

@@ -6,6 +6,4 @@
bool is_manager_apk(char *path);
bool is_dynamic_manager_apk(char *path, int *signature_index);
#endif

View File

@@ -1,302 +0,0 @@
#include <linux/capability.h>
#include <linux/cred.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/seccomp.h>
#include <linux/thread_info.h>
#include <linux/uidgid.h>
#include <linux/version.h>
#include "objsec.h"
#include "allowlist.h"
#include "app_profile.h"
#include "klog.h" // IWYU pragma: keep
#include "selinux/selinux.h"
#include "syscall_hook_manager.h"
#include "sucompat.h"
#include "sulog.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 7, 0)
static struct group_info root_groups = { .usage = REFCOUNT_INIT(2), };
#else
static struct group_info root_groups = { .usage = ATOMIC_INIT(2) };
#endif
static void setup_groups(struct root_profile *profile, struct cred *cred)
{
if (profile->groups_count > KSU_MAX_GROUPS) {
pr_warn("Failed to setgroups, too large group: %d!\n",
profile->uid);
return;
}
if (profile->groups_count == 1 && profile->groups[0] == 0) {
// setgroup to root and return early.
if (cred->group_info)
put_group_info(cred->group_info);
cred->group_info = get_group_info(&root_groups);
return;
}
u32 ngroups = profile->groups_count;
struct group_info *group_info = groups_alloc(ngroups);
if (!group_info) {
pr_warn("Failed to setgroups, ENOMEM for: %d\n", profile->uid);
return;
}
int i;
for (i = 0; i < ngroups; i++) {
gid_t gid = profile->groups[i];
kgid_t kgid = make_kgid(current_user_ns(), gid);
if (!gid_valid(kgid)) {
pr_warn("Failed to setgroups, invalid gid: %d\n", gid);
put_group_info(group_info);
return;
}
group_info->gid[i] = kgid;
}
groups_sort(group_info);
set_groups(cred, group_info);
put_group_info(group_info);
}
void disable_seccomp(void)
{
assert_spin_locked(&current->sighand->siglock);
// disable seccomp
#if defined(CONFIG_GENERIC_ENTRY) && \
LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
clear_syscall_work(SECCOMP);
#else
clear_thread_flag(TIF_SECCOMP);
#endif
#ifdef CONFIG_SECCOMP
current->seccomp.mode = 0;
current->seccomp.filter = NULL;
atomic_set(&current->seccomp.filter_count, 0);
#else
#endif
}
void escape_with_root_profile(void)
{
struct cred *cred;
struct task_struct *p = current;
struct task_struct *t;
cred = prepare_creds();
if (!cred) {
pr_warn("prepare_creds failed!\n");
return;
}
if (cred->euid.val == 0) {
pr_warn("Already root, don't escape!\n");
#if __SULOG_GATE
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root_failed");
#endif
abort_creds(cred);
return;
}
struct root_profile *profile = ksu_get_root_profile(cred->uid.val);
cred->uid.val = profile->uid;
cred->suid.val = profile->uid;
cred->euid.val = profile->uid;
cred->fsuid.val = profile->uid;
cred->gid.val = profile->gid;
cred->fsgid.val = profile->gid;
cred->sgid.val = profile->gid;
cred->egid.val = profile->gid;
cred->securebits = 0;
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
sizeof(kernel_cap_t));
// setup capabilities
// we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process
// we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec!
u64 cap_for_ksud =
profile->capabilities.effective | CAP_DAC_READ_SEARCH;
memcpy(&cred->cap_effective, &cap_for_ksud,
sizeof(cred->cap_effective));
memcpy(&cred->cap_permitted, &profile->capabilities.effective,
sizeof(cred->cap_permitted));
memcpy(&cred->cap_bset, &profile->capabilities.effective,
sizeof(cred->cap_bset));
setup_groups(profile, cred);
commit_creds(cred);
// Refer to kernel/seccomp.c: seccomp_set_mode_strict
// When disabling Seccomp, ensure that current->sighand->siglock is held during the operation.
spin_lock_irq(&current->sighand->siglock);
disable_seccomp();
spin_unlock_irq(&current->sighand->siglock);
setup_selinux(profile->selinux_domain);
#if __SULOG_GATE
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root");
#endif
for_each_thread (p, t) {
ksu_set_task_tracepoint_flag(t);
}
}
#ifdef CONFIG_KSU_MANUAL_SU
#include "ksud.h"
#ifndef DEVPTS_SUPER_MAGIC
#define DEVPTS_SUPER_MAGIC 0x1cd1
#endif
static int __manual_su_handle_devpts(struct inode *inode)
{
if (!current->mm) {
return 0;
}
uid_t uid = current_uid().val;
if (uid % 100000 < 10000) {
// not untrusted_app, ignore it
return 0;
}
if (likely(!ksu_is_allow_uid_for_current(uid)))
return 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) || defined(KSU_OPTIONAL_SELINUX_INODE)
struct inode_security_struct *sec = selinux_inode(inode);
#else
struct inode_security_struct *sec =
(struct inode_security_struct *)inode->i_security;
#endif
if (ksu_file_sid && sec)
sec->sid = ksu_file_sid;
return 0;
}
static void disable_seccomp_for_task(struct task_struct *tsk)
{
assert_spin_locked(&tsk->sighand->siglock);
#ifdef CONFIG_SECCOMP
if (tsk->seccomp.mode == SECCOMP_MODE_DISABLED && !tsk->seccomp.filter)
return;
#endif
clear_tsk_thread_flag(tsk, TIF_SECCOMP);
#ifdef CONFIG_SECCOMP
tsk->seccomp.mode = SECCOMP_MODE_DISABLED;
if (tsk->seccomp.filter) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
seccomp_filter_release(tsk);
#else
put_seccomp_filter(tsk);
tsk->seccomp.filter = NULL;
#endif
}
#endif
}
void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid)
{
struct cred *newcreds;
struct task_struct *target_task;
unsigned long flags;
struct task_struct *p = current;
struct task_struct *t;
pr_info("cmd_su: escape_to_root_for_cmd_su called for UID: %d, PID: %d\n", target_uid, target_pid);
// Find target task by PID
rcu_read_lock();
target_task = pid_task(find_vpid(target_pid), PIDTYPE_PID);
if (!target_task) {
rcu_read_unlock();
pr_err("cmd_su: target task not found for PID: %d\n", target_pid);
#if __SULOG_GATE
ksu_sulog_report_su_grant(target_uid, "cmd_su", "target_not_found");
#endif
return;
}
get_task_struct(target_task);
rcu_read_unlock();
if (task_uid(target_task).val == 0) {
pr_warn("cmd_su: target task is already root, PID: %d\n", target_pid);
put_task_struct(target_task);
return;
}
newcreds = prepare_kernel_cred(target_task);
if (newcreds == NULL) {
pr_err("cmd_su: failed to allocate new cred for PID: %d\n", target_pid);
#if __SULOG_GATE
ksu_sulog_report_su_grant(target_uid, "cmd_su", "cred_alloc_failed");
#endif
put_task_struct(target_task);
return;
}
struct root_profile *profile = ksu_get_root_profile(target_uid);
newcreds->uid.val = profile->uid;
newcreds->suid.val = profile->uid;
newcreds->euid.val = profile->uid;
newcreds->fsuid.val = profile->uid;
newcreds->gid.val = profile->gid;
newcreds->fsgid.val = profile->gid;
newcreds->sgid.val = profile->gid;
newcreds->egid.val = profile->gid;
newcreds->securebits = 0;
u64 cap_for_cmd_su = profile->capabilities.effective | CAP_DAC_READ_SEARCH | CAP_SETUID | CAP_SETGID;
memcpy(&newcreds->cap_effective, &cap_for_cmd_su, sizeof(newcreds->cap_effective));
memcpy(&newcreds->cap_permitted, &profile->capabilities.effective, sizeof(newcreds->cap_permitted));
memcpy(&newcreds->cap_bset, &profile->capabilities.effective, sizeof(newcreds->cap_bset));
setup_groups(profile, newcreds);
task_lock(target_task);
const struct cred *old_creds = get_task_cred(target_task);
rcu_assign_pointer(target_task->real_cred, newcreds);
rcu_assign_pointer(target_task->cred, get_cred(newcreds));
task_unlock(target_task);
if (target_task->sighand) {
spin_lock_irqsave(&target_task->sighand->siglock, flags);
disable_seccomp_for_task(target_task);
spin_unlock_irqrestore(&target_task->sighand->siglock, flags);
}
setup_selinux(profile->selinux_domain);
put_cred(old_creds);
wake_up_process(target_task);
if (target_task->signal->tty) {
struct inode *inode = target_task->signal->tty->driver_data;
if (inode && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC) {
__manual_su_handle_devpts(inode);
}
}
put_task_struct(target_task);
#if __SULOG_GATE
ksu_sulog_report_su_grant(target_uid, "cmd_su", "manual_escalation");
#endif
for_each_thread (p, t) {
ksu_set_task_tracepoint_flag(t);
}
pr_info("cmd_su: privilege escalation completed for UID: %d, PID: %d\n", target_uid, target_pid);
}
#endif

View File

@@ -1,70 +0,0 @@
#ifndef __KSU_H_APP_PROFILE
#define __KSU_H_APP_PROFILE
#include <linux/types.h>
// Forward declarations
struct cred;
#define KSU_APP_PROFILE_VER 2
#define KSU_MAX_PACKAGE_NAME 256
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
#define KSU_MAX_GROUPS 32
#define KSU_SELINUX_DOMAIN 64
struct root_profile {
int32_t uid;
int32_t gid;
int32_t groups_count;
int32_t groups[KSU_MAX_GROUPS];
// kernel_cap_t is u32[2] for capabilities v3
struct {
u64 effective;
u64 permitted;
u64 inheritable;
} capabilities;
char selinux_domain[KSU_SELINUX_DOMAIN];
int32_t namespaces;
};
struct non_root_profile {
bool umount_modules;
};
struct app_profile {
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
u32 version;
// this is usually the package of the app, but can be other value for special apps
char key[KSU_MAX_PACKAGE_NAME];
int32_t current_uid;
bool allow_su;
union {
struct {
bool use_default;
char template_name[KSU_MAX_PACKAGE_NAME];
struct root_profile profile;
} rp_config;
struct {
bool use_default;
struct non_root_profile profile;
} nrp_config;
};
};
// Escalate current process to root with the appropriate profile
void escape_with_root_profile(void);
void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid);
void disable_seccomp(void);
#endif

Some files were not shown because too many files have changed in this diff Show More