Compare commits
310 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdddca8637 | ||
|
|
032c3d4c54 | ||
|
|
2baaecc7f0 | ||
|
|
ad5042b66e | ||
|
|
2e067fa5d1 | ||
|
|
2c1beac1ca | ||
|
|
84be28708e | ||
|
|
6242c6ca8b | ||
|
|
ca5e93f221 | ||
|
|
e382a83895 | ||
|
|
2bc727bfbd | ||
|
|
8bdde66eaf | ||
|
|
0582a7554c | ||
|
|
48016fac7b | ||
|
|
823a3f9767 | ||
|
|
56a028d60d | ||
|
|
d0e8faea77 | ||
|
|
a772a0f82d | ||
|
|
de6dc65a56 | ||
|
|
c81e0c6c0f | ||
|
|
f87e705e2f | ||
|
|
078ffdb5e1 | ||
|
|
5a6ab43ea4 | ||
|
|
de6b2794b7 | ||
|
|
c574f39ae3 | ||
|
|
954ecd9644 | ||
|
|
8250c0ecc2 | ||
|
|
cc78812f50 | ||
|
|
088996da9b | ||
|
|
52a3a04b11 | ||
|
|
f290653088 | ||
|
|
33c498078f | ||
|
|
fb0eea2994 | ||
|
|
2ea748dac1 | ||
|
|
429874b4d6 | ||
|
|
cd86589ad3 | ||
|
|
22cb7596a7 | ||
|
|
029ae8d389 | ||
|
|
58c8289890 | ||
|
|
94fa1e360a | ||
|
|
1d1ce396d3 | ||
|
|
99d58c8cfd | ||
|
|
ad2a23f55e | ||
|
|
f48d2e6cac | ||
|
|
d45676f059 | ||
|
|
0439a00f1d | ||
|
|
cbcaa07fd5 | ||
|
|
c4d8c49e5c | ||
|
|
c8020b2066 | ||
|
|
8442ebcb7a | ||
|
|
25fbc22f66 | ||
|
|
a4a9df3a25 | ||
|
|
19a67fb76c | ||
|
|
3a2d55237d | ||
|
|
98e617f1bd | ||
|
|
d84a88f059 | ||
|
|
ae7b4dcbed | ||
|
|
684a5d1ccd | ||
|
|
35b02e3c73 | ||
|
|
db6a59ec4e | ||
|
|
46846dce91 | ||
|
|
cd21af6728 | ||
|
|
92a483d222 | ||
|
|
04ca981e4d | ||
|
|
83209a5259 | ||
|
|
3cfc6d6a31 | ||
|
|
01ac06c3fd | ||
|
|
906c4bdb01 | ||
|
|
c17d7b38eb | ||
|
|
fa57ccccf4 | ||
|
|
7e7713ee4a | ||
|
|
7afcdb3059 | ||
|
|
5df821ed41 | ||
|
|
12fc2e6d5e | ||
|
|
9f869090d2 | ||
|
|
bb8b991110 | ||
|
|
10548f9243 | ||
|
|
96d33e62bb | ||
|
|
7be8c15b85 | ||
|
|
14bb6afd0b | ||
|
|
54dad4ceb2 | ||
|
|
a60395ba35 | ||
|
|
095385f814 | ||
|
|
7e595e1730 | ||
|
|
ded31c2043 | ||
|
|
a7f840d811 | ||
|
|
05cca26075 | ||
|
|
5ce6c210c4 | ||
|
|
8f49898155 | ||
|
|
fd3a22360a | ||
|
|
ef36a36e9a | ||
|
|
7a1a08064b | ||
|
|
36862d6175 | ||
|
|
cda7e4c6c0 | ||
|
|
0b63cc445c | ||
|
|
2433ced81a | ||
|
|
1f04f13e44 | ||
|
|
184467c691 | ||
|
|
05ed1a3714 | ||
|
|
163531fcd2 | ||
|
|
049956aaa9 | ||
|
|
6530d06710 | ||
|
|
88135d8363 | ||
|
|
548258f922 | ||
|
|
46b9f5fb4b | ||
|
|
413e9ab8a9 | ||
|
|
c3644da85b | ||
|
|
7b4b5b431f | ||
|
|
7479c0b81b | ||
|
|
0381d12be2 | ||
|
|
4425c88d5a | ||
|
|
7828c5c107 | ||
|
|
76046c84cd | ||
|
|
623dd15cbf | ||
|
|
ab13ed5c16 | ||
|
|
e171ca15cb | ||
|
|
4fc369a059 | ||
|
|
18ad2afadb | ||
|
|
3badbcd4bc | ||
|
|
1b5b235bd9 | ||
|
|
fdf5e7104e | ||
|
|
ed6b2e0a8e | ||
|
|
704f7cba32 | ||
|
|
860bdce295 | ||
|
|
d8a8ef6458 | ||
|
|
d37a78ea2d | ||
|
|
6c9bf69718 | ||
|
|
776bcc4d5d | ||
|
|
bf5cb885b5 | ||
|
|
a533a490bd | ||
|
|
c6d9f76c7b | ||
|
|
66032391af | ||
|
|
da0e16bd26 | ||
|
|
53d763cdf9 | ||
|
|
9ebddde0d5 | ||
|
|
03a164ebb7 | ||
|
|
4769065cfc | ||
|
|
9b209765c4 | ||
|
|
d7c101e244 | ||
|
|
a32f89403b | ||
|
|
2cd673d776 | ||
|
|
14fea6f8a3 | ||
|
|
02f1aec6e9 | ||
|
|
826661dffb | ||
|
|
f86c71efc5 | ||
|
|
06018a2f03 | ||
|
|
a2193841d5 | ||
|
|
1324a7f54e | ||
|
|
5df9431a22 | ||
|
|
e54989e51a | ||
|
|
cf50be122e | ||
|
|
7f9048724f | ||
|
|
3dde6d9a25 | ||
|
|
132e9ef8ed | ||
|
|
e6436b340c | ||
|
|
9cdf98782d | ||
|
|
dece57cacf | ||
|
|
3f07ea29ae | ||
|
|
c8e103062a | ||
|
|
91312effba | ||
|
|
fd60cda3b3 | ||
|
|
5323a500dd | ||
|
|
0ce7bc2627 | ||
|
|
c9c62b25d2 | ||
|
|
f8904b1b02 | ||
|
|
89ce65e8ba | ||
|
|
994fdfddf2 | ||
|
|
557e7f8153 | ||
|
|
9e9bb685f0 | ||
|
|
99bec0e439 | ||
|
|
0400b94674 | ||
|
|
247f7d4aee | ||
|
|
088ce97697 | ||
|
|
c0a86544d8 | ||
|
|
47bd84f3d1 | ||
|
|
06e714b4e7 | ||
|
|
1439e486a1 | ||
|
|
801bcb0e1f | ||
|
|
54b5fb5fdb | ||
|
|
1cc9fce2c6 | ||
|
|
46fefc299c | ||
|
|
23cc0ceff1 | ||
|
|
4a610af452 | ||
|
|
8177afa81e | ||
|
|
3588282b43 | ||
|
|
257f0ca6de | ||
|
|
c863ff6f49 | ||
|
|
e99a14290f | ||
|
|
18e60ededa | ||
|
|
22e4b69231 | ||
|
|
1853d9decf | ||
|
|
d286f49e11 | ||
|
|
7103779a11 | ||
|
|
4350d309da | ||
|
|
7051b22536 | ||
|
|
e0bce04e79 | ||
|
|
c75b041c40 | ||
|
|
d2a3f0fcad | ||
|
|
696c3059b6 | ||
|
|
ab8e966b7f | ||
|
|
7ece40bb2c | ||
|
|
d1aa6c8beb | ||
|
|
bfed2d700a | ||
|
|
59339b806a | ||
|
|
2433d64b6b | ||
|
|
a622657092 | ||
|
|
b4e682148a | ||
|
|
02474a5953 | ||
|
|
450dbf14fc | ||
|
|
d89eab2c34 | ||
|
|
a6b86a4f99 | ||
|
|
33d1f18395 | ||
|
|
8ebe60ca04 | ||
|
|
980613c6a9 | ||
|
|
47bcc956a3 | ||
|
|
00de4e1c64 | ||
|
|
97ec718fea | ||
|
|
5c96f951b5 | ||
|
|
7e446efac4 | ||
|
|
c06d694ebc | ||
|
|
bd0b07cba9 | ||
|
|
e54339cf4e | ||
|
|
320e08b8fb | ||
|
|
0da8ecb071 | ||
|
|
cc54abd273 | ||
|
|
ee781cf959 | ||
|
|
a21b18c5de | ||
|
|
1644f22c98 | ||
|
|
7a338b1b43 | ||
|
|
324dc0844f | ||
|
|
f7fe0cf748 | ||
|
|
bf1a45963b | ||
|
|
64ee09fd12 | ||
|
|
b9a84a15bc | ||
|
|
6a1e1d788b | ||
|
|
4b86989bf9 | ||
|
|
d3f8c128da | ||
|
|
bbb2748494 | ||
|
|
b9e6246d65 | ||
|
|
7bf13fbfca | ||
|
|
e9ee2304d3 | ||
|
|
f7b4b4b82d | ||
|
|
15b9c4dbbf | ||
|
|
3b966c536b | ||
|
|
b5e5be2572 | ||
|
|
253df1ea47 | ||
|
|
134684a139 | ||
|
|
9c07fa6889 | ||
|
|
c3f66e15e9 | ||
|
|
b6e2fa383a | ||
|
|
61f85a029e | ||
|
|
b1564b77a2 | ||
|
|
cc0a3590ce | ||
|
|
e793219c2b | ||
|
|
776a753206 | ||
|
|
7b6470cc79 | ||
|
|
eb5fdbbf3f | ||
|
|
8db55f56a9 | ||
|
|
62635879e0 | ||
|
|
86634aac3d | ||
|
|
af25f8d49e | ||
|
|
cd78c2693a | ||
|
|
8ff9fab414 | ||
|
|
b8eebcda5a | ||
|
|
85291de02a | ||
|
|
cb7abc88dd | ||
|
|
918e7ae0b7 | ||
|
|
0a804ba170 | ||
|
|
4c512dc7ff | ||
|
|
fcb7c3e99d | ||
|
|
b827360ac6 | ||
|
|
ca7b53370e | ||
|
|
230ca54d63 | ||
|
|
2f43ad4f76 | ||
|
|
9c1ff635e3 | ||
|
|
ef97f0e4d9 | ||
|
|
2e394903cc | ||
|
|
7978cbafa5 | ||
|
|
c89a3dbcd9 | ||
|
|
13c7912320 | ||
|
|
abbe385382 | ||
|
|
0b80137f17 | ||
|
|
c4ff89c13d | ||
|
|
ce3a7ec189 | ||
|
|
2bb789212a | ||
|
|
7ef9230d66 | ||
|
|
fbaa69f3fb | ||
|
|
e78ee720b5 | ||
|
|
04b603394a | ||
|
|
c9c7a5f4e3 | ||
|
|
044b4a2f9c | ||
|
|
59cd8d1c3b | ||
|
|
36617bf0a1 | ||
|
|
5b49054055 | ||
|
|
a17a220745 | ||
|
|
0729066a6f | ||
|
|
d4dcf610c9 | ||
|
|
78e0dc6da2 | ||
|
|
a9a10466b3 | ||
|
|
65d5d6a494 | ||
|
|
e552163d9e | ||
|
|
c950705044 | ||
|
|
9e7aabf3f7 | ||
|
|
a20a89da03 | ||
|
|
9551ca4fe8 | ||
|
|
a2431d50ce | ||
|
|
8b74f7d466 | ||
|
|
b5d9607e8e | ||
|
|
475b3998a1 | ||
|
|
23ed4384e6 |
137
.github/workflows/avd-kernel.yml
vendored
@@ -1,137 +0,0 @@
|
||||
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 }}"
|
||||
20
.github/workflows/build-gki-image.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
111
.github/workflows/build-kernel-a12.yml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
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
|
||||
146
.github/workflows/build-kernel-a13.yml
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
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
|
||||
158
.github/workflows/build-kernel-a14.yml
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
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
|
||||
131
.github/workflows/build-kernel-a15.yml
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
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
|
||||
104
.github/workflows/build-kernel-a16.yml
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
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
@@ -1,74 +0,0 @@
|
||||
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
|
||||
77
.github/workflows/build-lkm.yml
vendored
@@ -1,74 +1,21 @@
|
||||
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:
|
||||
include:
|
||||
- version: "android12-5.10"
|
||||
sub_level: 240
|
||||
os_patch_level: 2025-09
|
||||
- version: "android13-5.10"
|
||||
sub_level: 238
|
||||
os_patch_level: 2025-07
|
||||
- version: "android13-5.15"
|
||||
sub_level: 189
|
||||
os_patch_level: 2025-09
|
||||
- version: "android14-5.15"
|
||||
sub_level: 185
|
||||
os_patch_level: 2025-07
|
||||
- version: "android14-6.1"
|
||||
sub_level: 145
|
||||
os_patch_level: 2025-09
|
||||
- version: "android15-6.6"
|
||||
sub_level: 98
|
||||
os_patch_level: 2025-09
|
||||
# uses: ./.github/workflows/gki-kernel-mock.yml when debugging
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
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
|
||||
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: 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
|
||||
kmi: ${{ matrix.kmi }}
|
||||
ddk_release: '20251104'
|
||||
|
||||
245
.github/workflows/build-manager-manual.yml
vendored
@@ -1,245 +0,0 @@
|
||||
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
|
||||
./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
|
||||
195
.github/workflows/build-manager.yml
vendored
@@ -2,117 +2,31 @@ name: Build Manager
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "ci" ]
|
||||
branches: [ "main", "dev", "ci", "miuix" ]
|
||||
paths:
|
||||
- '.github/workflows/build-manager.yml'
|
||||
- '.github/workflows/build-lkm.yml'
|
||||
- 'manager/**'
|
||||
- 'kernel/**'
|
||||
- 'userspace/ksud/**'
|
||||
- 'userspace/susfs/**'
|
||||
- 'userspace/kpmmgr/**'
|
||||
- 'userspace/user_scanner/**'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: [ "main", "dev", "miuix" ]
|
||||
paths:
|
||||
- '.github/workflows/build-manager.yml'
|
||||
- '.github/workflows/build-lkm.yml'
|
||||
- 'manager/**'
|
||||
- 'kernel/**'
|
||||
- 'userspace/ksud/**'
|
||||
- 'userspace/user_scanner/**'
|
||||
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-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-user_scanner:
|
||||
if: ${{ always() }}
|
||||
needs: [ check-build-lkm, build-lkm ]
|
||||
needs: build-lkm
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -124,8 +38,7 @@ jobs:
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud:
|
||||
if: ${{ always() }}
|
||||
needs: [ check-build-lkm, build-lkm ]
|
||||
needs: build-lkm
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -139,13 +52,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
|
||||
@@ -165,8 +78,34 @@ jobs:
|
||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
|
||||
- name: Determine manager variant for telegram bot
|
||||
id: determine
|
||||
run: |
|
||||
if [ "${{ github.ref_name }}" == "miuix" ] && [ "${{ matrix.spoofed }}" == "true" ]; then
|
||||
echo "SKIP=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
if [ "${{ github.ref_name }}" == "miuix" ]; then
|
||||
echo "title=Manager" >> $GITHUB_OUTPUT
|
||||
echo "topicid=${{ vars.MESSAGE_MIUIX_THREAD_ID }}" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ 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' && steps.determine.outputs.SKIP != 'true' }}
|
||||
run: |
|
||||
chmod +x randomizer
|
||||
./randomizer
|
||||
|
||||
- name: Write key
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref == 'refs/heads/susfs' || github.ref_type == 'tag' }}
|
||||
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' )) || github.ref_type == 'tag' }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||
{
|
||||
@@ -196,18 +135,6 @@ jobs:
|
||||
name: userscanner-all-linux-android
|
||||
path: .
|
||||
|
||||
- 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:
|
||||
@@ -231,19 +158,9 @@ 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/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
|
||||
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
|
||||
|
||||
- name: Copy user_scanner to app jniLibs
|
||||
run: |
|
||||
@@ -255,40 +172,34 @@ jobs:
|
||||
cp -f ../armeabi-v7a/uid_scanner ../manager/app/src/main/jniLibs/armeabi-v7a/libuid_scanner.so
|
||||
|
||||
- name: Build with Gradle
|
||||
if: ${{ steps.determine.outputs.SKIP != 'true' }}
|
||||
run: ./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' }}
|
||||
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' )) || github.ref_type == 'tag' }}
|
||||
with:
|
||||
name: manager
|
||||
name: ${{ steps.determine.outputs.title }}
|
||||
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' }}
|
||||
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' )) || github.ref_type == 'tag' }}
|
||||
with:
|
||||
name: "mappings"
|
||||
name: "${{ steps.determine.outputs.title }}-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'
|
||||
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true' && steps.determine.outputs.SKIP != 'true'
|
||||
env:
|
||||
CHAT_ID: ${{ vars.CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ vars.MESSAGE_THREAD_ID }}
|
||||
MESSAGE_THREAD_ID: ${{ steps.determine.outputs.topicid }}
|
||||
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
|
||||
TITLE: ${{ steps.determine.outputs.title }}
|
||||
BRANCH: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
export VERSION=$(git rev-list --count HEAD)
|
||||
|
||||
2
.github/workflows/clippy.yml
vendored
@@ -34,4 +34,4 @@ jobs:
|
||||
- name: Run clippy
|
||||
run: |
|
||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target aarch64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android --release
|
||||
53
.github/workflows/ddk-lkm.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
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
|
||||
67
.github/workflows/deploy-website.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Deploy Website
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- website
|
||||
paths:
|
||||
- '.github/workflows/deploy-website.yml'
|
||||
- 'website/**'
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./website
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Not needed if lastUpdated is not enabled
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
cache: yarn # or pnpm / yarn
|
||||
cache-dependency-path: website/yarn.lock
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build with VitePress
|
||||
run: |
|
||||
yarn docs:build
|
||||
touch docs/.vitepress/dist/.nojekyll
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: website/docs/.vitepress/dist
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
252
.github/workflows/gki-kernel-local.yml
vendored
@@ -1,252 +0,0 @@
|
||||
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
@@ -1,79 +0,0 @@
|
||||
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
|
||||
45
.github/workflows/gki-kernel.yml
vendored
@@ -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.35
|
||||
repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }} --repo-rev=v2.16
|
||||
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,12 +195,35 @@ 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: |
|
||||
if [ -e common/BUILD.bazel ]; then
|
||||
sed -i '/^[[:space:]]*"protected_exports_list"[[:space:]]*:[[:space:]]*"android\/abi_gki_protected_exports_aarch64",$/d' common/BUILD.bazel
|
||||
# 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
|
||||
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"
|
||||
@@ -217,7 +240,11 @@ 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
|
||||
tools/bazel run --disk_cache=/home/runner/.cache/bazel --config=fast --config=stamp --lto=thin //common:kernel_aarch64_dist -- --dist_dir=dist
|
||||
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
|
||||
fi
|
||||
|
||||
- name: Prepare artifacts
|
||||
@@ -228,7 +255,7 @@ jobs:
|
||||
OUTDIR=android-kernel/dist
|
||||
fi
|
||||
mkdir output
|
||||
if [ "${{ inputs.build_lkm}}" = "true" ]; then
|
||||
if [ "${{ inputs.build_lkm}}" = "true" ]; then
|
||||
llvm-strip-15 -d $OUTDIR/kernelsu.ko
|
||||
mv $OUTDIR/kernelsu.ko ./output/${{ inputs.version }}_kernelsu.ko
|
||||
else
|
||||
|
||||
40
.github/workflows/kpmmgr.yml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Build kpmmgr
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "mian" ]
|
||||
paths:
|
||||
- '.github/workflows/kpmmgr.yml'
|
||||
- 'userspace/kpmmgr/**'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
os:
|
||||
required: false
|
||||
type: string
|
||||
default: self-hosted
|
||||
|
||||
jobs:
|
||||
build-susfs:
|
||||
name: Build userspace kpmmgr
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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: kpmmgr-aarch64-linux-android
|
||||
path: ./userspace/kpmmgr/libs
|
||||
35
.github/workflows/ksud.yml
vendored
@@ -9,10 +9,6 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ubuntu-latest
|
||||
pull_lkm:
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
pack_lkm:
|
||||
required: false
|
||||
type: boolean
|
||||
@@ -21,6 +17,8 @@ on:
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ inputs.os }}
|
||||
@@ -29,36 +27,19 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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 }}
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- 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 }}
|
||||
|
||||
- name: Prepare LKM fies
|
||||
if: ${{ inputs.pack_lkm }}
|
||||
run: |
|
||||
cp android*-lkm/*_kernelsu.ko ./userspace/ksud/bin/aarch64/
|
||||
|
||||
|
||||
- name: Setup rustup
|
||||
run: |
|
||||
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: |
|
||||
@@ -71,4 +52,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ksud-${{ inputs.target }}
|
||||
path: userspace/ksud/target/**/release/zakozako*
|
||||
path: userspace/ksud/target/**/release/ksud*
|
||||
|
||||
65
.github/workflows/notify.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Notify
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'release/**'
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
branches:
|
||||
- main
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ts
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: notify-telegram-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- name: Checkout repository with sparse-checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
sparse-checkout: |
|
||||
ts/**
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Setup Bun runtime
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Cache Bun modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun
|
||||
ts/.node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('ts/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --cache
|
||||
|
||||
- name: Run telegram bot script
|
||||
env:
|
||||
BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TELEGRAM_GROUP_ID: ${{ secrets.TELEGRAM_GROUP_ID }}
|
||||
TELEGRAM_TOPIC_COMMITS: ${{ secrets.TELEGRAM_TOPIC_COMMITS }}
|
||||
TELEGRAM_TOPIC_PRS: ${{ secrets.TELEGRAM_TOPIC_PRS }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_EVENT_PATH: ${{ github.event_path }}
|
||||
run: bun run send.ts
|
||||
2
.github/workflows/shellcheck.yml
vendored
@@ -16,7 +16,7 @@ on:
|
||||
|
||||
jobs:
|
||||
shellcheck:
|
||||
runs-on: self-hosted
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
40
.github/workflows/susfs.yml
vendored
@@ -1,40 +0,0 @@
|
||||
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
@@ -1,3 +1,3 @@
|
||||
.idea
|
||||
.vscode
|
||||
CLAUDE.md
|
||||
.DS_Store
|
||||
|
||||
89
Website/.gitignore
vendored
@@ -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
|
||||
@@ -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
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# SukiSU-Ultra Documentation
|
||||
|
||||
[](https://sukisu.org)
|
||||
[](https://vitepress.dev)
|
||||
[](https://sukisu.org/guide/license)
|
||||
[](https://github.com/sukisu-ultra/sukisu-ultra/commits/main)
|
||||
[](https://github.com/sukisu-ultra/sukisu-ultra/stargazers)
|
||||
18
Website/docs/.gitignore
vendored
@@ -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
|
||||
@@ -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: ['..'],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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(() => {})
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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 |
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
:::
|
||||
@@ -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.
|
||||
:::
|
||||
@@ -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 |
|
||||
@@ -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.
|
||||
---
|
||||
|
Before Width: | Height: | Size: 200 KiB |
@@ -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
|
||||
|
Before Width: | Height: | Size: 58 KiB |
@@ -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 |
@@ -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>
|
||||
@@ -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: /
|
||||
@@ -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 |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 66 KiB |
@@ -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: '关闭',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# 兼容性状态
|
||||
|
||||
::: info KernelSU
|
||||
KernelSU(v0.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** | 🟡 部分支持 | 支持部分设备 |
|
||||
@@ -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,但目前无法使用。 -->
|
||||
|
||||
将 KernelSU(SukiSU)添加到内核源代码树的步骤的运行命令将被替换为:
|
||||
|
||||
### 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) 里找到它)
|
||||
@@ -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)
|
||||
- **直接支持**: 联系开发者处理关键问题
|
||||
@@ -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` 并知道如何在出现问题时恢复设备。
|
||||
:::
|
||||
@@ -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 时,请确保遵守各个组件的所有适用许可证和署名要求。
|
||||
:::
|
||||
@@ -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) | 设备特定构建 |
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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 功能。
|
||||
---
|
||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 58 KiB |
@@ -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"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 66 KiB |
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
packages:
|
||||
- docs
|
||||
- .
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
@@ -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`)
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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"
|
||||
@@ -44,7 +44,7 @@ Generally need to modify the `do_execve` and `compat_do_execve` methods in `fs/e
|
||||
.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 */
|
||||
+ 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);
|
||||
}
|
||||
@@ -237,34 +237,3 @@ Need to modify the `input_event` method in `drivers/input/input.c`, not `input_h
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
```
|
||||
|
||||
### devpts Hook (`pty.c`)
|
||||
|
||||
Need to modify the `pts_unix98_lookup` method in `drivers/tty/pty.c`
|
||||
|
||||
```patch
|
||||
--- 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);
|
||||
```
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
.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 */
|
||||
+ 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);
|
||||
}
|
||||
@@ -236,35 +236,4 @@
|
||||
if (is_event_supported(type, dev->evbit, EV_MAX)) {
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
```
|
||||
|
||||
### devpts 钩子 (`pty.c`)
|
||||
|
||||
需要修改 `drivers/tty/pty.c` 的 `pts_unix98_lookup` 方法
|
||||
|
||||
```patch
|
||||
--- 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);
|
||||
```
|
||||
```
|
||||
@@ -56,8 +56,8 @@ ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
#CompactNamespaces: false # Unknown to clang-format-4.0
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
@@ -501,7 +501,7 @@ IncludeCategories:
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: false
|
||||
#IndentPPDirectives: None # Unknown to clang-format-5.0
|
||||
IndentWidth: 8
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
@@ -511,7 +511,7 @@ MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
|
||||
ObjCBlockIndentWidth: 8
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
@@ -543,6 +543,6 @@ SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp03
|
||||
TabWidth: 8
|
||||
UseTab: Always
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
22
kernel/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
.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
|
||||
11
kernel/.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"cStandard": "c11",
|
||||
"intelliSenseMode": "gcc-arm64",
|
||||
"compileCommands": "${workspaceFolder}/compile_commands.json"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
92
kernel/.vscode/generate_compdb.py
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/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()
|
||||
16
kernel/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
// 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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,57 +1,42 @@
|
||||
menu "KernelSU"
|
||||
|
||||
config KSU
|
||||
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.
|
||||
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.
|
||||
|
||||
config KSU_DEBUG
|
||||
bool "KernelSU debug mode"
|
||||
depends on KSU
|
||||
default n
|
||||
help
|
||||
Enable KernelSU debug mode.
|
||||
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
|
||||
|
||||
config KPM
|
||||
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
|
||||
bool "Enable SukiSU KPM"
|
||||
depends on KSU && 64BIT
|
||||
default n
|
||||
help
|
||||
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.
|
||||
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
|
||||
|
||||
config KSU_MANUAL_HOOK
|
||||
bool "Hook KernelSU manually"
|
||||
depends on KSU != m
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with manually-patched function.
|
||||
|
||||
endchoice
|
||||
depends on KSU != m
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with manually-patched function.
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
kernelsu-objs := ksu.o
|
||||
kernelsu-objs += allowlist.o
|
||||
kernelsu-objs += app_profile.o
|
||||
kernelsu-objs += dynamic_manager.o
|
||||
kernelsu-objs += apk_sign.o
|
||||
kernelsu-objs += sucompat.o
|
||||
kernelsu-objs += syscall_hook_manager.o
|
||||
kernelsu-objs += throne_tracker.o
|
||||
kernelsu-objs += core_hook.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 += ksud.o
|
||||
kernelsu-objs += embed_ksud.o
|
||||
kernelsu-objs += kernel_compat.o
|
||||
kernelsu-objs += seccomp_cache.o
|
||||
kernelsu-objs += file_wrapper.o
|
||||
kernelsu-objs += throne_comm.o
|
||||
kernelsu-objs += sulog.o
|
||||
|
||||
ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y)
|
||||
kernelsu-objs += ksu_trace.o
|
||||
ifeq ($(CONFIG_KSU_MANUAL_SU), y)
|
||||
ccflags-y += -DCONFIG_KSU_MANUAL_SU
|
||||
kernelsu-objs += manual_su.o
|
||||
endif
|
||||
|
||||
kernelsu-objs += selinux/selinux.o
|
||||
@@ -21,46 +32,62 @@ 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 := 3.2.0
|
||||
KSU_VERSION_API := 4.0.0
|
||||
|
||||
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')
|
||||
|
||||
LOCAL_GIT_EXISTS := $(shell test -e $(srctree)/$(src)/../.git && echo 1 || echo 0)
|
||||
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)
|
||||
|
||||
define get_ksu_version_full
|
||||
v$1-$(shell cd $(srctree)/$(src); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(srctree)/$(src); $(GIT_BIN) rev-parse --abbrev-ref HEAD)
|
||||
v$1-$(shell cd $(KSU_SRC); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(KSU_SRC); $(GIT_BIN) rev-parse --abbrev-ref HEAD)
|
||||
endef
|
||||
|
||||
ifeq ($(KSU_GITHUB_VERSION_COMMIT),)
|
||||
ifeq ($(LOCAL_GIT_EXISTS),1)
|
||||
$(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)
|
||||
$(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)
|
||||
$(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 10000 + $(KSU_GITHUB_VERSION_COMMIT) + 700)
|
||||
KSU_VERSION := $(shell expr 40000 + $(KSU_GITHUB_VERSION_COMMIT) - 2815)
|
||||
$(info -- $(REPO_NAME) version (GitHub): $(KSU_VERSION))
|
||||
endif
|
||||
|
||||
ifeq ($(KSU_GITHUB_VERSION),)
|
||||
ifeq ($(LOCAL_GIT_EXISTS),1)
|
||||
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||
$(shell cd $(KSU_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))
|
||||
@@ -69,7 +96,7 @@ ifeq ($(KSU_GITHUB_VERSION),)
|
||||
$(warning -- $(REPO_NAME) version: $(KSU_VERSION_FULL))
|
||||
endif
|
||||
else
|
||||
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||
$(shell cd $(KSU_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
|
||||
@@ -93,14 +120,13 @@ ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
|
||||
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
endif
|
||||
|
||||
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
|
||||
|
||||
ifeq ($(CONFIG_KSU_KPROBES_HOOK), y)
|
||||
$(info -- SukiSU: CONFIG_KSU_KPROBES_HOOK)
|
||||
else ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y)
|
||||
$(info -- SukiSU: CONFIG_KSU_TRACEPOINT_HOOK)
|
||||
else ifeq ($(CONFIG_KSU_MANUAL_HOOK), y)
|
||||
$(info -- SukiSU: CONFIG_KSU_MANUAL_HOOK)
|
||||
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)
|
||||
endif
|
||||
|
||||
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
|
||||
@@ -117,14 +143,30 @@ endif
|
||||
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||
$(info -- KERNEL_TYPE: $(KERNEL_TYPE))
|
||||
|
||||
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||
ifeq ($(CONFIG_KPM), y)
|
||||
$(info -- KPM is enabled)
|
||||
else
|
||||
$(info -- KPM is disabled)
|
||||
endif
|
||||
|
||||
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
|
||||
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
|
||||
# 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
|
||||
|
||||
# Keep a new line here!! Because someone may append config
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/task_work.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/fs.h>
|
||||
@@ -8,14 +10,16 @@
|
||||
#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
|
||||
@@ -29,58 +33,61 @@ static DEFINE_MUTEX(allowlist_mutex);
|
||||
static struct root_profile default_root_profile;
|
||||
static struct non_root_profile default_non_root_profile;
|
||||
|
||||
static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly __aligned(PAGE_SIZE);
|
||||
void persistent_allow_list(void);
|
||||
|
||||
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 = kzalloc(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()
|
||||
static void init_default_profiles(void)
|
||||
{
|
||||
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;
|
||||
@@ -90,437 +97,535 @@ 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()
|
||||
static void ksu_grant_root_to_shell(void)
|
||||
{
|
||||
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 *)kzalloc(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();
|
||||
if (persist) {
|
||||
persistent_allow_list();
|
||||
// FIXME: use a new flag
|
||||
ksu_mark_running_process();
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool __ksu_is_allow_uid(uid_t uid)
|
||||
{
|
||||
int i;
|
||||
int i;
|
||||
|
||||
if (unlikely(uid == 0)) {
|
||||
// already root, but only allow our domain.
|
||||
return is_ksu_domain();
|
||||
}
|
||||
if (forbid_system_uid(uid)) {
|
||||
// do not bother going through the list if it's system
|
||||
return false;
|
||||
}
|
||||
|
||||
if (forbid_system_uid(uid)) {
|
||||
// do not bother going through the list if it's system
|
||||
return false;
|
||||
}
|
||||
if (likely(ksu_is_manager_uid_valid()) &&
|
||||
unlikely(ksu_get_manager_uid() == uid)) {
|
||||
// manager is always allowed!
|
||||
return true;
|
||||
}
|
||||
|
||||
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
|
||||
// manager is always allowed!
|
||||
return true;
|
||||
}
|
||||
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(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;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void do_save_allow_list(struct work_struct *work)
|
||||
static void do_persistent_allow_list(struct callback_head *_cb)
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) !=
|
||||
sizeof(version)) {
|
||||
pr_err("save_allow_list write version 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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile),
|
||||
&off);
|
||||
}
|
||||
kernel_write(fp, &p->profile, sizeof(p->profile), &off);
|
||||
}
|
||||
|
||||
exit:
|
||||
filp_close(fp, 0);
|
||||
close_file:
|
||||
filp_close(fp, 0);
|
||||
unlock:
|
||||
mutex_unlock(&allowlist_mutex);
|
||||
kfree(_cb);
|
||||
}
|
||||
|
||||
void do_load_allow_list(struct work_struct *work)
|
||||
void persistent_allow_list()
|
||||
{
|
||||
loff_t off = 0;
|
||||
ssize_t ret = 0;
|
||||
struct file *fp = NULL;
|
||||
u32 magic;
|
||||
u32 version;
|
||||
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();
|
||||
// always allow adb shell by default
|
||||
ksu_grant_root_to_shell();
|
||||
#endif
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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 (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) !=
|
||||
sizeof(magic) ||
|
||||
magic != FILE_MAGIC) {
|
||||
pr_err("allowlist file invalid: %d!\n", magic);
|
||||
goto exit;
|
||||
}
|
||||
// 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 (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) !=
|
||||
sizeof(version)) {
|
||||
pr_err("allowlist read version: %d failed\n", version);
|
||||
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);
|
||||
pr_info("allowlist version: %d\n", version);
|
||||
|
||||
while (true) {
|
||||
struct app_profile profile;
|
||||
while (true) {
|
||||
struct app_profile profile;
|
||||
|
||||
ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile),
|
||||
&off);
|
||||
ret = kernel_read(fp, &profile, sizeof(profile), &off);
|
||||
|
||||
if (ret <= 0) {
|
||||
pr_info("load_allow_list read err: %zd\n", ret);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
ksu_show_allow_list();
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data)
|
||||
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *),
|
||||
void *data)
|
||||
{
|
||||
struct perm_data *np = NULL;
|
||||
struct perm_data *n = NULL;
|
||||
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 (!ksu_boot_completed) {
|
||||
pr_info("boot not completed, skip prune\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
persistent_allow_list();
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
// 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);
|
||||
if (modified) {
|
||||
persistent_allow_list();
|
||||
}
|
||||
}
|
||||
|
||||
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_WORK(&ksu_save_work, do_save_allow_list);
|
||||
INIT_WORK(&ksu_load_work, do_load_allow_list);
|
||||
|
||||
init_default_profiles();
|
||||
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;
|
||||
|
||||
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);
|
||||
// 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
|
||||
@@ -2,19 +2,31 @@
|
||||
#define __KSU_H_ALLOWLIST
|
||||
|
||||
#include <linux/types.h>
|
||||
#include "ksu.h"
|
||||
#include <linux/uidgid.h>
|
||||
#include "app_profile.h"
|
||||
|
||||
#define PER_USER_RANGE 100000
|
||||
#define FIRST_APPLICATION_UID 10000
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
#define FIRST_ISOLATED_UID 99000
|
||||
#define LAST_ISOLATED_UID 99999
|
||||
|
||||
void ksu_allowlist_init(void);
|
||||
|
||||
void ksu_allowlist_exit(void);
|
||||
|
||||
bool ksu_load_allow_list(void);
|
||||
void 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);
|
||||
@@ -24,4 +36,22 @@ 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;
|
||||
}
|
||||
|
||||
static inline bool is_isolated_process(uid_t uid)
|
||||
{
|
||||
uid_t appid = uid % PER_USER_RANGE;
|
||||
return appid >= FIRST_ISOLATED_UID && appid <= LAST_ISOLATED_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
|
||||
|
||||
@@ -17,69 +17,65 @@
|
||||
#include "apk_sign.h"
|
||||
#include "dynamic_manager.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 struct apk_sign_key {
|
||||
unsigned size;
|
||||
const char *sha256;
|
||||
} apk_sign_keys[] = {
|
||||
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
|
||||
static apk_sign_key_t apk_sign_keys[] = {
|
||||
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
|
||||
#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 = kzalloc(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,304 +83,307 @@ 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(¤t_dynamic_key.size, ¤t_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;
|
||||
}
|
||||
struct dynamic_sign_key current_dynamic_key = dynamic_sign;
|
||||
|
||||
if (ksu_get_dynamic_manager_config(¤t_dynamic_key.size, ¤t_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;
|
||||
}
|
||||
|
||||
ksu_kernel_read_compat(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;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
struct apk_sign_key sign_key;
|
||||
bool signature_valid = false;
|
||||
int i;
|
||||
apk_sign_key_t sign_key;
|
||||
bool signature_valid = false;
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
*offset += 0x4 * 3;
|
||||
*offset += 0x4 * 3;
|
||||
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length
|
||||
kernel_read(fp, size4, 0x4, pos); // digests-sequence length
|
||||
|
||||
*pos += *size4;
|
||||
*offset += 0x4 + *size4;
|
||||
*pos += *size4;
|
||||
*offset += 0x4 + *size4;
|
||||
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length
|
||||
*offset += 0x4 * 2;
|
||||
kernel_read(fp, size4, 0x4, pos); // certificates length
|
||||
kernel_read(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;
|
||||
}
|
||||
}
|
||||
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 (*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;
|
||||
}
|
||||
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 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';
|
||||
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);
|
||||
|
||||
if (strcmp(sign_key.sha256, hash_str) == 0) {
|
||||
signature_valid = true;
|
||||
if (matched_index) {
|
||||
*matched_index = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return signature_valid;
|
||||
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;
|
||||
}
|
||||
|
||||
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 (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';
|
||||
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';
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
unsigned char buffer[0x11] = { 0 };
|
||||
u32 size4;
|
||||
u64 size8, size_of_block;
|
||||
|
||||
// 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;
|
||||
}
|
||||
loff_t pos;
|
||||
|
||||
// disable inotify for this file
|
||||
fp->f_mode |= FMODE_NONOTIFY;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
pos += 12;
|
||||
// offset
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
|
||||
pos = size4 - 0x18;
|
||||
// disable inotify for this file
|
||||
fp->f_mode |= FMODE_NONOTIFY;
|
||||
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
pos = size4 - (size8 + 0x8);
|
||||
ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos);
|
||||
if (size_of_block != size8) {
|
||||
goto clean;
|
||||
}
|
||||
pos += 12;
|
||||
// offset
|
||||
kernel_read(fp, &size4, 0x4, &pos);
|
||||
pos = size4 - 0x18;
|
||||
|
||||
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 {
|
||||
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 {
|
||||
#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 (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 (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;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
@@ -395,19 +394,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
|
||||
|
||||
|
||||
303
kernel/app_profile.c
Normal file
@@ -0,0 +1,303 @@
|
||||
#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(¤t->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(¤t->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(¤t->sighand->siglock);
|
||||
disable_seccomp();
|
||||
spin_unlock_irq(¤t->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
|
||||
70
kernel/app_profile.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#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
|
||||
@@ -18,10 +18,8 @@
|
||||
#define __PT_SP_REG sp
|
||||
#define __PT_IP_REG pc
|
||||
|
||||
#define PRCTL_SYMBOL "__arm64_sys_prctl"
|
||||
#define REBOOT_SYMBOL "__arm64_sys_reboot"
|
||||
#define SYS_READ_SYMBOL "__arm64_sys_read"
|
||||
#define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat"
|
||||
#define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat"
|
||||
#define SYS_EXECVE_SYMBOL "__arm64_sys_execve"
|
||||
|
||||
#elif defined(__x86_64__)
|
||||
@@ -39,10 +37,8 @@
|
||||
#define __PT_RC_REG ax
|
||||
#define __PT_SP_REG sp
|
||||
#define __PT_IP_REG ip
|
||||
#define PRCTL_SYMBOL "__x64_sys_prctl"
|
||||
#define REBOOT_SYMBOL "__x64_sys_reboot"
|
||||
#define SYS_READ_SYMBOL "__x64_sys_read"
|
||||
#define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat"
|
||||
#define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat"
|
||||
#define SYS_EXECVE_SYMBOL "__x64_sys_execve"
|
||||
|
||||
#else
|
||||
|
||||
1092
kernel/core_hook.c
@@ -1,10 +0,0 @@
|
||||
#ifndef __KSU_H_KSU_CORE
|
||||
#define __KSU_H_KSU_CORE
|
||||
|
||||
#include <linux/init.h>
|
||||
#include "apk_sign.h"
|
||||
|
||||
void __init ksu_core_init(void);
|
||||
void ksu_core_exit(void);
|
||||
|
||||
#endif
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include "dynamic_manager.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
#include "manager.h"
|
||||
|
||||
#define MAX_MANAGERS 2
|
||||
@@ -233,23 +232,23 @@ static void do_save_dynamic_manager(struct work_struct *work)
|
||||
return;
|
||||
}
|
||||
|
||||
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
fp = filp_open(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("save_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
||||
if (kernel_write(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
||||
pr_err("save_dynamic_manager write magic failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
if (kernel_write(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
pr_err("save_dynamic_manager write version failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
|
||||
if (kernel_write(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
|
||||
pr_err("save_dynamic_manager write config failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
@@ -271,7 +270,7 @@ static void do_load_dynamic_manager(struct work_struct *work)
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_RDONLY, 0);
|
||||
fp = filp_open(KERNEL_SU_DYNAMIC_MANAGER, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
if (PTR_ERR(fp) == -ENOENT) {
|
||||
pr_info("No saved dynamic manager config found\n");
|
||||
@@ -281,20 +280,20 @@ static void do_load_dynamic_manager(struct work_struct *work)
|
||||
return;
|
||||
}
|
||||
|
||||
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
|
||||
if (kernel_read(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
|
||||
magic != DYNAMIC_MANAGER_FILE_MAGIC) {
|
||||
pr_err("dynamic manager file invalid magic: %x!\n", magic);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
if (kernel_read(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
pr_err("dynamic manager read version failed\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
pr_info("dynamic manager file version: %d\n", version);
|
||||
|
||||
ret = ksu_kernel_read_compat(fp, &loaded_config, sizeof(loaded_config), &off);
|
||||
ret = kernel_read(fp, &loaded_config, sizeof(loaded_config), &off);
|
||||
if (ret <= 0) {
|
||||
pr_info("load_dynamic_manager read err: %zd\n", ret);
|
||||
goto exit;
|
||||
@@ -348,14 +347,14 @@ static void do_clear_dynamic_manager(struct work_struct *work)
|
||||
|
||||
memset(zero_buffer, 0, sizeof(zero_buffer));
|
||||
|
||||
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
fp = filp_open(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("clear_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
// Write null bytes to overwrite the file content
|
||||
if (ksu_kernel_write_compat(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) {
|
||||
if (kernel_write(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) {
|
||||
pr_err("clear_dynamic_manager write null bytes failed.\n");
|
||||
} else {
|
||||
pr_info("Dynamic sign config file cleared successfully\n");
|
||||
|
||||
173
kernel/feature.c
Normal file
@@ -0,0 +1,173 @@
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
|
||||
#include <linux/mutex.h>
|
||||
|
||||
static const struct ksu_feature_handler *feature_handlers[KSU_FEATURE_MAX];
|
||||
|
||||
static DEFINE_MUTEX(feature_mutex);
|
||||
|
||||
int ksu_register_feature_handler(const struct ksu_feature_handler *handler)
|
||||
{
|
||||
if (!handler) {
|
||||
pr_err("feature: register handler is NULL\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (handler->feature_id >= KSU_FEATURE_MAX) {
|
||||
pr_err("feature: invalid feature_id %u\n", handler->feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!handler->get_handler && !handler->set_handler) {
|
||||
pr_err("feature: no handler provided for feature %u\n", handler->feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
if (feature_handlers[handler->feature_id]) {
|
||||
pr_warn("feature: handler for %u already registered, overwriting\n",
|
||||
handler->feature_id);
|
||||
}
|
||||
|
||||
feature_handlers[handler->feature_id] = handler;
|
||||
|
||||
pr_info("feature: registered handler for %s (id=%u)\n",
|
||||
handler->name ? handler->name : "unknown", handler->feature_id);
|
||||
|
||||
mutex_unlock(&feature_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_unregister_feature_handler(u32 feature_id)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (feature_id >= KSU_FEATURE_MAX) {
|
||||
pr_err("feature: invalid feature_id %u\n", feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
if (!feature_handlers[feature_id]) {
|
||||
pr_warn("feature: no handler registered for %u\n", feature_id);
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
feature_handlers[feature_id] = NULL;
|
||||
|
||||
pr_info("feature: unregistered handler for id=%u\n", feature_id);
|
||||
|
||||
out:
|
||||
mutex_unlock(&feature_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ksu_get_feature(u32 feature_id, u64 *value, bool *supported)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct ksu_feature_handler *handler;
|
||||
|
||||
if (feature_id >= KSU_FEATURE_MAX) {
|
||||
pr_err("feature: invalid feature_id %u\n", feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!value || !supported) {
|
||||
pr_err("feature: invalid parameters\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
handler = feature_handlers[feature_id];
|
||||
|
||||
if (!handler) {
|
||||
*supported = false;
|
||||
*value = 0;
|
||||
pr_debug("feature: feature %u not supported\n", feature_id);
|
||||
goto out;
|
||||
}
|
||||
|
||||
*supported = true;
|
||||
|
||||
if (!handler->get_handler) {
|
||||
pr_warn("feature: no get_handler for feature %u\n", feature_id);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = handler->get_handler(value);
|
||||
if (ret) {
|
||||
pr_err("feature: get_handler for %u failed: %d\n", feature_id, ret);
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&feature_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ksu_set_feature(u32 feature_id, u64 value)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct ksu_feature_handler *handler;
|
||||
|
||||
if (feature_id >= KSU_FEATURE_MAX) {
|
||||
pr_err("feature: invalid feature_id %u\n", feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
handler = feature_handlers[feature_id];
|
||||
|
||||
if (!handler) {
|
||||
pr_err("feature: feature %u not registered\n", feature_id);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!handler->set_handler) {
|
||||
pr_warn("feature: no set_handler for feature %u\n", feature_id);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = handler->set_handler(value);
|
||||
if (ret) {
|
||||
pr_err("feature: set_handler for %u failed: %d\n", feature_id, ret);
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&feature_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ksu_feature_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < KSU_FEATURE_MAX; i++) {
|
||||
feature_handlers[i] = NULL;
|
||||
}
|
||||
|
||||
pr_info("feature: feature management initialized\n");
|
||||
}
|
||||
|
||||
void ksu_feature_exit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
for (i = 0; i < KSU_FEATURE_MAX; i++) {
|
||||
feature_handlers[i] = NULL;
|
||||
}
|
||||
|
||||
mutex_unlock(&feature_mutex);
|
||||
|
||||
pr_info("feature: feature management cleaned up\n");
|
||||
}
|
||||
37
kernel/feature.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef __KSU_H_FEATURE
|
||||
#define __KSU_H_FEATURE
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
enum ksu_feature_id {
|
||||
KSU_FEATURE_SU_COMPAT = 0,
|
||||
KSU_FEATURE_KERNEL_UMOUNT = 1,
|
||||
KSU_FEATURE_ENHANCED_SECURITY = 2,
|
||||
KSU_FEATURE_SULOG = 3,
|
||||
|
||||
KSU_FEATURE_MAX
|
||||
};
|
||||
|
||||
typedef int (*ksu_feature_get_t)(u64 *value);
|
||||
typedef int (*ksu_feature_set_t)(u64 value);
|
||||
|
||||
struct ksu_feature_handler {
|
||||
u32 feature_id;
|
||||
const char *name;
|
||||
ksu_feature_get_t get_handler;
|
||||
ksu_feature_set_t set_handler;
|
||||
};
|
||||
|
||||
int ksu_register_feature_handler(const struct ksu_feature_handler *handler);
|
||||
|
||||
int ksu_unregister_feature_handler(u32 feature_id);
|
||||
|
||||
int ksu_get_feature(u32 feature_id, u64 *value, bool *supported);
|
||||
|
||||
int ksu_set_feature(u32 feature_id, u64 value);
|
||||
|
||||
void ksu_feature_init(void);
|
||||
|
||||
void ksu_feature_exit(void);
|
||||
|
||||
#endif // __KSU_H_FEATURE
|
||||
341
kernel/file_wrapper.c
Normal file
@@ -0,0 +1,341 @@
|
||||
#include <linux/export.h>
|
||||
#include <linux/anon_inodes.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "selinux/selinux.h"
|
||||
|
||||
#include "file_wrapper.h"
|
||||
|
||||
static loff_t ksu_wrapper_llseek(struct file *fp, loff_t off, int flags) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->llseek(data->orig, off, flags);
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_read(struct file *fp, char __user *ptr, size_t sz, loff_t *off) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->read(orig, ptr, sz, off);
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_write(struct file *fp, const char __user *ptr, size_t sz, loff_t *off) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->write(orig, ptr, sz, off);
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_read_iter(struct kiocb *iocb, struct iov_iter *iovi) {
|
||||
struct ksu_file_wrapper* data = iocb->ki_filp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
iocb->ki_filp = orig;
|
||||
return orig->f_op->read_iter(iocb, iovi);
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_write_iter(struct kiocb *iocb, struct iov_iter *iovi) {
|
||||
struct ksu_file_wrapper* data = iocb->ki_filp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
iocb->ki_filp = orig;
|
||||
return orig->f_op->write_iter(iocb, iovi);
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
|
||||
static int ksu_wrapper_iopoll(struct kiocb *kiocb, struct io_comp_batch* icb, unsigned int v) {
|
||||
struct ksu_file_wrapper* data = kiocb->ki_filp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
kiocb->ki_filp = orig;
|
||||
return orig->f_op->iopoll(kiocb, icb, v);
|
||||
}
|
||||
#else
|
||||
static int ksu_wrapper_iopoll(struct kiocb *kiocb, bool spin) {
|
||||
struct ksu_file_wrapper* data = kiocb->ki_filp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
kiocb->ki_filp = orig;
|
||||
return orig->f_op->iopoll(kiocb, spin);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
static int ksu_wrapper_iterate (struct file *fp, struct dir_context *dc) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->iterate(orig, dc);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ksu_wrapper_iterate_shared(struct file *fp, struct dir_context *dc) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->iterate_shared(orig, dc);
|
||||
}
|
||||
|
||||
static __poll_t ksu_wrapper_poll(struct file *fp, struct poll_table_struct *pts) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->poll(orig, pts);
|
||||
}
|
||||
|
||||
static long ksu_wrapper_unlocked_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->unlocked_ioctl(orig, cmd, arg);
|
||||
}
|
||||
|
||||
static long ksu_wrapper_compat_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->compat_ioctl(orig, cmd, arg);
|
||||
}
|
||||
|
||||
static int ksu_wrapper_mmap(struct file *fp, struct vm_area_struct * vma) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->mmap(orig, vma);
|
||||
}
|
||||
|
||||
// static unsigned long mmap_supported_flags {}
|
||||
|
||||
static int ksu_wrapper_open(struct inode *ino, struct file *fp) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
struct inode *orig_ino = file_inode(orig);
|
||||
return orig->f_op->open(orig_ino, orig);
|
||||
}
|
||||
|
||||
static int ksu_wrapper_flush(struct file *fp, fl_owner_t id) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->flush(orig, id);
|
||||
}
|
||||
|
||||
|
||||
static int ksu_wrapper_fsync(struct file *fp, loff_t off1, loff_t off2, int datasync) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->fsync(orig, off1, off2, datasync);
|
||||
}
|
||||
|
||||
static int ksu_wrapper_fasync(int arg, struct file *fp, int arg2) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->fasync(arg, orig, arg2);
|
||||
}
|
||||
|
||||
static int ksu_wrapper_lock(struct file *fp, int arg1, struct file_lock *fl) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->lock(orig, arg1, fl);
|
||||
}
|
||||
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
static ssize_t ksu_wrapper_sendpage(struct file *fp, struct page *pg, int arg1, size_t sz, loff_t *off, int arg2) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->sendpage) {
|
||||
return orig->f_op->sendpage(orig, pg, arg1, sz, off, arg2);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static unsigned long ksu_wrapper_get_unmapped_area(struct file *fp, unsigned long arg1, unsigned long arg2, unsigned long arg3, unsigned long arg4) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->get_unmapped_area) {
|
||||
return orig->f_op->get_unmapped_area(orig, arg1, arg2, arg3, arg4);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// static int ksu_wrapper_check_flags(int arg) {}
|
||||
|
||||
static int ksu_wrapper_flock(struct file *fp, int arg1, struct file_lock *fl) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->flock) {
|
||||
return orig->f_op->flock(orig, arg1, fl);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_splice_write(struct pipe_inode_info * pii, struct file *fp, loff_t *off, size_t sz, unsigned int arg1) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->splice_write) {
|
||||
return orig->f_op->splice_write(pii, orig, off, sz, arg1);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_splice_read(struct file *fp, loff_t *off, struct pipe_inode_info *pii, size_t sz, unsigned int arg1) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->splice_read) {
|
||||
return orig->f_op->splice_read(orig, off, pii, sz, arg1);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
|
||||
void ksu_wrapper_splice_eof(struct file *fp) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->splice_eof) {
|
||||
return orig->f_op->splice_eof(orig);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
static int ksu_wrapper_setlease(struct file *fp, int arg1, struct file_lease **fl, void **p) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->setlease) {
|
||||
return orig->f_op->setlease(orig, arg1, fl, p);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
|
||||
static int ksu_wrapper_setlease(struct file *fp, int arg1, struct file_lock **fl, void **p) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->setlease) {
|
||||
return orig->f_op->setlease(orig, arg1, fl, p);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
#else
|
||||
static int ksu_wrapper_setlease(struct file *fp, long arg1, struct file_lock **fl, void **p) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->setlease) {
|
||||
return orig->f_op->setlease(orig, arg1, fl, p);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static long ksu_wrapper_fallocate(struct file *fp, int mode, loff_t offset, loff_t len) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->fallocate) {
|
||||
return orig->f_op->fallocate(orig, mode, offset, len);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void ksu_wrapper_show_fdinfo(struct seq_file *m, struct file *f) {
|
||||
struct ksu_file_wrapper* data = f->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->show_fdinfo) {
|
||||
orig->f_op->show_fdinfo(m, orig);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_copy_file_range(struct file *f1, loff_t off1, struct file *f2,
|
||||
loff_t off2, size_t sz, unsigned int flags) {
|
||||
// TODO: determine which file to use
|
||||
struct ksu_file_wrapper* data = f1->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->copy_file_range) {
|
||||
return orig->f_op->copy_file_range(orig, off1, f2, off2, sz, flags);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static loff_t ksu_wrapper_remap_file_range(struct file *file_in, loff_t pos_in,
|
||||
struct file *file_out, loff_t pos_out,
|
||||
loff_t len, unsigned int remap_flags) {
|
||||
// TODO: determine which file to use
|
||||
struct ksu_file_wrapper* data = file_in->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->remap_file_range) {
|
||||
return orig->f_op->remap_file_range(orig, pos_in, file_out, pos_out, len, remap_flags);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ksu_wrapper_fadvise(struct file *fp, loff_t off1, loff_t off2, int flags) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->fadvise) {
|
||||
return orig->f_op->fadvise(orig, off1, off2, flags);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ksu_wrapper_release(struct inode *inode, struct file *filp) {
|
||||
ksu_delete_file_wrapper(filp->private_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ksu_file_wrapper* ksu_create_file_wrapper(struct file* fp) {
|
||||
struct ksu_file_wrapper* p = kcalloc(sizeof(struct ksu_file_wrapper), 1, GFP_KERNEL);
|
||||
if (!p) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
get_file(fp);
|
||||
|
||||
p->orig = fp;
|
||||
p->ops.owner = THIS_MODULE;
|
||||
p->ops.llseek = fp->f_op->llseek ? ksu_wrapper_llseek : NULL;
|
||||
p->ops.read = fp->f_op->read ? ksu_wrapper_read : NULL;
|
||||
p->ops.write = fp->f_op->write ? ksu_wrapper_write : NULL;
|
||||
p->ops.read_iter = fp->f_op->read_iter ? ksu_wrapper_read_iter : NULL;
|
||||
p->ops.write_iter = fp->f_op->write_iter ? ksu_wrapper_write_iter : NULL;
|
||||
p->ops.iopoll = fp->f_op->iopoll ? ksu_wrapper_iopoll : NULL;
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
p->ops.iterate = fp->f_op->iterate ? ksu_wrapper_iterate : NULL;
|
||||
#endif
|
||||
p->ops.iterate_shared = fp->f_op->iterate_shared ? ksu_wrapper_iterate_shared : NULL;
|
||||
p->ops.poll = fp->f_op->poll ? ksu_wrapper_poll : NULL;
|
||||
p->ops.unlocked_ioctl = fp->f_op->unlocked_ioctl ? ksu_wrapper_unlocked_ioctl : NULL;
|
||||
p->ops.compat_ioctl = fp->f_op->compat_ioctl ? ksu_wrapper_compat_ioctl : NULL;
|
||||
p->ops.mmap = fp->f_op->mmap ? ksu_wrapper_mmap : NULL;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
p->ops.fop_flags = fp->f_op->fop_flags;
|
||||
#else
|
||||
p->ops.mmap_supported_flags = fp->f_op->mmap_supported_flags;
|
||||
#endif
|
||||
p->ops.open = fp->f_op->open ? ksu_wrapper_open : NULL;
|
||||
p->ops.flush = fp->f_op->flush ? ksu_wrapper_flush : NULL;
|
||||
p->ops.release = ksu_wrapper_release;
|
||||
p->ops.fsync = fp->f_op->fsync ? ksu_wrapper_fsync : NULL;
|
||||
p->ops.fasync = fp->f_op->fasync ? ksu_wrapper_fasync : NULL;
|
||||
p->ops.lock = fp->f_op->lock ? ksu_wrapper_lock : NULL;
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
p->ops.sendpage = fp->f_op->sendpage ? ksu_wrapper_sendpage : NULL;
|
||||
#endif
|
||||
p->ops.get_unmapped_area = fp->f_op->get_unmapped_area ? ksu_wrapper_get_unmapped_area : NULL;
|
||||
p->ops.check_flags = fp->f_op->check_flags;
|
||||
p->ops.flock = fp->f_op->flock ? ksu_wrapper_flock : NULL;
|
||||
p->ops.splice_write = fp->f_op->splice_write ? ksu_wrapper_splice_write : NULL;
|
||||
p->ops.splice_read = fp->f_op->splice_read ? ksu_wrapper_splice_read : NULL;
|
||||
p->ops.setlease = fp->f_op->setlease ? ksu_wrapper_setlease : NULL;
|
||||
p->ops.fallocate = fp->f_op->fallocate ? ksu_wrapper_fallocate : NULL;
|
||||
p->ops.show_fdinfo = fp->f_op->show_fdinfo ? ksu_wrapper_show_fdinfo : NULL;
|
||||
p->ops.copy_file_range = fp->f_op->copy_file_range ? ksu_wrapper_copy_file_range : NULL;
|
||||
p->ops.remap_file_range = fp->f_op->remap_file_range ? ksu_wrapper_remap_file_range : NULL;
|
||||
p->ops.fadvise = fp->f_op->fadvise ? ksu_wrapper_fadvise : NULL;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
|
||||
p->ops.splice_eof = fp->f_op->splice_eof ? ksu_wrapper_splice_eof : NULL;
|
||||
#endif
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void ksu_delete_file_wrapper(struct ksu_file_wrapper* data) {
|
||||
fput((struct file*) data->orig);
|
||||
kfree(data);
|
||||
}
|
||||
14
kernel/file_wrapper.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef KSU_FILE_WRAPPER_H
|
||||
#define KSU_FILE_WRAPPER_H
|
||||
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
struct ksu_file_wrapper {
|
||||
struct file* orig;
|
||||
struct file_operations ops;
|
||||
};
|
||||
|
||||
struct ksu_file_wrapper* ksu_create_file_wrapper(struct file* fp);
|
||||
void ksu_delete_file_wrapper(struct ksu_file_wrapper* data);
|
||||
#endif // KSU_FILE_WRAPPER_H
|
||||
@@ -1,28 +0,0 @@
|
||||
#ifndef __KSU_H_KSHOOK
|
||||
#define __KSU_H_KSHOOK
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
// For sucompat
|
||||
|
||||
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||
int *flags);
|
||||
|
||||
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
||||
|
||||
// For ksud
|
||||
|
||||
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
size_t *count_ptr, loff_t **pos);
|
||||
|
||||
// For ksud and sucompat
|
||||
|
||||
int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
|
||||
void *envp, int *flags);
|
||||
|
||||
// For volume button
|
||||
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||
int *value);
|
||||
|
||||
#endif
|
||||
@@ -1,94 +0,0 @@
|
||||
#include <linux/version.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
|
||||
extern struct task_struct init_task;
|
||||
|
||||
// mnt_ns context switch for environment that android_init->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns, such as WSA
|
||||
struct ksu_ns_fs_saved {
|
||||
struct nsproxy *ns;
|
||||
struct fs_struct *fs;
|
||||
};
|
||||
|
||||
static void ksu_save_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
|
||||
{
|
||||
ns_fs_saved->ns = current->nsproxy;
|
||||
ns_fs_saved->fs = current->fs;
|
||||
}
|
||||
|
||||
static void ksu_load_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
|
||||
{
|
||||
current->nsproxy = ns_fs_saved->ns;
|
||||
current->fs = ns_fs_saved->fs;
|
||||
}
|
||||
|
||||
static bool android_context_saved_checked = false;
|
||||
static bool android_context_saved_enabled = false;
|
||||
static struct ksu_ns_fs_saved android_context_saved;
|
||||
|
||||
void ksu_android_ns_fs_check()
|
||||
{
|
||||
if (android_context_saved_checked)
|
||||
return;
|
||||
android_context_saved_checked = true;
|
||||
task_lock(current);
|
||||
if (current->nsproxy && current->fs &&
|
||||
current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) {
|
||||
android_context_saved_enabled = true;
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("android context saved enabled due to init mnt_ns(%p) != android mnt_ns(%p)\n",
|
||||
current->nsproxy->mnt_ns, init_task.nsproxy->mnt_ns);
|
||||
#endif
|
||||
ksu_save_ns_fs(&android_context_saved);
|
||||
} else {
|
||||
pr_info("android context saved disabled\n");
|
||||
}
|
||||
task_unlock(current);
|
||||
}
|
||||
|
||||
struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode)
|
||||
{
|
||||
// switch mnt_ns even if current is not wq_worker, to ensure what we open is the correct file in android mnt_ns, rather than user created mnt_ns
|
||||
struct ksu_ns_fs_saved saved;
|
||||
if (android_context_saved_enabled) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("start switch current nsproxy and fs to android context\n");
|
||||
#endif
|
||||
task_lock(current);
|
||||
ksu_save_ns_fs(&saved);
|
||||
ksu_load_ns_fs(&android_context_saved);
|
||||
task_unlock(current);
|
||||
}
|
||||
struct file *fp = filp_open(filename, flags, mode);
|
||||
if (android_context_saved_enabled) {
|
||||
task_lock(current);
|
||||
ksu_load_ns_fs(&saved);
|
||||
task_unlock(current);
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("switch current nsproxy and fs back to saved successfully\n");
|
||||
#endif
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
return kernel_read(p, buf, count, pos);
|
||||
}
|
||||
|
||||
ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
return kernel_write(p, buf, count, pos);
|
||||
}
|
||||
|
||||
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
||||
long count)
|
||||
{
|
||||
return strncpy_from_user_nofault(dst, unsafe_addr, count);
|
||||
}
|
||||