Compare commits
234 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56b4664ec7 | ||
|
|
70f7c75a92 | ||
|
|
e414b4de92 | ||
|
|
79e68f473f | ||
|
|
6656604809 | ||
|
|
85b4d11912 | ||
|
|
7769a23f59 | ||
|
|
c442f43090 | ||
|
|
d73670bf43 | ||
|
|
dd1967f0d0 | ||
|
|
e3d2fc64ac | ||
|
|
e07f20bf29 | ||
|
|
34f216181f | ||
|
|
8aef775474 | ||
|
|
f669ad92b6 | ||
|
|
cc0b272770 | ||
|
|
9ea6de340d | ||
|
|
be37f8a2a3 | ||
|
|
8a12fac39f | ||
|
|
0242fe12e3 | ||
|
|
acf2e1a5ec | ||
|
|
626db4be56 | ||
|
|
5941fa1ec7 | ||
|
|
1dd8651a1a | ||
|
|
33dd0ca16b | ||
|
|
dfe7852c25 | ||
|
|
abe6184f63 | ||
|
|
e8afb2143b | ||
|
|
7e1c363bad | ||
|
|
bcf6809deb | ||
|
|
bce5f6cf61 | ||
|
|
5495eebb38 | ||
|
|
d255801666 | ||
|
|
44f2601126 | ||
|
|
3a7366c4bc | ||
|
|
5da289714d | ||
|
|
a18d718744 | ||
|
|
c90fc461d9 | ||
|
|
e80fbe8934 | ||
|
|
e0650ade4f | ||
|
|
378b8458f2 | ||
|
|
182028d9ea | ||
|
|
0c3b8e7610 | ||
|
|
332fdcd2a7 | ||
|
|
aa20d04d3a | ||
|
|
949106bc09 | ||
|
|
67babc2858 | ||
|
|
d087ec510e | ||
|
|
c2ed3da87c | ||
|
|
f2d159f732 | ||
|
|
468fc2207d | ||
|
|
eced8bae82 | ||
|
|
e61ecb3963 | ||
|
|
304f4f8b2c | ||
|
|
48888087e1 | ||
|
|
75a70e70be | ||
|
|
cf825f912c | ||
|
|
1944a49fd8 | ||
|
|
657f343f5c | ||
|
|
74bb90b3d8 | ||
|
|
8bf9828c80 | ||
|
|
06324def38 | ||
|
|
70259a5ec5 | ||
|
|
a63057c594 | ||
|
|
48d5270611 | ||
|
|
712d0f3342 | ||
|
|
d6084aeca1 | ||
|
|
562b9624d7 | ||
|
|
a68d5e8bbe | ||
|
|
d45aa8197e | ||
|
|
314d3ef97a | ||
|
|
e3750ccd51 | ||
|
|
a712efe9d8 | ||
|
|
2266362e24 | ||
|
|
b7056b5baa | ||
|
|
8bd07bf56c | ||
|
|
569183efe9 | ||
|
|
937cf25e9b | ||
|
|
e2b6617577 | ||
|
|
8323d6394b | ||
|
|
e31b892a20 | ||
|
|
65b1518e26 | ||
|
|
865dbd3799 | ||
|
|
7de9d7967a | ||
|
|
aa2d2454e1 | ||
|
|
b850336872 | ||
|
|
138dec35c7 | ||
|
|
569fffa962 | ||
|
|
2a283e6793 | ||
|
|
73a7ba3ac9 | ||
|
|
c0c4ea9f86 | ||
|
|
a13179cd09 | ||
|
|
de089b7b73 | ||
|
|
1b700fb8e0 | ||
|
|
f0febf13f2 | ||
|
|
a002967a92 | ||
|
|
1167b20d89 | ||
|
|
297ff3ae90 | ||
|
|
a39e2ce15a | ||
|
|
78eda275d6 | ||
|
|
1fce0fd77d | ||
|
|
c942393f21 | ||
|
|
040cc30e73 | ||
|
|
6b75ffc928 | ||
|
|
a7f1b21f91 | ||
|
|
c8f7d9d5bc | ||
|
|
2520d45dc4 | ||
|
|
8bdf8d98c3 | ||
|
|
04025f3d32 | ||
|
|
4c7ed9c8ee | ||
|
|
c36f8b0df3 | ||
|
|
5ccec93940 | ||
|
|
14b15e18f3 | ||
|
|
3e6f3b4d80 | ||
|
|
470b3106cb | ||
|
|
e0074bc3ab | ||
|
|
8d04ecdc52 | ||
|
|
c83b1e88b9 | ||
|
|
0446cc499e | ||
|
|
8a6507e834 | ||
|
|
09893b9472 | ||
|
|
13d2290205 | ||
|
|
46e4c85563 | ||
|
|
d925036bd6 | ||
|
|
fbf2799674 | ||
|
|
12c7558b91 | ||
|
|
d55af76260 | ||
|
|
e4c70e2efb | ||
|
|
a971fee132 | ||
|
|
a45e0f78ef | ||
|
|
bf07dcb9ea | ||
|
|
e5f5b8f831 | ||
|
|
a80513fc50 | ||
|
|
f71de1742a | ||
|
|
57c65fdcda | ||
|
|
0a9cf5f9aa | ||
|
|
08fdf2bdad | ||
|
|
690c6bac38 | ||
|
|
af81308097 | ||
|
|
90b79f5c04 | ||
|
|
0c5dcec7bc | ||
|
|
a30dfbc15d | ||
|
|
52f3335977 | ||
|
|
c8b3e953ad | ||
|
|
e9d0526e1b | ||
|
|
408c3be675 | ||
|
|
f67f16733f | ||
|
|
25a173ad7b | ||
|
|
75ec88f7a7 | ||
|
|
e9c5ffb430 | ||
|
|
cb2cdaed12 | ||
|
|
029c7f1e2a | ||
|
|
5a8d6895fa | ||
|
|
bb11e23006 | ||
|
|
37b00d49c8 | ||
|
|
8055aed507 | ||
|
|
e7cef05c6a | ||
|
|
313746b578 | ||
|
|
a7c557222c | ||
|
|
9c902fb264 | ||
|
|
079f74d960 | ||
|
|
00d7de5276 | ||
|
|
3e928365de | ||
|
|
47ba174fb1 | ||
|
|
9446296daa | ||
|
|
7175a6fa7d | ||
|
|
1b06f7d317 | ||
|
|
c739bf6bfb | ||
|
|
dec9a72b41 | ||
|
|
593cbaa067 | ||
|
|
fb8906e371 | ||
|
|
df943250ac | ||
|
|
5a522a1489 | ||
|
|
0b0d64b9d0 | ||
|
|
18876e8a69 | ||
|
|
b668378e23 | ||
|
|
9de2c09a27 | ||
|
|
6b3d2bef12 | ||
|
|
344ed41bc7 | ||
|
|
2e711c3ac9 | ||
|
|
1bf4486cf1 | ||
|
|
cb116286ed | ||
|
|
7f0ae95dfb | ||
|
|
fe7ec9dcf5 | ||
|
|
d88eccdda3 | ||
|
|
78fe01d9a4 | ||
|
|
60cb41c76b | ||
|
|
af78f3bac4 | ||
|
|
fff86dcc8d | ||
|
|
5ec053ca34 | ||
|
|
e9f1631b06 | ||
|
|
3705993330 | ||
|
|
3db338da3e | ||
|
|
d126d0f5b8 | ||
|
|
549adebb30 | ||
|
|
40bada35c6 | ||
|
|
074903a299 | ||
|
|
328bee94e5 | ||
|
|
0db25f14f1 | ||
|
|
877e4f9416 | ||
|
|
8b3e864ffa | ||
|
|
5f5f677b7b | ||
|
|
3933d83d3e | ||
|
|
4abd35fb44 | ||
|
|
e68afb04eb | ||
|
|
bf2be96b29 | ||
|
|
b0b5048b01 | ||
|
|
aff69af690 | ||
|
|
c1d156cd6b | ||
|
|
e58e00be9d | ||
|
|
440fe972f4 | ||
|
|
e24588b961 | ||
|
|
57c8d69e83 | ||
|
|
79c0bebcf5 | ||
|
|
321c9c20d5 | ||
|
|
f6134b47da | ||
|
|
8c282b28a0 | ||
|
|
656cd11876 | ||
|
|
5e77c08872 | ||
|
|
1090f64117 | ||
|
|
470aaa29dc | ||
|
|
c6664af45b | ||
|
|
d6b0ce2565 | ||
|
|
770c9632ae | ||
|
|
cd60773d73 | ||
|
|
06cdd92129 | ||
|
|
315df33bd6 | ||
|
|
f990bda4e5 | ||
|
|
b755ad3602 | ||
|
|
b060b2827e | ||
|
|
73493b288f | ||
|
|
87640fb824 | ||
|
|
fb0a48f9db | ||
|
|
acc8670aa9 |
28
.github/dependabot.yml
vendored
28
.github/dependabot.yml
vendored
@@ -1,28 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: cargo
|
||||
directory: userspace/ksud
|
||||
schedule:
|
||||
interval: daily
|
||||
allow:
|
||||
- dependency-type: "all"
|
||||
groups:
|
||||
crates:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: gradle
|
||||
directory: manager
|
||||
schedule:
|
||||
interval: daily
|
||||
groups:
|
||||
maven:
|
||||
patterns:
|
||||
- "*"
|
||||
28
.github/workflows/build-lkm.yml
vendored
28
.github/workflows/build-lkm.yml
vendored
@@ -3,9 +3,9 @@ on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
upload:
|
||||
required: false
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
default: true
|
||||
description: "Whether to upload to branch"
|
||||
secrets:
|
||||
# username:github_pat
|
||||
@@ -14,9 +14,9 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
upload:
|
||||
required: false
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
default: true
|
||||
description: "Whether to upload to branch"
|
||||
jobs:
|
||||
build-lkm:
|
||||
@@ -27,20 +27,20 @@ jobs:
|
||||
sub_level: 233
|
||||
os_patch_level: 2025-02
|
||||
- version: "android13-5.10"
|
||||
sub_level: 228
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 234
|
||||
os_patch_level: 2025-03
|
||||
- version: "android13-5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 178
|
||||
os_patch_level: 2025-03
|
||||
- version: "android14-5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 178
|
||||
os_patch_level: 2025-03
|
||||
- version: "android14-6.1"
|
||||
sub_level: 128
|
||||
os_patch_level: 2025-03
|
||||
sub_level: 129
|
||||
os_patch_level: 2025-04
|
||||
- version: "android15-6.6"
|
||||
sub_level: 77
|
||||
os_patch_level: 2025-03
|
||||
sub_level: 82
|
||||
os_patch_level: 2025-04
|
||||
# uses: ./.github/workflows/gki-kernel-mock.yml when debugging
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
|
||||
22
.github/workflows/build-manager.yml
vendored
22
.github/workflows/build-manager.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
- 'manager/**'
|
||||
- 'kernel/**'
|
||||
- 'userspace/ksud/**'
|
||||
- 'userspace/zakomksd/**'
|
||||
- 'userspace/susfs/**'
|
||||
- 'userspace/kpmmgr/**'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
@@ -29,7 +29,7 @@ on:
|
||||
upload_lkm:
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
default: true
|
||||
description: "Whether to upload lkm"
|
||||
jobs:
|
||||
check-build-lkm:
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
cd ..
|
||||
rm -rf tmp
|
||||
fi
|
||||
if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/susfs' ]; then
|
||||
if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/main' ]; then
|
||||
need_upload=true
|
||||
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
need_upload="${{ inputs.upload_lkm }}"
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
with:
|
||||
upload: ${{ needs.check-build-lkm.outputs.upload_lkm == 'true' }}
|
||||
secrets: inherit
|
||||
build-zakomksd:
|
||||
build-susfs:
|
||||
if: ${{ always() }}
|
||||
needs: [ check-build-lkm, build-lkm ]
|
||||
strategy:
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/zakomksd.yml
|
||||
uses: ./.github/workflows/susfs.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
@@ -174,10 +174,10 @@ jobs:
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Download arm64 zakomksd
|
||||
- name: Download arm64 susfs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: zakomksd-aarch64-linux-android
|
||||
name: susfs-aarch64-linux-android
|
||||
path: .
|
||||
|
||||
- name: Download arm64 kpmmgr
|
||||
@@ -202,18 +202,18 @@ jobs:
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
cp -f ../aarch64-linux-android/release/zakomk ../manager/app/src/main/jniLibs/arm64-v8a/libzakomk.so
|
||||
cp -f ../x86_64-linux-android/release/zakomk ../manager/app/src/main/jniLibs/x86_64/libzakomk.so
|
||||
cp -f ../aarch64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozako.so
|
||||
cp -f ../x86_64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/x86_64/libzakozako.so
|
||||
|
||||
- name: Copy kpmmgr to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
cp -f ../arm64-v8a/kpmmgr ../manager/app/src/main/jniLibs/arm64-v8a/libkpmmgr.so
|
||||
|
||||
- name: Copy zakomksd to app jniLibs
|
||||
- name: Copy susfs to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
cp -f ../arm64-v8a/zakomksd ../manager/app/src/main/jniLibs/arm64-v8a/libzakomksd.so
|
||||
cp -f ../arm64-v8a/zakozakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozakozako.so
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
|
||||
2
.github/workflows/ksud.yml
vendored
2
.github/workflows/ksud.yml
vendored
@@ -71,4 +71,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ksud-${{ inputs.target }}
|
||||
path: userspace/ksud/target/**/release/zakomk*
|
||||
path: userspace/ksud/target/**/release/zakozako*
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
name: Build zakomksd
|
||||
name: Build susfs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "mian" ]
|
||||
paths:
|
||||
- '.github/workflows/zakomksd.yml'
|
||||
- 'userspace/zakomksd/**'
|
||||
- '.github/workflows/susfs.yml'
|
||||
- 'userspace/susfs/**'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -19,7 +19,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build-susfs:
|
||||
name: Build userspace zakomksd
|
||||
name: Build userspace susfs
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build zakomksd
|
||||
working-directory: ./userspace/zakomksd
|
||||
- name: Build susfs
|
||||
working-directory: ./userspace/susfs
|
||||
run: |
|
||||
$ANDROID_NDK_HOME/ndk-build
|
||||
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: zakomksd-aarch64-linux-android
|
||||
path: ./userspace/zakomksd/libs
|
||||
name: susfs-aarch64-linux-android
|
||||
path: ./userspace/susfs/libs
|
||||
@@ -1,49 +1,63 @@
|
||||
# SukiSU
|
||||
# SukiSU Ultra
|
||||
|
||||
**Enlish** | [简体中文](README.md)
|
||||
**English** | [简体中文](README.md) | [日本語](README-ja.md)
|
||||
|
||||
|
||||
Android device root solution based on [KernelSU](https://github.com/KernelSU/KernelSU)
|
||||
Android device root solution based on [KernelSU](https://github.com/tiann/KernelSU)
|
||||
|
||||
**Experimental! Use at your own risk! **This solution is based on [KernelSU]() and is experimental!
|
||||
**Experimental! Use at your own risk!** This solution is based on [KernelSU](https://github.com/tiann/KernelSU) and is experimental!
|
||||
|
||||
>
|
||||
> This is an unofficial fork, all rights reserved [@tiann](https://github.com/tiann)
|
||||
> This is an unofficial fork. All rights are reserved to [@tiann](https://github.com/tiann)
|
||||
> However, we will be a separately maintained branch of KSU in the future
|
||||
>
|
||||
|
||||
- Fully adapted for non-GKI devices (susfs-dev and unsusfs-patched dev branches only)
|
||||
|
||||
## How to add
|
||||
|
||||
Using the susfs-dev branch (integrated susfs with support for non-GKI devices)
|
||||
Use the susfs-stable or susfs-dev branch (integrated susfs with support for non-GKI devices)
|
||||
|
||||
```
|
||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
|
||||
```
|
||||
|
||||
Use main branching (no longer with support for non-GKI devices)
|
||||
Use the main branch
|
||||
```
|
||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
|
||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
|
||||
```
|
||||
|
||||
## How to use integrated susfs
|
||||
|
||||
Use the susfs-dev branch directly without any patching
|
||||
1. Use the susfs-dev branch directly without any patching
|
||||
|
||||
|
||||
|
||||
## KPM support
|
||||
|
||||
- We have removed duplicate KSU functions based on KernelPatch and retained KPM support.
|
||||
- We will introduce more APatch-compatible functions to ensure the integrity of KPM functionality.
|
||||
|
||||
|
||||
Open source address: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
|
||||
|
||||
|
||||
KPM template address: https://github.com/udochina/KPM-Build-Anywhere
|
||||
|
||||
## More links
|
||||
Projects compiled based on Sukisu and susfs
|
||||
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
|
||||
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
|
||||
|
||||
## Hook method
|
||||
- This method references the hook manual to (https://github.com/rsuntk/KernelSU)
|
||||
- This method references the hook method from (https://github.com/rsuntk/KernelSU)
|
||||
|
||||
1. **KPROBES hook:**
|
||||
- This fork only supports GKI (5.10 - 6.x) kernels, all non-GKI kernels must use manual hooks.
|
||||
- This method only supports GKI (5.10 - 6.x) kernels, and all non-GKI kernels must use manual hooks.
|
||||
- For Loadable Kernel Modules (LKM)
|
||||
- Default hooking method for GKI kernels
|
||||
- Requires `CONFIG_KPROBES=y`. 2.
|
||||
2. **Hooks manual:**
|
||||
- Requires `CONFIG_KPROBES=y`.
|
||||
2. **Manual hooks:**
|
||||
- For GKI (5.10 - 6.x) kernels, add `CONFIG_KSU_MANUAL_HOOK=y` to the kernel defconfig and make sure to protect KernelSU hooks by using `#ifdef CONFIG_KSU_MANUAL_HOOK` instead of `#ifdef CONFIG_KSU`.
|
||||
- Standard KernelSU hooks: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
|
||||
- backslashxx syscall hooks: https://github.com/backslashxx/KernelSU/issues/5
|
||||
@@ -51,51 +65,56 @@ Projects compiled based on Sukisu and susfs
|
||||
|
||||
|
||||
## Usage
|
||||
[GKI]
|
||||
1. such as millet redmi samsung and other devices (does not include the magic kernel manufacturers such as: meizu, a plus real me oppo)
|
||||
2. find more links in the GKI build project to find the device kernel version directly download with TWRP or kernel flashing tool to brush into the zip with AnyKernel3 suffix can be
|
||||
3. General without the suffix of the .zip compressed package is universal, gz suffix for the special TianGui models, lz4 suffix for Google models, general brush without the suffix can be!
|
||||
### GKI
|
||||
1. such as Xiaomi, Redmi, Samsung, and other devices (does not include manufacturers that modified the kernel like Meizu, OnePlus, RealMe, and OPPO)
|
||||
2. Use the prebuilt GKI kernel, the ones with their name ending with AnyKernel3, mentioned in the 'More Links' section, and then flash it with recoveries like TWRP
|
||||
3. Generally, packages with a plain .zip suffix are universal. However, if your device has a MediaTek processor, you should use the ones with .gz suffix, and packages with .lz4 suffix are dedicated to Google devices.
|
||||
|
||||
[OnePlus]
|
||||
1. Find the Yiga project in the More link and fill in your own, then build it with cloud compilation, and finally brush in the zip with AnyKernel3 suffix.
|
||||
Note: You only need to fill in the first two kernel versions, such as 5.10, 5.15, 6.1, 6.6.
|
||||
- Please search for the processor codename by yourself, usually it is all English without numbers.
|
||||
- Branching and configuration files, please fill in the kernel open source address.
|
||||
### OnePlus
|
||||
1. Use the link mentioned in the 'More Links' section to create a customized build with your device information, and then flash the zip file with the AnyKernel3 suffix.
|
||||
> [!Note]
|
||||
> - You only need to fill in the first two parts of kernel versions, such as 5.10, 5.15, 6.1, or 6.6.
|
||||
> - Please search for the processor codename by yourself, usually it is all English without numbers.
|
||||
> - You can find the branch and configuration files from the OnePlus open-source kernel repository.
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
1. Kernel-based `su` and root access management.
|
||||
2. Not based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) module system. 3.
|
||||
3. [Application Profiles](https://kernelsu.org/guide/app-profile.html): Lock root privileges in a cage. 4.
|
||||
2. Not based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) module system, but based on [Magic Mount](https://github.com/5ec1cff/KernelSU) from 5ec1cff
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock root privileges in a cage.
|
||||
4. Bringing back non-GKI/GKI 1.0 support
|
||||
5. More customization
|
||||
6. Support for KPM kernel modules
|
||||
|
||||
|
||||
|
||||
## License
|
||||
|
||||
- The file in the “kernel” directory is [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- All other parts except the “kernel” directory are [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
- The file in the “kernel” directory is under [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) license.
|
||||
- All other parts except the “kernel” directory are under [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html) license.
|
||||
|
||||
## Sponsorship list
|
||||
- [Ktouls](https://github.com/Ktouls) Thanks so much for bringing me support
|
||||
- [zaoqi123](https://github.com/zaoqi123) It's not a bad idea to buy me a milk tea
|
||||
- [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project
|
||||
- [yspbwx2010](https://github.com/yspbwx2010) Many thanks
|
||||
- [DARKWWEE](https://github.com/DARKWWEE) Thanks for the 100 USDT Lao
|
||||
|
||||
|
||||
|
||||
|
||||
How the above list does not have your name, I will keep you updated, thanks again for your support!
|
||||
If the above list does not have your name, I will update it as soon as possible, and thanks again for your support!
|
||||
|
||||
## Contributions
|
||||
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): original project
|
||||
- [MKSU](https://github.com/5ec1cff/KernelSU): Used project
|
||||
- [RKSU](https://github.com/rsuntk/KernelsU):Re-support of non-GKI devices using the kernel of this project
|
||||
- [RKSU](https://github.com/rsuntk/KernelsU): Reintroduced the support of non-GKI devices using the kernel of this project
|
||||
- [susfs](https://gitlab.com/simonpunk/susfs4ksu):Used susfs file system
|
||||
- [KernelSU](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU conceptualization
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): Powerful root utility
|
||||
- [genuine](https://github.com/brevent/genuine/): APK v2 Signature Verification
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit skills.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit utilities.
|
||||
- [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch is a key part of the APatch implementation of the kernel module
|
||||
|
||||
113
docs/README-ja.md
Normal file
113
docs/README-ja.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# SukiSU Ultra
|
||||
|
||||
**日本語** | [简体中文](README.md) | [English](README-en.md)
|
||||
|
||||
[KernelSU](https://github.com/tiann/KernelSU) をベースとした Android デバイスの root ソリューション
|
||||
|
||||
**試験中なビルドです!自己責任で使用してください!**<br>
|
||||
このソリューションは [KernelSU](https://github.com/tiann/KernelSU) に基づいていますが、試験中なビルドです。
|
||||
|
||||
>
|
||||
> これは非公式なフォークです。すべての権利は [@tiann](https://github.com/tiann) に帰属します。
|
||||
>
|
||||
>ただし、将来的には KSU とは別に管理されるブランチとなる予定です。
|
||||
|
||||
- GKI 非対応なデバイスに完全に適応 (susfs-dev と unsusfs-patched dev ブランチのみ)
|
||||
|
||||
## 追加方法
|
||||
susfs-stable または susfs-dev ブランチ (GKI 非対応デバイスに対応する統合された susfs) 使用してください。
|
||||
|
||||
```
|
||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
|
||||
```
|
||||
|
||||
メインブランチを使用する場合
|
||||
```
|
||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
|
||||
```
|
||||
## 統合された susfs の使い方
|
||||
1. パッチを当てずに susfs-dev ブランチを直接使用してください。
|
||||
|
||||
## KPM に対応
|
||||
- KernelPatch に基づいて重複した KSU の機能を削除、KPM の対応を維持させています。
|
||||
- KPM 機能の整合性を確保するために、APatch の互換機能を更に向上させる予定です。
|
||||
|
||||
|
||||
オープンソースアドレス: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
|
||||
|
||||
|
||||
KPM テンプレートのアドレス: https://github.com/udochina/KPM-Build-Anywhere
|
||||
|
||||
## その他のリンク
|
||||
SukiSU と susfs をベースにコンパイルされたプロジェクトです。
|
||||
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
|
||||
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
|
||||
|
||||
## フックの方式
|
||||
- この方式は (https://github.com/rsuntk/KernelSU) のフック方式を参照してください。
|
||||
|
||||
1. **KPROBES フック:**
|
||||
- この方式は GKI (5.10 - 6.x) のカーネルのみに対応しています。GKI 以外のカーネルは手動でフックを使用する必要があります。
|
||||
- 読み込み可能なカーネルモジュールの場合 (LKM)
|
||||
- GKI カーネルのデフォルトとなるフック方式
|
||||
- `CONFIG_KPROBES=y` が必要です。
|
||||
2. **手動でフック:**
|
||||
- GKI (5.10 - 6.x) のカーネルの場合、カーネルの defconfig に `CONFIG_KSU_MANUAL_HOOK=y` を追加して `#ifdef CONFIG_KSU` ではなく `#ifdef CONFIG_KSU_MANUAL_HOOK` を使用して KernelSU フックを保護するようにしてください。
|
||||
- 標準の KernelSU フック: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
|
||||
- backslashxx syscall フック: https://github.com/backslashxx/KernelSU/issues/5
|
||||
- KPROBES を手動で統合する一部の非 GKI デバイスでは手動の VFS フック `new_hook.patch` パッチは不要です。
|
||||
|
||||
|
||||
## 使い方
|
||||
### GKI
|
||||
1. Xiaomi、Redmi、Samsung などのデバイス (Meizu、OnePlus、Realme、OPPO などのカーネルを変更したメーカー以外)
|
||||
2. `その他のリンク`の項目で言及されているカーネル名が、AnyKernel3 で終わるビルド済みの GKI カーネルを TWRP などのリカバリーでフラッシュします。
|
||||
3. 一般的な .zip の接頭辞を持つパッケージは汎用的になります。ただし、デバイスに MediaTek 製の SoC が搭載されている場合は、.gz の接頭辞を持つパッケージを使用する必要があります。その他に .lz4 の接頭辞を持つパッケージは Google 製デバイス専用です。
|
||||
|
||||
### OnePlus
|
||||
1. `その他のリンク`の項目に記載されているリンクを開き、デバイス情報を使用してカスタマイズされたカーネルをビルドし、AnyKernel3 の接頭辞を持つ .zip ファイルをフラッシュします。
|
||||
> [!Note]
|
||||
> - 5.10、5.15、6.1、6.6 などのカーネルバージョンの最初の 2 文字のみを入力する必要があります。
|
||||
> - SoC のコードネームは自分で検索してください。通常は、数字がなく英語表記のみです。
|
||||
> - ブランチと構成ファイルは、OnePlus オープンソースカーネルリポジトリから見つけることができます。
|
||||
|
||||
|
||||
## 機能
|
||||
|
||||
1. カーネルベースな `su` および root アクセスの管理。
|
||||
2. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) モジュールシステムではなく、 5ec1cff 氏の [Magic Mount](https://github.com/5ec1cff/KernelSU) に基づいています。
|
||||
3. [アプリプロファイル](https://kernelsu.org/guide/app-profile.html): root 権限をケージ内にロックします。
|
||||
4. 非 GKI / GKI 1.0 の対応を復活
|
||||
5. その他のカスタマイズ
|
||||
6. KPM カーネルモジュールに対応
|
||||
|
||||
|
||||
|
||||
## ライセンス
|
||||
|
||||
- “kernel” ディレクトリ内のファイルは [GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.ja.html) のみライセンス下にあります。
|
||||
- “kernel” ディレクトリを除くその他すべての部分は [GPL-3.0 またはそれ以降](https://www.gnu.org/licenses/gpl-3.0.html) のライセンス下にあります。
|
||||
|
||||
## スポンサーシップの一覧
|
||||
- [Ktouls](https://github.com/Ktouls) 応援をしてくれたことに感謝。
|
||||
- [zaoqi123](https://github.com/zaoqi123) ミルクティーを買ってあげるのも良い考えですね。
|
||||
- [wswzgdg](https://github.com/wswzgdg) このプロジェクトを支援していただき、ありがとうございます。
|
||||
- [yspbwx2010](https://github.com/yspbwx2010) どうもありがとう。
|
||||
- [DARKWWEE](https://github.com/DARKWWEE) ラオウ100USDTありがとう!
|
||||
|
||||
|
||||
|
||||
|
||||
上記の一覧にあなたの名前がない場合は、できるだけ早急に更新しますので再度ご支援をお願いします。
|
||||
|
||||
## 貢献者
|
||||
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): オリジナルのプロジェクトです。
|
||||
- [MKSU](https://github.com/5ec1cff/KernelSU): 使用しているプロジェクトです。
|
||||
- [RKSU](https://github.com/rsuntk/KernelsU): このプロジェクトのカーネルを使用して非 GKI デバイスのサポートを追加しています。
|
||||
- [susfs](https://gitlab.com/simonpunk/susfs4ksu):使用している susfs ファイルシステムです。
|
||||
- [KernelSU](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU について。
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): パワフルな root ユーティリティです。
|
||||
- [genuine](https://github.com/brevent/genuine/): APK v2 署名認証で使用しています。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): いくつかの rootkit ユーティリティを使用しています。
|
||||
- [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch はカーネルモジュールの APatch 実装での重要な部分となります。
|
||||
@@ -1,14 +1,15 @@
|
||||
# SukiSU
|
||||
# SukiSU Ultra
|
||||
|
||||
**简体中文** | [English](README-en.md)
|
||||
**简体中文** | [English](README-en.md) | [日本語](README-ja.md)
|
||||
|
||||
基于 [KernelSU](https://github.com/tiann/KernelSU) 的安卓设备 root 解决方案
|
||||
|
||||
**实验性!使用风险自负!**
|
||||
**实验性! 使用风险自负!**
|
||||
|
||||
|
||||
>
|
||||
> 这是非官方分支,保留所有权利 [@tiann](https://github.com/tiann)
|
||||
> 但是,我们将会在未来成为一个单独维护的KSU分支
|
||||
>
|
||||
|
||||
|
||||
@@ -16,33 +17,46 @@
|
||||
在内核源码的根目录下执行以下命令:
|
||||
|
||||
使用 susfs-dev 分支(已集成susfs,带非GKI设备的支持)
|
||||
|
||||
```
|
||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
|
||||
```
|
||||
|
||||
使用 main 分支(不再带非GKI设备的支持)
|
||||
|
||||
使用 main 分支
|
||||
```
|
||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
|
||||
```
|
||||
|
||||
## 如何集成 susfs
|
||||
|
||||
1. 直接使用 susfs-dev 分支,不需要再集成 susfs
|
||||
|
||||
1. 直接使用 susfs-stable 或者 susfs-dev 分支,不需要再集成 susfs
|
||||
|
||||
## 钩子方法
|
||||
- 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU)
|
||||
|
||||
1. **KPROBES 钩子:**
|
||||
- 此方法仅支持 GKI 2.0(5.10 - 6.x)内核,所有非 GKI 2.0 内核都必须使用手动钩子
|
||||
- 此方法仅支持 GKI 2.0 (5.10 - 6.x) 内核, 所有非 GKI 2.0 内核都必须使用手动钩子
|
||||
- 用于可加载内核模块 (LKM)
|
||||
- GKI 2.0 内核的默认钩子方法
|
||||
- 需要 `CONFIG_KPROBES=y`
|
||||
2. **手动钩子:**
|
||||
- 对于 GKI 2.0(5.10 - 6.x)内核,需要在对应设备的 defconfig 文件中添加 `CONFIG_KSU_MANUAL_HOOK=y` 并确保使用 `#ifdef CONFIG_KSU_MANUAL_HOOK` 而不是 `#ifdef CONFIG_KSU` 来保护 KernelSU 钩子
|
||||
- 对于 GKI 2.0 (5.10 - 6.x) 内核,需要在对应设备的 defconfig 文件中添加 `CONFIG_KSU_MANUAL_HOOK=y` 并确保使用 `#ifdef CONFIG_KSU_MANUAL_HOOK` 而不是 `#ifdef CONFIG_KSU` 来保护 KernelSU 钩子
|
||||
- 标准的 KernelSU 钩子:https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
|
||||
- backslashxx 的 syscall 手动钩子:https://github.com/backslashxx/KernelSU/issues/5
|
||||
- 部分手动集成KPROBES的非 GKI 2.0 设备不需要手动 VFS 钩子 `new_hook.patch` 补丁
|
||||
- 部分手动集成 KPROBES 的非 GKI 2.0 设备不需要手动 VFS 钩子 `new_hook.patch` 补丁
|
||||
|
||||
|
||||
## KPM支持
|
||||
|
||||
- 我们基于KernelPatch去掉了和KSU重复的功能,保留了KPM支持
|
||||
- 我们将会引入更多的兼容APatch的函数来确保KPM功能的完整性
|
||||
|
||||
|
||||
开源地址: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
|
||||
|
||||
|
||||
KPM模板地址: https://github.com/udochina/KPM-Build-Anywhere
|
||||
|
||||
|
||||
## 更多链接
|
||||
@@ -54,17 +68,17 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
|
||||
## 使用方法
|
||||
|
||||
### GKI
|
||||
1. 适用于如小米红米三星等的 GKI 2.0 的设备(不包含魔改内核的厂商如魅族、一加、真我和 oppo)
|
||||
1. 适用于如小米红米三星等的 GKI 2.0 的设备 (不包含魔改内核的厂商如魅族、一加、真我和 oppo)
|
||||
2. 找到更多链接里的 GKI 构建的项目找到设备内核版本直接下载用TWRP或者内核刷写工具刷入带 AnyKernel3 后缀的压缩包即可
|
||||
3. 一般不带后缀的 .zip 压缩包是通用,gz 后缀的为天玑机型专用,lz4 后缀的为谷歌系机型专用,一般刷不带后缀的即可
|
||||
|
||||
### 一加
|
||||
1.找到更多链接里的一加项目进行自行填写,然后云编译构建,最后刷入带 AnyKernel3 后缀的压缩包即可
|
||||
|
||||
注意事项:
|
||||
- 内核版本只需要填写前两位即可,如 5.10,5.15,6.1,6.6
|
||||
- 处理器代号请自行搜索,一般为全英文不带数字的代号
|
||||
- 分支和配置文件请自行到一加内核开源地址进行填写
|
||||
> [!Note]
|
||||
> - 内核版本只需要填写前两位即可,如 5.10,5.15,6.1,6.6
|
||||
> - 处理器代号请自行搜索,一般为全英文不带数字的代号
|
||||
> - 分支和配置文件请自行到一加内核开源地址进行填写
|
||||
|
||||
|
||||
## 特点
|
||||
@@ -72,8 +86,9 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
|
||||
1. 基于内核的 `su` 和 root 访问管理
|
||||
2. 基于 5ec1cff 的 [Magic Mount](https://github.com/5ec1cff/KernelSU) 的模块系统
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html):将 root 权限锁在笼子里
|
||||
4. 恢复对非 GKI 2.0 内核的支持(仅限susfs-dev和未进行susfs补丁的dev分支)
|
||||
4. 恢复对非 GKI 2.0 内核的支持
|
||||
5. 更多自定义功能
|
||||
6. 对KPM内核模块的支持
|
||||
|
||||
|
||||
## 许可证
|
||||
@@ -85,11 +100,13 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
|
||||
- [Ktouls](https://github.com/Ktouls) 非常感谢你给我带来的支持
|
||||
- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错
|
||||
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持
|
||||
- [yspbwx2010](https://github.com/yspbwx2010) 非常感谢
|
||||
- [DARKWWEE](https://github.com/DARKWWEE) 感谢老哥的 100 USDT
|
||||
|
||||
|
||||
|
||||
|
||||
如何以上名单没有你的名称,我会及时更新,再次感谢大家的支持
|
||||
如果以上名单没有你的名称,我会及时更新,再次感谢大家的支持
|
||||
|
||||
## 贡献
|
||||
|
||||
@@ -101,3 +118,4 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):强大的 root 工具
|
||||
- [genuine](https://github.com/brevent/genuine/):APK v2 签名验证
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技能
|
||||
- [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch是APatch实现内核模块的关键部分
|
||||
|
||||
@@ -24,11 +24,12 @@ config KSU_HOOK
|
||||
override the kernel version check and enable the hook functionality.
|
||||
|
||||
config KPM
|
||||
bool "Enable KernelSU KPM"
|
||||
bool "Enable SukiSU KPM"
|
||||
default n
|
||||
help
|
||||
This option enables the KernelSU KPM feature. If enabled, it will
|
||||
override the kernel version check and enable the hook functionality.
|
||||
Enabling this option will activate the KPM feature of SukiSU.
|
||||
This option is suitable for scenarios where you need to force KPM to be enabled.
|
||||
but it may affect system stability.
|
||||
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -22,7 +22,7 @@ obj-$(CONFIG_KPM) += kpm/
|
||||
# .git is a text file while the module is imported by 'git submodule add'.
|
||||
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
|
||||
$(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow)
|
||||
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD)
|
||||
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count main)
|
||||
# ksu_version: major * 10000 + git version + 606 for historical reasons
|
||||
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 606))
|
||||
$(info -- KernelSU version: $(KSU_VERSION))
|
||||
@@ -42,14 +42,32 @@ endif
|
||||
|
||||
ifdef KSU_MANAGER_PACKAGE
|
||||
ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
|
||||
$(info -- KernelSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
endif
|
||||
|
||||
$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
|
||||
$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
||||
$(info -- Supported Unofficial Manager: ShirkNeko (GKI) (Non-GKI))
|
||||
$(info -- SukiSU Manager signature size: $(KSU_EXPECTED_SIZE))
|
||||
$(info -- SukiSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
||||
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
|
||||
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
|
||||
KERNEL_TYPE := Non-GKI
|
||||
# Check for GKI 2.0 (5.10+ or 6.x+)
|
||||
ifneq ($(shell test \( $(VERSION) -ge 5 -a $(PATCHLEVEL) -ge 10 \) -o $(VERSION) -ge 6; echo $$?),0)
|
||||
# Check for GKI 1.0 (5.4)
|
||||
ifeq ($(shell test $(VERSION)-$(PATCHLEVEL) = 5-4; echo $$?),0)
|
||||
KERNEL_TYPE := GKI 1.0
|
||||
endif
|
||||
else
|
||||
KERNEL_TYPE := GKI 2.0
|
||||
endif
|
||||
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||
$(info -- KERNEL_TYPE: $(KERNEL_TYPE))
|
||||
|
||||
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||
ifeq ($(CONFIG_KPM),y)
|
||||
$(info -- KPM is enabled)
|
||||
else
|
||||
$(info -- KPM is disabled)
|
||||
endif
|
||||
|
||||
|
||||
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
|
||||
|
||||
@@ -419,10 +419,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
|
||||
pr_info("KPM: calling before arg2=%d\n", (int) arg2);
|
||||
|
||||
res = sukisu_handle_kpm(arg2, arg3, arg4);
|
||||
copy_to_user(result, &res, sizeof(res));
|
||||
|
||||
pr_info("KPM: calling before arg2=%d res=%d\n", (int) arg2, (int) res);
|
||||
res = sukisu_handle_kpm(arg2, arg3, arg4, arg5);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
obj-y += kpm.o
|
||||
obj-y += compact.o
|
||||
obj-y += super_access.o
|
||||
|
||||
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
|
||||
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
|
||||
@@ -26,37 +26,56 @@
|
||||
#include <linux/slab.h>
|
||||
#include "kpm.h"
|
||||
#include "compact.h"
|
||||
#include "../allowlist.h"
|
||||
#include "../manager.h"
|
||||
|
||||
unsigned long sukisu_compact_find_symbol(const char* name);
|
||||
|
||||
// ======================================================================
|
||||
// 兼容函数 for KPM
|
||||
|
||||
const char* kpver = "0.10";
|
||||
static
|
||||
int sukisu_is_su_allow_uid(uid_t uid) {
|
||||
return ksu_is_allow_uid(uid) ? 1 : 0;
|
||||
}
|
||||
|
||||
static
|
||||
int sukisu_get_ap_mod_exclude(uid_t uid) {
|
||||
// Not supported
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int sukisu_is_uid_should_umount(uid_t uid) {
|
||||
return ksu_uid_should_umount(uid) ? 1 : 0;
|
||||
}
|
||||
|
||||
static
|
||||
int sukisu_is_current_uid_manager() {
|
||||
return is_manager();
|
||||
}
|
||||
|
||||
static
|
||||
uid_t sukisu_get_manager_uid() {
|
||||
return ksu_manager_uid;
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
|
||||
struct CompactAddressSymbol {
|
||||
const char* symbol_name;
|
||||
void* addr;
|
||||
};
|
||||
|
||||
struct CompactAliasSymbol {
|
||||
const char* symbol_name;
|
||||
const char* compact_symbol_name;
|
||||
};
|
||||
|
||||
struct CompactAddressSymbol address_symbol [] = {
|
||||
static struct CompactAddressSymbol address_symbol [] = {
|
||||
{ "kallsyms_lookup_name", &kallsyms_lookup_name },
|
||||
{ "compact_find_symbol", &sukisu_compact_find_symbol },
|
||||
{ "compat_copy_to_user", ©_to_user },
|
||||
{ "compat_strncpy_from_user", &strncpy_from_user },
|
||||
{ "kpver", &kpver },
|
||||
{ "is_run_in_sukisu_ultra", (void*)1 }
|
||||
};
|
||||
|
||||
struct CompactAliasSymbol alias_symbol[] = {
|
||||
{"kf_strncat", "strncat"},
|
||||
{"kf_strlen", "strlen" },
|
||||
{"kf_strcpy", "strcpy"},
|
||||
{"compat_copy_to_user", "__arch_copy_to_user"}
|
||||
{ "is_run_in_sukisu_ultra", (void*)1 },
|
||||
{ "is_su_allow_uid", &sukisu_is_su_allow_uid },
|
||||
{ "get_ap_mod_exclude", &sukisu_get_ap_mod_exclude },
|
||||
{ "is_uid_should_umount", &sukisu_is_uid_should_umount },
|
||||
{ "is_current_uid_manager", &sukisu_is_current_uid_manager },
|
||||
{ "get_manager_uid", &sukisu_get_manager_uid }
|
||||
};
|
||||
|
||||
unsigned long sukisu_compact_find_symbol(const char* name) {
|
||||
@@ -71,30 +90,13 @@ unsigned long sukisu_compact_find_symbol(const char* name) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 如果符号名以 "kf__" 开头,尝试解析去掉前缀的部分 */
|
||||
if (strncmp(name, "kf__", 4) == 0) {
|
||||
const char *real_name = name + 4; // 去掉 "kf__"
|
||||
addr = (unsigned long)kallsyms_lookup_name(real_name);
|
||||
if (addr) {
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
// 通过内核来查
|
||||
addr = kallsyms_lookup_name(name);
|
||||
if(addr) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
// 查不到就查查兼容的符号
|
||||
for(i = 0; i < (sizeof(alias_symbol) / sizeof(struct CompactAliasSymbol)); i++) {
|
||||
struct CompactAliasSymbol* symbol = &alias_symbol[i];
|
||||
if(strcmp(name, symbol->symbol_name) == 0) {
|
||||
addr = kallsyms_lookup_name(symbol->compact_symbol_name);
|
||||
if(addr)
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(sukisu_compact_find_symbol);
|
||||
|
||||
1340
kernel/kpm/kpm.c
1340
kernel/kpm/kpm.c
File diff suppressed because it is too large
Load Diff
@@ -1,44 +1,44 @@
|
||||
#ifndef ___SUKISU_KPM_H
|
||||
#define ___SUKISU_KPM_H
|
||||
|
||||
int sukisu_handle_kpm(unsigned long arg3, unsigned long arg4, unsigned long arg5);
|
||||
int sukisu_handle_kpm(unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
|
||||
int sukisu_is_kpm_control_code(unsigned long arg2);
|
||||
|
||||
// KPM控制代码
|
||||
#define CMD_KPM_CONTROL 28
|
||||
#define CMD_KPM_CONTROL_MAX 34
|
||||
#define CMD_KPM_CONTROL_MAX 35
|
||||
|
||||
// 控制代码
|
||||
|
||||
// prctl(xxx, xxx, 1, "PATH", "ARGS")
|
||||
// prctl(xxx, 28, "PATH", "ARGS")
|
||||
// success return 0, error return -N
|
||||
#define SUKISU_KPM_LOAD 28
|
||||
|
||||
// prctl(xxx, xxx, 2, "NAME")
|
||||
// prctl(xxx, 29, "NAME")
|
||||
// success return 0, error return -N
|
||||
#define SUKISU_KPM_UNLOAD 29
|
||||
|
||||
// num = prctl(xxx, xxx, 3)
|
||||
// num = prctl(xxx, 30)
|
||||
// error return -N
|
||||
// success return +num or 0
|
||||
#define SUKISU_KPM_NUM 30
|
||||
|
||||
// prctl(xxx, xxx, 4, Buffer, BufferSize)
|
||||
// prctl(xxx, 31, Buffer, BufferSize)
|
||||
// success return +out, error return -N
|
||||
#define SUKISU_KPM_LIST 31
|
||||
|
||||
// prctl(xxx, xxx, 5, "NAME", Buffer[256])
|
||||
// prctl(xxx, 32, "NAME", Buffer[256])
|
||||
// success return +out, error return -N
|
||||
#define SUKISU_KPM_INFO 32
|
||||
|
||||
// prctl(xxx, xxx, 6, "NAME", "ARGS")
|
||||
// prctl(xxx, 33, "NAME", "ARGS")
|
||||
// success return KPM's result value
|
||||
// error return -N
|
||||
#define SUKISU_KPM_CONTROL 33
|
||||
|
||||
// prctl(xxx, xxx, 7)
|
||||
// success will printf to stdout and return 0
|
||||
// error will return -1
|
||||
#define SUKISU_KPM_PRINT 34
|
||||
// prctl(xxx, 34, buffer, bufferSize)
|
||||
// success return KPM's result value
|
||||
// error return -N
|
||||
#define SUKISU_KPM_VERSION 34
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,285 @@
|
||||
#include <linux/export.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/kernfs.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/elf.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <asm/elf.h> /* 包含 ARM64 重定位类型定义 */
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/string.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/set_memory.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.h>
|
||||
#include "kpm.h"
|
||||
#include "compact.h"
|
||||
#include <linux/types.h>
|
||||
#include <linux/stddef.h>
|
||||
|
||||
// 结构体成员元数据
|
||||
struct DynamicStructMember {
|
||||
const char* name;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
// 结构体元数据(包含总大小)
|
||||
struct DynamicStructInfo {
|
||||
const char* name;
|
||||
size_t count;
|
||||
size_t total_size;
|
||||
struct DynamicStructMember* members;
|
||||
};
|
||||
|
||||
// 定义结构体元数据的宏(直接使用 struct 名称)
|
||||
#define DYNAMIC_STRUCT_BEGIN(struct_name) \
|
||||
static struct DynamicStructMember struct_name##_members[] = {
|
||||
|
||||
#define DEFINE_MEMBER(struct_name, member) \
|
||||
{ \
|
||||
.name = #member, \
|
||||
.size = sizeof(((struct struct_name*)0)->member), \
|
||||
.offset = offsetof(struct struct_name, member) \
|
||||
},
|
||||
|
||||
#define DYNAMIC_STRUCT_END(struct_name) \
|
||||
}; \
|
||||
static struct DynamicStructInfo struct_name##_info = { \
|
||||
.name = #struct_name, \
|
||||
.count = sizeof(struct_name##_members) / sizeof(struct DynamicStructMember), \
|
||||
.total_size = sizeof(struct struct_name), \
|
||||
.members = struct_name##_members \
|
||||
};
|
||||
|
||||
// ==================================================================================
|
||||
|
||||
#include <linux/version.h>
|
||||
|
||||
#define KERNEL_VERSION_6_1 KERNEL_VERSION(6, 1, 0)
|
||||
#define KERNEL_VERSION_5_15 KERNEL_VERSION(5, 15, 0)
|
||||
|
||||
#include <../fs/mount.h>
|
||||
#include <linux/mount.h>
|
||||
|
||||
// 定义元数据
|
||||
DYNAMIC_STRUCT_BEGIN(mount)
|
||||
DEFINE_MEMBER(mount, mnt_parent)
|
||||
DEFINE_MEMBER(mount, mnt)
|
||||
DEFINE_MEMBER(mount, mnt_id)
|
||||
DEFINE_MEMBER(mount, mnt_group_id)
|
||||
DEFINE_MEMBER(mount, mnt_expiry_mark)
|
||||
DEFINE_MEMBER(mount, mnt_master)
|
||||
DEFINE_MEMBER(mount, mnt_devname)
|
||||
DYNAMIC_STRUCT_END(mount)
|
||||
|
||||
DYNAMIC_STRUCT_BEGIN(vfsmount)
|
||||
DEFINE_MEMBER(vfsmount, mnt_root)
|
||||
DEFINE_MEMBER(vfsmount, mnt_sb)
|
||||
DEFINE_MEMBER(vfsmount, mnt_flags)
|
||||
DYNAMIC_STRUCT_END(vfsmount)
|
||||
|
||||
DYNAMIC_STRUCT_BEGIN(mnt_namespace)
|
||||
DEFINE_MEMBER(mnt_namespace, ns)
|
||||
DEFINE_MEMBER(mnt_namespace, root)
|
||||
DEFINE_MEMBER(mnt_namespace, seq)
|
||||
DEFINE_MEMBER(mnt_namespace, mounts)
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION_5_15
|
||||
DEFINE_MEMBER(mnt_namespace, count)
|
||||
#endif
|
||||
DYNAMIC_STRUCT_END(mnt_namespace)
|
||||
|
||||
#include <linux/kprobes.h>
|
||||
|
||||
#ifdef CONFIG_KPROBES
|
||||
DYNAMIC_STRUCT_BEGIN(kprobe)
|
||||
DEFINE_MEMBER(kprobe, addr)
|
||||
DEFINE_MEMBER(kprobe, symbol_name)
|
||||
DEFINE_MEMBER(kprobe, offset)
|
||||
DEFINE_MEMBER(kprobe, pre_handler)
|
||||
DEFINE_MEMBER(kprobe, post_handler)
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION_5_15
|
||||
DEFINE_MEMBER(kprobe, fault_handler)
|
||||
#endif
|
||||
DEFINE_MEMBER(kprobe, flags)
|
||||
DYNAMIC_STRUCT_END(kprobe)
|
||||
#endif
|
||||
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mm_types.h>
|
||||
|
||||
DYNAMIC_STRUCT_BEGIN(vm_area_struct)
|
||||
DEFINE_MEMBER(vm_area_struct,vm_start)
|
||||
DEFINE_MEMBER(vm_area_struct,vm_end)
|
||||
DEFINE_MEMBER(vm_area_struct,vm_flags)
|
||||
DEFINE_MEMBER(vm_area_struct,anon_vma)
|
||||
DEFINE_MEMBER(vm_area_struct,vm_pgoff)
|
||||
DEFINE_MEMBER(vm_area_struct,vm_file)
|
||||
DEFINE_MEMBER(vm_area_struct,vm_private_data)
|
||||
#ifdef CONFIG_ANON_VMA_NAME
|
||||
DEFINE_MEMBER(vm_area_struct, anon_name)
|
||||
#endif
|
||||
DEFINE_MEMBER(vm_area_struct, vm_ops)
|
||||
DYNAMIC_STRUCT_END(vm_area_struct)
|
||||
|
||||
DYNAMIC_STRUCT_BEGIN(vm_operations_struct)
|
||||
DEFINE_MEMBER(vm_operations_struct, open)
|
||||
DEFINE_MEMBER(vm_operations_struct, close)
|
||||
DEFINE_MEMBER(vm_operations_struct, name)
|
||||
DEFINE_MEMBER(vm_operations_struct, access)
|
||||
DYNAMIC_STRUCT_END(vm_operations_struct)
|
||||
|
||||
#include <linux/netlink.h>
|
||||
|
||||
DYNAMIC_STRUCT_BEGIN(netlink_kernel_cfg)
|
||||
DEFINE_MEMBER(netlink_kernel_cfg, groups)
|
||||
DEFINE_MEMBER(netlink_kernel_cfg, flags)
|
||||
DEFINE_MEMBER(netlink_kernel_cfg, input)
|
||||
DEFINE_MEMBER(netlink_kernel_cfg, cb_mutex)
|
||||
DEFINE_MEMBER(netlink_kernel_cfg, bind)
|
||||
DEFINE_MEMBER(netlink_kernel_cfg, unbind)
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION_6_1
|
||||
DEFINE_MEMBER(netlink_kernel_cfg, compare)
|
||||
#endif
|
||||
DYNAMIC_STRUCT_END(netlink_kernel_cfg)
|
||||
|
||||
|
||||
#include <linux/sched.h>
|
||||
DYNAMIC_STRUCT_BEGIN(task_struct)
|
||||
DEFINE_MEMBER(task_struct, pid)
|
||||
DEFINE_MEMBER(task_struct, tgid)
|
||||
DEFINE_MEMBER(task_struct, cred)
|
||||
DEFINE_MEMBER(task_struct, real_cred)
|
||||
DEFINE_MEMBER(task_struct, comm)
|
||||
DEFINE_MEMBER(task_struct, parent)
|
||||
DEFINE_MEMBER(task_struct, group_leader)
|
||||
DEFINE_MEMBER(task_struct, mm)
|
||||
DEFINE_MEMBER(task_struct, active_mm)
|
||||
DEFINE_MEMBER(task_struct, thread_pid)
|
||||
DEFINE_MEMBER(task_struct, files)
|
||||
DEFINE_MEMBER(task_struct, seccomp)
|
||||
#ifdef CONFIG_THREAD_INFO_IN_TASK
|
||||
DEFINE_MEMBER(task_struct, thread_info)
|
||||
#endif
|
||||
#ifdef CONFIG_CGROUPS
|
||||
DEFINE_MEMBER(task_struct, cgroups)
|
||||
#endif
|
||||
#ifdef CONFIG_SECURITY
|
||||
DEFINE_MEMBER(task_struct, security)
|
||||
#endif
|
||||
DEFINE_MEMBER(task_struct, thread)
|
||||
DYNAMIC_STRUCT_END(task_struct)
|
||||
|
||||
// =====================================================================================================================
|
||||
|
||||
#define STRUCT_INFO(name) &(name##_info)
|
||||
|
||||
static
|
||||
struct DynamicStructInfo* dynamic_struct_infos[] = {
|
||||
STRUCT_INFO(mount),
|
||||
STRUCT_INFO(vfsmount),
|
||||
STRUCT_INFO(mnt_namespace),
|
||||
#ifdef CONFIG_KPROBES
|
||||
STRUCT_INFO(kprobe),
|
||||
#endif
|
||||
STRUCT_INFO(vm_area_struct),
|
||||
STRUCT_INFO(vm_operations_struct),
|
||||
STRUCT_INFO(netlink_kernel_cfg),
|
||||
STRUCT_INFO(task_struct)
|
||||
};
|
||||
|
||||
// return 0 if successful
|
||||
// return -1 if struct not defined
|
||||
int sukisu_super_find_struct(
|
||||
const char* struct_name,
|
||||
size_t* out_size,
|
||||
int* out_members
|
||||
) {
|
||||
for(size_t i = 0; i < (sizeof(dynamic_struct_infos) / sizeof(dynamic_struct_infos[0])); i++) {
|
||||
struct DynamicStructInfo* info = dynamic_struct_infos[i];
|
||||
if(strcmp(struct_name, info->name) == 0) {
|
||||
if(out_size)
|
||||
*out_size = info->total_size;
|
||||
if(out_members)
|
||||
*out_members = info->count;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_super_find_struct);
|
||||
|
||||
// Dynamic access struct
|
||||
// return 0 if successful
|
||||
// return -1 if struct not defined
|
||||
// return -2 if member not defined
|
||||
int sukisu_super_access (
|
||||
const char* struct_name,
|
||||
const char* member_name,
|
||||
size_t* out_offset,
|
||||
size_t* out_size
|
||||
) {
|
||||
for(size_t i = 0; i < (sizeof(dynamic_struct_infos) / sizeof(dynamic_struct_infos[0])); i++) {
|
||||
struct DynamicStructInfo* info = dynamic_struct_infos[i];
|
||||
if(strcmp(struct_name, info->name) == 0) {
|
||||
for (size_t i1 = 0; i1 < info->count; i1++) {
|
||||
if (strcmp(info->members[i1].name, member_name) == 0) {
|
||||
if(out_offset)
|
||||
*out_offset = info->members[i].offset;
|
||||
if(out_size)
|
||||
*out_size = info->members[i].size;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_super_access);
|
||||
|
||||
// 动态 container_of 宏
|
||||
#define DYNAMIC_CONTAINER_OF(offset, member_ptr) ({ \
|
||||
(offset != (size_t)-1) ? (void*)((char*)(member_ptr) - offset) : NULL; \
|
||||
})
|
||||
|
||||
// Dynamic container_of
|
||||
// return 0 if success
|
||||
// return -1 if current struct not defined
|
||||
// return -2 if target member not defined
|
||||
int sukisu_super_container_of(
|
||||
const char* struct_name,
|
||||
const char* member_name,
|
||||
void* ptr,
|
||||
void** out_ptr
|
||||
) {
|
||||
if(ptr == NULL) {
|
||||
return -3;
|
||||
}
|
||||
for(size_t i = 0; i < (sizeof(dynamic_struct_infos) / sizeof(dynamic_struct_infos[0])); i++) {
|
||||
struct DynamicStructInfo* info = dynamic_struct_infos[i];
|
||||
if(strcmp(struct_name, info->name) == 0) {
|
||||
for (size_t i1 = 0; i1 < info->count; i1++) {
|
||||
if (strcmp(info->members[i1].name, member_name) == 0) {
|
||||
*out_ptr = (void*) DYNAMIC_CONTAINER_OF(info->members[i1].offset, ptr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_super_container_of);
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef __SUKISU_SUPER_ACCESS_H
|
||||
#define __SUKISU_SUPER_ACCESS_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/stddef.h>
|
||||
#include "kpm.h"
|
||||
#include "compact.h"
|
||||
|
||||
// return 0 if successful
|
||||
// return -1 if struct not defined
|
||||
int sukisu_super_find_struct(
|
||||
const char* struct_name,
|
||||
size_t* out_size,
|
||||
int* out_members
|
||||
);
|
||||
|
||||
// Dynamic access struct
|
||||
// return 0 if successful
|
||||
// return -1 if struct not defined
|
||||
// return -2 if member not defined
|
||||
int sukisu_super_access (
|
||||
const char* struct_name,
|
||||
const char* member_name,
|
||||
size_t* out_offset,
|
||||
size_t* out_size
|
||||
);
|
||||
|
||||
// Dynamic container_of
|
||||
// return 0 if success
|
||||
// return -1 if current struct not defined
|
||||
// return -2 if target member not defined
|
||||
int sukisu_super_container_of(
|
||||
const char* struct_name,
|
||||
const char* member_name,
|
||||
void* ptr,
|
||||
void** out_ptr
|
||||
);
|
||||
|
||||
#endif
|
||||
@@ -25,7 +25,7 @@ apksign {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "shirkneko.zako.sukisu"
|
||||
namespace = "com.sukisu.ultra"
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// IKsuInterface.aidl
|
||||
package shirkneko.zako.sukisu;
|
||||
package com.sukisu.zako;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import rikka.parcelablelist.ParcelableListSlice;
|
||||
Binary file not shown.
@@ -12,7 +12,7 @@
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
|
||||
Java_com_sukisu_ultra_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
|
||||
auto cpkg = env->GetStringUTFChars(pkg, nullptr);
|
||||
auto result = become_manager(cpkg);
|
||||
env->ReleaseStringUTFChars(pkg, cpkg);
|
||||
@@ -21,13 +21,13 @@ Java_shirkneko_zako_sukisu_Natives_becomeManager(JNIEnv *env, jobject, jstring p
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_getVersion(JNIEnv *env, jobject) {
|
||||
Java_com_sukisu_ultra_Natives_getVersion(JNIEnv *env, jobject) {
|
||||
return get_version();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||
Java_com_sukisu_ultra_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||
int uids[1024];
|
||||
int size = 0;
|
||||
bool result = get_allow_list(uids, &size);
|
||||
@@ -42,13 +42,13 @@ Java_shirkneko_zako_sukisu_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
|
||||
Java_com_sukisu_ultra_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
|
||||
return is_safe_mode();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
|
||||
Java_com_sukisu_ultra_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
|
||||
return is_lkm_mode();
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
|
||||
Java_com_sukisu_ultra_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
|
||||
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ Java_shirkneko_zako_sukisu_Natives_getAppProfile(JNIEnv *env, jobject, jstring p
|
||||
|
||||
bool useDefaultProfile = !get_app_profile(key, &profile);
|
||||
|
||||
auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile");
|
||||
auto cls = env->FindClass("com/sukisu/ultra/Natives$Profile");
|
||||
auto constructor = env->GetMethodID(cls, "<init>", "()V");
|
||||
auto obj = env->NewObject(cls, constructor);
|
||||
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
||||
@@ -207,8 +207,8 @@ Java_shirkneko_zako_sukisu_Natives_getAppProfile(JNIEnv *env, jobject, jstring p
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
|
||||
auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile");
|
||||
Java_com_sukisu_ultra_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
|
||||
auto cls = env->FindClass("com/sukisu/ultra/Natives$Profile");
|
||||
|
||||
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
||||
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
|
||||
@@ -293,16 +293,16 @@ Java_shirkneko_zako_sukisu_Natives_setAppProfile(JNIEnv *env, jobject clazz, job
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
|
||||
Java_com_sukisu_ultra_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
|
||||
return uid_should_umount(uid);
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
|
||||
Java_com_sukisu_ultra_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
|
||||
return is_su_enabled();
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_shirkneko_zako_sukisu_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
|
||||
Java_com_sukisu_ultra_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
|
||||
return set_su_enabled(enabled);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu
|
||||
package com.sukisu.ultra
|
||||
|
||||
import android.app.Application
|
||||
import coil.Coil
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu
|
||||
package com.sukisu.ultra
|
||||
|
||||
import android.system.Os
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu
|
||||
package com.sukisu.ultra
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
@@ -16,14 +16,14 @@ object Natives {
|
||||
// 10946: add capabilities
|
||||
// 10977: change groups_count and groups to avoid overflow write
|
||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 11071
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 12800
|
||||
|
||||
// 11640: Support query working mode, LKM or GKI
|
||||
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 12800
|
||||
|
||||
// 12040: Support disable sucompat mode
|
||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
|
||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12800
|
||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||
|
||||
const val ROOT_UID = 0
|
||||
@@ -91,6 +91,14 @@ object Natives {
|
||||
return version < MINIMAL_SUPPORTED_KERNEL
|
||||
}
|
||||
|
||||
fun isKsuValid(pkgName: String?): Boolean {
|
||||
if (becomeManager(pkgName)) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
@Keep
|
||||
423
manager/app/src/main/java/com/sukisu/ultra/flash/KernelFlash.kt
Normal file
423
manager/app/src/main/java/com/sukisu/ultra/flash/KernelFlash.kt
Normal file
@@ -0,0 +1,423 @@
|
||||
package com.sukisu.ultra.flash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Error
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.utils.AssetsUtil
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
data class FlashState(
|
||||
val isFlashing: Boolean = false,
|
||||
val isCompleted: Boolean = false,
|
||||
val progress: Float = 0f,
|
||||
val currentStep: String = "",
|
||||
val logs: List<String> = emptyList(),
|
||||
val error: String = ""
|
||||
)
|
||||
|
||||
class HorizonKernelState {
|
||||
private val _state = MutableStateFlow(FlashState())
|
||||
val state: StateFlow<FlashState> = _state.asStateFlow()
|
||||
|
||||
fun updateProgress(progress: Float) {
|
||||
_state.update { it.copy(progress = progress) }
|
||||
}
|
||||
|
||||
fun updateStep(step: String) {
|
||||
_state.update { it.copy(currentStep = step) }
|
||||
}
|
||||
|
||||
fun addLog(log: String) {
|
||||
_state.update {
|
||||
it.copy(logs = it.logs + log)
|
||||
}
|
||||
}
|
||||
|
||||
fun setError(error: String) {
|
||||
_state.update { it.copy(error = error) }
|
||||
}
|
||||
|
||||
fun startFlashing() {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isFlashing = true,
|
||||
isCompleted = false,
|
||||
progress = 0f,
|
||||
currentStep = "under preparation...",
|
||||
logs = emptyList(),
|
||||
error = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun completeFlashing() {
|
||||
_state.update { it.copy(isCompleted = true, progress = 1f) }
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
_state.value = FlashState()
|
||||
}
|
||||
}
|
||||
|
||||
class HorizonKernelWorker(
|
||||
private val context: Context,
|
||||
private val state: HorizonKernelState,
|
||||
private val slot: String? = null
|
||||
) : Thread() {
|
||||
var uri: Uri? = null
|
||||
private lateinit var filePath: String
|
||||
private lateinit var binaryPath: String
|
||||
|
||||
private var onFlashComplete: (() -> Unit)? = null
|
||||
private var originalSlot: String? = null
|
||||
|
||||
fun setOnFlashCompleteListener(listener: () -> Unit) {
|
||||
onFlashComplete = listener
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
state.startFlashing()
|
||||
state.updateStep(context.getString(R.string.horizon_preparing))
|
||||
|
||||
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
|
||||
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
|
||||
|
||||
try {
|
||||
state.updateStep(context.getString(R.string.horizon_cleaning_files))
|
||||
state.updateProgress(0.1f)
|
||||
cleanup()
|
||||
|
||||
if (!rootAvailable()) {
|
||||
state.setError(context.getString(R.string.root_required))
|
||||
return
|
||||
}
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_copying_files))
|
||||
state.updateProgress(0.2f)
|
||||
copy()
|
||||
|
||||
if (!File(filePath).exists()) {
|
||||
state.setError(context.getString(R.string.horizon_copy_failed))
|
||||
return
|
||||
}
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_extracting_tool))
|
||||
state.updateProgress(0.4f)
|
||||
getBinary()
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_patching_script))
|
||||
state.updateProgress(0.6f)
|
||||
patch()
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_flashing))
|
||||
state.updateProgress(0.7f)
|
||||
|
||||
// 获取原始槽位信息
|
||||
if (slot != null) {
|
||||
state.updateStep(context.getString(R.string.horizon_getting_original_slot))
|
||||
state.updateProgress(0.72f)
|
||||
originalSlot = runCommandGetOutput(true, "getprop ro.boot.slot_suffix")
|
||||
}
|
||||
|
||||
// 设置目标槽位
|
||||
if (!slot.isNullOrEmpty()) {
|
||||
state.updateStep(context.getString(R.string.horizon_setting_target_slot))
|
||||
state.updateProgress(0.74f)
|
||||
runCommand(true, "resetprop -n ro.boot.slot_suffix _$slot")
|
||||
}
|
||||
|
||||
flash()
|
||||
|
||||
// 恢复原始槽位
|
||||
if (!originalSlot.isNullOrEmpty()) {
|
||||
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
|
||||
state.updateProgress(0.8f)
|
||||
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
|
||||
}
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_flash_complete_status))
|
||||
state.completeFlashing()
|
||||
|
||||
(context as? Activity)?.runOnUiThread {
|
||||
onFlashComplete?.invoke()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
state.setError(e.message ?: context.getString(R.string.horizon_unknown_error))
|
||||
|
||||
// 恢复原始槽位
|
||||
if (!originalSlot.isNullOrEmpty()) {
|
||||
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
|
||||
state.updateProgress(0.8f)
|
||||
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanup() {
|
||||
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
|
||||
}
|
||||
|
||||
private fun copy() {
|
||||
uri?.let { safeUri ->
|
||||
context.contentResolver.openInputStream(safeUri)?.use { input ->
|
||||
FileOutputStream(File(filePath)).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBinary() {
|
||||
runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}")
|
||||
if (!File(binaryPath).exists()) {
|
||||
throw IOException("Failed to extract update-binary")
|
||||
}
|
||||
}
|
||||
|
||||
private fun patch() {
|
||||
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
|
||||
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
|
||||
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
|
||||
}
|
||||
|
||||
private fun flash() {
|
||||
val process = ProcessBuilder("su")
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
|
||||
try {
|
||||
process.outputStream.bufferedWriter().use { writer ->
|
||||
writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n")
|
||||
|
||||
// 写入槽位信息到临时文件
|
||||
slot?.let { selectedSlot ->
|
||||
writer.write("echo \"$selectedSlot\" > ${context.filesDir.absolutePath}/bootslot\n")
|
||||
}
|
||||
|
||||
// 构建刷写命令
|
||||
val flashCommand = buildString {
|
||||
append("sh $binaryPath 3 1 \"$filePath\"")
|
||||
if (slot != null) {
|
||||
append(" \"$(cat ${context.filesDir.absolutePath}/bootslot)\"")
|
||||
}
|
||||
append(" && touch ${context.filesDir.absolutePath}/done\n")
|
||||
}
|
||||
|
||||
writer.write(flashCommand)
|
||||
writer.write("exit\n")
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.lineSequence().forEach { line ->
|
||||
if (line.startsWith("ui_print")) {
|
||||
val logMessage = line.removePrefix("ui_print").trim()
|
||||
state.addLog(logMessage)
|
||||
|
||||
when {
|
||||
logMessage.contains("extracting", ignoreCase = true) -> {
|
||||
state.updateProgress(0.75f)
|
||||
}
|
||||
logMessage.contains("installing", ignoreCase = true) -> {
|
||||
state.updateProgress(0.85f)
|
||||
}
|
||||
logMessage.contains("complete", ignoreCase = true) -> {
|
||||
state.updateProgress(0.95f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
process.destroy()
|
||||
}
|
||||
|
||||
if (!File("${context.filesDir.absolutePath}/done").exists()) {
|
||||
throw IOException(context.getString(R.string.flash_failed_message))
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCommand(su: Boolean, cmd: String): Int {
|
||||
val process = ProcessBuilder(if (su) "su" else "sh")
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
|
||||
return try {
|
||||
process.outputStream.bufferedWriter().use { writer ->
|
||||
writer.write("$cmd\n")
|
||||
writer.write("exit\n")
|
||||
writer.flush()
|
||||
}
|
||||
process.waitFor()
|
||||
} finally {
|
||||
process.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCommandGetOutput(su: Boolean, cmd: String): String {
|
||||
val process = ProcessBuilder(if (su) "su" else "sh")
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
|
||||
return try {
|
||||
process.outputStream.bufferedWriter().use { writer ->
|
||||
writer.write("$cmd\n")
|
||||
writer.write("exit\n")
|
||||
writer.flush()
|
||||
}
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.readText().trim()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
} finally {
|
||||
process.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun rootAvailable(): Boolean {
|
||||
return try {
|
||||
val process = Runtime.getRuntime().exec("su -c id")
|
||||
val exitValue = process.waitFor()
|
||||
exitValue == 0
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HorizonKernelFlashProgress(state: FlashState) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.horizon_flash_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
progress = { state.progress },
|
||||
)
|
||||
|
||||
Text(
|
||||
text = state.currentStep,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
)
|
||||
|
||||
if (state.logs.isNotEmpty()) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.horizon_logs_label),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Start)
|
||||
.padding(top = 8.dp, bottom = 4.dp)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 150.dp)
|
||||
.padding(vertical = 4.dp),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
tonalElevation = 1.dp,
|
||||
shape = MaterialTheme.shapes.small
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
state.logs.forEach { log ->
|
||||
Text(
|
||||
text = log,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(vertical = 2.dp),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.error.isNotEmpty()) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Error,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = state.error,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
} else if (state.isCompleted) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CheckCircle,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.horizon_flash_complete),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.profile
|
||||
package com.sukisu.ultra.profile
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.profile
|
||||
package com.sukisu.ultra.profile
|
||||
|
||||
/**
|
||||
* https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui;
|
||||
package com.sukisu.ultra.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -17,7 +17,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import shirkneko.zako.sukisu.IKsuInterface;
|
||||
import com.sukisu.zako.IKsuInterface;
|
||||
import rikka.parcelablelist.ParcelableListSlice;
|
||||
|
||||
/**
|
||||
168
manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt
Normal file
168
manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt
Normal file
@@ -0,0 +1,168 @@
|
||||
package com.sukisu.ultra.ui
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
||||
import com.ramcosta.composedestinations.generated.NavGraphs
|
||||
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
|
||||
import com.ramcosta.composedestinations.spec.RouteOrDirection
|
||||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||
import io.sukisu.ultra.UltraToolInstall
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||
import com.sukisu.ultra.ui.theme.*
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
// Enable edge to edge
|
||||
enableEdgeToEdge()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// 加载保存的背景设置
|
||||
loadCustomBackground()
|
||||
loadThemeMode()
|
||||
CardConfig.load(applicationContext)
|
||||
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
if (isManager) {
|
||||
install()
|
||||
UltraToolInstall.tryToInstall()
|
||||
}
|
||||
|
||||
setContent {
|
||||
KernelSUTheme {
|
||||
val navController = rememberNavController()
|
||||
val snackBarHostState = remember { SnackbarHostState() }
|
||||
Scaffold(
|
||||
bottomBar = { BottomBar(navController) },
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||
) { innerPadding ->
|
||||
CompositionLocalProvider(
|
||||
LocalSnackbarHost provides snackBarHostState,
|
||||
) {
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
navGraph = NavGraphs.root as NavHostGraphSpec,
|
||||
navController = navController,
|
||||
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
|
||||
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
|
||||
get() = { fadeIn(animationSpec = tween(340)) }
|
||||
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
|
||||
get() = { fadeOut(animationSpec = tween(340)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomBar(navController: NavHostController) {
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
||||
val kpmVersion = getKpmVersion()
|
||||
|
||||
// 获取卡片颜色和透明度
|
||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
val cardAlpha = CardConfig.cardAlpha
|
||||
val cardElevation = CardConfig.cardElevation
|
||||
|
||||
NavigationBar(
|
||||
tonalElevation = cardElevation, // 动态设置阴影
|
||||
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
||||
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
|
||||
)
|
||||
) {
|
||||
BottomBarDestination.entries.forEach { destination ->
|
||||
if (destination == BottomBarDestination.Kpm) {
|
||||
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
|
||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||
NavigationBarItem(
|
||||
selected = isCurrentDestOnBackStack,
|
||||
onClick = {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
navigator.popBackStack(destination.direction, false)
|
||||
}
|
||||
navigator.navigate(destination.direction) {
|
||||
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
alwaysShowLabel = false,
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||
NavigationBarItem(
|
||||
selected = isCurrentDestOnBackStack,
|
||||
onClick = {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
navigator.popBackStack(destination.direction, false)
|
||||
}
|
||||
navigator.navigate(destination.direction) {
|
||||
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
alwaysShowLabel = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -31,8 +31,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import shirkneko.zako.sukisu.BuildConfig
|
||||
import shirkneko.zako.sukisu.R
|
||||
import com.sukisu.ultra.BuildConfig
|
||||
import com.sukisu.ultra.R
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@@ -98,7 +98,7 @@ private fun AboutCardContent() {
|
||||
val annotatedString = AnnotatedString.Companion.fromHtml(
|
||||
htmlString = stringResource(
|
||||
id = R.string.about_source_code,
|
||||
"<b><a href=\"https://github.com/ShirkNeko/KernelSU\">GitHub</a></b>",
|
||||
"<b><a href=\"https://github.com/ShirkNeko/SukiSU-Ultra\">GitHub</a></b>",
|
||||
"<b><a href=\"https://t.me/SukiKSU\">Telegram</a></b>"
|
||||
),
|
||||
linkStyles = TextLinkStyles(
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.graphics.text.LineBreaker
|
||||
import android.os.Build
|
||||
@@ -0,0 +1,224 @@
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Fullscreen
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
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.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
||||
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import kotlin.math.max
|
||||
|
||||
@Composable
|
||||
fun ImageEditorDialog(
|
||||
imageUri: Uri,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (Uri) -> Unit
|
||||
) {
|
||||
var scale by remember { mutableFloatStateOf(1f) }
|
||||
var offsetX by remember { mutableFloatStateOf(0f) }
|
||||
var offsetY by remember { mutableFloatStateOf(0f) }
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val density = LocalDensity.current
|
||||
var lastScale by remember { mutableFloatStateOf(1f) }
|
||||
var lastOffsetX by remember { mutableFloatStateOf(0f) }
|
||||
var lastOffsetY by remember { mutableFloatStateOf(0f) }
|
||||
var imageSize by remember { mutableStateOf(Size.Zero) }
|
||||
var screenSize by remember { mutableStateOf(Size.Zero) }
|
||||
val animatedScale by animateFloatAsState(
|
||||
targetValue = scale,
|
||||
label = "ScaleAnimation"
|
||||
)
|
||||
val animatedOffsetX by animateFloatAsState(
|
||||
targetValue = offsetX,
|
||||
label = "OffsetXAnimation"
|
||||
)
|
||||
val animatedOffsetY by animateFloatAsState(
|
||||
targetValue = offsetY,
|
||||
label = "OffsetYAnimation"
|
||||
)
|
||||
val updateTransformation = remember {
|
||||
{ newScale: Float, newOffsetX: Float, newOffsetY: Float ->
|
||||
val scaleDiff = kotlin.math.abs(newScale - lastScale)
|
||||
val offsetXDiff = kotlin.math.abs(newOffsetX - lastOffsetX)
|
||||
val offsetYDiff = kotlin.math.abs(newOffsetY - lastOffsetY)
|
||||
if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) {
|
||||
scale = newScale
|
||||
offsetX = newOffsetX
|
||||
offsetY = newOffsetY
|
||||
lastScale = newScale
|
||||
lastOffsetX = newOffsetX
|
||||
lastOffsetY = newOffsetY
|
||||
}
|
||||
}
|
||||
}
|
||||
val scaleToFullScreen = remember {
|
||||
{
|
||||
if (imageSize.height > 0 && screenSize.height > 0) {
|
||||
val newScale = screenSize.height / imageSize.height
|
||||
updateTransformation(newScale, 0f, 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = true,
|
||||
dismissOnClickOutside = false,
|
||||
usePlatformDefaultWidth = false
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.9f))
|
||||
.onSizeChanged { size ->
|
||||
screenSize = Size(size.width.toFloat(), size.height.toFloat())
|
||||
}
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(imageUri)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = stringResource(R.string.settings_custom_background),
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer(
|
||||
scaleX = animatedScale,
|
||||
scaleY = animatedScale,
|
||||
translationX = animatedOffsetX,
|
||||
translationY = animatedOffsetY
|
||||
)
|
||||
.pointerInput(Unit) {
|
||||
detectTransformGestures { _, pan, zoom, _ ->
|
||||
scope.launch {
|
||||
try {
|
||||
val newScale = (scale * zoom).coerceIn(0.5f, 3f)
|
||||
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
|
||||
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
|
||||
val newOffsetX = if (maxOffsetX > 0) {
|
||||
(offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val newOffsetY = if (maxOffsetY > 0) {
|
||||
(offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
updateTransformation(newScale, newOffsetX, newOffsetY)
|
||||
} catch (e: Exception) {
|
||||
updateTransformation(lastScale, lastOffsetX, lastOffsetY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onSizeChanged { size ->
|
||||
imageSize = Size(size.width.toFloat(), size.height.toFloat())
|
||||
}
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.align(Alignment.TopCenter),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringResource(R.string.cancel),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { scaleToFullScreen() },
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Fullscreen,
|
||||
contentDescription = stringResource(R.string.reprovision),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
try {
|
||||
val transformation = BackgroundTransformation(scale, offsetX, offsetY)
|
||||
val savedUri = context.saveTransformedBackground(imageUri, transformation)
|
||||
savedUri?.let { onConfirm(it) }
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.confirm),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
.padding(16.dp)
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.image_editor_hint),
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
@@ -42,7 +42,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import shirkneko.zako.sukisu.ui.theme.CardConfig
|
||||
import com.sukisu.ultra.ui.theme.CardConfig
|
||||
|
||||
private const val TAG = "SearchBar"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
|
||||
/**
|
||||
* 槽位选择对话框组件
|
||||
* 用于HorizonKernel刷写时选择目标槽位
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SlotSelectionDialog(
|
||||
show: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onSlotSelected: (String) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var currentSlot by remember { mutableStateOf<String?>(null) }
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
currentSlot = getCurrentSlot(context)
|
||||
errorMessage = null
|
||||
} catch (e: Exception) {
|
||||
errorMessage = e.message
|
||||
currentSlot = null
|
||||
}
|
||||
}
|
||||
|
||||
if (show) {
|
||||
val backgroundColor = if (!ThemeConfig.useDynamicColor) {
|
||||
ThemeConfig.currentTheme.ButtonContrast.copy(alpha = 1.0f)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 1.0f)
|
||||
}
|
||||
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Card(
|
||||
shape = MaterialTheme.shapes.medium.copy(
|
||||
topStart = CornerSize(16.dp),
|
||||
topEnd = CornerSize(16.dp),
|
||||
bottomEnd = CornerSize(16.dp),
|
||||
bottomStart = CornerSize(16.dp)
|
||||
),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = backgroundColor
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(24.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.select_slot_title),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
if (errorMessage != null) {
|
||||
Text(
|
||||
text = "Error: $errorMessage",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.current_slot,
|
||||
currentSlot ?: "Unknown"
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.select_slot_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
val isDefaultSlotA = currentSlot == "_a" || currentSlot == "a"
|
||||
Button(
|
||||
onClick = { onSlotSelected("a") },
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (isDefaultSlotA)
|
||||
MaterialTheme.colorScheme.primary
|
||||
else
|
||||
MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = if (isDefaultSlotA)
|
||||
MaterialTheme.colorScheme.onPrimary
|
||||
else
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.slot_a),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
}
|
||||
|
||||
val isDefaultSlotB = currentSlot == "_b" || currentSlot == "b"
|
||||
Button(
|
||||
onClick = { onSlotSelected("b") },
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (isDefaultSlotB)
|
||||
MaterialTheme.colorScheme.secondary
|
||||
else
|
||||
MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = if (isDefaultSlotB)
|
||||
MaterialTheme.colorScheme.onSecondary
|
||||
else
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.slot_b),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
thickness = 1.dp,
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(text = stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
currentSlot?.let { onSlotSelected(it) }
|
||||
onDismiss()
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前槽位信息
|
||||
private fun getCurrentSlot(context: Context): String? {
|
||||
return runCommandGetOutput(true, "getprop ro.boot.slot_suffix")?.let {
|
||||
if (it.startsWith("_")) it.substring(1) else it
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCommandGetOutput(su: Boolean, cmd: String): String? {
|
||||
return try {
|
||||
val process = ProcessBuilder(if (su) "su" else "sh").start()
|
||||
process.outputStream.bufferedWriter().use { writer ->
|
||||
writer.write("$cmd\n")
|
||||
writer.write("exit\n")
|
||||
writer.flush()
|
||||
}
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.readText().trim()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.component.profile
|
||||
package com.sukisu.ultra.ui.component.profile
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
@@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.SwitchItem
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.SwitchItem
|
||||
|
||||
@Composable
|
||||
fun AppProfileConfig(
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.component.profile
|
||||
package com.sukisu.ultra.ui.component.profile
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -42,12 +42,12 @@ import com.maxkeppeler.sheets.input.models.ValidationResult
|
||||
import com.maxkeppeler.sheets.list.ListDialog
|
||||
import com.maxkeppeler.sheets.list.models.ListOption
|
||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.profile.Capabilities
|
||||
import shirkneko.zako.sukisu.profile.Groups
|
||||
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
|
||||
import shirkneko.zako.sukisu.ui.util.isSepolicyValid
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.profile.Capabilities
|
||||
import com.sukisu.ultra.profile.Groups
|
||||
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||
import com.sukisu.ultra.ui.util.isSepolicyValid
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.component.profile
|
||||
package com.sukisu.ultra.ui.component.profile
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -23,11 +23,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
|
||||
import shirkneko.zako.sukisu.ui.util.setSepolicy
|
||||
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.util.listAppProfileTemplates
|
||||
import com.sukisu.ultra.ui.util.setSepolicy
|
||||
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.Crossfade
|
||||
@@ -64,20 +64,20 @@ import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplat
|
||||
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.SwitchItem
|
||||
import shirkneko.zako.sukisu.ui.component.profile.AppProfileConfig
|
||||
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
|
||||
import shirkneko.zako.sukisu.ui.component.profile.TemplateConfig
|
||||
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
|
||||
import shirkneko.zako.sukisu.ui.util.forceStopApp
|
||||
import shirkneko.zako.sukisu.ui.util.getSepolicy
|
||||
import shirkneko.zako.sukisu.ui.util.launchApp
|
||||
import shirkneko.zako.sukisu.ui.util.restartApp
|
||||
import shirkneko.zako.sukisu.ui.util.setSepolicy
|
||||
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
|
||||
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.SwitchItem
|
||||
import com.sukisu.ultra.ui.component.profile.AppProfileConfig
|
||||
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
|
||||
import com.sukisu.ultra.ui.component.profile.TemplateConfig
|
||||
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||
import com.sukisu.ultra.ui.util.forceStopApp
|
||||
import com.sukisu.ultra.ui.util.getSepolicy
|
||||
import com.sukisu.ultra.ui.util.launchApp
|
||||
import com.sukisu.ultra.ui.util.restartApp
|
||||
import com.sukisu.ultra.ui.util.setSepolicy
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -11,7 +11,7 @@ import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDe
|
||||
import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination
|
||||
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
|
||||
import shirkneko.zako.sukisu.R
|
||||
import com.sukisu.ultra.R
|
||||
|
||||
enum class BottomBarDestination(
|
||||
val direction: DirectionDestinationSpec,
|
||||
@@ -21,8 +21,8 @@ enum class BottomBarDestination(
|
||||
val rootRequired: Boolean,
|
||||
) {
|
||||
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false),
|
||||
Kpm(KpmScreenDestination, R.string.kpm_title, Icons.Filled.Build, Icons.Outlined.Build, true),
|
||||
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true),
|
||||
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true),
|
||||
Kpm(KpmScreenDestination, R.string.kpm_title, Icons.Filled.Build, Icons.Outlined.Build, true),
|
||||
Settings(SettingScreenDestination, R.string.settings, Icons.Filled.Settings, Icons.Outlined.Settings, false),
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.os.Environment
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -37,10 +37,10 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
|
||||
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
|
||||
import shirkneko.zako.sukisu.ui.util.runModuleAction
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.KeyEventBlocker
|
||||
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||
import com.sukisu.ultra.ui.util.runModuleAction
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
@@ -30,9 +30,9 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
|
||||
import shirkneko.zako.sukisu.ui.util.*
|
||||
import com.sukisu.ultra.ui.component.KeyEventBlocker
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import com.sukisu.ultra.R
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@@ -45,10 +45,6 @@ enum class FlashingStatus {
|
||||
|
||||
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
|
||||
|
||||
fun getFlashingStatus(): FlashingStatus {
|
||||
return currentFlashingStatus.value
|
||||
}
|
||||
|
||||
fun setFlashingStatus(status: FlashingStatus) {
|
||||
currentFlashingStatus.value = status
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
@@ -33,15 +33,15 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import shirkneko.zako.sukisu.*
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
|
||||
import shirkneko.zako.sukisu.ui.util.*
|
||||
import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo
|
||||
import com.sukisu.ultra.*
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import shirkneko.zako.sukisu.ui.theme.getCardColors
|
||||
import shirkneko.zako.sukisu.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -49,8 +49,13 @@ import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import shirkneko.zako.sukisu.ui.theme.CardConfig
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.sukisu.ultra.ui.theme.CardConfig
|
||||
import androidx.core.content.edit
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.util.zip.GZIPInputStream
|
||||
import kotlin.random.Random
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>(start = true)
|
||||
@@ -59,6 +64,8 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
val context = LocalContext.current
|
||||
var isSimpleMode by rememberSaveable { mutableStateOf(false) }
|
||||
var isHideVersion by rememberSaveable { mutableStateOf(false) }
|
||||
var isHideOtherInfo by rememberSaveable { mutableStateOf(false) }
|
||||
var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
// 从 SharedPreferences 加载简洁模式状态
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -70,9 +77,36 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("is_hide_version", false)
|
||||
}
|
||||
// 从 SharedPreferences 加载隐藏模块数量等信息开关状态
|
||||
LaunchedEffect(Unit) {
|
||||
isHideOtherInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("is_hide_other_info", false)
|
||||
}
|
||||
// 从 SharedPreferences 加载隐藏 SuSFS 状态开关状态
|
||||
LaunchedEffect(Unit) {
|
||||
isHideSusfsStatus = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("is_hide_susfs_status", false)
|
||||
}
|
||||
val kernelVersion = getKernelVersion()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val deviceModel = getDeviceModel(context)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
val zako = "一.*加.*A.*c.*e.*5.*P.*r.*o".toRegex().matches(deviceModel)
|
||||
val isVersion = ksuVersion == 12777
|
||||
val shouldTriggerRestart = zako && kernelVersion.isGKI() && (isVersion)
|
||||
|
||||
LaunchedEffect(shouldTriggerRestart) {
|
||||
if (shouldTriggerRestart) {
|
||||
val random = Random.nextInt(0, 100)
|
||||
if (random <= 95) {
|
||||
reboot()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -96,6 +130,10 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
if (shouldTriggerRestart) {
|
||||
WarningCard(message = "zakozako")
|
||||
return@Column
|
||||
}
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
val lkmMode = ksuVersion?.let {
|
||||
@@ -155,10 +193,10 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
InfoCard()
|
||||
if (!isSimpleMode) {
|
||||
ContributionCard()
|
||||
DonateCard()
|
||||
LearnMoreCard()
|
||||
}
|
||||
|
||||
Spacer(Modifier)
|
||||
}
|
||||
}
|
||||
@@ -246,14 +284,18 @@ private fun TopBar(
|
||||
}
|
||||
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
if (Natives.isKsuValid(ksuApp.packageName)) {
|
||||
IconButton(onClick = { showDropdown = true }) {
|
||||
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }
|
||||
DropdownMenu(
|
||||
expanded = showDropdown,
|
||||
onDismissRequest = { showDropdown = false }
|
||||
) {
|
||||
|
||||
RebootDropdownItem(id = R.string.reboot)
|
||||
|
||||
val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
val pm =
|
||||
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||
@@ -264,12 +306,14 @@ private fun TopBar(
|
||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun StatusCard(
|
||||
kernelVersion: KernelVersion,
|
||||
@@ -308,6 +352,12 @@ private fun StatusCard(
|
||||
val isHideVersion = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("is_hide_version", false)
|
||||
|
||||
val isHideOtherInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("is_hide_other_info", false)
|
||||
|
||||
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("is_hide_susfs_status", false)
|
||||
|
||||
Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
@@ -321,6 +371,7 @@ private fun StatusCard(
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
if (!isHideOtherInfo) {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
@@ -332,9 +383,20 @@ private fun StatusCard(
|
||||
text = stringResource(R.string.home_module_count, getModuleCount()),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
val kpmVersion = getKpmVersion()
|
||||
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_kpm_module, getKpmModuleCount()),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!isHideSusfsStatus) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
val suSFS = getSuSFS()
|
||||
if (lkmMode != true) {
|
||||
val translatedStatus = when (suSFS) {
|
||||
"Supported" -> stringResource(R.string.status_supported)
|
||||
"Not Supported" -> stringResource(R.string.status_not_supported)
|
||||
@@ -347,6 +409,8 @@ private fun StatusCard(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kernelVersion.isGKI() -> {
|
||||
Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed))
|
||||
@@ -402,6 +466,38 @@ fun WarningCard(
|
||||
}
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
fun ContributionCard() {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val links = listOf("https://github.com/zako", "https://github.com/udochina")
|
||||
ElevatedCard(
|
||||
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
val randomIndex = Random.nextInt(links.size)
|
||||
uriHandler.openUri(links[randomIndex])
|
||||
}
|
||||
.padding(24.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.home_ContributionCard_kernelsu),
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_click_to_ContributionCard_kernelsu),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LearnMoreCard() {
|
||||
@@ -466,6 +562,7 @@ fun DonateCard() {
|
||||
|
||||
@Composable
|
||||
private fun InfoCard() {
|
||||
val lkmMode = Natives.isLkmMode
|
||||
val context = LocalContext.current
|
||||
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("is_simple_mode", false)
|
||||
@@ -477,8 +574,8 @@ private fun InfoCard() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)
|
||||
) {
|
||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp),
|
||||
) withContext@{
|
||||
val contents = StringBuilder()
|
||||
val uname = Os.uname()
|
||||
|
||||
@@ -486,74 +583,88 @@ private fun InfoCard() {
|
||||
fun InfoCardItem(
|
||||
label: String,
|
||||
content: String,
|
||||
icon: ImageVector = Icons.Default.Info
|
||||
) {
|
||||
contents.appendLine(label).appendLine(content).appendLine()
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = label,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column {
|
||||
Text(text = label, style = MaterialTheme.typography.bodyLarge)
|
||||
Text(text = content, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InfoCardItem(stringResource(R.string.home_kernel), uname.release)
|
||||
InfoCardItem(stringResource(R.string.home_kernel), uname.release, icon = Icons.Default.Memory)
|
||||
|
||||
if (!isSimpleMode) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val androidVersion = Build.VERSION.RELEASE
|
||||
InfoCardItem(stringResource(R.string.home_android_version), androidVersion)
|
||||
InfoCardItem(stringResource(R.string.home_android_version), androidVersion, icon = Icons.Default.Android)
|
||||
}
|
||||
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val deviceModel = getDeviceModel(context)
|
||||
InfoCardItem(stringResource(R.string.home_device_model), deviceModel)
|
||||
|
||||
|
||||
InfoCardItem(stringResource(R.string.home_device_model), deviceModel, icon = Icons.Default.PhoneAndroid)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val managerVersion = getManagerVersion(context)
|
||||
InfoCardItem(
|
||||
stringResource(R.string.home_manager_version),
|
||||
"${managerVersion.first} (${managerVersion.second})"
|
||||
)
|
||||
|
||||
|
||||
InfoCardItem(stringResource(R.string.home_manager_version), "${managerVersion.first} (${managerVersion.second})", icon = Icons.Default.Settings)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())
|
||||
|
||||
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus(), icon = Icons.Default.Security)
|
||||
|
||||
if (!isSimpleMode) {
|
||||
if (lkmMode != true) {
|
||||
val kpmVersion = getKpmVersion()
|
||||
var displayVersion: String
|
||||
val isKpmConfigured = checkKpmConfigured()
|
||||
|
||||
if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) {
|
||||
val statusText = if (isKpmConfigured) {
|
||||
stringResource(R.string.kernel_patched)
|
||||
} else {
|
||||
stringResource(R.string.kernel_not_enabled)
|
||||
}
|
||||
displayVersion = "${stringResource(R.string.not_supported)} ($statusText)"
|
||||
} else {
|
||||
displayVersion = "${stringResource(R.string.supported)} ($kpmVersion)"
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(stringResource(R.string.home_kpm_version), displayVersion, icon = Icons.Default.Code)
|
||||
}
|
||||
}
|
||||
|
||||
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("is_hide_susfs_status", false)
|
||||
|
||||
if ((!isSimpleMode) && (!isHideSusfsStatus)) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
val suSFS = getSuSFS()
|
||||
if (suSFS == "Supported") {
|
||||
InfoCardItem(
|
||||
stringResource(R.string.home_susfs_version),
|
||||
"${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})"
|
||||
)
|
||||
} else {
|
||||
val susSUMode = try {
|
||||
susfsSUS_SU_Mode()
|
||||
} catch (e: Exception) {
|
||||
0
|
||||
}
|
||||
|
||||
if (susSUMode == 2 || susSUMode == 0) {
|
||||
val suSFSVersion = getSuSFSVersion()
|
||||
if (suSFSVersion.isEmpty()) return@withContext
|
||||
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
|
||||
val susSUModeLabel = stringResource(R.string.sus_su_mode)
|
||||
val susSUModeValue = susSUMode.toString()
|
||||
val susSUModeText = if (isSUS_SU) " $susSUModeLabel $susSUModeValue" else ""
|
||||
|
||||
InfoCardItem(
|
||||
stringResource(R.string.home_susfs_version),
|
||||
"${getSuSFSVersion()} (${getSuSFSVariant()})$susSUModeText"
|
||||
)
|
||||
} else {
|
||||
InfoCardItem(
|
||||
stringResource(R.string.home_susfs_version),
|
||||
"${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})"
|
||||
)
|
||||
val infoText = buildString {
|
||||
append(suSFSVersion)
|
||||
append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})")
|
||||
if (isSUS_SU) {
|
||||
val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" }
|
||||
if (susSUMode.isNotEmpty()) {
|
||||
append(" ${stringResource(R.string.sus_su_mode)} $susSUMode")
|
||||
}
|
||||
}
|
||||
}
|
||||
InfoCardItem(
|
||||
stringResource(R.string.home_susfs_version), infoText, icon = Icons.Default.Storage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -605,7 +716,27 @@ private fun getDeviceModel(context: Context): String {
|
||||
}
|
||||
}
|
||||
Build.DEVICE
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
Build.DEVICE
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkKpmConfigured(): Boolean {
|
||||
try {
|
||||
val process = Runtime.getRuntime().exec("su -c cat /proc/config.gz")
|
||||
val inputStream = process.inputStream
|
||||
val gzipInputStream = GZIPInputStream(inputStream)
|
||||
val reader = BufferedReader(InputStreamReader(gzipInputStream))
|
||||
|
||||
var line: String?
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
if (line?.contains("CONFIG_KPM=y") == true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
reader.close()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -27,27 +32,25 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.maxkeppeker.sheets.core.models.base.Header
|
||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||
import com.maxkeppeler.sheets.list.ListDialog
|
||||
import com.maxkeppeler.sheets.list.models.ListOption
|
||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.DialogHandle
|
||||
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
|
||||
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
|
||||
import shirkneko.zako.sukisu.ui.util.*
|
||||
import shirkneko.zako.sukisu.utils.AssetsUtil
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.DialogHandle
|
||||
import com.sukisu.ultra.ui.component.SlotSelectionDialog
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||
import com.sukisu.ultra.flash.HorizonKernelFlashProgress
|
||||
import com.sukisu.ultra.flash.HorizonKernelState
|
||||
import com.sukisu.ultra.flash.HorizonKernelWorker
|
||||
import com.sukisu.ultra.ui.theme.CardConfig
|
||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||
import com.sukisu.ultra.ui.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -60,8 +63,12 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
|
||||
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
|
||||
val context = LocalContext.current
|
||||
|
||||
var showRebootDialog by remember { mutableStateOf(false) }
|
||||
var showSlotSelectionDialog by remember { mutableStateOf(false) }
|
||||
var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
|
||||
val horizonKernelState = remember { HorizonKernelState() }
|
||||
val flashState by horizonKernelState.state.collectAsState()
|
||||
val summary = stringResource(R.string.horizon_kernel_summary)
|
||||
|
||||
val onFlashComplete = {
|
||||
showRebootDialog = true
|
||||
@@ -79,7 +86,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
writer.write("svc power reboot\n")
|
||||
writer.write("exit\n")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
@@ -91,7 +98,11 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
when (method) {
|
||||
is InstallMethod.HorizonKernel -> {
|
||||
method.uri?.let { uri ->
|
||||
val worker = HorizonKernelWorker(context)
|
||||
val worker = HorizonKernelWorker(
|
||||
context = context,
|
||||
state = horizonKernelState,
|
||||
slot = method.slot
|
||||
)
|
||||
worker.uri = uri
|
||||
worker.setOnFlashCompleteListener(onFlashComplete)
|
||||
worker.start()
|
||||
@@ -110,6 +121,22 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
Unit
|
||||
}
|
||||
|
||||
// 槽位选择
|
||||
SlotSelectionDialog(
|
||||
show = showSlotSelectionDialog,
|
||||
onDismiss = { showSlotSelectionDialog = false },
|
||||
onSlotSelected = { slot ->
|
||||
showSlotSelectionDialog = false
|
||||
val horizonMethod = InstallMethod.HorizonKernel(
|
||||
uri = tempKernelUri,
|
||||
slot = slot,
|
||||
summary = summary
|
||||
)
|
||||
installMethod = horizonMethod
|
||||
onInstall()
|
||||
}
|
||||
)
|
||||
|
||||
val currentKmi by produceState(initialValue = "") {
|
||||
value = getCurrentKmi()
|
||||
}
|
||||
@@ -165,9 +192,25 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
SelectInstallMethod { method ->
|
||||
SelectInstallMethod(
|
||||
onSelected = { method ->
|
||||
if (method is InstallMethod.HorizonKernel && method.uri != null && method.slot == null) {
|
||||
tempKernelUri = method.uri
|
||||
showSlotSelectionDialog = true
|
||||
} else {
|
||||
installMethod = method
|
||||
}
|
||||
horizonKernelState.reset()
|
||||
}
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = flashState.isFlashing && installMethod is InstallMethod.HorizonKernel,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
HorizonKernelFlashProgress(flashState)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -182,9 +225,20 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
)
|
||||
}
|
||||
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
|
||||
if (method.slot != null) {
|
||||
Text(
|
||||
stringResource(
|
||||
id = R.string.selected_slot,
|
||||
if (method.slot == "a") stringResource(id = R.string.slot_a)
|
||||
else stringResource(id = R.string.slot_b)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = installMethod != null,
|
||||
enabled = installMethod != null && !flashState.isFlashing,
|
||||
onClick = onClickNext
|
||||
) {
|
||||
Text(
|
||||
@@ -197,14 +251,6 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchHorizonKernelFlash(context: Context, uri: Uri) {
|
||||
val worker = HorizonKernelWorker(context)
|
||||
worker.uri = uri
|
||||
worker.setOnFlashCompleteListener {
|
||||
}
|
||||
worker.start()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RebootDialog(
|
||||
show: Boolean,
|
||||
@@ -230,133 +276,6 @@ private fun RebootDialog(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class HorizonKernelWorker(private val context: Context) : Thread() {
|
||||
var uri: Uri? = null
|
||||
private lateinit var filePath: String
|
||||
private lateinit var binaryPath: String
|
||||
|
||||
|
||||
private var onFlashComplete: (() -> Unit)? = null
|
||||
|
||||
fun setOnFlashCompleteListener(listener: () -> Unit) {
|
||||
onFlashComplete = listener
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
|
||||
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
|
||||
|
||||
try {
|
||||
cleanup()
|
||||
if (!rootAvailable()) {
|
||||
showError(context.getString(R.string.root_required))
|
||||
return
|
||||
}
|
||||
|
||||
copy()
|
||||
if (!File(filePath).exists()) {
|
||||
showError(context.getString(R.string.copy_failed))
|
||||
return
|
||||
}
|
||||
|
||||
getBinary()
|
||||
patch()
|
||||
flash()
|
||||
|
||||
(context as? Activity)?.runOnUiThread {
|
||||
onFlashComplete?.invoke()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
showError(e.message ?: context.getString(R.string.unknown_error))
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanup() {
|
||||
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
|
||||
}
|
||||
|
||||
private fun copy() {
|
||||
uri?.let { safeUri ->
|
||||
context.contentResolver.openInputStream(safeUri)?.use { input ->
|
||||
FileOutputStream(File(filePath)).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBinary() {
|
||||
runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}")
|
||||
if (!File(binaryPath).exists()) {
|
||||
throw IOException("Failed to extract update-binary")
|
||||
}
|
||||
}
|
||||
|
||||
private fun patch() {
|
||||
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
|
||||
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
|
||||
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
|
||||
}
|
||||
|
||||
private fun flash() {
|
||||
val process = ProcessBuilder("su")
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
|
||||
try {
|
||||
process.outputStream.bufferedWriter().use { writer ->
|
||||
writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n")
|
||||
writer.write("sh $binaryPath 3 1 \"$filePath\" && touch ${context.filesDir.absolutePath}/done\nexit\n")
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.lineSequence().forEach { line ->
|
||||
if (line.startsWith("ui_print")) {
|
||||
showLog(line.removePrefix("ui_print"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
process.destroy()
|
||||
}
|
||||
|
||||
if (!File("${context.filesDir.absolutePath}/done").exists()) {
|
||||
throw IOException("Flash failed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCommand(su: Boolean, cmd: String): Int {
|
||||
val process = ProcessBuilder(if (su) "su" else "sh")
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
|
||||
return try {
|
||||
process.outputStream.bufferedWriter().use { writer ->
|
||||
writer.write("$cmd\n")
|
||||
writer.write("exit\n")
|
||||
writer.flush()
|
||||
}
|
||||
process.waitFor()
|
||||
} finally {
|
||||
process.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showError(message: String) {
|
||||
(context as? Activity)?.runOnUiThread {
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLog(message: String) {
|
||||
(context as? Activity)?.runOnUiThread {
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class InstallMethod {
|
||||
data class SelectFile(
|
||||
val uri: Uri? = null,
|
||||
@@ -376,6 +295,7 @@ sealed class InstallMethod {
|
||||
|
||||
data class HorizonKernel(
|
||||
val uri: Uri? = null,
|
||||
val slot: String? = null,
|
||||
@StringRes override val label: Int = R.string.horizon_kernel,
|
||||
override val summary: String? = null
|
||||
) : InstallMethod()
|
||||
@@ -388,6 +308,7 @@ sealed class InstallMethod {
|
||||
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||
val rootAvailable = rootAvailable()
|
||||
val isAbDevice = isAbDevice()
|
||||
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
|
||||
val selectFileTip = stringResource(
|
||||
id = R.string.select_file_tip,
|
||||
if (isInitBoot()) "init_boot" else "boot"
|
||||
@@ -402,7 +323,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||
if (isAbDevice) {
|
||||
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
|
||||
}
|
||||
radioOptions.add(InstallMethod.HorizonKernel(summary = "Flashing the Anykernel3 Kernel"))
|
||||
radioOptions.add(InstallMethod.HorizonKernel(summary = horizonKernelSummary))
|
||||
}
|
||||
|
||||
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
|
||||
@@ -415,7 +336,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||
it.data?.data?.let { uri ->
|
||||
val option = when (currentSelectingMethod) {
|
||||
is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip)
|
||||
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = " Flashing the Anykernel3 Kernel")
|
||||
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = horizonKernelSummary)
|
||||
else -> null
|
||||
}
|
||||
option?.let {
|
||||
@@ -506,40 +427,78 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
|
||||
val supportedKmi by produceState(initialValue = emptyList<String>()) {
|
||||
value = getSupportedKmis()
|
||||
}
|
||||
val options = supportedKmi.map { value ->
|
||||
val listOptions = supportedKmi.map { value ->
|
||||
ListOption(
|
||||
titleText = value
|
||||
titleText = value,
|
||||
subtitleText = null,
|
||||
icon = null
|
||||
)
|
||||
}
|
||||
|
||||
var selection by remember { mutableStateOf<String?>(null) }
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
shape = MaterialTheme.shapes.medium
|
||||
) {
|
||||
ListDialog(
|
||||
state = rememberUseCaseState(
|
||||
visible = true,
|
||||
onFinishedRequest = {
|
||||
onSelected(selection)
|
||||
},
|
||||
onCloseRequest = {
|
||||
var selection: String? = null
|
||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
||||
ThemeConfig.currentTheme.ButtonContrast
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
dismiss()
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.select_kmi))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
listOptions.forEachIndexed { index, option ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
selection = supportedKmi[index]
|
||||
}
|
||||
),
|
||||
header = Header.Default(
|
||||
title = stringResource(R.string.select_kmi),
|
||||
),
|
||||
selection = ListSelection.Single(
|
||||
showRadioButtons = true,
|
||||
options = options,
|
||||
) { _, option ->
|
||||
selection = option.titleText
|
||||
}
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Column {
|
||||
Text(text = option.titleText)
|
||||
option.subtitleText?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
if (selection != null) {
|
||||
onSelected(selection)
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
dismiss()
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
tonalElevation = getCardElevation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -549,8 +508,15 @@ private fun TopBar(
|
||||
onLkmUpload: () -> Unit = {},
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
val cardAlpha = CardConfig.cardAlpha
|
||||
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.install)) },
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||
631
manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt
Normal file
631
manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt
Normal file
@@ -0,0 +1,631 @@
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import com.sukisu.ultra.ui.component.*
|
||||
import com.sukisu.ultra.ui.theme.*
|
||||
import com.sukisu.ultra.ui.viewmodel.KpmViewModel
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import java.io.File
|
||||
import androidx.core.content.edit
|
||||
import com.sukisu.ultra.R
|
||||
import java.io.BufferedReader
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.net.*
|
||||
|
||||
/**
|
||||
* KPM 管理界面
|
||||
* 以下内核模块功能由KernelPatch开发,经过修改后加入SukiSU Ultra的内核模块功能
|
||||
* 开发者:ShirkNeko, Liaokong
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
fun KpmScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
viewModel: KpmViewModel = viewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackBarHost = remember { SnackbarHostState() }
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
||||
ThemeConfig.currentTheme.ButtonContrast
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
}
|
||||
|
||||
val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
|
||||
val moduleFileName = module.id
|
||||
module.id to stringResource(R.string.confirm_uninstall_content, moduleFileName)
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
|
||||
val kpmInstallFailed = stringResource(R.string.kpm_install_failed)
|
||||
val cancel = stringResource(R.string.cancel)
|
||||
val uninstall = stringResource(R.string.uninstall)
|
||||
val failedToCheckModuleFile = stringResource(R.string.snackbar_failed_to_check_module_file)
|
||||
val kpmUninstallSuccess = stringResource(R.string.kpm_uninstall_success)
|
||||
val kpmUninstallFailed = stringResource(R.string.kpm_uninstall_failed)
|
||||
val kpmInstallMode = stringResource(R.string.kpm_install_mode)
|
||||
val kpmInstallModeLoad = stringResource(R.string.kpm_install_mode_load)
|
||||
val kpmInstallModeEmbed = stringResource(R.string.kpm_install_mode_embed)
|
||||
val invalidFileTypeMessage = stringResource(R.string.invalid_file_type)
|
||||
val confirmTitle = stringResource(R.string.confirm_uninstall_title_with_filename)
|
||||
|
||||
var tempFileForInstall by remember { mutableStateOf<File?>(null) }
|
||||
val installModeDialog = rememberCustomDialog { dismiss ->
|
||||
var moduleName by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(tempFileForInstall) {
|
||||
tempFileForInstall?.let { tempFile ->
|
||||
try {
|
||||
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val inputStream = process.inputStream
|
||||
val reader = BufferedReader(InputStreamReader(inputStream))
|
||||
var line: String?
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
if (line!!.startsWith("name=")) {
|
||||
moduleName = line.substringAfter("name=").trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
process.waitFor()
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to get module name: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
dismiss()
|
||||
tempFileForInstall?.delete()
|
||||
tempFileForInstall = null
|
||||
},
|
||||
title = { Text(kpmInstallMode) },
|
||||
text = { moduleName?.let { Text(stringResource(R.string.kpm_install_mode_description, it)) } },
|
||||
confirmButton = {
|
||||
Column {
|
||||
Button(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
dismiss()
|
||||
tempFileForInstall?.let { tempFile ->
|
||||
handleModuleInstall(
|
||||
tempFile = tempFile,
|
||||
isEmbed = false,
|
||||
viewModel = viewModel,
|
||||
snackBarHost = snackBarHost,
|
||||
kpmInstallSuccess = kpmInstallSuccess,
|
||||
kpmInstallFailed = kpmInstallFailed
|
||||
)
|
||||
}
|
||||
tempFileForInstall = null
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(kpmInstallModeLoad)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
dismiss()
|
||||
tempFileForInstall?.let { tempFile ->
|
||||
handleModuleInstall(
|
||||
tempFile = tempFile,
|
||||
isEmbed = true,
|
||||
viewModel = viewModel,
|
||||
snackBarHost = snackBarHost,
|
||||
kpmInstallSuccess = kpmInstallSuccess,
|
||||
kpmInstallFailed = kpmInstallFailed
|
||||
)
|
||||
}
|
||||
tempFileForInstall = null
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(kpmInstallModeEmbed)
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
dismiss()
|
||||
tempFileForInstall?.delete()
|
||||
tempFileForInstall = null
|
||||
}
|
||||
) {
|
||||
Text(cancel)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val selectPatchLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult
|
||||
|
||||
val uri = result.data?.data ?: return@rememberLauncherForActivityResult
|
||||
|
||||
scope.launch {
|
||||
val fileName = uri.lastPathSegment ?: "unknown.kpm"
|
||||
val encodedFileName = URLEncoder.encode(fileName, "UTF-8")
|
||||
val tempFile = File(context.cacheDir, encodedFileName)
|
||||
|
||||
context.contentResolver.openInputStream(uri)?.use { input ->
|
||||
tempFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
val isCorrectMimeType = mimeType == null || mimeType.contains("application/octet-stream")
|
||||
|
||||
if (!isCorrectMimeType) {
|
||||
var shouldShowSnackbar = true
|
||||
try {
|
||||
val matchCount = checkStringsCommand(tempFile)
|
||||
val isElf = isElfFile(tempFile)
|
||||
|
||||
if (matchCount >= 1 || isElf) {
|
||||
shouldShowSnackbar = false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to execute checks: ${e.message}", e)
|
||||
}
|
||||
if (shouldShowSnackbar) {
|
||||
snackBarHost.showSnackbar(
|
||||
message = invalidFileTypeMessage,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
}
|
||||
tempFile.delete()
|
||||
return@launch
|
||||
}
|
||||
tempFileForInstall = tempFile
|
||||
installModeDialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
while(true) {
|
||||
viewModel.fetchModuleList()
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
|
||||
val sharedPreferences = context.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||
var isNoticeClosed by remember { mutableStateOf(sharedPreferences.getBoolean("is_notice_closed", false)) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SearchAppBar(
|
||||
title = { Text(stringResource(R.string.kpm_title)) },
|
||||
searchText = viewModel.search,
|
||||
onSearchTextChange = { viewModel.search = it },
|
||||
onClearClick = { viewModel.search = "" },
|
||||
scrollBehavior = scrollBehavior,
|
||||
dropdownContent = {
|
||||
IconButton(onClick = { viewModel.fetchModuleList() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Refresh,
|
||||
contentDescription = stringResource(R.string.refresh)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
selectPatchLauncher.launch(
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
type = "application/octet-stream"
|
||||
}
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Add,
|
||||
contentDescription = stringResource(R.string.kpm_install)
|
||||
)
|
||||
},
|
||||
text = { Text(stringResource(R.string.kpm_install)) },
|
||||
containerColor = cardColor.copy(alpha = 1f),
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackBarHost) }
|
||||
) { padding ->
|
||||
Column(modifier = Modifier.padding(padding)) {
|
||||
if (!isNoticeClosed) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.kernel_module_notice),
|
||||
modifier = Modifier.weight(1f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
IconButton(onClick = {
|
||||
isNoticeClosed = true
|
||||
sharedPreferences.edit { putBoolean("is_notice_closed", true) }
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.close_notice)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (viewModel.moduleList.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.kpm_empty),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
items(viewModel.moduleList) { module ->
|
||||
KpmModuleItem(
|
||||
module = module,
|
||||
onUninstall = {
|
||||
scope.launch {
|
||||
val confirmContent = moduleConfirmContentMap[module.id] ?: ""
|
||||
handleModuleUninstall(
|
||||
module = module,
|
||||
viewModel = viewModel,
|
||||
snackBarHost = snackBarHost,
|
||||
kpmUninstallSuccess = kpmUninstallSuccess,
|
||||
kpmUninstallFailed = kpmUninstallFailed,
|
||||
failedToCheckModuleFile = failedToCheckModuleFile,
|
||||
uninstall = uninstall,
|
||||
cancel = cancel,
|
||||
confirmDialog = confirmDialog,
|
||||
confirmTitle = confirmTitle,
|
||||
confirmContent = confirmContent
|
||||
)
|
||||
}
|
||||
},
|
||||
onControl = {
|
||||
viewModel.loadModuleDetail(module.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleModuleInstall(
|
||||
tempFile: File,
|
||||
isEmbed: Boolean,
|
||||
viewModel: KpmViewModel,
|
||||
snackBarHost: SnackbarHostState,
|
||||
kpmInstallSuccess: String,
|
||||
kpmInstallFailed: String
|
||||
) {
|
||||
var moduleId: String? = null
|
||||
try {
|
||||
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val inputStream = process.inputStream
|
||||
val reader = BufferedReader(InputStreamReader(inputStream))
|
||||
var line: String?
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
if (line!!.startsWith("name=")) {
|
||||
moduleId = line.substringAfter("name=").trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
process.waitFor()
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to get module ID from strings command: ${e.message}", e)
|
||||
}
|
||||
|
||||
if (moduleId == null || moduleId.isEmpty()) {
|
||||
Log.e("KsuCli", "Failed to extract module ID from file: ${tempFile.name}")
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmInstallFailed,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
tempFile.delete()
|
||||
return
|
||||
}
|
||||
|
||||
val targetPath = "/data/adb/kpm/$moduleId.kpm"
|
||||
|
||||
try {
|
||||
if (isEmbed) {
|
||||
Runtime.getRuntime().exec(arrayOf("su", "-c", "mkdir -p /data/adb/kpm")).waitFor()
|
||||
Runtime.getRuntime().exec(arrayOf("su", "-c", "cp ${tempFile.absolutePath} $targetPath")).waitFor()
|
||||
}
|
||||
|
||||
val loadResult = loadKpmModule(tempFile.absolutePath)
|
||||
if (loadResult.startsWith("Error")) {
|
||||
Log.e("KsuCli", "Failed to load KPM module: $loadResult")
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmInstallFailed,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
} else {
|
||||
viewModel.fetchModuleList()
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmInstallSuccess,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to load KPM module: ${e.message}", e)
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmInstallFailed,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
}
|
||||
tempFile.delete()
|
||||
}
|
||||
|
||||
private suspend fun handleModuleUninstall(
|
||||
module: KpmViewModel.ModuleInfo,
|
||||
viewModel: KpmViewModel,
|
||||
snackBarHost: SnackbarHostState,
|
||||
kpmUninstallSuccess: String,
|
||||
kpmUninstallFailed: String,
|
||||
failedToCheckModuleFile: String,
|
||||
uninstall: String,
|
||||
cancel: String,
|
||||
confirmTitle : String,
|
||||
confirmContent : String,
|
||||
confirmDialog: ConfirmDialogHandle
|
||||
) {
|
||||
val moduleFileName = "${module.id}.kpm"
|
||||
val moduleFilePath = "/data/adb/kpm/$moduleFileName"
|
||||
|
||||
val fileExists = try {
|
||||
val result = Runtime.getRuntime().exec(arrayOf("su", "-c", "ls /data/adb/kpm/$moduleFileName")).waitFor() == 0
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to check module file existence: ${e.message}", e)
|
||||
snackBarHost.showSnackbar(
|
||||
message = failedToCheckModuleFile,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
false
|
||||
}
|
||||
val confirmResult = confirmDialog.awaitConfirm(
|
||||
title = confirmTitle,
|
||||
content = confirmContent,
|
||||
confirm = uninstall,
|
||||
dismiss = cancel
|
||||
)
|
||||
|
||||
if (confirmResult == ConfirmResult.Confirmed) {
|
||||
try {
|
||||
val unloadResult = unloadKpmModule(module.id)
|
||||
if (unloadResult.startsWith("Error")) {
|
||||
Log.e("KsuCli", "Failed to unload KPM module: $unloadResult")
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmUninstallFailed,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (fileExists) {
|
||||
Runtime.getRuntime().exec(arrayOf("su", "-c", "rm $moduleFilePath")).waitFor()
|
||||
}
|
||||
|
||||
viewModel.fetchModuleList()
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmUninstallSuccess,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to unload KPM module: ${e.message}", e)
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmUninstallFailed,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KpmModuleItem(
|
||||
module: KpmViewModel.ModuleInfo,
|
||||
onUninstall: () -> Unit,
|
||||
onControl: () -> Unit
|
||||
) {
|
||||
val viewModel: KpmViewModel = viewModel()
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackBarHost = remember { SnackbarHostState() }
|
||||
val successMessage = stringResource(R.string.kpm_control_success)
|
||||
val failureMessage = stringResource(R.string.kpm_control_failed)
|
||||
|
||||
if (viewModel.showInputDialog && viewModel.selectedModuleId == module.id) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { viewModel.hideInputDialog() },
|
||||
title = { Text(stringResource(R.string.kpm_control)) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = viewModel.inputArgs,
|
||||
onValueChange = { viewModel.updateInputArgs(it) },
|
||||
label = { Text(stringResource(R.string.kpm_args)) },
|
||||
placeholder = { Text(module.args) }
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
val result = viewModel.executeControl()
|
||||
val message = when (result) {
|
||||
0 -> successMessage
|
||||
else -> failureMessage
|
||||
}
|
||||
snackBarHost.showSnackbar(message)
|
||||
onControl()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { viewModel.hideInputDialog() }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
ElevatedCard(
|
||||
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = module.name,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Text(
|
||||
text = "${stringResource(R.string.kpm_version)}: ${module.version}",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = "${stringResource(R.string.kpm_author)}: ${module.author}",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = "${stringResource(R.string.kpm_args)}: ${module.args}",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = module.description,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
FilledTonalButton(
|
||||
onClick = { viewModel.showInputDialog(module.id) },
|
||||
enabled = module.hasAction
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Settings,
|
||||
contentDescription = null
|
||||
)
|
||||
Text(stringResource(R.string.kpm_control))
|
||||
}
|
||||
|
||||
FilledTonalButton(
|
||||
onClick = onUninstall
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Delete,
|
||||
contentDescription = null
|
||||
)
|
||||
Text(stringResource(R.string.kpm_uninstall))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkStringsCommand(tempFile: File): Int {
|
||||
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep -E 'name=|version=|license=|author='")
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val inputStream = process.inputStream
|
||||
val reader = BufferedReader(InputStreamReader(inputStream))
|
||||
var line: String?
|
||||
var matchCount = 0
|
||||
val keywords = listOf("name=", "version=", "license=", "author=")
|
||||
var nameExists = false
|
||||
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
if (!nameExists && line!!.startsWith("name=")) {
|
||||
nameExists = true
|
||||
matchCount++
|
||||
} else if (nameExists) {
|
||||
for (keyword in keywords) {
|
||||
if (line!!.startsWith(keyword)) {
|
||||
matchCount++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
process.waitFor()
|
||||
|
||||
return if (nameExists) matchCount else 0
|
||||
}
|
||||
|
||||
private fun isElfFile(tempFile: File): Boolean {
|
||||
val elfMagic = byteArrayOf(0x7F, 'E'.code.toByte(), 'L'.code.toByte(), 'F'.code.toByte())
|
||||
val fileBytes = ByteArray(4)
|
||||
FileInputStream(tempFile).use { input ->
|
||||
input.read(fileBytes)
|
||||
}
|
||||
return fileBytes.contentEquals(elfMagic)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.app.Activity.*
|
||||
import android.content.Context
|
||||
@@ -88,32 +88,32 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.ConfirmResult
|
||||
import shirkneko.zako.sukisu.ui.component.SearchAppBar
|
||||
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
|
||||
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
|
||||
import shirkneko.zako.sukisu.ui.util.DownloadListener
|
||||
import shirkneko.zako.sukisu.ui.util.*
|
||||
import shirkneko.zako.sukisu.ui.util.download
|
||||
import shirkneko.zako.sukisu.ui.util.hasMagisk
|
||||
import shirkneko.zako.sukisu.ui.util.reboot
|
||||
import shirkneko.zako.sukisu.ui.util.restoreModule
|
||||
import shirkneko.zako.sukisu.ui.util.toggleModule
|
||||
import shirkneko.zako.sukisu.ui.util.uninstallModule
|
||||
import shirkneko.zako.sukisu.ui.webui.WebUIActivity
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.component.ConfirmResult
|
||||
import com.sukisu.ultra.ui.component.SearchAppBar
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.component.rememberLoadingDialog
|
||||
import com.sukisu.ultra.ui.util.DownloadListener
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import com.sukisu.ultra.ui.util.download
|
||||
import com.sukisu.ultra.ui.util.hasMagisk
|
||||
import com.sukisu.ultra.ui.util.reboot
|
||||
import com.sukisu.ultra.ui.util.restoreModule
|
||||
import com.sukisu.ultra.ui.util.toggleModule
|
||||
import com.sukisu.ultra.ui.util.uninstallModule
|
||||
import com.sukisu.ultra.ui.webui.WebUIActivity
|
||||
import okhttp3.OkHttpClient
|
||||
import shirkneko.zako.sukisu.ui.util.ModuleModify
|
||||
import shirkneko.zako.sukisu.ui.theme.getCardColors
|
||||
import shirkneko.zako.sukisu.ui.theme.getCardElevation
|
||||
import shirkneko.zako.sukisu.ui.viewmodel.ModuleViewModel
|
||||
import com.sukisu.ultra.ui.util.ModuleModify
|
||||
import com.sukisu.ultra.ui.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.util.zip.ZipInputStream
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import shirkneko.zako.sukisu.ui.theme.ThemeConfig
|
||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||
import com.sukisu.ultra.R
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -346,7 +346,11 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
floatingActionButton = {
|
||||
if (!hideInstallButton) {
|
||||
val moduleInstall = stringResource(id = R.string.module_install)
|
||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
||||
ThemeConfig.currentTheme.ButtonContrast
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
}
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
selectZipLauncher.launch(
|
||||
@@ -1,5 +1,6 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
@@ -23,9 +24,8 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SliderDefaults
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -35,21 +35,13 @@ import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.SwitchItem
|
||||
import shirkneko.zako.sukisu.ui.theme.CardConfig
|
||||
import shirkneko.zako.sukisu.ui.theme.ThemeColors
|
||||
import shirkneko.zako.sukisu.ui.theme.ThemeConfig
|
||||
import shirkneko.zako.sukisu.ui.theme.saveCustomBackground
|
||||
import shirkneko.zako.sukisu.ui.theme.saveThemeColors
|
||||
import shirkneko.zako.sukisu.ui.theme.saveThemeMode
|
||||
import shirkneko.zako.sukisu.ui.theme.saveDynamicColorState
|
||||
import shirkneko.zako.sukisu.ui.util.getSuSFS
|
||||
import shirkneko.zako.sukisu.ui.util.getSuSFSFeatures
|
||||
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_0
|
||||
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_2
|
||||
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_Mode
|
||||
import com.sukisu.ultra.ui.component.ImageEditorDialog
|
||||
import com.sukisu.ultra.ui.component.SwitchItem
|
||||
import com.sukisu.ultra.ui.theme.*
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import androidx.core.content.edit
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.*
|
||||
|
||||
|
||||
fun saveCardConfig(context: Context) {
|
||||
@@ -115,6 +107,28 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
isHideVersion = newValue
|
||||
}
|
||||
|
||||
// 隐藏模块数量等信息开关状态
|
||||
var isHideOtherInfo by remember {
|
||||
mutableStateOf(prefs.getBoolean("is_hide_other_info", false))
|
||||
}
|
||||
|
||||
// 隐藏模块数量等信息开关状态
|
||||
val onHideOtherInfoChange = { newValue: Boolean ->
|
||||
prefs.edit { putBoolean("is_hide_other_info", newValue) }
|
||||
isHideOtherInfo = newValue
|
||||
}
|
||||
|
||||
// 隐藏 SuSFS 状态开关状态
|
||||
var isHideSusfsStatus by remember {
|
||||
mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false))
|
||||
}
|
||||
|
||||
// 隐藏 SuSFS 状态开关状态
|
||||
val onHideSusfsStatusChange = { newValue: Boolean ->
|
||||
prefs.edit { putBoolean("is_hide_susfs_status", newValue) }
|
||||
isHideSusfsStatus = newValue
|
||||
}
|
||||
|
||||
// SELinux 状态
|
||||
var selinuxEnabled by remember {
|
||||
mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing")
|
||||
@@ -127,11 +141,15 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
mutableStateOf(ThemeConfig.customBackgroundUri != null)
|
||||
}
|
||||
|
||||
// 图片编辑状态
|
||||
var showImageEditor by remember { mutableStateOf(false) }
|
||||
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
|
||||
|
||||
// 初始化卡片配置
|
||||
val systemIsDark = isSystemInDarkTheme()
|
||||
LaunchedEffect(Unit) {
|
||||
CardConfig.apply {
|
||||
cardAlpha = prefs.getFloat("card_alpha", 0.65f)
|
||||
cardAlpha = prefs.getFloat("card_alpha", 0.45f)
|
||||
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation
|
||||
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
|
||||
|
||||
@@ -139,7 +157,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
if (!isCustomAlphaSet) {
|
||||
val isDarkMode = ThemeConfig.forceDarkMode ?: systemIsDark
|
||||
if (isDarkMode) {
|
||||
cardAlpha = 0.5f
|
||||
cardAlpha = 0.35f
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,17 +181,36 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
|
||||
var showThemeColorDialog by remember { mutableStateOf(false) }
|
||||
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
|
||||
|
||||
// 图片选择器
|
||||
val pickImageLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.GetContent()
|
||||
) { uri: Uri? ->
|
||||
uri?.let {
|
||||
context.saveCustomBackground(it)
|
||||
selectedImageUri = it
|
||||
showImageEditor = true
|
||||
}
|
||||
}
|
||||
|
||||
// 显示图片编辑对话框
|
||||
if (showImageEditor && selectedImageUri != null) {
|
||||
ImageEditorDialog(
|
||||
imageUri = selectedImageUri!!,
|
||||
onDismiss = {
|
||||
showImageEditor = false
|
||||
selectedImageUri = null
|
||||
},
|
||||
onConfirm = { transformedUri ->
|
||||
context.saveAndApplyCustomBackground(transformedUri)
|
||||
isCustomBackgroundEnabled = true
|
||||
CardConfig.cardElevation = 0.dp
|
||||
CardConfig.isCustomBackgroundEnabled = true
|
||||
saveCardConfig(context)
|
||||
showImageEditor = false
|
||||
selectedImageUri = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -196,6 +233,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
.padding(top = 12.dp)
|
||||
) {
|
||||
// SELinux 开关
|
||||
if (ksuIsValid) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Security,
|
||||
title = stringResource(R.string.selinux),
|
||||
@@ -209,26 +247,73 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
if (result.isSuccess) selinuxEnabled = enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.AutoFixHigh, null) },
|
||||
headlineContent = { Text(stringResource(R.string.custom_settings)) },
|
||||
modifier = Modifier.clickable {
|
||||
isExpanded = !isExpanded
|
||||
}
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = isExpanded,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
) {
|
||||
// 添加简洁模块开关
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.FormatPaint,
|
||||
icon = Icons.Filled.Brush,
|
||||
title = stringResource(R.string.simple_mode),
|
||||
summary = stringResource(R.string.simple_mode_summary),
|
||||
checked = isSimpleMode
|
||||
) {
|
||||
onSimpleModeChange(it)
|
||||
}
|
||||
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isExpanded,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
) {
|
||||
// 隐藏内核部分版本号
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.FormatPaint,
|
||||
icon = Icons.Filled.VisibilityOff,
|
||||
title = stringResource(R.string.hide_kernel_kernelsu_version),
|
||||
summary = stringResource(R.string.hide_kernel_kernelsu_version_summary),
|
||||
checked = isHideVersion
|
||||
) {
|
||||
onHideVersionChange(it)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isExpanded,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
) {
|
||||
// 模块数量等信息
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.VisibilityOff,
|
||||
title = stringResource(R.string.hide_other_info),
|
||||
summary = stringResource(R.string.hide_other_info_summary),
|
||||
checked = isHideOtherInfo
|
||||
) {
|
||||
onHideOtherInfoChange(it)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isExpanded,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
) {
|
||||
// SuSFS 状态信息
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.VisibilityOff,
|
||||
title = stringResource(R.string.hide_susfs_status),
|
||||
summary = stringResource(R.string.hide_susfs_status_summary),
|
||||
checked = isHideSusfsStatus
|
||||
) {
|
||||
onHideSusfsStatusChange(it)
|
||||
}
|
||||
}
|
||||
|
||||
// region SUSFS 配置(仅在支持时显示)
|
||||
val suSFS = getSuSFS()
|
||||
@@ -285,10 +370,12 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
// 只在未启用动态颜色时显示主题色选择
|
||||
if (!useDynamicColor) {
|
||||
AnimatedVisibility(
|
||||
visible = !useDynamicColor
|
||||
) {
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Default.Palette, null) },
|
||||
headlineContent = { Text("主题颜色") },
|
||||
headlineContent = { Text(stringResource(R.string.theme_color)) },
|
||||
supportingContent = {
|
||||
val currentThemeName = when (ThemeConfig.currentTheme) {
|
||||
is ThemeColors.Default -> stringResource(R.string.color_default)
|
||||
@@ -373,10 +460,11 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
context.saveCustomBackground(null)
|
||||
isCustomBackgroundEnabled = false
|
||||
CardConfig.cardElevation = CardConfig.defaultElevation
|
||||
CardConfig.cardAlpha = 1f
|
||||
CardConfig.cardAlpha = 0.45f
|
||||
CardConfig.isCustomAlphaSet = false
|
||||
CardConfig.isCustomBackgroundEnabled = false
|
||||
saveCardConfig(context)
|
||||
cardAlpha = 0.65f
|
||||
cardAlpha = 0.35f
|
||||
themeMode = 0
|
||||
context.saveThemeMode(null)
|
||||
CardConfig.isUserDarkModeEnabled = false
|
||||
@@ -387,9 +475,11 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (ThemeConfig.customBackgroundUri != null && showCardSettings) {
|
||||
// 透明度 Slider
|
||||
AnimatedVisibility(
|
||||
visible = ThemeConfig.customBackgroundUri != null && showCardSettings,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
) {
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.Opacity, null) },
|
||||
headlineContent = { Text(stringResource(R.string.settings_card_alpha)) },
|
||||
@@ -419,7 +509,11 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = ThemeConfig.customBackgroundUri != null && showCardSettings,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
){
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.DarkMode, null) },
|
||||
headlineContent = { Text(stringResource(R.string.theme_mode)) },
|
||||
@@ -428,7 +522,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
showThemeModeDialog = true
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// 主题模式选择对话框
|
||||
if (showThemeModeDialog) {
|
||||
@@ -490,8 +584,8 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false): SliderColors {
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -7,30 +7,14 @@ import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Undo
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -49,12 +33,9 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
import com.maxkeppeker.sheets.core.models.base.Header
|
||||
import androidx.core.content.edit
|
||||
import com.maxkeppeker.sheets.core.models.base.IconSource
|
||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||
import com.maxkeppeler.sheets.list.ListDialog
|
||||
import com.maxkeppeler.sheets.list.models.ListOption
|
||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
||||
@@ -65,24 +46,16 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import shirkneko.zako.sukisu.BuildConfig
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.AboutDialog
|
||||
import shirkneko.zako.sukisu.ui.component.ConfirmResult
|
||||
import shirkneko.zako.sukisu.ui.component.DialogHandle
|
||||
import shirkneko.zako.sukisu.ui.component.SwitchItem
|
||||
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
|
||||
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
|
||||
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
|
||||
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
|
||||
import shirkneko.zako.sukisu.ui.util.getBugreportFile
|
||||
import com.sukisu.ultra.BuildConfig
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.*
|
||||
import com.sukisu.ultra.ui.component.*
|
||||
import com.sukisu.ultra.ui.theme.*
|
||||
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||
import com.sukisu.ultra.ui.util.getBugreportFile
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import shirkneko.zako.sukisu.ui.theme.CardConfig
|
||||
import androidx.core.content.edit
|
||||
|
||||
|
||||
/**
|
||||
@@ -96,6 +69,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
// region 界面基础设置
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
|
||||
// endregion
|
||||
|
||||
Scaffold(
|
||||
@@ -111,7 +85,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
AboutDialog(it)
|
||||
}
|
||||
val loadingDialog = rememberLoadingDialog()
|
||||
val shrinkDialog = rememberConfirmDialog()
|
||||
// endregion
|
||||
|
||||
Column(
|
||||
@@ -140,11 +113,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
loadingDialog.hide()
|
||||
snackBarHost.showSnackbar(context.getString(R.string.log_saved))
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
// region 配置项列表
|
||||
// 配置文件模板入口
|
||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
||||
if (ksuIsValid) {
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
||||
headlineContent = { Text(profileTemplate) },
|
||||
@@ -153,10 +126,13 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
// 卸载模块开关
|
||||
var umountChecked by rememberSaveable {
|
||||
mutableStateOf(Natives.isDefaultUmountModules())
|
||||
}
|
||||
|
||||
if (ksuIsValid) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.FolderDelete,
|
||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||
@@ -167,7 +143,9 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
umountChecked = it
|
||||
}
|
||||
}
|
||||
}
|
||||
// SU 禁用开关(仅在兼容版本显示)
|
||||
if (ksuIsValid) {
|
||||
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
||||
var isSuDisabled by rememberSaveable {
|
||||
mutableStateOf(!Natives.isSuEnabled())
|
||||
@@ -184,6 +162,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
@@ -209,6 +188,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
prefs.getBoolean("enable_web_debugging", false)
|
||||
)
|
||||
}
|
||||
if (Natives.isKsuValid(ksuApp.packageName)) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.DeveloperMode,
|
||||
title = stringResource(id = R.string.enable_web_debugging),
|
||||
@@ -218,12 +198,13 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
prefs.edit { putBoolean("enable_web_debugging", it) }
|
||||
enableWebDebugging = it
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
// 更多设置
|
||||
val newButtonTitle = stringResource(id = R.string.more_settings)
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.ExpandMore,
|
||||
Icons.Filled.Settings,
|
||||
contentDescription = newButtonTitle
|
||||
)
|
||||
},
|
||||
@@ -452,20 +433,73 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
||||
}
|
||||
|
||||
var selection = UninstallType.NONE
|
||||
ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
||||
ThemeConfig.currentTheme.ButtonContrast
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
dismiss()
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.settings_uninstall))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
listOptions.forEachIndexed { index, option ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
selection = options[index]
|
||||
}
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = options[index].icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
Column {
|
||||
Text(text = option.titleText)
|
||||
option.subtitleText?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
androidx.compose.material3.TextButton(
|
||||
onClick = {
|
||||
if (selection != UninstallType.NONE) {
|
||||
onSelected(selection)
|
||||
}
|
||||
}, onCloseRequest = {
|
||||
dismiss()
|
||||
}), header = Header.Default(
|
||||
title = stringResource(R.string.settings_uninstall),
|
||||
), selection = ListSelection.Single(
|
||||
showRadioButtons = false,
|
||||
options = listOptions,
|
||||
) { index, _ ->
|
||||
selection = options[index]
|
||||
})
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
androidx.compose.material3.TextButton(
|
||||
onClick = {
|
||||
dismiss()
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
tonalElevation = getCardElevation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.sukisu.ultra.R
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -31,11 +32,10 @@ import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.SearchAppBar
|
||||
import shirkneko.zako.sukisu.ui.util.ModuleModify
|
||||
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.component.SearchAppBar
|
||||
import com.sukisu.ultra.ui.util.ModuleModify
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>
|
||||
@@ -1,5 +1,7 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -44,11 +46,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -59,8 +60,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient
|
||||
import com.ramcosta.composedestinations.result.getOr
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -77,7 +79,11 @@ fun AppProfileTemplateScreen(
|
||||
val viewModel = viewModel<TemplateViewModel>()
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
||||
ThemeConfig.currentTheme.ButtonContrast
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.templateList.isEmpty()) {
|
||||
@@ -94,8 +100,8 @@ fun AppProfileTemplateScreen(
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val context = LocalContext.current
|
||||
val clipboardManager = context.getSystemService<ClipboardManager>()
|
||||
val showToast = fun(msg: String) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
|
||||
@@ -107,21 +113,21 @@ fun AppProfileTemplateScreen(
|
||||
scope.launch { viewModel.fetchTemplates(true) }
|
||||
},
|
||||
onImport = {
|
||||
clipboardManager.getText()?.text?.let {
|
||||
if (it.isEmpty()) {
|
||||
showToast(context.getString(R.string.app_profile_template_import_empty))
|
||||
return@let
|
||||
}
|
||||
scope.launch {
|
||||
val clipboardText = clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString()
|
||||
if (clipboardText.isNullOrEmpty()) {
|
||||
showToast(context.getString(R.string.app_profile_template_import_empty))
|
||||
return@launch
|
||||
}
|
||||
viewModel.importTemplates(
|
||||
it, {
|
||||
clipboardText,
|
||||
{
|
||||
showToast(context.getString(R.string.app_profile_template_import_success))
|
||||
viewModel.fetchTemplates(false)
|
||||
},
|
||||
showToast
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onExport = {
|
||||
scope.launch {
|
||||
@@ -129,8 +135,8 @@ fun AppProfileTemplateScreen(
|
||||
{
|
||||
showToast(context.getString(R.string.app_profile_template_export_empty))
|
||||
}
|
||||
) {
|
||||
clipboardManager.setText(AnnotatedString(it))
|
||||
) { text ->
|
||||
clipboardManager?.setPrimaryClip(ClipData.newPlainText("", text))
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
@@ -47,14 +47,14 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
|
||||
import shirkneko.zako.sukisu.ui.util.deleteAppProfileTemplate
|
||||
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
|
||||
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
|
||||
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
|
||||
import shirkneko.zako.sukisu.ui.viewmodel.toJSON
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
|
||||
import com.sukisu.ultra.ui.util.deleteAppProfileTemplate
|
||||
import com.sukisu.ultra.ui.util.getAppProfileTemplate
|
||||
import com.sukisu.ultra.ui.util.setAppProfileTemplate
|
||||
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
|
||||
import com.sukisu.ultra.ui.viewmodel.toJSON
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.theme
|
||||
package com.sukisu.ultra.ui.theme
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
@@ -15,7 +15,7 @@ import androidx.compose.ui.unit.dp
|
||||
object CardConfig {
|
||||
val defaultElevation: Dp = 0.dp
|
||||
|
||||
var cardAlpha by mutableStateOf(1f)
|
||||
var cardAlpha by mutableStateOf(0.45f)
|
||||
var cardElevation by mutableStateOf(defaultElevation)
|
||||
var isShadowEnabled by mutableStateOf(true)
|
||||
var isCustomAlphaSet by mutableStateOf(false)
|
||||
@@ -38,7 +38,7 @@ object CardConfig {
|
||||
|
||||
fun load(context: Context) {
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
cardAlpha = prefs.getFloat("card_alpha", 1f)
|
||||
cardAlpha = prefs.getFloat("card_alpha", 0.45f)
|
||||
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation
|
||||
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
|
||||
isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false)
|
||||
@@ -53,7 +53,7 @@ object CardConfig {
|
||||
|
||||
fun setDarkModeDefaults() {
|
||||
if (!isCustomAlphaSet) {
|
||||
cardAlpha = 0.5f
|
||||
cardAlpha = 0.35f
|
||||
cardElevation = 0.dp
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.theme
|
||||
package com.sukisu.ultra.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.theme
|
||||
package com.sukisu.ultra.ui.theme
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
@@ -27,11 +27,14 @@ import androidx.compose.ui.zIndex
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.unit.dp
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
||||
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
||||
|
||||
object ThemeConfig {
|
||||
var customBackgroundUri by mutableStateOf<Uri?>(null)
|
||||
@@ -43,23 +46,23 @@ object ThemeConfig {
|
||||
@Composable
|
||||
private fun getDarkColorScheme() = darkColorScheme(
|
||||
primary = ThemeConfig.currentTheme.Primary.copy(alpha = 0.8f),
|
||||
onPrimary = Color.White,
|
||||
onPrimary = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
|
||||
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer.copy(alpha = 0.15f),
|
||||
onPrimaryContainer = Color.White,
|
||||
onPrimaryContainer = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
|
||||
secondary = ThemeConfig.currentTheme.Secondary.copy(alpha = 0.8f),
|
||||
onSecondary = Color.White,
|
||||
onSecondary = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f),
|
||||
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer.copy(alpha = 0.15f),
|
||||
onSecondaryContainer = Color.White,
|
||||
onSecondaryContainer = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f),
|
||||
tertiary = ThemeConfig.currentTheme.Tertiary.copy(alpha = 0.8f),
|
||||
onTertiary = Color.White,
|
||||
onTertiary = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f),
|
||||
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f),
|
||||
onTertiaryContainer = Color.White,
|
||||
onTertiaryContainer = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f),
|
||||
background = Color.Transparent,
|
||||
surface = Color.Transparent,
|
||||
onBackground = Color.White.copy(alpha = 0.87f),
|
||||
onSurface = Color.White.copy(alpha = 0.87f),
|
||||
onBackground = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
|
||||
onSurface = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
|
||||
surfaceVariant = Color(0xFF2F2F2F),
|
||||
onSurfaceVariant = Color.White.copy(alpha = 0.78f),
|
||||
onSurfaceVariant = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
|
||||
outline = Color.White.copy(alpha = 0.12f),
|
||||
outlineVariant = Color.White.copy(alpha = 0.12f)
|
||||
)
|
||||
@@ -88,29 +91,6 @@ private fun getLightColorScheme() = lightColorScheme(
|
||||
outlineVariant = Color.Black.copy(alpha = 0.12f)
|
||||
)
|
||||
|
||||
// 复制图片到应用内部存储
|
||||
fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
|
||||
try {
|
||||
val contentResolver: ContentResolver = contentResolver
|
||||
val inputStream: InputStream = contentResolver.openInputStream(uri)!!
|
||||
val fileName = "custom_background.jpg"
|
||||
val file = File(filesDir, fileName)
|
||||
val outputStream = FileOutputStream(file)
|
||||
val buffer = ByteArray(4 * 1024)
|
||||
var read: Int
|
||||
while (inputStream.read(buffer).also { read = it } != -1) {
|
||||
outputStream.write(buffer, 0, read)
|
||||
}
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
inputStream.close()
|
||||
return Uri.fromFile(file)
|
||||
} catch (e: Exception) {
|
||||
Log.e("ImageCopy", "Failed to copy image: ${e.message}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun KernelSUTheme(
|
||||
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
|
||||
@@ -131,7 +111,6 @@ fun KernelSUTheme(
|
||||
if (darkTheme) {
|
||||
val originalScheme = dynamicDarkColorScheme(context)
|
||||
originalScheme.copy(
|
||||
// 调整按钮相关颜色
|
||||
primary = adjustColor(originalScheme.primary),
|
||||
onPrimary = adjustColor(originalScheme.onPrimary),
|
||||
primaryContainer = adjustColor(originalScheme.primaryContainer),
|
||||
@@ -242,6 +221,48 @@ fun KernelSUTheme(
|
||||
}
|
||||
}
|
||||
|
||||
// 复制图片到应用内部存储
|
||||
private fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
|
||||
try {
|
||||
val contentResolver: ContentResolver = contentResolver
|
||||
val inputStream: InputStream = contentResolver.openInputStream(uri)!!
|
||||
val fileName = "custom_background.jpg"
|
||||
val file = File(filesDir, fileName)
|
||||
val outputStream = FileOutputStream(file)
|
||||
val buffer = ByteArray(4 * 1024)
|
||||
var read: Int
|
||||
while (inputStream.read(buffer).also { read = it } != -1) {
|
||||
outputStream.write(buffer, 0, read)
|
||||
}
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
inputStream.close()
|
||||
return Uri.fromFile(file)
|
||||
} catch (e: Exception) {
|
||||
Log.e("ImageCopy", "Failed to copy image: ${e.message}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 保存变换后的背景图片到应用内部存储并更新配置
|
||||
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
|
||||
val finalUri = if (transformation != null) {
|
||||
saveTransformedBackground(uri, transformation)
|
||||
} else {
|
||||
copyImageToInternalStorage(uri)
|
||||
}
|
||||
|
||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||
.edit {
|
||||
putString("custom_background", finalUri?.toString())
|
||||
}
|
||||
|
||||
ThemeConfig.customBackgroundUri = finalUri
|
||||
CardConfig.cardElevation = 0.dp
|
||||
CardConfig.isCustomBackgroundEnabled = true
|
||||
}
|
||||
|
||||
// 保存背景图片到应用内部存储并更新配置
|
||||
fun Context.saveCustomBackground(uri: Uri?) {
|
||||
val newUri = uri?.let { copyImageToInternalStorage(it) }
|
||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||
@@ -249,6 +270,10 @@ fun Context.saveCustomBackground(uri: Uri?) {
|
||||
putString("custom_background", newUri?.toString())
|
||||
}
|
||||
ThemeConfig.customBackgroundUri = newUri
|
||||
if (uri != null) {
|
||||
CardConfig.cardElevation = 0.dp
|
||||
CardConfig.isCustomBackgroundEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.loadCustomBackground() {
|
||||
@@ -340,3 +365,11 @@ private fun adjustColor(color: Color): Color {
|
||||
}
|
||||
return color.copy(luminance)
|
||||
}
|
||||
|
||||
private fun mixColors(color1: Color, color2: Color, ratio: Float): Color {
|
||||
val r = (color1.red * ratio + color2.red * (1 - ratio))
|
||||
val g = (color1.green * ratio + color2.green * (1 - ratio))
|
||||
val b = (color1.blue * ratio + color2.blue * (1 - ratio))
|
||||
val a = (color1.alpha * ratio + color2.alpha * (1 - ratio))
|
||||
return Color(r, g, b, a)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.theme
|
||||
package com.sukisu.ultra.ui.theme
|
||||
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Matrix
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import androidx.core.graphics.createBitmap
|
||||
|
||||
data class BackgroundTransformation(
|
||||
val scale: Float = 1f,
|
||||
val offsetX: Float = 0f,
|
||||
val offsetY: Float = 0f
|
||||
)
|
||||
|
||||
fun Context.getImageBitmap(uri: Uri): Bitmap? {
|
||||
return try {
|
||||
val contentResolver: ContentResolver = contentResolver
|
||||
val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream.close()
|
||||
bitmap
|
||||
} catch (e: Exception) {
|
||||
Log.e("BackgroundUtils", "Failed to get image bitmap: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.applyTransformationToBitmap(bitmap: Bitmap, transformation: BackgroundTransformation): Bitmap {
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
|
||||
// 创建与屏幕比例相同的目标位图
|
||||
val displayMetrics = resources.displayMetrics
|
||||
val screenWidth = displayMetrics.widthPixels
|
||||
val screenHeight = displayMetrics.heightPixels
|
||||
val screenRatio = screenHeight.toFloat() / screenWidth.toFloat()
|
||||
|
||||
// 计算目标宽高
|
||||
val targetWidth: Int
|
||||
val targetHeight: Int
|
||||
if (width.toFloat() / height.toFloat() > screenRatio) {
|
||||
targetHeight = height
|
||||
targetWidth = (height / screenRatio).toInt()
|
||||
} else {
|
||||
targetWidth = width
|
||||
targetHeight = (width * screenRatio).toInt()
|
||||
}
|
||||
|
||||
// 创建与目标相同大小的位图
|
||||
val scaledBitmap = createBitmap(targetWidth, targetHeight)
|
||||
val canvas = Canvas(scaledBitmap)
|
||||
|
||||
val matrix = Matrix()
|
||||
|
||||
// 确保缩放值有效
|
||||
val safeScale = maxOf(0.1f, transformation.scale)
|
||||
matrix.postScale(safeScale, safeScale)
|
||||
|
||||
// 计算中心点
|
||||
val centerX = targetWidth / 2f
|
||||
val centerY = targetHeight / 2f
|
||||
|
||||
// 计算偏移量,确保不会出现负最大值的问题
|
||||
val widthDiff = (bitmap.width * safeScale - targetWidth)
|
||||
val heightDiff = (bitmap.height * safeScale - targetHeight)
|
||||
|
||||
// 安全计算偏移量边界
|
||||
val maxOffsetX = maxOf(0f, widthDiff / 2)
|
||||
val maxOffsetY = maxOf(0f, heightDiff / 2)
|
||||
|
||||
// 限制偏移范围
|
||||
val safeOffsetX = if (maxOffsetX > 0)
|
||||
transformation.offsetX.coerceIn(-maxOffsetX, maxOffsetX) else 0f
|
||||
val safeOffsetY = if (maxOffsetY > 0)
|
||||
transformation.offsetY.coerceIn(-maxOffsetY, maxOffsetY) else 0f
|
||||
|
||||
// 应用偏移量到矩阵
|
||||
val translationX = -widthDiff / 2 + safeOffsetX
|
||||
val translationY = -heightDiff / 2 + safeOffsetY
|
||||
|
||||
matrix.postTranslate(translationX, translationY)
|
||||
|
||||
// 将原始位图绘制到新位图上
|
||||
canvas.drawBitmap(bitmap, matrix, null)
|
||||
|
||||
return scaledBitmap
|
||||
}
|
||||
|
||||
fun Context.saveTransformedBackground(uri: Uri, transformation: BackgroundTransformation): Uri? {
|
||||
try {
|
||||
val bitmap = getImageBitmap(uri) ?: return null
|
||||
val transformedBitmap = applyTransformationToBitmap(bitmap, transformation)
|
||||
|
||||
val fileName = "custom_background_transformed.jpg"
|
||||
val file = File(filesDir, fileName)
|
||||
val outputStream = FileOutputStream(file)
|
||||
|
||||
transformedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
return Uri.fromFile(file)
|
||||
} catch (e: Exception) {
|
||||
Log.e("BackgroundUtils", "Failed to save transformed image: ${e.message}", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.DownloadManager
|
||||
@@ -12,7 +12,8 @@ import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.core.content.ContextCompat
|
||||
import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo
|
||||
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||
import androidx.core.net.toUri
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -42,14 +43,14 @@ fun download(
|
||||
onDownloading()
|
||||
return
|
||||
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
||||
onDownloaded(Uri.parse(localUri))
|
||||
onDownloaded(localUri.toUri())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val request = DownloadManager.Request(Uri.parse(url))
|
||||
val request = DownloadManager.Request(url.toUri())
|
||||
.setDestinationInExternalPublicDir(
|
||||
Environment.DIRECTORY_DOWNLOADS,
|
||||
fileName
|
||||
@@ -63,7 +64,6 @@ fun download(
|
||||
}
|
||||
|
||||
fun checkNewVersion(): LatestVersionInfo {
|
||||
// 改为新的 release 接口
|
||||
val url = "https://api.github.com/repos/ShirkNeko/SukiSU-Ultra/releases/latest"
|
||||
val defaultValue = LatestVersionInfo()
|
||||
return runCatching {
|
||||
@@ -141,7 +141,7 @@ fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
||||
val uri = cursor.getString(
|
||||
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
|
||||
)
|
||||
onDownloaded(Uri.parse(uri))
|
||||
onDownloaded(uri.toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.util;
|
||||
package com.sukisu.ultra.ui.util;
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
@@ -16,9 +16,9 @@ import com.topjohnwu.superuser.ShellUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import shirkneko.zako.sukisu.BuildConfig
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.ksuApp
|
||||
import com.sukisu.ultra.BuildConfig
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.io.File
|
||||
private const val TAG = "KsuCli"
|
||||
|
||||
private fun getKsuDaemonPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomk.so"
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozako.so"
|
||||
}
|
||||
|
||||
object KsuCli {
|
||||
@@ -436,8 +436,8 @@ fun restartApp(packageName: String) {
|
||||
launchApp(packageName)
|
||||
}
|
||||
|
||||
private fun getSuSFSDaemonPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomksd.so"
|
||||
fun getSuSFSDaemonPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozakozako.so"
|
||||
}
|
||||
|
||||
fun getSuSFS(): String {
|
||||
@@ -481,7 +481,7 @@ fun susfsSUS_SU_Mode(): String {
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getKpmmgrPath(): String {
|
||||
fun getKpmmgrPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libkpmmgr.so"
|
||||
}
|
||||
|
||||
@@ -489,48 +489,62 @@ private fun getKpmmgrPath(): String {
|
||||
fun loadKpmModule(path: String, args: String? = null): String {
|
||||
val shell = getRootShell()
|
||||
val cmd = "${getKpmmgrPath()} load $path ${args ?: ""}"
|
||||
val result = ShellUtils.fastCmd(shell, cmd)
|
||||
return result
|
||||
return ShellUtils.fastCmd(shell, cmd)
|
||||
}
|
||||
|
||||
fun unloadKpmModule(name: String): String {
|
||||
val shell = getRootShell()
|
||||
val cmd = "${getKpmmgrPath()} unload $name"
|
||||
val result = ShellUtils.fastCmd(shell, cmd)
|
||||
return result
|
||||
return ShellUtils.fastCmd(shell, cmd)
|
||||
}
|
||||
|
||||
fun getKpmModuleCount(): String {
|
||||
fun getKpmModuleCount(): Int {
|
||||
val shell = getRootShell()
|
||||
val cmd = "${getKpmmgrPath()} num"
|
||||
val result = ShellUtils.fastCmd(shell, cmd)
|
||||
return result
|
||||
return result.trim().toIntOrNull() ?: 0
|
||||
}
|
||||
|
||||
fun runCmd(shell : Shell, cmd : String) : String {
|
||||
return shell.newJob()
|
||||
.add(cmd)
|
||||
.to(mutableListOf<String>(), null)
|
||||
.exec().out
|
||||
.joinToString("\n")
|
||||
}
|
||||
|
||||
fun listKpmModules(): String {
|
||||
val shell = getRootShell()
|
||||
val cmd = "${getKpmmgrPath()} list"
|
||||
val result = ShellUtils.fastCmd(shell, cmd)
|
||||
return result
|
||||
return try {
|
||||
runCmd(shell, cmd).trim()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to list KPM modules", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
fun getKpmModuleInfo(name: String): String {
|
||||
val shell = getRootShell()
|
||||
val cmd = "${getKpmmgrPath()} info $name"
|
||||
val result = ShellUtils.fastCmd(shell, cmd)
|
||||
return result
|
||||
return try {
|
||||
runCmd(shell, cmd).trim()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get KPM module info: $name", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
fun controlKpmModule(name: String, args: String? = null): String {
|
||||
fun controlKpmModule(name: String, args: String? = null): Int {
|
||||
val shell = getRootShell()
|
||||
val cmd = "${getKpmmgrPath()} control $name ${args ?: ""}"
|
||||
val result = ShellUtils.fastCmd(shell, cmd)
|
||||
return result
|
||||
val result = runCmd(shell, cmd)
|
||||
return result.trim().toIntOrNull() ?: -1
|
||||
}
|
||||
|
||||
fun printKpmModules(): String {
|
||||
fun getKpmVersion(): String {
|
||||
val shell = getRootShell()
|
||||
val cmd = "${getKpmmgrPath()} print"
|
||||
val cmd = "${getKpmmgrPath()} version"
|
||||
val result = ShellUtils.fastCmd(shell, cmd)
|
||||
return result
|
||||
return result.trim()
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package shirkneko.zako.sukisu.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.system.Os
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.ui.screen.getManagerVersion
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.screen.getManagerVersion
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
@@ -24,7 +24,7 @@ fun getBugreportFile(context: Context): File {
|
||||
val pstoreFile = File(bugreportDir, "pstore.tar.gz")
|
||||
// Xiaomi/Readmi devices have diag in /data/vendor/diag
|
||||
val diagFile = File(bugreportDir, "diag.tar.gz")
|
||||
val opulsFile = File(bugreportDir, "opuls.tar.gz")
|
||||
val oplusFile = File(bugreportDir, "oplus.tar.gz")
|
||||
val bootlogFile = File(bugreportDir, "bootlog.tar.gz")
|
||||
val mountsFile = File(bugreportDir, "mounts.txt")
|
||||
val fileSystemsFile = File(bugreportDir, "filesystems.txt")
|
||||
@@ -46,7 +46,7 @@ fun getBugreportFile(context: Context): File {
|
||||
shell.newJob().add("tar -czf ${dropboxFile.absolutePath} -C /data/system/dropbox .").exec()
|
||||
shell.newJob().add("tar -czf ${pstoreFile.absolutePath} -C /sys/fs/pstore .").exec()
|
||||
shell.newJob().add("tar -czf ${diagFile.absolutePath} -C /data/vendor/diag . --exclude=./minidump.gz").exec()
|
||||
shell.newJob().add("tar -czf ${opulsFile.absolutePath} -C /mnt/oplus/op2/media/log/boot_log/ .").exec()
|
||||
shell.newJob().add("tar -czf ${oplusFile.absolutePath} -C /mnt/oplus/op2/media/log/boot_log/ .").exec()
|
||||
shell.newJob().add("tar -czf ${bootlogFile.absolutePath} -C /data/adb/ksu/log .").exec()
|
||||
|
||||
shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec()
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
@@ -16,7 +16,7 @@ import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import shirkneko.zako.sukisu.R
|
||||
import com.sukisu.ultra.R
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
@@ -1,9 +1,9 @@
|
||||
package shirkneko.zako.sukisu.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import shirkneko.zako.sukisu.R
|
||||
import com.sukisu.ultra.R
|
||||
|
||||
@Composable
|
||||
fun getSELinuxStatus(): String {
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.util.module
|
||||
package com.sukisu.ultra.ui.util.module
|
||||
|
||||
data class LatestVersionInfo(
|
||||
val versionCode : Int = 0,
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.sukisu.ultra.ui.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
|
||||
class KpmViewModel : ViewModel() {
|
||||
var moduleList by mutableStateOf(emptyList<ModuleInfo>())
|
||||
private set
|
||||
|
||||
var search by mutableStateOf("")
|
||||
internal set
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var currentModuleDetail by mutableStateOf("")
|
||||
private set
|
||||
|
||||
fun fetchModuleList() {
|
||||
viewModelScope.launch {
|
||||
isRefreshing = true
|
||||
try {
|
||||
val moduleCount = getKpmModuleCount()
|
||||
Log.d("KsuCli", "Module count: $moduleCount")
|
||||
|
||||
moduleList = getAllKpmModuleInfo()
|
||||
|
||||
// 获取 KPM 版本信息
|
||||
val kpmVersion = getKpmVersion()
|
||||
Log.d("KsuCli", "KPM Version: $kpmVersion")
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "获取模块列表失败", e)
|
||||
} finally {
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAllKpmModuleInfo(): List<ModuleInfo> {
|
||||
val result = mutableListOf<ModuleInfo>()
|
||||
try {
|
||||
val str = listKpmModules()
|
||||
val moduleNames = str
|
||||
.split("\n")
|
||||
.filter { it.isNotBlank() }
|
||||
|
||||
for (name in moduleNames) {
|
||||
try {
|
||||
val moduleInfo = parseModuleInfo(name)
|
||||
moduleInfo?.let { result.add(it) }
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Error processing module $name", e)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to get module list", e)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun parseModuleInfo(name: String): ModuleInfo? {
|
||||
val info = getKpmModuleInfo(name)
|
||||
if (info.isBlank()) return null
|
||||
|
||||
val properties = info.lineSequence()
|
||||
.filter { line ->
|
||||
val trimmed = line.trim()
|
||||
trimmed.isNotEmpty() && !trimmed.startsWith("#")
|
||||
}
|
||||
.mapNotNull { line ->
|
||||
line.split("=", limit = 2).let { parts ->
|
||||
when (parts.size) {
|
||||
2 -> parts[0].trim() to parts[1].trim()
|
||||
1 -> parts[0].trim() to ""
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
.toMap()
|
||||
|
||||
return ModuleInfo(
|
||||
id = name,
|
||||
name = properties["name"] ?: name,
|
||||
version = properties["version"] ?: "",
|
||||
author = properties["author"] ?: "",
|
||||
description = properties["description"] ?: "",
|
||||
args = properties["args"] ?: "",
|
||||
enabled = true,
|
||||
hasAction = true
|
||||
)
|
||||
}
|
||||
|
||||
fun loadModuleDetail(moduleId: String) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
currentModuleDetail = withContext(Dispatchers.IO) {
|
||||
getKpmModuleInfo(moduleId)
|
||||
}
|
||||
Log.d("KsuCli", "Module detail loaded: $currentModuleDetail")
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to load module detail", e)
|
||||
currentModuleDetail = "Error: ${e.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var showInputDialog by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var selectedModuleId by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
var inputArgs by mutableStateOf("")
|
||||
private set
|
||||
|
||||
fun showInputDialog(moduleId: String) {
|
||||
selectedModuleId = moduleId
|
||||
showInputDialog = true
|
||||
}
|
||||
|
||||
fun hideInputDialog() {
|
||||
showInputDialog = false
|
||||
selectedModuleId = null
|
||||
inputArgs = ""
|
||||
}
|
||||
|
||||
fun updateInputArgs(args: String) {
|
||||
inputArgs = args
|
||||
}
|
||||
|
||||
fun executeControl(): Int {
|
||||
val moduleId = selectedModuleId ?: return -1
|
||||
val result = controlKpmModule(moduleId, inputArgs)
|
||||
hideInputDialog()
|
||||
return result
|
||||
}
|
||||
|
||||
data class ModuleInfo(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val version: String,
|
||||
val author: String,
|
||||
val description: String,
|
||||
val args: String,
|
||||
val enabled: Boolean,
|
||||
val hasAction: Boolean
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.viewmodel
|
||||
package com.sukisu.ultra.ui.viewmodel
|
||||
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
@@ -10,8 +10,8 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import shirkneko.zako.sukisu.ui.util.HanziToPinyin
|
||||
import shirkneko.zako.sukisu.ui.util.listModules
|
||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||
import com.sukisu.ultra.ui.util.listModules
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.text.Collator
|
||||
@@ -40,13 +40,6 @@ class ModuleViewModel : ViewModel() {
|
||||
val dirId: String, // real module id (dir name)
|
||||
)
|
||||
|
||||
data class ModuleUpdateInfo(
|
||||
val version: String,
|
||||
val versionCode: Int,
|
||||
val zipUrl: String,
|
||||
val changelog: String,
|
||||
)
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
var search by mutableStateOf("")
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.viewmodel
|
||||
package com.sukisu.ultra.ui.viewmodel
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
@@ -18,12 +18,12 @@ import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import shirkneko.zako.sukisu.IKsuInterface
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.ksuApp
|
||||
import shirkneko.zako.sukisu.ui.KsuService
|
||||
import shirkneko.zako.sukisu.ui.util.HanziToPinyin
|
||||
import shirkneko.zako.sukisu.ui.util.KsuCli
|
||||
import com.sukisu.zako.IKsuInterface
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.sukisu.ultra.ui.KsuService
|
||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||
import com.sukisu.ultra.ui.util.KsuCli
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.viewmodel
|
||||
package com.sukisu.ultra.ui.viewmodel
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
@@ -10,12 +10,12 @@ import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.profile.Capabilities
|
||||
import shirkneko.zako.sukisu.profile.Groups
|
||||
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
|
||||
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
|
||||
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.profile.Capabilities
|
||||
import com.sukisu.ultra.profile.Groups
|
||||
import com.sukisu.ultra.ui.util.getAppProfileTemplate
|
||||
import com.sukisu.ultra.ui.util.listAppProfileTemplates
|
||||
import com.sukisu.ultra.ui.util.setAppProfileTemplate
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONArray
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package shirkneko.zako.sukisu.ui.webui;
|
||||
package com.sukisu.ultra.ui.webui;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.webui;
|
||||
package com.sukisu.ultra.ui.webui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.webui
|
||||
package com.sukisu.ultra.ui.webui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
@@ -16,7 +16,7 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.webkit.WebViewAssetLoader
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import shirkneko.zako.sukisu.ui.util.createRootShell
|
||||
import com.sukisu.ultra.ui.util.createRootShell
|
||||
import java.io.File
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.ui.webui
|
||||
package com.sukisu.ultra.ui.webui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
@@ -14,11 +14,13 @@ import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import shirkneko.zako.sukisu.ui.util.createRootShell
|
||||
import shirkneko.zako.sukisu.ui.util.listModules
|
||||
import shirkneko.zako.sukisu.ui.util.withNewRootShell
|
||||
import com.sukisu.ultra.ui.util.createRootShell
|
||||
import com.sukisu.ultra.ui.util.listModules
|
||||
import com.sukisu.ultra.ui.util.withNewRootShell
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import com.sukisu.ultra.ui.util.controlKpmModule
|
||||
import com.sukisu.ultra.ui.util.listKpmModules
|
||||
import java.io.File
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@@ -197,6 +199,18 @@ class WebViewInterface(
|
||||
}
|
||||
return currentModuleInfo.toString()
|
||||
}
|
||||
|
||||
// =================== KPM支持 =============================
|
||||
|
||||
@JavascriptInterface
|
||||
fun listAllKpm() : String {
|
||||
return listKpmModules()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun controlKpm(name: String, args: String) : Int {
|
||||
return controlKpmModule(name, args)
|
||||
}
|
||||
}
|
||||
|
||||
fun hideSystemUI(window: Window) =
|
||||
@@ -1,4 +1,4 @@
|
||||
package shirkneko.zako.sukisu.utils
|
||||
package com.sukisu.ultra.utils
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.sukisu.ultra;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.sukisu.ultra.ui.util.KsuCli;
|
||||
|
||||
public class UltraShellHelper {
|
||||
public static String runCmd(String cmds) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(String str : KsuCli.INSTANCE.getGLOBAL_MNT_SHELL()
|
||||
.newJob()
|
||||
.add(cmds)
|
||||
.to(new ArrayList<>(), null)
|
||||
.exec()
|
||||
.getOut()) {
|
||||
sb.append(str).append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static boolean isPathExists(String path) {
|
||||
return runCmd("file " + path).contains("No such file or directory");
|
||||
}
|
||||
|
||||
public static void CopyFileTo(String path, String target) {
|
||||
runCmd("cp -f " + path + " " + target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.sukisu.ultra;
|
||||
|
||||
import static com.sukisu.ultra.ui.util.KsuCliKt.getKpmmgrPath;
|
||||
import static com.sukisu.ultra.ui.util.KsuCliKt.getSuSFSDaemonPath;
|
||||
|
||||
public class UltraToolInstall {
|
||||
private static final String OUTSIDE_KPMMGR_PATH = "/data/adb/ksu/bin/kpmmgr";
|
||||
private static final String OUTSIDE_SUSFSD_PATH = "/data/adb/ksu/bin/susfsd";
|
||||
public static void tryToInstall() {
|
||||
String kpmmgrPath = getKpmmgrPath();
|
||||
if (UltraShellHelper.isPathExists(OUTSIDE_KPMMGR_PATH)) {
|
||||
UltraShellHelper.CopyFileTo(kpmmgrPath, OUTSIDE_KPMMGR_PATH);
|
||||
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_KPMMGR_PATH);
|
||||
}
|
||||
String SuSFSDaemonPath = getSuSFSDaemonPath();
|
||||
if (UltraShellHelper.isPathExists(OUTSIDE_SUSFSD_PATH)) {
|
||||
UltraShellHelper.CopyFileTo(SuSFSDaemonPath, OUTSIDE_SUSFSD_PATH);
|
||||
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_SUSFSD_PATH);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
package shirkneko.zako.sukisu.ui
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.displayCutout
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
||||
import com.ramcosta.composedestinations.generated.NavGraphs
|
||||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||
import shirkneko.zako.sukisu.Natives
|
||||
import shirkneko.zako.sukisu.ksuApp
|
||||
import shirkneko.zako.sukisu.ui.screen.BottomBarDestination
|
||||
import shirkneko.zako.sukisu.ui.theme.CardConfig
|
||||
import shirkneko.zako.sukisu.ui.theme.KernelSUTheme
|
||||
import shirkneko.zako.sukisu.ui.theme.loadCustomBackground
|
||||
import shirkneko.zako.sukisu.ui.theme.loadThemeMode
|
||||
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
|
||||
import shirkneko.zako.sukisu.ui.util.rootAvailable
|
||||
import shirkneko.zako.sukisu.ui.util.install
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
// Enable edge to edge
|
||||
enableEdgeToEdge()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// 加载保存的背景设置
|
||||
loadCustomBackground()
|
||||
loadThemeMode()
|
||||
CardConfig.load(applicationContext)
|
||||
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
if (isManager) install()
|
||||
|
||||
setContent {
|
||||
KernelSUTheme {
|
||||
val navController = rememberNavController()
|
||||
val snackBarHostState = remember { SnackbarHostState() }
|
||||
Scaffold(
|
||||
bottomBar = { BottomBar(navController) },
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||
) { innerPadding ->
|
||||
CompositionLocalProvider(
|
||||
LocalSnackbarHost provides snackBarHostState,
|
||||
) {
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
navGraph = NavGraphs.root,
|
||||
navController = navController,
|
||||
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
|
||||
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
|
||||
get() = { fadeIn(animationSpec = tween(340)) }
|
||||
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
|
||||
get() = { fadeOut(animationSpec = tween(340)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomBar(navController: NavHostController) {
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
||||
|
||||
// 获取卡片颜色和透明度
|
||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
val cardAlpha = CardConfig.cardAlpha
|
||||
val cardElevation = CardConfig.cardElevation
|
||||
|
||||
NavigationBar(
|
||||
tonalElevation = cardElevation, // 动态设置阴影
|
||||
containerColor = cardColor.copy(alpha = cardAlpha), // 动态设置颜色和透明度
|
||||
contentColor = if (cardColor.luminance() > 0.5) Color.Black else Color.White, // 根据背景亮度设置文字颜色
|
||||
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
||||
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
|
||||
)
|
||||
) {
|
||||
BottomBarDestination.entries.forEach { destination ->
|
||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||
NavigationBarItem(
|
||||
selected = isCurrentDestOnBackStack,
|
||||
onClick = {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
navigator.popBackStack(destination.direction, false)
|
||||
}
|
||||
navigator.navigate(destination.direction) {
|
||||
popUpTo(NavGraphs.root) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
alwaysShowLabel = false,
|
||||
colors = androidx.compose.material3.NavigationBarItemDefaults.colors(
|
||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
package shirkneko.zako.sukisu.ui.screen
|
||||
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import shirkneko.zako.sukisu.R
|
||||
import shirkneko.zako.sukisu.ui.component.ConfirmResult
|
||||
import shirkneko.zako.sukisu.ui.component.SearchAppBar
|
||||
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
|
||||
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
|
||||
import shirkneko.zako.sukisu.ui.theme.getCardColors
|
||||
import shirkneko.zako.sukisu.ui.theme.getCardElevation
|
||||
import shirkneko.zako.sukisu.ui.viewmodel.KpmViewModel
|
||||
import shirkneko.zako.sukisu.ui.util.loadKpmModule
|
||||
import shirkneko.zako.sukisu.ui.util.unloadKpmModule
|
||||
import java.io.File
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
fun KpmScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
viewModel: KpmViewModel = viewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackBarHost = remember { SnackbarHostState() }
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
val loadingDialog = rememberLoadingDialog()
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
val kpmInstall = stringResource(R.string.kpm_install)
|
||||
val kpmInstallConfirm = stringResource(R.string.kpm_install_confirm)
|
||||
val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
|
||||
val kpmInstallFailed = stringResource(R.string.kpm_install_failed)
|
||||
val install = stringResource(R.string.install)
|
||||
val cancel = stringResource(R.string.cancel)
|
||||
val kpmUninstall = stringResource(R.string.kpm_uninstall)
|
||||
val kpmUninstallConfirmTemplate = stringResource(R.string.kpm_uninstall_confirm)
|
||||
val uninstall = stringResource(R.string.uninstall)
|
||||
val kpmUninstallSuccess = stringResource(R.string.kpm_uninstall_success)
|
||||
val kpmUninstallFailed = stringResource(R.string.kpm_uninstall_failed)
|
||||
|
||||
val selectPatchLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult
|
||||
|
||||
val uri = result.data?.data ?: return@rememberLauncherForActivityResult
|
||||
|
||||
scope.launch {
|
||||
// 复制文件到临时目录
|
||||
val tempFile = File(context.cacheDir, "temp_patch.kpm")
|
||||
context.contentResolver.openInputStream(uri)?.use { input ->
|
||||
tempFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
val confirmResult = confirmDialog.awaitConfirm(
|
||||
title = kpmInstall,
|
||||
content = kpmInstallConfirm,
|
||||
confirm = install,
|
||||
dismiss = cancel
|
||||
)
|
||||
|
||||
if (confirmResult == ConfirmResult.Confirmed) {
|
||||
val success = loadingDialog.withLoading {
|
||||
loadKpmModule(tempFile.absolutePath)
|
||||
}
|
||||
|
||||
Log.d("KsuCli", "loadKpmModule result: $success")
|
||||
|
||||
if (success == "success") {
|
||||
viewModel.fetchModuleList()
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmInstallSuccess,
|
||||
duration = SnackbarDuration.Long
|
||||
)
|
||||
} else {
|
||||
// 修正为显示安装失败的消息
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmInstallFailed,
|
||||
duration = SnackbarDuration.Long
|
||||
)
|
||||
}
|
||||
}
|
||||
tempFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.moduleList.isEmpty()) {
|
||||
viewModel.fetchModuleList()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SearchAppBar(
|
||||
title = { Text(stringResource(R.string.kpm_title)) },
|
||||
searchText = viewModel.search,
|
||||
onSearchTextChange = { viewModel.search = it },
|
||||
onClearClick = { viewModel.search = "" },
|
||||
scrollBehavior = scrollBehavior,
|
||||
dropdownContent = {
|
||||
IconButton(onClick = { viewModel.fetchModuleList() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Refresh,
|
||||
contentDescription = stringResource(R.string.refresh)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
selectPatchLauncher.launch(
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
type = "application/*"
|
||||
}
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Add,
|
||||
contentDescription = stringResource(R.string.kpm_install)
|
||||
)
|
||||
},
|
||||
text = { Text(stringResource(R.string.kpm_install)) },
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackBarHost) }
|
||||
) { padding ->
|
||||
PullToRefreshBox(
|
||||
onRefresh = { viewModel.fetchModuleList() },
|
||||
isRefreshing = viewModel.isRefreshing,
|
||||
modifier = Modifier.padding(padding)
|
||||
) {
|
||||
if (viewModel.moduleList.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.kpm_empty),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
items(viewModel.moduleList) { module ->
|
||||
val kpmUninstallConfirm = String.format(kpmUninstallConfirmTemplate, module.name)
|
||||
KpmModuleItem(
|
||||
module = module,
|
||||
onUninstall = {
|
||||
scope.launch {
|
||||
val confirmResult = confirmDialog.awaitConfirm(
|
||||
title = kpmUninstall,
|
||||
content = kpmUninstallConfirm,
|
||||
confirm = uninstall,
|
||||
dismiss = cancel
|
||||
)
|
||||
if (confirmResult == ConfirmResult.Confirmed) {
|
||||
val success = loadingDialog.withLoading {
|
||||
unloadKpmModule(module.id)
|
||||
}
|
||||
Log.d("KsuCli", "unloadKpmModule result: $success")
|
||||
if (success == "success") {
|
||||
viewModel.fetchModuleList()
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmUninstallSuccess,
|
||||
duration = SnackbarDuration.Long
|
||||
)
|
||||
} else {
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmUninstallFailed,
|
||||
duration = SnackbarDuration.Long
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onControl = {
|
||||
viewModel.loadModuleDetail(module.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KpmModuleItem(
|
||||
module: KpmViewModel.ModuleInfo,
|
||||
onUninstall: () -> Unit,
|
||||
onControl: () -> Unit
|
||||
) {
|
||||
ElevatedCard(
|
||||
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = module.name,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Text(
|
||||
text = "${stringResource(R.string.kpm_version)}: ${module.version}",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = "${stringResource(R.string.kpm_author)}: ${module.author}",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = "${stringResource(R.string.kpm_args)}: ${module.args}",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = module.description,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
FilledTonalButton(
|
||||
onClick = onControl
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Settings,
|
||||
contentDescription = null
|
||||
)
|
||||
Text(stringResource(R.string.kpm_control))
|
||||
}
|
||||
|
||||
FilledTonalButton(
|
||||
onClick = onUninstall
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Delete,
|
||||
contentDescription = null
|
||||
)
|
||||
Text(stringResource(R.string.kpm_uninstall))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package shirkneko.zako.sukisu.ui.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import shirkneko.zako.sukisu.ui.util.*
|
||||
|
||||
class KpmViewModel : ViewModel() {
|
||||
var moduleList by mutableStateOf(emptyList<ModuleInfo>())
|
||||
private set
|
||||
|
||||
|
||||
var search by mutableStateOf("")
|
||||
internal set
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var currentModuleDetail by mutableStateOf("")
|
||||
private set
|
||||
|
||||
fun loadModuleDetail(moduleId: String) {
|
||||
viewModelScope.launch {
|
||||
currentModuleDetail = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
getKpmModuleInfo(moduleId)
|
||||
} catch (e: Exception) {
|
||||
"无法获取模块详细信息: ${e.message}"
|
||||
}
|
||||
}
|
||||
Log.d("KsuCli", "Module detail: $currentModuleDetail")
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchModuleList() {
|
||||
viewModelScope.launch {
|
||||
isRefreshing = true
|
||||
try {
|
||||
val moduleCount = getKpmModuleCount()
|
||||
Log.d("KsuCli", "Module count: $moduleCount")
|
||||
|
||||
val moduleInfo = listKpmModules()
|
||||
Log.d("KsuCli", "Module info: $moduleInfo")
|
||||
|
||||
val modules = parseModuleList(moduleInfo)
|
||||
moduleList = modules
|
||||
} finally {
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInstalledKernelPatches(): List<ModuleInfo> {
|
||||
return try {
|
||||
val output = printKpmModules()
|
||||
parseModuleList(output)
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseModuleList(output: String): List<ModuleInfo> {
|
||||
return output.split("\n").mapNotNull { line ->
|
||||
if (line.isBlank()) return@mapNotNull null
|
||||
val parts = line.split("|")
|
||||
if (parts.size < 7) return@mapNotNull null
|
||||
|
||||
ModuleInfo(
|
||||
id = parts[0].trim(),
|
||||
name = parts[1].trim(),
|
||||
version = parts[2].trim(),
|
||||
author = parts[3].trim(),
|
||||
description = parts[4].trim(),
|
||||
args = parts[6].trim(),
|
||||
enabled = true,
|
||||
hasAction = controlKpmModule(parts[0].trim()).isNotBlank()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class ModuleInfo(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val version: String,
|
||||
val author: String,
|
||||
val description: String,
|
||||
val args: String,
|
||||
val enabled: Boolean,
|
||||
val hasAction: Boolean
|
||||
)
|
||||
}
|
||||
6
manager/app/src/main/jniLibs/.gitignore
vendored
6
manager/app/src/main/jniLibs/.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
libzakomk.so
|
||||
libzakomksd.so
|
||||
libzakozako.so
|
||||
libzakozakozako.so
|
||||
libkpmmgr.so
|
||||
libzako.so
|
||||
libandroidx.graphics.path.so
|
||||
@@ -50,7 +50,6 @@
|
||||
<string name="home_click_to_learn_kernelsu">یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید</string>
|
||||
<string name="home_support_title">از ما حمایت کنید</string>
|
||||
<string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string>
|
||||
<string name="profile">پروفایل برنامه</string>
|
||||
<string name="profile_default">پیشفرض</string>
|
||||
<string name="profile_template">قالب</string>
|
||||
<string name="profile_custom">شخصی سازی شده</string>
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
<string name="home_click_to_learn_kernelsu">Pelajari cara instal KernelSU dan menggunakan modul</string>
|
||||
<string name="home_support_title">Dukung Kami</string>
|
||||
<string name="home_support_content">KernelSU akan selalu menjadi aplikasi gratis dan terbuka. Anda dapat memberikan donasi sebagai bentuk dukungan.</string>
|
||||
<string name="profile">Profil Apl</string>
|
||||
<string name="profile_default">Bawaan</string>
|
||||
<string name="profile_template">Templat</string>
|
||||
<string name="profile_custom">Khusus</string>
|
||||
|
||||
@@ -8,50 +8,56 @@
|
||||
<string name="home_superuser_count">スーパーユーザー: %d</string>
|
||||
<string name="home_module_count">モジュール: %d</string>
|
||||
<string name="home_unsupported">非対応</string>
|
||||
<string name="home_unsupported_reason">現在、 KernelSU は GKI カーネルにのみ対応しています</string>
|
||||
<string name="home_kernel">カーネル</string>
|
||||
<string name="home_manager_version">アプリのバージョン</string>
|
||||
<string name="home_unsupported_reason">カーネルの KernelSU ドライバが未検出です。カーネルが間違ってませんか?</string>
|
||||
<string name="home_kernel">カーネルのバージョン</string>
|
||||
<string name="home_susfs">SuSFS: %s</string>
|
||||
<string name="home_susfs_version">SuSFS のバージョン</string>
|
||||
<string name="home_susfs_sus_su">SuS SU</string>
|
||||
<string name="home_manager_version">マネージャーのバージョン</string>
|
||||
<string name="home_fingerprint">Fingerprint</string>
|
||||
<string name="home_selinux_status">SELinux の状態</string>
|
||||
<string name="selinux_status_disabled">Disabled</string>
|
||||
<string name="home_selinux_status">SELinux のステータス</string>
|
||||
<string name="selinux_status_disabled">無効</string>
|
||||
<string name="selinux_status_enforcing">Enforcing</string>
|
||||
<string name="selinux_status_permissive">Permissive</string>
|
||||
<string name="selinux_status_unknown">不明</string>
|
||||
<string name="superuser">スーパーユーザー</string>
|
||||
<string name="module_failed_to_enable">%s モジュールをオンにできませんでした</string>
|
||||
<string name="module_failed_to_disable">%s モジュールをオフにできませんでした</string>
|
||||
<string name="module_failed_to_enable">%s モジュールを ON にできませんでした</string>
|
||||
<string name="module_failed_to_disable">%s モジュールを OFF にできませんでした</string>
|
||||
<string name="module_empty">モジュールがインストールされていません</string>
|
||||
<string name="module">モジュール</string>
|
||||
<string name="module_sort_action_first">並べ替え (アクション優先)</string>
|
||||
<string name="module_sort_enabled_first">並べ替え (最初に有効)</string>
|
||||
<string name="uninstall">アンインストール</string>
|
||||
<string name="restore">復元</string>
|
||||
<string name="module_install">インストール</string>
|
||||
<string name="install">インストール</string>
|
||||
<string name="reboot">再起動</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="reboot_userspace">通常の再起動</string>
|
||||
<string name="reboot_recovery">リカバリーへ再起動</string>
|
||||
<string name="reboot_bootloader">ブートローダー へ再起動</string>
|
||||
<string name="reboot_download">ダウンロードモードへ再起動</string>
|
||||
<string name="reboot_edl">EDL へ再起動</string>
|
||||
<string name="reboot_userspace">ソフトリブート</string>
|
||||
<string name="reboot_recovery">リカバリーで再起動</string>
|
||||
<string name="reboot_bootloader">ブートローダーで再起動</string>
|
||||
<string name="reboot_download">ダウンロードモードで再起動</string>
|
||||
<string name="reboot_edl">EDL で再起動</string>
|
||||
<string name="about">アプリについて</string>
|
||||
<string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string>
|
||||
<string name="module_uninstall_success">%s はアンインストールされました</string>
|
||||
<string name="module_uninstall_failed">%s をアンインストールできませんでした</string>
|
||||
<string name="module_version">バージョン</string>
|
||||
<string name="module_author">制作者</string>
|
||||
<string name="module_author">作者</string>
|
||||
<string name="refresh">更新</string>
|
||||
<string name="show_system_apps">システムアプリを表示</string>
|
||||
<string name="hide_system_apps">システムアプリを非表示</string>
|
||||
<string name="send_log">ログを送信</string>
|
||||
<string name="send_log">ログを送信する</string>
|
||||
<string name="safe_mode">セーフモード</string>
|
||||
<string name="reboot_to_apply">再起動すると有効化されます</string>
|
||||
<string name="module_magisk_conflict">モジュールが Magisk との競合により利用できません!</string>
|
||||
<string name="home_learn_kernelsu">KernelSU について</string>
|
||||
<string name="module_magisk_conflict">モジュールが Magisk との競合により利用できません!</string>
|
||||
<string name="home_learn_kernelsu">KernelSU について学ぶ</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/ja_JP/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">KernelSU のインストール方法やモジュールの使い方はこちら</string>
|
||||
<string name="home_click_to_learn_kernelsu">KernelSU のインストール方法やモジュールの使い方を学習できます。</string>
|
||||
<string name="home_support_title">支援する</string>
|
||||
<string name="home_support_content">KernelSU はこれからもずっと無料でオープンソースです。寄付をして頂くことで、開発を支援していただけます。</string>
|
||||
<string name="profile">アプリのプロファイル</string>
|
||||
<string name="profile_default">既定</string>
|
||||
<string name="home_support_content">KernelSU は今後も無料でオープンソースです。ですが、寄付をして頂けると開発者への貢献になります。</string>
|
||||
<string name="about_source_code"><![CDATA[ソースコードは %1$s で確認できます。<br/>%2$s チャンネルにご参加ください。]]></string>
|
||||
<string name="profile_default">デフォルト</string>
|
||||
<string name="profile_template">テンプレート</string>
|
||||
<string name="profile_custom">カスタム</string>
|
||||
<string name="profile_name">プロファイル名</string>
|
||||
@@ -59,76 +65,205 @@
|
||||
<string name="profile_namespace_inherited">継承</string>
|
||||
<string name="profile_namespace_global">共通</string>
|
||||
<string name="profile_namespace_individual">分離</string>
|
||||
<string name="profile_umount_modules">モジュールのアンマウント</string>
|
||||
<string name="profile_groups">グループ</string>
|
||||
<string name="profile_capabilities">ケーパビリティ</string>
|
||||
<string name="profile_selinux_context">SELinux コンテキスト</string>
|
||||
<string name="profile_umount_modules">モジュールのアンマウント</string>
|
||||
<string name="failed_to_update_app_profile">%s のアプリのプロファイルの更新をできませでした</string>
|
||||
<string name="require_kernel_version" formatted="false">現在の KernelSU のバージョン %d は低すぎるため、マネージャーは正常に動作しません。バージョン %d 以上に更新してください!</string>
|
||||
<string name="settings_umount_modules_default">デフォルトでモジュールのマウントを解除する</string>
|
||||
<string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通となるデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string>
|
||||
<string name="settings_susfs_toggle">Kprobe フックを非表示にする</string>
|
||||
<string name="settings_susfs_toggle_summary">KSU によって生成された Kprobe フックを無効化して、代替となる組み込みの非 Kprobe を有効化します。Kprobe をサポートしない 非 GKI カーネルに適用される同等の機能を実装します。</string>
|
||||
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
|
||||
<string name="profile_selinux_domain">ドメイン</string>
|
||||
<string name="profile_selinux_rules">ルール</string>
|
||||
<string name="new_version_available">新しいバージョン %s が利用可能です。タップしてダウンロード。</string>
|
||||
<string name="module_update">アップデート</string>
|
||||
<string name="module_update">更新</string>
|
||||
<string name="module_downloading">モジュールをダウンロード中: %s</string>
|
||||
<string name="module_start_downloading">ダウンロードを開始: %s</string>
|
||||
<string name="new_version_available">新しいバージョン %s が利用可能です。タップしてダウンロード。</string>
|
||||
<string name="launch_app">起動</string>
|
||||
<string name="force_stop_app" formatted="false">強制停止</string>
|
||||
<string name="restart_app">再起動</string>
|
||||
<string name="failed_to_update_sepolicy">SELinux ルールの更新に失敗しました %s</string>
|
||||
<string name="profile_capabilities">ケーパビリティ</string>
|
||||
<string name="module_downloading">モジュールをダウンロード中: %s</string>
|
||||
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
|
||||
<string name="settings_umount_modules_default">既定でモジュールのマウントを解除</string>
|
||||
<string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通のデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string>
|
||||
<string name="module_changelog">変更履歴</string>
|
||||
<string name="app_profile_template_import_success">インポート成功</string>
|
||||
<string name="app_profile_export_to_clipboard">クリップボードからエクスポート</string>
|
||||
<string name="app_profile_template_export_empty">エクスポートするローカル テンプレートが見つかりません!</string>
|
||||
<string name="app_profile_template_id_exist">テンプレート ID はすでに存在します!</string>
|
||||
<string name="app_profile_import_from_clipboard">クリップボードからインポート</string>
|
||||
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
|
||||
<string name="app_profile_template_name">名前</string>
|
||||
<string name="app_profile_template_id_invalid">無効なテンプレート ID</string>
|
||||
<string name="app_profile_template_sync">オンラインテンプレートの同期</string>
|
||||
<string name="settings_profile_template">アプリプロファイルのテンプレート</string>
|
||||
<string name="settings_profile_template_summary">アプリプロファイルのローカルおよびオンラインテンプレートを管理します</string>
|
||||
<string name="app_profile_template_create">テンプレートの作成</string>
|
||||
<string name="app_profile_template_readonly">読み取り専用</string>
|
||||
<string name="app_profile_import_export">インポート/エクスポート</string>
|
||||
<string name="app_profile_template_save_failed">テンプレートの保存に失敗しました</string>
|
||||
<string name="app_profile_template_edit">テンプレートの編集</string>
|
||||
<string name="app_profile_template_id">ID</string>
|
||||
<string name="settings_profile_template">アプリプロファイルのテンプレート</string>
|
||||
<string name="app_profile_template_id_invalid">無効なテンプレート ID</string>
|
||||
<string name="app_profile_template_name">名前</string>
|
||||
<string name="app_profile_template_description">説明</string>
|
||||
<string name="app_profile_template_save">保存</string>
|
||||
<string name="settings_profile_template_summary">アプリプロファイルのローカルおよびオンラインテンプレートを管理する</string>
|
||||
<string name="app_profile_template_delete">消去</string>
|
||||
<string name="app_profile_template_import_empty">クリップボードが空です!</string>
|
||||
<string name="app_profile_template_view">テンプレートを表示</string>
|
||||
<string name="settings_check_update">アップデートを確認</string>
|
||||
<string name="settings_check_update_summary">アプリを開いたときにアップデートを自動的に確認する</string>
|
||||
<string name="app_profile_template_readonly">読み取り専用</string>
|
||||
<string name="app_profile_template_id_exist">テンプレート ID はすでに存在します!</string>
|
||||
<string name="app_profile_import_export">インポートとエクスポート</string>
|
||||
<string name="app_profile_import_from_clipboard">クリップボードからインポート</string>
|
||||
<string name="app_profile_export_to_clipboard">クリップボードからエクスポート</string>
|
||||
<string name="app_profile_template_export_empty">エクスポートするローカル テンプレートが見つかりません!</string>
|
||||
<string name="app_profile_template_import_success">インポートが成功しました</string>
|
||||
<string name="app_profile_template_sync">オンラインテンプレートの同期</string>
|
||||
<string name="app_profile_template_save_failed">テンプレートの保存に失敗しました</string>
|
||||
<string name="app_profile_template_import_empty">クリップボードが空です!</string>
|
||||
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
|
||||
<string name="settings_check_update">更新を確認する</string>
|
||||
<string name="settings_check_update_summary">アプリを開いたときに更新を自動的に確認します</string>
|
||||
<string name="grant_root_failed">root の付与に失敗しました!</string>
|
||||
<string name="action">アクション</string>
|
||||
<string name="open">開く</string>
|
||||
<string name="enable_web_debugging">WebView デバッグを有効にする</string>
|
||||
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合にのみ有効にしてください。</string>
|
||||
<string name="select_file_tip">%1$s パーティション イメージが推奨されます</string>
|
||||
<string name="select_kmi">KMI を選択してください</string>
|
||||
<string name="install_next">次に</string>
|
||||
<string name="enable_web_debugging">WebView デバッグを有効化する</string>
|
||||
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合でのみ有効化してください</string>
|
||||
<string name="direct_install">直接インストール (推奨)</string>
|
||||
<string name="select_file">ファイルを選択</string>
|
||||
<string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string>
|
||||
<string name="install_inactive_slot_warning">再起動後、デバイスは**強制的に**、現在非アクティブなスロットから起動します。
|
||||
\nこのオプションは、OTA が完了した後にのみ使用してください。
|
||||
\n続く?</string>
|
||||
<string name="direct_install">直接インストール (推奨)</string>
|
||||
<string name="select_file">ファイルを選択してください</string>
|
||||
\n続行しますか?</string>
|
||||
<string name="install_next">次へ</string>
|
||||
<string name="select_file_tip">%1$s のパーティションイメージを推奨します</string>
|
||||
<string name="select_kmi">KMI を選択してください</string>
|
||||
<string name="settings_uninstall">アンインストール</string>
|
||||
<string name="settings_uninstall_temporary">一時的にアンインストールする</string>
|
||||
<string name="settings_uninstall_permanent">完全にアンインストールする</string>
|
||||
<string name="settings_restore_stock_image">ストックイメージを復元</string>
|
||||
<string name="settings_uninstall_temporary">一時的にアンインストールする</string>
|
||||
<string name="settings_uninstall">アンインストール</string>
|
||||
<string name="settings_uninstall_temporary_message">KernelSU を一時的にアンインストールし、次回の再起動後に元の状態に戻します。</string>
|
||||
<string name="settings_uninstall_permanent_message">KernelSU (ルートおよびすべてのモジュール) を完全かつ永久にアンインストールします。</string>
|
||||
<string name="settings_uninstall_permanent_message">KernelSU (root およびすべてのモジュール) を完全かつ恒久的にアンインストールします。</string>
|
||||
<string name="settings_restore_stock_image_message">バックアップが存在する場合、工場出荷時のイメージを復元できます (OTA の前に使用してください)。KernelSU をアンインストールする必要がある場合は、「完全にアンインストールする」を使用してください。</string>
|
||||
<string name="flashing">フラッシュ</string>
|
||||
<string name="flash_success">フラッシュ成功</string>
|
||||
<string name="flash_failed">フラッシュ失敗</string>
|
||||
<string name="flash_success">フラッシュが成功しました</string>
|
||||
<string name="flash_failed">フラッシュに失敗しました</string>
|
||||
<string name="selected_lkm">選択された LKM: %s</string>
|
||||
<string name="save_log">ログを保存</string>
|
||||
<string name="action">アクション</string>
|
||||
<string name="log_saved">保存されたログ</string>
|
||||
<string name="module_sort_enabled_first">並べ替え(最初に有効)</string>
|
||||
<string name="module_sort_action_first">並べ替え(アクション優先)</string>
|
||||
<string name="status_supported">対応</string>
|
||||
<string name="status_not_supported">非対応</string>
|
||||
<string name="status_unknown">不明</string>
|
||||
<string name="sus_su_mode">SuS SU モード:</string>
|
||||
<!-- Module related -->
|
||||
<string name="module_install_confirm">%1$s のモジュールをインストールしますか?</string>
|
||||
<string name="unknown_module">不明なモジュール</string>
|
||||
<!-- Restore related -->
|
||||
<string name="restore_confirm_title">モジュールの復元を確認</string>
|
||||
<string name="restore_confirm_message">この操作によりモジュールが上書きされます。続行しますか?</string>
|
||||
<string name="confirm">確認</string>
|
||||
<string name="cancel">キャンセル</string>
|
||||
<!-- Backup related -->
|
||||
<string name="backup_success">バックアップが完了しました (tar.gz)</string>
|
||||
<string name="backup_failed">バックアップに失敗: %1$s</string>
|
||||
<string name="backup_modules">モジュールをバックアップ</string>
|
||||
<string name="restore_modules">モジュールを復元</string>
|
||||
<!-- Restore related messages -->
|
||||
<string name="restore_success">モジュールは正常に復元されました、再起動が必要です</string>
|
||||
<string name="restore_failed">復元に失敗: %1$s</string>
|
||||
<string name="restart_now">今すぐ再起動</string>
|
||||
<string name="unknown_error">不明なエラー</string>
|
||||
<!-- Command related -->
|
||||
<string name="command_execution_failed">コマンドの実行に失敗しました: %1$s</string>
|
||||
<!-- Allowlist related -->
|
||||
<string name="allowlist_backup_success">許可リストのバックアップが成功しました</string>
|
||||
<string name="allowlist_backup_failed">許可リストのバックアップに失敗: %1$s</string>
|
||||
<string name="allowlist_restore_confirm_title">許可リストの復元を確認</string>
|
||||
<string name="allowlist_restore_confirm_message">この操作により許可リストが上書きされます。続行しますか?</string>
|
||||
<string name="allowlist_restore_success">許可リストの復元が成功しました</string>
|
||||
<string name="allowlist_restore_failed">許可リストの復元に失敗: %1$s</string>
|
||||
<string name="backup_allowlist">許可リストをバックアップ</string>
|
||||
<string name="restore_allowlist">許可リストを復元</string>
|
||||
<string name="settings_custom_background">カスタム背景を設定</string>
|
||||
<string name="settings_custom_background_summary">カスタム背景を設定します</string>
|
||||
<string name="settings_card_alpha">ナビゲーションバーの透過</string>
|
||||
<string name="settings_restore_default">デフォルトに復元</string>
|
||||
<string name="home_android_version">Android のバージョン</string>
|
||||
<string name="home_device_model">デバイスモデル</string>
|
||||
<string name="su_not_allowed">%s にスーパーユーザー権限を付与することはできません</string>
|
||||
<string name="settings_disable_su">su の互換性を無効化する</string>
|
||||
<string name="settings_disable_su_summary">su コマンドを使用してアプリが root 権限を取得する動作を一時的に無効化します (既存の root プロセスは影響を受けません)。</string>
|
||||
<string name="using_mksu_manager">SukiSU Beta Manager を使用しています。</string>
|
||||
<string name="module_install_multiple_confirm">選択した %d 個のモジュールをインストールしてもよろしいですか?</string>
|
||||
<string name="module_install_multiple_confirm_with_names">%1$d 個のモジュールをインストールしてもよろしいですか?\n\n%2$s</string>
|
||||
<string name="more_settings">その他の設定</string>
|
||||
<string name="selinux">SELinux</string>
|
||||
<string name="selinux_enabled">有効</string>
|
||||
<string name="selinux_disabled">無効</string>
|
||||
<string name="simple_mode">シンプルモード</string>
|
||||
<string name="simple_mode_summary">ON にすると不要なカードを非表示にします</string>
|
||||
<string name="hide_kernel_kernelsu_version">カーネルのバージョンを非表示にする</string>
|
||||
<string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします</string>
|
||||
<string name="hide_other_info">その他の情報を非表示にする</string>
|
||||
<string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします</string>
|
||||
<string name="hide_susfs_status">SuSFS ステータスを非表示にする</string>
|
||||
<string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします</string>
|
||||
<string name="theme_mode">テーマモード</string>
|
||||
<string name="theme_follow_system">システムに従う</string>
|
||||
<string name="theme_light">ライトカラー</string>
|
||||
<string name="theme_dark">ダークカラー</string>
|
||||
<string name="manual_hook">手動でフック</string>
|
||||
<string name="dynamic_color_title">ダイナミックカラー</string>
|
||||
<string name="dynamic_color_summary">システムテーマのダイナミックカラーを使用します</string>
|
||||
<string name="choose_theme_color">テーマカラーを選択</string>
|
||||
<string name="color_default">ホワイト</string>
|
||||
<string name="color_blue">ブルー</string>
|
||||
<string name="color_green">グリーン</string>
|
||||
<string name="color_purple">パープル</string>
|
||||
<string name="color_orange">オレンジ</string>
|
||||
<string name="color_pink">ピンク</string>
|
||||
<string name="color_gray">グレー</string>
|
||||
<string name="color_ivory">アイボリー</string>
|
||||
<string name="flash_option">ブラシの設定</string>
|
||||
<string name="flash_option_tip">フラッシュするファイルを選択</string>
|
||||
<string name="horizon_kernel">AnyKernel3 をフラッシュ</string>
|
||||
<string name="horizon_kernel_summary">AnyKernel3 をフラッシュします</string>
|
||||
<string name="root_required">root 権限が必要です</string>
|
||||
<string name="copy_failed">ファイルのコピーに失敗しました</string>
|
||||
<string name="reboot_complete_title">スクラブが完了しました</string>
|
||||
<string name="reboot_complete_msg">すぐに再起動しますか?</string>
|
||||
<string name="yes">はい</string>
|
||||
<string name="no">いいえ</string>
|
||||
<string name="failed_reboot">再起動に失敗しました</string>
|
||||
<string name="batch_authorization">bulk ライセンス</string>
|
||||
<string name="batch_cancel_authorization">認証を一括でキャンセル</string>
|
||||
<string name="backup">バックアップ</string>
|
||||
<string name="color_yellow">イエロー</string>
|
||||
<string name="kpm">KPM モジュール</string>
|
||||
<string name="kpm_title">カーネルモジュール</string>
|
||||
<string name="kpm_empty">カーネルモジュールは現在インストールされていません</string>
|
||||
<string name="kpm_version">リリース</string>
|
||||
<string name="kpm_author">作者</string>
|
||||
<string name="kpm_uninstall">アンインストール</string>
|
||||
<string name="kpm_uninstall_success">アンインストールに失敗しました</string>
|
||||
<string name="kpm_uninstall_failed">アンインストールに失敗しました</string>
|
||||
<string name="kpm_install">インストールを選択</string>
|
||||
<string name="kpm_install_success">KPM モジュールの読み込みに成功しました</string>
|
||||
<string name="kpm_install_failed">KPM モジュールの読み込みに失敗しました</string>
|
||||
<string name="kpm_args">KPM パラメーター</string>
|
||||
<string name="kpm_control">完全</string>
|
||||
<string name="home_kpm_version">KPM のバージョン</string>
|
||||
<string name="close_notice">閉じる</string>
|
||||
<string name="kernel_module_notice">以下のカーネルモジュール関数は KernelPatch によって開発され、SukiSU Ultra のカーネルモジュール関数を含むように変更されました</string>
|
||||
<string name="home_ContributionCard_kernelsu">SukiSU Ultra の今後にご期待ください</string>
|
||||
<string name="kpm_control_success">成功</string>
|
||||
<string name="kpm_control_failed">失敗</string>
|
||||
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra は将来的に KSU から比較的に独立したブランチになりますが、公式の KernelSU や MKSU などの貢献に繋がるでしょう!</string>
|
||||
<string name="not_supported">非対応</string>
|
||||
<string name="supported">対応</string>
|
||||
<string name="home_kpm_module">KPM モジュール数: %d </string>
|
||||
<string name="kpm_invalid_file">無効な KPM ファイル</string>
|
||||
<string name="kernel_patched">カーネルはパッチされていません</string>
|
||||
<string name="kernel_not_enabled">カーネルは未設定です</string>
|
||||
<string name="custom_settings">カスタム設定</string>
|
||||
<string name="kpm_install_mode">インストール</string>
|
||||
<string name="kpm_install_mode_load">読み込む</string>
|
||||
<string name="kpm_install_mode_embed">埋め込む</string>
|
||||
<string name="kpm_install_mode_description">モジュール:%1\$s のインストールモードを選択してください \n\n読み込む: モジュールを一時的に読み込みます\n埋め込む: システムに恒久的にインストールします</string>
|
||||
<string name="log_failed_to_check_module_file">モジュールファイルの存在を確認できませんでした</string>
|
||||
<string name="snackbar_failed_to_check_module_file">モジュールファイルが存在するか確認できません</string>
|
||||
<string name="confirm_uninstall_title">アンインストールを確認</string>
|
||||
<string name="confirm_uninstall_confirm">アンインストール中</string>
|
||||
<string name="confirm_uninstall_dismiss">中止</string>
|
||||
<string name="theme_color">テーマカラー</string>
|
||||
<string name="invalid_file_type">ファイルの種類が正しくありません。.kpm ファイルを選択してください。</string>
|
||||
<string name="confirm_uninstall_title_with_filename">アンインストール</string>
|
||||
<string name="confirm_uninstall_content">次の KPM モジュールがアンインストールされます:\n%s</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">KernelSU</string>
|
||||
<string name="home">Strona główna</string>
|
||||
<string name="home_not_installed">Nie zainstalowano</string>
|
||||
<string name="home_click_to_install">Kliknij, aby zainstalować</string>
|
||||
@@ -51,7 +50,6 @@
|
||||
<string name="home_click_to_learn_kernelsu">Dowiedz się jak zainstalować KernelSU i jak korzystać z modułów</string>
|
||||
<string name="home_support_title">Wesprzyj nas</string>
|
||||
<string name="home_support_content">KernelSU jest i zawsze będzie darmowy oraz otwarty. Niemniej jednak możesz nam pokazać, że Ci zależy, wysyłając darowiznę.</string>
|
||||
<string name="profile" translatable="false">Profil aplikacji</string>
|
||||
<string name="profile_default">Domyślny</string>
|
||||
<string name="profile_template">Szablon</string>
|
||||
<string name="profile_custom">Własny</string>
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
<string name="home_click_to_learn_kernelsu">Saiba como instalar o KernelSU e usar os módulos</string>
|
||||
<string name="home_support_title">Apoie-nos</string>
|
||||
<string name="home_support_content">KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação.</string>
|
||||
<string name="profile" translatable="false">Perfil do Aplicativo</string>
|
||||
<string name="profile_default">Padrão</string>
|
||||
<string name="profile_template">Modelo</string>
|
||||
<string name="profile_custom">Personalizado</string>
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
<string name="home_click_to_learn_kernelsu">Узнайте, как установить KernelSU и использовать модули</string>
|
||||
<string name="home_support_title">Поддержите нас</string>
|
||||
<string name="home_support_content">KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string>
|
||||
<string name="profile" translatable="false">App Profile</string>
|
||||
<!--Don't translate this string!-->
|
||||
<string name="profile_default">По умолчанию</string>
|
||||
<string name="profile_template">Шаблон</string>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">KernelSU</string>
|
||||
<string name="home">Ana Sayfa</string>
|
||||
<string name="home_not_installed">Kurulmadı</string>
|
||||
<string name="home_click_to_install">Kurmak için tıklayın</string>
|
||||
@@ -51,7 +50,6 @@
|
||||
<string name="home_click_to_learn_kernelsu">KernelSU\'nun nasıl kurulacağını ve modüllerin nasıl kullanılacağını öğrenin</string>
|
||||
<string name="home_support_title">Bizi destekleyin</string>
|
||||
<string name="home_support_content">KernelSU ücretsiz ve açık kaynaklı bir yazılımdır ve her zaman öyle kalacaktır. Ancak bağış yaparak bize destek olduğunuzu gösterebilirsiniz.</string>
|
||||
<string name="profile" translatable="false">Uygulama profili</string>
|
||||
<string name="profile_default">Varsayılan</string>
|
||||
<string name="profile_template">Şablon</string>
|
||||
<string name="profile_custom">Özel</string>
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
<string name="home_click_to_learn_kernelsu">Дізнайтеся, як інсталювати KernelSU і використовувати модулі</string>
|
||||
<string name="home_support_title">Підтримати нас</string>
|
||||
<string name="home_support_content">KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string>
|
||||
<string name="profile">Профіль додатка</string>
|
||||
<string name="profile_default">Типовий</string>
|
||||
<string name="profile_template">Шаблон</string>
|
||||
<string name="profile_custom">Власний</string>
|
||||
|
||||
@@ -1,2 +1,263 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="home">Home</string>
|
||||
<string name="home_not_installed">Chưa cài đặt</string>
|
||||
<string name="home_click_to_install">Nhấn vào đây để cài đặt</string>
|
||||
<string name="home_working">Đang hoạt động</string>
|
||||
<string name="home_working_version">Phiên bản: %d</string>
|
||||
<string name="home_superuser_count">Superusers: %d</string>
|
||||
<string name="home_module_count">Mô-đun: %d</string>
|
||||
<string name="home_unsupported">Không được hỗ trợ</string>
|
||||
<string name="home_unsupported_reason">Không phát hiện được trình điều khiển KernelSU trên kernel của bạn.</string>
|
||||
<string name="home_kernel">Phiên bản Kernel</string>
|
||||
<string name="home_susfs">SuSFS: %s</string>
|
||||
<string name="home_susfs_version">Phiên bản SuSFS</string>
|
||||
<string name="home_susfs_sus_su">SuS SU</string>
|
||||
<string name="home_manager_version">Phiên bản quản lý</string>
|
||||
<string name="home_fingerprint">Dấu vân tay</string>
|
||||
<string name="home_selinux_status">Trạng thái SELinux</string>
|
||||
<string name="selinux_status_disabled">Disabled</string>
|
||||
<string name="selinux_status_enforcing">Enforcing</string>
|
||||
<string name="selinux_status_permissive">Permissive</string>
|
||||
<string name="selinux_status_unknown">Không rõ</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="module_failed_to_enable">Không thể bật mô-đun: %s</string>
|
||||
<string name="module_failed_to_disable">Không thể vô hiệu hóa mô-đun: %s</string>
|
||||
<string name="module_empty">Không có mô-đun nào được cài đặt</string>
|
||||
<string name="module">Mô-đun</string>
|
||||
<string name="module_sort_action_first">Sắp xếp (theo hành động)</string>
|
||||
<string name="module_sort_enabled_first">Sắp xếp (theo trạng thái)</string>
|
||||
<string name="uninstall">Gỡ cài đặt</string>
|
||||
<string name="restore">Khôi phục</string>
|
||||
<string name="module_install">Cài đặt</string>
|
||||
<string name="install">Cài đặt</string>
|
||||
<string name="reboot">Khởi động lại</string>
|
||||
<string name="settings">Cài đặt</string>
|
||||
<string name="reboot_userspace">Khởi động lại không gian người dùng</string>
|
||||
<string name="reboot_recovery">Khởi động lại vào Recovery</string>
|
||||
<string name="reboot_bootloader">Khởi động lại vào Bootloader</string>
|
||||
<string name="reboot_download">Khởi động lại vào Download mode</string>
|
||||
<string name="reboot_edl">Khởi động lại vào EDL</string>
|
||||
<string name="about">Thông tin thêm</string>
|
||||
<string name="module_uninstall_confirm">Bạn có chắc chắn muốn gỡ cài đặt mô-đun %s không?</string>
|
||||
<string name="module_uninstall_success">%s đã gỡ cài đặt</string>
|
||||
<string name="module_uninstall_failed">Không thể gỡ cài đặt: %s</string>
|
||||
<string name="module_version">Phiên bản</string>
|
||||
<string name="module_author">Tác giả</string>
|
||||
<string name="refresh">Làm mới</string>
|
||||
<string name="show_system_apps">Hiển thị ứng dụng hệ thống</string>
|
||||
<string name="hide_system_apps">Ẩn ứng dụng hệ thống</string>
|
||||
<string name="send_log">Gửi nhật ký</string>
|
||||
<string name="safe_mode">Chế độ an toàn</string>
|
||||
<string name="reboot_to_apply">Khởi động lại để có hiệu lực</string>
|
||||
<string name="module_magisk_conflict">Các mô-đun không khả dụng do xung đột với Magisk!</string>
|
||||
<string name="home_learn_kernelsu">Tìm hiểu KernelSU</string>
|
||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
||||
<string name="home_click_to_learn_kernelsu">Tìm hiểu cách cài đặt KernelSU và sử dụng các mô-đun</string>
|
||||
<string name="home_support_title">Hỗ trợ chúng tôi</string>
|
||||
<string name="home_support_content">KernelSU sẽ luôn là miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách quyên góp.</string>
|
||||
<string name="about_source_code"><![CDATA[View source code at %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
|
||||
<string name="profile_default">Mặc định</string>
|
||||
<string name="profile_template">Bản mẫu</string>
|
||||
<string name="profile_custom">Tùy chỉnh</string>
|
||||
<string name="profile_name">Tên hồ sơ</string>
|
||||
<string name="profile_namespace">Tên không gian gắn kết</string>
|
||||
<string name="profile_namespace_inherited">Được thừa hưởng</string>
|
||||
<string name="profile_namespace_global">Toàn cầu</string>
|
||||
<string name="profile_namespace_individual">Cá nhân</string>
|
||||
<string name="profile_groups">Nhóm</string>
|
||||
<string name="profile_capabilities">Khả năng</string>
|
||||
<string name="profile_selinux_context">Bối cảnh SELinux</string>
|
||||
<string name="profile_umount_modules">Bỏ gắn kết mô-đun</string>
|
||||
<string name="failed_to_update_app_profile">Không cập nhật được Hồ sơ ứng dụng cho %s</string>
|
||||
<string name="require_kernel_version" formatted="false">Phiên bản KernelSU hiện tại %d quá thấp để trình quản lý hoạt động bình thường. Vui lòng nâng cấp lên phiên bản %d hoặc cao hơn!</string>
|
||||
<string name="settings_umount_modules_default">Bỏ gắn kết các mô-đun theo mặc định</string>
|
||||
<string name="settings_umount_modules_default_summary">Giá trị mặc định cho \"Bỏ gắn kết các mô-đun\" trong Hồ sơ ứng dụng. Nếu được bật, nó sẽ xóa tất cả các sửa đổi của mô-đun đối với hệ thống đối và các ứng dụng không có hồ sơ được đặt.</string>
|
||||
<string name="settings_susfs_toggle">Ẩn móc kprobe</string>
|
||||
<string name="settings_susfs_toggle_summary">Vô hiệu hóa các móc kprobe do KSU tạo ra và kích hoạt các móc không phải kprobe tích hợp sẵn, triển khai sẽ được áp dụng cho hạt nhân không phải GKI, không hỗ trợ krp</string>
|
||||
<string name="profile_umount_modules_summary">Bật tùy chọn này sẽ cho phép KernelSU khôi phục mọi tệp đã được các mô-đun sửa đổi cho ứng dụng này.</string>
|
||||
<string name="profile_selinux_domain">Domain</string>
|
||||
<string name="profile_selinux_rules">Quy tắc</string>
|
||||
<string name="module_update">Cập nhật</string>
|
||||
<string name="module_downloading">Đang tải xuống mô-đun: %s</string>
|
||||
<string name="module_start_downloading">Bắt đầu tải xuống: %s</string>
|
||||
<string name="new_version_available">Phiên bản mới %s đã có sẵn, hãy nhấp để cập nhật.</string>
|
||||
<string name="launch_app">Khởi chạy</string>
|
||||
<string name="force_stop_app" formatted="false">Buộc dừng</string>
|
||||
<string name="restart_app">Khởi động lại</string>
|
||||
<string name="failed_to_update_sepolicy">Không cập nhật được quy tắc SELinux cho %s</string>
|
||||
<string name="module_changelog">Nhật ký thay đổi</string>
|
||||
<string name="settings_profile_template">Mẫu hồ sơ ứng dụng</string>
|
||||
<string name="settings_profile_template_summary">Quản lý mẫu cục bộ và trực tuyến của Hồ sơ ứng dụng</string>
|
||||
<string name="app_profile_template_create">Tạo mẫu</string>
|
||||
<string name="app_profile_template_edit">Chỉnh sửa mẫu</string>
|
||||
<string name="app_profile_template_id">ID</string>
|
||||
<string name="app_profile_template_id_invalid">ID mẫu không hợp lệ</string>
|
||||
<string name="app_profile_template_name">Tên</string>
|
||||
<string name="app_profile_template_description">Miêu tả</string>
|
||||
<string name="app_profile_template_save">Lưu</string>
|
||||
<string name="app_profile_template_delete">Xoá</string>
|
||||
<string name="app_profile_template_view">Xem mẫu</string>
|
||||
<string name="app_profile_template_readonly">Chỉ đọc</string>
|
||||
<string name="app_profile_template_id_exist">ID mẫu đã tồn tại!</string>
|
||||
<string name="app_profile_import_export">Nhập/Xuất</string>
|
||||
<string name="app_profile_import_from_clipboard">Nhập từ bộ nhớ tạm clipboard</string>
|
||||
<string name="app_profile_export_to_clipboard">Xuất vào bộ nhớ tạm clipboard</string>
|
||||
<string name="app_profile_template_export_empty">Cannot find local template to export!</string>
|
||||
<string name="app_profile_template_import_success">Đã nhập thành công</string>
|
||||
<string name="app_profile_template_sync">Đồng bộ mẫu trực tuyến</string>
|
||||
<string name="app_profile_template_save_failed">Không lưu được mẫu</string>
|
||||
<string name="app_profile_template_import_empty">Bộ nhớ tạm đang trống!</string>
|
||||
<string name="module_changelog_failed">Lấy nhật ký thay đổi không thành công: %s</string>
|
||||
<string name="settings_check_update">Kiểm tra cập nhật</string>
|
||||
<string name="settings_check_update_summary">Tự động kiểm tra cập nhật khi mở ứng dụng</string>
|
||||
<string name="grant_root_failed">Không cấp được quyền root!</string>
|
||||
<string name="action">Khởi chạy</string>
|
||||
<string name="open">Mở</string>
|
||||
<string name="enable_web_debugging">Bật gỡ lỗi WebView</string>
|
||||
<string name="enable_web_debugging_summary">Có thể sử dụng để gỡ lỗi WebUI. Vui lòng chỉ bật khi cần thiết.</string>
|
||||
<string name="direct_install">Cài đặt trực tiếp (Khuyến nghị)</string>
|
||||
<string name="select_file">Chọn một tập tin</string>
|
||||
<string name="install_inactive_slot">Cài đặt vào khe không hoạt động (Sau OTA)</string>
|
||||
<string name="install_inactive_slot_warning">Thiết bị của bạn sẽ **BUỘC** phải khởi động vào khe cắm hiện tại không hoạt động sau khi khởi động lại!\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất.\nTiếp tục?</string>
|
||||
<string name="install_next">Kế tiếp</string>
|
||||
<string name="select_file_tip">Phân vùng %1$s được khuyến nghị</string>
|
||||
<string name="select_kmi">Chọn KMI</string>
|
||||
<string name="settings_uninstall">Uninstall</string>
|
||||
<string name="settings_uninstall_temporary">Gỡ cài đặt tạm thời</string>
|
||||
<string name="settings_uninstall_permanent">Gỡ cài đặt vĩnh viễn</string>
|
||||
<string name="settings_restore_stock_image">Khôi phục img ban đầu</string>
|
||||
<string name="settings_uninstall_temporary_message">Gỡ cài đặt tạm thời KernelSU, khôi phục lại trạng thái ban đầu sau lần khởi động lại tiếp theo.</string>
|
||||
<string name="settings_uninstall_permanent_message">Gỡ cài đặt KernelSU (Root và tất cả các mô-đun) hoàn toàn và vĩnh viễn.</string>
|
||||
<string name="settings_restore_stock_image_message">Khôi phục ảnh gốc (nếu có bản sao lưu), thường được sử dụng trước OTA; nếu bạn cần gỡ cài đặt KernelSU, vui lòng sử dụng \"Gỡ cài đặt vĩnh viễn\".</string>
|
||||
<string name="flashing">Đang flash...</string>
|
||||
<string name="flash_success">Flash thành công</string>
|
||||
<string name="flash_failed">Lỗi flash</string>
|
||||
<string name="selected_lkm">LKM đã chọn: %s</string>
|
||||
<string name="save_log">Lưu nhật ký</string>
|
||||
<string name="log_saved">Nhật ký đã lưu</string>
|
||||
<string name="status_supported">Được hỗ trợ</string>
|
||||
<string name="status_not_supported">Không được hỗ trợ</string>
|
||||
<string name="status_unknown">Không rõ</string>
|
||||
<string name="sus_su_mode">Chế độ SuS: SU</string>
|
||||
<!-- Module related -->
|
||||
<string name="module_install_confirm">Xác nhận cài đặt mô-đun %1$s ?</string>
|
||||
<string name="unknown_module">Mô-đun không xác định</string>
|
||||
<!-- Restore related -->
|
||||
<string name="restore_confirm_title">Xác nhận khôi phục mô-đun</string>
|
||||
<string name="restore_confirm_message">Hành động này sẽ ghi đè lên tất cả các mô-đun hiện có. Tiếp tục?</string>
|
||||
<string name="confirm">Xác nhận</string>
|
||||
<string name="cancel">Hủy bỏ</string>
|
||||
<!-- Backup related -->
|
||||
<string name="backup_success">Sao lưu thành công (tar.gz)</string>
|
||||
<string name="backup_failed">Sao lưu không thành công: %1$s</string>
|
||||
<string name="backup_modules">Sao lưu mô-đun</string>
|
||||
<string name="restore_modules">Khôi phục các mô-đun</string>
|
||||
<!-- Restore related messages -->
|
||||
<string name="restore_success">Các mô-đun đã được khôi phục thành công, cần khởi động lại</string>
|
||||
<string name="restore_failed">Khôi phục không thành công: %1$s</string>
|
||||
<string name="restart_now">Khởi động lại ngay</string>
|
||||
<string name="unknown_error">Lỗi không xác định</string>
|
||||
<!-- Command related -->
|
||||
<string name="command_execution_failed">Thực hiện lệnh không thành công: %1$s</string>
|
||||
<!-- Allowlist related -->
|
||||
<string name="allowlist_backup_success">Sao lưu danh sách cho phép thành công</string>
|
||||
<string name="allowlist_backup_failed">Sao lưu danh sách cho phép không thành công: %1$s</string>
|
||||
<string name="allowlist_restore_confirm_title">Xác nhận khôi phục danh sách cho phép</string>
|
||||
<string name="allowlist_restore_confirm_message">Hành động này sẽ ghi đè lên danh sách cho phép hiện tại. Tiếp tục?</string>
|
||||
<string name="allowlist_restore_success">Đã khôi phục danh sách cho phép thành công</string>
|
||||
<string name="allowlist_restore_failed">Khôi phục danh sách cho phép không thành công: %1$s</string>
|
||||
<string name="backup_allowlist">Sao lưu danh sách cho phép</string>
|
||||
<string name="restore_allowlist">Khôi phục danh sách cho phép</string>
|
||||
<string name="settings_custom_background">Cài đặt nền tùy chỉnh</string>
|
||||
<string name="settings_custom_background_summary">Cài đặt tùy chỉnh nền</string>
|
||||
<string name="settings_card_alpha">Thẻ alpha</string>
|
||||
<string name="settings_restore_default">Khôi phục mặc định</string>
|
||||
<string name="home_android_version">Phiên bản Android</string>
|
||||
<string name="home_device_model">Model thiết bị</string>
|
||||
<string name="su_not_allowed">Không được phép cấp siêu người dùng cho %s</string>
|
||||
<string name="settings_disable_su">Vô hiệu hóa khả năng tương thích su</string>
|
||||
<string name="settings_disable_su_summary">Tạm thời vô hiệu hóa mọi ứng dụng khỏi quyền root thông qua lệnh su (các tiến trình root hiện có sẽ không bị ảnh hưởng).</string>
|
||||
<string name="using_mksu_manager">Bạn đang sử dụng trình quản lý SukiSU thử nghiệm</string>
|
||||
<string name="module_install_multiple_confirm">Bạn có chắc chắn muốn cài đặt các mô-đun %d đã chọn không?</string>
|
||||
<string name="module_install_multiple_confirm_with_names">Bạn có chắc chắn muốn cài đặt các mô-đun %1$d sau không? \n\n%2$s</string>
|
||||
<string name="more_settings">Nhiều thiết lập hơn</string>
|
||||
<string name="selinux">SELinux</string>
|
||||
<string name="selinux_enabled">Đã bật</string>
|
||||
<string name="selinux_disabled">Đã tắt</string>
|
||||
<string name="simple_mode">Chế độ đơn giản</string>
|
||||
<string name="simple_mode_summary">Ẩn các thẻ không cần thiết khi bật</string>
|
||||
<string name="hide_kernel_kernelsu_version">Ẩn phiên bản kernel</string>
|
||||
<string name="hide_kernel_kernelsu_version_summary">Ẩn phiên bản kernel hiện tại</string>
|
||||
<string name="hide_other_info">Ẩn thông tin thêm</string>
|
||||
<string name="hide_other_info_summary">Ẩn thông tin về số lượng app đã được cấp root, mô-đun và mô-đun KPM trên trang chủ</string>
|
||||
<string name="hide_susfs_status">Ẩn trạng thái SuSFS</string>
|
||||
<string name="hide_susfs_status_summary">Ẩn thông tin trạng thái SuSFS trên trang chủ</string>
|
||||
<string name="theme_mode">Chế độ chủ đề</string>
|
||||
<string name="theme_follow_system">Theo hệ thống</string>
|
||||
<string name="theme_light">Sáng</string>
|
||||
<string name="theme_dark">Tối</string>
|
||||
<string name="manual_hook">Móc thủ công</string>
|
||||
<string name="dynamic_color_title">Màu sắc động</string>
|
||||
<string name="dynamic_color_summary">Màu sắc động sử dụng chủ đề hệ thống</string>
|
||||
<string name="choose_theme_color">Chọn màu chủ đề</string>
|
||||
<string name="color_default">Trắng</string>
|
||||
<string name="color_blue">Xanh dương</string>
|
||||
<string name="color_green">Xanh lá</string>
|
||||
<string name="color_purple">Tím</string>
|
||||
<string name="color_orange">Cam</string>
|
||||
<string name="color_pink">Hồng</string>
|
||||
<string name="color_gray">Xám</string>
|
||||
<string name="color_ivory">Ngà voi</string>
|
||||
<string name="flash_option">Tùy chọn cọ</string>
|
||||
<string name="flash_option_tip">Chọn tập tin cần flash</string>
|
||||
<string name="horizon_kernel">File Anykernel3</string>
|
||||
<string name="root_required">Yêu cầu quyền root</string>
|
||||
<string name="copy_failed">Sao chép tập tin không thành công</string>
|
||||
<string name="reboot_complete_title">Hoàn tất</string>
|
||||
<string name="reboot_complete_msg">khởi động lại ngay lập tức?</string>
|
||||
<string name="yes">Có</string>
|
||||
<string name="no">Không</string>
|
||||
<string name="failed_reboot">Khởi động lại không thành công</string>
|
||||
<string name="batch_authorization">Giấy phép số lượng lớn</string>
|
||||
<string name="batch_cancel_authorization">Hủy ủy quyền hàng loạt</string>
|
||||
<string name="backup">Sao lưu</string>
|
||||
<string name="color_yellow">Vàng</string>
|
||||
<string name="kpm">Mô-đun kpm</string>
|
||||
<string name="kpm_title">Mô-đun KPM</string>
|
||||
<string name="kpm_empty">Không có mô-đun nào được cài đặt.</string>
|
||||
<string name="kpm_version">Phát hành</string>
|
||||
<string name="kpm_author">Tác giả</string>
|
||||
<string name="kpm_uninstall">Gỡ cài đặt</string>
|
||||
<string name="kpm_uninstall_success">Đã gỡ cài đặt thành công</string>
|
||||
<string name="kpm_uninstall_failed">Không thể gỡ cài đặt</string>
|
||||
<string name="kpm_install">Cài đặt</string>
|
||||
<string name="kpm_install_success">Tải module kpm thành công</string>
|
||||
<string name="kpm_install_failed">Tải module kpm không thành công</string>
|
||||
<string name="kpm_args">thông số kpm</string>
|
||||
<string name="kpm_control">Tùy chỉnh</string>
|
||||
<string name="home_kpm_version">Phiên bản KPM</string>
|
||||
<string name="close_notice">Đóng</string>
|
||||
<string name="kernel_module_notice">Các chức năng mô-đun kernel sau đây được KernelPatch phát triển và sửa đổi để bao gồm các chức năng mô-đun hạt nhân của SukiSU Ultra</string>
|
||||
<string name="home_ContributionCard_kernelsu">SukiSU Ultra mong đợi:</string>
|
||||
<string name="kpm_control_success">Thành công</string>
|
||||
<string name="kpm_control_failed">Thất bại</string>
|
||||
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra sẽ là một nhánh tương đối độc lập của KSU trong tương lai, nhưng xin cảm ơn KernelSU và MKSU chính thức vì những đóng góp của họ!</string>
|
||||
<string name="not_supported">Không được hỗ trợ</string>
|
||||
<string name="supported">Được hỗ trợ</string>
|
||||
<string name="home_kpm_module">Mô-đun KPM:%d </string>
|
||||
<string name="kpm_invalid_file">Tệp KPM không hợp lệ</string>
|
||||
<string name="kernel_patched">chưa được vá</string>
|
||||
<string name="kernel_not_enabled">chưa được vá</string>
|
||||
<string name="custom_settings">Cài đặt tùy chỉnh</string>
|
||||
<string name="kpm_install_mode">Cài đặt</string>
|
||||
<string name="kpm_install_mode_load">Tải</string>
|
||||
<string name="kpm_install_mode_embed">Nhúng</string>
|
||||
<string name="kpm_install_mode_description">Vui lòng:%1\$s chọn chế độ cài đặt mô-đun \n\nTải: Tải tạm thời mô-đun \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string>
|
||||
<string name="log_failed_to_check_module_file">Không kiểm tra được sự tồn tại của tệp mô-đun</string>
|
||||
<string name="snackbar_failed_to_check_module_file">Không thể kiểm tra xem tệp mô-đun có tồn tại không</string>
|
||||
<string name="confirm_uninstall_title">Xác nhận gỡ cài đặt</string>
|
||||
<string name="confirm_uninstall_confirm">loại bỏ</string>
|
||||
<string name="confirm_uninstall_dismiss">bãi bỏ</string>
|
||||
<string name="theme_color">Màu chủ đề</string>
|
||||
</resources>
|
||||
|
||||
@@ -172,7 +172,6 @@
|
||||
<string name="restore_allowlist">还原应用列表</string>
|
||||
<string name="settings_custom_background">自定义背景</string>
|
||||
<string name="settings_custom_background_summary">选择一张图片作为应用背景</string>
|
||||
<string name="settings_card_manage">卡片管理</string>
|
||||
<string name="settings_card_alpha">卡片透明度</string>
|
||||
<string name="settings_restore_default">恢复默认</string>
|
||||
<string name="home_android_version">Android 版本</string>
|
||||
@@ -180,7 +179,7 @@
|
||||
<string name="su_not_allowed">不允许授予 %s 超级用户权限</string>
|
||||
<string name="settings_disable_su">禁用 su 兼容性</string>
|
||||
<string name="settings_disable_su_summary">临时禁止任何应用程序通过 su 命令获取 Root 权限(现有的 Root 进程不受影响)</string>
|
||||
<string name="using_mksu_manager">你正在使用的是 MKSU 第三方管理器</string>
|
||||
<string name="using_mksu_manager">你正在使用的是 SukiSU Beta 版管理器</string>
|
||||
<string name="module_install_multiple_confirm">确定要安装选择的 %d 个模块吗?</string>
|
||||
<string name="module_install_multiple_confirm_with_names">确定要安装以下 %1$d 个模块吗?\n\n%2$s</string>
|
||||
<string name="more_settings">更多设置</string>
|
||||
@@ -191,6 +190,10 @@
|
||||
<string name="simple_mode_summary">开启后将隐藏不必要的卡片</string>
|
||||
<string name="hide_kernel_kernelsu_version">隐藏内核版本号</string>
|
||||
<string name="hide_kernel_kernelsu_version_summary">隐藏内核部分的 KernelSU 版本号</string>
|
||||
<string name="hide_other_info">强迫症开关</string>
|
||||
<string name="hide_other_info_summary">隐藏主页上的超级用户数、模块数和 KPM 模块数信息</string>
|
||||
<string name="hide_susfs_status">隐藏 SuSFS 状态信息</string>
|
||||
<string name="hide_susfs_status_summary">隐藏主页上的 SuSFS 状态信息</string>
|
||||
<string name="theme_mode">主题模式</string>
|
||||
<string name="theme_follow_system">跟随系统</string>
|
||||
<string name="theme_light">浅色</string>
|
||||
@@ -210,6 +213,7 @@
|
||||
<string name="flash_option">刷入选项</string>
|
||||
<string name="flash_option_tip">选择要刷入的文件</string>
|
||||
<string name="horizon_kernel">刷写 AnyKernel3 压缩包</string>
|
||||
<string name="horizon_kernel_summary">刷入 Anykernel3 内核</string>
|
||||
<string name="root_required">需要 root 权限</string>
|
||||
<string name="copy_failed">文件复制失败</string>
|
||||
<string name="reboot_complete_title">刷写完成</string>
|
||||
@@ -225,13 +229,67 @@
|
||||
<string name="kpm_empty">暂无已安装的内核模块</string>
|
||||
<string name="kpm_version">版本</string>
|
||||
<string name="kpm_author">作者</string>
|
||||
<string name="kpm_execute">执行</string>
|
||||
<string name="kpm_uninstall">卸载</string>
|
||||
<string name="kpm_uninstall_confirm">确定要卸载内核模块 %1$s 吗?</string>
|
||||
<string name="kpm_uninstall_success">卸载成功</string>
|
||||
<string name="kpm_uninstall_failed">卸载失败</string>
|
||||
<string name="kpm_install">安装kpm模块</string>
|
||||
<string name="kpm_install_confirm">确认安装吗?</string>
|
||||
<string name="kpm_install_success">安装kpm模块成功</string>
|
||||
<string name="kpm_install_failed">安装kpm模块失败</string>
|
||||
<string name="kpm_install">选择安装</string>
|
||||
<string name="kpm_install_success">加载 kpm 模块成功</string>
|
||||
<string name="kpm_install_failed">加载 kpm 模块失败</string>
|
||||
<string name="home_kpm_version">KPM 版本</string>
|
||||
<string name="kpm_control">调参</string>
|
||||
<string name="close_notice">关闭</string>
|
||||
<string name="kpm_control_success">成功</string>
|
||||
<string name="kpm_control_failed">错误</string>
|
||||
<string name="not_supported">不支持</string>
|
||||
<string name="supported">支持</string>
|
||||
<string name="home_kpm_module">KPM 模块数:%d </string>
|
||||
<string name="kpm_invalid_file">KPM 文件无效</string>
|
||||
<string name="kernel_patched">内核未进行补丁</string>
|
||||
<string name="kernel_not_enabled">内核未配置</string>
|
||||
<string name="kernel_module_notice">以下内核模块功能由 KernelPatch 开发,经过修改后加入 SukiSU Ultra 的内核模块功能</string>
|
||||
<string name="home_ContributionCard_kernelsu">SukiSU Ultra 展望</string>
|
||||
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra 未来将会成为一个相对独立的 KSU 分支,但是依然感谢官方 KernelSU 和 MKSU 等做出的贡献</string>
|
||||
<string name="custom_settings">个性化设置</string>
|
||||
<string name="kpm_install_mode">安装</string>
|
||||
<string name="kpm_install_mode_load">加载</string>
|
||||
<string name="kpm_install_mode_embed">嵌入</string>
|
||||
<string name="kpm_install_mode_description">请选择: %1\$s 模块的安装模式 \n\n加载:临时加载模块\n嵌入:永久安装到系统</string>
|
||||
<string name="snackbar_failed_to_check_module_file">无法检查模块文件是否存在</string>
|
||||
<string name="confirm_uninstall_title">确认卸载</string>
|
||||
<string name="confirm_uninstall_confirm">删除</string>
|
||||
<string name="confirm_uninstall_dismiss">取消</string>
|
||||
<string name="theme_color">主题颜色</string>
|
||||
<string name="invalid_file_type">文件类型不正确,请选择 .kpm 文件</string>
|
||||
<string name="confirm_uninstall_title_with_filename">卸载</string>
|
||||
<string name="confirm_uninstall_content">将卸载以下 kpm 模块:\n%s</string>
|
||||
<string name="image_editor_title">调整背景图片</string>
|
||||
<string name="image_editor_hint">使用双指缩放图片,单指拖动调整位置</string>
|
||||
<string name="background_image_error">无法加载图片</string>
|
||||
<string name="reprovision">重置</string>
|
||||
<!-- Anykernel3 Kernel刷写进度相关 -->
|
||||
<string name="horizon_flash_title">刷写Kernel</string>
|
||||
<string name="horizon_logs_label">日志:</string>
|
||||
<string name="horizon_flash_complete">刷写完成</string>
|
||||
<!-- 刷写状态相关 -->
|
||||
<string name="horizon_preparing">准备中…</string>
|
||||
<string name="horizon_cleaning_files">清理文件…</string>
|
||||
<string name="horizon_copying_files">复制文件…</string>
|
||||
<string name="horizon_extracting_tool">提取刷写工具…</string>
|
||||
<string name="horizon_patching_script">修补刷写脚本…</string>
|
||||
<string name="horizon_flashing">刷写内核中…</string>
|
||||
<string name="horizon_flash_complete_status">刷写完成</string>
|
||||
<!-- 槽位选择相关字符串 -->
|
||||
<string name="select_slot_title">选择刷写槽位</string>
|
||||
<string name="select_slot_description">请选择要刷写boot的目标槽位</string>
|
||||
<string name="slot_a">A槽位</string>
|
||||
<string name="slot_b">B槽位</string>
|
||||
<string name="selected_slot">已选择槽位: %1$s</string>
|
||||
<!-- 错误信息 -->
|
||||
<string name="horizon_copy_failed">复制失败</string>
|
||||
<string name="horizon_unknown_error">未知错误</string>
|
||||
<string name="horizon_getting_original_slot">获取原有槽位</string>
|
||||
<string name="horizon_setting_target_slot">设置指定槽位</string>
|
||||
<string name="horizon_restoring_original_slot">恢复默认槽位</string>
|
||||
<string name="current_slot">当前槽位:%1$s </string>
|
||||
<string name="flash_failed_message">刷写失败</string>
|
||||
</resources>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">SukiSU</string>
|
||||
<string name="app_name" translatable="false">SukiSU Ultra</string>
|
||||
<string name="home">Home</string>
|
||||
<string name="home_not_installed">Not installed</string>
|
||||
<string name="home_click_to_install">Click to install</string>
|
||||
@@ -10,9 +10,9 @@
|
||||
<string name="home_module_count">Modules: %d</string>
|
||||
<string name="home_unsupported">Unsupported</string>
|
||||
<string name="home_unsupported_reason">No KernelSU driver detected on your kernel, wrong kernel?.</string>
|
||||
<string name="home_kernel">Kernel</string>
|
||||
<string name="home_kernel">Kernel version</string>
|
||||
<string name="home_susfs">SuSFS: %s</string>
|
||||
<string name="home_susfs_version">Version de SuSFS</string>
|
||||
<string name="home_susfs_version">SuSFS Version</string>
|
||||
<string name="home_susfs_sus_su">SuS SU</string>
|
||||
<string name="home_manager_version">Manager version</string>
|
||||
<string name="home_fingerprint">Fingerprint</string>
|
||||
@@ -75,8 +75,7 @@
|
||||
<string name="require_kernel_version" formatted="false">The current KernelSU version %d is too low for the manager to work properly. Please upgrade to version %d or higher!</string>
|
||||
<string name="settings_umount_modules_default">Umount modules by default</string>
|
||||
<string name="settings_umount_modules_default_summary">The global default value for \"Umount modules\" in App Profile. If enabled, it will remove all module modifications to the system for apps that don\'t have a profile set.</string>
|
||||
<string name="settings_susfs_toggle">Cacher les hooks kprobe</string>
|
||||
<string name="settings_susfs_toggle_summary">Désactive les hooks kprobe créés par KSU et, à la place, active les hooks non-kprobe intégrés, implémentant les mêmes fonctionnalités qui seraient appliquées à un kernel non-GKI, qui ne supportent krpobe.</string>
|
||||
<string name="settings_susfs_toggle">Disable kprobe hooks</string>
|
||||
<string name="profile_umount_modules_summary">Enabling this option will allow KernelSU to restore any modified files by the modules for this app.</string>
|
||||
<string name="profile_selinux_domain">Domain</string>
|
||||
<string name="profile_selinux_rules">Rules</string>
|
||||
@@ -171,71 +170,130 @@
|
||||
<string name="allowlist_restore_failed">Allowlist restore failed: %1$s</string>
|
||||
<string name="backup_allowlist">Backup Allowlist</string>
|
||||
<string name="restore_allowlist">Restore Allowlist</string>
|
||||
<string name="settings_custom_background">settings custom background</string>
|
||||
<string name="settings_custom_background_summary">settings custom background summary</string>
|
||||
<string name="settings_card_manage">card manage</string>
|
||||
<string name="settings_card_alpha">card alpha</string>
|
||||
<string name="settings_restore_default">restore default</string>
|
||||
<string name="settings_custom_background">Custom App Background</string>
|
||||
<string name="settings_custom_background_summary">Select an image as background</string>
|
||||
<string name="settings_card_alpha">Navigation bar transparency</string>
|
||||
<string name="settings_restore_default">Restore default</string>
|
||||
<string name="home_android_version">Android version</string>
|
||||
<string name="home_device_model">device model</string>
|
||||
<string name="home_device_model">Device model</string>
|
||||
<string name="su_not_allowed">Granting superuser to %s is not allowed</string>
|
||||
<string name="settings_disable_su">Disable su compatibility</string>
|
||||
<string name="settings_disable_su_summary">Temporarily disable any applications from obtaining root privileges via the su command (existing root processes will not be affected).</string>
|
||||
<string name="using_mksu_manager">You are using the MKSU third-party manager</string>
|
||||
<string name="using_mksu_manager">You are using the SukiSU Beta manager</string>
|
||||
<string name="module_install_multiple_confirm">Are you sure you want to install the selected %d modules?</string>
|
||||
<string name="module_install_multiple_confirm_with_names">Sure you want to install the following %1$d modules? \n\n%2$s</string>
|
||||
<string name="more_settings">more settings</string>
|
||||
<string name="more_settings">More settings</string>
|
||||
<string name="selinux">SELinux</string>
|
||||
<string name="selinux_enabled">Enabled</string>
|
||||
<string name="selinux_disabled">Disabled</string>
|
||||
<string name="simple_mode">simplicity mode</string>
|
||||
<string name="simple_mode">Simplicity mode</string>
|
||||
<string name="simple_mode_summary">Hides unnecessary cards when turned on</string>
|
||||
<string name="hide_kernel_kernelsu_version">Hide kernel version</string>
|
||||
<string name="hide_kernel_kernelsu_version_summary">Hide kernel version</string>
|
||||
<string name="theme_mode">Theme Mode</string>
|
||||
<string name="theme_follow_system">follow-up system</string>
|
||||
<string name="theme_light">light color</string>
|
||||
<string name="theme_dark">dark colored</string>
|
||||
<string name="hide_other_info">Hide other info</string>
|
||||
<string name="hide_other_info_summary">Hides information about the number of super users, modules and KPM modules on the home page</string>
|
||||
<string name="hide_susfs_status">Hide SuSFS status</string>
|
||||
<string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</string>
|
||||
<string name="theme_mode">Theme</string>
|
||||
<string name="theme_follow_system">Follow system</string>
|
||||
<string name="theme_light">Light</string>
|
||||
<string name="theme_dark">Dark</string>
|
||||
<string name="manual_hook">Manual Hook</string>
|
||||
<string name="dynamic_color_title">Dynamic colours</string>
|
||||
<string name="dynamic_color_summary">Dynamic colours using system themes</string>
|
||||
<string name="choose_theme_color">Choose a theme colour</string>
|
||||
<string name="color_default">White</string>
|
||||
<string name="color_blue">blue</string>
|
||||
<string name="color_green">green</string>
|
||||
<string name="color_purple">purple</string>
|
||||
<string name="color_orange">orange</string>
|
||||
<string name="color_pink">pink</string>
|
||||
<string name="color_gray">gray</string>
|
||||
<string name="color_ivory">ivory</string>
|
||||
<string name="color_blue">Blue</string>
|
||||
<string name="color_green">Green</string>
|
||||
<string name="color_purple">Purple</string>
|
||||
<string name="color_orange">Orange</string>
|
||||
<string name="color_pink">Pink</string>
|
||||
<string name="color_gray">Gray</string>
|
||||
<string name="color_ivory">Ivory</string>
|
||||
<string name="flash_option">Brush Options</string>
|
||||
<string name="flash_option_tip">Select the file to be flashed</string>
|
||||
<string name="horizon_kernel">Anykernel3 Flush</string>
|
||||
<string name="horizon_kernel">Install Anykernel3</string>
|
||||
<string name="horizon_kernel_summary">Flash AnyKernel3 kernel file</string>
|
||||
<string name="root_required">Requires root privileges</string>
|
||||
<string name="copy_failed">File Copy Failure</string>
|
||||
<string name="reboot_complete_title">Scrubbing complete</string>
|
||||
<string name="reboot_complete_msg">Whether to reboot immediately?</string>
|
||||
<string name="yes">yes</string>
|
||||
<string name="no">no</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="failed_reboot">Reboot Failed</string>
|
||||
<string name="batch_authorization">bulk license</string>
|
||||
<string name="batch_authorization">Bulk license</string>
|
||||
<string name="batch_cancel_authorization">Batch cancel authorization</string>
|
||||
<string name="backup">Backup</string>
|
||||
<string name="color_yellow">Yellow</string>
|
||||
<string name="kpm">kpm module</string>
|
||||
<string name="kpm_title">kernel module</string>
|
||||
<string name="kpm">Kernel Module</string>
|
||||
<string name="kpm_title">Kernel Module</string>
|
||||
<string name="kpm_empty">No installed kernel modules at this time</string>
|
||||
<string name="kpm_version">releases</string>
|
||||
<string name="kpm_author">author</string>
|
||||
<string name="kpm_execute">fulfillment</string>
|
||||
<string name="kpm_uninstall">uninstallation</string>
|
||||
<string name="kpm_uninstall_confirm">Determine the kernel module to uninstall: %1$s ?</string>
|
||||
<string name="kpm_version">Version</string>
|
||||
<string name="kpm_author">Author</string>
|
||||
<string name="kpm_uninstall">Uninstall</string>
|
||||
<string name="kpm_uninstall_success">Uninstalled successfully</string>
|
||||
<string name="kpm_uninstall_failed">Failed to uninstall</string>
|
||||
<string name="kpm_install">Installing the kpm module</string>
|
||||
<string name="kpm_install_confirm">Confirm installation?</string>
|
||||
<string name="kpm_install_success">Installation of kpm module successful</string>
|
||||
<string name="kpm_install_failed">Installation of kpm module failed</string>
|
||||
<string name="kpm_args">kpm 参数</string>
|
||||
<string name="kpm_control">kpm 控制</string>
|
||||
<string name="kpm_install">Install</string>
|
||||
<string name="kpm_install_success">Load of kpm module successful</string>
|
||||
<string name="kpm_install_failed">Load of kpm module failed</string>
|
||||
<string name="kpm_args">Parameters</string>
|
||||
<string name="kpm_control">Execute</string>
|
||||
<string name="home_kpm_version">KPM Version</string>
|
||||
<string name="close_notice">Close</string>
|
||||
<string name="kernel_module_notice">The following kernel module functions were developed by KernelPatch and modified to include the kernel module functions of SukiSU Ultra</string>
|
||||
<string name="home_ContributionCard_kernelsu">SukiSU Ultra Look forward to</string>
|
||||
<string name="kpm_control_success">Success</string>
|
||||
<string name="kpm_control_failed">Failed</string>
|
||||
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra will be a relatively independent branch of KSU in the future, but we still appreciate the official KernelSU and MKSU etc. for their contributions!</string>
|
||||
<string name="not_supported">Unsupported</string>
|
||||
<string name="supported">Supported</string>
|
||||
<string name="home_kpm_module">"Number of KPM modules: %d "</string>
|
||||
<string name="kpm_invalid_file">Invalid KPM file</string>
|
||||
<string name="kernel_patched">Kernel not patched</string>
|
||||
<string name="kernel_not_enabled">Kernel not configured</string>
|
||||
<string name="custom_settings">Custom settings</string>
|
||||
<string name="kpm_install_mode">KPM Install</string>
|
||||
<string name="kpm_install_mode_load">Load</string>
|
||||
<string name="kpm_install_mode_embed">Embed</string>
|
||||
<string name="kpm_install_mode_description">Please select: %1\$s Module Installation Mode \n\nLoad: Temporarily load the module \nEmbedded: Permanently install into the system</string>
|
||||
<string name="log_failed_to_check_module_file">Failed to check module file existence</string>
|
||||
<string name="snackbar_failed_to_check_module_file">Unable to check if module file exists</string>
|
||||
<string name="confirm_uninstall_title">Confirm uninstallation</string>
|
||||
<string name="confirm_uninstall_confirm">Uninstall</string>
|
||||
<string name="confirm_uninstall_dismiss">Cancel</string>
|
||||
<string name="theme_color">Theme Color</string>
|
||||
<string name="invalid_file_type">Incorrect file type! Please select .kpm file.</string>
|
||||
<string name="confirm_uninstall_title_with_filename">Uninstall</string>
|
||||
<string name="confirm_uninstall_content">The following KPM will be uninstalled: %s</string>
|
||||
<string name="settings_susfs_toggle_summary">Disable kprobe hooks created by KernelSU, using inline hooks instead, which is similar to non-GKI kernel hooking method.</string>
|
||||
<string name="image_editor_title">Adjust background image</string>
|
||||
<string name="image_editor_hint">Use two fingers to zoom the image, and one finger to drag it to adjust the position</string>
|
||||
<string name="background_image_error">Could not load image</string>
|
||||
<string name="reprovision">Reprovision</string>
|
||||
<!-- Kernel Flash Progress Related -->
|
||||
<string name="horizon_flash_title">Kernel Flashing</string>
|
||||
<string name="horizon_logs_label">Logs:</string>
|
||||
<string name="horizon_flash_complete">Flash Complete</string>
|
||||
<!-- Flash Status Related -->
|
||||
<string name="horizon_preparing">Preparing…</string>
|
||||
<string name="horizon_cleaning_files">Cleaning files…</string>
|
||||
<string name="horizon_copying_files">Copying files…</string>
|
||||
<string name="horizon_extracting_tool">Extracting flash tool…</string>
|
||||
<string name="horizon_patching_script">Patching flash script…</string>
|
||||
<string name="horizon_flashing">Flashing kernel…</string>
|
||||
<string name="horizon_flash_complete_status">Flash completed</string>
|
||||
<!-- Slot selection related strings -->
|
||||
<string name="select_slot_title">Select Flash Slot</string>
|
||||
<string name="select_slot_description">Please select the target slot for flashing boot</string>
|
||||
<string name="slot_a">Slot A</string>
|
||||
<string name="slot_b">Slot B</string>
|
||||
<string name="selected_slot">Selected slot: %1$s</string>
|
||||
<!-- Error Messages -->
|
||||
<string name="horizon_copy_failed">Copy failed</string>
|
||||
<string name="horizon_unknown_error">Unknown error</string>
|
||||
<string name="horizon_getting_original_slot">Getting the original slot</string>
|
||||
<string name="horizon_setting_target_slot">Setting the specified slot</string>
|
||||
<string name="horizon_restoring_original_slot">Restore Default Slot</string>
|
||||
<string name="current_slot">Current Slot:%1$s </string>
|
||||
<string name="flash_failed_message">Flash failed</string>
|
||||
</resources>
|
||||
|
||||
@@ -28,8 +28,8 @@ cmaker {
|
||||
}
|
||||
|
||||
val androidMinSdkVersion = 26
|
||||
val androidTargetSdkVersion = 35
|
||||
val androidCompileSdkVersion = 35
|
||||
val androidTargetSdkVersion = 36
|
||||
val androidCompileSdkVersion = 36
|
||||
val androidCompileNdkVersion = "28.0.13004108"
|
||||
val androidSourceCompatibility = JavaVersion.VERSION_21
|
||||
val androidTargetCompatibility = JavaVersion.VERSION_21
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
[versions]
|
||||
agp = "8.9.1"
|
||||
agp = "8.9.2"
|
||||
kotlin = "2.1.10"
|
||||
ksp = "2.1.10-1.0.30"
|
||||
compose-bom = "2025.02.00"
|
||||
compose-bom = "2025.04.01"
|
||||
lifecycle = "2.8.7"
|
||||
navigation = "2.8.7"
|
||||
activity-compose = "1.10.0"
|
||||
kotlinx-coroutines = "1.10.1"
|
||||
navigation = "2.8.9"
|
||||
activity-compose = "1.10.1"
|
||||
kotlinx-coroutines = "1.10.2"
|
||||
coil-compose = "2.7.0"
|
||||
compose-destination = "2.1.0-beta16"
|
||||
compose-destination = "2.1.0"
|
||||
sheets-compose-dialogs = "1.3.0"
|
||||
markdown = "4.6.2"
|
||||
webkit = "1.12.1"
|
||||
webkit = "1.13.0"
|
||||
appiconloader-coil = "1.5.0"
|
||||
parcelablelist = "2.0.1"
|
||||
libsu = "6.0.0"
|
||||
apksign = "1.4"
|
||||
cmaker = "1.2"
|
||||
compose-material = "1.7.8"
|
||||
compose-material3 = "1.3.1"
|
||||
compose-ui = "1.7.8"
|
||||
compose-material = "1.8.0"
|
||||
compose-material3 = "1.3.2"
|
||||
compose-ui = "1.8.0"
|
||||
compose-foundation = "1.7.8"
|
||||
documentfile = "1.0.1"
|
||||
|
||||
|
||||
@@ -13,15 +13,38 @@
|
||||
#define CMD_KPM_CONTROL_MAX 7
|
||||
|
||||
// 控制代码
|
||||
#define SUKISU_KPM_LOAD 1
|
||||
#define SUKISU_KPM_UNLOAD 2
|
||||
#define SUKISU_KPM_NUM 3
|
||||
#define SUKISU_KPM_LIST 4
|
||||
#define SUKISU_KPM_INFO 5
|
||||
#define SUKISU_KPM_CONTROL 6
|
||||
#define SUKISU_KPM_PRINT 7
|
||||
// prctl(xxx, 28, "PATH", "ARGS")
|
||||
// success return 0, error return -N
|
||||
#define SUKISU_KPM_LOAD 28
|
||||
|
||||
#define CONTROL_CODE(n) (CMD_KPM_CONTROL + n - 1)
|
||||
// prctl(xxx, 29, "NAME")
|
||||
// success return 0, error return -N
|
||||
#define SUKISU_KPM_UNLOAD 29
|
||||
|
||||
// num = prctl(xxx, 30)
|
||||
// error return -N
|
||||
// success return +num or 0
|
||||
#define SUKISU_KPM_NUM 30
|
||||
|
||||
// prctl(xxx, 31, Buffer, BufferSize)
|
||||
// success return +out, error return -N
|
||||
#define SUKISU_KPM_LIST 31
|
||||
|
||||
// prctl(xxx, 32, "NAME", Buffer[256])
|
||||
// success return +out, error return -N
|
||||
#define SUKISU_KPM_INFO 32
|
||||
|
||||
// prctl(xxx, 33, "NAME", "ARGS")
|
||||
// success return KPM's result value
|
||||
// error return -N
|
||||
#define SUKISU_KPM_CONTROL 33
|
||||
|
||||
// prctl(xxx, 34, buffer, bufferSize)
|
||||
// success return KPM's result value
|
||||
// error return -N
|
||||
#define SUKISU_KPM_VERSION 34
|
||||
|
||||
#define CONTROL_CODE(n) (n)
|
||||
|
||||
void print_usage(const char *prog) {
|
||||
printf("Usage: %s <command> [args]\n", prog);
|
||||
@@ -32,7 +55,7 @@ void print_usage(const char *prog) {
|
||||
printf(" list List loaded KPM modules\n");
|
||||
printf(" info <name> Get info of a KPM module\n");
|
||||
printf(" control <name> <args> Send control command to a KPM module\n");
|
||||
printf(" print Print KPM module list to stdout\n");
|
||||
printf(" version Print KPM Loader version\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@@ -47,6 +70,9 @@ int main(int argc, char *argv[]) {
|
||||
if (strcmp(argv[1], "load") == 0 && argc >= 3) {
|
||||
// 加载 KPM 模块
|
||||
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_LOAD), argv[2], (argc > 3 ? argv[3] : NULL), &out);
|
||||
if(out > 0) {
|
||||
printf("Success");
|
||||
}
|
||||
} else if (strcmp(argv[1], "unload") == 0 && argc >= 3) {
|
||||
// 卸载 KPM 模块
|
||||
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_UNLOAD), argv[2], NULL, &out);
|
||||
@@ -59,22 +85,25 @@ int main(int argc, char *argv[]) {
|
||||
// 获取模块列表
|
||||
char buffer[1024] = {0};
|
||||
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_LIST), buffer, sizeof(buffer), &out);
|
||||
if (ret >= 0) {
|
||||
if (out >= 0) {
|
||||
printf("%s", buffer);
|
||||
}
|
||||
} else if (strcmp(argv[1], "info") == 0 && argc >= 3) {
|
||||
// 获取指定模块信息
|
||||
char buffer[256] = {0};
|
||||
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_INFO), argv[2], buffer, &out);
|
||||
if (ret >= 0) {
|
||||
if (out >= 0) {
|
||||
printf("%s\n", buffer);
|
||||
}
|
||||
} else if (strcmp(argv[1], "control") == 0 && argc >= 4) {
|
||||
// 控制 KPM 模块
|
||||
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_CONTROL), argv[2], argv[3], &out);
|
||||
} else if (strcmp(argv[1], "print") == 0) {
|
||||
// 在 stdout 输出 KPM 列表
|
||||
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_PRINT), NULL, NULL, &out);
|
||||
} else if (strcmp(argv[1], "version") == 0) {
|
||||
char buffer[1024] = {0};
|
||||
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_VERSION), buffer, sizeof(buffer), &out);
|
||||
if (out >= 0) {
|
||||
printf("%s", buffer);
|
||||
}
|
||||
} else {
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
|
||||
2
userspace/ksud/Cargo.lock
generated
2
userspace/ksud/Cargo.lock
generated
@@ -784,7 +784,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zakomk"
|
||||
name = "zakozako"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"android-properties",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
[package]
|
||||
name = "zakomk"
|
||||
name = "zakozako"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
notify = "6.1"
|
||||
anyhow = "1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
const_format = "0.2"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
____ _ _ _ ____ _____ _____
|
||||
/ ___|| |__(_) |__ | ___|| ____|_ _|
|
||||
\___ \| '_ \ | '_ \|___ \| _| | |
|
||||
___) | | | | | | | ___) | |___ | |
|
||||
|____/|_| |_|_| |_| |____/|_____| |_|
|
||||
____ _ _ ____ _ _
|
||||
/ ___| _ _| | _(_) ___|| | | |
|
||||
\___ \| | | | |/ / \___ \| | | |
|
||||
___) | |_| | <| |___) | |_| |
|
||||
|____/ \__,_|_|\_\_|____/ \___/
|
||||
_ _ _ _
|
||||
| | | | | |_ _ __ __ _
|
||||
| | | | | __| '__/ _\ |
|
||||
| |_| | | |_| | | (_| |
|
||||
\___/|_|\__|_| \__,_|
|
||||
@@ -5,10 +5,13 @@ use anyhow::{Context, Result};
|
||||
use log::{info, warn};
|
||||
use rustix::fs::{MountFlags, mount};
|
||||
use std::path::Path;
|
||||
use crate::kpm;
|
||||
|
||||
pub fn on_post_data_fs() -> Result<()> {
|
||||
ksucalls::report_post_fs_data();
|
||||
|
||||
kpm::start_kpm_watcher()?;
|
||||
|
||||
utils::umask(0);
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -98,6 +101,9 @@ pub fn on_post_data_fs() -> Result<()> {
|
||||
|
||||
run_stage("post-mount", true);
|
||||
|
||||
// load kpm modules
|
||||
kpm::load_kpm_modules()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user