Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e65012c99 | ||
|
|
91ede12d23 | ||
|
|
cd12337f2a | ||
|
|
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 |
19
.github/workflows/build-manager.yml
vendored
@@ -2,7 +2,7 @@ name: Build Manager
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "dev", "ci" ]
|
||||
branches: [ "main", "dev", "ci", "miuix" ]
|
||||
paths:
|
||||
- '.github/workflows/build-manager.yml'
|
||||
- '.github/workflows/build-lkm.yml'
|
||||
@@ -11,13 +11,14 @@ on:
|
||||
- 'userspace/ksud/**'
|
||||
- 'userspace/user_scanner/**'
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
branches: [ "main", "dev", "miuix" ]
|
||||
paths:
|
||||
- '.github/workflows/build-manager.yml'
|
||||
- '.github/workflows/build-lkm.yml'
|
||||
- 'manager/**'
|
||||
- 'kernel/**'
|
||||
- 'userspace/ksud/**'
|
||||
- 'userspace/user_scanner/**'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
@@ -81,7 +82,14 @@ jobs:
|
||||
- name: Determine manager variant for telegram bot
|
||||
id: determine
|
||||
run: |
|
||||
if [ "${{ matrix.spoofed }}" == "true" ]; then
|
||||
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
|
||||
@@ -91,7 +99,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Run randomizer
|
||||
if: ${{ matrix.spoofed == 'true' }}
|
||||
if: ${{ matrix.spoofed == 'true' && steps.determine.outputs.SKIP != 'true' }}
|
||||
run: |
|
||||
chmod +x randomizer
|
||||
./randomizer
|
||||
@@ -164,6 +172,7 @@ 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
|
||||
@@ -181,7 +190,7 @@ jobs:
|
||||
path: "manager/app/build/outputs/mapping/release/"
|
||||
|
||||
- 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 }}
|
||||
|
||||
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"
|
||||
@@ -15,6 +15,7 @@ A kernel-based root solution for Android devices, forked from [`tiann/KernelSU`]
|
||||
|
||||
1. Kernel-based `su` and root access management
|
||||
2. Module system based on [Magic Mount](https://github.com/5ec1cff/KernelSU)
|
||||
> **Note:** SukiSU now delegates all module mounting to the installed *metamodule*; the core no longer handles mount operations.
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage
|
||||
4. Support non-GKI and GKI 1.0
|
||||
5. KPM Support
|
||||
|
||||
@@ -47,7 +47,7 @@ static void remove_uid_from_arr(uid_t uid)
|
||||
if (allow_list_pointer == 0)
|
||||
return;
|
||||
|
||||
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
|
||||
temp_arr = kzalloc(sizeof(allow_list_arr), GFP_KERNEL);
|
||||
if (temp_arr == NULL) {
|
||||
pr_err("%s: unable to allocate memory\n", __func__);
|
||||
return;
|
||||
@@ -200,7 +200,7 @@ bool ksu_set_app_profile(struct app_profile *profile, bool persist)
|
||||
}
|
||||
|
||||
// not found, alloc a new node!
|
||||
p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
|
||||
p = (struct perm_data *)kzalloc(sizeof(struct perm_data), GFP_KERNEL);
|
||||
if (!p) {
|
||||
pr_err("ksu_set_app_profile alloc failed\n");
|
||||
return false;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#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);
|
||||
|
||||
@@ -41,6 +43,12 @@ static inline bool is_appuid(uid_t uid)
|
||||
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);
|
||||
|
||||
@@ -37,7 +37,7 @@ static struct sdesc *init_sdesc(struct crypto_shash *alg)
|
||||
int size;
|
||||
|
||||
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
|
||||
sdesc = kmalloc(size, GFP_KERNEL);
|
||||
sdesc = kzalloc(size, GFP_KERNEL);
|
||||
if (!sdesc)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
sdesc->shash.tfm = alg;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "selinux/selinux.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "sucompat.h"
|
||||
|
||||
#include "sulog.h"
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 7, 0)
|
||||
|
||||
@@ -7,6 +7,7 @@ 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
|
||||
};
|
||||
|
||||
@@ -43,24 +43,6 @@ static const struct ksu_feature_handler kernel_umount_handler = {
|
||||
.set_handler = kernel_umount_feature_set,
|
||||
};
|
||||
|
||||
static bool should_umount(struct path *path)
|
||||
{
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) {
|
||||
pr_info("ignore global mnt namespace process: %d\n", current_uid().val);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) {
|
||||
const char *fstype = path->mnt->mnt_sb->s_type->name;
|
||||
return strcmp(fstype, "overlay") == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
extern int path_umount(struct path *path, int flags);
|
||||
|
||||
static void ksu_umount_mnt(struct path *path, int flags)
|
||||
@@ -71,7 +53,7 @@ static void ksu_umount_mnt(struct path *path, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
void try_umount(const char *mnt, bool check_mnt, int flags)
|
||||
void try_umount(const char *mnt, int flags)
|
||||
{
|
||||
struct path path;
|
||||
int err = kern_path(mnt, 0, &path);
|
||||
@@ -85,12 +67,6 @@ void try_umount(const char *mnt, bool check_mnt, int flags)
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interest in some specific mounts
|
||||
if (check_mnt && !should_umount(&path)) {
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
ksu_umount_mnt(&path, flags);
|
||||
}
|
||||
|
||||
@@ -107,8 +83,14 @@ static void umount_tw_func(struct callback_head *cb)
|
||||
saved = override_creds(tw->old_cred);
|
||||
}
|
||||
|
||||
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
|
||||
// filter the mountpoint whose target is `/data/adb`
|
||||
struct mount_entry *entry;
|
||||
down_read(&mount_list_lock);
|
||||
list_for_each_entry(entry, &mount_list, list) {
|
||||
pr_info("%s: unmounting: %s flags 0x%x\n", __func__, entry->umountable, entry->flags);
|
||||
try_umount(entry->umountable, entry->flags);
|
||||
}
|
||||
up_read(&mount_list_lock);
|
||||
|
||||
ksu_umount_manager_execute_all(tw->old_cred);
|
||||
|
||||
if (saved)
|
||||
@@ -124,7 +106,7 @@ int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
|
||||
{
|
||||
struct umount_tw *tw;
|
||||
|
||||
// this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it!
|
||||
// if there isn't any module mounted, just ignore it!
|
||||
if (!ksu_module_mounted) {
|
||||
return 0;
|
||||
}
|
||||
@@ -133,18 +115,24 @@ int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// FIXME: isolated process which directly forks from zygote is not handled
|
||||
if (!is_appuid(new_uid)) {
|
||||
// There are 5 scenarios:
|
||||
// 1. Normal app: zygote -> appuid
|
||||
// 2. Isolated process forked from zygote: zygote -> isolated_process
|
||||
// 3. App zygote forked from zygote: zygote -> appuid
|
||||
// 4. Isolated process froked from app zygote: appuid -> isolated_process (already handled by 3)
|
||||
// 5. Isolated process froked from webview zygote (no need to handle, app cannot run custom code)
|
||||
if (!is_appuid(new_uid) && !is_isolated_process(new_uid)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ksu_uid_should_umount(new_uid)) {
|
||||
if (!ksu_uid_should_umount(new_uid) && !is_isolated_process(new_uid)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// check old process's selinux context, if it is not zygote, ignore it!
|
||||
// because some su apps may setuid to untrusted_app but they are in global mount namespace
|
||||
// when we umount for such process, that is a disaster!
|
||||
// also handle case 4 and 5
|
||||
bool is_zygote_child = is_zygote(get_current_cred());
|
||||
if (!is_zygote_child) {
|
||||
pr_info("handle umount ignore non zygote child: %d\n", current->pid);
|
||||
@@ -156,7 +144,7 @@ int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
|
||||
// umount the target mnt
|
||||
pr_info("handle umount for uid: %d, pid: %d\n", new_uid, current->pid);
|
||||
|
||||
tw = kmalloc(sizeof(*tw), GFP_ATOMIC);
|
||||
tw = kzalloc(sizeof(*tw), GFP_ATOMIC);
|
||||
if (!tw)
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -2,13 +2,24 @@
|
||||
#define __KSU_H_KERNEL_UMOUNT
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/rwsem.h>
|
||||
|
||||
void ksu_kernel_umount_init(void);
|
||||
void ksu_kernel_umount_exit(void);
|
||||
|
||||
void try_umount(const char *mnt, bool check_mnt, int flags);
|
||||
void try_umount(const char *mnt, int flags);
|
||||
|
||||
// Handler function to be called from setresuid hook
|
||||
int ksu_handle_umount(uid_t old_uid, uid_t new_uid);
|
||||
|
||||
#endif
|
||||
// for the umount list
|
||||
struct mount_entry {
|
||||
char *umountable;
|
||||
unsigned int flags;
|
||||
struct list_head list;
|
||||
};
|
||||
extern struct list_head mount_list;
|
||||
extern struct rw_semaphore mount_list_lock;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -92,14 +92,14 @@ void on_post_fs_data(void)
|
||||
}
|
||||
|
||||
extern void ext4_unregister_sysfs(struct super_block *sb);
|
||||
static void nuke_ext4_sysfs(void)
|
||||
int nuke_ext4_sysfs(const char* mnt)
|
||||
{
|
||||
#ifdef CONFIG_EXT4_FS
|
||||
struct path path;
|
||||
int err = kern_path("/data/adb/modules", 0, &path);
|
||||
int err = kern_path(mnt, 0, &path);
|
||||
if (err) {
|
||||
pr_err("nuke path err: %d\n", err);
|
||||
return;
|
||||
return err;
|
||||
}
|
||||
|
||||
struct super_block *sb = path.dentry->d_inode->i_sb;
|
||||
@@ -107,18 +107,19 @@ static void nuke_ext4_sysfs(void)
|
||||
if (strcmp(name, "ext4") != 0) {
|
||||
pr_info("nuke but module aren't mounted\n");
|
||||
path_put(&path);
|
||||
return;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ext4_unregister_sysfs(sb);
|
||||
path_put(&path);
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void on_module_mounted(void){
|
||||
pr_info("on_module_mounted!\n");
|
||||
ksu_module_mounted = true;
|
||||
nuke_ext4_sysfs();
|
||||
}
|
||||
|
||||
void on_boot_completed(void){
|
||||
|
||||
@@ -12,6 +12,8 @@ void on_boot_completed(void);
|
||||
|
||||
bool ksu_is_safe_mode(void);
|
||||
|
||||
int nuke_ext4_sysfs(const char* mnt);
|
||||
|
||||
extern u32 ksu_file_sid;
|
||||
extern bool ksu_module_mounted;
|
||||
extern bool ksu_boot_completed;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/binfmts.h>
|
||||
|
||||
#include "manual_su.h"
|
||||
#include "ksu.h"
|
||||
#include "allowlist.h"
|
||||
@@ -49,7 +50,7 @@ static char* get_token_from_envp(void)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
env_copy = kmalloc(env_len + 1, GFP_KERNEL);
|
||||
env_copy = kzalloc(env_len + 1, GFP_KERNEL);
|
||||
if (!env_copy) {
|
||||
up_read(&mm->mmap_lock);
|
||||
return NULL;
|
||||
@@ -72,7 +73,7 @@ static char* get_token_from_envp(void)
|
||||
char *token_end = strchr(token_start, '\0');
|
||||
|
||||
if (token_end && (token_end - token_start) == KSU_TOKEN_LENGTH) {
|
||||
token = kmalloc(KSU_TOKEN_LENGTH + 1, GFP_KERNEL);
|
||||
token = kzalloc(KSU_TOKEN_LENGTH + 1, GFP_KERNEL);
|
||||
if (token) {
|
||||
memcpy(token, token_start, KSU_TOKEN_LENGTH);
|
||||
token[KSU_TOKEN_LENGTH] = '\0';
|
||||
|
||||
@@ -354,7 +354,7 @@ static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
|
||||
|
||||
if (datum->u.xperms == NULL) {
|
||||
datum->u.xperms =
|
||||
(struct avtab_extended_perms *)(kmalloc(
|
||||
(struct avtab_extended_perms *)(kzalloc(
|
||||
sizeof(xperms), GFP_KERNEL));
|
||||
if (!datum->u.xperms) {
|
||||
pr_err("alloc xperms failed\n");
|
||||
@@ -548,7 +548,7 @@ static bool add_filename_trans(struct policydb *db, const char *s,
|
||||
trans = (struct filename_trans_datum *)kcalloc(1 ,sizeof(*trans),
|
||||
GFP_ATOMIC);
|
||||
struct filename_trans_key *new_key =
|
||||
(struct filename_trans_key *)kmalloc(sizeof(*new_key),
|
||||
(struct filename_trans_key *)kzalloc(sizeof(*new_key),
|
||||
GFP_ATOMIC);
|
||||
*new_key = key;
|
||||
new_key->name = kstrdup(key.name, GFP_ATOMIC);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "app_profile.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
|
||||
|
||||
#include "sulog.h"
|
||||
|
||||
#define SU_PATH "/system/bin/su"
|
||||
@@ -108,20 +107,6 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
|
||||
char path[sizeof(su) + 1];
|
||||
memset(path, 0, sizeof(path));
|
||||
// Remove this later!! we use syscall hook, so this will never happen!!!!!
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) && 0
|
||||
// it becomes a `struct filename *` after 5.18
|
||||
// https://elixir.bootlin.com/linux/v5.18/source/fs/stat.c#L216
|
||||
const char sh[] = SH_PATH;
|
||||
struct filename *filename = *((struct filename **)filename_user);
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
if (likely(memcmp(filename->name, su, sizeof(su))))
|
||||
return 0;
|
||||
pr_info("vfs_statx su->sh!\n");
|
||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||
#else
|
||||
strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
|
||||
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
||||
@@ -131,7 +116,6 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
pr_info("newfstatat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include "klog.h"
|
||||
|
||||
#include "sulog.h"
|
||||
#include "ksu.h"
|
||||
#include "feature.h"
|
||||
|
||||
#if __SULOG_GATE
|
||||
|
||||
@@ -24,7 +26,28 @@ static DEFINE_SPINLOCK(dedup_lock);
|
||||
static LIST_HEAD(sulog_queue);
|
||||
static struct workqueue_struct *sulog_workqueue;
|
||||
static struct work_struct sulog_work;
|
||||
static bool sulog_enabled = true;
|
||||
static bool sulog_enabled __read_mostly = true;
|
||||
|
||||
static int sulog_feature_get(u64 *value)
|
||||
{
|
||||
*value = sulog_enabled ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sulog_feature_set(u64 value)
|
||||
{
|
||||
bool enable = value != 0;
|
||||
sulog_enabled = enable;
|
||||
pr_info("sulog: set to %d\n", enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ksu_feature_handler sulog_handler = {
|
||||
.feature_id = KSU_FEATURE_SULOG,
|
||||
.name = "sulog",
|
||||
.get_handler = sulog_feature_get,
|
||||
.set_handler = sulog_feature_set,
|
||||
};
|
||||
|
||||
static void get_timestamp(char *buf, size_t len)
|
||||
{
|
||||
@@ -180,7 +203,7 @@ static void sulog_add_entry(char *log_buf, size_t len, uid_t uid, u8 dedup_type)
|
||||
if (!dedup_should_print(uid, dedup_type, log_buf, len))
|
||||
return;
|
||||
|
||||
entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
|
||||
entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
@@ -303,6 +326,10 @@ void ksu_sulog_report_syscall(uid_t uid, const char *comm, const char *syscall,
|
||||
|
||||
int ksu_sulog_init(void)
|
||||
{
|
||||
if (ksu_register_feature_handler(&sulog_handler)) {
|
||||
pr_err("Failed to register sulog feature handler\n");
|
||||
}
|
||||
|
||||
sulog_workqueue = alloc_workqueue("ksu_sulog", WQ_UNBOUND | WQ_HIGHPRI, 1);
|
||||
if (!sulog_workqueue) {
|
||||
pr_err("sulog: failed to create workqueue\n");
|
||||
@@ -319,6 +346,8 @@ void ksu_sulog_exit(void)
|
||||
struct sulog_entry *entry, *tmp;
|
||||
unsigned long flags;
|
||||
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_SULOG);
|
||||
|
||||
sulog_enabled = false;
|
||||
|
||||
if (sulog_workqueue) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
extern struct timezone sys_tz;
|
||||
|
||||
#define SULOG_PATH "/data/adb/ksu/log/sulog.log"
|
||||
#define SULOG_MAX_SIZE (128 * 1024 * 1024) // 128MB
|
||||
#define SULOG_MAX_SIZE (32 * 1024 * 1024) // 128MB
|
||||
#define SULOG_ENTRY_MAX_LEN 512
|
||||
#define SULOG_COMM_LEN 256
|
||||
#define DEDUP_SECS 10
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#include "supercalls.h"
|
||||
|
||||
#include <linux/anon_inodes.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
@@ -14,13 +12,14 @@
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "supercalls.h"
|
||||
#include "arch.h"
|
||||
#include "allowlist.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksud.h"
|
||||
#include "kernel_umount.h"
|
||||
#include "manager.h"
|
||||
#include "sulog.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "objsec.h"
|
||||
#include "file_wrapper.h"
|
||||
@@ -29,6 +28,7 @@
|
||||
#include "dynamic_manager.h"
|
||||
#include "umount_manager.h"
|
||||
|
||||
#include "sulog.h"
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
#include "manual_su.h"
|
||||
#endif
|
||||
@@ -481,6 +481,141 @@ static int do_manage_mark(void __user *arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_nuke_ext4_sysfs(void __user *arg)
|
||||
{
|
||||
struct ksu_nuke_ext4_sysfs_cmd cmd;
|
||||
char mnt[256];
|
||||
long ret;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd)))
|
||||
return -EFAULT;
|
||||
|
||||
if (!cmd.arg)
|
||||
return -EINVAL;
|
||||
|
||||
memset(mnt, 0, sizeof(mnt));
|
||||
|
||||
ret = strncpy_from_user(mnt, cmd.arg, sizeof(mnt));
|
||||
if (ret < 0) {
|
||||
pr_err("nuke ext4 copy mnt failed: %ld\\n", ret);
|
||||
return -EFAULT; // 或者 return ret;
|
||||
}
|
||||
|
||||
if (ret == sizeof(mnt)) {
|
||||
pr_err("nuke ext4 mnt path too long\\n");
|
||||
return -ENAMETOOLONG;
|
||||
}
|
||||
|
||||
pr_info("do_nuke_ext4_sysfs: %s\n", mnt);
|
||||
|
||||
return nuke_ext4_sysfs(mnt);
|
||||
}
|
||||
|
||||
struct list_head mount_list = LIST_HEAD_INIT(mount_list);
|
||||
DECLARE_RWSEM(mount_list_lock);
|
||||
|
||||
static int add_try_umount(void __user *arg)
|
||||
{
|
||||
struct mount_entry *new_entry, *entry, *tmp;
|
||||
struct ksu_add_try_umount_cmd cmd;
|
||||
char buf[256] = {0};
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof cmd))
|
||||
return -EFAULT;
|
||||
|
||||
switch (cmd.mode) {
|
||||
case KSU_UMOUNT_WIPE: {
|
||||
struct mount_entry *entry, *tmp;
|
||||
down_write(&mount_list_lock);
|
||||
list_for_each_entry_safe(entry, tmp, &mount_list, list) {
|
||||
pr_info("wipe_umount_list: removing entry: %s\n", entry->umountable);
|
||||
list_del(&entry->list);
|
||||
kfree(entry->umountable);
|
||||
kfree(entry);
|
||||
}
|
||||
up_write(&mount_list_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case KSU_UMOUNT_ADD: {
|
||||
long len = strncpy_from_user(buf, (const char __user *)cmd.arg, 256);
|
||||
if (len <= 0)
|
||||
return -EFAULT;
|
||||
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
|
||||
if (!new_entry)
|
||||
return -ENOMEM;
|
||||
|
||||
new_entry->umountable = kstrdup(buf, GFP_KERNEL);
|
||||
if (!new_entry->umountable) {
|
||||
kfree(new_entry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
down_write(&mount_list_lock);
|
||||
|
||||
// disallow dupes
|
||||
// if this gets too many, we can consider moving this whole task to a kthread
|
||||
list_for_each_entry(entry, &mount_list, list) {
|
||||
if (!strcmp(entry->umountable, buf)) {
|
||||
pr_info("cmd_add_try_umount: %s is already here!\n", buf);
|
||||
up_write(&mount_list_lock);
|
||||
kfree(new_entry->umountable);
|
||||
kfree(new_entry);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// now check flags and add
|
||||
// this also serves as a null check
|
||||
if (cmd.flags)
|
||||
new_entry->flags = cmd.flags;
|
||||
else
|
||||
new_entry->flags = 0;
|
||||
|
||||
// debug
|
||||
list_add(&new_entry->list, &mount_list);
|
||||
up_write(&mount_list_lock);
|
||||
pr_info("cmd_add_try_umount: %s added!\n", buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// this is just strcmp'd wipe anyway
|
||||
case KSU_UMOUNT_DEL: {
|
||||
long len = strncpy_from_user(buf, (const char __user *)cmd.arg, sizeof(buf) - 1);
|
||||
if (len <= 0)
|
||||
return -EFAULT;
|
||||
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
down_write(&mount_list_lock);
|
||||
list_for_each_entry_safe(entry, tmp, &mount_list, list) {
|
||||
if (!strcmp(entry->umountable, buf)) {
|
||||
pr_info("cmd_add_try_umount: entry removed: %s\n", entry->umountable);
|
||||
list_del(&entry->list);
|
||||
kfree(entry->umountable);
|
||||
kfree(entry);
|
||||
}
|
||||
}
|
||||
up_write(&mount_list_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
default: {
|
||||
pr_err("cmd_add_try_umount: invalid operation %u\n", cmd.mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
} // switch(cmd.mode)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 100. GET_FULL_VERSION - Get full version string
|
||||
static int do_get_full_version(void __user *arg)
|
||||
{
|
||||
@@ -692,7 +827,7 @@ static int do_umount_manager(void __user *arg)
|
||||
|
||||
switch (cmd.operation) {
|
||||
case UMOUNT_OP_ADD: {
|
||||
return ksu_umount_manager_add(cmd.path, cmd.check_mnt, cmd.flags, false);
|
||||
return ksu_umount_manager_add(cmd.path, cmd.flags, false);
|
||||
}
|
||||
case UMOUNT_OP_REMOVE: {
|
||||
return ksu_umount_manager_remove(cmd.path);
|
||||
@@ -728,6 +863,8 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
|
||||
{ .cmd = KSU_IOCTL_SET_FEATURE, .name = "SET_FEATURE", .handler = do_set_feature, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_GET_WRAPPER_FD, .name = "GET_WRAPPER_FD", .handler = do_get_wrapper_fd, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_MANAGE_MARK, .name = "MANAGE_MARK", .handler = do_manage_mark, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_NUKE_EXT4_SYSFS, .name = "NUKE_EXT4_SYSFS", .handler = do_nuke_ext4_sysfs, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_ADD_TRY_UMOUNT, .name = "ADD_TRY_UMOUNT", .handler = add_try_umount, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_GET_FULL_VERSION,.name = "GET_FULL_VERSION", .handler = do_get_full_version, .perm_check = always_allow},
|
||||
{ .cmd = KSU_IOCTL_HOOK_TYPE,.name = "GET_HOOK_TYPE", .handler = do_get_hook_type, .perm_check = manager_or_root},
|
||||
{ .cmd = KSU_IOCTL_ENABLE_KPM, .name = "GET_ENABLE_KPM", .handler = do_enable_kpm, .perm_check = manager_or_root},
|
||||
|
||||
@@ -94,6 +94,21 @@ struct ksu_manage_mark_cmd {
|
||||
#define KSU_MARK_UNMARK 3
|
||||
#define KSU_MARK_REFRESH 4
|
||||
|
||||
struct ksu_nuke_ext4_sysfs_cmd {
|
||||
__aligned_u64 arg; // Input: mnt pointer
|
||||
};
|
||||
|
||||
struct ksu_add_try_umount_cmd {
|
||||
__aligned_u64 arg; // char ptr, this is the mountpoint
|
||||
__u32 flags; // this is the flag we use for it
|
||||
__u8 mode; // denotes what to do with it 0:wipe_list 1:add_to_list 2:delete_entry
|
||||
};
|
||||
|
||||
#define KSU_UMOUNT_WIPE 0 // ignore everything and wipe list
|
||||
#define KSU_UMOUNT_ADD 1 // add entry (path + flags)
|
||||
#define KSU_UMOUNT_DEL 2 // delete entry, strcmp
|
||||
|
||||
|
||||
// Other command structures
|
||||
struct ksu_get_full_version_cmd {
|
||||
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
|
||||
@@ -147,6 +162,8 @@ struct ksu_manual_su_cmd {
|
||||
#define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 0)
|
||||
#define KSU_IOCTL_GET_WRAPPER_FD _IOC(_IOC_WRITE, 'K', 15, 0)
|
||||
#define KSU_IOCTL_MANAGE_MARK _IOC(_IOC_READ|_IOC_WRITE, 'K', 16, 0)
|
||||
#define KSU_IOCTL_NUKE_EXT4_SYSFS _IOC(_IOC_WRITE, 'K', 17, 0)
|
||||
#define KSU_IOCTL_ADD_TRY_UMOUNT _IOC(_IOC_WRITE, 'K', 18, 0)
|
||||
// Other IOCTL command definitions
|
||||
#define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0)
|
||||
#define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0)
|
||||
|
||||
@@ -268,7 +268,7 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||
|
||||
if (d_type == DT_DIR && my_ctx->depth > 0 &&
|
||||
(my_ctx->stop && !*my_ctx->stop)) {
|
||||
struct data_path *data = kmalloc(sizeof(struct data_path), GFP_ATOMIC);
|
||||
struct data_path *data = kzalloc(sizeof(struct data_path), GFP_ATOMIC);
|
||||
|
||||
if (!data) {
|
||||
pr_err("Failed to allocate memory for %s\n", dirpath);
|
||||
@@ -303,29 +303,24 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||
// Check for dynamic sign or multi-manager signatures
|
||||
if (is_multi_manager && (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2)) {
|
||||
crown_manager(dirpath, my_ctx->private_data, signature_index);
|
||||
|
||||
struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC);
|
||||
if (apk_data) {
|
||||
apk_data->hash = hash;
|
||||
apk_data->exists = true;
|
||||
list_add_tail(&apk_data->list, &apk_path_hash_list);
|
||||
}
|
||||
} else if (is_manager_apk(dirpath)) {
|
||||
crown_manager(dirpath, my_ctx->private_data, 0);
|
||||
*my_ctx->stop = 1;
|
||||
}
|
||||
|
||||
struct apk_path_hash *apk_data = kzalloc(sizeof(*apk_data), GFP_ATOMIC);
|
||||
if (apk_data) {
|
||||
apk_data->hash = hash;
|
||||
apk_data->exists = true;
|
||||
list_add_tail(&apk_data->list, &apk_path_hash_list);
|
||||
}
|
||||
|
||||
if (is_manager_apk(dirpath)) {
|
||||
// Manager found, clear APK cache list
|
||||
list_for_each_entry_safe (pos, n, &apk_path_hash_list, list) {
|
||||
list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) {
|
||||
list_del(&pos->list);
|
||||
kfree(pos);
|
||||
}
|
||||
} else {
|
||||
struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC);
|
||||
if (apk_data) {
|
||||
apk_data->hash = hash;
|
||||
apk_data->exists = true;
|
||||
list_add_tail(&apk_data->list, &apk_path_hash_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
|
||||
static struct umount_manager g_umount_mgr = {
|
||||
.entry_count = 0,
|
||||
.max_entries = 64,
|
||||
.max_entries = 512,
|
||||
};
|
||||
|
||||
static void try_umount_path(struct umount_entry *entry)
|
||||
{
|
||||
try_umount(entry->path, entry->check_mnt, entry->flags);
|
||||
try_umount(entry->path, entry->flags);
|
||||
}
|
||||
|
||||
static struct umount_entry *find_entry_locked(const char *path)
|
||||
@@ -33,37 +33,123 @@ static struct umount_entry *find_entry_locked(const char *path)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int init_default_entries(void)
|
||||
static bool is_path_in_mount_list(const char *path)
|
||||
{
|
||||
int ret;
|
||||
struct mount_entry *entry;
|
||||
bool found = false;
|
||||
|
||||
const struct {
|
||||
const char *path;
|
||||
bool check_mnt;
|
||||
int flags;
|
||||
} defaults[] = {
|
||||
{ "/odm", true, 0 },
|
||||
{ "/system", true, 0 },
|
||||
{ "/vendor", true, 0 },
|
||||
{ "/product", true, 0 },
|
||||
{ "/system_ext", true, 0 },
|
||||
{ "/data/adb/modules", false, MNT_DETACH },
|
||||
{ "/debug_ramdisk", false, MNT_DETACH },
|
||||
};
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(defaults); i++) {
|
||||
ret = ksu_umount_manager_add(defaults[i].path,
|
||||
defaults[i].check_mnt,
|
||||
defaults[i].flags,
|
||||
true); // is_default = true
|
||||
if (ret) {
|
||||
pr_err("Failed to add default entry: %s, ret=%d\n",
|
||||
defaults[i].path, ret);
|
||||
return ret;
|
||||
down_read(&mount_list_lock);
|
||||
list_for_each_entry(entry, &mount_list, list) {
|
||||
if (entry->umountable && strcmp(entry->umountable, path) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
up_read(&mount_list_lock);
|
||||
|
||||
pr_info("Initialized %zu default umount entries\n", ARRAY_SIZE(defaults));
|
||||
return found;
|
||||
}
|
||||
|
||||
static int copy_mount_entry_to_user(struct ksu_umount_entry_info __user *entries,
|
||||
u32 idx, const char *path, int flags)
|
||||
{
|
||||
struct ksu_umount_entry_info info;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
strncpy(info.path, path, sizeof(info.path) - 1);
|
||||
info.path[sizeof(info.path) - 1] = '\0';
|
||||
info.flags = flags;
|
||||
info.is_default = 1;
|
||||
info.state = UMOUNT_STATE_IDLE;
|
||||
info.ref_count = 0;
|
||||
|
||||
if (copy_to_user(&entries[idx], &info, sizeof(info))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int copy_umount_entry_to_user(struct ksu_umount_entry_info __user *entries,
|
||||
u32 idx, struct umount_entry *entry)
|
||||
{
|
||||
struct ksu_umount_entry_info info;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
strncpy(info.path, entry->path, sizeof(info.path) - 1);
|
||||
info.path[sizeof(info.path) - 1] = '\0';
|
||||
info.flags = entry->flags;
|
||||
info.is_default = entry->is_default;
|
||||
info.state = entry->state;
|
||||
info.ref_count = entry->ref_count;
|
||||
|
||||
if (copy_to_user(&entries[idx], &info, sizeof(info))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int collect_mount_list_entries(struct ksu_umount_entry_info __user *entries,
|
||||
u32 max_count, u32 *out_idx)
|
||||
{
|
||||
struct mount_entry *mount_entry;
|
||||
u32 idx = 0;
|
||||
|
||||
down_read(&mount_list_lock);
|
||||
list_for_each_entry(mount_entry, &mount_list, list) {
|
||||
if (idx >= max_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!mount_entry->umountable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (copy_mount_entry_to_user(entries, idx, mount_entry->umountable,
|
||||
mount_entry->flags)) {
|
||||
up_read(&mount_list_lock);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
up_read(&mount_list_lock);
|
||||
|
||||
*out_idx = idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int collect_umount_manager_entries(struct ksu_umount_entry_info __user *entries,
|
||||
u32 start_idx, u32 max_count, u32 *out_idx)
|
||||
{
|
||||
struct umount_entry *entry;
|
||||
unsigned long flags;
|
||||
u32 idx = start_idx;
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
|
||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
||||
if (idx >= max_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_path_in_mount_list(entry->path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
|
||||
if (copy_umount_entry_to_user(entries, idx, entry)) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
idx++;
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
*out_idx = idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -72,7 +158,7 @@ int ksu_umount_manager_init(void)
|
||||
INIT_LIST_HEAD(&g_umount_mgr.entry_list);
|
||||
spin_lock_init(&g_umount_mgr.lock);
|
||||
|
||||
return init_default_entries();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_umount_manager_exit(void)
|
||||
@@ -93,7 +179,7 @@ void ksu_umount_manager_exit(void)
|
||||
pr_info("Umount manager cleaned up\n");
|
||||
}
|
||||
|
||||
int ksu_umount_manager_add(const char *path, bool check_mnt, int flags, bool is_default)
|
||||
int ksu_umount_manager_add(const char *path, int flags, bool is_default)
|
||||
{
|
||||
struct umount_entry *entry;
|
||||
unsigned long irqflags;
|
||||
@@ -106,6 +192,11 @@ int ksu_umount_manager_add(const char *path, bool check_mnt, int flags, bool is_
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (is_path_in_mount_list(path)) {
|
||||
pr_warn("Umount manager: path already exists in mount_list: %s\n", path);
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, irqflags);
|
||||
|
||||
if (g_umount_mgr.entry_count >= g_umount_mgr.max_entries) {
|
||||
@@ -127,7 +218,6 @@ int ksu_umount_manager_add(const char *path, bool check_mnt, int flags, bool is_
|
||||
}
|
||||
|
||||
strncpy(entry->path, path, sizeof(entry->path) - 1);
|
||||
entry->check_mnt = check_mnt;
|
||||
entry->flags = flags;
|
||||
entry->state = UMOUNT_STATE_IDLE;
|
||||
entry->is_default = is_default;
|
||||
@@ -219,38 +309,23 @@ void ksu_umount_manager_execute_all(const struct cred *cred)
|
||||
|
||||
int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries, u32 *count)
|
||||
{
|
||||
struct umount_entry *entry;
|
||||
struct ksu_umount_entry_info info;
|
||||
unsigned long flags;
|
||||
u32 idx = 0;
|
||||
u32 max_count = *count;
|
||||
u32 idx;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
ret = collect_mount_list_entries(entries, max_count, &idx);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
||||
if (idx >= max_count) {
|
||||
break;
|
||||
if (idx < max_count) {
|
||||
ret = collect_umount_manager_entries(entries, idx, max_count, &idx);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
strncpy(info.path, entry->path, sizeof(info.path) - 1);
|
||||
info.check_mnt = entry->check_mnt;
|
||||
info.flags = entry->flags;
|
||||
info.is_default = entry->is_default;
|
||||
info.state = entry->state;
|
||||
info.ref_count = entry->ref_count;
|
||||
|
||||
if (copy_to_user(&entries[idx], &info, sizeof(info))) {
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
*count = idx;
|
||||
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ enum umount_entry_state {
|
||||
struct umount_entry {
|
||||
struct list_head list;
|
||||
char path[256];
|
||||
bool check_mnt;
|
||||
int flags;
|
||||
enum umount_entry_state state;
|
||||
bool is_default;
|
||||
@@ -40,7 +39,6 @@ enum umount_manager_op {
|
||||
struct ksu_umount_manager_cmd {
|
||||
__u32 operation;
|
||||
char path[256];
|
||||
__u8 check_mnt;
|
||||
__s32 flags;
|
||||
__u32 count;
|
||||
__aligned_u64 entries_ptr;
|
||||
@@ -48,7 +46,6 @@ struct ksu_umount_manager_cmd {
|
||||
|
||||
struct ksu_umount_entry_info {
|
||||
char path[256];
|
||||
__u8 check_mnt;
|
||||
__s32 flags;
|
||||
__u8 is_default;
|
||||
__u32 state;
|
||||
@@ -57,7 +54,7 @@ struct ksu_umount_entry_info {
|
||||
|
||||
int ksu_umount_manager_init(void);
|
||||
void ksu_umount_manager_exit(void);
|
||||
int ksu_umount_manager_add(const char *path, bool check_mnt, int flags, bool is_default);
|
||||
int ksu_umount_manager_add(const char *path, int flags, bool is_default);
|
||||
int ksu_umount_manager_remove(const char *path);
|
||||
void ksu_umount_manager_execute_all(const struct cred *cred);
|
||||
int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries, u32 *count);
|
||||
|
||||
@@ -319,6 +319,14 @@ NativeBridge(setEnhancedSecurityEnabled, jboolean, jboolean enabled) {
|
||||
return set_enhanced_security_enabled(enabled);
|
||||
}
|
||||
|
||||
NativeBridgeNP(isSuLogEnabled, jboolean) {
|
||||
return is_sulog_enabled();
|
||||
}
|
||||
|
||||
NativeBridge(setSuLogEnabled, jboolean, jboolean enabled) {
|
||||
return set_sulog_enabled(enabled);
|
||||
}
|
||||
|
||||
NativeBridge(getUserName, jstring, jint uid) {
|
||||
struct passwd *pw = getpwuid((uid_t) uid);
|
||||
if (pw && pw->pw_name && pw->pw_name[0] != '\0') {
|
||||
|
||||
@@ -231,6 +231,22 @@ bool is_enhanced_security_enabled() {
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
bool set_sulog_enabled(bool enabled) {
|
||||
return set_feature(KSU_FEATURE_SULOG, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
bool is_sulog_enabled() {
|
||||
uint64_t value = 0;
|
||||
bool supported = false;
|
||||
if (!get_feature(KSU_FEATURE_SULOG, &value, &supported)) {
|
||||
return false;
|
||||
}
|
||||
if (!supported) {
|
||||
return false;
|
||||
}
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
void get_full_version(char* buff) {
|
||||
struct ksu_get_full_version_cmd cmd = {0};
|
||||
if (ksuctl(KSU_IOCTL_GET_FULL_VERSION, &cmd) == 0) {
|
||||
|
||||
@@ -132,6 +132,7 @@ enum ksu_feature_id {
|
||||
KSU_FEATURE_SU_COMPAT = 0,
|
||||
KSU_FEATURE_KERNEL_UMOUNT = 1,
|
||||
KSU_FEATURE_ENHANCED_SECURITY = 2,
|
||||
KSU_FEATURE_SULOG = 3,
|
||||
};
|
||||
|
||||
// Generic feature API
|
||||
@@ -211,9 +212,12 @@ bool is_kernel_umount_enabled();
|
||||
|
||||
// Enhanced security
|
||||
bool set_enhanced_security_enabled(bool enabled);
|
||||
|
||||
bool is_enhanced_security_enabled();
|
||||
|
||||
// Su log
|
||||
bool set_sulog_enabled(bool enabled);
|
||||
bool is_sulog_enabled();
|
||||
|
||||
// Other command structures
|
||||
struct ksu_get_full_version_cmd {
|
||||
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
|
||||
|
||||
@@ -118,6 +118,15 @@ object Natives {
|
||||
external fun isEnhancedSecurityEnabled(): Boolean
|
||||
external fun setEnhancedSecurityEnabled(enabled: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* Su Log can be enabled/disabled.
|
||||
* 0: disabled
|
||||
* 1: enabled
|
||||
* negative : error
|
||||
*/
|
||||
external fun isSuLogEnabled(): Boolean
|
||||
external fun setSuLogEnabled(enabled: Boolean): Boolean
|
||||
|
||||
external fun isKPMEnabled(): Boolean
|
||||
external fun getHookType(): String
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||
@@ -52,8 +54,10 @@ import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import androidx.core.content.edit
|
||||
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||
import com.sukisu.ultra.ui.util.module.ModuleOperationUtils
|
||||
import com.sukisu.ultra.ui.util.module.ModuleUtils
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
|
||||
/**
|
||||
* @author ShirkNeko
|
||||
@@ -152,6 +156,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
var tempText: String
|
||||
val logContent = rememberSaveable { StringBuilder() }
|
||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
||||
var shouldWarningUserMetaModule by rememberSaveable { mutableStateOf(false) }
|
||||
// 添加状态跟踪是否已经完成刷写
|
||||
var hasFlashCompleted by rememberSaveable { mutableStateOf(false) }
|
||||
var hasExecuted by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -170,6 +175,39 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
val logSavedString = stringResource(R.string.log_saved)
|
||||
val installingModuleString = stringResource(R.string.installing_module)
|
||||
|
||||
val alertDialog = rememberCustomDialog { dismiss: () -> Unit ->
|
||||
val uriHandler = LocalUriHandler.current
|
||||
AlertDialog(
|
||||
onDismissRequest = { dismiss() },
|
||||
icon = {
|
||||
Icon(Icons.Outlined.Info, contentDescription = null)
|
||||
},
|
||||
title = {
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(text = stringResource(R.string.warning_of_meta_module_title))
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.warning_of_meta_module_summary))
|
||||
},
|
||||
confirmButton = {
|
||||
FilledTonalButton(onClick = { dismiss() }) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
OutlinedButton(onClick = {
|
||||
uriHandler.openUri("https://kernelsu.org/guide/metamodule.html")
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.learn_more))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// 当前模块安装状态
|
||||
val currentStatus = moduleInstallStatus.value
|
||||
|
||||
@@ -182,16 +220,19 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
totalModules = flashIt.uris.size,
|
||||
currentModule = 1
|
||||
)
|
||||
shouldWarningUserMetaModule = false
|
||||
hasFlashCompleted = false
|
||||
hasExecuted = false
|
||||
moduleVerificationMap.clear()
|
||||
}
|
||||
}
|
||||
is FlashIt.FlashModuleUpdate -> {
|
||||
shouldWarningUserMetaModule = false
|
||||
hasUpdateCompleted = false
|
||||
hasUpdateExecuted = false
|
||||
}
|
||||
else -> {
|
||||
shouldWarningUserMetaModule = false
|
||||
hasFlashCompleted = false
|
||||
hasExecuted = false
|
||||
}
|
||||
@@ -240,10 +281,29 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
}
|
||||
hasUpdateCompleted = true
|
||||
|
||||
if (!hasMetaModule() && code == 0) {
|
||||
// 如果没安装 MetaModule,且此模块需要挂载,并且当前模块安装成功,警告用户
|
||||
scope.launch {
|
||||
val mountOldDirectory = SuFile.open("/data/adb/modules/${getModuleIdFromUri(context,flashIt.uri)}/system")
|
||||
val mountNewDirectory = SuFile.open("/data/adb/modules_update/${getModuleIdFromUri(context,flashIt.uri)}/system")
|
||||
if (!(mountNewDirectory.isDirectory) && !(mountOldDirectory.isDirectory)) return@launch
|
||||
shouldWarningUserMetaModule = true
|
||||
|
||||
alertDialog.show()
|
||||
shouldWarningUserMetaModule = false
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是外部安装或需要自动退出的模块更新且不需要重启,延迟后自动返回
|
||||
if (isExternalInstall || shouldAutoExit) {
|
||||
scope.launch {
|
||||
while (shouldWarningUserMetaModule) {
|
||||
kotlinx.coroutines.delay(100)
|
||||
}
|
||||
kotlinx.coroutines.delay(1000)
|
||||
while (shouldWarningUserMetaModule) {
|
||||
kotlinx.coroutines.delay(100)
|
||||
}
|
||||
if (shouldAutoExit) {
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
||||
@@ -334,6 +394,38 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
}
|
||||
|
||||
hasFlashCompleted = true
|
||||
if (!hasMetaModule() && code == 0) {
|
||||
// 没有 MetaModule,且安装成功,检查此模块是否有自动挂载
|
||||
scope.launch {
|
||||
var mountOldDirectory : File
|
||||
var mountNewDirectory : File
|
||||
when (flashIt) {
|
||||
is FlashIt.FlashModules -> {
|
||||
mountOldDirectory = SuFile.open("/data/adb/modules/${getModuleIdFromUri(context,flashIt.uris[flashIt.currentIndex])}/system")
|
||||
mountNewDirectory = SuFile.open("/data/adb/modules_update/${getModuleIdFromUri(context,flashIt.uris[flashIt.currentIndex])}/system")
|
||||
}
|
||||
|
||||
is FlashIt.FlashModule -> {
|
||||
mountOldDirectory = SuFile.open("/data/adb/modules/${getModuleIdFromUri(context,flashIt.uri)}/system")
|
||||
mountNewDirectory = SuFile.open("/data/adb/modules_update/${getModuleIdFromUri(context,flashIt.uri)}/system")
|
||||
}
|
||||
|
||||
is FlashIt.FlashModuleUpdate -> {
|
||||
mountOldDirectory = SuFile.open("/data/adb/modules/${getModuleIdFromUri(context,flashIt.uri)}/system")
|
||||
mountNewDirectory = SuFile.open("/data/adb/modules_update/${getModuleIdFromUri(context,flashIt.uri)}/system")
|
||||
}
|
||||
|
||||
else -> return@launch
|
||||
}
|
||||
if (!mountNewDirectory.isDirectory && !mountOldDirectory.isDirectory) return@launch
|
||||
shouldWarningUserMetaModule = true
|
||||
|
||||
if (!hasMetaModule() && (flashIt !is FlashIt.FlashModules || flashIt.currentIndex >= flashIt.uris.size - 1)) {
|
||||
// 如果没有 MetaModule,且当前不是多模块刷写或是最后一个需要自动刷写的模块,而且有模块需要挂载,警告用户
|
||||
alertDialog.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flashIt is FlashIt.FlashModules && flashIt.currentIndex < flashIt.uris.size - 1) {
|
||||
val nextFlashIt = flashIt.copy(
|
||||
@@ -346,7 +438,13 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
} else if ((isExternalInstall || shouldAutoExit) && flashIt is FlashIt.FlashModules && flashIt.currentIndex >= flashIt.uris.size - 1) {
|
||||
// 如果是外部安装或需要自动退出且是最后一个模块,安装完成后自动返回
|
||||
scope.launch {
|
||||
while (shouldWarningUserMetaModule) {
|
||||
kotlinx.coroutines.delay(100)
|
||||
}
|
||||
kotlinx.coroutines.delay(1000)
|
||||
while (shouldWarningUserMetaModule) {
|
||||
kotlinx.coroutines.delay(100)
|
||||
}
|
||||
if (shouldAutoExit) {
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
||||
@@ -356,7 +454,13 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
} else if ((isExternalInstall || shouldAutoExit) && flashIt is FlashIt.FlashModule) {
|
||||
// 如果是外部安装或需要自动退出的单个模块,安装完成后自动返回
|
||||
scope.launch {
|
||||
while (shouldWarningUserMetaModule) {
|
||||
kotlinx.coroutines.delay(100)
|
||||
}
|
||||
kotlinx.coroutines.delay(1000)
|
||||
while (shouldWarningUserMetaModule) {
|
||||
kotlinx.coroutines.delay(100)
|
||||
}
|
||||
if (shouldAutoExit) {
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
||||
@@ -705,6 +809,22 @@ suspend fun getModuleNameFromUri(context: Context, uri: Uri): String {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getModuleIdFromUri(context: Context, uri: Uri): String? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (uri == Uri.EMPTY) {
|
||||
return@withContext null
|
||||
}
|
||||
if (!ModuleUtils.isUriAccessible(context, uri)) {
|
||||
return@withContext null
|
||||
}
|
||||
ModuleUtils.extractModuleId(context, uri)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
sealed class FlashIt : Parcelable {
|
||||
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean, val partition: String? = null) : FlashIt()
|
||||
|
||||
@@ -182,6 +182,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
isSimpleMode = viewModel.isSimpleMode,
|
||||
isHideSusfsStatus = viewModel.isHideSusfsStatus,
|
||||
isHideZygiskImplement = viewModel.isHideZygiskImplement,
|
||||
isHideMetaModuleImplement = viewModel.isHideMetaModuleImplement,
|
||||
showKpmInfo = viewModel.showKpmInfo,
|
||||
lkmMode = viewModel.systemStatus.lkmMode,
|
||||
)
|
||||
@@ -652,6 +653,7 @@ private fun InfoCard(
|
||||
isSimpleMode: Boolean,
|
||||
isHideSusfsStatus: Boolean,
|
||||
isHideZygiskImplement: Boolean,
|
||||
isHideMetaModuleImplement: Boolean,
|
||||
showKpmInfo: Boolean,
|
||||
lkmMode: Boolean?
|
||||
) {
|
||||
@@ -784,6 +786,14 @@ private fun InfoCard(
|
||||
)
|
||||
}
|
||||
|
||||
if (!isHideMetaModuleImplement && !isSimpleMode && systemInfo.metaModuleImplement != "None") {
|
||||
InfoCardItem(
|
||||
stringResource(R.string.home_meta_module_implement),
|
||||
systemInfo.metaModuleImplement,
|
||||
icon = Icons.Default.Extension,
|
||||
)
|
||||
}
|
||||
|
||||
if (!isSimpleMode) {
|
||||
if (lkmMode != true && !showKpmInfo) {
|
||||
val displayVersion =
|
||||
|
||||
@@ -353,38 +353,40 @@ fun InstallScreen(
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// 使用本地的LKM文件
|
||||
ElevatedCard(
|
||||
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||
elevation = getCardElevation(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp),
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(stringResource(id = R.string.install_upload_lkm_file))
|
||||
},
|
||||
supportingContent = {
|
||||
(lkmSelection as? LkmSelection.LkmUri)?.let {
|
||||
Text(
|
||||
stringResource(
|
||||
id = R.string.selected_lkm,
|
||||
it.uri.lastPathSegment ?: "(file)"
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.Input,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
if (isGKI) {
|
||||
// 使用本地的LKM文件
|
||||
ElevatedCard(
|
||||
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||
elevation = getCardElevation(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onLkmUpload() }
|
||||
)
|
||||
.padding(bottom = 12.dp),
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(stringResource(id = R.string.install_upload_lkm_file))
|
||||
},
|
||||
supportingContent = {
|
||||
(lkmSelection as? LkmSelection.LkmUri)?.let {
|
||||
Text(
|
||||
stringResource(
|
||||
id = R.string.selected_lkm,
|
||||
it.uri.lastPathSegment ?: "(file)"
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.Input,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onLkmUpload() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
|
||||
|
||||
@@ -752,6 +752,7 @@ private fun ModuleList(
|
||||
val uninstall = stringResource(R.string.uninstall)
|
||||
val cancel = stringResource(android.R.string.cancel)
|
||||
val moduleUninstallConfirm = stringResource(R.string.module_uninstall_confirm)
|
||||
val metaModuleUninstallConfirm = stringResource(R.string.metamodule_uninstall_confirm)
|
||||
val updateText = stringResource(R.string.module_update)
|
||||
val changelogText = stringResource(R.string.module_changelog)
|
||||
val downloadingText = stringResource(R.string.module_downloading)
|
||||
@@ -847,9 +848,10 @@ private fun ModuleList(
|
||||
suspend fun onModuleUninstallClicked(module: ModuleViewModel.ModuleInfo) {
|
||||
val isUninstall = !module.remove
|
||||
if (isUninstall) {
|
||||
val formatter = if (module.metamodule) metaModuleUninstallConfirm else moduleUninstallConfirm
|
||||
val confirmResult = confirmDialog.awaitConfirm(
|
||||
moduleStr,
|
||||
content = moduleUninstallConfirm.format(module.name),
|
||||
content = formatter.format(module.name),
|
||||
confirm = uninstall,
|
||||
dismiss = cancel
|
||||
)
|
||||
@@ -865,7 +867,7 @@ private fun ModuleList(
|
||||
ModuleOperationUtils.handleModuleUninstall(module.dirId)
|
||||
uninstallModule(module.dirId)
|
||||
} else {
|
||||
restoreModule(module.dirId)
|
||||
undoUninstallModule(module.dirId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1199,6 +1201,22 @@ fun ModuleItem(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
if (module.metamodule) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
) {
|
||||
Text(
|
||||
text = "META",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
Surface(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
@@ -1329,8 +1347,9 @@ fun ModuleItemPreview() {
|
||||
update = true,
|
||||
remove = false,
|
||||
updateJson = "",
|
||||
hasWebUi = false,
|
||||
hasActionScript = false,
|
||||
hasWebUi = true,
|
||||
hasActionScript = true,
|
||||
metamodule = true,
|
||||
dirId = "dirId",
|
||||
config = ModuleConfig(),
|
||||
isVerified = true,
|
||||
|
||||
@@ -11,12 +11,10 @@ import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.automirrored.filled.Undo
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.rounded.EnhancedEncryption
|
||||
@@ -56,7 +54,6 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
|
||||
import com.sukisu.ultra.ui.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -79,6 +76,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val context = LocalContext.current
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
var isSuLogEnabled by remember { mutableStateOf(Natives.isSuLogEnabled()) }
|
||||
var selectedEngine by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getString("webui_engine", "default") ?: "default"
|
||||
@@ -150,11 +148,28 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
}
|
||||
val enhancedStatus by produceState(initialValue = "") {
|
||||
value = getFeatureStatus("enhanced_security")
|
||||
}
|
||||
val enhancedSummary = when (enhancedStatus) {
|
||||
"unsupported" -> stringResource(id = R.string.feature_status_unsupported_summary)
|
||||
"managed" -> stringResource(id = R.string.feature_status_managed_summary)
|
||||
else -> stringResource(id = R.string.settings_enable_enhanced_security_summary)
|
||||
}
|
||||
SuperDropdown(
|
||||
icon = Icons.Rounded.EnhancedEncryption,
|
||||
title = stringResource(id = R.string.settings_enable_enhanced_security),
|
||||
summary = stringResource(id = R.string.settings_enable_enhanced_security_summary),
|
||||
summary = enhancedSummary,
|
||||
items = modeItems,
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Rounded.EnhancedEncryption,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = stringResource(id = R.string.settings_enable_enhanced_security),
|
||||
tint = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
enabled = enhancedStatus == "supported",
|
||||
selectedIndex = enhancedSecurityMode,
|
||||
onSelectedIndexChange = { index ->
|
||||
when (index) {
|
||||
@@ -193,11 +208,28 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
}
|
||||
val suStatus by produceState(initialValue = "") {
|
||||
value = getFeatureStatus("su_compat")
|
||||
}
|
||||
val suSummary = when (suStatus) {
|
||||
"unsupported" -> stringResource(id = R.string.feature_status_unsupported_summary)
|
||||
"managed" -> stringResource(id = R.string.feature_status_managed_summary)
|
||||
else -> stringResource(id = R.string.settings_disable_su_summary)
|
||||
}
|
||||
SuperDropdown(
|
||||
icon = Icons.Rounded.RemoveModerator,
|
||||
title = stringResource(id = R.string.settings_disable_su),
|
||||
summary = stringResource(id = R.string.settings_disable_su_summary),
|
||||
summary = suSummary,
|
||||
items = modeItems,
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Rounded.RemoveModerator,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = stringResource(id = R.string.settings_disable_su),
|
||||
tint = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
enabled = suStatus == "supported",
|
||||
selectedIndex = suCompatMode,
|
||||
onSelectedIndexChange = { index ->
|
||||
when (index) {
|
||||
@@ -236,11 +268,28 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
}
|
||||
val umountStatus by produceState(initialValue = "") {
|
||||
value = getFeatureStatus("kernel_umount")
|
||||
}
|
||||
val umountSummary = when (umountStatus) {
|
||||
"unsupported" -> stringResource(id = R.string.feature_status_unsupported_summary)
|
||||
"managed" -> stringResource(id = R.string.feature_status_managed_summary)
|
||||
else -> stringResource(id = R.string.settings_disable_kernel_umount_summary)
|
||||
}
|
||||
SuperDropdown(
|
||||
icon = Icons.Rounded.RemoveCircle,
|
||||
title = stringResource(id = R.string.settings_disable_kernel_umount),
|
||||
summary = stringResource(id = R.string.settings_disable_kernel_umount_summary),
|
||||
summary = umountSummary,
|
||||
items = modeItems,
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Rounded.RemoveCircle,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = stringResource(id = R.string.settings_disable_kernel_umount),
|
||||
tint = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
enabled = umountStatus == "supported",
|
||||
selectedIndex = kernelUmountMode,
|
||||
onSelectedIndexChange = { index ->
|
||||
when (index) {
|
||||
@@ -270,6 +319,68 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
|
||||
var suLogMode by rememberSaveable {
|
||||
mutableIntStateOf(
|
||||
run {
|
||||
val currentEnabled = Natives.isSuLogEnabled()
|
||||
val savedPersist = prefs.getInt("sulog_mode", 0)
|
||||
if (savedPersist == 2) 2 else if (!currentEnabled) 1 else 0
|
||||
}
|
||||
)
|
||||
}
|
||||
val suLogStatus by produceState(initialValue = "") {
|
||||
value = getFeatureStatus("sulog")
|
||||
}
|
||||
val suLogSummary = when (suLogStatus) {
|
||||
"unsupported" -> stringResource(id = R.string.feature_status_unsupported_summary)
|
||||
"managed" -> stringResource(id = R.string.feature_status_managed_summary)
|
||||
else -> stringResource(id = R.string.settings_disable_sulog_summary)
|
||||
}
|
||||
SuperDropdown(
|
||||
title = stringResource(id = R.string.settings_disable_sulog),
|
||||
summary = suLogSummary,
|
||||
items = modeItems,
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Rounded.RemoveCircle,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = stringResource(id = R.string.settings_disable_sulog),
|
||||
tint = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
enabled = suLogStatus == "supported",
|
||||
selectedIndex = suLogMode,
|
||||
onSelectedIndexChange = { index ->
|
||||
when (index) {
|
||||
// Default: enable and save to persist
|
||||
0 -> if (Natives.setSuLogEnabled(true)) {
|
||||
execKsud("feature save", true)
|
||||
prefs.edit { putInt("sulog_mode", 0) }
|
||||
suLogMode = 0
|
||||
isSuLogEnabled = true
|
||||
}
|
||||
|
||||
// Temporarily disable: save enabled state first, then disable
|
||||
1 -> if (Natives.setSuLogEnabled(true)) {
|
||||
execKsud("feature save", true)
|
||||
if (Natives.setSuLogEnabled(false)) {
|
||||
prefs.edit { putInt("sulog_mode", 0) }
|
||||
suLogMode = 1
|
||||
isSuLogEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
// Permanently disable: disable and save
|
||||
2 -> if (Natives.setSuLogEnabled(false)) {
|
||||
execKsud("feature save", true)
|
||||
prefs.edit { putInt("sulog_mode", 2) }
|
||||
suLogMode = 2
|
||||
isSuLogEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 卸载模块开关
|
||||
var umountChecked by rememberSaveable { mutableStateOf(Natives.isDefaultUmountModules()) }
|
||||
SwitchItem(
|
||||
@@ -283,26 +394,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
// 强制签名验证开关
|
||||
var forceSignatureVerification by rememberSaveable {
|
||||
mutableStateOf(prefs.getBoolean("force_signature_verification", false))
|
||||
}
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Security,
|
||||
title = stringResource(R.string.module_signature_verification),
|
||||
summary = stringResource(R.string.module_signature_verification_summary),
|
||||
checked = forceSignatureVerification,
|
||||
onCheckedChange = { enabled ->
|
||||
prefs.edit { putBoolean("force_signature_verification", enabled) }
|
||||
forceSignatureVerification = enabled
|
||||
}
|
||||
)
|
||||
// UID 扫描开关
|
||||
if (Natives.version >= Natives.MINIMAL_SUPPORTED_UID_SCANNER && Natives.version >= Natives.MINIMAL_NEW_IOCTL_KERNEL) {
|
||||
UidScannerSection(prefs, snackBarHost, scope, context)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -403,28 +494,27 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
// 查看使用日志
|
||||
KsuIsValid {
|
||||
SettingItem(
|
||||
icon = Icons.Filled.Visibility,
|
||||
title = stringResource(R.string.log_viewer_view_logs),
|
||||
summary = stringResource(R.string.log_viewer_view_logs_summary),
|
||||
onClick = {
|
||||
navigator.navigate(LogViewerScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
val lkmMode = Natives.isLkmMode
|
||||
KsuIsValid {
|
||||
if (lkmMode) {
|
||||
if (isSuLogEnabled) {
|
||||
SettingItem(
|
||||
icon = Icons.Filled.FolderOff,
|
||||
title = stringResource(R.string.umount_path_manager),
|
||||
summary = stringResource(R.string.umount_path_manager_summary),
|
||||
icon = Icons.Filled.Visibility,
|
||||
title = stringResource(R.string.log_viewer_view_logs),
|
||||
summary = stringResource(R.string.log_viewer_view_logs_summary),
|
||||
onClick = {
|
||||
navigator.navigate(UmountManagerScreenDestination)
|
||||
navigator.navigate(LogViewerScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
KsuIsValid {
|
||||
SettingItem(
|
||||
icon = Icons.Filled.FolderOff,
|
||||
title = stringResource(R.string.umount_path_manager),
|
||||
summary = stringResource(R.string.umount_path_manager_summary),
|
||||
onClick = {
|
||||
navigator.navigate(UmountManagerScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showBottomsheet) {
|
||||
LogBottomSheet(
|
||||
@@ -467,7 +557,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
}
|
||||
if (lkmMode) {
|
||||
if (Natives.isLkmMode) {
|
||||
UninstallItem(navigator) {
|
||||
loadingDialog.withLoading(it)
|
||||
}
|
||||
@@ -956,124 +1046,3 @@ private fun TopBar(
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UidScannerSection(
|
||||
prefs: SharedPreferences,
|
||||
snackBarHost: SnackbarHostState,
|
||||
scope: CoroutineScope,
|
||||
context: Context
|
||||
) {
|
||||
if (Natives.version < Natives.MINIMAL_SUPPORTED_UID_SCANNER) return
|
||||
|
||||
val realAuto = Natives.isUidScannerEnabled()
|
||||
val realMulti = getUidMultiUserScan()
|
||||
|
||||
var autoOn by remember { mutableStateOf(realAuto) }
|
||||
var multiOn by remember { mutableStateOf(realMulti) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
autoOn = realAuto
|
||||
multiOn = realMulti
|
||||
prefs.edit {
|
||||
putBoolean("uid_auto_scan", autoOn)
|
||||
putBoolean("uid_multi_user_scan", multiOn)
|
||||
}
|
||||
}
|
||||
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Scanner,
|
||||
title = stringResource(R.string.uid_auto_scan_title),
|
||||
summary = stringResource(R.string.uid_auto_scan_summary),
|
||||
checked = autoOn,
|
||||
onCheckedChange = { target ->
|
||||
autoOn = target
|
||||
if (!target) multiOn = false
|
||||
|
||||
scope.launch(Dispatchers.IO) {
|
||||
setUidAutoScan(target)
|
||||
val actual = Natives.isUidScannerEnabled() || readUidScannerFile()
|
||||
withContext(Dispatchers.Main) {
|
||||
autoOn = actual
|
||||
if (!actual) multiOn = false
|
||||
prefs.edit {
|
||||
putBoolean("uid_auto_scan", actual)
|
||||
putBoolean("uid_multi_user_scan", multiOn)
|
||||
}
|
||||
if (actual != target) {
|
||||
snackBarHost.showSnackbar(
|
||||
context.getString(R.string.uid_scanner_setting_failed)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = autoOn,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Groups,
|
||||
title = stringResource(R.string.uid_multi_user_scan_title),
|
||||
summary = stringResource(R.string.uid_multi_user_scan_summary),
|
||||
checked = multiOn,
|
||||
onCheckedChange = { target ->
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val ok = setUidMultiUserScan(target)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (ok) {
|
||||
multiOn = target
|
||||
prefs.edit { putBoolean("uid_multi_user_scan", target) }
|
||||
} else {
|
||||
snackBarHost.showSnackbar(
|
||||
context.getString(R.string.uid_scanner_setting_failed)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = autoOn,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
SettingItem(
|
||||
icon = Icons.Filled.CleaningServices,
|
||||
title = stringResource(R.string.clean_runtime_environment),
|
||||
summary = stringResource(R.string.clean_runtime_environment_summary),
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.clean_runtime_environment),
|
||||
content = context.getString(R.string.clean_runtime_environment_confirm)
|
||||
) == ConfirmResult.Confirmed
|
||||
) {
|
||||
if (cleanRuntimeEnvironment()) {
|
||||
autoOn = false
|
||||
multiOn = false
|
||||
prefs.edit {
|
||||
putBoolean("uid_auto_scan", false)
|
||||
putBoolean("uid_multi_user_scan", false)
|
||||
}
|
||||
Natives.setUidScannerEnabled(false)
|
||||
snackBarHost.showSnackbar(
|
||||
context.getString(R.string.clean_runtime_environment_success)
|
||||
)
|
||||
} else {
|
||||
snackBarHost.showSnackbar(
|
||||
context.getString(R.string.clean_runtime_environment_failed)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.animation.core.*
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -31,6 +32,7 @@ import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -38,12 +40,15 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -51,6 +56,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.request.ImageRequest
|
||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||
import com.dergoogler.mmrl.ui.component.LabelItemDefaults
|
||||
@@ -314,6 +320,9 @@ private fun SuperUserContent(
|
||||
scope: CoroutineScope
|
||||
) {
|
||||
val expandedGroups = remember { mutableStateOf(setOf<Int>()) }
|
||||
val density = LocalDensity.current
|
||||
val targetSizePx = remember(density) { with(density) { 36.dp.roundToPx() } }
|
||||
val context = LocalContext.current
|
||||
|
||||
PullToRefreshBox(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
@@ -329,6 +338,7 @@ private fun SuperUserContent(
|
||||
filteredAndSortedAppGroups.forEachIndexed { _, appGroup ->
|
||||
item(key = "${appGroup.uid}-${appGroup.mainApp.packageName}") {
|
||||
AppGroupItem(
|
||||
expandedGroups = expandedGroups,
|
||||
appGroup = appGroup,
|
||||
isSelected = appGroup.packageNames.any { viewModel.selectedApps.contains(it) },
|
||||
onToggleSelection = {
|
||||
@@ -357,32 +367,46 @@ private fun SuperUserContent(
|
||||
)
|
||||
}
|
||||
|
||||
if (appGroup.apps.size <= 1) return@forEachIndexed
|
||||
|
||||
items(appGroup.apps, key = { "${it.packageName}-${it.uid}" }) { app ->
|
||||
val painter = rememberAsyncImagePainter(
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(app.packageInfo)
|
||||
.size(targetSizePx)
|
||||
.crossfade(true)
|
||||
.build()
|
||||
)
|
||||
|
||||
val listItemContent = remember(app.packageName, appGroup.uid) {
|
||||
@Composable {
|
||||
ListItem(
|
||||
modifier = Modifier
|
||||
.clickable { navigator.navigate(AppProfileScreenDestination(app)) }
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp),
|
||||
headlineContent = { Text(app.label, style = MaterialTheme.typography.bodyMedium) },
|
||||
supportingContent = { Text(app.packageName, style = MaterialTheme.typography.bodySmall) },
|
||||
leadingContent = {
|
||||
Image(
|
||||
painter = painter,
|
||||
contentDescription = app.label,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.size(36.dp),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = expandedGroups.value.contains(appGroup.uid) && appGroup.apps.size > 1,
|
||||
visible = expandedGroups.value.contains(appGroup.uid),
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp)
|
||||
.clickable {
|
||||
navigator.navigate(AppProfileScreenDestination(app))
|
||||
},
|
||||
headlineContent = { Text(app.label, style = MaterialTheme.typography.bodyMedium) },
|
||||
supportingContent = { Text(app.packageName, style = MaterialTheme.typography.bodySmall) },
|
||||
leadingContent = {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(app.packageInfo)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = app.label,
|
||||
modifier = Modifier.padding(4.dp).width(36.dp).height(36.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
listItemContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -797,7 +821,8 @@ private fun AppGroupItem(
|
||||
onToggleSelection: () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
viewModel: SuperUserViewModel
|
||||
viewModel: SuperUserViewModel,
|
||||
expandedGroups: MutableState<Set<Int>>
|
||||
) {
|
||||
val mainApp = appGroup.mainApp
|
||||
|
||||
@@ -818,9 +843,27 @@ private fun AppGroupItem(
|
||||
} else {
|
||||
mainApp.packageName
|
||||
}
|
||||
Text(summaryText)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(summaryText)
|
||||
|
||||
if (appGroup.apps.size > 1) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.KeyboardArrowDown,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.rotate(
|
||||
animateFloatAsState(
|
||||
targetValue = if (expandedGroups.value.contains(appGroup.uid)) 180f else 0f,
|
||||
animationSpec = tween(200, easing = LinearOutSlowInEasing),
|
||||
label = ""
|
||||
).value
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
if (appGroup.allowSu) {
|
||||
@@ -853,7 +896,7 @@ private fun AppGroupItem(
|
||||
)
|
||||
}
|
||||
if (appGroup.apps.size > 1) {
|
||||
Natives.getUserName(appGroup.uid)?.let {
|
||||
appGroup.userName?.let {
|
||||
LabelItem(
|
||||
text = it,
|
||||
style = LabelItemDefaults.style.copy(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@@ -37,9 +38,7 @@ private val SPACING_LARGE = 16.dp
|
||||
|
||||
data class UmountPathEntry(
|
||||
val path: String,
|
||||
val checkMnt: Boolean,
|
||||
val flags: Int,
|
||||
val isDefault: Boolean
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -243,11 +242,11 @@ fun UmountManagerScreen(navigator: DestinationsNavigator) {
|
||||
if (showAddDialog) {
|
||||
AddUmountPathDialog(
|
||||
onDismiss = { showAddDialog = false },
|
||||
onConfirm = { path, checkMnt, flags ->
|
||||
onConfirm = { path, flags ->
|
||||
showAddDialog = false
|
||||
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val success = addUmountPath(path, checkMnt, flags)
|
||||
val success = addUmountPath(path, flags)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (success) {
|
||||
saveUmountConfig()
|
||||
@@ -291,10 +290,7 @@ fun UmountPathCard(
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Folder,
|
||||
contentDescription = null,
|
||||
tint = if (entry.isDefault)
|
||||
MaterialTheme.colorScheme.primary
|
||||
else
|
||||
MaterialTheme.colorScheme.secondary,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
|
||||
@@ -308,42 +304,31 @@ fun UmountPathCard(
|
||||
Spacer(modifier = Modifier.height(SPACING_SMALL))
|
||||
Text(
|
||||
text = buildString {
|
||||
append(context.getString(R.string.check_mount_type))
|
||||
append(": ")
|
||||
append(if (entry.checkMnt) context.getString(R.string.yes) else context.getString(R.string.no))
|
||||
append(" | ")
|
||||
append(context.getString(R.string.flags))
|
||||
append(": ")
|
||||
append(entry.flags.toUmountFlagName(context))
|
||||
if (entry.isDefault) {
|
||||
append(" | ")
|
||||
append(context.getString(R.string.default_entry))
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
if (!entry.isDefault) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.confirm_delete),
|
||||
content = context.getString(R.string.confirm_delete_umount_path, entry.path)
|
||||
) == ConfirmResult.Confirmed) {
|
||||
onDelete()
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.confirm_delete),
|
||||
content = context.getString(R.string.confirm_delete_umount_path, entry.path)
|
||||
) == ConfirmResult.Confirmed) {
|
||||
onDelete()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Delete,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Delete,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,10 +337,9 @@ fun UmountPathCard(
|
||||
@Composable
|
||||
fun AddUmountPathDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (String, Boolean, Int) -> Unit
|
||||
onConfirm: (String, Int) -> Unit
|
||||
) {
|
||||
var path by rememberSaveable { mutableStateOf("") }
|
||||
var checkMnt by rememberSaveable { mutableStateOf(false) }
|
||||
var flags by rememberSaveable { mutableStateOf("-1") }
|
||||
|
||||
AlertDialog(
|
||||
@@ -373,20 +357,6 @@ fun AddUmountPathDialog(
|
||||
|
||||
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = checkMnt,
|
||||
onCheckedChange = { checkMnt = it }
|
||||
)
|
||||
Spacer(modifier = Modifier.width(SPACING_SMALL))
|
||||
Text(stringResource(R.string.check_mount_type_overlay))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
|
||||
|
||||
OutlinedTextField(
|
||||
value = flags,
|
||||
onValueChange = { flags = it },
|
||||
@@ -402,7 +372,7 @@ fun AddUmountPathDialog(
|
||||
TextButton(
|
||||
onClick = {
|
||||
val flagsInt = flags.toIntOrNull() ?: -1
|
||||
onConfirm(path, checkMnt, flagsInt)
|
||||
onConfirm(path, flagsInt)
|
||||
},
|
||||
enabled = path.isNotBlank()
|
||||
) {
|
||||
@@ -423,18 +393,16 @@ private fun parseUmountPaths(output: String): List<UmountPathEntry> {
|
||||
|
||||
return lines.drop(2).mapNotNull { line ->
|
||||
val parts = line.trim().split(Regex("\\s+"))
|
||||
if (parts.size >= 4) {
|
||||
if (parts.size >= 2) {
|
||||
UmountPathEntry(
|
||||
path = parts[0],
|
||||
checkMnt = parts[1].equals("true", ignoreCase = true),
|
||||
flags = parts[2].toIntOrNull() ?: -1,
|
||||
isDefault = parts[3].equals("Yes", ignoreCase = true)
|
||||
flags = parts[1].toIntOrNull() ?: -1
|
||||
)
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.toUmountFlagName(context: android.content.Context): String {
|
||||
private fun Int.toUmountFlagName(context: Context): String {
|
||||
return when (this) {
|
||||
-1 -> context.getString(R.string.mnt_detach)
|
||||
else -> this.toString()
|
||||
|
||||
@@ -47,8 +47,6 @@ enum class SuSFSTab(val displayNameRes: Int) {
|
||||
SUS_PATHS(R.string.susfs_tab_sus_paths),
|
||||
SUS_LOOP_PATHS(R.string.susfs_tab_sus_loop_paths),
|
||||
SUS_MAPS(R.string.susfs_tab_sus_maps),
|
||||
SUS_MOUNTS(R.string.susfs_tab_sus_mounts),
|
||||
TRY_UMOUNT(R.string.susfs_tab_try_umount),
|
||||
KSTAT_CONFIG(R.string.susfs_tab_kstat_config),
|
||||
PATH_SETTINGS(R.string.susfs_tab_path_settings),
|
||||
ENABLED_FEATURES(R.string.susfs_tab_enabled_features);
|
||||
@@ -99,8 +97,6 @@ fun SuSFSConfigScreen(
|
||||
var susPaths by remember { mutableStateOf(emptySet<String>()) }
|
||||
var susLoopPaths by remember { mutableStateOf(emptySet<String>()) }
|
||||
var susMaps by remember { mutableStateOf(emptySet<String>()) }
|
||||
var susMounts by remember { mutableStateOf(emptySet<String>()) }
|
||||
var tryUmounts by remember { mutableStateOf(emptySet<String>()) }
|
||||
var androidDataPath by remember { mutableStateOf("") }
|
||||
var sdcardPath by remember { mutableStateOf("") }
|
||||
|
||||
@@ -125,8 +121,6 @@ fun SuSFSConfigScreen(
|
||||
var showAddLoopPathDialog by remember { mutableStateOf(false) }
|
||||
var showAddSusMapDialog by remember { mutableStateOf(false) }
|
||||
var showAddAppPathDialog by remember { mutableStateOf(false) }
|
||||
var showAddMountDialog by remember { mutableStateOf(false) }
|
||||
var showAddUmountDialog by remember { mutableStateOf(false) }
|
||||
var showAddKstatStaticallyDialog by remember { mutableStateOf(false) }
|
||||
var showAddKstatDialog by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -134,8 +128,6 @@ fun SuSFSConfigScreen(
|
||||
var editingPath by remember { mutableStateOf<String?>(null) }
|
||||
var editingLoopPath by remember { mutableStateOf<String?>(null) }
|
||||
var editingSusMap by remember { mutableStateOf<String?>(null) }
|
||||
var editingMount by remember { mutableStateOf<String?>(null) }
|
||||
var editingUmount by remember { mutableStateOf<String?>(null) }
|
||||
var editingKstatConfig by remember { mutableStateOf<String?>(null) }
|
||||
var editingKstatPath by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
@@ -143,8 +135,6 @@ fun SuSFSConfigScreen(
|
||||
var showResetPathsDialog by remember { mutableStateOf(false) }
|
||||
var showResetLoopPathsDialog by remember { mutableStateOf(false) }
|
||||
var showResetSusMapsDialog by remember { mutableStateOf(false) }
|
||||
var showResetMountsDialog by remember { mutableStateOf(false) }
|
||||
var showResetUmountsDialog by remember { mutableStateOf(false) }
|
||||
var showResetKstatDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// 备份还原相关状态
|
||||
@@ -304,8 +294,6 @@ fun SuSFSConfigScreen(
|
||||
susPaths = SuSFSManager.getSusPaths(context)
|
||||
susLoopPaths = SuSFSManager.getSusLoopPaths(context)
|
||||
susMaps = SuSFSManager.getSusMaps(context)
|
||||
susMounts = SuSFSManager.getSusMounts(context)
|
||||
tryUmounts = SuSFSManager.getTryUmounts(context)
|
||||
androidDataPath = SuSFSManager.getAndroidDataPath(context)
|
||||
sdcardPath = SuSFSManager.getSdcardPath(context)
|
||||
kstatConfigs = SuSFSManager.getKstatConfigs(context)
|
||||
@@ -477,8 +465,6 @@ fun SuSFSConfigScreen(
|
||||
susPaths = SuSFSManager.getSusPaths(context)
|
||||
susLoopPaths = SuSFSManager.getSusLoopPaths(context)
|
||||
susMaps = SuSFSManager.getSusMaps(context)
|
||||
susMounts = SuSFSManager.getSusMounts(context)
|
||||
tryUmounts = SuSFSManager.getTryUmounts(context)
|
||||
androidDataPath = SuSFSManager.getAndroidDataPath(context)
|
||||
sdcardPath = SuSFSManager.getSdcardPath(context)
|
||||
kstatConfigs = SuSFSManager.getKstatConfigs(context)
|
||||
@@ -649,62 +635,6 @@ fun SuSFSConfigScreen(
|
||||
existingSusPaths = susPaths
|
||||
)
|
||||
|
||||
AddPathDialog(
|
||||
showDialog = showAddMountDialog,
|
||||
onDismiss = {
|
||||
showAddMountDialog = false
|
||||
editingMount = null
|
||||
},
|
||||
onConfirm = { mount ->
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
val success = if (editingMount != null) {
|
||||
SuSFSManager.editSusMount(context, editingMount!!, mount)
|
||||
} else {
|
||||
SuSFSManager.addSusMount(context, mount)
|
||||
}
|
||||
if (success) {
|
||||
susMounts = SuSFSManager.getSusMounts(context)
|
||||
}
|
||||
isLoading = false
|
||||
showAddMountDialog = false
|
||||
editingMount = null
|
||||
}
|
||||
},
|
||||
isLoading = isLoading,
|
||||
titleRes = if (editingMount != null) R.string.susfs_edit_sus_mount else R.string.susfs_add_sus_mount,
|
||||
labelRes = R.string.susfs_mount_path_label,
|
||||
placeholderRes = R.string.susfs_path_placeholder,
|
||||
initialValue = editingMount ?: ""
|
||||
)
|
||||
|
||||
AddTryUmountDialog(
|
||||
showDialog = showAddUmountDialog,
|
||||
onDismiss = {
|
||||
showAddUmountDialog = false
|
||||
editingUmount = null
|
||||
},
|
||||
onConfirm = { path, mode ->
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
val success = if (editingUmount != null) {
|
||||
SuSFSManager.editTryUmount(context, editingUmount!!, path, mode)
|
||||
} else {
|
||||
SuSFSManager.addTryUmount(context, path, mode)
|
||||
}
|
||||
if (success) {
|
||||
tryUmounts = SuSFSManager.getTryUmounts(context)
|
||||
}
|
||||
isLoading = false
|
||||
showAddUmountDialog = false
|
||||
editingUmount = null
|
||||
}
|
||||
},
|
||||
isLoading = isLoading,
|
||||
initialPath = editingUmount?.split("|")?.get(0) ?: "",
|
||||
initialMode = editingUmount?.split("|")?.get(1)?.toIntOrNull() ?: 0
|
||||
)
|
||||
|
||||
AddKstatStaticallyDialog(
|
||||
showDialog = showAddKstatStaticallyDialog,
|
||||
onDismiss = {
|
||||
@@ -865,48 +795,6 @@ fun SuSFSConfigScreen(
|
||||
isDestructive = true
|
||||
)
|
||||
|
||||
ConfirmDialog(
|
||||
showDialog = showResetMountsDialog,
|
||||
onDismiss = { showResetMountsDialog = false },
|
||||
onConfirm = {
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
SuSFSManager.saveSusMounts(context, emptySet())
|
||||
susMounts = emptySet()
|
||||
if (SuSFSManager.isAutoStartEnabled(context)) {
|
||||
SuSFSManager.configureAutoStart(context, true)
|
||||
}
|
||||
isLoading = false
|
||||
showResetMountsDialog = false
|
||||
}
|
||||
},
|
||||
titleRes = R.string.susfs_reset_mounts_title,
|
||||
messageRes = R.string.susfs_reset_mounts_message,
|
||||
isLoading = isLoading,
|
||||
isDestructive = true
|
||||
)
|
||||
|
||||
ConfirmDialog(
|
||||
showDialog = showResetUmountsDialog,
|
||||
onDismiss = { showResetUmountsDialog = false },
|
||||
onConfirm = {
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
SuSFSManager.saveTryUmounts(context, emptySet())
|
||||
tryUmounts = emptySet()
|
||||
if (SuSFSManager.isAutoStartEnabled(context)) {
|
||||
SuSFSManager.configureAutoStart(context, true)
|
||||
}
|
||||
isLoading = false
|
||||
showResetUmountsDialog = false
|
||||
}
|
||||
},
|
||||
titleRes = R.string.susfs_reset_umounts_title,
|
||||
messageRes = R.string.susfs_reset_umounts_message,
|
||||
isLoading = isLoading,
|
||||
isDestructive = true
|
||||
)
|
||||
|
||||
ConfirmDialog(
|
||||
showDialog = showResetKstatDialog,
|
||||
onDismiss = { showResetKstatDialog = false },
|
||||
@@ -1106,50 +994,6 @@ fun SuSFSConfigScreen(
|
||||
}
|
||||
}
|
||||
|
||||
SuSFSTab.SUS_MOUNTS -> {
|
||||
OutlinedButton(
|
||||
onClick = { showResetMountsDialog = true },
|
||||
enabled = !isLoading && susMounts.isNotEmpty(),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.RestoreFromTrash,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
stringResource(R.string.susfs_reset_mounts_title),
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SuSFSTab.TRY_UMOUNT -> {
|
||||
OutlinedButton(
|
||||
onClick = { showResetUmountsDialog = true },
|
||||
enabled = !isLoading && tryUmounts.isNotEmpty(),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.RestoreFromTrash,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
stringResource(R.string.susfs_reset_umounts_title),
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SuSFSTab.KSTAT_CONFIG -> {
|
||||
OutlinedButton(
|
||||
onClick = { showResetKstatDialog = true },
|
||||
@@ -1325,6 +1169,32 @@ fun SuSFSConfigScreen(
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
},
|
||||
hideSusMountsForAllProcs = hideSusMountsForAllProcs,
|
||||
onHideSusMountsForAllProcsChange = { hideForAll ->
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
if (SuSFSManager.setHideSusMountsForAllProcs(
|
||||
context,
|
||||
hideForAll
|
||||
)
|
||||
) {
|
||||
hideSusMountsForAllProcs = hideForAll
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
},
|
||||
umountForZygoteIsoService = umountForZygoteIsoService,
|
||||
onUmountForZygoteIsoServiceChange = { enabled ->
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
val success =
|
||||
SuSFSManager.setUmountForZygoteIsoService(context, enabled)
|
||||
if (success) {
|
||||
umountForZygoteIsoService = enabled
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1390,76 +1260,6 @@ fun SuSFSConfigScreen(
|
||||
}
|
||||
)
|
||||
}
|
||||
SuSFSTab.SUS_MOUNTS -> {
|
||||
val isSusVersion158 = remember { isSusVersion158() }
|
||||
|
||||
SusMountsContent(
|
||||
susMounts = susMounts,
|
||||
hideSusMountsForAllProcs = hideSusMountsForAllProcs,
|
||||
isSusVersion158 = isSusVersion158,
|
||||
isLoading = isLoading,
|
||||
onAddMount = { showAddMountDialog = true },
|
||||
onRemoveMount = { mount ->
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
if (SuSFSManager.removeSusMount(context, mount)) {
|
||||
susMounts = SuSFSManager.getSusMounts(context)
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
},
|
||||
onEditMount = { mount ->
|
||||
editingMount = mount
|
||||
showAddMountDialog = true
|
||||
},
|
||||
onToggleHideSusMountsForAllProcs = { hideForAll ->
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
if (SuSFSManager.setHideSusMountsForAllProcs(
|
||||
context,
|
||||
hideForAll
|
||||
)
|
||||
) {
|
||||
hideSusMountsForAllProcs = hideForAll
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
SuSFSTab.TRY_UMOUNT -> {
|
||||
TryUmountContent(
|
||||
tryUmounts = tryUmounts,
|
||||
umountForZygoteIsoService = umountForZygoteIsoService,
|
||||
isLoading = isLoading,
|
||||
onAddUmount = { showAddUmountDialog = true },
|
||||
onRemoveUmount = { umountEntry ->
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
if (SuSFSManager.removeTryUmount(context, umountEntry)) {
|
||||
tryUmounts = SuSFSManager.getTryUmounts(context)
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
},
|
||||
onEditUmount = { umountEntry ->
|
||||
editingUmount = umountEntry
|
||||
showAddUmountDialog = true
|
||||
},
|
||||
onToggleUmountForZygoteIsoService = { enabled ->
|
||||
coroutineScope.launch {
|
||||
isLoading = true
|
||||
val success =
|
||||
SuSFSManager.setUmountForZygoteIsoService(context, enabled)
|
||||
if (success) {
|
||||
umountForZygoteIsoService = enabled
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
SuSFSTab.KSTAT_CONFIG -> {
|
||||
KstatConfigContent(
|
||||
@@ -1570,7 +1370,11 @@ private fun BasicSettingsContent(
|
||||
enableCleanupResidue: Boolean,
|
||||
onEnableCleanupResidueChange: (Boolean) -> Unit,
|
||||
enableAvcLogSpoofing: Boolean,
|
||||
onEnableAvcLogSpoofingChange: (Boolean) -> Unit
|
||||
onEnableAvcLogSpoofingChange: (Boolean) -> Unit,
|
||||
hideSusMountsForAllProcs: Boolean,
|
||||
onHideSusMountsForAllProcsChange: (Boolean) -> Unit,
|
||||
umountForZygoteIsoService: Boolean,
|
||||
onUmountForZygoteIsoServiceChange: (Boolean) -> Unit
|
||||
) {
|
||||
var scriptLocationExpanded by remember { mutableStateOf(false) }
|
||||
val isAbDevice = produceState(initialValue = false) {
|
||||
@@ -1946,6 +1750,69 @@ private fun BasicSettingsContent(
|
||||
}
|
||||
}
|
||||
|
||||
// 对所有进程隐藏sus挂载开关(仅在1.5.8+版本显示)
|
||||
val isSusVersion158 = isSusVersion158()
|
||||
if (isSusVersion158) {
|
||||
SusMountHidingControlCard(
|
||||
hideSusMountsForAllProcs = hideSusMountsForAllProcs,
|
||||
isLoading = isLoading,
|
||||
onToggleHiding = onHideSusMountsForAllProcsChange
|
||||
)
|
||||
}
|
||||
|
||||
// 卸载 Zygote 隔离服务开关(仅在1.5.8+版本显示)
|
||||
if (isSusVersion158) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Security,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.umount_zygote_iso_service),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.umount_zygote_iso_service_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
lineHeight = 14.sp
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = umountForZygoteIsoService,
|
||||
onCheckedChange = onUmountForZygoteIsoServiceChange,
|
||||
enabled = !isLoading
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 槽位信息按钮
|
||||
if (isAbDevice) {
|
||||
Card(
|
||||
|
||||
@@ -401,126 +401,6 @@ fun AppIcon(
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加尝试卸载对话框
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AddTryUmountDialog(
|
||||
showDialog: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (String, Int) -> Unit,
|
||||
isLoading: Boolean,
|
||||
initialPath: String = "",
|
||||
initialMode: Int = 0
|
||||
) {
|
||||
var newUmountPath by remember { mutableStateOf("") }
|
||||
var newUmountMode by remember { mutableIntStateOf(0) }
|
||||
var umountModeExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
// 当对话框显示时,设置初始值
|
||||
LaunchedEffect(showDialog, initialPath, initialMode) {
|
||||
if (showDialog) {
|
||||
newUmountPath = initialPath
|
||||
newUmountMode = initialMode
|
||||
}
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(if (initialPath.isNotEmpty()) R.string.susfs_edit_try_umount else R.string.susfs_add_try_umount),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = newUmountPath,
|
||||
onValueChange = { newUmountPath = it },
|
||||
label = { Text(stringResource(R.string.susfs_path_label)) },
|
||||
placeholder = { Text(stringResource(R.string.susfs_path_placeholder)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = umountModeExpanded,
|
||||
onExpandedChange = { umountModeExpanded = !umountModeExpanded }
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = if (newUmountMode == 0)
|
||||
stringResource(R.string.susfs_umount_mode_normal)
|
||||
else
|
||||
stringResource(R.string.susfs_umount_mode_detach),
|
||||
onValueChange = { },
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(R.string.susfs_umount_mode_label)) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = umountModeExpanded) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = umountModeExpanded,
|
||||
onDismissRequest = { umountModeExpanded = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.susfs_umount_mode_normal)) },
|
||||
onClick = {
|
||||
newUmountMode = 0
|
||||
umountModeExpanded = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.susfs_umount_mode_detach)) },
|
||||
onClick = {
|
||||
newUmountMode = 1
|
||||
umountModeExpanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
if (newUmountPath.isNotBlank()) {
|
||||
onConfirm(newUmountPath.trim(), newUmountMode)
|
||||
newUmountPath = ""
|
||||
newUmountMode = 0
|
||||
}
|
||||
},
|
||||
enabled = newUmountPath.isNotBlank() && !isLoading,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Text(stringResource(if (initialPath.isNotEmpty()) R.string.susfs_save else R.string.add))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
newUmountPath = ""
|
||||
newUmountMode = 0
|
||||
},
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Kstat静态配置对话框
|
||||
*/
|
||||
|
||||
@@ -52,7 +52,7 @@ fun SusPathsContent(
|
||||
|
||||
val (appPathGroups, otherPaths) = remember(susPaths) {
|
||||
val appPathRegex = Regex(".*/Android/data/([^/]+)/?.*")
|
||||
val uidPathRegex = Regex("/sys/fs/cgroup/uid_([0-9]+)")
|
||||
val uidPathRegex = Regex("/sys/fs/cgroup(?:/[^/]+)*/uid_([0-9]+)")
|
||||
val appPathMap = mutableMapOf<String, MutableList<String>>()
|
||||
val uidToPackageMap = mutableMapOf<String, String>()
|
||||
val others = mutableListOf<String>()
|
||||
@@ -420,210 +420,6 @@ fun SusMapsContent(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SUS挂载内容组件
|
||||
*/
|
||||
@Composable
|
||||
fun SusMountsContent(
|
||||
susMounts: Set<String>,
|
||||
hideSusMountsForAllProcs: Boolean,
|
||||
isSusVersion158: Boolean,
|
||||
isLoading: Boolean,
|
||||
onAddMount: () -> Unit,
|
||||
onRemoveMount: (String) -> Unit,
|
||||
onEditMount: ((String) -> Unit)? = null,
|
||||
onToggleHideSusMountsForAllProcs: (Boolean) -> Unit
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
if (isSusVersion158) {
|
||||
item {
|
||||
SusMountHidingControlCard(
|
||||
hideSusMountsForAllProcs = hideSusMountsForAllProcs,
|
||||
isLoading = isLoading,
|
||||
onToggleHiding = onToggleHideSusMountsForAllProcs
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (susMounts.isEmpty()) {
|
||||
item {
|
||||
EmptyStateCard(
|
||||
message = stringResource(R.string.susfs_no_mounts_configured)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
items(susMounts.toList()) { mount ->
|
||||
PathItemCard(
|
||||
path = mount,
|
||||
icon = Icons.Default.Storage,
|
||||
onDelete = { onRemoveMount(mount) },
|
||||
onEdit = if (onEditMount != null) { { onEditMount(mount) } } else null,
|
||||
isLoading = isLoading
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Button(
|
||||
onClick = onAddMount,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(48.dp),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = stringResource(R.string.add))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试卸载内容组件
|
||||
*/
|
||||
@Composable
|
||||
fun TryUmountContent(
|
||||
tryUmounts: Set<String>,
|
||||
umountForZygoteIsoService: Boolean,
|
||||
isLoading: Boolean,
|
||||
onAddUmount: () -> Unit,
|
||||
onRemoveUmount: (String) -> Unit,
|
||||
onEditUmount: ((String) -> Unit)? = null,
|
||||
onToggleUmountForZygoteIsoService: (Boolean) -> Unit
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
if (isSusVersion158()) {
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Security,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.umount_zygote_iso_service),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.umount_zygote_iso_service_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
lineHeight = 14.sp
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = umountForZygoteIsoService,
|
||||
onCheckedChange = onToggleUmountForZygoteIsoService,
|
||||
enabled = !isLoading
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tryUmounts.isEmpty()) {
|
||||
item {
|
||||
EmptyStateCard(
|
||||
message = stringResource(R.string.susfs_no_umounts_configured)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
items(tryUmounts.toList()) { umountEntry ->
|
||||
val parts = umountEntry.split("|")
|
||||
val path = if (parts.isNotEmpty()) parts[0] else umountEntry
|
||||
val mode = if (parts.size > 1) parts[1] else "0"
|
||||
val modeText = if (mode == "0")
|
||||
stringResource(R.string.susfs_umount_mode_normal_short)
|
||||
else
|
||||
stringResource(R.string.susfs_umount_mode_detach_short)
|
||||
|
||||
PathItemCard(
|
||||
path = path,
|
||||
icon = Icons.Default.Storage,
|
||||
additionalInfo = stringResource(R.string.susfs_umount_mode_display, modeText, mode),
|
||||
onDelete = { onRemoveUmount(umountEntry) },
|
||||
onEdit = if (onEditUmount != null) { { onEditUmount(umountEntry) } } else null,
|
||||
isLoading = isLoading
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Button(
|
||||
onClick = onAddUmount,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(48.dp),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = stringResource(R.string.add))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kstat配置内容组件
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.sukisu.ultra.ui.util.getRootShell
|
||||
import com.sukisu.ultra.ui.util.getSuSFSVersion
|
||||
import com.sukisu.ultra.ui.util.getSuSFSFeatures
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
@@ -44,8 +45,6 @@ object SuSFSManager {
|
||||
private const val KEY_SUS_LOOP_PATHS = "sus_loop_paths"
|
||||
|
||||
private const val KEY_SUS_MAPS = "sus_maps"
|
||||
private const val KEY_SUS_MOUNTS = "sus_mounts"
|
||||
private const val KEY_TRY_UMOUNTS = "try_umounts"
|
||||
private const val KEY_ANDROID_DATA_PATH = "android_data_path"
|
||||
private const val KEY_SDCARD_PATH = "sdcard_path"
|
||||
private const val KEY_ENABLE_LOG = "enable_log"
|
||||
@@ -71,7 +70,7 @@ object SuSFSManager {
|
||||
const val MAX_SUSFS_VERSION = "2.0.0"
|
||||
private const val BACKUP_FILE_EXTENSION = ".susfs_backup"
|
||||
private const val MEDIA_DATA_PATH = "/data/media/0/Android/data"
|
||||
private const val CGROUP_UID_PATH_PREFIX = "/sys/fs/cgroup/uid_"
|
||||
private const val CGROUP_BASE_PATH = "/sys/fs/cgroup"
|
||||
|
||||
data class SlotInfo(val slotName: String, val uname: String, val buildTime: String)
|
||||
data class CommandResult(val isSuccess: Boolean, val output: String, val errorOutput: String = "")
|
||||
@@ -157,8 +156,6 @@ object SuSFSManager {
|
||||
val susPaths: Set<String>,
|
||||
val susLoopPaths: Set<String>,
|
||||
val susMaps: Set<String>,
|
||||
val susMounts: Set<String>,
|
||||
val tryUmounts: Set<String>,
|
||||
val androidDataPath: String,
|
||||
val sdcardPath: String,
|
||||
val enableLog: Boolean,
|
||||
@@ -180,8 +177,6 @@ object SuSFSManager {
|
||||
susPaths.isNotEmpty() ||
|
||||
susLoopPaths.isNotEmpty() ||
|
||||
susMaps.isNotEmpty() ||
|
||||
susMounts.isNotEmpty() ||
|
||||
tryUmounts.isNotEmpty() ||
|
||||
kstatConfigs.isNotEmpty() ||
|
||||
addKstatPaths.isNotEmpty()
|
||||
}
|
||||
@@ -271,8 +266,6 @@ object SuSFSManager {
|
||||
susPaths = getSusPaths(context),
|
||||
susLoopPaths = getSusLoopPaths(context),
|
||||
susMaps = getSusMaps(context),
|
||||
susMounts = getSusMounts(context),
|
||||
tryUmounts = getTryUmounts(context),
|
||||
androidDataPath = getAndroidDataPath(context),
|
||||
sdcardPath = getSdcardPath(context),
|
||||
enableLog = getEnableLogState(context),
|
||||
@@ -381,18 +374,6 @@ object SuSFSManager {
|
||||
fun getSusMaps(context: Context): Set<String> =
|
||||
getPrefs(context).getStringSet(KEY_SUS_MAPS, emptySet()) ?: emptySet()
|
||||
|
||||
fun saveSusMounts(context: Context, mounts: Set<String>) =
|
||||
getPrefs(context).edit { putStringSet(KEY_SUS_MOUNTS, mounts) }
|
||||
|
||||
fun getSusMounts(context: Context): Set<String> =
|
||||
getPrefs(context).getStringSet(KEY_SUS_MOUNTS, emptySet()) ?: emptySet()
|
||||
|
||||
fun saveTryUmounts(context: Context, umounts: Set<String>) =
|
||||
getPrefs(context).edit { putStringSet(KEY_TRY_UMOUNTS, umounts) }
|
||||
|
||||
fun getTryUmounts(context: Context): Set<String> =
|
||||
getPrefs(context).getStringSet(KEY_TRY_UMOUNTS, emptySet()) ?: emptySet()
|
||||
|
||||
fun saveKstatConfigs(context: Context, configs: Set<String>) =
|
||||
getPrefs(context).edit { putStringSet(KEY_KSTAT_CONFIGS, configs) }
|
||||
|
||||
@@ -494,7 +475,44 @@ object SuSFSManager {
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildUidPath(uid: Int): String = "$CGROUP_UID_PATH_PREFIX$uid"
|
||||
private fun checkPathExists(path: String): Boolean {
|
||||
return try {
|
||||
val shell = try {
|
||||
getRootShell()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
val file = if (shell != null) {
|
||||
SuFile(path).apply { setShell(shell) }
|
||||
} else {
|
||||
File(path)
|
||||
}
|
||||
|
||||
file.exists() && file.isDirectory
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildUidPath(uid: Int): String {
|
||||
val possiblePaths = listOf(
|
||||
"$CGROUP_BASE_PATH/uid_$uid",
|
||||
"$CGROUP_BASE_PATH/apps/uid_$uid",
|
||||
"$CGROUP_BASE_PATH/system/uid_$uid",
|
||||
"$CGROUP_BASE_PATH/freezer/uid_$uid",
|
||||
"$CGROUP_BASE_PATH/memory/uid_$uid",
|
||||
"$CGROUP_BASE_PATH/cpuset/uid_$uid",
|
||||
"$CGROUP_BASE_PATH/cpu/uid_$uid"
|
||||
)
|
||||
|
||||
for (path in possiblePaths) {
|
||||
if (checkPathExists(path)) {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return possiblePaths[0]
|
||||
}
|
||||
|
||||
|
||||
// 快捷添加应用路径
|
||||
@@ -547,8 +565,6 @@ object SuSFSManager {
|
||||
KEY_SUS_PATHS to getSusPaths(context),
|
||||
KEY_SUS_LOOP_PATHS to getSusLoopPaths(context),
|
||||
KEY_SUS_MAPS to getSusMaps(context),
|
||||
KEY_SUS_MOUNTS to getSusMounts(context),
|
||||
KEY_TRY_UMOUNTS to getTryUmounts(context),
|
||||
KEY_ANDROID_DATA_PATH to getAndroidDataPath(context),
|
||||
KEY_SDCARD_PATH to getSdcardPath(context),
|
||||
KEY_ENABLE_LOG to getEnableLogState(context),
|
||||
@@ -860,18 +876,12 @@ object SuSFSManager {
|
||||
|
||||
val featureMap = mapOf(
|
||||
"CONFIG_KSU_SUSFS_SUS_PATH" to context.getString(R.string.sus_path_feature_label),
|
||||
"CONFIG_KSU_SUSFS_SUS_MOUNT" to context.getString(R.string.sus_mount_feature_label),
|
||||
"CONFIG_KSU_SUSFS_TRY_UMOUNT" to context.getString(R.string.try_umount_feature_label),
|
||||
"CONFIG_KSU_SUSFS_SPOOF_UNAME" to context.getString(R.string.spoof_uname_feature_label),
|
||||
"CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG" to context.getString(R.string.spoof_cmdline_feature_label),
|
||||
"CONFIG_KSU_SUSFS_OPEN_REDIRECT" to context.getString(R.string.open_redirect_feature_label),
|
||||
"CONFIG_KSU_SUSFS_ENABLE_LOG" to context.getString(R.string.enable_log_feature_label),
|
||||
"CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT" to context.getString(R.string.auto_default_mount_feature_label),
|
||||
"CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT" to context.getString(R.string.auto_bind_mount_feature_label),
|
||||
"CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT" to context.getString(R.string.auto_try_umount_bind_feature_label),
|
||||
"CONFIG_KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS" to context.getString(R.string.hide_symbols_feature_label),
|
||||
"CONFIG_KSU_SUSFS_SUS_KSTAT" to context.getString(R.string.sus_kstat_feature_label),
|
||||
"CONFIG_KSU_SUSFS_SUS_SU" to context.getString(R.string.sus_su_feature_label)
|
||||
)
|
||||
|
||||
|
||||
@@ -893,18 +903,12 @@ object SuSFSManager {
|
||||
private fun getDefaultDisabledFeatures(context: Context): List<EnabledFeature> {
|
||||
val defaultFeatures = listOf(
|
||||
"sus_path_feature_label" to context.getString(R.string.sus_path_feature_label),
|
||||
"sus_mount_feature_label" to context.getString(R.string.sus_mount_feature_label),
|
||||
"try_umount_feature_label" to context.getString(R.string.try_umount_feature_label),
|
||||
"spoof_uname_feature_label" to context.getString(R.string.spoof_uname_feature_label),
|
||||
"spoof_cmdline_feature_label" to context.getString(R.string.spoof_cmdline_feature_label),
|
||||
"open_redirect_feature_label" to context.getString(R.string.open_redirect_feature_label),
|
||||
"enable_log_feature_label" to context.getString(R.string.enable_log_feature_label),
|
||||
"auto_default_mount_feature_label" to context.getString(R.string.auto_default_mount_feature_label),
|
||||
"auto_bind_mount_feature_label" to context.getString(R.string.auto_bind_mount_feature_label),
|
||||
"auto_try_umount_bind_feature_label" to context.getString(R.string.auto_try_umount_bind_feature_label),
|
||||
"hide_symbols_feature_label" to context.getString(R.string.hide_symbols_feature_label),
|
||||
"sus_kstat_feature_label" to context.getString(R.string.sus_kstat_feature_label),
|
||||
"sus_su_feature_label" to context.getString(R.string.sus_su_feature_label)
|
||||
)
|
||||
|
||||
return defaultFeatures.map { (_, displayName) ->
|
||||
@@ -1188,107 +1192,6 @@ object SuSFSManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加SUS挂载
|
||||
suspend fun addSusMount(context: Context, mount: String): Boolean {
|
||||
val success = executeSusfsCommand(context, "add_sus_mount '$mount'")
|
||||
if (success) {
|
||||
saveSusMounts(context, getSusMounts(context) + mount)
|
||||
if (isAutoStartEnabled(context)) updateMagiskModule(context)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun removeSusMount(context: Context, mount: String): Boolean {
|
||||
saveSusMounts(context, getSusMounts(context) - mount)
|
||||
if (isAutoStartEnabled(context)) updateMagiskModule(context)
|
||||
showToast(context, "Removed SUS mount: $mount")
|
||||
return true
|
||||
}
|
||||
|
||||
// 编辑SUS挂载
|
||||
suspend fun editSusMount(context: Context, oldMount: String, newMount: String): Boolean {
|
||||
return try {
|
||||
val currentMounts = getSusMounts(context).toMutableSet()
|
||||
if (!currentMounts.remove(oldMount)) {
|
||||
showToast(context, "Original mount not found: $oldMount")
|
||||
return false
|
||||
}
|
||||
|
||||
saveSusMounts(context, currentMounts)
|
||||
|
||||
val success = addSusMount(context, newMount)
|
||||
|
||||
if (success) {
|
||||
showToast(context, "SUS mount updated: $oldMount -> $newMount")
|
||||
return true
|
||||
} else {
|
||||
// 如果添加新挂载点失败,恢复旧挂载点
|
||||
currentMounts.add(oldMount)
|
||||
saveSusMounts(context, currentMounts)
|
||||
if (isAutoStartEnabled(context)) updateMagiskModule(context)
|
||||
showToast(context, "Failed to update mount, reverted to original")
|
||||
return false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
showToast(context, "Error updating SUS mount: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// 添加尝试卸载
|
||||
suspend fun addTryUmount(context: Context, path: String, mode: Int): Boolean {
|
||||
val commandSuccess = executeSusfsCommand(context, "add_try_umount '$path' $mode")
|
||||
saveTryUmounts(context, getTryUmounts(context) + "$path|$mode")
|
||||
if (isAutoStartEnabled(context)) updateMagiskModule(context)
|
||||
|
||||
showToast(context, if (commandSuccess) {
|
||||
context.getString(R.string.susfs_try_umount_added_success, path)
|
||||
} else {
|
||||
context.getString(R.string.susfs_try_umount_added_saved, path)
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun removeTryUmount(context: Context, umountEntry: String): Boolean {
|
||||
saveTryUmounts(context, getTryUmounts(context) - umountEntry)
|
||||
if (isAutoStartEnabled(context)) updateMagiskModule(context)
|
||||
val path = umountEntry.split("|").firstOrNull() ?: umountEntry
|
||||
showToast(context, "Removed Try to uninstall: $path")
|
||||
return true
|
||||
}
|
||||
|
||||
// 编辑尝试卸载
|
||||
suspend fun editTryUmount(context: Context, oldEntry: String, newPath: String, newMode: Int): Boolean {
|
||||
return try {
|
||||
val currentUmounts = getTryUmounts(context).toMutableSet()
|
||||
if (!currentUmounts.remove(oldEntry)) {
|
||||
showToast(context, "Original umount entry not found: $oldEntry")
|
||||
return false
|
||||
}
|
||||
|
||||
saveTryUmounts(context, currentUmounts)
|
||||
|
||||
val success = addTryUmount(context, newPath, newMode)
|
||||
|
||||
if (success) {
|
||||
showToast(context, "Try umount updated: $oldEntry -> $newPath|$newMode")
|
||||
return true
|
||||
} else {
|
||||
// 如果添加新条目失败,恢复旧条目
|
||||
currentUmounts.add(oldEntry)
|
||||
saveTryUmounts(context, currentUmounts)
|
||||
if (isAutoStartEnabled(context)) updateMagiskModule(context)
|
||||
showToast(context, "Failed to update umount entry, reverted to original")
|
||||
return false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
showToast(context, "Error updating try umount: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Zygote隔离服务卸载控制
|
||||
suspend fun setUmountForZygoteIsoService(context: Context, enabled: Boolean): Boolean {
|
||||
if (!isSusVersion158()) {
|
||||
|
||||
@@ -26,13 +26,13 @@ object ScriptGenerator {
|
||||
}
|
||||
|
||||
// 日志相关的通用脚本片段
|
||||
private fun generateLogSetup(logFileName: String): String = """
|
||||
private fun generateLogSetup(logFileName: String): String = $$"""
|
||||
# 日志目录
|
||||
LOG_DIR="$LOG_DIR"
|
||||
LOG_FILE="${'$'}LOG_DIR/$logFileName"
|
||||
LOG_DIR="$$LOG_DIR"
|
||||
LOG_FILE="$LOG_DIR/$$logFileName"
|
||||
|
||||
# 创建日志目录
|
||||
mkdir -p "${'$'}LOG_DIR"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
# 获取当前时间
|
||||
get_current_time() {
|
||||
@@ -41,11 +41,11 @@ object ScriptGenerator {
|
||||
""".trimIndent()
|
||||
|
||||
// 二进制文件检查的通用脚本片段
|
||||
private fun generateBinaryCheck(targetPath: String): String = """
|
||||
private fun generateBinaryCheck(targetPath: String): String = $$"""
|
||||
# 检查SuSFS二进制文件
|
||||
SUSFS_BIN="$targetPath"
|
||||
if [ ! -f "${'$'}SUSFS_BIN" ]; then
|
||||
echo "$(get_current_time): SuSFS二进制文件未找到: ${'$'}SUSFS_BIN" >> "${'$'}LOG_FILE"
|
||||
SUSFS_BIN="$$targetPath"
|
||||
if [ ! -f "$SUSFS_BIN" ]; then
|
||||
echo "$(get_current_time): SuSFS二进制文件未找到: $SUSFS_BIN" >> "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
""".trimIndent()
|
||||
@@ -94,7 +94,7 @@ object ScriptGenerator {
|
||||
generateCleanupResidueSection()
|
||||
}
|
||||
|
||||
appendLine("echo \"$(get_current_time): Service脚本执行完成\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"echo \"$(get_current_time): Service脚本执行完成\" >> \"$LOG_FILE\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,16 +112,16 @@ object ScriptGenerator {
|
||||
private fun StringBuilder.generateLogSettingSection(enableLog: Boolean) {
|
||||
appendLine("# 设置日志启用状态")
|
||||
val logValue = if (enableLog) 1 else 0
|
||||
appendLine("\"${'$'}SUSFS_BIN\" enable_log $logValue")
|
||||
appendLine("echo \"$(get_current_time): 日志功能设置为: ${if (enableLog) "启用" else "禁用"}\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" enable_log $$logValue")
|
||||
appendLine($$"echo \"$(get_current_time): 日志功能设置为: $${if (enableLog) "启用" else "禁用"}\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
private fun StringBuilder.generateAvcLogSpoofingSection(enableAvcLogSpoofing: Boolean) {
|
||||
appendLine("# 设置AVC日志欺骗状态")
|
||||
val avcLogValue = if (enableAvcLogSpoofing) 1 else 0
|
||||
appendLine("\"${'$'}SUSFS_BIN\" enable_avc_log_spoofing $avcLogValue")
|
||||
appendLine("echo \"$(get_current_time): AVC日志欺骗功能设置为: ${if (enableAvcLogSpoofing) "启用" else "禁用"}\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" enable_avc_log_spoofing $$avcLogValue")
|
||||
appendLine($$"echo \"$(get_current_time): AVC日志欺骗功能设置为: $${if (enableAvcLogSpoofing) "启用" else "禁用"}\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ object ScriptGenerator {
|
||||
if (susPaths.isNotEmpty()) {
|
||||
appendLine("# 添加SUS路径")
|
||||
susPaths.forEach { path ->
|
||||
appendLine("\"${'$'}SUSFS_BIN\" add_sus_path '$path'")
|
||||
appendLine("echo \"$(get_current_time): 添加SUS路径: $path\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" add_sus_path '$$path'")
|
||||
appendLine($$"echo \"$(get_current_time): 添加SUS路径: $$path\" >> \"$LOG_FILE\"")
|
||||
}
|
||||
appendLine()
|
||||
}
|
||||
@@ -140,8 +140,8 @@ object ScriptGenerator {
|
||||
if (susLoopPaths.isNotEmpty()) {
|
||||
appendLine("# 添加SUS循环路径")
|
||||
susLoopPaths.forEach { path ->
|
||||
appendLine("\"${'$'}SUSFS_BIN\" add_sus_path_loop '$path'")
|
||||
appendLine("echo \"$(get_current_time): 添加SUS循环路径: $path\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" add_sus_path_loop '$$path'")
|
||||
appendLine($$"echo \"$(get_current_time): 添加SUS循环路径: $$path\" >> \"$LOG_FILE\"")
|
||||
}
|
||||
appendLine()
|
||||
}
|
||||
@@ -156,8 +156,8 @@ object ScriptGenerator {
|
||||
if (addKstatPaths.isNotEmpty()) {
|
||||
appendLine("# 添加Kstat路径")
|
||||
addKstatPaths.forEach { path ->
|
||||
appendLine("\"${'$'}SUSFS_BIN\" add_sus_kstat '$path'")
|
||||
appendLine("echo \"$(get_current_time): 添加Kstat路径: $path\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" add_sus_kstat '$$path'")
|
||||
appendLine($$"echo \"$(get_current_time): 添加Kstat路径: $$path\" >> \"$LOG_FILE\"")
|
||||
}
|
||||
appendLine()
|
||||
}
|
||||
@@ -171,11 +171,11 @@ object ScriptGenerator {
|
||||
val path = parts[0]
|
||||
val params = parts.drop(1).joinToString("' '", "'", "'")
|
||||
appendLine()
|
||||
appendLine("\"${'$'}SUSFS_BIN\" add_sus_kstat_statically '$path' $params")
|
||||
appendLine("echo \"$(get_current_time): 添加Kstat静态配置: $path\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" add_sus_kstat_statically '$$path' $$params")
|
||||
appendLine($$"echo \"$(get_current_time): 添加Kstat静态配置: $$path\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
appendLine("\"${'$'}SUSFS_BIN\" update_sus_kstat '$path'")
|
||||
appendLine("echo \"$(get_current_time): 更新Kstat配置: $path\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" update_sus_kstat '$$path'")
|
||||
appendLine($$"echo \"$(get_current_time): 更新Kstat配置: $$path\" >> \"$LOG_FILE\"")
|
||||
}
|
||||
}
|
||||
appendLine()
|
||||
@@ -185,8 +185,8 @@ object ScriptGenerator {
|
||||
private fun StringBuilder.generateUnameSection(config: SuSFSManager.ModuleConfig) {
|
||||
if (!config.executeInPostFsData && (config.unameValue != DEFAULT_UNAME || config.buildTimeValue != DEFAULT_BUILD_TIME)) {
|
||||
appendLine("# 设置uname和构建时间")
|
||||
appendLine("\"${'$'}SUSFS_BIN\" set_uname '${config.unameValue}' '${config.buildTimeValue}'")
|
||||
appendLine("echo \"$(get_current_time): 设置uname为: ${config.unameValue}, 构建时间为: ${config.buildTimeValue}\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" set_uname '$${config.unameValue}' '$${config.buildTimeValue}'")
|
||||
appendLine($$"echo \"$(get_current_time): 设置uname为: $${config.unameValue}, 构建时间为: $${config.buildTimeValue}\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
@@ -194,44 +194,44 @@ object ScriptGenerator {
|
||||
private fun StringBuilder.generateHideBlSection() {
|
||||
appendLine("# 隐藏BL 来自 Shamiko 脚本")
|
||||
appendLine(
|
||||
"""
|
||||
$$"""
|
||||
RESETPROP_BIN="/data/adb/ksu/bin/resetprop"
|
||||
|
||||
check_reset_prop() {
|
||||
local NAME=$1
|
||||
local EXPECTED=$2
|
||||
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
|
||||
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
|
||||
local VALUE=$("$RESETPROP_BIN" $NAME)
|
||||
[ -z $VALUE ] || [ $VALUE = $EXPECTED ] || "$RESETPROP_BIN" $NAME $EXPECTED
|
||||
}
|
||||
|
||||
check_missing_prop() {
|
||||
local NAME=$1
|
||||
local EXPECTED=$2
|
||||
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
|
||||
[ -z ${'$'}VALUE ] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
|
||||
local VALUE=$("$RESETPROP_BIN" $NAME)
|
||||
[ -z $VALUE ] && "$RESETPROP_BIN" $NAME $EXPECTED
|
||||
}
|
||||
|
||||
check_missing_match_prop() {
|
||||
local NAME=$1
|
||||
local EXPECTED=$2
|
||||
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
|
||||
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
|
||||
[ -z ${'$'}VALUE ] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
|
||||
local VALUE=$("$RESETPROP_BIN" $NAME)
|
||||
[ -z $VALUE ] || [ $VALUE = $EXPECTED ] || "$RESETPROP_BIN" $NAME $EXPECTED
|
||||
[ -z $VALUE ] && "$RESETPROP_BIN" $NAME $EXPECTED
|
||||
}
|
||||
|
||||
contains_reset_prop() {
|
||||
local NAME=$1
|
||||
local CONTAINS=$2
|
||||
local NEWVAL=$3
|
||||
case "$("${'$'}RESETPROP_BIN" ${'$'}NAME)" in
|
||||
*"${'$'}CONTAINS"*) "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}NEWVAL ;;
|
||||
case "$("$RESETPROP_BIN" $NAME)" in
|
||||
*"$CONTAINS"*) "$RESETPROP_BIN" $NAME $NEWVAL ;;
|
||||
esac
|
||||
}
|
||||
""".trimIndent())
|
||||
appendLine()
|
||||
appendLine("sleep 30")
|
||||
appendLine()
|
||||
appendLine("\"${'$'}RESETPROP_BIN\" -w sys.boot_completed 0")
|
||||
appendLine($$"\"$RESETPROP_BIN\" -w sys.boot_completed 0")
|
||||
|
||||
// 添加所有系统属性重置
|
||||
val systemProps = listOf(
|
||||
@@ -292,27 +292,28 @@ object ScriptGenerator {
|
||||
// 清理残留脚本生成
|
||||
private fun StringBuilder.generateCleanupResidueSection() {
|
||||
appendLine("# 清理工具残留文件")
|
||||
appendLine("echo \"$(get_current_time): 开始清理工具残留\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"echo \"$(get_current_time): 开始清理工具残留\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
|
||||
// 定义清理函数
|
||||
appendLine("""
|
||||
appendLine(
|
||||
$$"""
|
||||
cleanup_path() {
|
||||
local path="$1"
|
||||
local desc="$2"
|
||||
local current="$3"
|
||||
local total="$4"
|
||||
|
||||
if [ -n "${'$'}desc" ]; then
|
||||
echo "$(get_current_time): [${'$'}current/${'$'}total] 清理: ${'$'}path (${'$'}desc)" >> "${'$'}LOG_FILE"
|
||||
if [ -n "$desc" ]; then
|
||||
echo "$(get_current_time): [$current/$total] 清理: $path ($desc)" >> "$LOG_FILE"
|
||||
else
|
||||
echo "$(get_current_time): [${'$'}current/${'$'}total] 清理: ${'$'}path" >> "${'$'}LOG_FILE"
|
||||
echo "$(get_current_time): [$current/$total] 清理: $path" >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
if rm -rf "${'$'}path" 2>/dev/null; then
|
||||
echo "$(get_current_time): ✓ 成功清理: ${'$'}path" >> "${'$'}LOG_FILE"
|
||||
if rm -rf "$path" 2>/dev/null; then
|
||||
echo "$(get_current_time): ✓ 成功清理: $path" >> "$LOG_FILE"
|
||||
else
|
||||
echo "$(get_current_time): ✗ 清理失败或不存在: ${'$'}path" >> "${'$'}LOG_FILE"
|
||||
echo "$(get_current_time): ✗ 清理失败或不存在: $path" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
""".trimIndent())
|
||||
@@ -360,11 +361,11 @@ object ScriptGenerator {
|
||||
|
||||
cleanupPaths.forEachIndexed { index, (path, desc) ->
|
||||
val current = index + 1
|
||||
appendLine("cleanup_path '$path' '$desc' $current \$TOTAL")
|
||||
appendLine($$"cleanup_path '$$path' '$$desc' $$current $TOTAL")
|
||||
}
|
||||
|
||||
appendLine()
|
||||
appendLine("echo \"$(get_current_time): 工具残留清理完成\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"echo \"$(get_current_time): 工具残留清理完成\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
@@ -381,14 +382,14 @@ object ScriptGenerator {
|
||||
appendLine()
|
||||
appendLine(generateBinaryCheck(config.targetPath))
|
||||
appendLine()
|
||||
appendLine("echo \"$(get_current_time): Post-FS-Data脚本开始执行\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"echo \"$(get_current_time): Post-FS-Data脚本开始执行\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
|
||||
// 设置uname和构建时间 - 只有在选择在post-fs-data中执行时才执行
|
||||
if (config.executeInPostFsData && (config.unameValue != DEFAULT_UNAME || config.buildTimeValue != DEFAULT_BUILD_TIME)) {
|
||||
appendLine("# 设置uname和构建时间")
|
||||
appendLine("\"${'$'}SUSFS_BIN\" set_uname '${config.unameValue}' '${config.buildTimeValue}'")
|
||||
appendLine("echo \"$(get_current_time): 设置uname为: ${config.unameValue}, 构建时间为: ${config.buildTimeValue}\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" set_uname '$${config.unameValue}' '$${config.buildTimeValue}'")
|
||||
appendLine($$"echo \"$(get_current_time): 设置uname为: $${config.unameValue}, 构建时间为: $${config.buildTimeValue}\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
@@ -397,7 +398,7 @@ object ScriptGenerator {
|
||||
// 添加AVC日志欺骗设置
|
||||
generateAvcLogSpoofingSection(config.enableAvcLogSpoofing)
|
||||
|
||||
appendLine("echo \"$(get_current_time): Post-FS-Data脚本执行完成\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"echo \"$(get_current_time): Post-FS-Data脚本执行完成\" >> \"$LOG_FILE\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,8 +407,8 @@ object ScriptGenerator {
|
||||
if (support158) {
|
||||
appendLine("# 设置Zygote隔离服务卸载状态")
|
||||
val umountValue = if (umountForZygoteIsoService) 1 else 0
|
||||
appendLine("\"${'$'}SUSFS_BIN\" umount_for_zygote_iso_service $umountValue")
|
||||
appendLine("echo \"$(get_current_time): Zygote隔离服务卸载设置为: ${if (umountForZygoteIsoService) "启用" else "禁用"}\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" umount_for_zygote_iso_service $$umountValue")
|
||||
appendLine($$"echo \"$(get_current_time): Zygote隔离服务卸载设置为: $${if (umountForZygoteIsoService) "启用" else "禁用"}\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
@@ -423,37 +424,12 @@ object ScriptGenerator {
|
||||
appendLine()
|
||||
appendLine(generateLogSetup("susfs_post_mount.log"))
|
||||
appendLine()
|
||||
appendLine("echo \"$(get_current_time): Post-Mount脚本开始执行\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"echo \"$(get_current_time): Post-Mount脚本开始执行\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
appendLine(generateBinaryCheck(config.targetPath))
|
||||
appendLine()
|
||||
|
||||
// 添加SUS挂载
|
||||
if (config.susMounts.isNotEmpty()) {
|
||||
appendLine("# 添加SUS挂载")
|
||||
config.susMounts.forEach { mount ->
|
||||
appendLine("\"${'$'}SUSFS_BIN\" add_sus_mount '$mount'")
|
||||
appendLine("echo \"$(get_current_time): 添加SUS挂载: $mount\" >> \"${'$'}LOG_FILE\"")
|
||||
}
|
||||
appendLine()
|
||||
}
|
||||
|
||||
// 添加尝试卸载
|
||||
if (config.tryUmounts.isNotEmpty()) {
|
||||
appendLine("# 添加尝试卸载")
|
||||
config.tryUmounts.forEach { umount ->
|
||||
val parts = umount.split("|")
|
||||
if (parts.size == 2) {
|
||||
val path = parts[0]
|
||||
val mode = parts[1]
|
||||
appendLine("\"${'$'}SUSFS_BIN\" add_try_umount '$path' $mode")
|
||||
appendLine("echo \"$(get_current_time): 添加尝试卸载: $path (模式: $mode)\" >> \"${'$'}LOG_FILE\"")
|
||||
}
|
||||
}
|
||||
appendLine()
|
||||
}
|
||||
|
||||
appendLine("echo \"$(get_current_time): Post-Mount脚本执行完成\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"echo \"$(get_current_time): Post-Mount脚本执行完成\" >> \"$LOG_FILE\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,7 +445,7 @@ object ScriptGenerator {
|
||||
appendLine()
|
||||
appendLine(generateLogSetup("susfs_boot_completed.log"))
|
||||
appendLine()
|
||||
appendLine("echo \"$(get_current_time): Boot-Completed脚本开始执行\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"echo \"$(get_current_time): Boot-Completed脚本开始执行\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
appendLine(generateBinaryCheck(config.targetPath))
|
||||
appendLine()
|
||||
@@ -479,8 +455,8 @@ object ScriptGenerator {
|
||||
// SUS挂载隐藏控制
|
||||
val hideValue = if (config.hideSusMountsForAllProcs) 1 else 0
|
||||
appendLine("# 设置SUS挂载隐藏控制")
|
||||
appendLine("\"${'$'}SUSFS_BIN\" hide_sus_mnts_for_all_procs $hideValue")
|
||||
appendLine("echo \"$(get_current_time): SUS挂载隐藏控制设置为: ${if (config.hideSusMountsForAllProcs) "对所有进程隐藏" else "仅对非KSU进程隐藏"}\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" hide_sus_mnts_for_all_procs $$hideValue")
|
||||
appendLine($$"echo \"$(get_current_time): SUS挂载隐藏控制设置为: $${if (config.hideSusMountsForAllProcs) "对所有进程隐藏" else "仅对非KSU进程隐藏"}\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
|
||||
// 路径设置和SUS路径设置
|
||||
@@ -504,7 +480,7 @@ object ScriptGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
appendLine("echo \"$(get_current_time): Boot-Completed脚本执行完成\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"echo \"$(get_current_time): Boot-Completed脚本执行完成\" >> \"$LOG_FILE\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,8 +488,8 @@ object ScriptGenerator {
|
||||
if (susMaps.isNotEmpty()) {
|
||||
appendLine("# 添加SUS映射")
|
||||
susMaps.forEach { map ->
|
||||
appendLine("\"${'$'}SUSFS_BIN\" add_sus_map '$map'")
|
||||
appendLine("echo \"$(get_current_time): 添加SUS映射: $map\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" add_sus_map '$$map'")
|
||||
appendLine($$"echo \"$(get_current_time): 添加SUS映射: $$map\" >> \"$LOG_FILE\"")
|
||||
}
|
||||
appendLine()
|
||||
}
|
||||
@@ -526,12 +502,12 @@ object ScriptGenerator {
|
||||
appendLine("until [ -d \"/sdcard/Android\" ]; do sleep 1; done")
|
||||
appendLine("sleep 60")
|
||||
appendLine()
|
||||
appendLine("\"${'$'}SUSFS_BIN\" set_android_data_root_path '$androidDataPath'")
|
||||
appendLine("echo \"$(get_current_time): Android Data路径设置为: $androidDataPath\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" set_android_data_root_path '$$androidDataPath'")
|
||||
appendLine($$"echo \"$(get_current_time): Android Data路径设置为: $$androidDataPath\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
appendLine("# 设置SD卡路径")
|
||||
appendLine("\"${'$'}SUSFS_BIN\" set_sdcard_root_path '$sdcardPath'")
|
||||
appendLine("echo \"$(get_current_time): SD卡路径设置为: $sdcardPath\" >> \"${'$'}LOG_FILE\"")
|
||||
appendLine($$"\"$SUSFS_BIN\" set_sdcard_root_path '$$sdcardPath'")
|
||||
appendLine($$"echo \"$(get_current_time): SD卡路径设置为: $$sdcardPath\" >> \"$LOG_FILE\"")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,572 +0,0 @@
|
||||
package com.sukisu.ultra.ui.util;
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* An object to convert Chinese character to its corresponding pinyin string. For characters with
|
||||
* multiple possible pinyin string, only one is selected according to collator. Polyphone is not
|
||||
* supported in this implementation. This class is implemented to achieve the best runtime
|
||||
* performance and minimum runtime resources with tolerable sacrifice of accuracy. This
|
||||
* implementation highly depends on zh_CN ICU collation data and must be always synchronized with
|
||||
* ICU.
|
||||
* <p>
|
||||
* Currently this file is aligned to zh.txt in ICU 4.6
|
||||
*/
|
||||
@SuppressWarnings("SizeReplaceableByIsEmpty")
|
||||
public record HanziToPinyin(boolean mHasChinaCollator) {
|
||||
private static final String TAG = "HanziToPinyin";
|
||||
|
||||
// Turn on this flag when we want to check internal data structure.
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* Unihans array.
|
||||
* <p>
|
||||
* Each unihans is the first one within same pinyin when collator is zh_CN.
|
||||
*/
|
||||
public static final char[] UNIHANS = {
|
||||
'阿', '哎', '安', '肮', '凹', '八',
|
||||
'挀', '扳', '邦', '勹', '陂', '奔',
|
||||
'伻', '屄', '边', '灬', '憋', '汃',
|
||||
'冫', '癶', '峬', '嚓', '偲', '参',
|
||||
'仓', '撡', '冊', '嵾', '曽', '曾',
|
||||
'層', '叉', '芆', '辿', '伥', '抄',
|
||||
'车', '抻', '沈', '沉', '阷', '吃',
|
||||
'充', '抽', '出', '欻', '揣', '巛',
|
||||
'刅', '吹', '旾', '逴', '呲', '匆',
|
||||
'凑', '粗', '汆', '崔', '邨', '搓',
|
||||
'咑', '呆', '丹', '当', '刀', '嘚',
|
||||
'扥', '灯', '氐', '嗲', '甸', '刁',
|
||||
'爹', '丁', '丟', '东', '吺', '厾',
|
||||
'耑', '襨', '吨', '多', '妸', '诶',
|
||||
'奀', '鞥', '儿', '发', '帆', '匚',
|
||||
'飞', '分', '丰', '覅', '仏', '紑',
|
||||
'伕', '旮', '侅', '甘', '冈', '皋',
|
||||
'戈', '给', '根', '刯', '工', '勾',
|
||||
'估', '瓜', '乖', '关', '光', '归',
|
||||
'丨', '呙', '哈', '咍', '佄', '夯',
|
||||
'茠', '诃', '黒', '拫', '亨', '噷',
|
||||
'叿', '齁', '乯', '花', '怀', '犿',
|
||||
'巟', '灰', '昏', '吙', '丌', '加',
|
||||
'戋', '江', '艽', '阶', '巾', '坕',
|
||||
'冂', '丩', '凥', '姢', '噘', '军',
|
||||
'咔', '开', '刊', '忼', '尻', '匼',
|
||||
'肎', '劥', '空', '抠', '扝', '夸',
|
||||
'蒯', '宽', '匡', '亏', '坤', '扩',
|
||||
'垃', '来', '兰', '啷', '捞', '肋',
|
||||
'勒', '崚', '刕', '俩', '奁', '良',
|
||||
'撩', '列', '拎', '刢', '溜', '囖',
|
||||
'龙', '瞜', '噜', '娈', '畧', '抡',
|
||||
'罗', '呣', '妈', '埋', '嫚', '牤',
|
||||
'猫', '么', '呅', '门', '甿', '咪',
|
||||
'宀', '喵', '乜', '民', '名', '谬',
|
||||
'摸', '哞', '毪', '嗯', '拏', '腉',
|
||||
'囡', '囔', '孬', '疒', '娞', '恁',
|
||||
'能', '妮', '拈', '嬢', '鸟', '捏',
|
||||
'囜', '宁', '妞', '农', '羺', '奴',
|
||||
'奻', '疟', '黁', '郍', '喔', '讴',
|
||||
'妑', '拍', '眅', '乓', '抛', '呸',
|
||||
'喷', '匉', '丕', '囨', '剽', '氕',
|
||||
'姘', '乒', '钋', '剖', '仆', '七',
|
||||
'掐', '千', '呛', '悄', '癿', '亲',
|
||||
'狅', '芎', '丘', '区', '峑', '缺',
|
||||
'夋', '呥', '穣', '娆', '惹', '人',
|
||||
'扔', '日', '茸', '厹', '邚', '挼',
|
||||
'堧', '婑', '瞤', '捼', '仨', '毢',
|
||||
'三', '桒', '掻', '閪', '森', '僧',
|
||||
'杀', '筛', '山', '伤', '弰', '奢',
|
||||
'申', '莘', '敒', '升', '尸', '収',
|
||||
'书', '刷', '衰', '闩', '双', '谁',
|
||||
'吮', '说', '厶', '忪', '捜', '苏',
|
||||
'狻', '夊', '孙', '唆', '他', '囼',
|
||||
'坍', '汤', '夲', '忑', '熥', '剔',
|
||||
'天', '旫', '帖', '厅', '囲', '偷',
|
||||
'凸', '湍', '推', '吞', '乇', '穵',
|
||||
'歪', '弯', '尣', '危', '昷', '翁',
|
||||
'挝', '乌', '夕', '虲', '仚', '乡',
|
||||
'灱', '些', '心', '星', '凶', '休',
|
||||
'吁', '吅', '削', '坃', '丫', '恹',
|
||||
'央', '幺', '倻', '一', '囙', '应',
|
||||
'哟', '佣', '优', '扜', '囦', '曰',
|
||||
'晕', '筠', '筼', '帀', '災', '兂',
|
||||
'匨', '傮', '则', '贼', '怎', '増',
|
||||
'扎', '捚', '沾', '张', '长', '長',
|
||||
'佋', '蜇', '贞', '争', '之', '峙',
|
||||
'庢', '中', '州', '朱', '抓', '拽',
|
||||
'专', '妆', '隹', '宒', '卓', '乲',
|
||||
'宗', '邹', '租', '钻', '厜', '尊',
|
||||
'昨', '兙', '鿃', '鿄'};
|
||||
|
||||
/**
|
||||
* Pinyin array.
|
||||
* <p>
|
||||
* Each pinyin is corresponding to unihans of same
|
||||
* offset in the unihans array.
|
||||
*/
|
||||
public static final byte[][] PINYINS = {
|
||||
{65, 0, 0, 0, 0, 0}, {65, 73, 0, 0, 0, 0},
|
||||
{65, 78, 0, 0, 0, 0}, {65, 78, 71, 0, 0, 0},
|
||||
{65, 79, 0, 0, 0, 0}, {66, 65, 0, 0, 0, 0},
|
||||
{66, 65, 73, 0, 0, 0}, {66, 65, 78, 0, 0, 0},
|
||||
{66, 65, 78, 71, 0, 0}, {66, 65, 79, 0, 0, 0},
|
||||
{66, 69, 73, 0, 0, 0}, {66, 69, 78, 0, 0, 0},
|
||||
{66, 69, 78, 71, 0, 0}, {66, 73, 0, 0, 0, 0},
|
||||
{66, 73, 65, 78, 0, 0}, {66, 73, 65, 79, 0, 0},
|
||||
{66, 73, 69, 0, 0, 0}, {66, 73, 78, 0, 0, 0},
|
||||
{66, 73, 78, 71, 0, 0}, {66, 79, 0, 0, 0, 0},
|
||||
{66, 85, 0, 0, 0, 0}, {67, 65, 0, 0, 0, 0},
|
||||
{67, 65, 73, 0, 0, 0}, {67, 65, 78, 0, 0, 0},
|
||||
{67, 65, 78, 71, 0, 0}, {67, 65, 79, 0, 0, 0},
|
||||
{67, 69, 0, 0, 0, 0}, {67, 69, 78, 0, 0, 0},
|
||||
{67, 69, 78, 71, 0, 0}, {90, 69, 78, 71, 0, 0},
|
||||
{67, 69, 78, 71, 0, 0}, {67, 72, 65, 0, 0, 0},
|
||||
{67, 72, 65, 73, 0, 0}, {67, 72, 65, 78, 0, 0},
|
||||
{67, 72, 65, 78, 71, 0}, {67, 72, 65, 79, 0, 0},
|
||||
{67, 72, 69, 0, 0, 0}, {67, 72, 69, 78, 0, 0},
|
||||
{83, 72, 69, 78, 0, 0}, {67, 72, 69, 78, 0, 0},
|
||||
{67, 72, 69, 78, 71, 0}, {67, 72, 73, 0, 0, 0},
|
||||
{67, 72, 79, 78, 71, 0}, {67, 72, 79, 85, 0, 0},
|
||||
{67, 72, 85, 0, 0, 0}, {67, 72, 85, 65, 0, 0},
|
||||
{67, 72, 85, 65, 73, 0}, {67, 72, 85, 65, 78, 0},
|
||||
{67, 72, 85, 65, 78, 71}, {67, 72, 85, 73, 0, 0},
|
||||
{67, 72, 85, 78, 0, 0}, {67, 72, 85, 79, 0, 0},
|
||||
{67, 73, 0, 0, 0, 0}, {67, 79, 78, 71, 0, 0},
|
||||
{67, 79, 85, 0, 0, 0}, {67, 85, 0, 0, 0, 0},
|
||||
{67, 85, 65, 78, 0, 0}, {67, 85, 73, 0, 0, 0},
|
||||
{67, 85, 78, 0, 0, 0}, {67, 85, 79, 0, 0, 0},
|
||||
{68, 65, 0, 0, 0, 0}, {68, 65, 73, 0, 0, 0},
|
||||
{68, 65, 78, 0, 0, 0}, {68, 65, 78, 71, 0, 0},
|
||||
{68, 65, 79, 0, 0, 0}, {68, 69, 0, 0, 0, 0},
|
||||
{68, 69, 78, 0, 0, 0}, {68, 69, 78, 71, 0, 0},
|
||||
{68, 73, 0, 0, 0, 0}, {68, 73, 65, 0, 0, 0},
|
||||
{68, 73, 65, 78, 0, 0}, {68, 73, 65, 79, 0, 0},
|
||||
{68, 73, 69, 0, 0, 0}, {68, 73, 78, 71, 0, 0},
|
||||
{68, 73, 85, 0, 0, 0}, {68, 79, 78, 71, 0, 0},
|
||||
{68, 79, 85, 0, 0, 0}, {68, 85, 0, 0, 0, 0},
|
||||
{68, 85, 65, 78, 0, 0}, {68, 85, 73, 0, 0, 0},
|
||||
{68, 85, 78, 0, 0, 0}, {68, 85, 79, 0, 0, 0},
|
||||
{69, 0, 0, 0, 0, 0}, {69, 73, 0, 0, 0, 0},
|
||||
{69, 78, 0, 0, 0, 0}, {69, 78, 71, 0, 0, 0},
|
||||
{69, 82, 0, 0, 0, 0}, {70, 65, 0, 0, 0, 0},
|
||||
{70, 65, 78, 0, 0, 0}, {70, 65, 78, 71, 0, 0},
|
||||
{70, 69, 73, 0, 0, 0}, {70, 69, 78, 0, 0, 0},
|
||||
{70, 69, 78, 71, 0, 0}, {70, 73, 65, 79, 0, 0},
|
||||
{70, 79, 0, 0, 0, 0}, {70, 79, 85, 0, 0, 0},
|
||||
{70, 85, 0, 0, 0, 0}, {71, 65, 0, 0, 0, 0},
|
||||
{71, 65, 73, 0, 0, 0}, {71, 65, 78, 0, 0, 0},
|
||||
{71, 65, 78, 71, 0, 0}, {71, 65, 79, 0, 0, 0},
|
||||
{71, 69, 0, 0, 0, 0}, {71, 69, 73, 0, 0, 0},
|
||||
{71, 69, 78, 0, 0, 0}, {71, 69, 78, 71, 0, 0},
|
||||
{71, 79, 78, 71, 0, 0}, {71, 79, 85, 0, 0, 0},
|
||||
{71, 85, 0, 0, 0, 0}, {71, 85, 65, 0, 0, 0},
|
||||
{71, 85, 65, 73, 0, 0}, {71, 85, 65, 78, 0, 0},
|
||||
{71, 85, 65, 78, 71, 0}, {71, 85, 73, 0, 0, 0},
|
||||
{71, 85, 78, 0, 0, 0}, {71, 85, 79, 0, 0, 0},
|
||||
{72, 65, 0, 0, 0, 0}, {72, 65, 73, 0, 0, 0},
|
||||
{72, 65, 78, 0, 0, 0}, {72, 65, 78, 71, 0, 0},
|
||||
{72, 65, 79, 0, 0, 0}, {72, 69, 0, 0, 0, 0},
|
||||
{72, 69, 73, 0, 0, 0}, {72, 69, 78, 0, 0, 0},
|
||||
{72, 69, 78, 71, 0, 0}, {72, 77, 0, 0, 0, 0},
|
||||
{72, 79, 78, 71, 0, 0}, {72, 79, 85, 0, 0, 0},
|
||||
{72, 85, 0, 0, 0, 0}, {72, 85, 65, 0, 0, 0},
|
||||
{72, 85, 65, 73, 0, 0}, {72, 85, 65, 78, 0, 0},
|
||||
{72, 85, 65, 78, 71, 0}, {72, 85, 73, 0, 0, 0},
|
||||
{72, 85, 78, 0, 0, 0}, {72, 85, 79, 0, 0, 0},
|
||||
{74, 73, 0, 0, 0, 0}, {74, 73, 65, 0, 0, 0},
|
||||
{74, 73, 65, 78, 0, 0}, {74, 73, 65, 78, 71, 0},
|
||||
{74, 73, 65, 79, 0, 0}, {74, 73, 69, 0, 0, 0},
|
||||
{74, 73, 78, 0, 0, 0}, {74, 73, 78, 71, 0, 0},
|
||||
{74, 73, 79, 78, 71, 0}, {74, 73, 85, 0, 0, 0},
|
||||
{74, 85, 0, 0, 0, 0}, {74, 85, 65, 78, 0, 0},
|
||||
{74, 85, 69, 0, 0, 0}, {74, 85, 78, 0, 0, 0},
|
||||
{75, 65, 0, 0, 0, 0}, {75, 65, 73, 0, 0, 0},
|
||||
{75, 65, 78, 0, 0, 0}, {75, 65, 78, 71, 0, 0},
|
||||
{75, 65, 79, 0, 0, 0}, {75, 69, 0, 0, 0, 0},
|
||||
{75, 69, 78, 0, 0, 0}, {75, 69, 78, 71, 0, 0},
|
||||
{75, 79, 78, 71, 0, 0}, {75, 79, 85, 0, 0, 0},
|
||||
{75, 85, 0, 0, 0, 0}, {75, 85, 65, 0, 0, 0},
|
||||
{75, 85, 65, 73, 0, 0}, {75, 85, 65, 78, 0, 0},
|
||||
{75, 85, 65, 78, 71, 0}, {75, 85, 73, 0, 0, 0},
|
||||
{75, 85, 78, 0, 0, 0}, {75, 85, 79, 0, 0, 0},
|
||||
{76, 65, 0, 0, 0, 0}, {76, 65, 73, 0, 0, 0},
|
||||
{76, 65, 78, 0, 0, 0}, {76, 65, 78, 71, 0, 0},
|
||||
{76, 65, 79, 0, 0, 0}, {76, 69, 0, 0, 0, 0},
|
||||
{76, 69, 73, 0, 0, 0}, {76, 69, 78, 71, 0, 0},
|
||||
{76, 73, 0, 0, 0, 0}, {76, 73, 65, 0, 0, 0},
|
||||
{76, 73, 65, 78, 0, 0}, {76, 73, 65, 78, 71, 0},
|
||||
{76, 73, 65, 79, 0, 0}, {76, 73, 69, 0, 0, 0},
|
||||
{76, 73, 78, 0, 0, 0}, {76, 73, 78, 71, 0, 0},
|
||||
{76, 73, 85, 0, 0, 0}, {76, 79, 0, 0, 0, 0},
|
||||
{76, 79, 78, 71, 0, 0}, {76, 79, 85, 0, 0, 0},
|
||||
{76, 85, 0, 0, 0, 0}, {76, 85, 65, 78, 0, 0},
|
||||
{76, 85, 69, 0, 0, 0}, {76, 85, 78, 0, 0, 0},
|
||||
{76, 85, 79, 0, 0, 0}, {77, 0, 0, 0, 0, 0},
|
||||
{77, 65, 0, 0, 0, 0}, {77, 65, 73, 0, 0, 0},
|
||||
{77, 65, 78, 0, 0, 0}, {77, 65, 78, 71, 0, 0},
|
||||
{77, 65, 79, 0, 0, 0}, {77, 69, 0, 0, 0, 0},
|
||||
{77, 69, 73, 0, 0, 0}, {77, 69, 78, 0, 0, 0},
|
||||
{77, 69, 78, 71, 0, 0}, {77, 73, 0, 0, 0, 0},
|
||||
{77, 73, 65, 78, 0, 0}, {77, 73, 65, 79, 0, 0},
|
||||
{77, 73, 69, 0, 0, 0}, {77, 73, 78, 0, 0, 0},
|
||||
{77, 73, 78, 71, 0, 0}, {77, 73, 85, 0, 0, 0},
|
||||
{77, 79, 0, 0, 0, 0}, {77, 79, 85, 0, 0, 0},
|
||||
{77, 85, 0, 0, 0, 0}, {78, 0, 0, 0, 0, 0},
|
||||
{78, 65, 0, 0, 0, 0}, {78, 65, 73, 0, 0, 0},
|
||||
{78, 65, 78, 0, 0, 0}, {78, 65, 78, 71, 0, 0},
|
||||
{78, 65, 79, 0, 0, 0}, {78, 69, 0, 0, 0, 0},
|
||||
{78, 69, 73, 0, 0, 0}, {78, 69, 78, 0, 0, 0},
|
||||
{78, 69, 78, 71, 0, 0}, {78, 73, 0, 0, 0, 0},
|
||||
{78, 73, 65, 78, 0, 0}, {78, 73, 65, 78, 71, 0},
|
||||
{78, 73, 65, 79, 0, 0}, {78, 73, 69, 0, 0, 0},
|
||||
{78, 73, 78, 0, 0, 0}, {78, 73, 78, 71, 0, 0},
|
||||
{78, 73, 85, 0, 0, 0}, {78, 79, 78, 71, 0, 0},
|
||||
{78, 79, 85, 0, 0, 0}, {78, 85, 0, 0, 0, 0},
|
||||
{78, 85, 65, 78, 0, 0}, {78, 85, 69, 0, 0, 0},
|
||||
{78, 85, 78, 0, 0, 0}, {78, 85, 79, 0, 0, 0},
|
||||
{79, 0, 0, 0, 0, 0}, {79, 85, 0, 0, 0, 0},
|
||||
{80, 65, 0, 0, 0, 0}, {80, 65, 73, 0, 0, 0},
|
||||
{80, 65, 78, 0, 0, 0}, {80, 65, 78, 71, 0, 0},
|
||||
{80, 65, 79, 0, 0, 0}, {80, 69, 73, 0, 0, 0},
|
||||
{80, 69, 78, 0, 0, 0}, {80, 69, 78, 71, 0, 0},
|
||||
{80, 73, 0, 0, 0, 0}, {80, 73, 65, 78, 0, 0},
|
||||
{80, 73, 65, 79, 0, 0}, {80, 73, 69, 0, 0, 0},
|
||||
{80, 73, 78, 0, 0, 0}, {80, 73, 78, 71, 0, 0},
|
||||
{80, 79, 0, 0, 0, 0}, {80, 79, 85, 0, 0, 0},
|
||||
{80, 85, 0, 0, 0, 0}, {81, 73, 0, 0, 0, 0},
|
||||
{81, 73, 65, 0, 0, 0}, {81, 73, 65, 78, 0, 0},
|
||||
{81, 73, 65, 78, 71, 0}, {81, 73, 65, 79, 0, 0},
|
||||
{81, 73, 69, 0, 0, 0}, {81, 73, 78, 0, 0, 0},
|
||||
{81, 73, 78, 71, 0, 0}, {81, 73, 79, 78, 71, 0},
|
||||
{81, 73, 85, 0, 0, 0}, {81, 85, 0, 0, 0, 0},
|
||||
{81, 85, 65, 78, 0, 0}, {81, 85, 69, 0, 0, 0},
|
||||
{81, 85, 78, 0, 0, 0}, {82, 65, 78, 0, 0, 0},
|
||||
{82, 65, 78, 71, 0, 0}, {82, 65, 79, 0, 0, 0},
|
||||
{82, 69, 0, 0, 0, 0}, {82, 69, 78, 0, 0, 0},
|
||||
{82, 69, 78, 71, 0, 0}, {82, 73, 0, 0, 0, 0},
|
||||
{82, 79, 78, 71, 0, 0}, {82, 79, 85, 0, 0, 0},
|
||||
{82, 85, 0, 0, 0, 0}, {82, 85, 65, 0, 0, 0},
|
||||
{82, 85, 65, 78, 0, 0}, {82, 85, 73, 0, 0, 0},
|
||||
{82, 85, 78, 0, 0, 0}, {82, 85, 79, 0, 0, 0},
|
||||
{83, 65, 0, 0, 0, 0}, {83, 65, 73, 0, 0, 0},
|
||||
{83, 65, 78, 0, 0, 0}, {83, 65, 78, 71, 0, 0},
|
||||
{83, 65, 79, 0, 0, 0}, {83, 69, 0, 0, 0, 0},
|
||||
{83, 69, 78, 0, 0, 0}, {83, 69, 78, 71, 0, 0},
|
||||
{83, 72, 65, 0, 0, 0}, {83, 72, 65, 73, 0, 0},
|
||||
{83, 72, 65, 78, 0, 0}, {83, 72, 65, 78, 71, 0},
|
||||
{83, 72, 65, 79, 0, 0}, {83, 72, 69, 0, 0, 0},
|
||||
{83, 72, 69, 78, 0, 0}, {88, 73, 78, 0, 0, 0},
|
||||
{83, 72, 69, 78, 0, 0}, {83, 72, 69, 78, 71, 0},
|
||||
{83, 72, 73, 0, 0, 0}, {83, 72, 79, 85, 0, 0},
|
||||
{83, 72, 85, 0, 0, 0}, {83, 72, 85, 65, 0, 0},
|
||||
{83, 72, 85, 65, 73, 0}, {83, 72, 85, 65, 78, 0},
|
||||
{83, 72, 85, 65, 78, 71}, {83, 72, 85, 73, 0, 0},
|
||||
{83, 72, 85, 78, 0, 0}, {83, 72, 85, 79, 0, 0},
|
||||
{83, 73, 0, 0, 0, 0}, {83, 79, 78, 71, 0, 0},
|
||||
{83, 79, 85, 0, 0, 0}, {83, 85, 0, 0, 0, 0},
|
||||
{83, 85, 65, 78, 0, 0}, {83, 85, 73, 0, 0, 0},
|
||||
{83, 85, 78, 0, 0, 0}, {83, 85, 79, 0, 0, 0},
|
||||
{84, 65, 0, 0, 0, 0}, {84, 65, 73, 0, 0, 0},
|
||||
{84, 65, 78, 0, 0, 0}, {84, 65, 78, 71, 0, 0},
|
||||
{84, 65, 79, 0, 0, 0}, {84, 69, 0, 0, 0, 0},
|
||||
{84, 69, 78, 71, 0, 0}, {84, 73, 0, 0, 0, 0},
|
||||
{84, 73, 65, 78, 0, 0}, {84, 73, 65, 79, 0, 0},
|
||||
{84, 73, 69, 0, 0, 0}, {84, 73, 78, 71, 0, 0},
|
||||
{84, 79, 78, 71, 0, 0}, {84, 79, 85, 0, 0, 0},
|
||||
{84, 85, 0, 0, 0, 0}, {84, 85, 65, 78, 0, 0},
|
||||
{84, 85, 73, 0, 0, 0}, {84, 85, 78, 0, 0, 0},
|
||||
{84, 85, 79, 0, 0, 0}, {87, 65, 0, 0, 0, 0},
|
||||
{87, 65, 73, 0, 0, 0}, {87, 65, 78, 0, 0, 0},
|
||||
{87, 65, 78, 71, 0, 0}, {87, 69, 73, 0, 0, 0},
|
||||
{87, 69, 78, 0, 0, 0}, {87, 69, 78, 71, 0, 0},
|
||||
{87, 79, 0, 0, 0, 0}, {87, 85, 0, 0, 0, 0},
|
||||
{88, 73, 0, 0, 0, 0}, {88, 73, 65, 0, 0, 0},
|
||||
{88, 73, 65, 78, 0, 0}, {88, 73, 65, 78, 71, 0},
|
||||
{88, 73, 65, 79, 0, 0}, {88, 73, 69, 0, 0, 0},
|
||||
{88, 73, 78, 0, 0, 0}, {88, 73, 78, 71, 0, 0},
|
||||
{88, 73, 79, 78, 71, 0}, {88, 73, 85, 0, 0, 0},
|
||||
{88, 85, 0, 0, 0, 0}, {88, 85, 65, 78, 0, 0},
|
||||
{88, 85, 69, 0, 0, 0}, {88, 85, 78, 0, 0, 0},
|
||||
{89, 65, 0, 0, 0, 0}, {89, 65, 78, 0, 0, 0},
|
||||
{89, 65, 78, 71, 0, 0}, {89, 65, 79, 0, 0, 0},
|
||||
{89, 69, 0, 0, 0, 0}, {89, 73, 0, 0, 0, 0},
|
||||
{89, 73, 78, 0, 0, 0}, {89, 73, 78, 71, 0, 0},
|
||||
{89, 79, 0, 0, 0, 0}, {89, 79, 78, 71, 0, 0},
|
||||
{89, 79, 85, 0, 0, 0}, {89, 85, 0, 0, 0, 0},
|
||||
{89, 85, 65, 78, 0, 0}, {89, 85, 69, 0, 0, 0},
|
||||
{89, 85, 78, 0, 0, 0}, {74, 85, 78, 0, 0, 0},
|
||||
{89, 85, 78, 0, 0, 0}, {90, 65, 0, 0, 0, 0},
|
||||
{90, 65, 73, 0, 0, 0}, {90, 65, 78, 0, 0, 0},
|
||||
{90, 65, 78, 71, 0, 0}, {90, 65, 79, 0, 0, 0},
|
||||
{90, 69, 0, 0, 0, 0}, {90, 69, 73, 0, 0, 0},
|
||||
{90, 69, 78, 0, 0, 0}, {90, 69, 78, 71, 0, 0},
|
||||
{90, 72, 65, 0, 0, 0}, {90, 72, 65, 73, 0, 0},
|
||||
{90, 72, 65, 78, 0, 0}, {90, 72, 65, 78, 71, 0},
|
||||
{67, 72, 65, 78, 71, 0}, {90, 72, 65, 78, 71, 0},
|
||||
{90, 72, 65, 79, 0, 0}, {90, 72, 69, 0, 0, 0},
|
||||
{90, 72, 69, 78, 0, 0}, {90, 72, 69, 78, 71, 0},
|
||||
{90, 72, 73, 0, 0, 0}, {83, 72, 73, 0, 0, 0},
|
||||
{90, 72, 73, 0, 0, 0}, {90, 72, 79, 78, 71, 0},
|
||||
{90, 72, 79, 85, 0, 0}, {90, 72, 85, 0, 0, 0},
|
||||
{90, 72, 85, 65, 0, 0}, {90, 72, 85, 65, 73, 0},
|
||||
{90, 72, 85, 65, 78, 0}, {90, 72, 85, 65, 78, 71},
|
||||
{90, 72, 85, 73, 0, 0}, {90, 72, 85, 78, 0, 0},
|
||||
{90, 72, 85, 79, 0, 0}, {90, 73, 0, 0, 0, 0},
|
||||
{90, 79, 78, 71, 0, 0}, {90, 79, 85, 0, 0, 0},
|
||||
{90, 85, 0, 0, 0, 0}, {90, 85, 65, 78, 0, 0},
|
||||
{90, 85, 73, 0, 0, 0}, {90, 85, 78, 0, 0, 0},
|
||||
{90, 85, 79, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
|
||||
{83, 72, 65, 78, 0, 0}, {0, 0, 0, 0, 0, 0}};
|
||||
|
||||
/**
|
||||
* First and last Chinese character with known Pinyin according to zh collation
|
||||
*/
|
||||
private static final String FIRST_PINYIN_UNIHAN = "阿";
|
||||
private static final String LAST_PINYIN_UNIHAN = "鿿";
|
||||
|
||||
private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA);
|
||||
|
||||
private static HanziToPinyin sInstance;
|
||||
|
||||
public static class Token {
|
||||
/**
|
||||
* Separator between target string for each source char
|
||||
*/
|
||||
public static final String SEPARATOR = " ";
|
||||
|
||||
public static final int LATIN = 1;
|
||||
public static final int PINYIN = 2;
|
||||
public static final int UNKNOWN = 3;
|
||||
|
||||
public Token() {
|
||||
}
|
||||
|
||||
public Token(int type, String source, String target) {
|
||||
this.type = type;
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of this token, ASCII, PINYIN or UNKNOWN.
|
||||
*/
|
||||
public int type;
|
||||
/**
|
||||
* Original string before translation.
|
||||
*/
|
||||
public String source;
|
||||
/**
|
||||
* Translated string of source. For Han, target is corresponding Pinyin. Otherwise target is
|
||||
* original string in source.
|
||||
*/
|
||||
public String target;
|
||||
}
|
||||
|
||||
public static HanziToPinyin getInstance() {
|
||||
synchronized (HanziToPinyin.class) {
|
||||
if (sInstance != null) {
|
||||
return sInstance;
|
||||
}
|
||||
// Check if zh_CN collation data is available
|
||||
final Locale[] locale = Collator.getAvailableLocales();
|
||||
for (Locale value : locale) {
|
||||
if (value.equals(Locale.CHINA) || value.getLanguage().contains("zh")) {
|
||||
// Do self validation just once.
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Self validation. Result: " + doSelfValidation());
|
||||
}
|
||||
sInstance = new HanziToPinyin(true);
|
||||
return sInstance;
|
||||
}
|
||||
}
|
||||
if (sInstance == null) {//这个判断是用于处理国产ROM的兼容性问题
|
||||
if (Locale.CHINA.equals(Locale.getDefault())) {
|
||||
sInstance = new HanziToPinyin(true);
|
||||
return sInstance;
|
||||
}
|
||||
}
|
||||
Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled");
|
||||
sInstance = new HanziToPinyin(false);
|
||||
return sInstance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if our internal table has some wrong value.
|
||||
*
|
||||
* @return true when the table looks correct.
|
||||
*/
|
||||
private static boolean doSelfValidation() {
|
||||
char lastChar = UNIHANS[0];
|
||||
String lastString = Character.toString(lastChar);
|
||||
for (char c : UNIHANS) {
|
||||
if (lastChar == c) {
|
||||
continue;
|
||||
}
|
||||
final String curString = Character.toString(c);
|
||||
int cmp = COLLATOR.compare(lastString, curString);
|
||||
if (cmp >= 0) {
|
||||
Log.e(TAG, "Internal error in Unihan table. " + "The last string \"" + lastString
|
||||
+ "\" is greater than current string \"" + curString + "\".");
|
||||
return false;
|
||||
}
|
||||
lastString = curString;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Token getToken(char character) {
|
||||
Token token = new Token();
|
||||
final String letter = Character.toString(character);
|
||||
token.source = letter;
|
||||
int offset = -1;
|
||||
int cmp;
|
||||
if (character < 256) {
|
||||
token.type = Token.LATIN;
|
||||
token.target = letter;
|
||||
return token;
|
||||
} else {
|
||||
cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN);
|
||||
if (cmp < 0) {
|
||||
token.type = Token.UNKNOWN;
|
||||
token.target = letter;
|
||||
return token;
|
||||
} else if (cmp == 0) {
|
||||
token.type = Token.PINYIN;
|
||||
offset = 0;
|
||||
} else {
|
||||
cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN);
|
||||
if (cmp > 0) {
|
||||
token.type = Token.UNKNOWN;
|
||||
token.target = letter;
|
||||
return token;
|
||||
} else if (cmp == 0) {
|
||||
token.type = Token.PINYIN;
|
||||
offset = UNIHANS.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
token.type = Token.PINYIN;
|
||||
if (offset < 0) {
|
||||
int begin = 0;
|
||||
int end = UNIHANS.length - 1;
|
||||
while (begin <= end) {
|
||||
offset = (begin + end) / 2;
|
||||
final String unihan = Character.toString(UNIHANS[offset]);
|
||||
cmp = COLLATOR.compare(letter, unihan);
|
||||
if (cmp == 0) {
|
||||
break;
|
||||
} else if (cmp > 0) {
|
||||
begin = offset + 1;
|
||||
} else {
|
||||
end = offset - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cmp < 0) {
|
||||
offset--;
|
||||
}
|
||||
StringBuilder pinyin = new StringBuilder();
|
||||
for (int j = 0; j < PINYINS[offset].length && PINYINS[offset][j] != 0; j++) {
|
||||
pinyin.append((char) PINYINS[offset][j]);
|
||||
}
|
||||
token.target = pinyin.toString();
|
||||
if (TextUtils.isEmpty(token.target)) {
|
||||
token.type = Token.UNKNOWN;
|
||||
token.target = token.source;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the input to a array of tokens. The sequence of ASCII or Unknown characters without
|
||||
* space will be put into a Token, One Hanzi character which has pinyin will be treated as a
|
||||
* Token. If these is no China collator, the empty token array is returned.
|
||||
*/
|
||||
public ArrayList<Token> get(final String input) {
|
||||
ArrayList<Token> tokens = new ArrayList<>();
|
||||
if (!mHasChinaCollator || TextUtils.isEmpty(input)) {
|
||||
// return empty tokens.
|
||||
return tokens;
|
||||
}
|
||||
final int inputLength = input.length();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
int tokenType = Token.LATIN;
|
||||
// Go through the input, create a new token when
|
||||
// a. Token type changed
|
||||
// b. Get the Pinyin of current charater.
|
||||
// c. current character is space.
|
||||
for (int i = 0; i < inputLength; i++) {
|
||||
final char character = input.charAt(i);
|
||||
if (character == ' ') {
|
||||
if (sb.length() > 0) {
|
||||
addToken(sb, tokens, tokenType);
|
||||
}
|
||||
} else if (character < 256) {
|
||||
if (tokenType != Token.LATIN && sb.length() > 0) {
|
||||
addToken(sb, tokens, tokenType);
|
||||
}
|
||||
tokenType = Token.LATIN;
|
||||
sb.append(character);
|
||||
} else {
|
||||
Token t = getToken(character);
|
||||
if (t.type == Token.PINYIN) {
|
||||
if (sb.length() > 0) {
|
||||
addToken(sb, tokens, tokenType);
|
||||
}
|
||||
tokens.add(t);
|
||||
tokenType = Token.PINYIN;
|
||||
} else {
|
||||
if (tokenType != t.type && sb.length() > 0) {
|
||||
addToken(sb, tokens, tokenType);
|
||||
}
|
||||
tokenType = t.type;
|
||||
sb.append(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
addToken(sb, tokens, tokenType);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private void addToken(
|
||||
final StringBuilder sb, final ArrayList<Token> tokens, final int tokenType) {
|
||||
String str = sb.toString();
|
||||
tokens.add(new Token(tokenType, str, str));
|
||||
sb.setLength(0);
|
||||
}
|
||||
|
||||
public String toPinyinString(String string) {
|
||||
if (string == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
ArrayList<Token> tokens = get(string);
|
||||
for (Token token : tokens) {
|
||||
sb.append(token.target);
|
||||
}
|
||||
return sb.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import java.text.Collator
|
||||
import java.util.Locale
|
||||
|
||||
class HanziToPinyin private constructor(val hasChinaCollator: Boolean) {
|
||||
|
||||
class Token(
|
||||
var type: Int = 0,
|
||||
var source: String = "",
|
||||
var target: String = ""
|
||||
) {
|
||||
companion object {
|
||||
const val LATIN = 1
|
||||
const val PINYIN = 2
|
||||
const val UNKNOWN = 3
|
||||
}
|
||||
}
|
||||
|
||||
private fun getToken(character: Char): Token {
|
||||
val token = Token()
|
||||
val letter = character.toString()
|
||||
token.source = letter
|
||||
var offset = -1
|
||||
var cmp: Int
|
||||
|
||||
if (character < 256.toChar()) {
|
||||
token.type = Token.LATIN
|
||||
token.target = letter
|
||||
return token
|
||||
} else {
|
||||
cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN)
|
||||
if (cmp < 0) {
|
||||
token.type = Token.UNKNOWN
|
||||
token.target = letter
|
||||
return token
|
||||
} else if (cmp == 0) {
|
||||
token.type = Token.PINYIN
|
||||
offset = 0
|
||||
} else {
|
||||
cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN)
|
||||
if (cmp > 0) {
|
||||
token.type = Token.UNKNOWN
|
||||
token.target = letter
|
||||
return token
|
||||
} else if (cmp == 0) {
|
||||
token.type = Token.PINYIN
|
||||
offset = UNIHANS.size - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
token.type = Token.PINYIN
|
||||
if (offset < 0) {
|
||||
var begin = 0
|
||||
var end = UNIHANS.size - 1
|
||||
while (begin <= end) {
|
||||
offset = (begin + end) / 2
|
||||
val unihan = UNIHANS[offset].toString()
|
||||
cmp = COLLATOR.compare(letter, unihan)
|
||||
when {
|
||||
cmp == 0 -> break
|
||||
cmp > 0 -> begin = offset + 1
|
||||
else -> end = offset - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cmp < 0) {
|
||||
offset--
|
||||
}
|
||||
|
||||
val pinyin = StringBuilder()
|
||||
for (j in PINYINS[offset].indices) {
|
||||
if (PINYINS[offset][j] == 0.toByte()) break
|
||||
pinyin.append(PINYINS[offset][j].toInt().toChar())
|
||||
}
|
||||
token.target = pinyin.toString()
|
||||
if (TextUtils.isEmpty(token.target)) {
|
||||
token.type = Token.UNKNOWN
|
||||
token.target = token.source
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
fun get(input: String?): ArrayList<Token> {
|
||||
val tokens = ArrayList<Token>()
|
||||
if (!hasChinaCollator || TextUtils.isEmpty(input)) {
|
||||
return tokens
|
||||
}
|
||||
|
||||
val inputLength = input!!.length
|
||||
val sb = StringBuilder()
|
||||
var tokenType = Token.LATIN
|
||||
|
||||
for (i in 0 until inputLength) {
|
||||
val character = input[i]
|
||||
when {
|
||||
character == ' ' -> {
|
||||
if (sb.isNotEmpty()) {
|
||||
addToken(sb, tokens, tokenType)
|
||||
}
|
||||
}
|
||||
character < 256.toChar() -> {
|
||||
if (tokenType != Token.LATIN && sb.isNotEmpty()) {
|
||||
addToken(sb, tokens, tokenType)
|
||||
}
|
||||
tokenType = Token.LATIN
|
||||
sb.append(character)
|
||||
}
|
||||
else -> {
|
||||
val t = getToken(character)
|
||||
if (t.type == Token.PINYIN) {
|
||||
if (sb.isNotEmpty()) {
|
||||
addToken(sb, tokens, tokenType)
|
||||
}
|
||||
tokens.add(t)
|
||||
tokenType = Token.PINYIN
|
||||
} else {
|
||||
if (tokenType != t.type && sb.isNotEmpty()) {
|
||||
addToken(sb, tokens, tokenType)
|
||||
}
|
||||
tokenType = t.type
|
||||
sb.append(character)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sb.isNotEmpty()) {
|
||||
addToken(sb, tokens, tokenType)
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
private fun addToken(sb: StringBuilder, tokens: ArrayList<Token>, tokenType: Int) {
|
||||
val str = sb.toString()
|
||||
tokens.add(Token(tokenType, str, str))
|
||||
sb.setLength(0)
|
||||
}
|
||||
|
||||
fun toPinyinString(string: String?): String? {
|
||||
if (string == null) {
|
||||
return null
|
||||
}
|
||||
val sb = StringBuilder()
|
||||
val tokens = get(string)
|
||||
for (token in tokens) {
|
||||
sb.append(token.target)
|
||||
}
|
||||
return sb.toString().lowercase()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "HanziToPinyin"
|
||||
private const val DEBUG = false
|
||||
|
||||
val UNIHANS = charArrayOf(
|
||||
'阿', '哎', '安', '肮', '凹', '八',
|
||||
'挀', '扳', '邦', '勹', '陂', '奔',
|
||||
'伻', '屄', '边', '灬', '憋', '汃',
|
||||
'冫', '癶', '峬', '嚓', '偲', '参',
|
||||
'仓', '撡', '冊', '嵾', '曽', '曾',
|
||||
'層', '叉', '芆', '辿', '伥', '抄',
|
||||
'车', '抻', '沈', '沉', '阷', '吃',
|
||||
'充', '抽', '出', '欻', '揣', '巛',
|
||||
'刅', '吹', '旾', '逴', '呲', '匆',
|
||||
'凑', '粗', '汆', '崔', '邨', '搓',
|
||||
'咑', '呆', '丹', '当', '刀', '嘚',
|
||||
'扥', '灯', '氐', '嗲', '甸', '刁',
|
||||
'爹', '丁', '丟', '东', '吺', '厾',
|
||||
'耑', '襨', '吨', '多', '妸', '诶',
|
||||
'奀', '鞥', '儿', '发', '帆', '匚',
|
||||
'飞', '分', '丰', '覅', '仏', '紑',
|
||||
'伕', '旮', '侅', '甘', '冈', '皋',
|
||||
'戈', '给', '根', '刯', '工', '勾',
|
||||
'估', '瓜', '乖', '关', '光', '归',
|
||||
'丨', '呙', '哈', '咍', '佄', '夯',
|
||||
'茠', '诃', '黒', '拫', '亨', '噷',
|
||||
'叿', '齁', '乯', '花', '怀', '犿',
|
||||
'巟', '灰', '昏', '吙', '丌', '加',
|
||||
'戋', '江', '艽', '阶', '巾', '坕',
|
||||
'冂', '丩', '凥', '姢', '噘', '军',
|
||||
'咔', '开', '刊', '忼', '尻', '匼',
|
||||
'肎', '劥', '空', '抠', '扝', '夸',
|
||||
'蒯', '宽', '匡', '亏', '坤', '扩',
|
||||
'垃', '来', '兰', '啷', '捞', '肋',
|
||||
'勒', '崚', '刕', '俩', '奁', '良',
|
||||
'撩', '列', '拎', '刢', '溜', '囖',
|
||||
'龙', '瞜', '噜', '娈', '畧', '抡',
|
||||
'罗', '呣', '妈', '埋', '嫚', '牤',
|
||||
'猫', '么', '呅', '门', '甿', '咪',
|
||||
'宀', '喵', '乜', '民', '名', '谬',
|
||||
'摸', '哞', '毪', '嗯', '拏', '腉',
|
||||
'囡', '囔', '孬', '疒', '娞', '恁',
|
||||
'能', '妮', '拈', '嬢', '鸟', '捏',
|
||||
'囜', '宁', '妞', '农', '羺', '奴',
|
||||
'奻', '疟', '黁', '郍', '喔', '讴',
|
||||
'妑', '拍', '眅', '乓', '抛', '呸',
|
||||
'喷', '匉', '丕', '囨', '剽', '氕',
|
||||
'姘', '乒', '钋', '剖', '仆', '七',
|
||||
'掐', '千', '呛', '悄', '癿', '亲',
|
||||
'狅', '芎', '丘', '区', '峑', '缺',
|
||||
'夋', '呥', '穣', '娆', '惹', '人',
|
||||
'扔', '日', '茸', '厹', '邚', '挼',
|
||||
'堧', '婑', '瞤', '捼', '仨', '毢',
|
||||
'三', '桒', '掻', '閪', '森', '僧',
|
||||
'杀', '筛', '山', '伤', '弰', '奢',
|
||||
'申', '莘', '敒', '升', '尸', '収',
|
||||
'书', '刷', '衰', '闩', '双', '谁',
|
||||
'吮', '说', '厶', '忪', '捜', '苏',
|
||||
'狻', '夊', '孙', '唆', '他', '囼',
|
||||
'坍', '汤', '夲', '忑', '熥', '剔',
|
||||
'天', '旫', '帖', '厅', '囲', '偷',
|
||||
'凸', '湍', '推', '吞', '乇', '穵',
|
||||
'歪', '弯', '尣', '危', '昷', '翁',
|
||||
'挝', '乌', '夕', '虲', '仚', '乡',
|
||||
'灱', '些', '心', '星', '凶', '休',
|
||||
'吁', '吅', '削', '坃', '丫', '恹',
|
||||
'央', '幺', '倻', '一', '囙', '应',
|
||||
'哟', '佣', '优', '扜', '囦', '曰',
|
||||
'晕', '筠', '筼', '帀', '災', '兂',
|
||||
'匨', '傮', '则', '贼', '怎', '増',
|
||||
'扎', '捚', '沾', '张', '长', '長',
|
||||
'佋', '蜇', '贞', '争', '之', '峙',
|
||||
'庢', '中', '州', '朱', '抓', '拽',
|
||||
'专', '妆', '隹', '宒', '卓', '乲',
|
||||
'宗', '邹', '租', '钻', '厜', '尊',
|
||||
'昨', '兙', '鿃', '鿄'
|
||||
)
|
||||
|
||||
val PINYINS = arrayOf(
|
||||
byteArrayOf(65, 0, 0, 0, 0, 0), byteArrayOf(65, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(65, 78, 0, 0, 0, 0), byteArrayOf(65, 78, 71, 0, 0, 0),
|
||||
byteArrayOf(65, 79, 0, 0, 0, 0), byteArrayOf(66, 65, 0, 0, 0, 0),
|
||||
byteArrayOf(66, 65, 73, 0, 0, 0), byteArrayOf(66, 65, 78, 0, 0, 0),
|
||||
byteArrayOf(66, 65, 78, 71, 0, 0), byteArrayOf(66, 65, 79, 0, 0, 0),
|
||||
byteArrayOf(66, 69, 73, 0, 0, 0), byteArrayOf(66, 69, 78, 0, 0, 0),
|
||||
byteArrayOf(66, 69, 78, 71, 0, 0), byteArrayOf(66, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(66, 73, 65, 78, 0, 0), byteArrayOf(66, 73, 65, 79, 0, 0),
|
||||
byteArrayOf(66, 73, 69, 0, 0, 0), byteArrayOf(66, 73, 78, 0, 0, 0),
|
||||
byteArrayOf(66, 73, 78, 71, 0, 0), byteArrayOf(66, 79, 0, 0, 0, 0),
|
||||
byteArrayOf(66, 85, 0, 0, 0, 0), byteArrayOf(67, 65, 0, 0, 0, 0),
|
||||
byteArrayOf(67, 65, 73, 0, 0, 0), byteArrayOf(67, 65, 78, 0, 0, 0),
|
||||
byteArrayOf(67, 65, 78, 71, 0, 0), byteArrayOf(67, 65, 79, 0, 0, 0),
|
||||
byteArrayOf(67, 69, 0, 0, 0, 0), byteArrayOf(67, 69, 78, 0, 0, 0),
|
||||
byteArrayOf(67, 69, 78, 71, 0, 0), byteArrayOf(90, 69, 78, 71, 0, 0),
|
||||
byteArrayOf(67, 69, 78, 71, 0, 0), byteArrayOf(67, 72, 65, 0, 0, 0),
|
||||
byteArrayOf(67, 72, 65, 73, 0, 0), byteArrayOf(67, 72, 65, 78, 0, 0),
|
||||
byteArrayOf(67, 72, 65, 78, 71, 0), byteArrayOf(67, 72, 65, 79, 0, 0),
|
||||
byteArrayOf(67, 72, 69, 0, 0, 0), byteArrayOf(67, 72, 69, 78, 0, 0),
|
||||
byteArrayOf(83, 72, 69, 78, 0, 0), byteArrayOf(67, 72, 69, 78, 0, 0),
|
||||
byteArrayOf(67, 72, 69, 78, 71, 0), byteArrayOf(67, 72, 73, 0, 0, 0),
|
||||
byteArrayOf(67, 72, 79, 78, 71, 0), byteArrayOf(67, 72, 79, 85, 0, 0),
|
||||
byteArrayOf(67, 72, 85, 0, 0, 0), byteArrayOf(67, 72, 85, 65, 0, 0),
|
||||
byteArrayOf(67, 72, 85, 65, 73, 0), byteArrayOf(67, 72, 85, 65, 78, 0),
|
||||
byteArrayOf(67, 72, 85, 65, 78, 71), byteArrayOf(67, 72, 85, 73, 0, 0),
|
||||
byteArrayOf(67, 72, 85, 78, 0, 0), byteArrayOf(67, 72, 85, 79, 0, 0),
|
||||
byteArrayOf(67, 73, 0, 0, 0, 0), byteArrayOf(67, 79, 78, 71, 0, 0),
|
||||
byteArrayOf(67, 79, 85, 0, 0, 0), byteArrayOf(67, 85, 0, 0, 0, 0),
|
||||
byteArrayOf(67, 85, 65, 78, 0, 0), byteArrayOf(67, 85, 73, 0, 0, 0),
|
||||
byteArrayOf(67, 85, 78, 0, 0, 0), byteArrayOf(67, 85, 79, 0, 0, 0),
|
||||
byteArrayOf(68, 65, 0, 0, 0, 0), byteArrayOf(68, 65, 73, 0, 0, 0),
|
||||
byteArrayOf(68, 65, 78, 0, 0, 0), byteArrayOf(68, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(68, 65, 79, 0, 0, 0), byteArrayOf(68, 69, 0, 0, 0, 0),
|
||||
byteArrayOf(68, 69, 78, 0, 0, 0), byteArrayOf(68, 69, 78, 71, 0, 0),
|
||||
byteArrayOf(68, 73, 0, 0, 0, 0), byteArrayOf(68, 73, 65, 0, 0, 0),
|
||||
byteArrayOf(68, 73, 65, 78, 0, 0), byteArrayOf(68, 73, 65, 79, 0, 0),
|
||||
byteArrayOf(68, 73, 69, 0, 0, 0), byteArrayOf(68, 73, 78, 71, 0, 0),
|
||||
byteArrayOf(68, 73, 85, 0, 0, 0), byteArrayOf(68, 79, 78, 71, 0, 0),
|
||||
byteArrayOf(68, 79, 85, 0, 0, 0), byteArrayOf(68, 85, 0, 0, 0, 0),
|
||||
byteArrayOf(68, 85, 65, 78, 0, 0), byteArrayOf(68, 85, 73, 0, 0, 0),
|
||||
byteArrayOf(68, 85, 78, 0, 0, 0), byteArrayOf(68, 85, 79, 0, 0, 0),
|
||||
byteArrayOf(69, 0, 0, 0, 0, 0), byteArrayOf(69, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(69, 78, 0, 0, 0, 0), byteArrayOf(69, 78, 71, 0, 0, 0),
|
||||
byteArrayOf(69, 82, 0, 0, 0, 0), byteArrayOf(70, 65, 0, 0, 0, 0),
|
||||
byteArrayOf(70, 65, 78, 0, 0, 0), byteArrayOf(70, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(70, 69, 73, 0, 0, 0), byteArrayOf(70, 69, 78, 0, 0, 0),
|
||||
byteArrayOf(70, 69, 78, 71, 0, 0), byteArrayOf(70, 73, 65, 79, 0, 0),
|
||||
byteArrayOf(70, 79, 0, 0, 0, 0), byteArrayOf(70, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(70, 85, 0, 0, 0, 0), byteArrayOf(71, 65, 0, 0, 0, 0),
|
||||
byteArrayOf(71, 65, 73, 0, 0, 0), byteArrayOf(71, 65, 78, 0, 0, 0),
|
||||
byteArrayOf(71, 65, 78, 71, 0, 0), byteArrayOf(71, 65, 79, 0, 0, 0),
|
||||
byteArrayOf(71, 69, 0, 0, 0, 0), byteArrayOf(71, 69, 73, 0, 0, 0),
|
||||
byteArrayOf(71, 69, 78, 0, 0, 0), byteArrayOf(71, 69, 78, 71, 0, 0),
|
||||
byteArrayOf(71, 79, 78, 71, 0, 0), byteArrayOf(71, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(71, 85, 0, 0, 0, 0), byteArrayOf(71, 85, 65, 0, 0, 0),
|
||||
byteArrayOf(71, 85, 65, 73, 0, 0), byteArrayOf(71, 85, 65, 78, 0, 0),
|
||||
byteArrayOf(71, 85, 65, 78, 71, 0), byteArrayOf(71, 85, 73, 0, 0, 0),
|
||||
byteArrayOf(71, 85, 78, 0, 0, 0), byteArrayOf(71, 85, 79, 0, 0, 0),
|
||||
byteArrayOf(72, 65, 0, 0, 0, 0), byteArrayOf(72, 65, 73, 0, 0, 0),
|
||||
byteArrayOf(72, 65, 78, 0, 0, 0), byteArrayOf(72, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(72, 65, 79, 0, 0, 0), byteArrayOf(72, 69, 0, 0, 0, 0),
|
||||
byteArrayOf(72, 69, 73, 0, 0, 0), byteArrayOf(72, 69, 78, 0, 0, 0),
|
||||
byteArrayOf(72, 69, 78, 71, 0, 0), byteArrayOf(72, 77, 0, 0, 0, 0),
|
||||
byteArrayOf(72, 79, 78, 71, 0, 0), byteArrayOf(72, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(72, 85, 0, 0, 0, 0), byteArrayOf(72, 85, 65, 0, 0, 0),
|
||||
byteArrayOf(72, 85, 65, 73, 0, 0), byteArrayOf(72, 85, 65, 78, 0, 0),
|
||||
byteArrayOf(72, 85, 65, 78, 71, 0), byteArrayOf(72, 85, 73, 0, 0, 0),
|
||||
byteArrayOf(72, 85, 78, 0, 0, 0), byteArrayOf(72, 85, 79, 0, 0, 0),
|
||||
byteArrayOf(74, 73, 0, 0, 0, 0), byteArrayOf(74, 73, 65, 0, 0, 0),
|
||||
byteArrayOf(74, 73, 65, 78, 0, 0), byteArrayOf(74, 73, 65, 78, 71, 0),
|
||||
byteArrayOf(74, 73, 65, 79, 0, 0), byteArrayOf(74, 73, 69, 0, 0, 0),
|
||||
byteArrayOf(74, 73, 78, 0, 0, 0), byteArrayOf(74, 73, 78, 71, 0, 0),
|
||||
byteArrayOf(74, 73, 79, 78, 71, 0), byteArrayOf(74, 73, 85, 0, 0, 0),
|
||||
byteArrayOf(74, 85, 0, 0, 0, 0), byteArrayOf(74, 85, 65, 78, 0, 0),
|
||||
byteArrayOf(74, 85, 69, 0, 0, 0), byteArrayOf(74, 85, 78, 0, 0, 0),
|
||||
byteArrayOf(75, 65, 0, 0, 0, 0), byteArrayOf(75, 65, 73, 0, 0, 0),
|
||||
byteArrayOf(75, 65, 78, 0, 0, 0), byteArrayOf(75, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(75, 65, 79, 0, 0, 0), byteArrayOf(75, 69, 0, 0, 0, 0),
|
||||
byteArrayOf(75, 69, 78, 0, 0, 0), byteArrayOf(75, 69, 78, 71, 0, 0),
|
||||
byteArrayOf(75, 79, 78, 71, 0, 0), byteArrayOf(75, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(75, 85, 0, 0, 0, 0), byteArrayOf(75, 85, 65, 0, 0, 0),
|
||||
byteArrayOf(75, 85, 65, 73, 0, 0), byteArrayOf(75, 85, 65, 78, 0, 0),
|
||||
byteArrayOf(75, 85, 65, 78, 71, 0), byteArrayOf(75, 85, 73, 0, 0, 0),
|
||||
byteArrayOf(75, 85, 78, 0, 0, 0), byteArrayOf(75, 85, 79, 0, 0, 0),
|
||||
byteArrayOf(76, 65, 0, 0, 0, 0), byteArrayOf(76, 65, 73, 0, 0, 0),
|
||||
byteArrayOf(76, 65, 78, 0, 0, 0), byteArrayOf(76, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(76, 65, 79, 0, 0, 0), byteArrayOf(76, 69, 0, 0, 0, 0),
|
||||
byteArrayOf(76, 69, 73, 0, 0, 0), byteArrayOf(76, 69, 78, 71, 0, 0),
|
||||
byteArrayOf(76, 73, 0, 0, 0, 0), byteArrayOf(76, 73, 65, 0, 0, 0),
|
||||
byteArrayOf(76, 73, 65, 78, 0, 0), byteArrayOf(76, 73, 65, 78, 71, 0),
|
||||
byteArrayOf(76, 73, 65, 79, 0, 0), byteArrayOf(76, 73, 69, 0, 0, 0),
|
||||
byteArrayOf(76, 73, 78, 0, 0, 0), byteArrayOf(76, 73, 78, 71, 0, 0),
|
||||
byteArrayOf(76, 73, 85, 0, 0, 0), byteArrayOf(76, 79, 0, 0, 0, 0),
|
||||
byteArrayOf(76, 79, 78, 71, 0, 0), byteArrayOf(76, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(76, 85, 0, 0, 0, 0), byteArrayOf(76, 85, 65, 78, 0, 0),
|
||||
byteArrayOf(76, 85, 69, 0, 0, 0), byteArrayOf(76, 85, 78, 0, 0, 0),
|
||||
byteArrayOf(76, 85, 79, 0, 0, 0), byteArrayOf(77, 0, 0, 0, 0, 0),
|
||||
byteArrayOf(77, 65, 0, 0, 0, 0), byteArrayOf(77, 65, 73, 0, 0, 0),
|
||||
byteArrayOf(77, 65, 78, 0, 0, 0), byteArrayOf(77, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(77, 65, 79, 0, 0, 0), byteArrayOf(77, 69, 0, 0, 0, 0),
|
||||
byteArrayOf(77, 69, 73, 0, 0, 0), byteArrayOf(77, 69, 78, 0, 0, 0),
|
||||
byteArrayOf(77, 69, 78, 71, 0, 0), byteArrayOf(77, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(77, 73, 65, 78, 0, 0), byteArrayOf(77, 73, 65, 79, 0, 0),
|
||||
byteArrayOf(77, 73, 69, 0, 0, 0), byteArrayOf(77, 73, 78, 0, 0, 0),
|
||||
byteArrayOf(77, 73, 78, 71, 0, 0), byteArrayOf(77, 73, 85, 0, 0, 0),
|
||||
byteArrayOf(77, 79, 0, 0, 0, 0), byteArrayOf(77, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(77, 85, 0, 0, 0, 0), byteArrayOf(78, 0, 0, 0, 0, 0),
|
||||
byteArrayOf(78, 65, 0, 0, 0, 0), byteArrayOf(78, 65, 73, 0, 0, 0),
|
||||
byteArrayOf(78, 65, 78, 0, 0, 0), byteArrayOf(78, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(78, 65, 79, 0, 0, 0), byteArrayOf(78, 69, 0, 0, 0, 0),
|
||||
byteArrayOf(78, 69, 73, 0, 0, 0), byteArrayOf(78, 69, 78, 0, 0, 0),
|
||||
byteArrayOf(78, 69, 78, 71, 0, 0), byteArrayOf(78, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(78, 73, 65, 78, 0, 0), byteArrayOf(78, 73, 65, 78, 71, 0),
|
||||
byteArrayOf(78, 73, 65, 79, 0, 0), byteArrayOf(78, 73, 69, 0, 0, 0),
|
||||
byteArrayOf(78, 73, 78, 0, 0, 0), byteArrayOf(78, 73, 78, 71, 0, 0),
|
||||
byteArrayOf(78, 73, 85, 0, 0, 0), byteArrayOf(78, 79, 78, 71, 0, 0),
|
||||
byteArrayOf(78, 79, 85, 0, 0, 0), byteArrayOf(78, 85, 0, 0, 0, 0),
|
||||
byteArrayOf(78, 85, 65, 78, 0, 0), byteArrayOf(78, 85, 69, 0, 0, 0),
|
||||
byteArrayOf(78, 85, 78, 0, 0, 0), byteArrayOf(78, 85, 79, 0, 0, 0),
|
||||
byteArrayOf(79, 0, 0, 0, 0, 0), byteArrayOf(79, 85, 0, 0, 0, 0),
|
||||
byteArrayOf(80, 65, 0, 0, 0, 0), byteArrayOf(80, 65, 73, 0, 0, 0),
|
||||
byteArrayOf(80, 65, 78, 0, 0, 0), byteArrayOf(80, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(80, 65, 79, 0, 0, 0), byteArrayOf(80, 69, 73, 0, 0, 0),
|
||||
byteArrayOf(80, 69, 78, 0, 0, 0), byteArrayOf(80, 69, 78, 71, 0, 0),
|
||||
byteArrayOf(80, 73, 0, 0, 0, 0), byteArrayOf(80, 73, 65, 78, 0, 0),
|
||||
byteArrayOf(80, 73, 65, 79, 0, 0), byteArrayOf(80, 73, 69, 0, 0, 0),
|
||||
byteArrayOf(80, 73, 78, 0, 0, 0), byteArrayOf(80, 73, 78, 71, 0, 0),
|
||||
byteArrayOf(80, 79, 0, 0, 0, 0), byteArrayOf(80, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(80, 85, 0, 0, 0, 0), byteArrayOf(81, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(81, 73, 65, 0, 0, 0), byteArrayOf(81, 73, 65, 78, 0, 0),
|
||||
byteArrayOf(81, 73, 65, 78, 71, 0), byteArrayOf(81, 73, 65, 79, 0, 0),
|
||||
byteArrayOf(81, 73, 69, 0, 0, 0), byteArrayOf(81, 73, 78, 0, 0, 0),
|
||||
byteArrayOf(81, 73, 78, 71, 0, 0), byteArrayOf(81, 73, 79, 78, 71, 0),
|
||||
byteArrayOf(81, 73, 85, 0, 0, 0), byteArrayOf(81, 85, 0, 0, 0, 0),
|
||||
byteArrayOf(81, 85, 65, 78, 0, 0), byteArrayOf(81, 85, 69, 0, 0, 0),
|
||||
byteArrayOf(81, 85, 78, 0, 0, 0), byteArrayOf(82, 65, 78, 0, 0, 0),
|
||||
byteArrayOf(82, 65, 78, 71, 0, 0), byteArrayOf(82, 65, 79, 0, 0, 0),
|
||||
byteArrayOf(82, 69, 0, 0, 0, 0), byteArrayOf(82, 69, 78, 0, 0, 0),
|
||||
byteArrayOf(82, 69, 78, 71, 0, 0), byteArrayOf(82, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(82, 79, 78, 71, 0, 0), byteArrayOf(82, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(82, 85, 0, 0, 0, 0), byteArrayOf(82, 85, 65, 0, 0, 0),
|
||||
byteArrayOf(82, 85, 65, 78, 0, 0), byteArrayOf(82, 85, 73, 0, 0, 0),
|
||||
byteArrayOf(82, 85, 78, 0, 0, 0), byteArrayOf(82, 85, 79, 0, 0, 0),
|
||||
byteArrayOf(83, 65, 0, 0, 0, 0), byteArrayOf(83, 65, 73, 0, 0, 0),
|
||||
byteArrayOf(83, 65, 78, 0, 0, 0), byteArrayOf(83, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(83, 65, 79, 0, 0, 0), byteArrayOf(83, 69, 0, 0, 0, 0),
|
||||
byteArrayOf(83, 69, 78, 0, 0, 0), byteArrayOf(83, 69, 78, 71, 0, 0),
|
||||
byteArrayOf(83, 72, 65, 0, 0, 0), byteArrayOf(83, 72, 65, 73, 0, 0),
|
||||
byteArrayOf(83, 72, 65, 78, 0, 0), byteArrayOf(83, 72, 65, 78, 71, 0),
|
||||
byteArrayOf(83, 72, 65, 79, 0, 0), byteArrayOf(83, 72, 69, 0, 0, 0),
|
||||
byteArrayOf(83, 72, 69, 78, 0, 0), byteArrayOf(88, 73, 78, 0, 0, 0),
|
||||
byteArrayOf(83, 72, 69, 78, 0, 0), byteArrayOf(83, 72, 69, 78, 71, 0),
|
||||
byteArrayOf(83, 72, 73, 0, 0, 0), byteArrayOf(83, 72, 79, 85, 0, 0),
|
||||
byteArrayOf(83, 72, 85, 0, 0, 0), byteArrayOf(83, 72, 85, 65, 0, 0),
|
||||
byteArrayOf(83, 72, 85, 65, 73, 0), byteArrayOf(83, 72, 85, 65, 78, 0),
|
||||
byteArrayOf(83, 72, 85, 65, 78, 71), byteArrayOf(83, 72, 85, 73, 0, 0),
|
||||
byteArrayOf(83, 72, 85, 78, 0, 0), byteArrayOf(83, 72, 85, 79, 0, 0),
|
||||
byteArrayOf(83, 73, 0, 0, 0, 0), byteArrayOf(83, 79, 78, 71, 0, 0),
|
||||
byteArrayOf(83, 79, 85, 0, 0, 0), byteArrayOf(83, 85, 0, 0, 0, 0),
|
||||
byteArrayOf(83, 85, 65, 78, 0, 0), byteArrayOf(83, 85, 73, 0, 0, 0),
|
||||
byteArrayOf(83, 85, 78, 0, 0, 0), byteArrayOf(83, 85, 79, 0, 0, 0),
|
||||
byteArrayOf(84, 65, 0, 0, 0, 0), byteArrayOf(84, 65, 73, 0, 0, 0),
|
||||
byteArrayOf(84, 65, 78, 0, 0, 0), byteArrayOf(84, 65, 78, 71, 0, 0),
|
||||
byteArrayOf(84, 65, 79, 0, 0, 0), byteArrayOf(84, 69, 0, 0, 0, 0),
|
||||
byteArrayOf(84, 69, 78, 71, 0, 0), byteArrayOf(84, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(84, 73, 65, 78, 0, 0), byteArrayOf(84, 73, 65, 79, 0, 0),
|
||||
byteArrayOf(84, 73, 69, 0, 0, 0), byteArrayOf(84, 73, 78, 71, 0, 0),
|
||||
byteArrayOf(84, 79, 78, 71, 0, 0), byteArrayOf(84, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(84, 85, 0, 0, 0, 0), byteArrayOf(84, 85, 65, 78, 0, 0),
|
||||
byteArrayOf(84, 85, 73, 0, 0, 0), byteArrayOf(84, 85, 78, 0, 0, 0),
|
||||
byteArrayOf(84, 85, 79, 0, 0, 0), byteArrayOf(87, 65, 0, 0, 0, 0),
|
||||
byteArrayOf(87, 65, 73, 0, 0, 0), byteArrayOf(87, 65, 78, 0, 0, 0),
|
||||
byteArrayOf(87, 65, 78, 71, 0, 0), byteArrayOf(87, 69, 73, 0, 0, 0),
|
||||
byteArrayOf(87, 69, 78, 0, 0, 0), byteArrayOf(87, 69, 78, 71, 0, 0),
|
||||
byteArrayOf(87, 79, 0, 0, 0, 0), byteArrayOf(87, 85, 0, 0, 0, 0),
|
||||
byteArrayOf(88, 73, 0, 0, 0, 0), byteArrayOf(88, 73, 65, 0, 0, 0),
|
||||
byteArrayOf(88, 73, 65, 78, 0, 0), byteArrayOf(88, 73, 65, 78, 71, 0),
|
||||
byteArrayOf(88, 73, 65, 79, 0, 0), byteArrayOf(88, 73, 69, 0, 0, 0),
|
||||
byteArrayOf(88, 73, 78, 0, 0, 0), byteArrayOf(88, 73, 78, 71, 0, 0),
|
||||
byteArrayOf(88, 73, 79, 78, 71, 0), byteArrayOf(88, 73, 85, 0, 0, 0),
|
||||
byteArrayOf(88, 85, 0, 0, 0, 0), byteArrayOf(88, 85, 65, 78, 0, 0),
|
||||
byteArrayOf(88, 85, 69, 0, 0, 0), byteArrayOf(88, 85, 78, 0, 0, 0),
|
||||
byteArrayOf(89, 65, 0, 0, 0, 0), byteArrayOf(89, 65, 78, 0, 0, 0),
|
||||
byteArrayOf(89, 65, 78, 71, 0, 0), byteArrayOf(89, 65, 79, 0, 0, 0),
|
||||
byteArrayOf(89, 69, 0, 0, 0, 0), byteArrayOf(89, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(89, 73, 78, 0, 0, 0), byteArrayOf(89, 73, 78, 71, 0, 0),
|
||||
byteArrayOf(89, 79, 0, 0, 0, 0), byteArrayOf(89, 79, 78, 71, 0, 0),
|
||||
byteArrayOf(89, 79, 85, 0, 0, 0), byteArrayOf(89, 85, 0, 0, 0, 0),
|
||||
byteArrayOf(89, 85, 65, 78, 0, 0), byteArrayOf(89, 85, 69, 0, 0, 0),
|
||||
byteArrayOf(89, 85, 78, 0, 0, 0), byteArrayOf(74, 85, 78, 0, 0, 0),
|
||||
byteArrayOf(89, 85, 78, 0, 0, 0), byteArrayOf(90, 65, 0, 0, 0, 0),
|
||||
byteArrayOf(90, 65, 73, 0, 0, 0), byteArrayOf(90, 65, 78, 0, 0, 0),
|
||||
byteArrayOf(90, 65, 78, 71, 0, 0), byteArrayOf(90, 65, 79, 0, 0, 0),
|
||||
byteArrayOf(90, 69, 0, 0, 0, 0), byteArrayOf(90, 69, 73, 0, 0, 0),
|
||||
byteArrayOf(90, 69, 78, 0, 0, 0), byteArrayOf(90, 69, 78, 71, 0, 0),
|
||||
byteArrayOf(90, 72, 65, 0, 0, 0), byteArrayOf(90, 72, 65, 73, 0, 0),
|
||||
byteArrayOf(90, 72, 65, 78, 0, 0), byteArrayOf(90, 72, 65, 78, 71, 0),
|
||||
byteArrayOf(67, 72, 65, 78, 71, 0), byteArrayOf(90, 72, 65, 78, 71, 0),
|
||||
byteArrayOf(90, 72, 65, 79, 0, 0), byteArrayOf(90, 72, 69, 0, 0, 0),
|
||||
byteArrayOf(90, 72, 69, 78, 0, 0), byteArrayOf(90, 72, 69, 78, 71, 0),
|
||||
byteArrayOf(90, 72, 73, 0, 0, 0), byteArrayOf(83, 72, 73, 0, 0, 0),
|
||||
byteArrayOf(90, 72, 73, 0, 0, 0), byteArrayOf(90, 72, 79, 78, 71, 0),
|
||||
byteArrayOf(90, 72, 79, 85, 0, 0), byteArrayOf(90, 72, 85, 0, 0, 0),
|
||||
byteArrayOf(90, 72, 85, 65, 0, 0), byteArrayOf(90, 72, 85, 65, 73, 0),
|
||||
byteArrayOf(90, 72, 85, 65, 78, 0), byteArrayOf(90, 72, 85, 65, 78, 71),
|
||||
byteArrayOf(90, 72, 85, 73, 0, 0), byteArrayOf(90, 72, 85, 78, 0, 0),
|
||||
byteArrayOf(90, 72, 85, 79, 0, 0), byteArrayOf(90, 73, 0, 0, 0, 0),
|
||||
byteArrayOf(90, 79, 78, 71, 0, 0), byteArrayOf(90, 79, 85, 0, 0, 0),
|
||||
byteArrayOf(90, 85, 0, 0, 0, 0), byteArrayOf(90, 85, 65, 78, 0, 0),
|
||||
byteArrayOf(90, 85, 73, 0, 0, 0), byteArrayOf(90, 85, 78, 0, 0, 0),
|
||||
byteArrayOf(90, 85, 79, 0, 0, 0), byteArrayOf(0, 0, 0, 0, 0, 0),
|
||||
byteArrayOf(83, 72, 65, 78, 0, 0), byteArrayOf(0, 0, 0, 0, 0, 0)
|
||||
)
|
||||
|
||||
private const val FIRST_PINYIN_UNIHAN = "阿"
|
||||
private const val LAST_PINYIN_UNIHAN = "鿿"
|
||||
|
||||
private val COLLATOR: Collator = Collator.getInstance(Locale.CHINA)
|
||||
|
||||
private var sInstance: HanziToPinyin? = null
|
||||
|
||||
fun getInstance(): HanziToPinyin {
|
||||
synchronized(HanziToPinyin::class.java) {
|
||||
if (sInstance != null) {
|
||||
return sInstance!!
|
||||
}
|
||||
|
||||
val locale = Collator.getAvailableLocales()
|
||||
for (value in locale) {
|
||||
if (value == Locale.CHINA || value.language.contains("zh")) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Self validation. Result: ${doSelfValidation()}")
|
||||
}
|
||||
sInstance = HanziToPinyin(true)
|
||||
return sInstance!!
|
||||
}
|
||||
}
|
||||
|
||||
if (sInstance == null) {
|
||||
if (Locale.CHINA == Locale.getDefault()) {
|
||||
sInstance = HanziToPinyin(true)
|
||||
return sInstance!!
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled")
|
||||
sInstance = HanziToPinyin(false)
|
||||
return sInstance!!
|
||||
}
|
||||
}
|
||||
|
||||
private fun doSelfValidation(): Boolean {
|
||||
val lastChar = UNIHANS[0]
|
||||
var lastString = lastChar.toString()
|
||||
for (c in UNIHANS) {
|
||||
if (lastChar == c) {
|
||||
continue
|
||||
}
|
||||
val curString = c.toString()
|
||||
val cmp = COLLATOR.compare(lastString, curString)
|
||||
if (cmp >= 0) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"Internal error in Unihan table. The last string \"$lastString\" " +
|
||||
"is greater than current string \"$curString\"."
|
||||
)
|
||||
return false
|
||||
}
|
||||
lastString = curString
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,16 @@ import android.util.Log
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import com.sukisu.ultra.BuildConfig
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.Properties
|
||||
|
||||
|
||||
/**
|
||||
@@ -99,6 +99,13 @@ fun execKsud(args: String, newShell: Boolean = false): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getFeatureStatus(feature: String): String = withContext(Dispatchers.IO) {
|
||||
val shell = getRootShell()
|
||||
val out = shell.newJob()
|
||||
.add("${getKsuDaemonPath()} feature check $feature").to(ArrayList<String>(), null).exec().out
|
||||
out.firstOrNull()?.trim().orEmpty()
|
||||
}
|
||||
|
||||
fun install() {
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so").absolutePath
|
||||
@@ -106,11 +113,15 @@ fun install() {
|
||||
Log.w(TAG, "install result: $result, cost: ${SystemClock.elapsedRealtime() - start}ms")
|
||||
}
|
||||
|
||||
fun hasMetaModule(): Boolean {
|
||||
return getMetaModuleImplement() != "None"
|
||||
}
|
||||
|
||||
fun listModules(): String {
|
||||
val shell = getRootShell()
|
||||
|
||||
val out =
|
||||
shell.newJob().add("${getKsuDaemonPath()} module list").to(ArrayList(), null).exec().out
|
||||
val out = shell.newJob()
|
||||
.add("${getKsuDaemonPath()} module list").to(ArrayList(), null).exec().out
|
||||
return out.joinToString("\n").ifBlank { "[]" }
|
||||
}
|
||||
|
||||
@@ -151,6 +162,13 @@ fun restoreModule(id: String): Boolean {
|
||||
return result
|
||||
}
|
||||
|
||||
fun undoUninstallModule(id: String): Boolean {
|
||||
val cmd = "module undo-uninstall $id"
|
||||
val result = execKsud(cmd, true)
|
||||
Log.i(TAG, "undo uninstall module $id result: $result")
|
||||
return result
|
||||
}
|
||||
|
||||
private fun flashWithIO(
|
||||
cmd: String,
|
||||
onStdout: (String) -> Unit,
|
||||
@@ -563,9 +581,27 @@ fun getSuSFSFeatures(): String {
|
||||
return runCmd(shell, cmd)
|
||||
}
|
||||
|
||||
fun getZygiskImplement(): String {
|
||||
val shell = getRootShell()
|
||||
fun getMetaModuleImplement(): String {
|
||||
try {
|
||||
val metaModuleProp = SuFile.open("/data/adb/metamodule/module.prop")
|
||||
if (!metaModuleProp.isFile) {
|
||||
Log.i(TAG, "Meta module implement: None")
|
||||
return "None"
|
||||
}
|
||||
|
||||
val prop = Properties()
|
||||
prop.load(metaModuleProp.newInputStream())
|
||||
|
||||
val name = prop.getProperty("name")
|
||||
Log.i(TAG, "Meta module implement: $name")
|
||||
return name
|
||||
} catch (t : Throwable) {
|
||||
Log.i(TAG, "Meta module implement: None")
|
||||
return "None"
|
||||
}
|
||||
}
|
||||
|
||||
fun getZygiskImplement(): String {
|
||||
val zygiskModuleIds = listOf(
|
||||
"zygisksu",
|
||||
"rezygisk",
|
||||
@@ -573,14 +609,19 @@ fun getZygiskImplement(): String {
|
||||
)
|
||||
|
||||
for (moduleId in zygiskModuleIds) {
|
||||
val modulePath = "/data/adb/modules/$moduleId"
|
||||
when {
|
||||
ShellUtils.fastCmdResult(shell, "test -f $modulePath/module.prop && test ! -f $modulePath/disable") -> {
|
||||
val result = ShellUtils.fastCmd(shell, "grep '^name=' $modulePath/module.prop | cut -d'=' -f2")
|
||||
Log.i(TAG, "Zygisk implement: $result")
|
||||
return result
|
||||
}
|
||||
}
|
||||
// 忽略禁用/即将删除
|
||||
if (SuFile.open("/data/adb/modules/$moduleId/disable").isFile || SuFile.open("/data/adb/modules/$moduleId/remove").isFile) continue
|
||||
|
||||
// 读取prop
|
||||
val propFile = SuFile.open("/data/adb/modules/$moduleId/module.prop")
|
||||
if (!propFile.isFile) continue
|
||||
|
||||
val prop = Properties()
|
||||
prop.load(propFile.newInputStream())
|
||||
|
||||
val name = prop.getProperty("name")
|
||||
Log.i(TAG, "Zygisk implement: $name")
|
||||
return name
|
||||
}
|
||||
|
||||
Log.i(TAG, "Zygisk implement: None")
|
||||
@@ -669,15 +710,14 @@ fun readUidScannerFile(): Boolean {
|
||||
return try {
|
||||
ShellUtils.fastCmd(shell, "cat /data/adb/ksu/.uid_scanner").trim() == "1"
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun addUmountPath(path: String, checkMnt: Boolean, flags: Int): Boolean {
|
||||
fun addUmountPath(path: String, flags: Int): Boolean {
|
||||
val shell = getRootShell()
|
||||
val checkMntFlag = if (checkMnt) "--check-mnt" else ""
|
||||
val flagsArg = if (flags >= 0) "--flags $flags" else ""
|
||||
val cmd = "${getKsuDaemonPath()} umount add $path $checkMntFlag $flagsArg"
|
||||
val cmd = "${getKsuDaemonPath()} umount add $path $flagsArg"
|
||||
val result = ShellUtils.fastCmdResult(shell, cmd)
|
||||
Log.i(TAG, "add umount path $path result: $result")
|
||||
return result
|
||||
|
||||
@@ -54,7 +54,8 @@ class HomeViewModel : ViewModel() {
|
||||
val kpmModuleCount: Int = 0,
|
||||
val managersList: Natives.ManagersList? = null,
|
||||
val isDynamicSignEnabled: Boolean = false,
|
||||
val zygiskImplement: String = ""
|
||||
val zygiskImplement: String = "",
|
||||
val metaModuleImplement: String = ""
|
||||
)
|
||||
|
||||
// 状态变量
|
||||
@@ -79,6 +80,8 @@ class HomeViewModel : ViewModel() {
|
||||
private set
|
||||
var isHideZygiskImplement by mutableStateOf(false)
|
||||
private set
|
||||
var isHideMetaModuleImplement by mutableStateOf(false)
|
||||
private set
|
||||
var isHideLinkCard by mutableStateOf(false)
|
||||
private set
|
||||
var showKpmInfo by mutableStateOf(false)
|
||||
@@ -109,6 +112,7 @@ class HomeViewModel : ViewModel() {
|
||||
isHideSusfsStatus = settingsPrefs.getBoolean("is_hide_susfs_status", false)
|
||||
isHideLinkCard = settingsPrefs.getBoolean("is_hide_link_card", false)
|
||||
isHideZygiskImplement = settingsPrefs.getBoolean("is_hide_zygisk_Implement", false)
|
||||
isHideMetaModuleImplement = settingsPrefs.getBoolean("is_hide_meta_module_Implement", false)
|
||||
showKpmInfo = settingsPrefs.getBoolean("show_kpm_info", false)
|
||||
}
|
||||
}
|
||||
@@ -222,7 +226,8 @@ class HomeViewModel : ViewModel() {
|
||||
superuserCount = moduleInfo.second,
|
||||
moduleCount = moduleInfo.third,
|
||||
kpmModuleCount = moduleInfo.fourth,
|
||||
zygiskImplement = moduleInfo.fifth
|
||||
zygiskImplement = moduleInfo.fifth,
|
||||
metaModuleImplement = moduleInfo.sixth
|
||||
)
|
||||
}
|
||||
|
||||
@@ -398,7 +403,7 @@ class HomeViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadModuleInfo(): Tuple5<String, Int, Int, Int, String> {
|
||||
private suspend fun loadModuleInfo(): Tuple6<String, Int, Int, Int, String, String> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val kpmVersion = try {
|
||||
getKpmVersion()
|
||||
@@ -430,7 +435,13 @@ class HomeViewModel : ViewModel() {
|
||||
"None"
|
||||
}
|
||||
|
||||
Tuple5(kpmVersion, superuserCount, moduleCount, kpmModuleCount, zygiskImplement)
|
||||
val metaModuleImplement = try {
|
||||
getMetaModuleImplement()
|
||||
} catch (_: Exception) {
|
||||
"None"
|
||||
}
|
||||
|
||||
Tuple6(kpmVersion, superuserCount, moduleCount, kpmModuleCount, zygiskImplement, metaModuleImplement)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,6 +578,15 @@ class HomeViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
data class Tuple6<T1, T2, T3, T4, T5, T6>(
|
||||
val first: T1,
|
||||
val second: T2,
|
||||
val third: T3,
|
||||
val fourth: T4,
|
||||
val fifth: T5,
|
||||
val sixth: T6
|
||||
)
|
||||
|
||||
data class Tuple5<T1, T2, T3, T4, T5>(
|
||||
val first: T1,
|
||||
val second: T2,
|
||||
|
||||
@@ -85,6 +85,7 @@ class ModuleViewModel : ViewModel() {
|
||||
val updateJson: String,
|
||||
val hasWebUi: Boolean,
|
||||
val hasActionScript: Boolean,
|
||||
val metamodule: Boolean,
|
||||
val dirId: String, // real module id (dir name)
|
||||
var config: ModuleConfig? = null,
|
||||
var isVerified: Boolean = false, // 添加验证状态字段
|
||||
@@ -105,7 +106,7 @@ class ModuleViewModel : ViewModel() {
|
||||
).thenBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
|
||||
modules.filter {
|
||||
it.id.contains(search, true) || it.name.contains(search, true) || HanziToPinyin.getInstance()
|
||||
.toPinyinString(it.name).contains(search, true)
|
||||
.toPinyinString(it.name)?.contains(search, true) == true
|
||||
}.sortedWith(comparator).also {
|
||||
isRefreshing = false
|
||||
}
|
||||
@@ -143,15 +144,16 @@ class ModuleViewModel : ViewModel() {
|
||||
obj.optString("name"),
|
||||
obj.optString("author", "Unknown"),
|
||||
obj.optString("version", "Unknown"),
|
||||
obj.optInt("versionCode", 0),
|
||||
obj.getIntCompat("versionCode", 0),
|
||||
obj.optString("description"),
|
||||
obj.getBoolean("enabled"),
|
||||
obj.getBoolean("update"),
|
||||
obj.getBoolean("remove"),
|
||||
obj.getBooleanCompat("enabled"),
|
||||
obj.getBooleanCompat("update"),
|
||||
obj.getBooleanCompat("remove"),
|
||||
obj.optString("updateJson"),
|
||||
obj.optBoolean("web"),
|
||||
obj.optBoolean("action"),
|
||||
obj.getString("dir_id")
|
||||
obj.getBooleanCompat("web"),
|
||||
obj.getBooleanCompat("action"),
|
||||
obj.getBooleanCompat("metamodule"),
|
||||
obj.optString("dir_id", obj.getString("id"))
|
||||
)
|
||||
}.toList()
|
||||
|
||||
@@ -305,6 +307,7 @@ fun ModuleViewModel.ModuleInfo.copy(
|
||||
updateJson: String = this.updateJson,
|
||||
hasWebUi: Boolean = this.hasWebUi,
|
||||
hasActionScript: Boolean = this.hasActionScript,
|
||||
metamodule: Boolean = this.metamodule,
|
||||
dirId: String = this.dirId,
|
||||
config: ModuleConfig? = this.config,
|
||||
isVerified: Boolean = this.isVerified,
|
||||
@@ -312,7 +315,7 @@ fun ModuleViewModel.ModuleInfo.copy(
|
||||
): ModuleViewModel.ModuleInfo {
|
||||
return ModuleViewModel.ModuleInfo(
|
||||
id, name, author, version, versionCode, description,
|
||||
enabled, update, remove, updateJson, hasWebUi, hasActionScript,
|
||||
enabled, update, remove, updateJson, hasWebUi, hasActionScript, metamodule,
|
||||
dirId, config, isVerified, verificationTimestamp
|
||||
)
|
||||
}
|
||||
@@ -469,6 +472,26 @@ class ModuleSizeCache(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun JSONObject.getBooleanCompat(key: String, default: Boolean = false): Boolean {
|
||||
if (!has(key)) return default
|
||||
return when (val value = opt(key)) {
|
||||
is Boolean -> value
|
||||
is String -> value.equals("true", ignoreCase = true) || value == "1"
|
||||
is Number -> value.toInt() != 0
|
||||
else -> default
|
||||
}
|
||||
}
|
||||
|
||||
private fun JSONObject.getIntCompat(key: String, default: Int = 0): Int {
|
||||
if (!has(key)) return default
|
||||
return when (val value = opt(key)) {
|
||||
is Int -> value
|
||||
is Number -> value.toInt()
|
||||
is String -> value.toIntOrNull() ?: default
|
||||
else -> default
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小的工具函数
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
@@ -27,6 +26,10 @@ import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import com.sukisu.zako.IKsuInterface
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
enum class AppCategory(val displayNameRes: Int, val persistKey: String) {
|
||||
ALL(com.sukisu.ultra.R.string.category_all_apps, "ALL"),
|
||||
@@ -58,7 +61,8 @@ class SuperUserViewModel : ViewModel() {
|
||||
private const val TAG = "SuperUserViewModel"
|
||||
private val appsLock = Any()
|
||||
var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
||||
var appGroups by mutableStateOf<List<AppGroup>>(emptyList())
|
||||
private val _isAppListLoaded = MutableStateFlow(false)
|
||||
val isAppListLoaded = _isAppListLoaded.asStateFlow()
|
||||
|
||||
@JvmStatic
|
||||
fun getAppIconDrawable(context: Context, packageName: String): Drawable? {
|
||||
@@ -67,6 +71,8 @@ class SuperUserViewModel : ViewModel() {
|
||||
?.packageInfo?.applicationInfo?.loadIcon(context.packageManager)
|
||||
}
|
||||
|
||||
var appGroups by mutableStateOf<List<AppGroup>>(emptyList())
|
||||
|
||||
private const val PREFS_NAME = "settings"
|
||||
private const val KEY_SHOW_SYSTEM_APPS = "show_system_apps"
|
||||
private const val KEY_SELECTED_CATEGORY = "selected_category"
|
||||
@@ -77,31 +83,36 @@ class SuperUserViewModel : ViewModel() {
|
||||
private const val BATCH_SIZE = 20
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
data class AppInfo(
|
||||
val label: String,
|
||||
val packageInfo: PackageInfo,
|
||||
val profile: Natives.Profile?,
|
||||
) : Parcelable {
|
||||
val packageName: String get() = packageInfo.packageName
|
||||
val uid: Int get() = packageInfo.applicationInfo!!.uid
|
||||
@IgnoredOnParcel
|
||||
val packageName: String = packageInfo.packageName
|
||||
@IgnoredOnParcel
|
||||
val uid: Int = packageInfo.applicationInfo!!.uid
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
data class AppGroup(
|
||||
val uid: Int,
|
||||
val apps: List<AppInfo>,
|
||||
val profile: Natives.Profile?
|
||||
) : Parcelable {
|
||||
val mainApp: AppInfo get() = apps.first()
|
||||
val packageNames: List<String> get() = apps.map { it.packageName }
|
||||
val allowSu: Boolean get() = profile?.allowSu == true
|
||||
|
||||
val userName: String? get() = Natives.getUserName(uid)
|
||||
val hasCustomProfile: Boolean
|
||||
get() = profile?.let {
|
||||
if (it.allowSu) !it.rootUseDefault else !it.nonRootUseDefault
|
||||
} ?: false
|
||||
@IgnoredOnParcel
|
||||
val mainApp: AppInfo = apps.first()
|
||||
@IgnoredOnParcel
|
||||
val packageNames: List<String> = apps.map { it.packageName }
|
||||
@IgnoredOnParcel
|
||||
val allowSu: Boolean = profile?.allowSu == true
|
||||
@IgnoredOnParcel
|
||||
val userName: String? = Natives.getUserName(uid)
|
||||
@IgnoredOnParcel
|
||||
val hasCustomProfile : Boolean = profile?.let { if (it.allowSu) !it.rootUseDefault else !it.nonRootUseDefault } ?: false
|
||||
}
|
||||
|
||||
private val appProcessingThreadPool = ThreadPoolExecutor(
|
||||
@@ -331,6 +342,10 @@ class SuperUserViewModel : ViewModel() {
|
||||
|
||||
stopKsuService()
|
||||
|
||||
synchronized(appsLock) {
|
||||
_isAppListLoaded.value = true
|
||||
}
|
||||
|
||||
appListMutex.withLock {
|
||||
val filteredApps = result.filter { it.packageName != ksuApp.packageName }
|
||||
apps = filteredApps
|
||||
@@ -346,7 +361,7 @@ class SuperUserViewModel : ViewModel() {
|
||||
group.apps.any { app ->
|
||||
app.label.contains(search, true) ||
|
||||
app.packageName.contains(search, true) ||
|
||||
HanziToPinyin.getInstance().toPinyinString(app.label).contains(search, true)
|
||||
HanziToPinyin.getInstance().toPinyinString(app.label)?.contains(search, true) == true
|
||||
}
|
||||
}.filter { group ->
|
||||
group.uid == 2000 || showSystemApps ||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.sukisu.ultra.ui.webui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.LruCache;
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel;
|
||||
|
||||
public class AppIconUtil {
|
||||
// Limit cache size to 200 icons
|
||||
private static final int CACHE_SIZE = 200;
|
||||
private static final LruCache<String, Bitmap> iconCache = new LruCache<>(CACHE_SIZE);
|
||||
|
||||
public static synchronized Bitmap loadAppIconSync(Context context, String packageName, int sizePx) {
|
||||
Bitmap cached = iconCache.get(packageName);
|
||||
if (cached != null) return cached;
|
||||
|
||||
try {
|
||||
Drawable drawable = SuperUserViewModel.getAppIconDrawable(context, packageName);
|
||||
if (drawable == null) {
|
||||
return null;
|
||||
}
|
||||
Bitmap raw = drawableToBitmap(drawable, sizePx);
|
||||
Bitmap icon = Bitmap.createScaledBitmap(raw, sizePx, sizePx, true);
|
||||
if (raw != icon) raw.recycle();
|
||||
iconCache.put(packageName, icon);
|
||||
return icon;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap drawableToBitmap(Drawable drawable, int size) {
|
||||
if (drawable instanceof BitmapDrawable) return ((BitmapDrawable) drawable).getBitmap();
|
||||
|
||||
int width = drawable.getIntrinsicWidth() > 0 ? drawable.getIntrinsicWidth() : size;
|
||||
int height = drawable.getIntrinsicHeight() > 0 ? drawable.getIntrinsicHeight() : size;
|
||||
|
||||
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bmp);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
return bmp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.sukisu.ultra.ui.webui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.LruCache
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.core.graphics.scale
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel.Companion.getAppIconDrawable
|
||||
|
||||
object AppIconUtil {
|
||||
// Limit cache size to 200 icons
|
||||
private const val CACHE_SIZE = 200
|
||||
private val iconCache = LruCache<String?, Bitmap?>(CACHE_SIZE)
|
||||
|
||||
@Synchronized
|
||||
fun loadAppIconSync(context: Context, packageName: String, sizePx: Int): Bitmap? {
|
||||
val cached = iconCache.get(packageName)
|
||||
if (cached != null) return cached
|
||||
|
||||
try {
|
||||
val drawable = getAppIconDrawable(context, packageName) ?: return null
|
||||
val raw = drawableToBitmap(drawable, sizePx)
|
||||
val icon = raw.scale(sizePx, sizePx)
|
||||
iconCache.put(packageName, icon)
|
||||
return icon
|
||||
} catch (_: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawableToBitmap(drawable: Drawable, size: Int): Bitmap {
|
||||
if (drawable is BitmapDrawable) return drawable.bitmap
|
||||
|
||||
val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else size
|
||||
val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else size
|
||||
|
||||
val bmp = createBitmap(width, height)
|
||||
val canvas = Canvas(bmp)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
return bmp
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.sukisu.ultra.ui.webui
|
||||
|
||||
/**
|
||||
* Insets data class from GitHub@MMRLApp/WebUI-X-Portable
|
||||
*
|
||||
* Data class representing insets (top, bottom, left, right) for a view.
|
||||
*
|
||||
* This class provides methods to generate CSS code that can be injected into a WebView
|
||||
* to apply these insets as CSS variables. This is useful for adapting web content
|
||||
* to the safe areas of a device screen, considering notches, status bars, and navigation bars.
|
||||
*
|
||||
* @property top The top inset value in pixels.
|
||||
* @property bottom The bottom inset value in pixels.
|
||||
* @property left The left inset value in pixels.
|
||||
* @property right The right inset value in pixels.
|
||||
*/
|
||||
data class Insets(
|
||||
val top: Int,
|
||||
val bottom: Int,
|
||||
val left: Int,
|
||||
val right: Int,
|
||||
) {
|
||||
val css
|
||||
get() = buildString {
|
||||
appendLine(":root {")
|
||||
appendLine("\t--safe-area-inset-top: ${top}px;")
|
||||
appendLine("\t--safe-area-inset-right: ${right}px;")
|
||||
appendLine("\t--safe-area-inset-bottom: ${bottom}px;")
|
||||
appendLine("\t--safe-area-inset-left: ${left}px;")
|
||||
appendLine("\t--window-inset-top: var(--safe-area-inset-top, 0px);")
|
||||
appendLine("\t--window-inset-bottom: var(--safe-area-inset-bottom, 0px);")
|
||||
appendLine("\t--window-inset-left: var(--safe-area-inset-left, 0px);")
|
||||
appendLine("\t--window-inset-right: var(--safe-area-inset-right, 0px);")
|
||||
appendLine("\t--f7-safe-area-top: var(--window-inset-top, 0px) !important;")
|
||||
appendLine("\t--f7-safe-area-bottom: var(--window-inset-bottom, 0px) !important;")
|
||||
appendLine("\t--f7-safe-area-left: var(--window-inset-left, 0px) !important;")
|
||||
appendLine("\t--f7-safe-area-right: var(--window-inset-right, 0px) !important;")
|
||||
append("}")
|
||||
}
|
||||
}
|
||||