You've already forked KernelSU-Next
mirror of
https://github.com/KernelSU-Next/KernelSU-Next.git
synced 2025-08-27 23:46:34 +00:00
Compare commits
341 Commits
magicoverl
...
next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1de68a8ed2 | ||
|
|
e0c461322b | ||
|
|
edb99a2c1a | ||
|
|
eaab98b7ec | ||
|
|
aa37bcc368 | ||
|
|
107cd4add0 | ||
|
|
94c4b41ea3 | ||
|
|
0bde9047b9 | ||
|
|
bc9927b9b6 | ||
|
|
d4f4c0a0cc | ||
|
|
0aaae919c0 | ||
|
|
3f4c23a34f | ||
|
|
d69a72c658 | ||
|
|
adbff41a22 | ||
|
|
512f84504e | ||
|
|
c44f48c8a4 | ||
|
|
5c6c3870a0 | ||
|
|
c6b5440682 | ||
|
|
a917314e84 | ||
|
|
948975ba35 | ||
|
|
45ad73e9dd | ||
|
|
892a62afdf | ||
|
|
d61de07c21 | ||
|
|
4382dca515 | ||
|
|
2c9078e038 | ||
|
|
81f4f09d0c | ||
|
|
2241696498 | ||
|
|
514c5458ed | ||
|
|
d30bb33a9c | ||
|
|
ec7fe6b039 | ||
|
|
3fd61e0ee5 | ||
|
|
8fc85993f1 | ||
|
|
df07860e44 | ||
|
|
bd03c296a1 | ||
|
|
14b7861f44 | ||
|
|
3c421b1362 | ||
|
|
c6b60a24e8 | ||
|
|
1baedd89b7 | ||
|
|
b567e9b275 | ||
|
|
3ba5b028d4 | ||
|
|
4d633a1e0e | ||
|
|
f08fcec777 | ||
|
|
afe6ad7261 | ||
|
|
54b26fd32c | ||
|
|
4b56b14a4f | ||
|
|
9b4c6057a0 | ||
|
|
873740ef1c | ||
|
|
ad80c1ea77 | ||
|
|
d5c6f0affb | ||
|
|
162056f9ff | ||
|
|
3af1d0354d | ||
|
|
6adb2eaf51 | ||
|
|
ce91e4fedb | ||
|
|
a36390ea03 | ||
|
|
e31e0271cb | ||
|
|
b97fc2bec2 | ||
|
|
10875ee190 | ||
|
|
fdb01c918a | ||
|
|
6afa86d2ae | ||
|
|
25fa6b7b9b | ||
|
|
a361fa3272 | ||
|
|
4a9733c078 | ||
|
|
31aa571bc2 | ||
|
|
58167a4289 | ||
|
|
a8bfd1cc7d | ||
|
|
58aeb2697a | ||
|
|
7f68766dc4 | ||
|
|
310c0573c6 | ||
|
|
d1aad01df3 | ||
|
|
9733b92d30 | ||
|
|
aaf776f421 | ||
|
|
5e33aee99f | ||
|
|
4e3f06d405 | ||
|
|
98b9863041 | ||
|
|
29ae76d1fb | ||
|
|
2c3841558e | ||
|
|
c8b357e31b | ||
|
|
3ad02ff50b | ||
|
|
6a54b30a9d | ||
|
|
b0cb3bb4c2 | ||
|
|
7f957be99b | ||
|
|
a54c319d55 | ||
|
|
2c71531533 | ||
|
|
5c61a70e5a | ||
|
|
fdf1d61735 | ||
|
|
93dc61e113 | ||
|
|
d80a3ebcda | ||
|
|
4270fd8b1e | ||
|
|
764dbc3782 | ||
|
|
080ab9a952 | ||
|
|
a9cab5ccfd | ||
|
|
3d44602537 | ||
|
|
88eb2a2723 | ||
|
|
e272e557b0 | ||
|
|
2a4794e422 | ||
|
|
818bdbead6 | ||
|
|
76249fa67d | ||
|
|
ed50b57b57 | ||
|
|
f05f776a08 | ||
|
|
c9b79c3016 | ||
|
|
3ff10d6622 | ||
|
|
b95d2b69b6 | ||
|
|
2cd8453877 | ||
|
|
b7300b0525 | ||
|
|
3a278d560f | ||
|
|
03fa2eddb2 | ||
|
|
b74e953ad2 | ||
|
|
39717b0a3f | ||
|
|
7f0eccd3d5 | ||
|
|
78eb3b0b22 | ||
|
|
39f20bf573 | ||
|
|
092eb1b23d | ||
|
|
30e2ed5db5 | ||
|
|
dc7ae2db5f | ||
|
|
b3b7ef1cb3 | ||
|
|
4de4d1e091 | ||
|
|
0beea57ab7 | ||
|
|
49aee1ff4c | ||
|
|
f7a3699fe3 | ||
|
|
66d42de599 | ||
|
|
d562594ae1 | ||
|
|
02afc6710c | ||
|
|
9d9f9ed5d5 | ||
|
|
b3b6320946 | ||
|
|
d43b8320ad | ||
|
|
a2260ae330 | ||
|
|
aa221acad1 | ||
|
|
c41d35db62 | ||
|
|
e5f0c733b8 | ||
|
|
70b52f85e9 | ||
|
|
303b5192ec | ||
|
|
443d3a6abe | ||
|
|
a2a7383343 | ||
|
|
ed64f933f5 | ||
|
|
4c7ad8eac4 | ||
|
|
6a922febcd | ||
|
|
71eba5df8b | ||
|
|
f1ef0afc20 | ||
|
|
844c9f94e9 | ||
|
|
07a4d3457c | ||
|
|
8c9728df95 | ||
|
|
14ec1194e4 | ||
|
|
a37f398cc7 | ||
|
|
80bd797737 | ||
|
|
600d9ce5d2 | ||
|
|
f15d9b18e9 | ||
|
|
aa19e8c609 | ||
|
|
2b2320000c | ||
|
|
06135cc827 | ||
|
|
fc58bdf0e2 | ||
|
|
9b5e60912d | ||
|
|
c108a8ed32 | ||
|
|
adce657583 | ||
|
|
d6601e1e54 | ||
|
|
0d4efa649f | ||
|
|
85f4e6ac27 | ||
|
|
c91f9c18ec | ||
|
|
bf35f73430 | ||
|
|
ad290a51a0 | ||
|
|
d218346613 | ||
|
|
502e5599fe | ||
|
|
11fb52b929 | ||
|
|
057388ccef | ||
|
|
67759ad723 | ||
|
|
f231cbdba7 | ||
|
|
7abc9bc821 | ||
|
|
e7697d86fe | ||
|
|
68394fddd5 | ||
|
|
90d34bf511 | ||
|
|
236fbc7615 | ||
|
|
6871cbdba7 | ||
|
|
38a9949211 | ||
|
|
9fba0faa43 | ||
|
|
b1250b002e | ||
|
|
10f7d5cf50 | ||
|
|
0c883ddfd6 | ||
|
|
3921175e4c | ||
|
|
84fdcf8bf5 | ||
|
|
886cfd5a33 | ||
|
|
aea384bdd4 | ||
|
|
84695cea71 | ||
|
|
f91afe6c46 | ||
|
|
3f7e731df6 | ||
|
|
582662dce9 | ||
|
|
f3b49723e8 | ||
|
|
c4deee1e49 | ||
|
|
6a6fc07cd4 | ||
|
|
d8cb7ef772 | ||
|
|
dddf2f06a0 | ||
|
|
e7e935aeb2 | ||
|
|
cb146200aa | ||
|
|
609926bff1 | ||
|
|
8803058521 | ||
|
|
c74805b12b | ||
|
|
43870237fc | ||
|
|
ca24085b5b | ||
|
|
93191c2b8b | ||
|
|
ebc3ded2b2 | ||
|
|
d9d1c874ab | ||
|
|
08477fc361 | ||
|
|
11836b876f | ||
|
|
bf20965c46 | ||
|
|
22a48e52eb | ||
|
|
15b703b5f2 | ||
|
|
18f0eb8a36 | ||
|
|
32cdcc6fbe | ||
|
|
437c4b9bc5 | ||
|
|
0a42dbf5ba | ||
|
|
ea4f319898 | ||
|
|
7b6b944106 | ||
|
|
59c966771e | ||
|
|
a83c20b667 | ||
|
|
d5c4f85d73 | ||
|
|
36f683a299 | ||
|
|
652e9719ed | ||
|
|
011b658422 | ||
|
|
5fa1050e1b | ||
|
|
39617497ca | ||
|
|
44ad960da7 | ||
|
|
db223a1e92 | ||
|
|
ea4b8f51c2 | ||
|
|
407826396b | ||
|
|
124547c9c5 | ||
|
|
db7a9df880 | ||
|
|
29725214f7 | ||
|
|
9189de4db1 | ||
|
|
fec0032883 | ||
|
|
1ee737b1aa | ||
|
|
2a32e1d746 | ||
|
|
bdaf50ab0f | ||
|
|
37a129080a | ||
|
|
024b6ce292 | ||
|
|
b13a12e3d5 | ||
|
|
9d9738eed0 | ||
|
|
135fe1e5d4 | ||
|
|
2d033b6b87 | ||
|
|
9048ffbdab | ||
|
|
d43fdd3cb4 | ||
|
|
14d16eff46 | ||
|
|
1a603c2b48 | ||
|
|
2092161fe0 | ||
|
|
a8b92f50cc | ||
|
|
4556c9a5d1 | ||
|
|
e6fcae4f02 | ||
|
|
74ab5488e1 | ||
|
|
5cdec242eb | ||
|
|
e938a499f6 | ||
|
|
84b5915eea | ||
|
|
540ce1d2c3 | ||
|
|
b40f2af0f0 | ||
|
|
324d09a61e | ||
|
|
d5dfecefea | ||
|
|
ce37e17c87 | ||
|
|
3b1d5f15f4 | ||
|
|
714ec4695b | ||
|
|
3795d92d7a | ||
|
|
eed685507a | ||
|
|
26d3ec14a6 | ||
|
|
96d475407a | ||
|
|
27d8bc458f | ||
|
|
519f86c47e | ||
|
|
980f71c1bd | ||
|
|
7692665428 | ||
|
|
aaca0b5283 | ||
|
|
e5a495489d | ||
|
|
e07a6fb3ff | ||
|
|
b8c2660996 | ||
|
|
ec2ecdcacb | ||
|
|
3c3ab77f65 | ||
|
|
ffb2c89c36 | ||
|
|
bda62cc8a1 | ||
|
|
a052af4180 | ||
|
|
8835c37536 | ||
|
|
69f3c9f6ab | ||
|
|
af3e0bd6a5 | ||
|
|
5b14512323 | ||
|
|
6f176ad1c4 | ||
|
|
bc9d720a3c | ||
|
|
93c5013251 | ||
|
|
bc5c993093 | ||
|
|
759b3c5baf | ||
|
|
e1303d13a3 | ||
|
|
8824115697 | ||
|
|
a78a1e7d2e | ||
|
|
9e150b2c44 | ||
|
|
217d230b61 | ||
|
|
ba1b3c4fc7 | ||
|
|
5f871cd713 | ||
|
|
4a37422af5 | ||
|
|
a081fc87c9 | ||
|
|
0e8286e195 | ||
|
|
e9d53c4084 | ||
|
|
697a0ac9fc | ||
|
|
7f12e1c19a | ||
|
|
bf99cb50fd | ||
|
|
1333f0113f | ||
|
|
1b544bd22d | ||
|
|
24f514c949 | ||
|
|
8eed26e0a1 | ||
|
|
3c5b3f0a49 | ||
|
|
1d23b4bb67 | ||
|
|
48e533f660 | ||
|
|
86fbed60eb | ||
|
|
164341b543 | ||
|
|
94942fe488 | ||
|
|
ca64f6c8ac | ||
|
|
e8ef483098 | ||
|
|
85faf43fdd | ||
|
|
09620c269e | ||
|
|
0c05a4c375 | ||
|
|
298aa7960e | ||
|
|
b112513df0 | ||
|
|
f19b5a453a | ||
|
|
72e54653a2 | ||
|
|
5696d72a3f | ||
|
|
757d20166a | ||
|
|
1336996129 | ||
|
|
4f35240203 | ||
|
|
5565a6b3f8 | ||
|
|
9dbe135e66 | ||
|
|
fe5c031701 | ||
|
|
0511a90640 | ||
|
|
2c87dfbb35 | ||
|
|
9b4d07008d | ||
|
|
c40218a919 | ||
|
|
0c410b87ec | ||
|
|
114a07a440 | ||
|
|
babae3b2ad | ||
|
|
a7a9f86a71 | ||
|
|
c65fcd5133 | ||
|
|
b298b4203a | ||
|
|
250310fc9f | ||
|
|
89cf05d3a4 | ||
|
|
1d258196b1 | ||
|
|
63ebf730f9 | ||
|
|
810889a964 | ||
|
|
0a97eee7ad | ||
|
|
cc8f0e456a | ||
|
|
0f7e8d3214 | ||
|
|
2c193e0dd4 | ||
|
|
2512239ea7 |
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -60,8 +60,9 @@ body:
|
||||
value: |
|
||||
- Device:
|
||||
- OS Version:
|
||||
- KernelSU Version:
|
||||
- Kernel Version:
|
||||
- KSUN Driver Version:
|
||||
- KSUN Manager Version:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature Request
|
||||
url: https://t.me/ksunext_group
|
||||
about: "We accept external Feature Requests, see this link for more details."
|
||||
blank_issues_enabled: false
|
||||
41
.github/ISSUE_TEMPLATE/request.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/request.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature or improvement for KernelSU Next.
|
||||
title: "[Feature] <short description>"
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to suggest a feature! Please fill out the following details.
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: "Is your feature request related to a problem?"
|
||||
description: "A clear and concise description of what the problem is. Ex: I'm always frustrated when..."
|
||||
placeholder: "Describe the problem or need."
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: "Describe the solution you'd like"
|
||||
description: "A clear and concise description of what you want to happen."
|
||||
placeholder: "Describe your proposed solution."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: "Describe alternatives you've considered"
|
||||
description: "A clear and concise description of any alternative solutions or features you've considered."
|
||||
placeholder: "List any alternatives."
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: "Additional context"
|
||||
description: "Add any other context or screenshots about the feature request here."
|
||||
placeholder: "Additional context or screenshots."
|
||||
validations:
|
||||
required: false
|
||||
2
.github/workflows/avd-kernel.yml
vendored
2
.github/workflows/avd-kernel.yml
vendored
@@ -37,7 +37,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.version_name }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Maximize build space
|
||||
uses: easimon/maximize-build-space@master
|
||||
|
||||
26
.github/workflows/build-debug-kernel.yml
vendored
26
.github/workflows/build-debug-kernel.yml
vendored
@@ -7,9 +7,9 @@ jobs:
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android12-5.10
|
||||
version_name: android12-5.10.226
|
||||
tag: android12-5.10-2024-11
|
||||
os_patch_level: 2024-11
|
||||
version_name: android12-5.10.236
|
||||
tag: android12-5.10-2025-05
|
||||
os_patch_level: 2025-05
|
||||
patch_path: "5.10"
|
||||
debug: true
|
||||
build-debug-kernel-a13:
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 228
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
- version: "5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android13-${{ matrix.version }}
|
||||
@@ -34,11 +34,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
- version: "6.1"
|
||||
sub_level: 118
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 138
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android14-${{ matrix.version }}
|
||||
@@ -51,8 +51,8 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "6.6"
|
||||
sub_level: 58
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 89
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android15-${{ matrix.version }}
|
||||
|
||||
10
.github/workflows/build-kernel-a12.yml
vendored
10
.github/workflows/build-kernel-a12.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- sub_level: 209
|
||||
os_patch_level: 2024-05
|
||||
- sub_level: 218
|
||||
os_patch_level: 2024-08
|
||||
- sub_level: 226
|
||||
os_patch_level: 2024-11
|
||||
- sub_level: 233
|
||||
os_patch_level: 2025-02
|
||||
- sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android12-5.10
|
||||
version_name: android12-5.10.223
|
||||
tag: android12-5.10-2024-11
|
||||
os_patch_level: 2024-11
|
||||
version_name: android12-5.10.236
|
||||
tag: android12-5.10-2025-05
|
||||
os_patch_level: 2025-05
|
||||
patch_path: "5.10"
|
||||
26
.github/workflows/build-kernel-a13.yml
vendored
26
.github/workflows/build-kernel-a13.yml
vendored
@@ -21,9 +21,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 209
|
||||
os_patch_level: 2024-05
|
||||
- version: "5.10"
|
||||
sub_level: 210
|
||||
os_patch_level: 2024-06
|
||||
@@ -39,9 +36,12 @@ jobs:
|
||||
- version: "5.10"
|
||||
sub_level: 228
|
||||
os_patch_level: 2025-01
|
||||
- version: "5.15"
|
||||
sub_level: 148
|
||||
os_patch_level: 2024-05
|
||||
- version: "5.10"
|
||||
sub_level: 234
|
||||
os_patch_level: 2025-03
|
||||
- version: "5.10"
|
||||
sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
- version: "5.15"
|
||||
sub_level: 149
|
||||
os_patch_level: 2024-07
|
||||
@@ -57,6 +57,12 @@ jobs:
|
||||
- version: "5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
- version: "5.15"
|
||||
sub_level: 178
|
||||
os_patch_level: 2025-03
|
||||
- version: "5.15"
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -143,11 +149,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 228
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
- version: "5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android13-${{ matrix.version }}
|
||||
|
||||
32
.github/workflows/build-kernel-a14.yml
vendored
32
.github/workflows/build-kernel-a14.yml
vendored
@@ -21,9 +21,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.15"
|
||||
sub_level: 148
|
||||
os_patch_level: 2024-05
|
||||
- version: "5.15"
|
||||
sub_level: 149
|
||||
os_patch_level: 2024-06
|
||||
@@ -42,9 +39,12 @@ jobs:
|
||||
- version: "5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
- version: "6.1"
|
||||
sub_level: 75
|
||||
os_patch_level: 2024-05
|
||||
- version: "5.15"
|
||||
sub_level: 178
|
||||
os_patch_level: 2025-03
|
||||
- version: "5.15"
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
- version: "6.1"
|
||||
sub_level: 78
|
||||
os_patch_level: 2024-06
|
||||
@@ -72,6 +72,18 @@ jobs:
|
||||
- version: "6.1"
|
||||
sub_level: 124
|
||||
os_patch_level: 2025-02
|
||||
- version: "6.1"
|
||||
sub_level: 128
|
||||
os_patch_level: 2025-03
|
||||
- version: "6.1"
|
||||
sub_level: 129
|
||||
os_patch_level: 2025-04
|
||||
- version: "6.1"
|
||||
sub_level: 134
|
||||
os_patch_level: 2025-05
|
||||
- version: "6.1"
|
||||
sub_level: 138
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -158,11 +170,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
- version: "6.1"
|
||||
sub_level: 118
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 138
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android14-${{ matrix.version }}
|
||||
|
||||
16
.github/workflows/build-kernel-a15.yml
vendored
16
.github/workflows/build-kernel-a15.yml
vendored
@@ -42,6 +42,18 @@ jobs:
|
||||
- version: "6.6"
|
||||
sub_level: 66
|
||||
os_patch_level: 2025-02
|
||||
- version: "6.6"
|
||||
sub_level: 77
|
||||
os_patch_level: 2025-03
|
||||
- version: "6.6"
|
||||
sub_level: 82
|
||||
os_patch_level: 2025-04
|
||||
- version: "6.6"
|
||||
sub_level: 87
|
||||
os_patch_level: 2025-05
|
||||
- version: "6.6"
|
||||
sub_level: 89
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -128,8 +140,8 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "6.6"
|
||||
sub_level: 58
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 89
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android15-${{ matrix.version }}
|
||||
|
||||
137
.github/workflows/build-kernel-arcvm.yml
vendored
Normal file
137
.github/workflows/build-kernel-arcvm.yml
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
name: Build Kernel - ChromeOS ARCVM
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-arcvm.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-arcvm.yml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
git_tag: chromeos-5.10-arcvm
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: x86_64
|
||||
kernel_image_name: bzImage
|
||||
build_config: build.config.gki.x86_64
|
||||
defconfig: x86_64_arcvm_defconfig
|
||||
- arch: arm64
|
||||
kernel_image_name: Image
|
||||
build_config: build.config.gki.aarch64
|
||||
defconfig: arm64_arcvm_defconfig
|
||||
|
||||
name: Build ChromeOS ARCVM kernel
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
LTO: thin
|
||||
ROOT_DIR: /
|
||||
KERNEL_DIR: ${{ github.workspace }}/kernel
|
||||
|
||||
steps:
|
||||
- name: Install Build Tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends bc \
|
||||
bison build-essential ca-certificates flex git gnupg \
|
||||
libelf-dev libssl-dev lsb-release software-properties-common wget \
|
||||
libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget gzip \
|
||||
rsync python3 device-tree-compiler
|
||||
|
||||
sudo ln -s --force python3 /usr/bin/python
|
||||
|
||||
export LLVM_VERSION=14
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh $LLVM_VERSION
|
||||
rm ./llvm.sh
|
||||
sudo ln -s --force /usr/bin/clang-$LLVM_VERSION /usr/bin/clang
|
||||
sudo ln -s --force /usr/bin/ld.lld-$LLVM_VERSION /usr/bin/ld.lld
|
||||
sudo ln -s --force /usr/bin/llvm-objdump-$LLVM_VERSION /usr/bin/llvm-objdump
|
||||
sudo ln -s --force /usr/bin/llvm-ar-$LLVM_VERSION /usr/bin/llvm-ar
|
||||
sudo ln -s --force /usr/bin/llvm-nm-$LLVM_VERSION /usr/bin/llvm-nm
|
||||
sudo ln -s --force /usr/bin/llvm-strip-$LLVM_VERSION /usr/bin/llvm-strip
|
||||
sudo ln -s --force /usr/bin/llvm-objcopy-$LLVM_VERSION /usr/bin/llvm-objcopy
|
||||
sudo ln -s --force /usr/bin/llvm-readelf-$LLVM_VERSION /usr/bin/llvm-readelf
|
||||
sudo ln -s --force /usr/bin/clang++-$LLVM_VERSION /usr/bin/clang++
|
||||
|
||||
- name: Checkout KernelSU-Next
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU-Next
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup kernel source
|
||||
run: git clone https://chromium.googlesource.com/chromiumos/third_party/kernel.git -b ${{ env.git_tag }} --depth=1
|
||||
|
||||
- name: Extract version from Makefile
|
||||
working-directory: kernel
|
||||
run: |
|
||||
VERSION=$(grep -E '^VERSION = ' Makefile | awk '{print $3}')
|
||||
PATCHLEVEL=$(grep -E '^PATCHLEVEL = ' Makefile | awk '{print $3}')
|
||||
SUBLEVEL=$(grep -E '^SUBLEVEL = ' Makefile | awk '{print $3}')
|
||||
echo "ChromeOS ARCVM Linux kernel version: $VERSION.$PATCHLEVEL.$SUBLEVEL"
|
||||
echo "version=$VERSION.$PATCHLEVEL.$SUBLEVEL" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup KernelSU-Next
|
||||
working-directory: kernel
|
||||
run: |
|
||||
echo "[+] KernelSU-Next setup"
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/kernel
|
||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
||||
echo "[+] Copy KernelSU-Next driver to $KERNEL_ROOT/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU-Next/kernel $KERNEL_ROOT/drivers/kernelsu-next
|
||||
|
||||
echo "[+] Add KernelSU-Next driver to Makefile"
|
||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
||||
DRIVER_KCONFIG=$KERNEL_ROOT/drivers/Kconfig
|
||||
grep -q "kernelsu-next" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu-next/\n" >> "$DRIVER_MAKEFILE"
|
||||
grep -q "kernelsu-next" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu-next/Kconfig\"" "$DRIVER_KCONFIG"
|
||||
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU-Next/.github/patches/5.10/*.patch || echo "[-] No patch found"
|
||||
|
||||
echo "[+] Patch script/setlocalversion"
|
||||
sed -i 's/-dirty//g' $KERNEL_ROOT/scripts/setlocalversion
|
||||
|
||||
echo "[+] KernelSU-Next setup done."
|
||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
||||
KSU_VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "KernelSU-Next version: $KSU_VERSION"
|
||||
echo "kernelsu-next_version=$KSU_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Kernel
|
||||
working-directory: kernel
|
||||
env:
|
||||
KERNEL_IMAGE_NAME: ${{ matrix.kernel_image_name }}
|
||||
ARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
set -a && . ${{ matrix.build_config }}; set +a
|
||||
export DEFCONFIG=${{ matrix.defconfig }}
|
||||
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
|
||||
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
|
||||
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
|
||||
fi
|
||||
|
||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} mrproper
|
||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} ${DEFCONFIG} < /dev/null
|
||||
scripts/config --file .config -e LTO_CLANG -d LTO_NONE -e LTO_CLANG_THIN -d LTO_CLANG_FULL -e THINLTO
|
||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} -j$(nproc) ${KERNEL_IMAGE_NAME} modules prepare-objtool
|
||||
ls -l -h ${PWD}/arch/${ARCH}/boot
|
||||
echo "file_path=${PWD}/arch/${ARCH}/boot/${KERNEL_IMAGE_NAME}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload kernel-ARCVM-${{ matrix.arch }}-${{ env.version }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kernel-ARCVM-${{ matrix.arch }}-${{ env.version }}
|
||||
path: "${{ env.file_path }}"
|
||||
39
.github/workflows/build-kernel-avd.yml
vendored
Normal file
39
.github/workflows/build-kernel-avd.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Build Kernel - AVD
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-avd.yml"
|
||||
- ".github/workflows/avd-kernel.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-avd.yml"
|
||||
- ".github/workflows/avd-kernel.yml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-kernel:
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
||||
uses: ./.github/workflows/avd-kernel.yml
|
||||
secrets: inherit
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "android-14-avd_x86_64"
|
||||
manifest: "android-14-avd_x86_64.xml"
|
||||
arch: "x86_64"
|
||||
- version: "android-15-avd_aarch64"
|
||||
manifest: "android-15-avd_aarch64.xml"
|
||||
arch: "aarch64"
|
||||
- version: "android-15-avd_x86_64"
|
||||
manifest: "android-15-avd_x86_64.xml"
|
||||
arch: "x86_64"
|
||||
with:
|
||||
version_name: ${{ matrix.version }}
|
||||
manifest_name: ${{ matrix.manifest }}
|
||||
arch: ${{ matrix.arch }}
|
||||
debug: true
|
||||
38
.github/workflows/build-kernel-wsa.yml
vendored
Normal file
38
.github/workflows/build-kernel-wsa.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Build Kernel - WSA
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-wsa.yml"
|
||||
- ".github/workflows/wsa-kernel.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-wsa.yml"
|
||||
- ".github/workflows/wsa-kernel.yml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64, arm64]
|
||||
version: ["5.15.94.2", "5.15.104.1", "5.15.104.2", "5.15.104.3", "5.15.104.4"]
|
||||
uses: ./.github/workflows/wsa-kernel.yml
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
version: ${{ matrix.version }}
|
||||
|
||||
check_build:
|
||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
|
||||
uses: ./.github/workflows/wsa-kernel.yml
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64, arm64]
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
version: "5.15.104.4"
|
||||
24
.github/workflows/build-lkm.yml
vendored
24
.github/workflows/build-lkm.yml
vendored
@@ -15,23 +15,23 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "android12-5.10"
|
||||
sub_level: 233
|
||||
os_patch_level: 2025-02
|
||||
sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
- version: "android13-5.10"
|
||||
sub_level: 228
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
- version: "android13-5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
- version: "android14-5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
- version: "android14-6.1"
|
||||
sub_level: 124
|
||||
os_patch_level: 2025-02
|
||||
sub_level: 138
|
||||
os_patch_level: 2025-06
|
||||
- version: "android15-6.6"
|
||||
sub_level: 66
|
||||
os_patch_level: 2025-02
|
||||
sub_level: 89
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: ${{ matrix.version }}
|
||||
|
||||
212
.github/workflows/build-manager-ci.yml
vendored
212
.github/workflows/build-manager-ci.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build Manager
|
||||
name: Build Manager CI
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -19,36 +19,110 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-cache:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
cache-hit: ${{ steps.cache-artifacts.outputs.cache-hit }}
|
||||
cache-key: ${{ steps.generate-cache-key.outputs.cache-key }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate cache key from source files
|
||||
id: generate-cache-key
|
||||
run: |
|
||||
# Calculate hash of all files except manager directory
|
||||
HASH=$(find . -type f \
|
||||
-not -path "./manager/*" \
|
||||
-not -path "./.git/*" \
|
||||
-not -path "./.github/workflows/build-manager-ci.yml" \
|
||||
-exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1)
|
||||
echo "cache-key=artifacts-$HASH" >> $GITHUB_OUTPUT
|
||||
echo "Generated cache key: artifacts-$HASH"
|
||||
|
||||
- name: Check for cached artifacts
|
||||
id: cache-artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ steps.generate-cache-key.outputs.cache-key }}
|
||||
|
||||
build-lkm:
|
||||
needs: check-cache
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: ./.github/workflows/build-lkm.yml
|
||||
secrets: inherit
|
||||
|
||||
build-susfsd:
|
||||
needs: build-lkm
|
||||
needs: [check-cache, build-lkm]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
uses: ./.github/workflows/susfsd.yml
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud:
|
||||
needs: [check-cache, build-susfsd]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/susfsd.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud:
|
||||
needs: build-susfsd
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
- target: armv7-linux-androideabi
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
cache-artifacts:
|
||||
needs: [check-cache, build-ksud]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download susfsd artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-linux-android
|
||||
path: cached-artifacts/susfsd
|
||||
|
||||
- name: Download ksud_overlayfs artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: ksud_overlayfs-*
|
||||
path: cached-artifacts/ksud_overlayfs
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download ksud_magic artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: ksud_magic-*
|
||||
path: cached-artifacts/ksud_magic
|
||||
merge-multiple: true
|
||||
|
||||
- name: Cache artifacts
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ needs.check-cache.outputs.cache-key }}
|
||||
|
||||
build-manager:
|
||||
needs: build-ksud
|
||||
needs: [check-cache, build-ksud]
|
||||
if: always() && needs.check-cache.result == 'success' && (needs.check-cache.outputs.cache-hit == 'true' || needs.build-ksud.result == 'success')
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
@@ -90,36 +164,122 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Download arm64 susfsd
|
||||
# Restore cached artifacts if cache hit
|
||||
- name: Restore cached artifacts
|
||||
if: needs.check-cache.outputs.cache-hit == 'true'
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ needs.check-cache.outputs.cache-key }}
|
||||
|
||||
# Download fresh artifacts if cache miss
|
||||
- name: Download susfsd
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-aarch64-linux-android
|
||||
name: susfsd-linux-android
|
||||
path: .
|
||||
|
||||
- name: Copy susfsd to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
|
||||
# Download fresh ksud artifacts if cache miss
|
||||
- name: Download arm64 ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-aarch64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download arm ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-armv7-linux-androideabi
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Copy ksud_overlayfs to app jniLibs
|
||||
run: |
|
||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
- name: Download x86_64 ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-x86_64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download arm64 ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-aarch64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download arm ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-armv7-linux-androideabi
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download x86_64 ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-x86_64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
# Copy artifacts to jniLibs (works for both cached and fresh)
|
||||
- name: Copy susfsd to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/susfsd/arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
cp -f ../cached-artifacts/susfsd/armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
cp -f ../cached-artifacts/susfsd/x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
cp -f ../armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
cp -f ../x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
fi
|
||||
|
||||
- name: Copy ksud_overlayfs to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
cp -f ../ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
cp -f ../ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
fi
|
||||
|
||||
- name: Copy ksud_magic to app jniLibs
|
||||
run: |
|
||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
cp -f ../cached-artifacts/ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
cp -f ../cached-artifacts/ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
cp -f ../ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
cp -f ../ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
fi
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
@@ -171,4 +331,4 @@ jobs:
|
||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
||||
pip3 install telethon
|
||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
||||
fi
|
||||
fi
|
||||
|
||||
341
.github/workflows/build-manager-spoofed.yml
vendored
Normal file
341
.github/workflows/build-manager-spoofed.yml
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
name: Build Manager Spoofed
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "next" ]
|
||||
paths:
|
||||
- '.github/workflows/build-manager-ci.yml'
|
||||
- 'manager/**'
|
||||
- 'kernel/**'
|
||||
- 'userspace/ksud_overlayfs**'
|
||||
- 'userspace/ksud_magic/**'
|
||||
- 'userspace/susfsd/**'
|
||||
pull_request:
|
||||
branches: [ "next" ]
|
||||
paths:
|
||||
- '.github/workflows/build-manager-ci.yml'
|
||||
- 'manager/**'
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 12 * * *" # 6 PM UTC+6 | 12 PM UTC
|
||||
|
||||
jobs:
|
||||
check-cache:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
cache-hit: ${{ steps.cache-artifacts.outputs.cache-hit }}
|
||||
cache-key: ${{ steps.generate-cache-key.outputs.cache-key }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate cache key from source files
|
||||
id: generate-cache-key
|
||||
run: |
|
||||
# Calculate hash of all files except manager directory
|
||||
HASH=$(find . -type f \
|
||||
-not -path "./manager/*" \
|
||||
-not -path "./.git/*" \
|
||||
-not -path "./.github/workflows/build-manager-spoofed.yml" \
|
||||
-exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1)
|
||||
echo "cache-key=artifacts-spoofed-$HASH" >> $GITHUB_OUTPUT
|
||||
echo "Generated cache key: artifacts-spoofed-$HASH"
|
||||
|
||||
- name: Check for cached artifacts
|
||||
id: cache-artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ steps.generate-cache-key.outputs.cache-key }}
|
||||
|
||||
build-lkm:
|
||||
needs: check-cache
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: ./.github/workflows/build-lkm.yml
|
||||
secrets: inherit
|
||||
|
||||
build-susfsd:
|
||||
needs: [check-cache, build-lkm]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
uses: ./.github/workflows/susfsd.yml
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud:
|
||||
needs: [check-cache, build-susfsd]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
- target: armv7-linux-androideabi
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
cache-artifacts:
|
||||
needs: [check-cache, build-ksud]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download susfsd artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-linux-android
|
||||
path: cached-artifacts/susfsd
|
||||
|
||||
- name: Download ksud_overlayfs artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: ksud_overlayfs-*
|
||||
path: cached-artifacts/ksud_overlayfs
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download ksud_magic artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: ksud_magic-*
|
||||
path: cached-artifacts/ksud_magic
|
||||
merge-multiple: true
|
||||
|
||||
- name: Cache artifacts
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ needs.check-cache.outputs.cache-key }}
|
||||
|
||||
build-manager:
|
||||
needs: [check-cache, build-ksud]
|
||||
if: always() && needs.check-cache.result == 'success' && (needs.check-cache.outputs.cache-hit == 'true' || needs.build-ksud.result == 'success')
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./manager
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Spoof Package ID
|
||||
run: |
|
||||
chmod +x spoof
|
||||
./spoof
|
||||
|
||||
- name: Setup need_upload
|
||||
id: need_upload
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
echo "UPLOAD=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Write Key
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||
{
|
||||
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}'
|
||||
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}'
|
||||
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}'
|
||||
echo KEYSTORE_FILE='key.jks'
|
||||
} >> gradle.properties
|
||||
echo ${{ secrets.KEYSTORE }} | base64 -d > key.jks
|
||||
fi
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
# Restore cached artifacts if cache hit
|
||||
- name: Restore cached artifacts
|
||||
if: needs.check-cache.outputs.cache-hit == 'true'
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ needs.check-cache.outputs.cache-key }}
|
||||
|
||||
# Download fresh artifacts if cache miss
|
||||
- name: Download susfsd
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-linux-android
|
||||
path: .
|
||||
|
||||
# Download fresh ksud artifacts if cache miss
|
||||
- name: Download arm64 ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-aarch64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download arm ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-armv7-linux-androideabi
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download x86_64 ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-x86_64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download arm64 ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-aarch64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download arm ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-armv7-linux-androideabi
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download x86_64 ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-x86_64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
# Copy artifacts to jniLibs (works for both cached and fresh)
|
||||
- name: Copy susfsd to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/susfsd/arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
cp -f ../cached-artifacts/susfsd/armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
cp -f ../cached-artifacts/susfsd/x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
cp -f ../armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
cp -f ../x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
fi
|
||||
|
||||
- name: Copy ksud_overlayfs to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
cp -f ../ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
cp -f ../ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
fi
|
||||
|
||||
- name: Copy ksud_magic to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
cp -f ../cached-artifacts/ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
cp -f ../cached-artifacts/ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
cp -f ../ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
cp -f ../ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
fi
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
{
|
||||
echo 'org.gradle.parallel=true'
|
||||
echo 'org.gradle.vfs.watch=true'
|
||||
echo 'org.gradle.jvmargs=-Xmx2048m'
|
||||
echo 'android.native.buildOutput=verbose'
|
||||
} >> gradle.properties
|
||||
sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties
|
||||
chmod +x gradlew
|
||||
./gradlew clean assembleRelease
|
||||
|
||||
- name: Upload Build Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Manager
|
||||
path: manager/app/build/outputs/apk/release/*.apk
|
||||
|
||||
- name: Upload Mappings
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Mappings
|
||||
path: manager/app/build/outputs/mapping/release/
|
||||
|
||||
- name: Bot Session Cache
|
||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: scripts/ksunextbot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Upload to Telegram
|
||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||
env:
|
||||
API_ID: ${{ secrets.API_ID }}
|
||||
API_HASH: ${{ secrets.API_HASH }}
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
TITLE: CI Manager (SPOOFED BUILD)
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
export VERSION=$(git rev-list --count HEAD)
|
||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
||||
pip3 install telethon
|
||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
||||
fi
|
||||
53
.github/workflows/build-manager.yml
vendored
53
.github/workflows/build-manager.yml
vendored
@@ -28,11 +28,9 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
- os: ubuntu-latest
|
||||
uses: ./.github/workflows/susfsd.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud:
|
||||
@@ -42,6 +40,10 @@ jobs:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
- target: armv7-linux-androideabi
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
@@ -90,36 +92,75 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Download arm64 susfsd
|
||||
- name: Download susfsd
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-aarch64-linux-android
|
||||
name: susfsd-linux-android
|
||||
path: .
|
||||
|
||||
- name: Copy susfsd to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
cp -f ../armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
cp -f ../x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
|
||||
|
||||
- name: Download arm64 ksud_overlayfs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-aarch64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download arm ksud_overlayfs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-armv7-linux-androideabi
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download x86_64 ksud_overlayfs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-x86_64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Copy ksud_overlayfs to app jniLibs
|
||||
run: |
|
||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
|
||||
cp -f ../ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
|
||||
cp -f ../ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
|
||||
- name: Download arm64 ksud_magic
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-aarch64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download arm ksud_magic
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-armv7-linux-androideabi
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download x86_64 ksud_magic
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-x86_64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
- name: Copy ksud_magic to app jniLibs
|
||||
run: |
|
||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
|
||||
cp -f ../ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
|
||||
cp -f ../ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
@@ -171,4 +212,4 @@ jobs:
|
||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
||||
pip3 install telethon
|
||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
||||
fi
|
||||
fi
|
||||
|
||||
8
.github/workflows/clippy.yml
vendored
8
.github/workflows/clippy.yml
vendored
@@ -45,4 +45,10 @@ jobs:
|
||||
- name: Run Clippy
|
||||
run: |
|
||||
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target aarch64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target aarch64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target aarch64-linux-android --release
|
||||
|
||||
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target armv7-linux-androideabi --release
|
||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target armv7-linux-androideabi --release
|
||||
|
||||
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target x86_64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target x86_64-linux-android --release
|
||||
17
.github/workflows/gki-kernel.yml
vendored
17
.github/workflows/gki-kernel.yml
vendored
@@ -195,9 +195,24 @@ jobs:
|
||||
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
|
||||
fi
|
||||
|
||||
- name: Setup Syscall Hooks
|
||||
if: ${{ inputs.build_lkm == false }}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/android-kernel/common
|
||||
echo "[+] Patch Kernel"
|
||||
curl -L https://github.com/KernelSU-Next/kernel_patches/raw/main/syscall_hook/min_scope_syscall_hooks_v1.4.patch | patch -p1 --fuzz=3
|
||||
echo "[+] Set Config"
|
||||
echo "CONFIG_KSU=y" >> ./arch/arm64/configs/gki_defconfig
|
||||
echo "CONFIG_KSU_KPROBES_HOOK=n" >> ./arch/arm64/configs/gki_defconfig
|
||||
echo "[+] Disable Config Check"
|
||||
sed -i 's/check_defconfig//' ./build.config.gki
|
||||
|
||||
- name: Make working directory clean to avoid dirty
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
if [ -f ./common/BUILD.bazel ]; then
|
||||
sed -i '/^[[:space:]]*"protected_exports_list"[[:space:]]*:[[:space:]]*"android\/abi_gki_protected_exports_aarch64",$/d' ./common/BUILD.bazel
|
||||
fi
|
||||
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
|
||||
git config --global user.email "bot@kernelsu.org"
|
||||
git config --global user.name "KernelSU-NextBot"
|
||||
@@ -255,4 +270,4 @@ jobs:
|
||||
if: ${{ inputs.build_lkm == true }}
|
||||
with:
|
||||
name: ${{ inputs.version }}-lkm
|
||||
path: ./output/*_kernelsu.ko
|
||||
path: ./output/*_kernelsu.ko
|
||||
|
||||
11
.github/workflows/ksud.yml
vendored
11
.github/workflows/ksud.yml
vendored
@@ -37,12 +37,17 @@ jobs:
|
||||
|
||||
- name: Import susfsd Libraries
|
||||
run: |
|
||||
cp susfsd-aarch64-linux-android/arm64-v8a/susfsd ./userspace/ksud_overlayfs/bin/aarch64/
|
||||
cp susfsd-aarch64-linux-android/arm64-v8a/susfsd ./userspace/ksud_magic/bin/aarch64/
|
||||
cp susfsd-linux-android/arm64-v8a/susfsd ./userspace/ksud_overlayfs/bin/aarch64/
|
||||
cp susfsd-linux-android/arm64-v8a/susfsd ./userspace/ksud_magic/bin/aarch64/
|
||||
cp susfsd-linux-android/armeabi-v7a/susfsd ./userspace/ksud_overlayfs/bin/arm/
|
||||
cp susfsd-linux-android/armeabi-v7a/susfsd ./userspace/ksud_magic/bin/arm/
|
||||
cp susfsd-linux-android/x86_64/susfsd ./userspace/ksud_overlayfs/bin/x86_64/
|
||||
cp susfsd-linux-android/x86_64/susfsd ./userspace/ksud_magic/bin/x86_64/
|
||||
|
||||
- name: Setup Rust
|
||||
run: |
|
||||
rustup update stable
|
||||
rustup target add x86_64-apple-darwin
|
||||
rustup target add aarch64-apple-darwin
|
||||
|
||||
- name: Cache ksud_overlayfs
|
||||
@@ -76,4 +81,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-${{ inputs.target }}
|
||||
path: userspace/ksud_magic/target/**/release/ksud*
|
||||
path: userspace/ksud_magic/target/**/release/ksud*
|
||||
|
||||
36
.github/workflows/release.yml
vendored
36
.github/workflows/release.yml
vendored
@@ -21,6 +21,12 @@ jobs:
|
||||
build-a15-kernel:
|
||||
uses: ./.github/workflows/build-kernel-a15.yml
|
||||
secrets: inherit
|
||||
build-wsa-kernel:
|
||||
uses: ./.github/workflows/build-kernel-wsa.yml
|
||||
secrets: inherit
|
||||
build-arcvm-kernel:
|
||||
uses: ./.github/workflows/build-kernel-arcvm.yml
|
||||
secrets: inherit
|
||||
release:
|
||||
needs:
|
||||
- build-manager
|
||||
@@ -28,6 +34,8 @@ jobs:
|
||||
- build-a13-kernel
|
||||
- build-a14-kernel
|
||||
- build-a15-kernel
|
||||
- build-wsa-kernel
|
||||
- build-arcvm-kernel
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
@@ -41,6 +49,24 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Zip WSA kernel
|
||||
run: |
|
||||
for dir in kernel-WSA-*; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "------ Zip $dir ----------"
|
||||
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Zip ChromeOS ARCVM kernel
|
||||
run: |
|
||||
for dir in kernel-ARCVM-*; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "------ Zip $dir ----------"
|
||||
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
@@ -48,10 +74,12 @@ jobs:
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
manager/*.apk
|
||||
Manager/*.apk
|
||||
android*-lkm/*_kernelsu.ko
|
||||
AnyKernel3-*.zip
|
||||
boot-images-*/Image-*/*.img.gz
|
||||
ksud_magic-*
|
||||
ksud_overlayfs-*
|
||||
susfsd-*
|
||||
kernel-WSA*.zip
|
||||
kernel-ARCVM*.zip
|
||||
ksud_magic*.zip
|
||||
ksud_overlayfs*.zip
|
||||
susfsd*.zip
|
||||
9
.github/workflows/susfsd.yml
vendored
9
.github/workflows/susfsd.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build susfsd
|
||||
name: Build susfsd aarch64
|
||||
on:
|
||||
push:
|
||||
branches: [ "next" ]
|
||||
@@ -8,9 +8,6 @@ on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
os:
|
||||
required: false
|
||||
type: string
|
||||
@@ -26,9 +23,9 @@ jobs:
|
||||
- name: Build susfsd
|
||||
working-directory: ./userspace/susfsd
|
||||
run: $ANDROID_NDK/ndk-build
|
||||
- name: Upload a Build Artifact
|
||||
- name: Upload Build Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: susfsd-aarch64-linux-android
|
||||
name: susfsd-linux-android
|
||||
path: ./userspace/susfsd/libs
|
||||
|
||||
|
||||
106
.github/workflows/wsa-kernel.yml
vendored
Normal file
106
.github/workflows/wsa-kernel.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
name: Build Kernel - WSA
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
arch:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
Build arch: x86_64 / arm64
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
Build version
|
||||
jobs:
|
||||
build:
|
||||
name: Build WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }}
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_HARDLINK: "true"
|
||||
|
||||
steps:
|
||||
- name: Install Build Tools
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
with:
|
||||
packages: bc bison build-essential flex libelf-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu gzip ccache
|
||||
version: 1.0
|
||||
|
||||
- name: Cache LLVM
|
||||
id: cache-llvm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./llvm
|
||||
key: llvm-12.0.1
|
||||
|
||||
- name: Setup LLVM
|
||||
uses: KyleMayes/install-llvm-action@v1
|
||||
with:
|
||||
version: "12.0.1"
|
||||
force-version: true
|
||||
ubuntu-version: "16.04"
|
||||
cached: ${{ steps.cache-llvm.outputs.cache-hit }}
|
||||
|
||||
- name: Checkout KernelSU-Next
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU-Next
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup kernel source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: microsoft/WSA-Linux-Kernel
|
||||
ref: android-lts/latte-2/${{ inputs.version }}
|
||||
path: WSA-Linux-Kernel
|
||||
|
||||
- name: Setup Ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }}
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
max-size: 2G
|
||||
|
||||
- name: Setup KernelSU-Next
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
echo "[+] KernelSU-Next setup"
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel
|
||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
||||
echo "[+] Copy KernelSU-Next driver to $KERNEL_ROOT/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU-Next/kernel $KERNEL_ROOT/drivers/kernelsu
|
||||
echo "[+] Add KernelSU-Next driver to Makefile"
|
||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
||||
DRIVER_KCONFIG=$KERNEL_ROOT/drivers/Kconfig
|
||||
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
|
||||
grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG"
|
||||
echo "[+] Apply KernelSU-Next patches"
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU-Next/.github/patches/5.15/*.patch || echo "[-] No patch found"
|
||||
echo "[+] KernelSU-Next setup done."
|
||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
||||
VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "VERSION: $VERSION"
|
||||
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Kernel
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
|
||||
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
|
||||
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
|
||||
fi
|
||||
declare -A ARCH_MAP=(["x86_64"]="x64" ["arm64"]="arm64")
|
||||
cp configs/wsa/config-wsa-${ARCH_MAP[${{ inputs.arch }}]} .config
|
||||
make olddefconfig
|
||||
declare -A FILE_NAME=(["x86_64"]="bzImage" ["arm64"]="Image")
|
||||
make -j`nproc` LLVM=1 ARCH=${{ inputs.arch }} $(if [ "${{ inputs.arch }}" == "arm64" ]; then echo CROSS_COMPILE=aarch64-linux-gnu; fi) ${FILE_NAME[${{ inputs.arch }}]} CCACHE="/usr/bin/ccache"
|
||||
declare -A ARCH_MAP_FILE=(["x86_64"]="x86" ["arm64"]="arm64")
|
||||
echo "file_path=WSA-Linux-Kernel/arch/${ARCH_MAP_FILE[${{ inputs.arch }}]}/boot/${FILE_NAME[${{ inputs.arch }}]}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload kernel-${{ inputs.arch }}-${{ inputs.version }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kernel-WSA-${{ inputs.arch }}-${{ inputs.version }}
|
||||
path: "${{ env.file_path }}"
|
||||
31
build.sh
Executable file
31
build.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script builds the KernelSU Next manager APK.
|
||||
|
||||
# Ensure you have the setup Android SDK & NDK installed and necessary environment variables set and sourced.
|
||||
|
||||
# For LKM make sure you have imported the androidX-X.X_kernelsu.ko drivers to userspace/ksud_*/bin/aarch64 directory.
|
||||
|
||||
cross build --target aarch64-linux-android --release --manifest-path ./userspace/ksud_magic/Cargo.toml
|
||||
|
||||
cp userspace/ksud_magic/target/aarch64-linux-android/release/ksud manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
|
||||
cross build --target aarch64-linux-android --release --manifest-path ./userspace/ksud_overlayfs/Cargo.toml
|
||||
|
||||
cp userspace/ksud_overlayfs/target/aarch64-linux-android/release/ksud manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
|
||||
cd userspace/susfsd/jni
|
||||
|
||||
ndk-build
|
||||
|
||||
cp ../libs/arm64-v8a/susfsd ../../../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
|
||||
cd ../../..
|
||||
|
||||
cd manager
|
||||
|
||||
./setup.sh
|
||||
|
||||
cd ..
|
||||
|
||||
adb install manager/app/build/outputs/apk/release/KernelSU_Next_v*.apk
|
||||
2
crowdin.yml
Normal file
2
crowdin.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
bundles:
|
||||
- 8
|
||||
106
docs/README.md
106
docs/README.md
@@ -1,63 +1,91 @@
|
||||
**English** | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
**English** | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Українська](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md) | [Español](README_ES.md)
|
||||
|
||||
# KernelSU Next
|
||||
---
|
||||
|
||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||
<div align="center">
|
||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
||||
|
||||
A kernel-based root solution for Android devices.
|
||||
<h2>KernelSU Next</h2>
|
||||
<p><strong>A kernel-based root solution for Android devices.</strong></p>
|
||||
|
||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/kernelsu-next"><img src="https://badges.crowdin.net/kernelsu-next/localized.svg"></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Features
|
||||
---
|
||||
|
||||
1. Kernel-based `su` and root access management.
|
||||
2. Module system based on dynamic mount system [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage.
|
||||
## 🚀 Features
|
||||
|
||||
## Compatibility state
|
||||
- Kernel-based `su` and root access management.
|
||||
- Module system based on [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) and [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
- [App Profile](https://kernelsu.org/guide/app-profile.html): Limit root privileges per app.
|
||||
|
||||
KernelSU Next officially supports most Android kernels starting from 4.4 up to 6.6.
|
||||
- GKI 2.0 (5.10+) kernels can run pre-built images and LKM/KMI.
|
||||
- GKI 1.0 (4.19 - 5.4) kernels need to rebuilt with KernelSU driver.
|
||||
- EOL (<4.14) kernels also need to be rebuilt with KernelSU driver (3.18+ is experimental and may need some function backports).
|
||||
---
|
||||
|
||||
Currently, only the `arm64-v8a` architecture is supported.
|
||||
## ✅ Compatibility
|
||||
|
||||
## Usage
|
||||
KernelSU Next supports Android kernels from **4.4 up to 6.6**:
|
||||
|
||||
- [Installation instruction](https://ksunext.org/pages/installation.html)
|
||||
| Kernel version | Support notes |
|
||||
|----------------------|-------------------------------------------------------------------------|
|
||||
| 5.10+ (GKI 2.0) | Supports pre-built images and LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | Requires KernelSU driver built-in |
|
||||
| < 4.14 (EOL) | Requires KernelSU driver (3.18+ is experimental and may need backports) |
|
||||
|
||||
## Security
|
||||
**Supported architectures:** `arm64-v8a`, `armeabi-v7a` and `x86_64`
|
||||
|
||||
For information on reporting security vulnerabilities in KernelSU, see [SECURITY.md](/SECURITY.md).
|
||||
---
|
||||
|
||||
## License
|
||||
## 📦 Installation
|
||||
|
||||
- Files under the `kernel` directory are [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- All other parts except the `kernel` directory are [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
Please refer to the [Installation](https://kernelsu-next.github.io/webpage/pages/installation.html) guide for setup instructions.
|
||||
|
||||
## Donations
|
||||
---
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
||||
## 🔐 Security
|
||||
|
||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
||||
To report security issues, please see [SECURITY.md](/SECURITY.md).
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
||||
---
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
||||
## 📜 License
|
||||
|
||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
||||
- **`/kernel` directory:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- **All other files:** [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
||||
---
|
||||
|
||||
## Credits
|
||||
## 💸 Donations
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): The KernelSU idea.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): The powerful root tool.
|
||||
- [genuine](https://github.com/brevent/genuine/): APK v2 signature validation.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit skills.
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): Thanks to tiann, or else KernelSU Next wouldn't even exist.
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff for saving KernelSU!
|
||||
If you’d like to support the project:
|
||||
|
||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **USDT (SOL)**: `A4wqBXYd6Ey4nK4SJ2bmjeMgGyaLKT9TwDLh8BEo8Zu6`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Credits
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Concept inspiration
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Core root implementation
|
||||
- [Genuine](https://github.com/brevent/genuine/) – APK v2 signature validation
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Rootkit techniques
|
||||
- [KernelSU](https://github.com/tiann/KernelSU) – The original base that made KernelSU Next possible
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – For magic mount support
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | **Български**
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | **Български** | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
|
||||
@@ -24,7 +24,7 @@ KernelSU Next официално поддържа повечето Android яд
|
||||
- Ядра GKI 1.0 (4.19 - 5.4) изискват прекомпилиране с драйвера на KernelSU
|
||||
- Остарели ядра (<4.14) също изискват прекомпилиране (3.18+ е експериментална поддръжка)
|
||||
|
||||
В момента се поддържа само архитектурата `arm64-v8a`.
|
||||
В момента се поддържа само архитектурата `arm64-v8a`, `armeabi-v7a` & `x86_64`.
|
||||
|
||||
## Инсталация
|
||||
|
||||
|
||||
@@ -1,49 +1,90 @@
|
||||
[English](README.md) | **简体中文** | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | **简体中文** | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
---
|
||||
|
||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||
<div align="center">
|
||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
||||
|
||||
<h2>KernelSU Next</h2>
|
||||
<p><strong>安卓设备基于内核的 Root 方案</strong></p>
|
||||
|
||||
安卓基于内核的 Root 方案
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
---
|
||||
|
||||
## 特性
|
||||
## 🚀 特性
|
||||
|
||||
1. 基于内核的 `SU` 和权限管理
|
||||
2. 基于动态挂载系统 [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模块系统。
|
||||
3. [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 权限关进笼子里
|
||||
- 基于内核的 `su` 和超级用户权限管理
|
||||
- 动态挂载系统基于 **[Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount)** 以及 **[OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)**
|
||||
- [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 权限关进笼子里
|
||||
|
||||
## 兼容状态
|
||||
---
|
||||
|
||||
KernelSU Next 支持从 4.4 到 6.6 的大多数安卓内核
|
||||
- GKI 2.0(5.10+)内核可运行预置镜像和 LKM/KMI
|
||||
- GKI 1.0(4.19 - 5.4)内核需要使用 KernelSU 内核驱动重新编译
|
||||
- EOL (<4.14) 内核也需要使用 KernelSU 内核驱动重新编译 (3.18+ 的版本处于试验阶段,可能需要移植一些功能)
|
||||
## ✅ 兼容性
|
||||
|
||||
目前只支持 `arm64-v8a` 架构
|
||||
KernelSU Next 支持从 **4.4 到 6.6** 的大多数安卓内核
|
||||
|
||||
## 用法
|
||||
| 内核版本 | 支持情况 |
|
||||
|----------------|---------------|
|
||||
| 5.10+ (GKI 2.0) | 可运行预置镜像和 LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | 需要使用 KernelSU 内核驱动重新编译 |
|
||||
| <4.14 (EOL) | 需要使用 KernelSU 内核驱动重新编译(3.18+ 的版本处于试验阶段,可能需要进行回溯移植) |
|
||||
|
||||
- [安装说明](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
**支持的架构:**
|
||||
`arm64-v8a`、`armeabi-v7a`、`x86_64`
|
||||
|
||||
## 安全性
|
||||
---
|
||||
|
||||
## 📦 安装
|
||||
|
||||
请遵循该[安装说明](https://kernelsu-next.github.io/webpage/zh_CN/pages/installation.html)进行操作。
|
||||
|
||||
---
|
||||
|
||||
## 🔐 安全性
|
||||
|
||||
有关报告 KernelSU Next 漏洞的信息,请参阅 [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## 许可证
|
||||
---
|
||||
|
||||
- 目录 `kernel` 下所有文件为 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- `kernel` 目录以外的其他部分均为 [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
## 📜 许可
|
||||
|
||||
## 鸣谢
|
||||
- **目录 `/kernel` 下所有文件**为 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- **`/kernel` 目录以外的其他部分**均为 [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU 的灵感.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): 强大的 Root 工具.
|
||||
- [genuine](https://github.com/brevent/genuine/): APK v2 签名验证。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): 一些 Rootkit 技巧。
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): 感谢 tiann,否则 KernelSU Next 根本不会存在。
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff 為了拯救 KernelSU!
|
||||
---
|
||||
|
||||
## 💸 捐赠
|
||||
|
||||
如果你喜欢这个项目还请支持:
|
||||
|
||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 鸣谢
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的灵感.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):强大的 Root 工具.
|
||||
- [genuine](https://github.com/brevent/genuine/):APK v2 签名验证。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 Rootkit 技巧。
|
||||
- [KernelSU](https://github.com/tiann/KernelSU):感谢 tiann,否则 KernelSU Next 根本不会存在。
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs):💜 5ec1cff 为了拯救 KernelSU!
|
||||
|
||||
89
docs/README_ES.md
Normal file
89
docs/README_ES.md
Normal file
@@ -0,0 +1,89 @@
|
||||
**English** | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Українська](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md) | [Español](README_ES.md)
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
||||
|
||||
<h2>KernelSU Next</h2>
|
||||
<p><strong>Una solución de root basada en el kernel para tus dispositivos Android.</strong></p>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Características
|
||||
|
||||
- `su` y gestión de acceso root basados en el kernel.
|
||||
- Sistema de módulos basado en [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) y [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
- [Perfil de Aplicación](https://kernelsu.org/guide/app-profile.html): Limita los privilegios de root por aplicación.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Compatibilidad
|
||||
|
||||
KernelSU Next es compatible con kernels de Android desde la versión **4.4 hasta la 6.6**:
|
||||
|
||||
| Kernel version | Support notes |
|
||||
|----------------------|-----------------------------------------------------------------------------------|
|
||||
| 5.10+ (GKI 2.0) | Admite imágenes precompiladas y LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | Requiere que el driver de KernelSU esté integrado |
|
||||
| < 4.14 (EOL) | Requiere el driver de KernelSU (3.18+ es experimental y puede necesitar backports |
|
||||
|
||||
**Arquitecturas compatibles: ** `arm64-v8a`, `armeabi-v7a` y `x86_64`
|
||||
|
||||
---
|
||||
|
||||
## 📦 Instalación
|
||||
|
||||
Por favor, consulta la guía de [Instalación](https://kernelsu-next.github.io/webpage/pages/installation.html) para ver las instrucciones de configuración.
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Seguridad
|
||||
|
||||
Para informar sobre problemas de seguridad, por favor, consulta [SECURITY.md](/SECURITY.md).
|
||||
|
||||
---
|
||||
|
||||
## 📜 Licencia
|
||||
|
||||
- **Directorio `/kernel`:** [Solo-GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- **Todos los demás archivos:** [GPL-3.0-o-superior](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
---
|
||||
|
||||
## 💸 Donaciones
|
||||
|
||||
Si te gustaría apoyar el proyecto:
|
||||
|
||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Créditos
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Inspiración para el concepto
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Implementación principal del root
|
||||
- [Genuine](https://github.com/brevent/genuine/) – Validación de la firma v2 de los APK
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Técnicas de rootkit
|
||||
- [KernelSU](https://github.com/tiann/KernelSU) – La base original que hizo posible KernelSU Next
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 a 5ec1cff por mantener vivo KernelSU
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | **Français** | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | **Français** | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
|
||||
@@ -24,11 +24,11 @@ KernelSU Next prend officiellement en charge la plupart des noyaux Android de la
|
||||
- Les noyaux GKI 1.0 (4.19 - 5.4) doivent être reconstruits avec le pilote KernelSU.
|
||||
- Les noyaux EOL (<4.14) doivent également être reconstruits avec le pilote KernelSU (3.18+ est expérimental et peut nécessiter des rétroportages fonctionnels).
|
||||
|
||||
Actuellement, seul `arm64-v8a` est pris en charge.
|
||||
Actuellement, seul `arm64-v8a`, `armeabi-v7a` & `x86_64` est pris en charge.
|
||||
|
||||
## Utilisation
|
||||
|
||||
- [Instructions d'installation](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
- [Instructions d'installation](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## Sécurité
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | **Bahasa Indonesia** | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | **Bahasa Indonesia** | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
|
||||
@@ -24,11 +24,11 @@ KernelSU Next secara resmi mendukung sebagian besar kernel Android mulai dari 4.
|
||||
- Kernel GKI 1.0 (4.19 - 5.4) perlu dibangun ulang dengan driver KernelSU.
|
||||
- Kernel EOL (<4.14) juga perlu dibangun ulang dengan driver KernelSU (3.18+ bersifat eksperimental dan mungkin memerlukan beberapa backport fungsi).
|
||||
|
||||
Saat ini, hanya `arm64-v8a` yang didukung.
|
||||
Saat ini, hanya `arm64-v8a`, `armeabi-v7a` & `x86_64` yang didukung.
|
||||
|
||||
## Penggunaan
|
||||
|
||||
- [Petunjuk instalasi](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
- [Petunjuk instalasi](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## Keamanan
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | **Italiano** | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | **Italiano** | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
|
||||
@@ -24,11 +24,11 @@ KernelSU Next supporta ufficialmente la maggior parte dei kernel Android dalla v
|
||||
- I kernel GKI 1.0 (4.19 - 5.4) devono essere ricostruiti con il driver KernelSU.
|
||||
- Anche i kernel EOL (<4.14) devono essere ricostruiti con il driver KernelSU (la versione 3.18+ è sperimentale e potrebbe richiedere alcuni backport di funzioni).
|
||||
|
||||
Attualmente è supportata solo l'architettura `arm64-v8a`.
|
||||
Attualmente è supportata solo l'architettura `arm64-v8a`, `armeabi-v7a` & `x86_64`.
|
||||
|
||||
## Utilizzo
|
||||
|
||||
- [Istruzioni per l'installazione](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
- [Istruzioni per l'installazione](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## Security
|
||||
|
||||
|
||||
63
docs/README_JA.md
Normal file
63
docs/README_JA.md
Normal file
@@ -0,0 +1,63 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | **日本語**
|
||||
|
||||
# KernelSU Next
|
||||
|
||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Android デバイス用のカーネルベースな root ソリューション。
|
||||
|
||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## 機能
|
||||
|
||||
1. カーネルベースの `su` および root アクセスの管理。
|
||||
2. 動的マウントシステム [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) をベースとしたモジュールシステム。
|
||||
3. [アプリプロファイル](https://kernelsu.org/guide/app-profile.html): root 権限をケージに閉じ込めます。
|
||||
|
||||
## 互換性の状態
|
||||
|
||||
KernelSU Next は 4.4 から 6.6 までのほとんどの Android カーネルを公式でサポートしています。
|
||||
- GKI 2.0 (5.10 以降) のカーネルはビルド済みイメージで LKM/KMI を実行できます。
|
||||
- GKI 1.0 (4.19 - 5.4) のカーネルは、KernelSU ドライバを使用してビルドする必要があります。
|
||||
- EOL (4.14 未満) のカーネルも KernelSU ドライバを使用して再ビルドする必要があります (3.18 以降は実験中の段階であり、一部の関数のバックポートが必要になる場合があります)。
|
||||
|
||||
現在 `arm64-v8a`, `armeabi-v7a` & `x86_64` アーキテクチャのみをサポートしています。
|
||||
|
||||
## 使い方
|
||||
|
||||
- [インストール手順](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## セキュリティ
|
||||
|
||||
KernelSU のセキュリティ脆弱性の報告については [SECURITY.md](/SECURITY.md) を参照してください。
|
||||
|
||||
## ライセンス
|
||||
|
||||
- `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) のライセンス下にあります。
|
||||
|
||||
## 寄付
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
||||
|
||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
||||
|
||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
||||
|
||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
||||
|
||||
## クレジット
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU のアイデアを考案。
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): パワフルな root ツール。
|
||||
- [genuine](https://github.com/brevent/genuine/): APK v2 署名認証。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): いくつかの rootkit スキル。
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): tiann に感謝を申し上げます。これが存在しなければ KernelSU Next は存在しませんでした。
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff へ KernelSU を救ってくれてありがとう!
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | **한국어** | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | **한국어** | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
|
||||
@@ -24,11 +24,11 @@ KernelSU Next는 공식적으로 대부분의 4.4부터 6.6의 안드로이드
|
||||
- GKI 1.0 (4.19 - 5.4) 커널은 KernelSU 드라이버로 다시 빌드해야 합니다.
|
||||
- EOL (<4.14) 커널도 역시 KernelSU 드라이버로 다시 빌드해야 합니다.(3.18+는 실험적이며 일부 함수의 이식이 필요할 수 있습니다.).
|
||||
|
||||
현재는, `arm64-v8a`만 지원됩니다.
|
||||
현재는, `arm64-v8a`, `armeabi-v7a` & `x86_64` 만 지원됩니다.
|
||||
|
||||
## 사용 방법
|
||||
|
||||
- [설치 방법](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
- [설치 방법](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## 보안
|
||||
|
||||
|
||||
@@ -1,63 +1,89 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | **Polski** | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Українська](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | **Polski** | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
---
|
||||
|
||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||
<div align="center">
|
||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
||||
|
||||
Bazujące na jądrze rozwiązanie root dla urządzeń z Androidem.
|
||||
<h2>KernelSU Next</h2>
|
||||
<p><strong>Bazujące na jądrze rozwiązanie root dla urządzeń z Androidem.</strong></p>
|
||||
|
||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Funkcjonalności
|
||||
---
|
||||
|
||||
1. Oparte na jądrze `su` i zarządzanie dostępem do roota.
|
||||
2. System modułów oparty na dynamicznym systemie montowania [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [Profil aplikacji](https://kernelsu.org/guide/app-profile.html): Ujarzmij moc roota poprzez możliwość nakładania ograniczeń na uprawnienia roota dla poszczególnych aplikacji.
|
||||
## 🚀 Funkcjonalności
|
||||
|
||||
## Stan zgodności
|
||||
- Oparte na jądrze `su` i zarządzanie dostępem do roota.
|
||||
- System modułów oparty na [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) i [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
- [Profil aplikacji](https://kernelsu.org/guide/app-profile.html): Ograniczaj uprawnienia roota dla poszczególnych aplikacji.
|
||||
|
||||
KernelSU Next oficjalnie obsługuje większość jąder Androida od wersji 4.4 do 6.6.
|
||||
- Jądra GKI 2.0 (5.10+) mogą uruchamiać wstępnie przygotowane obrazy i LKM/KMI.
|
||||
- Jądra GKI 1.0 (4.19 - 5.4) muszą zostać zrekompilowane z dodatkiem sterownika KernelSU.
|
||||
- Jądra EOL (<4.14) również muszą zostać zrekompilowane z dodatkiem sterownika KernelSU (obsługa 3.18+ jest eksperymentalna i może wymagać backportu pewnych funkcji).
|
||||
---
|
||||
|
||||
Obecnie obsługiwana jest tylko architektura `arm64-v8a`.
|
||||
## ✅ Kompatybilność
|
||||
|
||||
## Użycie
|
||||
KernelSU Next obsługuje jądra Androida od wersji **4.4 do 6.6**:
|
||||
|
||||
- [Instrukcja instalacji](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
| Wersja jądra | Informacje techniczne |
|
||||
|----------------------|-------------------------------------------------------------------------------------------|
|
||||
| 5.10+ (GKI 2.0) | Obsługuje wstępnie skompilowane obrazy i LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | Wymaga wbudowania sterownika KernelSU |
|
||||
| < 4.14 (EOL) | Wymaga sterownika KernelSU (obsługa 3.18+ jest eksperymentalna i może wymagać backportów) |
|
||||
|
||||
## Bezpieczeństwo
|
||||
**Obsługiwane architektury:** `arm64-v8a`, `armeabi-v7a` i `x86_64`
|
||||
|
||||
Informacje na temat zgłaszania luk bezpieczeństwa w KernelSU znajdziesz w [SECURITY.md](/SECURITY.md).
|
||||
---
|
||||
|
||||
## Licencje
|
||||
## 📦 Instalacja
|
||||
|
||||
- Pliki w katalogu `kernel` są dostępne na licencji [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- Wszystkie inne elementy, z wyjątkiem katalogu `kernel`, są dostępne na licencji [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
Instrukcje dotyczące instalacji można znaleźć w przewodniku [Instalacja](https://kernelsu-next.github.io/webpage/pages/installation.html).
|
||||
|
||||
## Darowizny
|
||||
---
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
||||
## 🔐 Bezpieczeństwo
|
||||
|
||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
||||
Aby zgłosić problemy związane z bezpieczeństwem, zapoznaj się z [SECURITY.md](/SECURITY.md).
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
||||
---
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
||||
## 📜 Licencje
|
||||
|
||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
||||
- **katalog `/kernel`:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- **Wszystkie pozostałe pliki:** [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
||||
---
|
||||
|
||||
## Podziękowania
|
||||
## 💸 Darowizny
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): Idea, na której opiera się KernelSU.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): Potężne narzędzie do rootowania.
|
||||
- [genuine](https://github.com/brevent/genuine/): Walidacja podpisu APK v2.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Część zdolności rootkitowych.
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): Dzięki tiann, bez ciebie KernelSU Next w ogóle by nie istniał.
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff za uratowanie KernelSU!
|
||||
Jeśli chciałbyś wesprzeć projekt:
|
||||
|
||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Podziękowania
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Inspiracja konceptem
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Bazowa implementacja roota
|
||||
- [Genuine](https://github.com/brevent/genuine/) – Walidacja podpisu APK v2
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Techniki rootkit
|
||||
- [KernelSU](https://github.com/tiann/KernelSU) – Oryginalna baza, która umożliwiła powstanie KernelSU Next
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 dla 5ec1cff za utrzymanie KernelSU przy życiu
|
||||
|
||||
@@ -1,63 +1,89 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | **Português (Brasil)** | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | **Português (Brasil)** | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
---
|
||||
|
||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||
<div align="center">
|
||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
||||
|
||||
Uma solução root baseada em kernel para dispositivos Android.
|
||||
<h2>KernelSU Next</h2>
|
||||
<p><strong>Uma solução root baseada em kernel para dispositivos Android.</strong></p>
|
||||
|
||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Características
|
||||
---
|
||||
|
||||
1. `su` e gerenciamento de acesso root baseado em kernel.
|
||||
2. Sistema de módulos baseado em sistema de montagem dinâmica [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [Perfil do Aplicativo](https://kernelsu.org/pt_BR/guide/app-profile.html): Tranque o poder root em uma gaiola.
|
||||
## 🚀 Características
|
||||
|
||||
## Estado de compatibilidade
|
||||
- `su` e gerenciamento de acesso root baseado em kernel.
|
||||
- Sistema de módulos baseado em [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) e [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
- [Perfil do app](https://kernelsu.org/pt_BR/guide/app-profile.html): Limitar privilégios root por app.
|
||||
|
||||
KernelSU Next suporta oficialmente a maioria dos kernels Android a partir de 4.4 até 6.6.
|
||||
- Os kernels GKI 2.0 (5.10+) podem executar imagens pré-construídas e LKM/KMI.
|
||||
- Os kernels GKI 1.0 (4.19 - 5.4) precisam ser reconstruídos com o driver KernelSU.
|
||||
- Os kernels EOL (<4.14) também precisam ser reconstruídos com o driver KernelSU (3.18+ é experimental e pode precisar portar algumas funções).
|
||||
---
|
||||
|
||||
Atualmente, apenas a arquitetura `arm64-v8a` é compatível.
|
||||
## ✅ Compatibilidade
|
||||
|
||||
## Uso
|
||||
O KernelSU Next oferece suporte a kernels Android **4.4 até 6.6**:
|
||||
|
||||
- [Instruções de instalação](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
| Versão do kernel | Notas de suporte |
|
||||
|----------------------|-------------------------------------------------------------------------------|
|
||||
| 5.10+ (GKI 2.0) | Suporta imagens pré-compiladas e LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | Requer driver do KernelSU integrado |
|
||||
| < 4.14 (EOL) | Requer driver do KernelSU (3.18+ é experimental e pode precisar de backports) |
|
||||
|
||||
## Segurança
|
||||
**Arquiteturas suportadas:** `arm64-v8a`, `armeabi-v7a` e `x86_64`
|
||||
|
||||
Para obter informações sobre como relatar vulnerabilidades de segurança do KernelSU, consulte [SECURITY.md](/SECURITY.md).
|
||||
---
|
||||
|
||||
## Licença
|
||||
## 📦 Instalação
|
||||
|
||||
- Os arquivos no diretório `kernel` são [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- Todas as outras partes, exceto o diretório `kernel` são [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
Consulte o guia de [Instalação](https://kernelsu-next.github.io/webpage/pt_BR/pages/installation.html) para obter instruções de configuração.
|
||||
|
||||
## Doações
|
||||
---
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
||||
## 🔐 Segurança
|
||||
|
||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
||||
Para relatar problemas de segurança, consulte [SECURITY.md](/SECURITY.md).
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
||||
---
|
||||
|
||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
||||
## 📜 Licença
|
||||
|
||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
||||
- **Diretório `/kernel`:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- **Todos os outros arquivos:** [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
||||
---
|
||||
|
||||
## Créditos
|
||||
## 💸 Doações
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): A ideia do KernelSU.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): A poderosa ferramenta root.
|
||||
- [genuine](https://github.com/brevent/genuine/): Validação de assinatura APK v2.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Algumas habilidades de rootkit.
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): Obrigado ao tiann, ou então o KernelSU Next nem existiria.
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff por salvar o KernelSU!
|
||||
Se você quiser apoiar o projeto:
|
||||
|
||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Créditos
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Inspiração do conceito
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Implementação root principal
|
||||
- [Genuine](https://github.com/brevent/genuine/) – Validação de assinatura APK v2
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Técnicas de rootkit
|
||||
- [KernelSU](https://github.com/tiann/KernelSU) – A base original que tornou o KernelSU Next possível
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 para 5ec1cff por manter o KernelSU vivo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | **Русский** | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | **Русский** | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
|
||||
@@ -24,11 +24,11 @@ KernelSU Next работает с большинством ядер Android (4.4
|
||||
- GKI 1.0 (4.19 - 5.4) требуют пересборки с драйвером KernelSU.
|
||||
- EOL (<4.14) также требуют пересборки с драйвером KernelSU (версии 3.18+ экспериментальные и могут потребовать некоторые функции бэкпортов).
|
||||
|
||||
Сейчас поддерживается только `arm64-v8a`.
|
||||
Сейчас поддерживается только `arm64-v8a`, `armeabi-v7a` & `x86_64`.
|
||||
|
||||
## Использование
|
||||
|
||||
- [Инструкция по установке](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
- [Инструкция по установке](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## Безопасность
|
||||
|
||||
@@ -59,5 +59,5 @@ KernelSU Next работает с большинством ядер Android (4.4
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): Топовый инструмент для root.
|
||||
- [genuine](https://github.com/brevent/genuine/): Валидация подписи APK v2.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Некоторые навыки rootkit.
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): Спасибо tiann, без него KernelSU Next не релизнулся бы.
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): Спасибо tiann, без него KernelSU Next даже не существовал бы.
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff за сохранение KernelSU!
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | **ภาษาไทย** | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | **ภาษาไทย** | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
|
||||
@@ -24,11 +24,11 @@ KernelSU Next รองรับแบบเป็นทางการตั้
|
||||
- GKI 1.0 (4.19 - 5.4) เคอร์เนลจะต้องรีบิ้วร่วมกับไดร์เวอร์ของ KernelSU
|
||||
- EOL (<4.14) เคอร์เนลก็ต้องรีบิ้วร่วมกับไดร์เวอร์ของ KernelSU เช่นกัน (3.18+ ยังเป็นเวอร์ชั่นทดลอง และยังต้องเขียนฟังก์ชั่นหลังบ้านเพิ่มเติม)
|
||||
|
||||
ในขณะนี้, มีแค่สถาปัตยกรรม `arm64-v8a` ที่รองรับเท่านั้น
|
||||
ในขณะนี้, มีแค่สถาปัตยกรรม `arm64-v8a`, `armeabi-v7a` & `x86_64` ที่รองรับเท่านั้น
|
||||
|
||||
## การใช้งาน
|
||||
|
||||
- [คำแนะนำในการติดตั้ง](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
- [คำแนะนำในการติดตั้ง](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## ความปลอดภัย
|
||||
|
||||
|
||||
@@ -1,49 +1,89 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | **Türkçe** | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | **Türkçe** | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Українська](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
---
|
||||
|
||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||
<div align="center">
|
||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logosu">
|
||||
|
||||
Android cihazlar için Kernel tabanlı bir root çözümü.
|
||||
<h2>KernelSU Next</h2>
|
||||
<p><strong>Android cihazlar için çekirdek tabanlı bir root çözümüdür.</strong></p>
|
||||
|
||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Özellikler
|
||||
---
|
||||
|
||||
1. Çekirdek tabanlı `su` ve kök erişim yönetimi.
|
||||
2. Dinamik montaj sistemine dayalı modül sistemi [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Kök gücünü bir kafese kilitleyin.
|
||||
## 🚀 Özellikler
|
||||
|
||||
## Uyumluluk Durumu
|
||||
- Çekirdek tabanlı `su` ve root erişim yönetimi.
|
||||
- **[Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount)** ve **[OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)** tabanlı modül sistemi.
|
||||
- [Uygulama Profili](https://kernelsu.org/guide/app-profile.html): Uygulama başına root yetkisini sınırlandırma.
|
||||
|
||||
KernelSU Next, 4.4'dan başlayarak 6.6'ya kadar çoğu Android çekirdeğini resmi olarak desteklemektedir.
|
||||
- GKI 2.0 (5.10+) çekirdekleri önceden oluşturulmuş görüntüleri ve LKM/KMI'yi çalıştırabilir.
|
||||
- GKI 1.0 (4.19 - 5.4) çekirdeklerinin KernelSU sürücüsü ile yeniden oluşturulması gerekir.
|
||||
- EOL (<4.14) çekirdeklerinin de KernelSU sürücüsü ile yeniden oluşturulması gerekir. (3.18+ deneyseldir ve bazı fonksiyon geri yüklemelerine ihtiyaç duyulabilir.)
|
||||
---
|
||||
|
||||
Şu anda sadece `arm64-v8a` desteklenmektedir.
|
||||
## ✅ Uyumluluk
|
||||
|
||||
## Kullanım
|
||||
KernelSU Next, **4.4 ile 6.6** arasındaki Android çekirdeklerini destekler:
|
||||
|
||||
- [Kurulum Talimatı](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
| Çekirdek Sürümü | Destek Notları |
|
||||
|------------------------|--------------------------------------------------------------------------|
|
||||
| 5.10+ (GKI 2.0) | Hazır imajlar ve LKM/KMI desteği |
|
||||
| 4.19 – 5.4 (GKI 1.0) | KernelSU sürücüsünün çekirdeğe gömülü olması gerekir |
|
||||
| < 4.14 (EOL) | KernelSU sürücüsü gerekir (3.18+ deneysel olup yama gerektirebilir) |
|
||||
|
||||
## Güvenlik
|
||||
**Desteklenen mimariler:** `arm64-v8a`, `armeabi-v7a`, `x86_64`
|
||||
|
||||
KernelSU'daki güvenlik açıklarını bildirme hakkında bilgi için [SECURITY.md](/SECURITY.md) bölümüne bakın.
|
||||
---
|
||||
|
||||
## Lisans
|
||||
## 📦 Kurulum
|
||||
|
||||
- `kernel` dizini altındaki dosyalar sadece [GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) lisansına tabiidir.
|
||||
- `kernel` dizini dışındaki diğer tüm kısımlar [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.html) ya da daha sonraki bir sürüm lisansa tabiidir.
|
||||
Kurulum talimatları için [Kurulum Kılavuzu](https://kernelsu-next.github.io/webpage/pages/installation.html) sayfasına bakınız.
|
||||
|
||||
## Krediler
|
||||
---
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU fikri.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): Güçlü kök aracı.
|
||||
- [genuine](https://github.com/brevent/genuine/): APK v2 imza doğrulama.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Bazı rootkit becerileri.
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): tiann'a teşekkürler, yoksa KernelSU Next var olamazdı bile.
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff KernelSU'yu kurtardığınız için!
|
||||
## 🔐 Güvenlik
|
||||
|
||||
Güvenlik açıklarını bildirmek için lütfen [SECURITY.md](/SECURITY.md) dosyasına bakınız.
|
||||
|
||||
---
|
||||
|
||||
## 📜 Lisans
|
||||
|
||||
- **`/kernel` dizini:** [Yalnızca GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- **Diğer tüm dosyalar:** [GPL-3.0-veya-sonrası](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
---
|
||||
|
||||
## 💸 Bağışlar
|
||||
|
||||
Projeye destek olmak isterseniz:
|
||||
|
||||
- **USDT (BEP20, ERC20):** `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20):** `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20):** `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC:** `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC:** `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Katkıda Bulunanlar
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – KernelSU Fikrinin temeli
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Temel root altyapısı
|
||||
- [Genuine](https://github.com/brevent/genuine/) – APK v2 imza doğrulaması
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Rootkit teknikleri
|
||||
- [KernelSU](https://github.com/tiann/KernelSU) – KernelSU Next'in temelini oluşturan orijinal proje
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – KernelSU’yu kurtardığı için 💜 5ec1cff’e teşekkürler
|
||||
|
||||
@@ -1,50 +1,88 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | **繁體中文** | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | **繁體中文** | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
---
|
||||
|
||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||
<div align="center">
|
||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
||||
|
||||
<h2>KernelSU Next</h2>
|
||||
<p><strong>基於內核的 Android 設備 Root 解決方案</strong></p>
|
||||
|
||||
基於內核的 Android 設備 Root 解決方案
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
---
|
||||
|
||||
## 特性
|
||||
## 🚀 特性
|
||||
|
||||
1. 基於內核的 `su` 和 Root 權限管理
|
||||
2. 基於動態掛載系統 [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模塊系統。
|
||||
3. [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 權限關進籠子裡
|
||||
- 基於內核的 `su` 和 Root 權限管理
|
||||
- 模塊系統基於 **[Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount)** 以及 **[OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)**
|
||||
- [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 權限關進籠子裡
|
||||
|
||||
## 兼容狀態
|
||||
---
|
||||
|
||||
KernelSU Next 正式支持大多數從 4.4 到 6.6 的 Android 內核
|
||||
- GKI 2.0 (5.10+) 內核可以運行預構建的映像和 LKM/KMI
|
||||
- GKI 1.0 (4.19 - 5.4) 內核需要重新編譯 KernelSU 驅動程序
|
||||
- EOL (<4.14) 內核也需要重新編譯 KernelSU 驅動程序(3.18+ 是實驗性的,可能需要移植一些功能)
|
||||
## ✅ 兼容狀態
|
||||
|
||||
目前僅支持 `arm64-v8a`
|
||||
KernelSU Next 正式支持大多數從 **4.4 到 6.6** 的 Android 內核
|
||||
|
||||
## 用法
|
||||
| 内核版本 | 支援狀況 |
|
||||
|----------------|---------------|
|
||||
| 5.10+ (GKI 2.0) | 可以運行預構建的映像和 LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | 需要重新編譯 KernelSU 驅動程序 |
|
||||
| <4.14 (EOL) | 需要重新編譯 KernelSU 驅動程序(3.18+ 是實驗性的,可能需要回溯移植一些功能) |
|
||||
|
||||
- [安裝說明](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
**支援的架構:**
|
||||
`arm64-v8a`、`armeabi-v7a`、`x86_64`
|
||||
|
||||
## 安全性
|
||||
---
|
||||
|
||||
有關報告 KernelSU Next 漏洞的信息,請參閱 [SECURITY.md](/SECURITY.md).
|
||||
## 📦 用法
|
||||
|
||||
## 許可證
|
||||
請遵循[安裝説明](https://kernelsu-next.github.io/webpage/pages/installation.html)進行操作
|
||||
|
||||
- 目錄 `kernel` 下所有文件為 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- `kernel` 目錄以外的其他部分均為 [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
---
|
||||
|
||||
## 鳴謝
|
||||
## 🔐 安全性
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU 的靈感.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): 強大的 Root 工具.
|
||||
- [genuine](https://github.com/brevent/genuine/): APK v2 簽名驗證。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): 一些 Rootkit 技巧。
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): 感謝 tiann,否則 KernelSU Next 根本不會存在。
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff 為了拯救 KernelSU!
|
||||
有關報告 KernelSU Next 漏洞的信息,請參閱 [SECURITY.md](/SECURITY.md)
|
||||
|
||||
---
|
||||
|
||||
## 📜 許可證
|
||||
|
||||
- **目錄 `/kernel` 下所有文件**為 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- **`/kernel` 目錄以外的其他部分**均為 [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
---
|
||||
|
||||
## 💸 抖内
|
||||
|
||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 鳴謝
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的靈感.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):強大的 Root 工具.
|
||||
- [genuine](https://github.com/brevent/genuine/):APK v2 簽名驗證。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 Rootkit 技巧。
|
||||
- [KernelSU](https://github.com/tiann/KernelSU):感謝 tiann,否則 KernelSU Next 根本不會存在。
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs):💜 5ec1cff 為了拯救 KernelSU!
|
||||
|
||||
90
docs/README_UA.md
Normal file
90
docs/README_UA.md
Normal file
@@ -0,0 +1,90 @@
|
||||
**Languages**:
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | **Українська** | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
||||
|
||||
<h2>KernelSU Next</h2>
|
||||
<p><strong>Рішення для root-прав на основі ядра для пристроїв Android.</strong></p>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Особливості
|
||||
|
||||
- Керування `su` та root-доступом на основі ядра.
|
||||
- Модульна система на основі [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) та [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
- [Профілі програм](https://kernelsu.org/guide/app-profile.html): Обмеження root-прав для кожної програми.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Сумісність
|
||||
|
||||
KernelSU Next підтримує ядра Android від **4.4 до 6.6**:
|
||||
|
||||
| Версія ядра | Примітки підтримки |
|
||||
|----------------------|-------------------------------------------------------------------------------------------|
|
||||
| 5.10+ (GKI 2.0) | Підтримує попередньо створені образи та LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | Потрібен вбудований драйвер KernelSU |
|
||||
| <4.14 (EOL) | Потрібен драйвер KernelSU (версія 3.18+ є експериментальною, може знадобитися портування) |
|
||||
|
||||
**Підтримувані архітектури:** `arm64-v8a`, `armeabi-v7a`, `x86_64`
|
||||
|
||||
---
|
||||
|
||||
## 📦 Встановлення
|
||||
|
||||
Будь ласка, зверніться до [Посібника з встановлення](https://kernelsu-next.github.io/webpage/pages/installation.html) для отримання інструкцій з налаштування.
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Безпека
|
||||
|
||||
Щоб повідомити про проблеми безпеки, див [SECURITY.md](/SECURITY.md).
|
||||
|
||||
---
|
||||
|
||||
## 📜 Ліцензія
|
||||
|
||||
- **Каталог `/kernel`:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- **Усі інші файли:** [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
---
|
||||
|
||||
## 💸 Пожертви
|
||||
|
||||
Якщо ви хочете підтримати проєкт:
|
||||
|
||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Подяки
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Натхнення для концепції
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Топовий інструмент для root
|
||||
- [Genuine](https://github.com/brevent/genuine/) – Перевірка підпису APK версії 2
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Деякі навики RootKit
|
||||
- [KernelSU](https://github.com/tiann/KernelSU) – Основа для KernelSU Next
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 до 5ec1cff за збереження KernelSU
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md) | **Tiếng Việt** | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md)
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | **Tiếng Việt** | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
# KernelSU Next
|
||||
|
||||
@@ -24,11 +24,11 @@ KernelSU Next hỗ trợ chính thức các kernel Android từ phiên bản 4.4
|
||||
- GKI 1.0 (4.19 - 5.4) kernels cần dược build lại với các nhân KernelSU Next
|
||||
- EOL (<4.14) kernels cần dược build lại với các nhân KernelSU Next (các kernels 3.18+ đang dược thử nghiệm và có thể cần backports 1 vài thứ ).
|
||||
|
||||
Hiện tại kernelSU Next chỉ hỗ trợ những cpu có `arm64-v8a`
|
||||
Hiện tại kernelSU Next chỉ hỗ trợ những cpu có `arm64-v8a`, `armeabi-v7a` & `x86_64`
|
||||
|
||||
## Sử dụng
|
||||
|
||||
- [Hướng dẫn vá KernelSU Next vào Kernel của bạn (yêu cầu kernel source)](https://KernelSU-Next.github.io/KernelSU-Next/)
|
||||
- [Hướng dẫn vá KernelSU Next vào Kernel của bạn (yêu cầu kernel source)](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## Bảo mật
|
||||
|
||||
|
||||
326
docs/WebUi_Next/API_DOC.md
Normal file
326
docs/WebUi_Next/API_DOC.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# WebUI-Next API Documentation
|
||||
|
||||
This document provides examples of how to use the `WebUI-Next` JavaScript APIs exposed to a module WebUI. These APIs allow code to run in the WebUI to interact with the system, execute shell commands, manage packages, control UI elements, and more coming soon.
|
||||
|
||||
|
||||
## Table of Contents
|
||||
1. [exec(cmd)](#exec-cmd)
|
||||
2. [exec(cmd, callbackFunc)](#exec-cmd-callbackfunc)
|
||||
3. [exec(cmd, options, callbackFunc)](#exec-cmd-options-callbackfunc)
|
||||
4. [spawn(command, args, options, callbackFunc)](#spawn-command-args-options-callbackfunc)
|
||||
5. [toast(msg)](#toast-msg)
|
||||
6. [fullScreen(enable)](#fullscreen-enable)
|
||||
7. [moduleInfo()](#moduleinfo)
|
||||
8. [listSystemPackages()](#listsystempackages)
|
||||
9. [listUserPackages()](#listuserpackages)
|
||||
10. [listAllPackages()](#listallpackages)
|
||||
11. [getPackagesInfo(packageNamesJson)](#getpackagesinfo-packagenamesjson)
|
||||
12. [cacheAllPackageIcons(size)](#cacheallpackageicons-size)
|
||||
13. [getPackagesIcons(packageNamesJson, size)](#getpackagesicons-packagenamesjson-size)
|
||||
|
||||
---
|
||||
|
||||
## exec(cmd)
|
||||
|
||||
Executes a shell command synchronously and returns the output as a string.
|
||||
|
||||
### Parameters
|
||||
- `cmd` (String): The shell command to execute.
|
||||
|
||||
### Returns
|
||||
- `String`: The command output (stdout).
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
const output = ksu.exec("ls /system");
|
||||
console.log("Output:", output);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## exec(cmd, callbackFunc)
|
||||
|
||||
Executes a shell command asynchronously and invokes the provided callback function with the result.
|
||||
|
||||
### Parameters
|
||||
- `cmd` (String): The shell command to execute.
|
||||
- `callbackFunc` (String): The name of the JavaScript callback function to invoke with the result.
|
||||
|
||||
### Callback Signature
|
||||
```javascript
|
||||
function callbackFunc(exitCode, stdout, stderr) {
|
||||
// Handle result
|
||||
}
|
||||
```
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
function handleResult(exitCode, stdout, stderr) {
|
||||
console.log("Exit Code:", exitCode);
|
||||
console.log("Stdout:", stdout);
|
||||
console.log("Stderr:", stderr);
|
||||
}
|
||||
|
||||
ksu.exec("ls /system", "handleResult");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## exec(cmd, options, callbackFunc)
|
||||
|
||||
Executes a shell command asynchronously with options (e.g., working directory, environment variables) and invokes the provided callback function with the result.
|
||||
|
||||
### Parameters
|
||||
- `cmd` (String): The shell command to execute.
|
||||
- `options` (String): A JSON string specifying options like `cwd` (working directory) and `env` (environment variables).
|
||||
- `callbackFunc` (String): The name of the JavaScript callback function to invoke with the result.
|
||||
|
||||
### Options Format
|
||||
```javascript
|
||||
{
|
||||
"cwd": "/path/to/working/directory",
|
||||
"env": {
|
||||
"KEY1": "VALUE1",
|
||||
"KEY2": "VALUE2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Callback Signature
|
||||
```javascript
|
||||
function callbackFunc(exitCode, stdout, stderr) {
|
||||
// Handle result
|
||||
}
|
||||
```
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
const options = JSON.stringify({
|
||||
cwd: "/system",
|
||||
env: { PATH: "/system/bin" }
|
||||
});
|
||||
|
||||
function handleResult(exitCode, stdout, stderr) {
|
||||
console.log("Exit Code:", exitCode);
|
||||
console.log("Stdout:", stdout);
|
||||
console.log("Stderr:", stderr);
|
||||
}
|
||||
|
||||
ksu.exec("ls", JSON.stringify(options), "handleResult");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## spawn(command, args, options, callbackFunc)
|
||||
|
||||
Spawns a shell command with arguments and streams output through events to a JavaScript object.
|
||||
|
||||
### Parameters
|
||||
- `command` (String): The shell command to execute.
|
||||
- `args` (String): A JSON array of command arguments.
|
||||
- `options` (String): A JSON string specifying options like `cwd` and `env` (optional).
|
||||
- `callbackFunc` (String): The name of the JavaScript object to receive events (`stdout`, `stderr`, `exit`, `error`).
|
||||
|
||||
### Callback Object
|
||||
The callback object should implement methods to handle events:
|
||||
- `stdout.emit('data', data)`: Emits stdout data.
|
||||
- `stderr.emit('data', data)`: Emits stderr data.
|
||||
- `exit(code)`: Emits the exit code.
|
||||
- `error(err)`: Emits an error object with `exitCode` and `message`.
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
const streamHandler = {
|
||||
stdout: {
|
||||
emit: (event, data) => {
|
||||
if (event === "data") console.log("Stdout:", data);
|
||||
}
|
||||
},
|
||||
stderr: {
|
||||
emit: (event, data) => {
|
||||
if (event === "data") console.log("Stderr:", data);
|
||||
}
|
||||
},
|
||||
emit: (event, data) => {
|
||||
if (event === "exit") console.log("Exit Code:", data);
|
||||
if (event === "error") console.error("Error:", data);
|
||||
}
|
||||
};
|
||||
|
||||
const args = JSON.stringify(["-l", "/system"]);
|
||||
const options = JSON.stringify({ cwd: "/system" });
|
||||
|
||||
ksu.spawn("ls", args, options, "streamHandler");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## toast(msg)
|
||||
|
||||
Displays a short Android toast message.
|
||||
|
||||
### Parameters
|
||||
- `msg` (String): The message to display.
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
ksu.toast("Hello from WebUI-Next!");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## fullScreen(enable)
|
||||
|
||||
Toggles full-screen mode by hiding or showing system UI (status and navigation bars).
|
||||
|
||||
### Parameters
|
||||
- `enable` (Boolean): `true` to enable full-screen mode, `false` to disable.
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
// Enable full-screen
|
||||
ksu.fullScreen(true);
|
||||
|
||||
// Disable full-screen
|
||||
ksu.fullScreen(false);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## moduleInfo()
|
||||
|
||||
Returns information about the current module as a JSON string.
|
||||
|
||||
### Returns
|
||||
- `String`: A JSON string containing module information, including `moduleDir` and other module-specific details.
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
const moduleInfo = JSON.parse(ksu.moduleInfo());
|
||||
console.log("Module Directory:", moduleInfo.moduleDir);
|
||||
console.log("Module ID:", moduleInfo.id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## listSystemPackages()
|
||||
|
||||
Returns a list of system package names as a JSON array.
|
||||
|
||||
### Returns
|
||||
- `String`: A JSON array of system package names.
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
const systemPackages = JSON.parse(ksu.listSystemPackages());
|
||||
console.log("System Packages:", systemPackages);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## listUserPackages()
|
||||
|
||||
Returns a list of user-installed package names as a JSON array.
|
||||
|
||||
### Returns
|
||||
- `String`: A JSON array of user package names.
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
const userPackages = JSON.parse(ksu.listUserPackages());
|
||||
console.log("User Packages:", userPackages);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## listAllPackages()
|
||||
|
||||
Returns a list of all installed package names as a JSON array.
|
||||
|
||||
### Returns
|
||||
- `String`: A JSON array of all package names.
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
const allPackages = JSON.parse(ksu.listAllPackages());
|
||||
console.log("All Packages:", allPackages);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## getPackagesInfo(packageNamesJson)
|
||||
|
||||
Returns detailed information about specified packages as a JSON array.
|
||||
|
||||
### Parameters
|
||||
- `packageNamesJson` (String): A JSON array of package names.
|
||||
|
||||
### Returns
|
||||
- `String`: A JSON array of objects containing package details (`packageName`, `versionName`, `versionCode`, `appLabel`, `isSystem`, `uid`) or an error object if the package is not found.
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
const packageNames = JSON.stringify(["com.android.settings", "com.example.app"]);
|
||||
const packageInfos = JSON.parse(ksu.getPackagesInfo(packageNames));
|
||||
packageInfos.forEach(info => {
|
||||
if (info.error) {
|
||||
console.error(`Error for ${info.packageName}: ${info.error}`);
|
||||
} else {
|
||||
console.log(`Package: ${info.packageName}, Version: ${info.versionName}, System: ${info.isSystem}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## cacheAllPackageIcons(size)
|
||||
|
||||
Caches icons for all installed packages at the specified size.
|
||||
|
||||
### Parameters
|
||||
- `size` (Number): The size (in pixels) for the square icon.
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
// Cache all package icons at 48x48 pixels
|
||||
ksu.cacheAllPackageIcons(48);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## getPackagesIcons(packageNamesJson, size)
|
||||
|
||||
Returns base64-encoded icons for specified packages as a JSON array.
|
||||
|
||||
### Parameters
|
||||
- `packageNamesJson` (String): A JSON array of package names.
|
||||
- `size` (Number): The size (in pixels) for the square icon.
|
||||
|
||||
### Returns
|
||||
- `String`: A JSON array of objects containing `packageName` and `icon` (base64-encoded PNG or empty string if unavailable).
|
||||
|
||||
### Example
|
||||
```javascript
|
||||
const packageNames = JSON.stringify(["com.android.settings", "com.example.app"]);
|
||||
const packageIcons = JSON.parse(ksu.getPackagesIcons(packageNames, 48));
|
||||
packageIcons.forEach(item => {
|
||||
if (item.icon) {
|
||||
console.log(`Icon for ${item.packageName}: ${item.icon.substring(0, 30)}...`);
|
||||
// Example: Display icon in an <img> element
|
||||
const img = document.createElement("img");
|
||||
img.src = item.icon;
|
||||
document.body.appendChild(img);
|
||||
} else {
|
||||
console.log(`No icon for ${item.packageName}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
- **Root Access**: Methods like `exec` and `spawn` require root access and use the `libsu` library for shell execution.
|
||||
- **Asynchronous Operations**: Use `WebUI.post` to ensure UI thread safety when invoking JavaScript callbacks.
|
||||
- **Error Handling**: Always check for errors in callbacks (e.g., `stderr` in `exec`, `error` event in `spawn`).
|
||||
- **Icon Caching**: Use `cacheAllPackageIcons` to improve performance for subsequent `getPackagesIcons` calls.
|
||||
- **JSON Parsing**: Ensure valid JSON strings are passed to methods like `getPackagesInfo` and `getPackagesIcons`.
|
||||
@@ -9,7 +9,7 @@ config KSU
|
||||
To compile as a module, choose M here: the
|
||||
module will be called kernelsu.
|
||||
|
||||
config KSU_WITH_KPROBES
|
||||
config KSU_KPROBES_HOOK
|
||||
bool "Use kprobes for kernelsu"
|
||||
depends on KSU
|
||||
depends on KPROBES
|
||||
@@ -32,4 +32,19 @@ config KSU_ALLOWLIST_WORKAROUND
|
||||
Enable session keyring init workaround for problematic devices.
|
||||
Useful for situations where the SU allowlist is not kept after a reboot.
|
||||
|
||||
config KSU_LSM_SECURITY_HOOKS
|
||||
bool "use lsm security hooks"
|
||||
depends on KSU
|
||||
default y
|
||||
help
|
||||
Disabling this is mostly only useful for kernel 4.1 and older.
|
||||
Make sure to implement manual hooks on security/security.c.
|
||||
|
||||
config KSU_SWITCH_MANAGER
|
||||
bool "KernelSU switch manager support"
|
||||
depends on KSU
|
||||
default n
|
||||
help
|
||||
Enable KernelSU switch manager support.
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -49,12 +49,12 @@ ifeq ($(shell grep "ssize_t kernel_write" $(srctree)/fs/read_write.c | grep -q "
|
||||
ccflags-y += -DKSU_KERNEL_WRITE
|
||||
endif
|
||||
|
||||
ifndef KSU_NEXT_EXPECTED_SIZE
|
||||
KSU_NEXT_EXPECTED_SIZE := 0x3e6
|
||||
ifndef KSU_NEXT_MANAGER_SIZE
|
||||
KSU_NEXT_MANAGER_SIZE := 0x3e6
|
||||
endif
|
||||
|
||||
ifndef KSU_NEXT_EXPECTED_HASH
|
||||
KSU_NEXT_EXPECTED_HASH := 79e590113c4c4c0c222978e413a5faa801666957b1212a328e46c00c69821bf7
|
||||
ifndef KSU_NEXT_MANAGER_HASH
|
||||
KSU_NEXT_MANAGER_HASH := 79e590113c4c4c0c222978e413a5faa801666957b1212a328e46c00c69821bf7
|
||||
endif
|
||||
|
||||
ifdef KSU_MANAGER_PACKAGE
|
||||
@@ -62,48 +62,14 @@ ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
|
||||
$(info -- KernelSU-Next Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
endif
|
||||
|
||||
$(info -- KernelSU-Next Manager signature size: $(KSU_NEXT_EXPECTED_SIZE))
|
||||
$(info -- KernelSU-Next Manager signature hash: $(KSU_NEXT_EXPECTED_HASH))
|
||||
$(info -- KernelSU-Next Manager signature size: $(KSU_NEXT_MANAGER_SIZE))
|
||||
$(info -- KernelSU-Next Manager signature hash: $(KSU_NEXT_MANAGER_HASH))
|
||||
|
||||
ccflags-y += -DEXPECTED_NEXT_SIZE=$(KSU_NEXT_EXPECTED_SIZE)
|
||||
ccflags-y += -DEXPECTED_NEXT_HASH=\"$(KSU_NEXT_EXPECTED_HASH)\"
|
||||
|
||||
ccflags-y += -DKSU_COMPAT_GET_CRED_RCU
|
||||
ccflags-y += -DEXPECTED_MANAGER_SIZE=$(KSU_NEXT_MANAGER_SIZE)
|
||||
ccflags-y += -DEXPECTED_MANAGER_HASH=\"$(KSU_NEXT_MANAGER_HASH)\"
|
||||
|
||||
ccflags-y += -DKSU_UMOUNT
|
||||
|
||||
# Determine the appropriate atomic function and apply patch accordingly
|
||||
ifeq ($(shell grep -q "atomic_inc_not_zero" $(srctree)/kernel/cred.c; echo $$?),0)
|
||||
ATOMIC_INC_FUNC = atomic_inc_not_zero
|
||||
else ifeq ($(shell grep -q "atomic_long_inc_not_zero" $(srctree)/kernel/cred.c; echo $$?),0)
|
||||
ATOMIC_INC_FUNC = atomic_long_inc_not_zero
|
||||
else
|
||||
$(info -- KSU_NEXT: Neither atomic_inc_not_zero nor atomic_long_inc_not_zero found in kernel/cred.c)
|
||||
endif
|
||||
|
||||
# Inform which function is being patched
|
||||
$(info -- KSU_NEXT: Using $(ATOMIC_INC_FUNC) in get_cred_rcu patch.)
|
||||
|
||||
# Add the get_cred_rcu function to cred.h if not already present
|
||||
ifneq ($(shell grep -Eq "^static inline const struct cred \*get_cred_rcu" $(srctree)/include/linux/cred.h; echo $$?),0)
|
||||
$(info -- KSU_NEXT: adding function 'static inline const struct cred *get_cred_rcu(const struct cred *cred);' to $(srctree)/include/linux/cred.h)
|
||||
GET_CRED_RCU = static inline const struct cred *get_cred_rcu(const struct cred *cred)\n\
|
||||
{\n\t\
|
||||
struct cred *nonconst_cred = (struct cred *) cred;\n\t\
|
||||
if (!cred)\n\t\t\
|
||||
return NULL;\n\t\
|
||||
if (!$(ATOMIC_INC_FUNC)(&nonconst_cred->usage))\n\t\t\
|
||||
return NULL;\n\t\
|
||||
validate_creds(cred);\n\t\
|
||||
return cred;\n\
|
||||
}\n
|
||||
$(shell grep -qF "$(GET_CRED_RCU)" $(srctree)/include/linux/cred.h || sed -i '/^static inline void put_cred/i $(GET_CRED_RCU)' $(srctree)/include/linux/cred.h)
|
||||
|
||||
# Modify get_task_cred in cred.c
|
||||
$(info -- KSU_NEXT: modifying 'get_task_cred' function in $(srctree)/kernel/cred.c)
|
||||
$(shell sed -i "s/!$(ATOMIC_INC_FUNC)(&((struct cred \*)cred)->usage)/!get_cred_rcu(cred)/g" $(srctree)/kernel/cred.c)
|
||||
endif
|
||||
|
||||
ifneq ($(shell grep -Eq "^static int can_umount" $(srctree)/fs/namespace.c; echo $$?),0)
|
||||
$(info -- KSU_NEXT: adding function 'static int can_umount(const struct path *path, int flags);' to $(srctree)/fs/namespace.c)
|
||||
CAN_UMOUNT = static int can_umount(const struct path *path, int flags)\n\
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/version.h>
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
#include <linux/moduleparam.h>
|
||||
#endif
|
||||
#include <crypto/hash.h>
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
#include <crypto/sha2.h>
|
||||
@@ -17,7 +16,10 @@
|
||||
#include "apk_sign.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
#include "throne_tracker.h"
|
||||
|
||||
static unsigned int expected_manager_size = EXPECTED_MANAGER_SIZE;
|
||||
static char expected_manager_hash[SHA256_DIGEST_SIZE * 2 + 1] = EXPECTED_MANAGER_HASH;
|
||||
|
||||
struct sdesc {
|
||||
struct shash_desc shash;
|
||||
@@ -314,7 +316,76 @@ module_param_cb(ksu_debug_manager_uid, &expected_size_ops,
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_SWITCH_MANAGER
|
||||
|
||||
static int set_expected_size(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
int rv = param_set_uint(val, kp);
|
||||
pr_info("expected_manager_size set to %u\n", expected_manager_size);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int get_expected_size(char *buf, const struct kernel_param *kp)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", expected_manager_size);
|
||||
}
|
||||
|
||||
static int set_expected_hash(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
if (strlen(val) != SHA256_DIGEST_SIZE * 2) {
|
||||
pr_err("Invalid hash length: %s\n", val);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
strncpy(expected_manager_hash, val, SHA256_DIGEST_SIZE * 2);
|
||||
expected_manager_hash[SHA256_DIGEST_SIZE * 2] = '\0';
|
||||
|
||||
pr_info("expected_manager_hash set to %s\n", expected_manager_hash);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_expected_hash(char *buf, const struct kernel_param *kp)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%s\n", expected_manager_hash);
|
||||
}
|
||||
|
||||
static struct kernel_param_ops expected_size_ops = {
|
||||
.set = set_expected_size,
|
||||
.get = get_expected_size,
|
||||
};
|
||||
|
||||
static struct kernel_param_ops expected_hash_ops = {
|
||||
.set = set_expected_hash,
|
||||
.get = get_expected_hash,
|
||||
};
|
||||
|
||||
module_param_cb(expected_manager_size, &expected_size_ops, &expected_manager_size, 0644);
|
||||
|
||||
module_param_cb(expected_manager_hash, &expected_hash_ops, &expected_manager_hash, 0644);
|
||||
|
||||
#endif
|
||||
|
||||
bool is_manager_apk(char *path)
|
||||
{
|
||||
return check_v2_signature(path, EXPECTED_NEXT_SIZE, EXPECTED_NEXT_HASH);
|
||||
}
|
||||
int tries = 0;
|
||||
|
||||
while (tries++ < 10) {
|
||||
if (!is_lock_held(path))
|
||||
break;
|
||||
|
||||
pr_info("%s: waiting for %s\n", __func__, path);
|
||||
msleep(100);
|
||||
}
|
||||
|
||||
// let it go, if retry fails, check_v2_signature will fail to open it anyway
|
||||
if (tries == 10) {
|
||||
pr_info("%s: timeout for %s\n", __func__, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// set debug info to print size and hash to kernel log
|
||||
pr_info("%s: expected size: %u, expected hash: %s\n",
|
||||
path, expected_manager_size, expected_manager_hash);
|
||||
|
||||
return check_v2_signature(path, expected_manager_size, expected_manager_hash);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kprobes.h>
|
||||
#ifdef CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||
#include <linux/lsm_hooks.h>
|
||||
#endif
|
||||
#include <linux/mm.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/path.h>
|
||||
@@ -45,10 +47,6 @@
|
||||
#include "throne_tracker.h"
|
||||
#include "kernel_compat.h"
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) || defined(KSU_COMPAT_GET_CRED_RCU)
|
||||
#define KSU_GET_CRED_RCU
|
||||
#endif
|
||||
|
||||
static bool ksu_module_mounted = false;
|
||||
|
||||
extern int handle_sepolicy(unsigned long arg3, void __user *arg4);
|
||||
@@ -116,6 +114,7 @@ static void setup_groups(struct root_profile *profile, struct cred *cred)
|
||||
|
||||
groups_sort(group_info);
|
||||
set_groups(cred, group_info);
|
||||
put_group_info(group_info);
|
||||
}
|
||||
|
||||
static void disable_seccomp(void)
|
||||
@@ -140,27 +139,17 @@ void escape_to_root(void)
|
||||
{
|
||||
struct cred *cred;
|
||||
|
||||
#ifdef KSU_GET_CRED_RCU
|
||||
rcu_read_lock();
|
||||
|
||||
do {
|
||||
cred = (struct cred *)__task_cred((current));
|
||||
BUG_ON(!cred);
|
||||
} while (!get_cred_rcu(cred));
|
||||
cred = prepare_creds();
|
||||
if (!cred) {
|
||||
pr_warn("prepare_creds failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cred->euid.val == 0) {
|
||||
pr_warn("Already root, don't escape!\n");
|
||||
rcu_read_unlock();
|
||||
abort_creds(cred);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
cred = (struct cred *)__task_cred(current);
|
||||
|
||||
if (cred->euid.val == 0) {
|
||||
pr_warn("Already root, don't escape!\n");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct root_profile *profile = ksu_get_root_profile(cred->uid.val);
|
||||
|
||||
@@ -195,10 +184,8 @@ void escape_to_root(void)
|
||||
sizeof(cred->cap_ambient));
|
||||
|
||||
setup_groups(profile, cred);
|
||||
|
||||
#ifdef KSU_GET_CRED_RCU
|
||||
rcu_read_unlock();
|
||||
#endif
|
||||
|
||||
commit_creds(cred);
|
||||
|
||||
// Refer to kernel/seccomp.c: seccomp_set_mode_strict
|
||||
// When disabling Seccomp, ensure that current->sighand->siglock is held during the operation.
|
||||
@@ -265,7 +252,35 @@ static void nuke_ext4_sysfs() {
|
||||
}
|
||||
|
||||
ext4_unregister_sysfs(sb);
|
||||
path_put(&path);
|
||||
path_put(&path);
|
||||
}
|
||||
|
||||
static bool is_system_bin_su(void)
|
||||
{
|
||||
static const char *su_paths[] = {
|
||||
"/system/bin/su",
|
||||
"/vendor/bin/su",
|
||||
"/product/bin/su",
|
||||
"/system_ext/bin/su",
|
||||
"/odm/bin/su",
|
||||
"/system/xbin/su",
|
||||
"/system_ext/xbin/su"
|
||||
};
|
||||
char path_buf[256];
|
||||
char *pathname;
|
||||
int i;
|
||||
|
||||
struct mm_struct *mm = current->mm;
|
||||
if (mm && mm->exe_file) {
|
||||
pathname = d_path(&mm->exe_file->f_path, path_buf, sizeof(path_buf));
|
||||
if (!IS_ERR(pathname)) {
|
||||
for (i = 0; i < ARRAY_SIZE(su_paths); i++) {
|
||||
if (strcmp(pathname, su_paths[i]) == 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
@@ -290,10 +305,18 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
bool from_root = 0 == current_uid().val;
|
||||
bool from_manager = is_manager();
|
||||
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!from_root && !from_manager
|
||||
&& !(is_allow_su() && is_system_bin_su())) {
|
||||
// only root or manager can access this interface
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
if (!from_root && !from_manager) {
|
||||
// only root or manager can access this interface
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("option: 0x%x, cmd: %ld\n", option, arg2);
|
||||
@@ -337,6 +360,29 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_GET_MANAGER_UID) {
|
||||
uid_t manager_uid = ksu_get_manager_uid();
|
||||
if (copy_to_user(arg3, &manager_uid, sizeof(manager_uid))) {
|
||||
pr_err("get manager uid failed\n");
|
||||
}
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_HOOK_MODE) {
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
const char *mode = "Kprobes";
|
||||
#else
|
||||
const char *mode = "Manual";
|
||||
#endif
|
||||
if (copy_to_user((void __user *)arg3, mode, strlen(mode) + 1)) {
|
||||
pr_info("hook: copy_to_user() failed\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_REPORT_EVENT) {
|
||||
if (!from_root) {
|
||||
return 0;
|
||||
@@ -436,6 +482,32 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
if (arg2 == CMD_ENABLE_SU) {
|
||||
bool enabled = (arg3 != 0);
|
||||
if (enabled == ksu_su_compat_enabled) {
|
||||
pr_info("cmd enable su but no need to change.\n");
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {// return the reply_ok directly
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
ksu_sucompat_init();
|
||||
} else {
|
||||
ksu_sucompat_exit();
|
||||
}
|
||||
ksu_su_compat_enabled = enabled;
|
||||
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// all other cmds are for 'root manager'
|
||||
if (!from_manager) {
|
||||
return 0;
|
||||
@@ -489,7 +561,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (arg2 == CMD_ENABLE_SU) {
|
||||
bool enabled = (arg3 != 0);
|
||||
if (enabled == ksu_su_compat_enabled) {
|
||||
@@ -513,6 +585,8 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -566,11 +640,13 @@ static void try_umount(const char *mnt, bool check_mnt, int flags)
|
||||
|
||||
if (path.dentry != path.mnt->mnt_root) {
|
||||
// it is not root mountpoint, maybe umounted by others already.
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interest in some specific mounts
|
||||
if (check_mnt && !should_umount(&path)) {
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -634,6 +710,7 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
||||
|
||||
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
|
||||
// filter the mountpoint whose target is `/data/adb`
|
||||
try_umount("/odm", true, 0);
|
||||
try_umount("/system", true, 0);
|
||||
try_umount("/system_ext", true, 0);
|
||||
try_umount("/vendor", true, 0);
|
||||
@@ -643,7 +720,7 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
||||
// try umount ksu temp path
|
||||
try_umount("/debug_ramdisk", false, MNT_DETACH);
|
||||
try_umount("/sbin", false, MNT_DETACH);
|
||||
|
||||
|
||||
// try umount hosts file
|
||||
try_umount("/system/etc/hosts", false, MNT_DETACH);
|
||||
|
||||
@@ -723,15 +800,22 @@ __maybe_unused int ksu_kprobe_exit(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
unsigned long arg4, unsigned long arg5)
|
||||
extern int ksu_handle_devpts(struct inode *inode); // sucompat.c
|
||||
|
||||
static int ksu_inode_permission(struct inode *inode, int mask)
|
||||
{
|
||||
ksu_handle_prctl(option, arg2, arg3, arg4, arg5);
|
||||
return -ENOSYS;
|
||||
if (unlikely(inode->i_sb && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC)) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("%s: devpts inode accessed with mask: %x\n", __func__, mask);
|
||||
#endif
|
||||
ksu_handle_devpts(inode);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// kernel 4.4 and 4.9
|
||||
|
||||
// kernel 4.9 and older
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND)
|
||||
static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred,
|
||||
int ksu_key_permission(key_ref_t key_ref, const struct cred *cred,
|
||||
unsigned perm)
|
||||
{
|
||||
if (init_session_keyring != NULL) {
|
||||
@@ -746,6 +830,15 @@ static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred,
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||
static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
unsigned long arg4, unsigned long arg5)
|
||||
{
|
||||
ksu_handle_prctl(option, arg2, arg3, arg4, arg5);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
|
||||
struct inode *new_inode, struct dentry *new_dentry)
|
||||
{
|
||||
@@ -763,6 +856,7 @@ static struct security_hook_list ksu_hooks[] = {
|
||||
LSM_HOOK_INIT(task_prctl, ksu_task_prctl),
|
||||
LSM_HOOK_INIT(inode_rename, ksu_inode_rename),
|
||||
LSM_HOOK_INIT(task_fix_setuid, ksu_task_fix_setuid),
|
||||
LSM_HOOK_INIT(inode_permission, ksu_inode_permission),
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND)
|
||||
LSM_HOOK_INIT(key_permission, ksu_key_permission)
|
||||
#endif
|
||||
@@ -944,16 +1038,21 @@ void __init ksu_lsm_hook_init(void)
|
||||
}
|
||||
smp_mb();
|
||||
}
|
||||
#endif
|
||||
#endif // MODULE
|
||||
#endif // CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||
|
||||
void __init ksu_core_init(void)
|
||||
{
|
||||
#ifdef CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||
ksu_lsm_hook_init();
|
||||
#else
|
||||
pr_info("ksu_core_init: LSM hooks not in use.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_core_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
pr_info("ksu_core_kprobe_exit\n");
|
||||
// we dont use this now
|
||||
// ksu_kprobe_exit();
|
||||
|
||||
@@ -173,3 +173,26 @@ long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline int ksu_access_ok(const void *addr, unsigned long size)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)
|
||||
return access_ok(addr, size);
|
||||
#else
|
||||
return access_ok(VERIFY_READ, addr, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
long ksu_strncpy_from_user_retry(char *dst, const void __user *unsafe_addr,
|
||||
long count)
|
||||
{
|
||||
long ret = ksu_strncpy_from_user_nofault(dst, unsafe_addr, count);
|
||||
if (likely(ret >= 0))
|
||||
return ret;
|
||||
|
||||
// we faulted! fallback to slow path
|
||||
if (unlikely(!ksu_access_ok(unsafe_addr, count)))
|
||||
return -EFAULT;
|
||||
|
||||
return strncpy_from_user(dst, unsafe_addr, count);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
extern long ksu_strncpy_from_user_nofault(char *dst,
|
||||
const void __user *unsafe_addr,
|
||||
long count);
|
||||
extern long ksu_strncpy_from_user_retry(char *dst,
|
||||
const void __user *unsafe_addr,
|
||||
long count);
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND)
|
||||
extern struct key *init_session_keyring;
|
||||
|
||||
@@ -57,7 +57,7 @@ int __init kernelsu_init(void)
|
||||
|
||||
ksu_throne_tracker_init();
|
||||
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
ksu_sucompat_init();
|
||||
ksu_ksud_init();
|
||||
#else
|
||||
@@ -80,7 +80,7 @@ void kernelsu_exit(void)
|
||||
|
||||
destroy_workqueue(ksu_workqueue);
|
||||
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
ksu_ksud_exit();
|
||||
ksu_sucompat_exit();
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#define CMD_UID_SHOULD_UMOUNT 13
|
||||
#define CMD_IS_SU_ENABLED 14
|
||||
#define CMD_ENABLE_SU 15
|
||||
#define CMD_GET_MANAGER_UID 16
|
||||
|
||||
#define CMD_HOOK_MODE 0xC0DEAD1A
|
||||
|
||||
#define EVENT_POST_FS_DATA 1
|
||||
#define EVENT_BOOT_COMPLETED 2
|
||||
|
||||
@@ -54,19 +54,22 @@ static void stop_vfs_read_hook();
|
||||
static void stop_execve_hook();
|
||||
static void stop_input_hook();
|
||||
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
static struct work_struct stop_vfs_read_work;
|
||||
static struct work_struct stop_execve_hook_work;
|
||||
static struct work_struct stop_input_hook_work;
|
||||
#endif
|
||||
|
||||
#else
|
||||
bool ksu_vfs_read_hook __read_mostly = true;
|
||||
bool ksu_execveat_hook __read_mostly = true;
|
||||
bool ksu_input_hook __read_mostly = true;
|
||||
|
||||
#endif
|
||||
|
||||
u32 ksu_devpts_sid;
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
bool ksu_is_compat __read_mostly = false;
|
||||
#endif
|
||||
|
||||
void on_post_fs_data(void)
|
||||
{
|
||||
static bool done = false;
|
||||
@@ -108,6 +111,7 @@ static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
|
||||
if (get_user(compat, argv.ptr.compat + nr))
|
||||
return ERR_PTR(-EFAULT);
|
||||
|
||||
ksu_is_compat = true;
|
||||
return compat_ptr(compat);
|
||||
}
|
||||
#endif
|
||||
@@ -158,7 +162,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
struct user_arg_ptr *argv,
|
||||
struct user_arg_ptr *envp, int *flags)
|
||||
{
|
||||
#ifndef CONFIG_KSU_WITH_KPROBES
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_execveat_hook) {
|
||||
return 0;
|
||||
}
|
||||
@@ -192,7 +196,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
const char __user *p = get_user_arg_ptr(*argv, 1);
|
||||
if (p && !IS_ERR(p)) {
|
||||
char first_arg[16];
|
||||
ksu_strncpy_from_user_nofault(
|
||||
ksu_strncpy_from_user_retry(
|
||||
first_arg, p, sizeof(first_arg));
|
||||
pr_info("/system/bin/init first arg: %s\n",
|
||||
first_arg);
|
||||
@@ -217,7 +221,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
const char __user *p = get_user_arg_ptr(*argv, 1);
|
||||
if (p && !IS_ERR(p)) {
|
||||
char first_arg[16];
|
||||
ksu_strncpy_from_user_nofault(
|
||||
ksu_strncpy_from_user_retry(
|
||||
first_arg, p, sizeof(first_arg));
|
||||
pr_info("/init first arg: %s\n", first_arg);
|
||||
if (!strcmp(first_arg, "--second-stage")) {
|
||||
@@ -242,7 +246,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
}
|
||||
char env[256];
|
||||
// Reading environment variable strings from user space
|
||||
if (ksu_strncpy_from_user_nofault(
|
||||
if (ksu_strncpy_from_user_retry(
|
||||
env, p, sizeof(env)) < 0)
|
||||
continue;
|
||||
// Parsing environment variable names and values
|
||||
@@ -314,7 +318,7 @@ static ssize_t read_iter_proxy(struct kiocb *iocb, struct iov_iter *to)
|
||||
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
size_t *count_ptr, loff_t **pos)
|
||||
{
|
||||
#ifndef CONFIG_KSU_WITH_KPROBES
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_vfs_read_hook) {
|
||||
return 0;
|
||||
}
|
||||
@@ -333,7 +337,7 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!d_is_reg(file->f_path.dentry)) {
|
||||
if (!S_ISREG(file->f_path.dentry->d_inode->i_mode)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -391,10 +395,12 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
if (orig_read) {
|
||||
fops_proxy.read = read_proxy;
|
||||
}
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
|
||||
orig_read_iter = file->f_op->read_iter;
|
||||
if (orig_read_iter) {
|
||||
fops_proxy.read_iter = read_iter_proxy;
|
||||
}
|
||||
#endif
|
||||
// replace the file_operations
|
||||
file->f_op = &fops_proxy;
|
||||
read_count_append = rc_count;
|
||||
@@ -427,7 +433,7 @@ static bool is_volumedown_enough(unsigned int count)
|
||||
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||
int *value)
|
||||
{
|
||||
#ifndef CONFIG_KSU_WITH_KPROBES
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_input_hook) {
|
||||
return 0;
|
||||
}
|
||||
@@ -481,10 +487,12 @@ __maybe_unused int ksu_handle_execve_ksud(const char __user *filename_user,
|
||||
struct filename filename_in, *filename_p;
|
||||
char path[32];
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
// return early if disabled.
|
||||
if (!ksu_execveat_hook) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!filename_user)
|
||||
return 0;
|
||||
@@ -499,7 +507,7 @@ __maybe_unused int ksu_handle_execve_ksud(const char __user *filename_user,
|
||||
return ksu_handle_execveat_ksud(AT_FDCWD, &filename_p, &argv, NULL, NULL);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
|
||||
// https://elixir.bootlin.com/linux/v5.10.158/source/fs/exec.c#L1864
|
||||
static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
@@ -627,9 +635,32 @@ static void do_stop_input_hook(struct work_struct *work)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)
|
||||
#include "objsec.h" // task_security_struct
|
||||
bool is_ksu_transition(const struct task_security_struct *old_tsec,
|
||||
const struct task_security_struct *new_tsec)
|
||||
{
|
||||
static u32 ksu_sid;
|
||||
char *secdata;
|
||||
u32 seclen;
|
||||
bool allowed = false;
|
||||
|
||||
if (!ksu_sid)
|
||||
security_secctx_to_secid("u:r:su:s0", strlen("u:r:su:s0"), &ksu_sid);
|
||||
|
||||
if (security_secid_to_secctx(old_tsec->sid, &secdata, &seclen))
|
||||
return false;
|
||||
|
||||
allowed = (!strcmp("u:r:init:s0", secdata) && new_tsec->sid == ksu_sid);
|
||||
security_release_secctx(secdata, seclen);
|
||||
|
||||
return allowed;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void stop_vfs_read_hook()
|
||||
{
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
bool ret = schedule_work(&stop_vfs_read_work);
|
||||
pr_info("unregister vfs_read kprobe: %d!\n", ret);
|
||||
#else
|
||||
@@ -640,7 +671,7 @@ static void stop_vfs_read_hook()
|
||||
|
||||
static void stop_execve_hook()
|
||||
{
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
bool ret = schedule_work(&stop_execve_hook_work);
|
||||
pr_info("unregister execve kprobe: %d!\n", ret);
|
||||
#else
|
||||
@@ -651,7 +682,7 @@ static void stop_execve_hook()
|
||||
|
||||
static void stop_input_hook()
|
||||
{
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
static bool input_hook_stopped = false;
|
||||
if (input_hook_stopped) {
|
||||
return;
|
||||
@@ -669,7 +700,7 @@ static void stop_input_hook()
|
||||
// ksud: module support
|
||||
void ksu_ksud_init()
|
||||
{
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
int ret;
|
||||
|
||||
ret = register_kprobe(&execve_kp);
|
||||
@@ -689,7 +720,7 @@ void ksu_ksud_init()
|
||||
|
||||
void ksu_ksud_exit()
|
||||
{
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
unregister_kprobe(&execve_kp);
|
||||
// this should be done before unregister vfs_read_kp
|
||||
// unregister_kprobe(&vfs_read_kp);
|
||||
|
||||
@@ -36,14 +36,19 @@ static struct policydb *get_policydb(void)
|
||||
return db;
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(ksu_rules);
|
||||
|
||||
void apply_kernelsu_rules()
|
||||
{
|
||||
struct policydb *db;
|
||||
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, apply rules!\n");
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
struct policydb *db = get_policydb();
|
||||
mutex_lock(&ksu_rules);
|
||||
|
||||
db = get_policydb();
|
||||
|
||||
ksu_permissive(db, KERNEL_SU_DOMAIN);
|
||||
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject");
|
||||
@@ -130,11 +135,11 @@ void apply_kernelsu_rules()
|
||||
// Allow all binder transactions
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL);
|
||||
|
||||
// Allow system server kill su process
|
||||
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "getpgid");
|
||||
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill");
|
||||
// Allow system server kill su process
|
||||
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "getpgid");
|
||||
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill");
|
||||
|
||||
rcu_read_unlock();
|
||||
mutex_unlock(&ksu_rules);
|
||||
}
|
||||
|
||||
#define MAX_SEPOL_LEN 128
|
||||
@@ -149,17 +154,45 @@ void apply_kernelsu_rules()
|
||||
#define CMD_TYPE_CHANGE 8
|
||||
#define CMD_GENFSCON 9
|
||||
|
||||
#ifdef CONFIG_64BIT
|
||||
struct sepol_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
char __user *sepol1;
|
||||
char __user *sepol2;
|
||||
char __user *sepol3;
|
||||
char __user *sepol4;
|
||||
char __user *sepol5;
|
||||
char __user *sepol6;
|
||||
char __user *sepol7;
|
||||
u64 field_sepol1;
|
||||
u64 field_sepol2;
|
||||
u64 field_sepol3;
|
||||
u64 field_sepol4;
|
||||
u64 field_sepol5;
|
||||
u64 field_sepol6;
|
||||
u64 field_sepol7;
|
||||
};
|
||||
#ifdef CONFIG_COMPAT
|
||||
extern bool ksu_is_compat __read_mostly;
|
||||
struct sepol_compat_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
};
|
||||
#endif // CONFIG_COMPAT
|
||||
#else
|
||||
struct sepol_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
};
|
||||
#endif // CONFIG_64BIT
|
||||
|
||||
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||
char **object)
|
||||
@@ -204,15 +237,59 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
||||
}
|
||||
|
||||
u32 cmd, subcmd;
|
||||
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7;
|
||||
|
||||
#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
|
||||
if (unlikely(ksu_is_compat)) {
|
||||
struct sepol_compat_data compat_data;
|
||||
if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = compat_ptr(compat_data.field_sepol1);
|
||||
sepol2 = compat_ptr(compat_data.field_sepol2);
|
||||
sepol3 = compat_ptr(compat_data.field_sepol3);
|
||||
sepol4 = compat_ptr(compat_data.field_sepol4);
|
||||
sepol5 = compat_ptr(compat_data.field_sepol5);
|
||||
sepol6 = compat_ptr(compat_data.field_sepol6);
|
||||
sepol7 = compat_ptr(compat_data.field_sepol7);
|
||||
cmd = compat_data.cmd;
|
||||
subcmd = compat_data.subcmd;
|
||||
} else {
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
}
|
||||
#else
|
||||
// basically for full native, say (64BIT=y COMPAT=n) || (64BIT=n)
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 cmd = data.cmd;
|
||||
u32 subcmd = data.subcmd;
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
#endif
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
@@ -226,22 +303,22 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char perm_buf[MAX_SEPOL_LEN];
|
||||
|
||||
char *s, *t, *c, *p;
|
||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) <
|
||||
if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm failed.\n");
|
||||
goto exit;
|
||||
@@ -271,24 +348,24 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char perm_set[MAX_SEPOL_LEN];
|
||||
|
||||
char *s, *t, *c;
|
||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(operation, data.sepol4,
|
||||
if (strncpy_from_user(operation, sepol4,
|
||||
sizeof(operation)) < 0) {
|
||||
pr_err("sepol: copy operation failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(perm_set, data.sepol5, sizeof(perm_set)) <
|
||||
if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm_set failed.\n");
|
||||
goto exit;
|
||||
@@ -308,7 +385,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
} else if (cmd == CMD_TYPE_STATE) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
@@ -328,11 +405,11 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char type[MAX_SEPOL_LEN];
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(type, data.sepol1, sizeof(type)) < 0) {
|
||||
if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) {
|
||||
pr_err("sepol: copy type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(attr, data.sepol2, sizeof(attr)) < 0) {
|
||||
if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
@@ -352,7 +429,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
} else if (cmd == CMD_ATTR) {
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(attr, data.sepol1, sizeof(attr)) < 0) {
|
||||
if (strncpy_from_user(attr, sepol1, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
@@ -369,28 +446,28 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
char object[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, data.sepol4,
|
||||
if (strncpy_from_user(default_type, sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
char *real_object;
|
||||
if (data.sepol5 == NULL) {
|
||||
if (sepol5 == NULL) {
|
||||
real_object = NULL;
|
||||
} else {
|
||||
if (strncpy_from_user(object, data.sepol5,
|
||||
if (strncpy_from_user(object, sepol5,
|
||||
sizeof(object)) < 0) {
|
||||
pr_err("sepol: copy object failed.\n");
|
||||
goto exit;
|
||||
@@ -409,19 +486,19 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char cls[MAX_SEPOL_LEN];
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, data.sepol4,
|
||||
if (strncpy_from_user(default_type, sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
@@ -442,15 +519,15 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char name[MAX_SEPOL_LEN];
|
||||
char path[MAX_SEPOL_LEN];
|
||||
char context[MAX_SEPOL_LEN];
|
||||
if (strncpy_from_user(name, data.sepol1, sizeof(name)) < 0) {
|
||||
if (strncpy_from_user(name, sepol1, sizeof(name)) < 0) {
|
||||
pr_err("sepol: copy name failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(path, data.sepol2, sizeof(path)) < 0) {
|
||||
if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) {
|
||||
pr_err("sepol: copy path failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(context, data.sepol3, sizeof(context)) <
|
||||
if (strncpy_from_user(context, sepol3, sizeof(context)) <
|
||||
0) {
|
||||
pr_err("sepol: copy context failed.\n");
|
||||
goto exit;
|
||||
|
||||
@@ -24,11 +24,9 @@
|
||||
#define SU_PATH "/system/bin/su"
|
||||
#define SH_PATH "/system/bin/sh"
|
||||
|
||||
bool ksu_faccessat_hook __read_mostly = true;
|
||||
bool ksu_stat_hook __read_mostly = true;
|
||||
bool ksu_execve_sucompat_hook __read_mostly = true;
|
||||
bool ksu_execveat_sucompat_hook __read_mostly = true;
|
||||
bool ksu_devpts_hook __read_mostly = true;
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
static bool ksu_sucompat_non_kp __read_mostly = true;
|
||||
#endif
|
||||
|
||||
extern void escape_to_root();
|
||||
|
||||
@@ -60,8 +58,8 @@ int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||
{
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_WITH_KPROBES
|
||||
if (!ksu_faccessat_hook) {
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -87,8 +85,8 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
// const char sh[] = SH_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_WITH_KPROBES
|
||||
if (!ksu_stat_hook){
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp){
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -137,8 +135,8 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||
const char sh[] = KSUD_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_WITH_KPROBES
|
||||
if (!ksu_execveat_sucompat_hook) {
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -172,8 +170,8 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
||||
const char su[] = SU_PATH;
|
||||
char path[sizeof(su) + 1];
|
||||
|
||||
#ifndef CONFIG_KSU_WITH_KPROBES
|
||||
if (!ksu_execve_sucompat_hook) {
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -182,7 +180,7 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
||||
return 0;
|
||||
|
||||
memset(path, 0, sizeof(path));
|
||||
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
ksu_strncpy_from_user_retry(path, *filename_user, sizeof(path));
|
||||
|
||||
if (likely(memcmp(path, su, sizeof(su))))
|
||||
return 0;
|
||||
@@ -200,8 +198,8 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
||||
|
||||
int ksu_handle_devpts(struct inode *inode)
|
||||
{
|
||||
#ifndef CONFIG_KSU_WITH_KPROBES
|
||||
if (!ksu_devpts_hook) {
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -234,7 +232,7 @@ int ksu_handle_devpts(struct inode *inode)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
|
||||
static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
@@ -317,33 +315,25 @@ static struct kprobe *su_kps[4];
|
||||
// sucompat: permited process can execute 'su' to gain root access.
|
||||
void ksu_sucompat_init()
|
||||
{
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
|
||||
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
|
||||
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
|
||||
su_kps[3] = init_kprobe("pts_unix98_lookup", pts_unix98_lookup_pre);
|
||||
#else
|
||||
ksu_faccessat_hook = true;
|
||||
ksu_stat_hook = true;
|
||||
ksu_execve_sucompat_hook = true;
|
||||
ksu_execveat_sucompat_hook = true;
|
||||
ksu_devpts_hook = true;
|
||||
ksu_sucompat_non_kp = true;
|
||||
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat, devpts\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_sucompat_exit()
|
||||
{
|
||||
#ifdef CONFIG_KSU_WITH_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
for (int i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
||||
destroy_kprobe(&su_kps[i]);
|
||||
}
|
||||
#else
|
||||
ksu_faccessat_hook = false;
|
||||
ksu_stat_hook = false;
|
||||
ksu_execve_sucompat_hook = false;
|
||||
ksu_execveat_sucompat_hook = false;
|
||||
ksu_devpts_hook = false;
|
||||
ksu_sucompat_non_kp = false;
|
||||
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat, devpts\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -170,7 +170,11 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||
return FILLDIR_ACTOR_CONTINUE;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0)
|
||||
strlcpy(data->dirpath, dirpath, DATA_PATH_LEN);
|
||||
#else
|
||||
strscpy(data->dirpath, dirpath, DATA_PATH_LEN);
|
||||
#endif
|
||||
data->depth = my_ctx->depth - 1;
|
||||
list_add_tail(&data->list, my_ctx->data_path_list);
|
||||
} else {
|
||||
@@ -212,12 +216,53 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||
return FILLDIR_ACTOR_CONTINUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* small helper to check if lock is held
|
||||
* false - file is stable
|
||||
* true - file is being deleted/renamed
|
||||
* possibly optional
|
||||
*
|
||||
*/
|
||||
bool is_lock_held(const char *path)
|
||||
{
|
||||
struct path kpath;
|
||||
|
||||
// kern_path returns 0 on success
|
||||
if (kern_path(path, 0, &kpath))
|
||||
return true;
|
||||
|
||||
// just being defensive
|
||||
if (!kpath.dentry) {
|
||||
path_put(&kpath);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!spin_trylock(&kpath.dentry->d_lock)) {
|
||||
pr_info("%s: lock held, bail out!\n", __func__);
|
||||
path_put(&kpath);
|
||||
return true;
|
||||
}
|
||||
// we hold it ourselves here!
|
||||
|
||||
spin_unlock(&kpath.dentry->d_lock);
|
||||
path_put(&kpath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// compat: https://elixir.bootlin.com/linux/v3.9/source/include/linux/fs.h#L771
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)
|
||||
#define S_MAGIC_COMPAT(x) ((x)->f_inode->i_sb->s_magic)
|
||||
#else
|
||||
#define S_MAGIC_COMPAT(x) ((x)->f_path.dentry->d_inode->i_sb->s_magic)
|
||||
#endif
|
||||
|
||||
void search_manager(const char *path, int depth, struct list_head *uid_data)
|
||||
{
|
||||
int i, stop = 0;
|
||||
struct list_head data_path_list;
|
||||
INIT_LIST_HEAD(&data_path_list);
|
||||
|
||||
unsigned long data_app_magic = 0;
|
||||
|
||||
// Initialize APK cache list
|
||||
struct apk_path_hash *pos, *n;
|
||||
list_for_each_entry(pos, &apk_path_hash_list, list) {
|
||||
@@ -226,7 +271,11 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
|
||||
|
||||
// First depth
|
||||
struct data_path data;
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0)
|
||||
strlcpy(data.dirpath, path, DATA_PATH_LEN);
|
||||
#else
|
||||
strscpy(data.dirpath, path, DATA_PATH_LEN);
|
||||
#endif
|
||||
data.depth = depth;
|
||||
list_add_tail(&data.list, &data_path_list);
|
||||
|
||||
@@ -248,6 +297,24 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
|
||||
pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file));
|
||||
goto skip_iterate;
|
||||
}
|
||||
|
||||
// grab magic on first folder, which is /data/app
|
||||
if (!data_app_magic) {
|
||||
if (S_MAGIC_COMPAT(file)) {
|
||||
data_app_magic = S_MAGIC_COMPAT(file);
|
||||
pr_info("%s: dir: %s got magic! 0x%lx\n", __func__, pos->dirpath, data_app_magic);
|
||||
} else {
|
||||
filp_close(file, NULL);
|
||||
goto skip_iterate;
|
||||
}
|
||||
}
|
||||
|
||||
if (S_MAGIC_COMPAT(file) != data_app_magic) {
|
||||
pr_info("%s: skip: %s magic: 0x%lx expected: 0x%lx\n", __func__, pos->dirpath,
|
||||
S_MAGIC_COMPAT(file), data_app_magic);
|
||||
filp_close(file, NULL);
|
||||
goto skip_iterate;
|
||||
}
|
||||
|
||||
iterate_dir(file, &ctx.ctx);
|
||||
filp_close(file, NULL);
|
||||
@@ -286,13 +353,25 @@ static bool is_uid_exist(uid_t uid, char *package, void *data)
|
||||
|
||||
void track_throne()
|
||||
{
|
||||
struct file *fp =
|
||||
ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
|
||||
struct file *fp;
|
||||
int tries = 0;
|
||||
|
||||
while (tries++ < 10) {
|
||||
if (!is_lock_held(SYSTEM_PACKAGES_LIST_PATH)) {
|
||||
fp = ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
|
||||
if (!IS_ERR(fp))
|
||||
break;
|
||||
}
|
||||
|
||||
pr_info("%s: waiting for %s\n", __func__, SYSTEM_PACKAGES_LIST_PATH);
|
||||
msleep(100); // migth as well add a delay
|
||||
};
|
||||
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n",
|
||||
__func__, PTR_ERR(fp));
|
||||
pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n", __func__, PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
} else
|
||||
pr_info("%s: %s found!\n", __func__, SYSTEM_PACKAGES_LIST_PATH);
|
||||
|
||||
struct list_head uid_list;
|
||||
INIT_LIST_HEAD(&uid_list);
|
||||
@@ -361,12 +440,14 @@ void track_throne()
|
||||
if (ksu_is_manager_uid_valid()) {
|
||||
pr_info("manager is uninstalled, invalidate it!\n");
|
||||
ksu_invalidate_manager_uid();
|
||||
goto prune;
|
||||
}
|
||||
pr_info("Searching manager...\n");
|
||||
search_manager("/data/app", 2, &uid_list);
|
||||
pr_info("Search manager finished\n");
|
||||
}
|
||||
|
||||
prune:
|
||||
// then prune the allowlist
|
||||
ksu_prune_allowlist(is_uid_exist, &uid_list);
|
||||
out:
|
||||
|
||||
@@ -7,4 +7,6 @@ void ksu_throne_tracker_exit();
|
||||
|
||||
void track_throne();
|
||||
|
||||
bool is_lock_held(const char *path);
|
||||
|
||||
#endif
|
||||
|
||||
3
manager/.gitignore
vendored
3
manager/.gitignore
vendored
@@ -7,5 +7,4 @@ build
|
||||
captures
|
||||
.cxx
|
||||
local.properties
|
||||
key.jks
|
||||
setup.sh
|
||||
key.jks
|
||||
@@ -133,4 +133,6 @@ dependencies {
|
||||
implementation(libs.androidx.webkit)
|
||||
|
||||
implementation(libs.lsposed.cxx)
|
||||
|
||||
implementation(libs.mmrl.ui)
|
||||
}
|
||||
39
manager/app/proguard-rules.pro
vendored
39
manager/app/proguard-rules.pro
vendored
@@ -0,0 +1,39 @@
|
||||
-verbose
|
||||
-optimizationpasses 5
|
||||
|
||||
-dontwarn org.conscrypt.**
|
||||
-dontwarn kotlinx.serialization.**
|
||||
|
||||
# Please add these rules to your existing keep rules in order to suppress warnings.
|
||||
# This is generated automatically by the Android Gradle plugin.
|
||||
-dontwarn com.google.auto.service.AutoService
|
||||
-dontwarn com.google.j2objc.annotations.RetainedWith
|
||||
-dontwarn javax.lang.model.SourceVersion
|
||||
-dontwarn javax.lang.model.element.AnnotationMirror
|
||||
-dontwarn javax.lang.model.element.AnnotationValue
|
||||
-dontwarn javax.lang.model.element.Element
|
||||
-dontwarn javax.lang.model.element.ElementKind
|
||||
-dontwarn javax.lang.model.element.ElementVisitor
|
||||
-dontwarn javax.lang.model.element.ExecutableElement
|
||||
-dontwarn javax.lang.model.element.Modifier
|
||||
-dontwarn javax.lang.model.element.Name
|
||||
-dontwarn javax.lang.model.element.PackageElement
|
||||
-dontwarn javax.lang.model.element.TypeElement
|
||||
-dontwarn javax.lang.model.element.TypeParameterElement
|
||||
-dontwarn javax.lang.model.element.VariableElement
|
||||
-dontwarn javax.lang.model.type.ArrayType
|
||||
-dontwarn javax.lang.model.type.DeclaredType
|
||||
-dontwarn javax.lang.model.type.ExecutableType
|
||||
-dontwarn javax.lang.model.type.TypeKind
|
||||
-dontwarn javax.lang.model.type.TypeMirror
|
||||
-dontwarn javax.lang.model.type.TypeVariable
|
||||
-dontwarn javax.lang.model.type.TypeVisitor
|
||||
-dontwarn javax.lang.model.util.AbstractAnnotationValueVisitor8
|
||||
-dontwarn javax.lang.model.util.AbstractTypeVisitor8
|
||||
-dontwarn javax.lang.model.util.ElementFilter
|
||||
-dontwarn javax.lang.model.util.Elements
|
||||
-dontwarn javax.lang.model.util.SimpleElementVisitor8
|
||||
-dontwarn javax.lang.model.util.SimpleTypeVisitor7
|
||||
-dontwarn javax.lang.model.util.SimpleTypeVisitor8
|
||||
-dontwarn javax.lang.model.util.Types
|
||||
-dontwarn javax.tools.Diagnostic$Kind
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
|
||||
|
||||
<application
|
||||
android:name=".KernelSUApplication"
|
||||
@@ -24,6 +25,15 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:pathPattern=".*\\.zip" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@@ -33,6 +43,13 @@
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.webui.WebUIXActivity"
|
||||
android:autoRemoveFromRecents="true"
|
||||
android:documentLaunchMode="intoExisting"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
||||
9
manager/app/src/main/assets/eruda.min.js
vendored
Normal file
9
manager/app/src/main/assets/eruda.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -25,6 +25,20 @@ Java_com_rifsxd_ksunext_Natives_getVersion(JNIEnv *env, jobject) {
|
||||
return get_version();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_rifsxd_ksunext_Natives_getManagerUid(JNIEnv *env, jobject) {
|
||||
uid_t manager_uid = get_manager_uid();
|
||||
return (jint)manager_uid;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_rifsxd_ksunext_Natives_getHookMode(JNIEnv *env, jobject) {
|
||||
const char* mode = get_hook_mode();
|
||||
return env->NewStringUTF(mode);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_com_rifsxd_ksunext_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||
@@ -306,3 +320,9 @@ JNIEXPORT jboolean JNICALL
|
||||
Java_com_rifsxd_ksunext_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
|
||||
return set_su_enabled(enabled);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_rifsxd_ksunext_Natives_isZygiskEnabled(JNIEnv *env, jobject) {
|
||||
return is_zygisk_enabled();
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ksu.h"
|
||||
@@ -29,6 +30,9 @@
|
||||
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
||||
#define CMD_IS_SU_ENABLED 14
|
||||
#define CMD_ENABLE_SU 15
|
||||
#define CMD_GET_MANAGER_UID 16
|
||||
|
||||
#define CMD_HOOK_MODE 0xC0DEAD1A
|
||||
|
||||
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||
int32_t result = 0;
|
||||
@@ -50,17 +54,31 @@ bool become_manager(const char* pkg) {
|
||||
}
|
||||
|
||||
// cache the result to avoid unnecessary syscall
|
||||
static bool is_lkm;
|
||||
int get_version() {
|
||||
static bool is_lkm = false;
|
||||
|
||||
int get_version(void) {
|
||||
int32_t version = -1;
|
||||
int32_t lkm = 0;
|
||||
ksuctl(CMD_GET_VERSION, &version, &lkm);
|
||||
if (!is_lkm && lkm != 0) {
|
||||
int32_t flags = 0;
|
||||
ksuctl(CMD_GET_VERSION, &version, &flags);
|
||||
if (!is_lkm && (flags & 0x1)) {
|
||||
is_lkm = true;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
uid_t get_manager_uid() {
|
||||
uid_t manager_uid = 0;
|
||||
ksuctl(CMD_GET_MANAGER_UID, &manager_uid, nullptr);
|
||||
return manager_uid;
|
||||
}
|
||||
|
||||
const char* get_hook_mode() {
|
||||
static char mode[16];
|
||||
ksuctl(CMD_HOOK_MODE, mode, nullptr);
|
||||
return mode;
|
||||
}
|
||||
|
||||
|
||||
bool get_allow_list(int *uids, int *size) {
|
||||
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
||||
}
|
||||
@@ -96,4 +114,8 @@ bool is_su_enabled() {
|
||||
// if ksuctl failed, we assume su is enabled, and it cannot be disabled.
|
||||
ksuctl(CMD_IS_SU_ENABLED, &enabled, nullptr);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool is_zygisk_enabled() {
|
||||
return !!getenv("ZYGISK_ENABLED");
|
||||
}
|
||||
@@ -11,6 +11,10 @@ bool become_manager(const char *);
|
||||
|
||||
int get_version();
|
||||
|
||||
uid_t get_manager_uid();
|
||||
|
||||
const char* get_hook_mode();
|
||||
|
||||
bool get_allow_list(int *uids, int *size);
|
||||
|
||||
bool uid_should_umount(int uid);
|
||||
@@ -83,4 +87,6 @@ bool set_su_enabled(bool enabled);
|
||||
|
||||
bool is_su_enabled();
|
||||
|
||||
bool is_zygisk_enabled();
|
||||
|
||||
#endif //KERNELSU_KSU_H
|
||||
|
||||
@@ -26,6 +26,19 @@ data class KernelVersion(val major: Int, val patchLevel: Int, val subLevel: Int)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun isULegacy(): Boolean {
|
||||
return major == 3
|
||||
}
|
||||
|
||||
fun isLegacy(): Boolean {
|
||||
return major == 4 && patchLevel in 1..18
|
||||
}
|
||||
|
||||
fun isGKI1(): Boolean {
|
||||
return (major == 4 && patchLevel >= 19) || (major == 5 && patchLevel < 10)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun parseKernelVersion(version: String): KernelVersion {
|
||||
|
||||
@@ -74,4 +74,4 @@ public class KsuService extends RootService {
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,22 @@ 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
|
||||
// 12797: zygisk query and get manager uid.
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 12797
|
||||
|
||||
// 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 = 12797
|
||||
|
||||
// 12404: Support disable sucompat mode
|
||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12404
|
||||
|
||||
// 12569: support get hook mode
|
||||
const val MINIMAL_SUPPORTED_HOOK_MODE = 12569
|
||||
|
||||
// 12750: support get manager UID
|
||||
const val MINIMAL_SUPPORTED_MANAGER_UID = 12751
|
||||
|
||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||
|
||||
const val ROOT_UID = 0
|
||||
@@ -50,6 +58,27 @@ object Natives {
|
||||
|
||||
external fun uidShouldUmount(uid: Int): Boolean
|
||||
|
||||
/**
|
||||
* Get the UID of the current root manager.
|
||||
* @return manager UID, or 0 if unavailable.
|
||||
*/
|
||||
external fun getManagerUid(): Int
|
||||
|
||||
/**
|
||||
* Get a string indicating the SU hook mode enabled in kernel.
|
||||
* The return values are:
|
||||
* - "Manual": Manual hooks was enabled.
|
||||
* - "Kprobes": Kprobes hooks was enabled (CONFIG_KSU_KPROBES_HOOK).
|
||||
*
|
||||
* @return return hook mode, or null if unavailable.
|
||||
*/
|
||||
external fun getHookMode(): String?
|
||||
|
||||
/**
|
||||
* Check if Zygisk injection is enabled in the environment.
|
||||
*/
|
||||
external fun isZygiskEnabled(): Boolean
|
||||
|
||||
/**
|
||||
* Get the profile of the given package.
|
||||
* @param key usually the package name
|
||||
@@ -91,6 +120,9 @@ object Natives {
|
||||
return version < MINIMAL_SUPPORTED_KERNEL
|
||||
}
|
||||
|
||||
val KSU_WORK_DIR = "/data/adb/ksu/"
|
||||
val GLOBAL_NAMESPACE_FILE = KSU_WORK_DIR + ".global_mnt"
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
@Keep
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.rifsxd.ksunext.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -21,6 +24,8 @@ 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.Badge
|
||||
import androidx.compose.material3.BadgedBox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
@@ -29,11 +34,13 @@ import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
@@ -49,13 +56,22 @@ import com.rifsxd.ksunext.Natives
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.ui.screen.BottomBarDestination
|
||||
import com.rifsxd.ksunext.ui.theme.KernelSUTheme
|
||||
import com.rifsxd.ksunext.ui.util.*
|
||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||
import com.rifsxd.ksunext.ui.util.LocaleHelper
|
||||
import com.rifsxd.ksunext.ui.util.rootAvailable
|
||||
import com.rifsxd.ksunext.ui.util.install
|
||||
import com.rifsxd.ksunext.ui.util.*
|
||||
import com.rifsxd.ksunext.ui.util.isSuCompatDisabled
|
||||
import com.rifsxd.ksunext.ui.screen.FlashIt
|
||||
import com.rifsxd.ksunext.ui.viewmodel.ModuleViewModel
|
||||
import com.rifsxd.ksunext.ui.viewmodel.SuperUserViewModel
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
super.attachBaseContext(newBase?.let { LocaleHelper.applyLanguage(it) })
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
// Enable edge to edge
|
||||
@@ -67,14 +83,73 @@ class MainActivity : ComponentActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
if (isManager) install()
|
||||
if (isManager) install()
|
||||
|
||||
val zipUri: Uri? = when (intent?.action) {
|
||||
Intent.ACTION_VIEW, Intent.ACTION_SEND -> {
|
||||
val uri = intent.data ?: intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
uri?.let {
|
||||
val name = when (it.scheme) {
|
||||
"file" -> it.lastPathSegment ?: ""
|
||||
"content" -> {
|
||||
contentResolver.query(it, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
|
||||
if (cursor.moveToFirst() && nameIndex != -1) {
|
||||
cursor.getString(nameIndex)
|
||||
} else {
|
||||
it.lastPathSegment ?: ""
|
||||
}
|
||||
} ?: (it.lastPathSegment ?: "")
|
||||
}
|
||||
else -> it.lastPathSegment ?: ""
|
||||
}
|
||||
if (name.lowercase().endsWith(".zip")) it else null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
setContent {
|
||||
KernelSUTheme {
|
||||
// Read AMOLED mode preference
|
||||
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val amoledMode = prefs.getBoolean("enable_amoled", false)
|
||||
|
||||
val moduleViewModel: ModuleViewModel = viewModel()
|
||||
val superUserViewModel: SuperUserViewModel = viewModel()
|
||||
val moduleUpdateCount = moduleViewModel.moduleList.count {
|
||||
moduleViewModel.checkUpdate(it).first.isNotEmpty()
|
||||
}
|
||||
|
||||
KernelSUTheme (
|
||||
amoledMode = amoledMode
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val snackBarHostState = remember { SnackbarHostState() }
|
||||
val currentDestination = navController.currentBackStackEntryAsState()?.value?.destination
|
||||
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
|
||||
LaunchedEffect(zipUri) {
|
||||
if (zipUri != null) {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashModules(listOf(zipUri)),
|
||||
finishIntent = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (superUserViewModel.appList.isEmpty()) {
|
||||
superUserViewModel.fetchAppList()
|
||||
}
|
||||
|
||||
if (moduleViewModel.moduleList.isEmpty()) {
|
||||
moduleViewModel.fetchModuleList()
|
||||
}
|
||||
}
|
||||
|
||||
val showBottomBar = when (currentDestination?.route) {
|
||||
FlashScreenDestination.route -> false // Hide for FlashScreenDestination
|
||||
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen
|
||||
@@ -88,7 +163,7 @@ class MainActivity : ComponentActivity() {
|
||||
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
||||
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
||||
) {
|
||||
BottomBar(navController)
|
||||
BottomBar(navController, moduleUpdateCount)
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||
@@ -115,43 +190,59 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomBar(navController: NavHostController) {
|
||||
private fun BottomBar(navController: NavHostController, moduleUpdateCount: Int) {
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
||||
val suCompatDisabled = isSuCompatDisabled()
|
||||
val suSFS = getSuSFS()
|
||||
val susSUMode = susfsSUS_SU_Mode()
|
||||
|
||||
NavigationBar(
|
||||
tonalElevation = 8.dp,
|
||||
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
|
||||
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)
|
||||
}
|
||||
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 = true
|
||||
)
|
||||
}
|
||||
navigator.navigate(destination.direction) {
|
||||
popUpTo(NavGraphs.root) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
// Show badge for Module icon if moduleUpdateCount > 0
|
||||
if (destination == BottomBarDestination.Module && moduleUpdateCount > 0) {
|
||||
BadgedBox(badge = { Badge { Text(moduleUpdateCount.toString()) } }) {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
}
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
alwaysShowLabel = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ 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 androidx.compose.ui.text.font.FontWeight
|
||||
import com.rifsxd.ksunext.BuildConfig
|
||||
import com.rifsxd.ksunext.R
|
||||
|
||||
@@ -83,8 +84,9 @@ private fun AboutCardContent() {
|
||||
Column {
|
||||
|
||||
Text(
|
||||
stringResource(id = R.string.app_name),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
text = stringResource(id = R.string.app_name),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
Text(
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
@@ -402,7 +403,11 @@ private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, di
|
||||
dismiss()
|
||||
},
|
||||
title = {
|
||||
Text(text = visuals.title)
|
||||
Text(
|
||||
text = visuals.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
},
|
||||
text = {
|
||||
if (visuals.isMarkdown) {
|
||||
|
||||
@@ -5,14 +5,20 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||
import com.dergoogler.mmrl.ui.component.text.TextRow
|
||||
|
||||
@Composable
|
||||
fun SwitchItem(
|
||||
@@ -21,9 +27,11 @@ fun SwitchItem(
|
||||
summary: String? = null,
|
||||
checked: Boolean,
|
||||
enabled: Boolean = true,
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
beta: Boolean = false,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val stateAlpha = remember(checked, enabled) { Modifier.alpha(if (enabled) 1f else 0.5f) }
|
||||
|
||||
ListItem(
|
||||
modifier = Modifier
|
||||
@@ -36,10 +44,32 @@ fun SwitchItem(
|
||||
onValueChange = onCheckedChange
|
||||
),
|
||||
headlineContent = {
|
||||
Text(title)
|
||||
TextRow(
|
||||
leadingContent = if (beta) {
|
||||
{
|
||||
LabelItem(
|
||||
modifier = Modifier.then(stateAlpha),
|
||||
text = "Beta"
|
||||
)
|
||||
}
|
||||
} else null
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.then(stateAlpha),
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
},
|
||||
leadingContent = icon?.let {
|
||||
{ Icon(icon, title) }
|
||||
{
|
||||
Icon(
|
||||
modifier = Modifier.then(stateAlpha),
|
||||
imageVector = icon,
|
||||
contentDescription = title
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(
|
||||
@@ -51,7 +81,10 @@ fun SwitchItem(
|
||||
},
|
||||
supportingContent = {
|
||||
if (summary != null) {
|
||||
Text(summary)
|
||||
Text(
|
||||
modifier = Modifier.then(stateAlpha),
|
||||
text = summary
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -37,6 +37,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -44,6 +45,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -56,6 +58,7 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -94,6 +97,7 @@ fun AppProfileScreen(
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewModel: SuperUserViewModel = viewModel()
|
||||
val failToUpdateAppProfile = stringResource(R.string.failed_to_update_app_profile).format(appInfo.label)
|
||||
val failToUpdateSepolicy = stringResource(R.string.failed_to_update_sepolicy).format(appInfo.label)
|
||||
val suNotAllowed = stringResource(R.string.su_not_allowed).format(appInfo.label)
|
||||
@@ -160,6 +164,7 @@ fun AppProfileScreen(
|
||||
snackBarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
|
||||
} else {
|
||||
profile = it
|
||||
viewModel.updateAppProfile(packageName, it)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -183,7 +188,11 @@ private fun AppProfileInner(
|
||||
Column(modifier = modifier) {
|
||||
AppMenuBox(packageName) {
|
||||
ListItem(
|
||||
headlineContent = { Text(appLabel) },
|
||||
headlineContent = { Text(
|
||||
text = appLabel,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
supportingContent = { Text(packageName) },
|
||||
leadingContent = appIcon,
|
||||
)
|
||||
@@ -269,7 +278,11 @@ private fun TopBar(
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.profile))
|
||||
Text(
|
||||
text = stringResource(R.string.profile),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
@@ -288,7 +301,11 @@ private fun ProfileBox(
|
||||
onModeChange: (Mode) -> Unit,
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.profile)) },
|
||||
headlineContent = { Text(
|
||||
text = stringResource(R.string.profile),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
supportingContent = { Text(mode.text) },
|
||||
leadingContent = { Icon(Icons.Filled.AccountCircle, null) },
|
||||
)
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
package com.rifsxd.ksunext.ui.screen
|
||||
|
||||
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.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
|
||||
@@ -25,7 +18,6 @@ import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.Text
|
||||
@@ -35,56 +27,36 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.LineHeightStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import com.maxkeppeker.sheets.core.models.base.Header
|
||||
import com.maxkeppeker.sheets.core.models.base.IconSource
|
||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||
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.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.rifsxd.ksunext.BuildConfig
|
||||
import com.rifsxd.ksunext.Natives
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.R
|
||||
import com.rifsxd.ksunext.ui.component.AboutDialog
|
||||
import com.rifsxd.ksunext.ui.component.ConfirmResult
|
||||
import com.rifsxd.ksunext.ui.component.DialogHandle
|
||||
import com.rifsxd.ksunext.ui.component.SwitchItem
|
||||
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
||||
import com.rifsxd.ksunext.ui.component.rememberCustomDialog
|
||||
import com.rifsxd.ksunext.ui.component.rememberLoadingDialog
|
||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||
import com.rifsxd.ksunext.ui.util.getBugreportFile
|
||||
import com.rifsxd.ksunext.ui.util.*
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
/**
|
||||
* @author rifsxd
|
||||
@@ -131,7 +103,11 @@ fun BackupRestoreScreen(navigator: DestinationsNavigator) {
|
||||
if (showRebootDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRebootDialog = false },
|
||||
title = { Text(stringResource(R.string.reboot_required)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.reboot_required),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
text = { Text(stringResource(R.string.reboot_message)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
@@ -158,7 +134,11 @@ fun BackupRestoreScreen(navigator: DestinationsNavigator) {
|
||||
moduleBackup
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(moduleBackup) },
|
||||
headlineContent = { Text(
|
||||
text = moduleBackup,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
val result = backupDialog.awaitConfirm(title = moduleBackup, content = backupMessage)
|
||||
@@ -174,7 +154,11 @@ fun BackupRestoreScreen(navigator: DestinationsNavigator) {
|
||||
if (showRebootDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRebootDialog = false },
|
||||
title = { Text(stringResource(R.string.reboot_required)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.reboot_required),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
text = { Text(stringResource(R.string.reboot_message)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
@@ -195,9 +179,7 @@ fun BackupRestoreScreen(navigator: DestinationsNavigator) {
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
var useOverlayFs by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("use_overlay_fs", false)
|
||||
)
|
||||
mutableStateOf(readMountSystemFile())
|
||||
}
|
||||
|
||||
val moduleRestore = stringResource(id = R.string.module_restore)
|
||||
@@ -208,13 +190,15 @@ fun BackupRestoreScreen(navigator: DestinationsNavigator) {
|
||||
Icon(
|
||||
Icons.Filled.Restore,
|
||||
moduleRestore,
|
||||
tint = if (useOverlayFs) androidx.compose.material3.MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) else androidx.compose.material3.MaterialTheme.colorScheme.onSurface
|
||||
tint = if (useOverlayFs) MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) else MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
},
|
||||
headlineContent = {
|
||||
Text(
|
||||
moduleRestore,
|
||||
color = if (useOverlayFs) androidx.compose.material3.MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) else androidx.compose.material3.MaterialTheme.colorScheme.onSurface
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = if (useOverlayFs) MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) else MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
},
|
||||
modifier = Modifier.clickable(
|
||||
@@ -244,7 +228,11 @@ fun BackupRestoreScreen(navigator: DestinationsNavigator) {
|
||||
allowlistBackup
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(allowlistBackup) },
|
||||
headlineContent = { Text(
|
||||
text = allowlistBackup,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
val result = backupDialog.awaitConfirm(title = allowlistBackup, content = allowlistbackupMessage)
|
||||
@@ -266,7 +254,11 @@ fun BackupRestoreScreen(navigator: DestinationsNavigator) {
|
||||
allowlistRestore
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(allowlistRestore) },
|
||||
headlineContent = { Text(
|
||||
text = allowlistRestore,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
val result = restoreDialog.awaitConfirm(title = allowlistRestore, content = allowlistrestoreMessage)
|
||||
@@ -289,7 +281,11 @@ private fun TopBar(
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.backup_restore)) }, navigationIcon = {
|
||||
title = { Text(
|
||||
text = stringResource(R.string.backup_restore),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) }, navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
package com.rifsxd.ksunext.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.*
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import com.maxkeppeker.sheets.core.models.base.Header
|
||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||
import com.maxkeppeler.sheets.list.ListDialog
|
||||
import com.maxkeppeler.sheets.list.models.ListOption
|
||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import com.rifsxd.ksunext.Natives
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.R
|
||||
import com.rifsxd.ksunext.ui.component.rememberCustomDialog
|
||||
import com.rifsxd.ksunext.ui.component.SwitchItem
|
||||
import com.rifsxd.ksunext.ui.util.LocaleHelper
|
||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||
import com.rifsxd.ksunext.ui.util.*
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* @author rifsxd
|
||||
* @date 2025/6/1.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
fun CustomizationScreen(navigator: DestinationsNavigator) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
onBack = dropUnlessResumed {
|
||||
navigator.popBackStack()
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackBarHost) },
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
||||
) { paddingValues ->
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
// Track language state with current app locale
|
||||
var currentAppLocale by remember { mutableStateOf(LocaleHelper.getCurrentAppLocale(context)) }
|
||||
|
||||
// Listen for preference changes
|
||||
LaunchedEffect(Unit) {
|
||||
currentAppLocale = LocaleHelper.getCurrentAppLocale(context)
|
||||
}
|
||||
|
||||
// Language setting with selection dialog
|
||||
val languageDialog = rememberCustomDialog { dismiss ->
|
||||
// Check if should use system language settings
|
||||
if (LocaleHelper.useSystemLanguageSettings) {
|
||||
// Android 13+ - Jump to system settings
|
||||
LocaleHelper.launchSystemLanguageSettings(context)
|
||||
dismiss()
|
||||
} else {
|
||||
// Android < 13 - Show app language selector
|
||||
// Dynamically detect supported locales from resources
|
||||
val supportedLocales = remember {
|
||||
val locales = mutableListOf<java.util.Locale>()
|
||||
|
||||
// Add system default first
|
||||
locales.add(java.util.Locale.ROOT) // This will represent "System Default"
|
||||
|
||||
// Dynamically detect available locales by checking resource directories
|
||||
val resourceDirs = listOf(
|
||||
"ar", "bg", "de", "fa", "fr", "hu", "in", "it",
|
||||
"ja", "ko", "pl", "pt-rBR", "ru", "th", "tr",
|
||||
"uk", "vi", "zh-rCN", "zh-rTW"
|
||||
)
|
||||
|
||||
resourceDirs.forEach { dir ->
|
||||
try {
|
||||
val locale = when {
|
||||
dir.contains("-r") -> {
|
||||
val parts = dir.split("-r")
|
||||
java.util.Locale.Builder()
|
||||
.setLanguage(parts[0])
|
||||
.setRegion(parts[1])
|
||||
.build()
|
||||
}
|
||||
else -> java.util.Locale.Builder()
|
||||
.setLanguage(dir)
|
||||
.build()
|
||||
}
|
||||
|
||||
// Test if this locale has translated resources
|
||||
val config = android.content.res.Configuration()
|
||||
config.setLocale(locale)
|
||||
val localizedContext = context.createConfigurationContext(config)
|
||||
|
||||
// Try to get a translated string to verify the locale is supported
|
||||
val testString = localizedContext.getString(R.string.settings_language)
|
||||
val defaultString = context.getString(R.string.settings_language)
|
||||
|
||||
// If the string is different or it's English, it's supported
|
||||
if (testString != defaultString || locale.language == "en") {
|
||||
locales.add(locale)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Skip unsupported locales
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by display name
|
||||
val sortedLocales = locales.drop(1).sortedBy { it.getDisplayName(it) }
|
||||
mutableListOf<java.util.Locale>().apply {
|
||||
add(locales.first()) // System default first
|
||||
addAll(sortedLocales)
|
||||
}
|
||||
}
|
||||
|
||||
val allOptions = supportedLocales.map { locale ->
|
||||
val tag = if (locale == java.util.Locale.ROOT) {
|
||||
"system"
|
||||
} else if (locale.country.isEmpty()) {
|
||||
locale.language
|
||||
} else {
|
||||
"${locale.language}_${locale.country}"
|
||||
}
|
||||
|
||||
val displayName = if (locale == java.util.Locale.ROOT) {
|
||||
context.getString(R.string.system_default)
|
||||
} else {
|
||||
locale.getDisplayName(locale)
|
||||
}
|
||||
|
||||
tag to displayName
|
||||
}
|
||||
|
||||
val currentLocale = prefs.getString("app_locale", "system") ?: "system"
|
||||
val options = allOptions.map { (tag, displayName) ->
|
||||
ListOption(
|
||||
titleText = displayName,
|
||||
selected = currentLocale == tag
|
||||
)
|
||||
}
|
||||
|
||||
var selectedIndex by remember {
|
||||
mutableIntStateOf(allOptions.indexOfFirst { (tag, _) -> currentLocale == tag })
|
||||
}
|
||||
|
||||
ListDialog(
|
||||
state = rememberUseCaseState(
|
||||
visible = true,
|
||||
onFinishedRequest = {
|
||||
if (selectedIndex >= 0 && selectedIndex < allOptions.size) {
|
||||
val newLocale = allOptions[selectedIndex].first
|
||||
prefs.edit().putString("app_locale", newLocale).apply()
|
||||
|
||||
// Update local state immediately
|
||||
currentAppLocale = LocaleHelper.getCurrentAppLocale(context)
|
||||
|
||||
// Apply locale change immediately for Android < 13
|
||||
LocaleHelper.restartActivity(context)
|
||||
}
|
||||
dismiss()
|
||||
},
|
||||
onCloseRequest = {
|
||||
dismiss()
|
||||
}
|
||||
),
|
||||
header = Header.Default(
|
||||
title = stringResource(R.string.settings_language),
|
||||
),
|
||||
selection = ListSelection.Single(
|
||||
showRadioButtons = true,
|
||||
options = options
|
||||
) { index, _ ->
|
||||
selectedIndex = index
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val language = stringResource(id = R.string.settings_language)
|
||||
|
||||
// Compute display name based on current app locale (similar to the reference implementation)
|
||||
val currentLanguageDisplay = remember(currentAppLocale) {
|
||||
val locale = currentAppLocale
|
||||
if (locale != null) {
|
||||
locale.getDisplayName(locale)
|
||||
} else {
|
||||
context.getString(R.string.system_default)
|
||||
}
|
||||
}
|
||||
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.Translate, language) },
|
||||
headlineContent = { Text(
|
||||
text = language,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
) },
|
||||
supportingContent = { Text(currentLanguageDisplay) },
|
||||
modifier = Modifier.clickable {
|
||||
languageDialog.show()
|
||||
}
|
||||
)
|
||||
|
||||
var useBanner by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("use_banner", true)
|
||||
)
|
||||
}
|
||||
if (ksuVersion != null) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.ViewCarousel,
|
||||
title = stringResource(id = R.string.settings_banner),
|
||||
summary = stringResource(id = R.string.settings_banner_summary),
|
||||
checked = useBanner
|
||||
) {
|
||||
prefs.edit().putBoolean("use_banner", it).apply()
|
||||
useBanner = it
|
||||
}
|
||||
}
|
||||
|
||||
var enableAmoled by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("enable_amoled", false)
|
||||
)
|
||||
}
|
||||
var showRestartDialog by remember { mutableStateOf(false) }
|
||||
if (isSystemInDarkTheme()) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Contrast,
|
||||
title = stringResource(id = R.string.settings_amoled_mode),
|
||||
summary = stringResource(id = R.string.settings_amoled_mode_summary),
|
||||
checked = enableAmoled
|
||||
) { checked ->
|
||||
prefs.edit().putBoolean("enable_amoled", checked).apply()
|
||||
enableAmoled = checked
|
||||
showRestartDialog = true
|
||||
}
|
||||
if (showRestartDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRestartDialog = false },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.restart_required),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
text = { Text(stringResource(R.string.restart_app_message)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showRestartDialog = false
|
||||
// Restart the app
|
||||
val packageManager = context.packageManager
|
||||
val intent = packageManager.getLaunchIntentForPackage(context.packageName)
|
||||
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
Runtime.getRuntime().exit(0)
|
||||
}) {
|
||||
Text(stringResource(R.string.restart_app))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showRestartDialog = false }) {
|
||||
Text(stringResource(R.string.later))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
onBack: () -> Unit = {},
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
TopAppBar(
|
||||
title = { Text(
|
||||
text = stringResource(R.string.customization),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) }, navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
||||
},
|
||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun CustomizationPreview() {
|
||||
CustomizationScreen(EmptyDestinationsNavigator)
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.rifsxd.ksunext.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
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.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.*
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.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.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import com.rifsxd.ksunext.Natives
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.R
|
||||
import com.rifsxd.ksunext.ui.component.SwitchItem
|
||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||
import com.rifsxd.ksunext.ui.util.*
|
||||
|
||||
/**
|
||||
* @author rifsxd
|
||||
* @date 2025/6/15.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
fun DeveloperScreen(navigator: DestinationsNavigator) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
onBack = { navigator.popBackStack() },
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackBarHost) },
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
||||
) { paddingValues ->
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
// --- Developer Options Switch ---
|
||||
var developerOptionsEnabled by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("enable_developer_options", false)
|
||||
)
|
||||
}
|
||||
if (ksuVersion != null) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.DeveloperMode,
|
||||
title = stringResource(id = R.string.enable_developer_options),
|
||||
summary = stringResource(id = R.string.enable_developer_options_summary),
|
||||
checked = developerOptionsEnabled
|
||||
) {
|
||||
prefs.edit().putBoolean("enable_developer_options", it).apply()
|
||||
developerOptionsEnabled = it
|
||||
}
|
||||
}
|
||||
|
||||
var enableWebDebugging by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("enable_web_debugging", false)
|
||||
)
|
||||
}
|
||||
if (ksuVersion != null) {
|
||||
SwitchItem(
|
||||
enabled = developerOptionsEnabled,
|
||||
icon = Icons.Filled.Web,
|
||||
title = stringResource(id = R.string.enable_web_debugging),
|
||||
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
||||
checked = enableWebDebugging
|
||||
) {
|
||||
prefs.edit().putBoolean("enable_web_debugging", it).apply()
|
||||
enableWebDebugging = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
onBack: () -> Unit = {},
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
TopAppBar(
|
||||
title = { Text(
|
||||
text = stringResource(R.string.developer),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) }, navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
||||
},
|
||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun DeveloperPreview() {
|
||||
DeveloperScreen(EmptyDestinationsNavigator)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.rifsxd.ksunext.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -27,15 +28,19 @@ import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -67,6 +72,19 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
||||
var actionResult: Boolean
|
||||
var isActionRunning by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
val context = LocalContext.current
|
||||
// Read developer options from SharedPreferences
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||
|
||||
val view = LocalView.current
|
||||
DisposableEffect(isActionRunning) {
|
||||
view.keepScreenOn = isActionRunning
|
||||
onDispose {
|
||||
view.keepScreenOn = false
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = isActionRunning) {
|
||||
// Disable back button if action is running
|
||||
}
|
||||
@@ -148,7 +166,7 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = text,
|
||||
text = if (developerOptionsEnabled) logContent.toString() else text,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||
@@ -161,7 +179,11 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
||||
@Composable
|
||||
private fun TopBar(isActionRunning: Boolean, onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.action)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.action),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.rifsxd.ksunext.ui.screen
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.os.Parcelable
|
||||
@@ -33,17 +35,21 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -59,7 +65,12 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import com.rifsxd.ksunext.R
|
||||
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
||||
import com.rifsxd.ksunext.ui.component.ConfirmResult
|
||||
import com.rifsxd.ksunext.ui.component.KeyEventBlocker
|
||||
import com.rifsxd.ksunext.ui.theme.ORANGE
|
||||
import com.rifsxd.ksunext.ui.theme.GREEN
|
||||
import com.rifsxd.ksunext.ui.theme.RED
|
||||
import com.rifsxd.ksunext.ui.util.FlashResult
|
||||
import com.rifsxd.ksunext.ui.util.LkmSelection
|
||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||
@@ -79,6 +90,12 @@ enum class FlashingStatus {
|
||||
FAILED
|
||||
}
|
||||
|
||||
fun Context.findActivity(): Activity? = when (this) {
|
||||
is Activity -> this
|
||||
is android.content.ContextWrapper -> baseContext.findActivity()
|
||||
else -> null
|
||||
}
|
||||
|
||||
// Lets you flash modules sequentially when mutiple zipUris are selected
|
||||
fun flashModulesSequentially(
|
||||
uris: List<Uri>,
|
||||
@@ -102,7 +119,11 @@ fun flashModulesSequentially(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Destination<RootGraph>
|
||||
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
fun FlashScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
flashIt: FlashIt,
|
||||
finishIntent: Boolean = false
|
||||
) {
|
||||
|
||||
var text by rememberSaveable { mutableStateOf("") }
|
||||
var tempText: String
|
||||
@@ -117,16 +138,68 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
mutableStateOf(FlashingStatus.FLASHING)
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||
|
||||
val activity = context.findActivity()
|
||||
|
||||
val view = LocalView.current
|
||||
DisposableEffect(flashing) {
|
||||
view.keepScreenOn = flashing == FlashingStatus.FLASHING
|
||||
onDispose {
|
||||
view.keepScreenOn = false
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = flashing == FlashingStatus.FLASHING) {
|
||||
// Disable back button if flashing is running
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (text.isNotEmpty()) {
|
||||
return@LaunchedEffect
|
||||
BackHandler(enabled = flashing != FlashingStatus.FLASHING) {
|
||||
navigator.popBackStack()
|
||||
if (finishIntent) activity?.finish()
|
||||
}
|
||||
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
var confirmed by rememberSaveable { mutableStateOf(flashIt !is FlashIt.FlashModules) }
|
||||
var pendingFlashIt by rememberSaveable { mutableStateOf<FlashIt?>(null) }
|
||||
var hasFlashed by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(flashIt) {
|
||||
if (flashIt is FlashIt.FlashModules && !confirmed) {
|
||||
val uris = flashIt.uris
|
||||
val moduleNames =
|
||||
uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }
|
||||
.joinToString("")
|
||||
val confirmContent =
|
||||
context.getString(R.string.module_install_prompt_with_name, moduleNames)
|
||||
val confirmTitle = context.getString(R.string.module)
|
||||
val result = confirmDialog.awaitConfirm(
|
||||
title = confirmTitle,
|
||||
content = confirmContent,
|
||||
markdown = true
|
||||
)
|
||||
if (result == ConfirmResult.Confirmed) {
|
||||
confirmed = true
|
||||
pendingFlashIt = flashIt
|
||||
} else {
|
||||
// User cancelled, go back
|
||||
navigator.popBackStack()
|
||||
if (finishIntent) activity?.finish()
|
||||
}
|
||||
} else {
|
||||
confirmed = true
|
||||
pendingFlashIt = flashIt
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(confirmed, pendingFlashIt) {
|
||||
if (!confirmed || pendingFlashIt == null || text.isNotEmpty() || hasFlashed) return@LaunchedEffect
|
||||
hasFlashed = true
|
||||
withContext(Dispatchers.IO) {
|
||||
flashIt(flashIt, onStdout = {
|
||||
flashIt(pendingFlashIt!!, onStdout = {
|
||||
tempText = "$it\n"
|
||||
if (tempText.startsWith("[H[J")) { // clear command
|
||||
text = tempText.substring(6)
|
||||
@@ -155,6 +228,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
flashing,
|
||||
onBack = dropUnlessResumed {
|
||||
navigator.popBackStack()
|
||||
if (finishIntent) activity?.finish()
|
||||
},
|
||||
onSave = {
|
||||
scope.launch {
|
||||
@@ -172,8 +246,8 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (showFloatAction) {
|
||||
// Reboot button (bottom left)
|
||||
if (flashIt is FlashIt.FlashModules && (flashing == FlashingStatus.SUCCESS)) {
|
||||
// Reboot button for modules flashing
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
@@ -186,6 +260,58 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
text = { Text(text = stringResource(R.string.reboot)) }
|
||||
)
|
||||
}
|
||||
|
||||
if (flashIt is FlashIt.FlashModules && (flashing == FlashingStatus.FAILED)) {
|
||||
// Close button for modules flashing
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(text = stringResource(R.string.close)) },
|
||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
||||
onClick = {
|
||||
navigator.popBackStack()
|
||||
if (finishIntent) activity?.finish()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (flashIt is FlashIt.FlashBoot && (flashing == FlashingStatus.SUCCESS || flashing == FlashingStatus.FAILED)) {
|
||||
val isLocalPatch = flashIt.boot != null && !flashIt.ota
|
||||
val isDirectOrOta = flashIt.boot == null || flashIt.ota
|
||||
|
||||
if (flashing == FlashingStatus.FAILED) {
|
||||
// Always show close on failure
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(text = stringResource(R.string.close)) },
|
||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
||||
onClick = {
|
||||
navigator.popBackStack()
|
||||
}
|
||||
)
|
||||
} else if (flashing == FlashingStatus.SUCCESS) {
|
||||
if (isLocalPatch) {
|
||||
// Local patching: show only Close
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(text = stringResource(R.string.close)) },
|
||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
||||
onClick = {
|
||||
navigator.popBackStack()
|
||||
}
|
||||
)
|
||||
} else if (isDirectOrOta) {
|
||||
// Direct install or OTA inactive slot: show only Reboot
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
reboot()
|
||||
}
|
||||
}
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.reboot)) },
|
||||
text = { Text(text = stringResource(R.string.reboot)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets.safeDrawing,
|
||||
snackbarHost = { SnackbarHost(hostState = snackBarHost) }
|
||||
@@ -205,7 +331,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = text,
|
||||
text = if (developerOptionsEnabled) logContent.toString() else text,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||
@@ -214,6 +340,19 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
}
|
||||
}
|
||||
|
||||
fun Uri.getFileName(context: Context): String {
|
||||
val contentResolver = context.contentResolver
|
||||
val cursor = contentResolver.query(this, null, null, null, null)
|
||||
return cursor?.use {
|
||||
val nameIndex = it.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
|
||||
if (it.moveToFirst() && nameIndex != -1) {
|
||||
it.getString(nameIndex)
|
||||
} else {
|
||||
this.lastPathSegment ?: "unknown.zip"
|
||||
}
|
||||
} ?: (this.lastPathSegment ?: "unknown.zip")
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
sealed class FlashIt : Parcelable {
|
||||
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :
|
||||
@@ -267,7 +406,14 @@ private fun TopBar(
|
||||
FlashingStatus.SUCCESS -> R.string.flash_success
|
||||
FlashingStatus.FAILED -> R.string.flash_failed
|
||||
}
|
||||
)
|
||||
),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
color = when (status) {
|
||||
FlashingStatus.FLASHING -> ORANGE
|
||||
FlashingStatus.SUCCESS -> GREEN
|
||||
FlashingStatus.FAILED -> RED
|
||||
}
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
|
||||
@@ -6,9 +6,15 @@ import android.os.PowerManager
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.system.Os
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -28,10 +34,17 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.toUpperCase
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||
import com.dergoogler.mmrl.ui.component.LabelItemDefaults
|
||||
import com.dergoogler.mmrl.ui.component.text.TextRow
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
||||
@@ -55,6 +68,10 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
val context = LocalContext.current
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
@@ -83,6 +100,19 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
StatusCard(kernelVersion, ksuVersion, lkmMode) {
|
||||
navigator.navigate(InstallScreenDestination)
|
||||
}
|
||||
|
||||
if (ksuVersion != null && rootAvailable()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min),
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp)
|
||||
) {
|
||||
Box(modifier = Modifier.weight(1f)) { SuperuserCard() }
|
||||
Box(modifier = Modifier.weight(1f)) { ModuleCard() }
|
||||
}
|
||||
}
|
||||
|
||||
if (isManager && Natives.requireNewKernel()) {
|
||||
WarningCard(
|
||||
stringResource(id = R.string.require_kernel_version).format(
|
||||
@@ -97,12 +127,12 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
val checkUpdate =
|
||||
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("check_update", true)
|
||||
.getBoolean("check_update", false)
|
||||
if (checkUpdate) {
|
||||
UpdateCard()
|
||||
}
|
||||
//NextCard()
|
||||
InfoCard()
|
||||
InfoCard(autoExpand = developerOptionsEnabled)
|
||||
IssueReportCard()
|
||||
//EXperimentalCard()
|
||||
Spacer(Modifier)
|
||||
@@ -110,6 +140,78 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SuperuserCard() {
|
||||
val count = getSuperuserCount()
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (count <= 1) {
|
||||
stringResource(R.string.home_superuser_count_singular)
|
||||
} else {
|
||||
stringResource(R.string.home_superuser_count_plural)
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
Text(
|
||||
text = count.toString(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ModuleCard() {
|
||||
val count = getModuleCount()
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (count <= 1) {
|
||||
stringResource(R.string.home_module_count_singular)
|
||||
} else {
|
||||
stringResource(R.string.home_module_count_plural)
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
Text(
|
||||
text = count.toString(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpdateCard() {
|
||||
val context = LocalContext.current
|
||||
@@ -162,6 +264,19 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getSeasonalIcon(): ImageVector {
|
||||
val month = Calendar.getInstance().get(Calendar.MONTH) // 0-11 for January-December
|
||||
return when (month) {
|
||||
Calendar.DECEMBER, Calendar.JANUARY, Calendar.FEBRUARY -> Icons.Filled.AcUnit // Winter
|
||||
Calendar.MARCH, Calendar.APRIL, Calendar.MAY -> Icons.Filled.Spa // Spring
|
||||
Calendar.JUNE, Calendar.JULY, Calendar.AUGUST -> Icons.Filled.WbSunny // Summer
|
||||
Calendar.SEPTEMBER, Calendar.OCTOBER, Calendar.NOVEMBER -> Icons.Filled.Forest // Fall
|
||||
else -> Icons.Filled.Whatshot // Fallback icon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
@@ -170,15 +285,55 @@ private fun TopBar(
|
||||
onInstallClick: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
var isSpinning by remember { mutableStateOf(false) }
|
||||
val rotation by animateFloatAsState(
|
||||
targetValue = if (isSpinning) 360f else 0f,
|
||||
animationSpec = tween(durationMillis = 800),
|
||||
finishedListener = {
|
||||
isSpinning = false
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
isSpinning = true
|
||||
}
|
||||
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.app_name)) },
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
if (!isSpinning) isSpinning = true
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = getSeasonalIcon(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.graphicsLayer {
|
||||
rotationZ = rotation
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (kernelVersion.isGKI()) {
|
||||
IconButton(onClick = onInstallClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Archive,
|
||||
contentDescription = stringResource(id = R.string.install)
|
||||
)
|
||||
if (ksuVersion != null) {
|
||||
if (kernelVersion.isGKI()) {
|
||||
IconButton(onClick = onInstallClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Archive,
|
||||
contentDescription = stringResource(id = R.string.install)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +343,7 @@ private fun TopBar(
|
||||
showDropdown = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Refresh,
|
||||
imageVector = Icons.Filled.PowerSettingsNew,
|
||||
contentDescription = stringResource(id = R.string.reboot)
|
||||
)
|
||||
|
||||
@@ -197,7 +352,8 @@ private fun TopBar(
|
||||
}) {
|
||||
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")
|
||||
@@ -215,93 +371,131 @@ private fun TopBar(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getSeasonalIcon(): ImageVector {
|
||||
val month = Calendar.getInstance().get(Calendar.MONTH) // 0-11 for January-December
|
||||
return when (month) {
|
||||
Calendar.DECEMBER, Calendar.JANUARY, Calendar.FEBRUARY -> Icons.Filled.AcUnit // Winter
|
||||
Calendar.MARCH, Calendar.APRIL, Calendar.MAY -> Icons.Filled.Spa // Spring
|
||||
Calendar.JUNE, Calendar.JULY, Calendar.AUGUST -> Icons.Filled.WbSunny // Summer
|
||||
Calendar.SEPTEMBER, Calendar.OCTOBER, Calendar.NOVEMBER -> Icons.Filled.Forest // Fall
|
||||
else -> Icons.Filled.Whatshot // Fallback icon
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusCard(
|
||||
kernelVersion: KernelVersion,
|
||||
ksuVersion: Int?,
|
||||
lkmMode: Boolean?,
|
||||
moduleUpdateCount: Int = 0,
|
||||
onClickInstall: () -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var tapCount by remember { mutableStateOf(0) }
|
||||
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
||||
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
|
||||
if (ksuVersion != null) MaterialTheme.colorScheme.primaryContainer
|
||||
else MaterialTheme.colorScheme.errorContainer
|
||||
})
|
||||
) {
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (kernelVersion.isGKI()) {
|
||||
onClickInstall()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
tapCount++
|
||||
if (tapCount == 5) {
|
||||
Toast.makeText(context, "What are you doing? 🤔", Toast.LENGTH_SHORT).show()
|
||||
} else if (tapCount == 10) {
|
||||
Toast.makeText(context, "Never gonna give you up! 💜", Toast.LENGTH_SHORT).show()
|
||||
val url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
val intent = android.content.Intent(android.content.Intent.ACTION_VIEW, android.net.Uri.parse(url))
|
||||
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (ksuVersion != null) {
|
||||
context.startActivity(intent)
|
||||
} else if (kernelVersion.isGKI()) {
|
||||
onClickInstall()
|
||||
} else {
|
||||
Toast.makeText(context, "Something weird happened... 🤔", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else if (ksuVersion == null && kernelVersion.isGKI()) {
|
||||
onClickInstall()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
when {
|
||||
ksuVersion != null -> {
|
||||
val safeMode = when {
|
||||
Natives.isSafeMode -> " [${stringResource(id = R.string.safe_mode)}]"
|
||||
else -> ""
|
||||
val workingMode = when {
|
||||
lkmMode == true -> "LKM"
|
||||
lkmMode == false || kernelVersion.isGKI() -> "GKI2"
|
||||
lkmMode == null && kernelVersion.isULegacy() -> "U-LEGACY"
|
||||
lkmMode == null && kernelVersion.isLegacy() -> "LEGACY"
|
||||
lkmMode == null && kernelVersion.isGKI1() -> "GKI1"
|
||||
else -> "NON-STANDARD"
|
||||
}
|
||||
|
||||
val workingMode = when (lkmMode) {
|
||||
null -> " <LTS>"
|
||||
true -> " <LKM>"
|
||||
else -> " <GKI>"
|
||||
}
|
||||
|
||||
val workingText =
|
||||
"${stringResource(id = R.string.home_working)}$workingMode$safeMode"
|
||||
|
||||
Icon(
|
||||
getSeasonalIcon(), // Use dynamic seasonal icon
|
||||
imageVector = Icons.Filled.CheckCircle,
|
||||
contentDescription = stringResource(R.string.home_working)
|
||||
)
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = workingText,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.home_superuser_count, getSuperuserCount()
|
||||
), style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_module_count, getModuleCount()),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
val suSFS = getSuSFS()
|
||||
if (suSFS == "Supported") {
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 20.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
val labelStyle = LabelItemDefaults.style
|
||||
TextRow(
|
||||
trailingContent = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
LabelItem(
|
||||
icon = if (Natives.isSafeMode) {
|
||||
{
|
||||
Icon(
|
||||
tint = labelStyle.contentColor,
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = workingMode,
|
||||
style = labelStyle.textStyle.copy(color = labelStyle.contentColor),
|
||||
)
|
||||
}
|
||||
)
|
||||
if (isSuCompatDisabled()) {
|
||||
LabelItem(
|
||||
icon = {
|
||||
Icon(
|
||||
tint = labelStyle.contentColor,
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.sucompat_disabled),
|
||||
style = labelStyle.textStyle.copy(
|
||||
color = labelStyle.contentColor,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = "SuSFS: " + stringResource(R.string.susfs_supported),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
text = stringResource(id = R.string.home_working),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
kernelVersion.isGKI() -> {
|
||||
Icon(Icons.Filled.AutoFixHigh, stringResource(R.string.home_not_installed))
|
||||
Icon(Icons.Filled.NewReleases, stringResource(R.string.home_not_installed))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.home_not_installed),
|
||||
@@ -316,7 +510,7 @@ private fun StatusCard(
|
||||
}
|
||||
|
||||
else -> {
|
||||
Icon(Icons.Filled.Dangerous, stringResource(R.string.home_failure))
|
||||
Icon(Icons.Filled.Cancel, stringResource(R.string.home_failure))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.home_failure),
|
||||
@@ -357,31 +551,30 @@ fun WarningCard(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoCard() {
|
||||
private fun InfoCard(autoExpand: Boolean = false) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
var useOverlayFs by rememberSaveable {
|
||||
mutableStateOf(prefs.getBoolean("use_overlay_fs", false))
|
||||
}
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
useOverlayFs = prefs.getBoolean("use_overlay_fs", false)
|
||||
}
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||
|
||||
LaunchedEffect(autoExpand) {
|
||||
if (autoExpand) {
|
||||
expanded = true
|
||||
}
|
||||
}
|
||||
|
||||
ElevatedCard {
|
||||
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 = 24.dp)
|
||||
) {
|
||||
val contents = StringBuilder()
|
||||
val uname = Os.uname()
|
||||
|
||||
@Composable
|
||||
fun InfoCardItem(label: String, content: String, icon: Any? = null) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
@@ -402,69 +595,137 @@ private fun InfoCard() {
|
||||
Column {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Text(
|
||||
text = content,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_kernel),
|
||||
content = uname.release,
|
||||
icon = painterResource(R.drawable.ic_linux),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_android),
|
||||
content = "${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})",
|
||||
icon = Icons.Filled.Android,
|
||||
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val managerVersion = getManagerVersion(context)
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_manager_version),
|
||||
content = "${managerVersion.first} (${managerVersion.second})",
|
||||
icon = painterResource(R.drawable.ic_ksu_next),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_selinux_status),
|
||||
content = getSELinuxStatus(),
|
||||
icon = Icons.Filled.Security,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_mount_system),
|
||||
content = currentMountSystem().ifEmpty { stringResource(R.string.unavailable) },
|
||||
icon = Icons.Filled.SettingsSuggest,
|
||||
)
|
||||
|
||||
val suSFS = getSuSFS()
|
||||
if (suSFS == "Supported") {
|
||||
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
|
||||
val susSUMode = if (isSUS_SU) {
|
||||
val mode = susfsSUS_SU_Mode()
|
||||
val modeString = if (mode == "2") stringResource(R.string.enabled) else stringResource(R.string.disabled)
|
||||
"| SuS SU: $modeString"
|
||||
} else ""
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Column {
|
||||
val managerVersion = getManagerVersion(context)
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_susfs_version),
|
||||
content = "${getSuSFSVersion()} (${getSuSFSVariant()}) $susSUMode",
|
||||
icon = painterResource(R.drawable.ic_sus),
|
||||
label = stringResource(R.string.home_manager_version),
|
||||
content = if (
|
||||
developerOptionsEnabled &&
|
||||
Natives.version >= Natives.MINIMAL_SUPPORTED_MANAGER_UID
|
||||
) {
|
||||
"${managerVersion.first} (${managerVersion.second}) | UID: ${Natives.getManagerUid()}"
|
||||
} else {
|
||||
"${managerVersion.first} (${managerVersion.second})"
|
||||
},
|
||||
icon = painterResource(R.drawable.ic_ksu_next),
|
||||
)
|
||||
|
||||
if (ksuVersion != null &&
|
||||
Natives.version >= Natives.MINIMAL_SUPPORTED_HOOK_MODE) {
|
||||
|
||||
val hookMode =
|
||||
Natives.getHookMode()
|
||||
.takeUnless { it.isNullOrBlank() }
|
||||
?: stringResource(R.string.unavailable)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.hook_mode),
|
||||
content = hookMode,
|
||||
icon = Icons.Filled.Phishing,
|
||||
)
|
||||
}
|
||||
|
||||
if (ksuVersion != null) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_mount_system),
|
||||
content = currentMountSystem().ifEmpty { stringResource(R.string.unavailable) },
|
||||
icon = Icons.Filled.SettingsSuggest,
|
||||
)
|
||||
|
||||
|
||||
val suSFS = getSuSFS()
|
||||
if (suSFS == "Supported") {
|
||||
val isSUS_SU = hasSuSFs_SUS_SU() == "Supported"
|
||||
val susSUMode = if (isSUS_SU) {
|
||||
val mode = susfsSUS_SU_Mode()
|
||||
val modeString =
|
||||
if (mode == "2") stringResource(R.string.enabled) else stringResource(R.string.disabled)
|
||||
"| SuS SU: $modeString"
|
||||
} else ""
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_susfs_version),
|
||||
content = "${stringResource(R.string.susfs_supported)} | ${getSuSFSVersion()} (${getSuSFSVariant()}) $susSUMode",
|
||||
icon = painterResource(R.drawable.ic_sus),
|
||||
)
|
||||
}
|
||||
|
||||
if (Natives.isZygiskEnabled()) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.zygisk_status),
|
||||
content = stringResource(R.string.enabled),
|
||||
icon = Icons.Filled.Vaccines
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!expanded) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { expanded = true },
|
||||
modifier = Modifier.size(36.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.KeyboardArrowDown,
|
||||
contentDescription = "Show more"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
val uname = Os.uname()
|
||||
Column {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_kernel),
|
||||
content = "${uname.release} (${uname.machine})",
|
||||
icon = painterResource(R.drawable.ic_linux),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_android),
|
||||
content = "${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})",
|
||||
icon = Icons.Filled.Android,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_abi),
|
||||
content = Build.SUPPORTED_ABIS.joinToString(", "),
|
||||
icon = Icons.Filled.Memory,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_selinux_status),
|
||||
content = getSELinuxStatus(),
|
||||
icon = Icons.Filled.Security,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,12 +738,13 @@ fun NextCard() {
|
||||
|
||||
ElevatedCard {
|
||||
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
uriHandler.openUri(url)
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
uriHandler.openUri(url)
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.home_next_kernelsu),
|
||||
@@ -506,13 +768,15 @@ fun EXperimentalCard() {
|
||||
|
||||
ElevatedCard {
|
||||
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
/*.clickable {
|
||||
uriHandler.openUri(url)
|
||||
}
|
||||
*/
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
/*.clickable {
|
||||
uriHandler.openUri(url)
|
||||
}
|
||||
*/
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.home_experimental_kernelsu),
|
||||
@@ -559,17 +823,18 @@ fun IssueReportCard() {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = stringResource(R.string.issue_report_title),
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.issue_report_body),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.issue_report_body_2),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.FileUpload
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -30,6 +31,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
@@ -39,7 +41,9 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -77,6 +81,36 @@ import com.rifsxd.ksunext.ui.util.rootAvailable
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
var showLkmWarning by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
if (showLkmWarning) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
showLkmWarning = false
|
||||
navigator.popBackStack()
|
||||
},
|
||||
title = { Text(
|
||||
text = stringResource(R.string.warning),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
text = { Text(stringResource(R.string.lkm_warning_message)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showLkmWarning = false }) {
|
||||
Text(stringResource(R.string.proceed))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = {
|
||||
showLkmWarning = false
|
||||
navigator.popBackStack()
|
||||
}) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var installMethod by remember {
|
||||
mutableStateOf<InstallMethod?>(null)
|
||||
}
|
||||
@@ -205,7 +239,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||
val rootAvailable = rootAvailable()
|
||||
val isAbDevice = isAbDevice()
|
||||
val selectFileTip = stringResource(
|
||||
id = R.string.select_file_tip, if (isInitBoot()) "init_boot" else "boot"
|
||||
id = R.string.select_file_tip, if (isInitBoot()) "init_boot/vendor_boot" else "boot"
|
||||
)
|
||||
val radioOptions =
|
||||
mutableListOf<InstallMethod>(InstallMethod.SelectFile(summary = selectFileTip))
|
||||
@@ -341,7 +375,11 @@ private fun TopBar(
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.install)) }, navigationIcon = {
|
||||
title = { Text(
|
||||
text = stringResource(R.string.install),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) }, navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -34,6 +35,7 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -42,6 +44,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
@@ -64,6 +67,8 @@ import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.BackupRestoreScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.CustomizationScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.DeveloperScreenDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -83,6 +88,8 @@ import com.rifsxd.ksunext.ui.component.rememberLoadingDialog
|
||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||
import com.rifsxd.ksunext.ui.util.getBugreportFile
|
||||
import com.rifsxd.ksunext.ui.util.*
|
||||
import com.rifsxd.ksunext.ui.util.isGlobalNamespaceEnabled
|
||||
import com.rifsxd.ksunext.ui.util.setGlobalNamespaceEnabled
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@@ -96,6 +103,8 @@ import java.time.format.DateTimeFormatter
|
||||
fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
var isGlobalNamespaceEnabled by rememberSaveable { mutableStateOf(false) }
|
||||
isGlobalNamespaceEnabled = isGlobalNamespaceEnabled()
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
@@ -145,7 +154,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
if (ksuVersion != null) {
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
||||
headlineContent = { Text(profileTemplate) },
|
||||
headlineContent = { Text(
|
||||
text = profileTemplate,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||
@@ -162,7 +175,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||
checked = umountChecked
|
||||
|
||||
|
||||
) {
|
||||
if (Natives.setDefaultUmountModules(it)) {
|
||||
umountChecked = it
|
||||
@@ -187,14 +200,31 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Engineering,
|
||||
title = stringResource(id = R.string.settings_global_namespace_mode),
|
||||
summary = stringResource(id = R.string.settings_global_namespace_mode_summary),
|
||||
checked = isGlobalNamespaceEnabled,
|
||||
onCheckedChange = {
|
||||
setGlobalNamespaceEnabled(
|
||||
if (isGlobalNamespaceEnabled) {
|
||||
"0"
|
||||
} else {
|
||||
"1"
|
||||
}
|
||||
)
|
||||
isGlobalNamespaceEnabled = it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
val suSFS = getSuSFS()
|
||||
val isSUS_SU = getSuSFSFeatures()
|
||||
val isSUS_SU = hasSuSFs_SUS_SU() == "Supported"
|
||||
if (suSFS == "Supported") {
|
||||
if (isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") {
|
||||
if (isSUS_SU) {
|
||||
var isEnabled by rememberSaveable {
|
||||
mutableStateOf(susfsSUS_SU_Mode() == "2")
|
||||
}
|
||||
@@ -221,14 +251,18 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
|
||||
var useOverlayFs by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("use_overlay_fs", false)
|
||||
)
|
||||
mutableStateOf(readMountSystemFile())
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
useOverlayFs = readMountSystemFile()
|
||||
}
|
||||
|
||||
var showRebootDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (ksuVersion != null) {
|
||||
val isOverlayAvailable = overlayFsAvailable()
|
||||
|
||||
if (ksuVersion != null && isOverlayAvailable) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Build,
|
||||
title = stringResource(id = R.string.use_overlay_fs),
|
||||
@@ -239,8 +273,10 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
useOverlayFs = it
|
||||
if (useOverlayFs) {
|
||||
moduleBackup()
|
||||
updateMountSystemFile(true)
|
||||
} else {
|
||||
moduleMigration()
|
||||
updateMountSystemFile(false)
|
||||
}
|
||||
if (isManager) install()
|
||||
showRebootDialog = true
|
||||
@@ -250,7 +286,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
if (showRebootDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRebootDialog = false },
|
||||
title = { Text(stringResource(R.string.reboot_required)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.reboot_required),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
text = { Text(stringResource(R.string.reboot_message)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
@@ -271,7 +311,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
var checkUpdate by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("check_update", true)
|
||||
prefs.getBoolean("check_update", false)
|
||||
)
|
||||
}
|
||||
SwitchItem(
|
||||
@@ -284,58 +324,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
checkUpdate = it
|
||||
}
|
||||
|
||||
var enableWebDebugging by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("enable_web_debugging", false)
|
||||
)
|
||||
}
|
||||
|
||||
if (ksuVersion != null) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Web,
|
||||
title = stringResource(id = R.string.enable_web_debugging),
|
||||
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
||||
checked = enableWebDebugging
|
||||
) {
|
||||
prefs.edit().putBoolean("enable_web_debugging", it).apply()
|
||||
enableWebDebugging = it
|
||||
}
|
||||
}
|
||||
|
||||
var developerOptionsEnabled by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("enable_developer_options", false)
|
||||
)
|
||||
}
|
||||
if (ksuVersion != null) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.DeveloperMode,
|
||||
title = stringResource(id = R.string.enable_developer_options),
|
||||
summary = stringResource(id = R.string.enable_developer_options_summary),
|
||||
checked = developerOptionsEnabled
|
||||
) {
|
||||
prefs.edit().putBoolean("enable_developer_options", it).apply()
|
||||
developerOptionsEnabled = it
|
||||
}
|
||||
}
|
||||
|
||||
if (ksuVersion != null) {
|
||||
val backupRestore = stringResource(id = R.string.backup_restore)
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Backup,
|
||||
backupRestore
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(backupRestore) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(BackupRestoreScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (useOverlayFs) {
|
||||
if (isOverlayAvailable && useOverlayFs) {
|
||||
val shrink = stringResource(id = R.string.shrink_sparse_image)
|
||||
val shrinkMessage = stringResource(id = R.string.shrink_sparse_image_message)
|
||||
ListItem(
|
||||
@@ -345,7 +334,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
shrink
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(shrink) },
|
||||
headlineContent = { Text(
|
||||
text = shrink,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
val result = shrinkDialog.awaitConfirm(title = shrink, content = shrinkMessage)
|
||||
@@ -359,6 +352,63 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
}
|
||||
|
||||
val customization = stringResource(id = R.string.customization)
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Palette,
|
||||
customization
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(
|
||||
text = customization,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(CustomizationScreenDestination)
|
||||
}
|
||||
)
|
||||
|
||||
if (ksuVersion != null) {
|
||||
val backupRestore = stringResource(id = R.string.backup_restore)
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Backup,
|
||||
backupRestore
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(
|
||||
text = backupRestore,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(BackupRestoreScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val developer = stringResource(id = R.string.developer)
|
||||
if (ksuVersion != null) {
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.DeveloperBoard,
|
||||
developer
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(
|
||||
text = developer,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(DeveloperScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
||||
if (lkmMode) {
|
||||
@@ -376,7 +426,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
stringResource(id = R.string.export_log)
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(stringResource(id = R.string.export_log)) },
|
||||
headlineContent = { Text(
|
||||
text = stringResource(id = R.string.export_log),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
showBottomsheet = true
|
||||
}
|
||||
@@ -484,7 +538,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
about
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(about) },
|
||||
headlineContent = { Text(
|
||||
text = about,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
aboutDialog.show()
|
||||
}
|
||||
@@ -496,7 +554,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
@Composable
|
||||
fun UninstallItem(
|
||||
navigator: DestinationsNavigator,
|
||||
withLoading: suspend (suspend () -> Unit) -> Unit
|
||||
withLoading: suspend (suspend () -> Unit) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -534,7 +592,11 @@ fun UninstallItem(
|
||||
uninstall
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(uninstall) },
|
||||
headlineContent = { Text(
|
||||
text = uninstall,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
uninstallDialog.show()
|
||||
}
|
||||
@@ -598,10 +660,14 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.settings)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.settings),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) },
|
||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -21,9 +22,12 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||
import com.dergoogler.mmrl.ui.component.LabelItemDefaults
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
|
||||
@@ -43,30 +47,21 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LaunchedEffect(key1 = navigator) {
|
||||
LaunchedEffect(navigator) {
|
||||
viewModel.search = ""
|
||||
if (viewModel.appList.isEmpty()) {
|
||||
viewModel.fetchAppList()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(viewModel.search) {
|
||||
if (viewModel.search.isEmpty()) {
|
||||
listState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.refreshOnReturn) {
|
||||
viewModel.fetchAppList()
|
||||
viewModel.refreshOnReturn = false
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SearchAppBar(
|
||||
title = { Text(stringResource(R.string.superuser)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.superuser),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) },
|
||||
searchText = viewModel.search,
|
||||
onSearchTextChange = { viewModel.search = it },
|
||||
onClearClick = { viewModel.search = "" },
|
||||
@@ -101,7 +96,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
}, onClick = {
|
||||
viewModel.showSystemApps = !viewModel.showSystemApps
|
||||
viewModel.updateShowSystemApps(!viewModel.showSystemApps)
|
||||
showDropdown = false
|
||||
})
|
||||
}
|
||||
@@ -127,7 +122,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
) {
|
||||
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
||||
AppItem(app) {
|
||||
viewModel.refreshOnReturn = true
|
||||
navigator.navigate(AppProfileScreenDestination(app))
|
||||
}
|
||||
}
|
||||
@@ -144,20 +138,54 @@ private fun AppItem(
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable(onClick = onClickListener),
|
||||
headlineContent = { Text(app.label) },
|
||||
headlineContent = { Text(
|
||||
text = app.label,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
) },
|
||||
supportingContent = {
|
||||
Column {
|
||||
Text(app.packageName)
|
||||
FlowRow {
|
||||
Text(
|
||||
text = app.packageName,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
if (app.allowSu) {
|
||||
LabelText(label = "ROOT")
|
||||
LabelItem(
|
||||
text = "ROOT",
|
||||
)
|
||||
} else {
|
||||
if (Natives.uidShouldUmount(app.uid)) {
|
||||
LabelText(label = "UMOUNT")
|
||||
LabelItem(
|
||||
text = "UMOUNT",
|
||||
style = LabelItemDefaults.style.copy(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (app.hasCustomProfile) {
|
||||
LabelText(label = "CUSTOM")
|
||||
LabelItem(
|
||||
text = "CUSTOM",
|
||||
style = LabelItemDefaults.style.copy(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
)
|
||||
)
|
||||
} else if (!app.allowSu && !Natives.uidShouldUmount(app.uid)) {
|
||||
LabelItem(
|
||||
text = "DEFAULT",
|
||||
style = LabelItemDefaults.style.copy(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
@@ -42,6 +45,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
@@ -51,6 +55,7 @@ import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
|
||||
@@ -62,6 +67,15 @@ import kotlinx.coroutines.launch
|
||||
import com.rifsxd.ksunext.R
|
||||
import com.rifsxd.ksunext.ui.viewmodel.TemplateViewModel
|
||||
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.core.tween
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/10/20.
|
||||
@@ -91,6 +105,30 @@ fun AppProfileTemplateScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
var showFab by remember { mutableStateOf(true) }
|
||||
|
||||
LaunchedEffect(listState) {
|
||||
var lastIndex = listState.firstVisibleItemIndex
|
||||
var lastOffset = listState.firstVisibleItemScrollOffset
|
||||
|
||||
snapshotFlow { listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
|
||||
.collect { (currIndex, currOffset) ->
|
||||
val isScrollingDown = currIndex > lastIndex ||
|
||||
(currIndex == lastIndex && currOffset > lastOffset + 4)
|
||||
val isScrollingUp = currIndex < lastIndex ||
|
||||
(currIndex == lastIndex && currOffset < lastOffset - 4)
|
||||
|
||||
when {
|
||||
isScrollingDown && showFab -> showFab = false
|
||||
isScrollingUp && !showFab -> showFab = true
|
||||
}
|
||||
|
||||
lastIndex = currIndex
|
||||
lastOffset = currOffset
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
@@ -137,18 +175,30 @@ fun AppProfileTemplateScreen(
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
navigator.navigate(
|
||||
TemplateEditorScreenDestination(
|
||||
TemplateViewModel.TemplateInfo(),
|
||||
false
|
||||
AnimatedVisibility(
|
||||
visible = showFab,
|
||||
enter = scaleIn(
|
||||
animationSpec = tween(200),
|
||||
initialScale = 0.8f
|
||||
) + fadeIn(animationSpec = tween(400)),
|
||||
exit = scaleOut(
|
||||
animationSpec = tween(200),
|
||||
targetScale = 0.8f
|
||||
) + fadeOut(animationSpec = tween(400))
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
navigator.navigate(
|
||||
TemplateEditorScreenDestination(
|
||||
TemplateViewModel.TemplateInfo(),
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Add, null) },
|
||||
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
||||
)
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Add, null) },
|
||||
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
||||
)
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
||||
) { innerPadding ->
|
||||
@@ -160,11 +210,12 @@ fun AppProfileTemplateScreen(
|
||||
}
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
contentPadding = remember {
|
||||
PaddingValues(bottom = 16.dp + 56.dp + 16.dp /* Scaffold Fab Spacing + Fab container height */)
|
||||
PaddingValues(bottom = 16.dp /* Scaffold Fab Spacing + Fab container height */)
|
||||
}
|
||||
) {
|
||||
items(viewModel.templateList, key = { it.id }) { app ->
|
||||
@@ -186,7 +237,11 @@ private fun TemplateItem(
|
||||
.clickable {
|
||||
navigator.navigate(TemplateEditorScreenDestination(template, !template.local))
|
||||
},
|
||||
headlineContent = { Text(template.name) },
|
||||
headlineContent = { Text(
|
||||
text = template.name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
) },
|
||||
supportingContent = {
|
||||
Column {
|
||||
Text(
|
||||
@@ -195,14 +250,19 @@ private fun TemplateItem(
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
)
|
||||
Text(template.description)
|
||||
FlowRow {
|
||||
LabelText(label = "UID: ${template.uid}")
|
||||
LabelText(label = "GID: ${template.gid}")
|
||||
LabelText(label = template.context)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
LabelItem(text = "UID: ${template.uid}")
|
||||
LabelItem(text = "GID: ${template.gid}")
|
||||
LabelItem(text = template.context)
|
||||
if (template.local) {
|
||||
LabelText(label = "local")
|
||||
LabelItem(text = "local")
|
||||
} else {
|
||||
LabelText(label = "remote")
|
||||
LabelItem(text = "remote")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +281,11 @@ private fun TopBar(
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.settings_profile_template))
|
||||
Text(
|
||||
text = stringResource(R.string.settings_profile_template),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
|
||||
@@ -35,6 +35,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -262,7 +263,11 @@ private fun TopBar(
|
||||
TopAppBar(
|
||||
title = {
|
||||
Column {
|
||||
Text(title)
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
)
|
||||
if (summary.isNotBlank()) {
|
||||
Text(
|
||||
text = summary,
|
||||
|
||||
@@ -2,9 +2,19 @@ package com.rifsxd.ksunext.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val YELLOW = Color(0xFFeed502)
|
||||
val YELLOW_LIGHT = Color(0xFFffff52)
|
||||
val SECONDARY_LIGHT = Color(0xffa9817f)
|
||||
val PRIMARY = Color(0xFF8AADF4) // Catppuccin Blue
|
||||
val PRIMARY_LIGHT = Color(0xFFB7BDF8) // Catppuccin Lavender
|
||||
val SECONDARY_LIGHT = Color(0xFFA6DA95) // Catppuccin Green
|
||||
|
||||
val YELLOW_DARK = Color(0xFFb7a400)
|
||||
val SECONDARY_DARK = Color(0xFF4c2b2b)
|
||||
val PRIMARY_DARK = Color(0xFF7DC4E4) // Catppuccin Sky
|
||||
val SECONDARY_DARK = Color(0xFFF5BDE6) // Catppuccin Pink
|
||||
|
||||
val AMOLED_BLACK = Color(0xFF000000) // Pure black for AMOLED
|
||||
|
||||
val DARK_PURPLE = Color(0xFF6E6CB6) // Catppuccin Mauve (dark purple)
|
||||
val DARK_GREY = Color(0xFF363A4F) // Catppuccin Surface (dark grey)
|
||||
|
||||
val GREEN = Color(0xFF4CAF50) // Green
|
||||
val RED = Color(0xFFF44336) // Red
|
||||
val YELLOW = Color(0xFFFFEB3B) // Yellow
|
||||
val ORANGE = Color(0xFFFF9800) // Orange
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.rifsxd.ksunext.ui.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
@@ -8,39 +11,115 @@ import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = YELLOW,
|
||||
secondary = YELLOW_DARK,
|
||||
primary = PRIMARY,
|
||||
secondary = PRIMARY_DARK,
|
||||
tertiary = SECONDARY_DARK
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = YELLOW,
|
||||
secondary = YELLOW_LIGHT,
|
||||
primary = PRIMARY,
|
||||
secondary = PRIMARY_LIGHT,
|
||||
tertiary = SECONDARY_LIGHT
|
||||
)
|
||||
|
||||
fun Color.blend(other: Color, ratio: Float): Color {
|
||||
val inverse = 1f - ratio
|
||||
return Color(
|
||||
red = red * inverse + other.red * ratio,
|
||||
green = green * inverse + other.green * ratio,
|
||||
blue = blue * inverse + other.blue * ratio,
|
||||
alpha = alpha
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun KernelSUTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
amoledMode: Boolean = false,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
amoledMode && darkTheme && dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
val dynamicScheme = dynamicDarkColorScheme(context)
|
||||
dynamicScheme.copy(
|
||||
background = AMOLED_BLACK,
|
||||
surface = AMOLED_BLACK,
|
||||
surfaceVariant = dynamicScheme.surfaceVariant.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainer = dynamicScheme.surfaceContainer.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerLow = dynamicScheme.surfaceContainerLow.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerLowest = dynamicScheme.surfaceContainerLowest.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerHigh = dynamicScheme.surfaceContainerHigh.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerHighest = dynamicScheme.surfaceContainerHighest.blend(AMOLED_BLACK, 0.6f),
|
||||
primaryContainer = dynamicScheme.primaryContainer.blend(AMOLED_BLACK, 0.6f),
|
||||
secondaryContainer = dynamicScheme.secondaryContainer.blend(AMOLED_BLACK, 0.6f),
|
||||
tertiaryContainer = dynamicScheme.tertiaryContainer.blend(AMOLED_BLACK, 0.6f)
|
||||
)
|
||||
}
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
amoledMode && darkTheme -> {
|
||||
DarkColorScheme.copy(
|
||||
background = AMOLED_BLACK,
|
||||
surface = AMOLED_BLACK,
|
||||
surfaceVariant = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainer = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainerLow = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainerLowest = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainerHigh = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainerHighest = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
)
|
||||
}
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
SystemBarStyle(
|
||||
darkMode = darkTheme
|
||||
)
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SystemBarStyle(
|
||||
darkMode: Boolean,
|
||||
statusBarScrim: Color = Color.Transparent,
|
||||
navigationBarScrim: Color = Color.Transparent,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val activity = context as ComponentActivity
|
||||
|
||||
SideEffect {
|
||||
activity.enableEdgeToEdge(
|
||||
statusBarStyle = SystemBarStyle.auto(
|
||||
statusBarScrim.toArgb(),
|
||||
statusBarScrim.toArgb(),
|
||||
) { darkMode },
|
||||
navigationBarStyle = when {
|
||||
darkMode -> SystemBarStyle.dark(
|
||||
navigationBarScrim.toArgb()
|
||||
)
|
||||
|
||||
else -> SystemBarStyle.light(
|
||||
navigationBarScrim.toArgb(),
|
||||
navigationBarScrim.toArgb(),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import java.io.File
|
||||
* @date 2023/1/1.
|
||||
*/
|
||||
private const val TAG = "KsuCli"
|
||||
private const val BUSYBOX = "/data/adb/ksu/bin/busybox"
|
||||
|
||||
private fun ksuDaemonMagicPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud_magic.so"
|
||||
@@ -37,10 +38,16 @@ private fun ksuDaemonOverlayfsPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud_overlayfs.so"
|
||||
}
|
||||
|
||||
fun readMountSystemFile(): Boolean {
|
||||
val shell = getRootShell()
|
||||
val filePath = "/data/adb/ksu/mount_system"
|
||||
val result = ShellUtils.fastCmd(shell, "cat $filePath").trim()
|
||||
return result == "OVERLAYFS"
|
||||
}
|
||||
|
||||
// Get the path based on the user's choice
|
||||
fun getKsuDaemonPath(): String {
|
||||
val prefs = ksuApp.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val useOverlayFs = prefs.getBoolean("use_overlay_fs", false)
|
||||
val useOverlayFs = readMountSystemFile()
|
||||
|
||||
return if (useOverlayFs) {
|
||||
ksuDaemonOverlayfsPath()
|
||||
@@ -49,6 +56,16 @@ fun getKsuDaemonPath(): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMountSystemFile(useOverlayFs: Boolean) {
|
||||
val shell = getRootShell()
|
||||
val filePath = "/data/adb/ksu/mount_system"
|
||||
if (useOverlayFs) {
|
||||
ShellUtils.fastCmd(shell, "echo -n OVERLAYFS > $filePath")
|
||||
} else {
|
||||
ShellUtils.fastCmd(shell, "echo -n MAGIC_MOUNT > $filePath")
|
||||
}
|
||||
}
|
||||
|
||||
data class FlashResult(val code: Int, val err: String, val showReboot: Boolean) {
|
||||
constructor(result: Shell.Result, showReboot: Boolean) : this(result.code, result.err.joinToString("\n"), showReboot)
|
||||
constructor(result: Shell.Result) : this(result, result.isSuccess)
|
||||
@@ -86,20 +103,23 @@ fun Uri.getFileName(context: Context): String? {
|
||||
|
||||
fun createRootShell(globalMnt: Boolean = false): Shell {
|
||||
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
||||
val builder = Shell.Builder.create()
|
||||
val builder = Shell.Builder.create().apply {
|
||||
setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||
}
|
||||
|
||||
return try {
|
||||
if (globalMnt) {
|
||||
builder.build(getKsuDaemonPath(), "debug", "su", "-g")
|
||||
builder.build(ksuDaemonMagicPath(), "debug", "su", "-g")
|
||||
} else {
|
||||
builder.build(getKsuDaemonPath(), "debug", "su")
|
||||
builder.build(ksuDaemonMagicPath(), "debug", "su")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.w(TAG, "ksu failed: ", e)
|
||||
try {
|
||||
if (globalMnt) {
|
||||
builder.build("su")
|
||||
} else {
|
||||
builder.build("su", "-mm")
|
||||
} else {
|
||||
builder.build("su")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "su failed: ", e)
|
||||
@@ -386,6 +406,22 @@ fun hasMagisk(): Boolean {
|
||||
return result.isSuccess
|
||||
}
|
||||
|
||||
fun isGlobalNamespaceEnabled(): Boolean {
|
||||
val shell = getRootShell()
|
||||
val result =
|
||||
ShellUtils.fastCmd(shell, "nsenter --mount=/proc/1/ns/mnt cat ${Natives.GLOBAL_NAMESPACE_FILE}")
|
||||
Log.i(TAG, "is global namespace enabled: $result")
|
||||
return result == "1"
|
||||
}
|
||||
|
||||
fun setGlobalNamespaceEnabled(value: String) {
|
||||
getRootShell().newJob()
|
||||
.add("nsenter --mount=/proc/1/ns/mnt echo $value > ${Natives.GLOBAL_NAMESPACE_FILE}")
|
||||
.submit { result ->
|
||||
Log.i(TAG, "setGlobalNamespaceEnabled result: ${result.isSuccess} [${result.out}]")
|
||||
}
|
||||
}
|
||||
|
||||
fun isSepolicyValid(rules: String?): Boolean {
|
||||
if (rules == null) {
|
||||
return true
|
||||
@@ -443,7 +479,7 @@ fun getFileName(context: Context, uri: Uri): String {
|
||||
|
||||
fun moduleBackupDir(): String? {
|
||||
val shell = getRootShell()
|
||||
val baseBackupDir = "/data/adb/ksu/modules_bak"
|
||||
val baseBackupDir = "/data/adb/ksu/backup/modules"
|
||||
val resultBase = ShellUtils.fastCmd(shell, "mkdir -p $baseBackupDir").trim()
|
||||
if (resultBase.isNotEmpty()) return null
|
||||
|
||||
@@ -462,60 +498,42 @@ fun moduleBackup(): Boolean {
|
||||
|
||||
val checkEmptyCommand = "if [ -z \"$(ls -A /data/adb/modules)\" ]; then echo 'empty'; fi"
|
||||
val resultCheckEmpty = ShellUtils.fastCmd(shell, checkEmptyCommand).trim()
|
||||
|
||||
if (resultCheckEmpty == "empty") {
|
||||
return false
|
||||
}
|
||||
|
||||
val backupDir = moduleBackupDir() ?: return false
|
||||
val command = "cp -rp /data/adb/modules/* $backupDir"
|
||||
val result = ShellUtils.fastCmd(shell, command).trim()
|
||||
val timestamp = ShellUtils.fastCmd(shell, "date +%Y%m%d_%H%M%S").trim()
|
||||
if (timestamp.isEmpty()) return false
|
||||
|
||||
return result.isEmpty()
|
||||
}
|
||||
val tarName = "modules_backup_$timestamp.tar"
|
||||
val tarPath = "/data/local/tmp/$tarName"
|
||||
val internalBackupDir = "/data/adb/ksu/backup/modules"
|
||||
val internalBackupPath = "$internalBackupDir/$tarName"
|
||||
|
||||
fun moduleMigration(): Boolean {
|
||||
val shell = getRootShell()
|
||||
val command = "cp -rp /data/adb/modules/* /data/adb/modules_update"
|
||||
val result = ShellUtils.fastCmd(shell, command).trim()
|
||||
val tarCmd = "$BUSYBOX tar -cpf $tarPath -C /data/adb/modules $(ls /data/adb/modules)"
|
||||
val tarResult = ShellUtils.fastCmd(shell, tarCmd).trim()
|
||||
if (tarResult.isNotEmpty()) return false
|
||||
|
||||
return result.isEmpty()
|
||||
ShellUtils.fastCmd(shell, "mkdir -p $internalBackupDir")
|
||||
|
||||
val cpResult = ShellUtils.fastCmd(shell, "cp $tarPath $internalBackupPath").trim()
|
||||
if (cpResult.isNotEmpty()) return false
|
||||
|
||||
ShellUtils.fastCmd(shell, "rm -f $tarPath")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun moduleRestore(): Boolean {
|
||||
val shell = getRootShell()
|
||||
|
||||
val command = "ls -t /data/adb/ksu/modules_bak | head -n 1"
|
||||
val latestBackupDir = ShellUtils.fastCmd(shell, command).trim()
|
||||
val findTarCmd = "ls -t /data/adb/ksu/backup/modules/modules_backup_*.tar 2>/dev/null | head -n 1"
|
||||
val tarPath = ShellUtils.fastCmd(shell, findTarCmd).trim()
|
||||
if (tarPath.isEmpty()) return false
|
||||
|
||||
if (latestBackupDir.isEmpty()) return false
|
||||
|
||||
val sourceDir = "/data/adb/ksu/modules_bak/$latestBackupDir"
|
||||
val destinationDir = "/data/adb/modules_update"
|
||||
|
||||
val createDestDirCommand = "mkdir -p $destinationDir"
|
||||
ShellUtils.fastCmd(shell, createDestDirCommand)
|
||||
|
||||
val moveCommand = "cp -rp $sourceDir/* $destinationDir"
|
||||
val result = ShellUtils.fastCmd(shell, moveCommand).trim()
|
||||
|
||||
return result.isEmpty()
|
||||
}
|
||||
|
||||
fun allowlistBackupDir(): String? {
|
||||
val shell = getRootShell()
|
||||
val baseBackupDir = "/data/adb/ksu/allowlist_bak"
|
||||
val resultBase = ShellUtils.fastCmd(shell, "mkdir -p $baseBackupDir").trim()
|
||||
if (resultBase.isNotEmpty()) return null
|
||||
|
||||
val timestamp = ShellUtils.fastCmd(shell, "date +%Y%m%d_%H%M%S").trim()
|
||||
if (timestamp.isEmpty()) return null
|
||||
|
||||
val newBackupDir = "$baseBackupDir/$timestamp"
|
||||
val resultNewDir = ShellUtils.fastCmd(shell, "mkdir -p $newBackupDir").trim()
|
||||
|
||||
if (resultNewDir.isEmpty()) return newBackupDir
|
||||
return null
|
||||
val extractCmd = "$BUSYBOX tar -xpf $tarPath -C /data/adb/modules_update"
|
||||
val extractResult = ShellUtils.fastCmd(shell, extractCmd).trim()
|
||||
return extractResult.isEmpty()
|
||||
}
|
||||
|
||||
fun allowlistBackup(): Boolean {
|
||||
@@ -523,34 +541,50 @@ fun allowlistBackup(): Boolean {
|
||||
|
||||
val checkEmptyCommand = "if [ -z \"$(ls -A /data/adb/ksu/.allowlist)\" ]; then echo 'empty'; fi"
|
||||
val resultCheckEmpty = ShellUtils.fastCmd(shell, checkEmptyCommand).trim()
|
||||
|
||||
if (resultCheckEmpty == "empty") {
|
||||
return false
|
||||
}
|
||||
|
||||
val backupDir = allowlistBackupDir() ?: return false
|
||||
val command = "cp -rp /data/adb/ksu/.allowlist $backupDir"
|
||||
val result = ShellUtils.fastCmd(shell, command).trim()
|
||||
val timestamp = ShellUtils.fastCmd(shell, "date +%Y%m%d_%H%M%S").trim()
|
||||
if (timestamp.isEmpty()) return false
|
||||
|
||||
return result.isEmpty()
|
||||
val tarName = "allowlist_backup_$timestamp.tar"
|
||||
val tarPath = "/data/local/tmp/$tarName"
|
||||
val internalBackupDir = "/data/adb/ksu/backup/allowlist"
|
||||
val internalBackupPath = "$internalBackupDir/$tarName"
|
||||
|
||||
val tarCmd = "$BUSYBOX tar -cpf $tarPath -C /data/adb/ksu .allowlist"
|
||||
val tarResult = ShellUtils.fastCmd(shell, tarCmd).trim()
|
||||
if (tarResult.isNotEmpty()) return false
|
||||
|
||||
ShellUtils.fastCmd(shell, "mkdir -p $internalBackupDir")
|
||||
|
||||
val cpResult = ShellUtils.fastCmd(shell, "cp $tarPath $internalBackupPath").trim()
|
||||
if (cpResult.isNotEmpty()) return false
|
||||
|
||||
ShellUtils.fastCmd(shell, "rm -f $tarPath")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun allowlistRestore(): Boolean {
|
||||
val shell = getRootShell()
|
||||
|
||||
val command = "ls -t /data/adb/ksu/allowlist_bak | head -n 1"
|
||||
val latestBackupDir = ShellUtils.fastCmd(shell, command).trim()
|
||||
// Find the latest allowlist tar backup in /data/adb/ksu/backup/allowlist
|
||||
val findTarCmd = "ls -t /data/adb/ksu/backup/allowlist/allowlist_backup_*.tar 2>/dev/null | head -n 1"
|
||||
val tarPath = ShellUtils.fastCmd(shell, findTarCmd).trim()
|
||||
if (tarPath.isEmpty()) return false
|
||||
|
||||
if (latestBackupDir.isEmpty()) return false
|
||||
// Extract the tar to /data/adb/ksu (restores .allowlist folder with permissions)
|
||||
val extractCmd = "$BUSYBOX tar -xpf $tarPath -C /data/adb/ksu"
|
||||
val extractResult = ShellUtils.fastCmd(shell, extractCmd).trim()
|
||||
return extractResult.isEmpty()
|
||||
}
|
||||
|
||||
val sourceDir = "/data/adb/ksu/allowlist_bak/$latestBackupDir"
|
||||
val destinationDir = "/data/adb/ksu/"
|
||||
|
||||
val createDestDirCommand = "mkdir -p $destinationDir"
|
||||
ShellUtils.fastCmd(shell, createDestDirCommand)
|
||||
|
||||
val moveCommand = "cp -rp $sourceDir/.allowlist $destinationDir"
|
||||
val result = ShellUtils.fastCmd(shell, moveCommand).trim()
|
||||
fun moduleMigration(): Boolean {
|
||||
val shell = getRootShell()
|
||||
val command = "cp -rp /data/adb/modules/* /data/adb/modules_update"
|
||||
val result = ShellUtils.fastCmd(shell, command).trim()
|
||||
|
||||
return result.isEmpty()
|
||||
}
|
||||
@@ -576,12 +610,19 @@ fun getSuSFSVariant(): String {
|
||||
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} variant")
|
||||
return result
|
||||
}
|
||||
|
||||
fun getSuSFSFeatures(): String {
|
||||
val shell = getRootShell()
|
||||
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} features")
|
||||
return result
|
||||
}
|
||||
|
||||
fun hasSuSFs_SUS_SU(): String {
|
||||
val shell = getRootShell()
|
||||
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su support")
|
||||
return result
|
||||
}
|
||||
|
||||
fun susfsSUS_SU_0(): String {
|
||||
val shell = getRootShell()
|
||||
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 0")
|
||||
@@ -607,6 +648,25 @@ fun currentMountSystem(): String {
|
||||
return result.substringAfter(":").substringAfter(" ").trim()
|
||||
}
|
||||
|
||||
fun getModuleSize(dir: File): Long {
|
||||
val shell = getRootShell()
|
||||
val cmd = "$BUSYBOX du -sb '${dir.absolutePath}' | awk '{print \$1}'"
|
||||
val result = ShellUtils.fastCmd(shell, cmd).trim()
|
||||
return result.toLongOrNull() ?: 0L
|
||||
}
|
||||
|
||||
fun isSuCompatDisabled(): Boolean {
|
||||
return Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT && !Natives.isSuEnabled()
|
||||
}
|
||||
|
||||
fun zygiskRequired(dir: File): Boolean {
|
||||
val shell = getRootShell()
|
||||
val zygiskLib = "${dir.absolutePath}/zygisk"
|
||||
val cmd = "ls \"$zygiskLib\""
|
||||
val result = ShellUtils.fastCmdResult(shell, cmd)
|
||||
return result
|
||||
}
|
||||
|
||||
fun setAppProfileTemplate(id: String, template: String): Boolean {
|
||||
val shell = getRootShell()
|
||||
val escapedTemplate = template.replace("\"", "\\\"")
|
||||
@@ -640,4 +700,4 @@ fun launchApp(packageName: String) {
|
||||
fun restartApp(packageName: String) {
|
||||
forceStopApp(packageName)
|
||||
launchApp(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.rifsxd.ksunext.ui.util
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import java.util.Locale
|
||||
|
||||
object LocaleHelper {
|
||||
|
||||
/**
|
||||
* Check if should use system language settings (Android 13+)
|
||||
*/
|
||||
val useSystemLanguageSettings: Boolean
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
|
||||
/**
|
||||
* Launch system app locale settings (Android 13+)
|
||||
*/
|
||||
fun launchSystemLanguageSettings(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
try {
|
||||
val intent = Intent(Settings.ACTION_APP_LOCALE_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", context.packageName, null)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
// Fallback to app language settings if system settings not available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply saved language setting to context (for Android < 13)
|
||||
*/
|
||||
fun applyLanguage(context: Context): Context {
|
||||
// On Android 13+, language is handled by system
|
||||
if (useSystemLanguageSettings) {
|
||||
return context
|
||||
}
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val localeTag = prefs.getString("app_locale", "system") ?: "system"
|
||||
|
||||
return if (localeTag == "system") {
|
||||
context
|
||||
} else {
|
||||
val locale = parseLocaleTag(localeTag)
|
||||
setLocale(context, locale)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set locale for context (Android < 13)
|
||||
*/
|
||||
private fun setLocale(context: Context, locale: Locale): Context {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
updateResources(context, locale)
|
||||
} else {
|
||||
updateResourcesLegacy(context, locale)
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
private fun updateResources(context: Context, locale: Locale): Context {
|
||||
val configuration = Configuration()
|
||||
configuration.setLocale(locale)
|
||||
configuration.setLayoutDirection(locale)
|
||||
return context.createConfigurationContext(configuration)
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private fun updateResourcesLegacy(context: Context, locale: Locale): Context {
|
||||
Locale.setDefault(locale)
|
||||
val resources = context.resources
|
||||
val configuration = resources.configuration
|
||||
configuration.locale = locale
|
||||
configuration.setLayoutDirection(locale)
|
||||
resources.updateConfiguration(configuration, resources.displayMetrics)
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse locale tag to Locale object
|
||||
*/
|
||||
private fun parseLocaleTag(tag: String): Locale {
|
||||
return try {
|
||||
if (tag.contains("_")) {
|
||||
val parts = tag.split("_")
|
||||
Locale.Builder()
|
||||
.setLanguage(parts[0])
|
||||
.setRegion(parts.getOrNull(1) ?: "")
|
||||
.build()
|
||||
} else {
|
||||
Locale.Builder()
|
||||
.setLanguage(tag)
|
||||
.build()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Locale.getDefault()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart activity to apply language change (Android < 13)
|
||||
*/
|
||||
fun restartActivity(context: Context) {
|
||||
if (context is Activity && !useSystemLanguageSettings) {
|
||||
context.recreate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current app locale
|
||||
*/
|
||||
fun getCurrentAppLocale(context: Context): Locale? {
|
||||
return if (useSystemLanguageSettings) {
|
||||
// Android 13+ - get from system app locale settings
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
try {
|
||||
val localeManager = context.getSystemService(Context.LOCALE_SERVICE) as? android.app.LocaleManager
|
||||
val locales = localeManager?.applicationLocales
|
||||
if (locales != null && !locales.isEmpty) {
|
||||
locales.get(0)
|
||||
} else {
|
||||
null // System default
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null // System default
|
||||
}
|
||||
} else {
|
||||
null // System default
|
||||
}
|
||||
} else {
|
||||
// Android < 13 - get from SharedPreferences
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val localeTag = prefs.getString("app_locale", "system") ?: "system"
|
||||
if (localeTag == "system") {
|
||||
null // System default
|
||||
} else {
|
||||
parseLocaleTag(localeTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.rifsxd.ksunext.ui.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@@ -8,14 +9,19 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.io.File
|
||||
import java.text.Collator
|
||||
import java.util.Locale
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
||||
import com.rifsxd.ksunext.ui.util.listModules
|
||||
import com.rifsxd.ksunext.ui.util.overlayFsAvailable
|
||||
import com.rifsxd.ksunext.ui.util.getModuleSize
|
||||
import com.rifsxd.ksunext.ui.util.zygiskRequired
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
@@ -39,7 +45,10 @@ class ModuleViewModel : ViewModel() {
|
||||
val updateJson: String,
|
||||
val hasWebUi: Boolean,
|
||||
val hasActionScript: Boolean,
|
||||
val dirId: String
|
||||
val dirId: String,
|
||||
val size: Long,
|
||||
val banner: String,
|
||||
val zygiskRequired: Boolean
|
||||
)
|
||||
|
||||
data class ModuleUpdateInfo(
|
||||
@@ -49,9 +58,6 @@ class ModuleViewModel : ViewModel() {
|
||||
val changelog: String,
|
||||
)
|
||||
|
||||
var isOverlayAvailable by mutableStateOf(overlayFsAvailable())
|
||||
private set
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
@@ -59,11 +65,21 @@ class ModuleViewModel : ViewModel() {
|
||||
|
||||
var sortAToZ by mutableStateOf(false)
|
||||
var sortZToA by mutableStateOf(false)
|
||||
var sortSizeLowToHigh by mutableStateOf(false)
|
||||
var sortSizeHighToLow by mutableStateOf(false)
|
||||
var sortEnabledFirst by mutableStateOf(false)
|
||||
var sortActionFirst by mutableStateOf(false)
|
||||
var sortWebUiFirst by mutableStateOf(false)
|
||||
|
||||
val moduleList by derivedStateOf {
|
||||
val comparator = when {
|
||||
sortWebUiFirst -> compareByDescending<ModuleInfo> { it.hasWebUi }
|
||||
sortEnabledFirst -> compareByDescending<ModuleInfo> { it.enabled }
|
||||
sortActionFirst -> compareByDescending<ModuleInfo> { it.hasActionScript }
|
||||
sortAToZ -> compareBy<ModuleInfo> { it.name.lowercase() }
|
||||
sortZToA -> compareByDescending<ModuleInfo> { it.name.lowercase() }
|
||||
sortSizeLowToHigh -> compareBy<ModuleInfo> { it.size }
|
||||
sortSizeHighToLow -> compareByDescending<ModuleInfo> { it.size }
|
||||
else -> compareBy<ModuleInfo> { it.dirId }
|
||||
}.thenBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
|
||||
|
||||
@@ -84,58 +100,81 @@ class ModuleViewModel : ViewModel() {
|
||||
isNeedRefresh = true
|
||||
}
|
||||
|
||||
var zipUris by mutableStateOf<List<Uri>>(emptyList())
|
||||
|
||||
fun updateZipUris(uris: List<Uri>) {
|
||||
zipUris = uris
|
||||
}
|
||||
|
||||
fun clearZipUris() {
|
||||
zipUris = emptyList()
|
||||
}
|
||||
|
||||
fun fetchModuleList() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
viewModelScope.launch {
|
||||
|
||||
isRefreshing = true
|
||||
|
||||
val oldModuleList = modules
|
||||
withContext(Dispatchers.IO) {
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
val oldModuleList = modules
|
||||
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
kotlin.runCatching {
|
||||
val result = listModules()
|
||||
Log.i(TAG, "result: $result")
|
||||
|
||||
kotlin.runCatching {
|
||||
isOverlayAvailable = overlayFsAvailable()
|
||||
val array = JSONArray(result)
|
||||
modules = (0 until array.length())
|
||||
.asSequence()
|
||||
.map { array.getJSONObject(it) }
|
||||
.map { obj ->
|
||||
val id = obj.getString("id")
|
||||
val dirId = obj.getString("dir_id")
|
||||
val moduleDir = File("/data/adb/modules/$dirId")
|
||||
val size = getModuleSize(moduleDir)
|
||||
val zygiskRequired = zygiskRequired(moduleDir)
|
||||
|
||||
val result = listModules()
|
||||
ModuleInfo(
|
||||
id,
|
||||
obj.optString("name"),
|
||||
obj.optString("author", "Unknown"),
|
||||
obj.optString("version", "Unknown"),
|
||||
obj.optInt("versionCode", 0),
|
||||
obj.optString("description"),
|
||||
obj.getBoolean("enabled"),
|
||||
obj.getBoolean("update"),
|
||||
obj.getBoolean("remove"),
|
||||
obj.optString("updateJson"),
|
||||
obj.optBoolean("web"),
|
||||
obj.optBoolean("action"),
|
||||
dirId,
|
||||
size,
|
||||
obj.optString("banner"),
|
||||
zygiskRequired
|
||||
)
|
||||
}.toList()
|
||||
isNeedRefresh = false
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "fetchModuleList: ", e)
|
||||
isRefreshing = false
|
||||
}
|
||||
|
||||
Log.i(TAG, "result: $result")
|
||||
// when both old and new is kotlin.collections.EmptyList
|
||||
// moduleList update will don't trigger
|
||||
if (oldModuleList === modules) {
|
||||
isRefreshing = false
|
||||
}
|
||||
|
||||
val array = JSONArray(result)
|
||||
modules = (0 until array.length())
|
||||
.asSequence()
|
||||
.map { array.getJSONObject(it) }
|
||||
.map { obj ->
|
||||
ModuleInfo(
|
||||
obj.getString("id"),
|
||||
obj.optString("name"),
|
||||
obj.optString("author", "Unknown"),
|
||||
obj.optString("version", "Unknown"),
|
||||
obj.optInt("versionCode", 0),
|
||||
obj.optString("description"),
|
||||
obj.getBoolean("enabled"),
|
||||
obj.getBoolean("update"),
|
||||
obj.getBoolean("remove"),
|
||||
obj.optString("updateJson"),
|
||||
obj.optBoolean("web"),
|
||||
obj.optBoolean("action"),
|
||||
obj.getString("dir_id")
|
||||
)
|
||||
}.toList()
|
||||
isNeedRefresh = false
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "fetchModuleList: ", e)
|
||||
isRefreshing = false
|
||||
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
||||
}
|
||||
|
||||
// when both old and new is kotlin.collections.EmptyList
|
||||
// moduleList update will don't trigger
|
||||
if (oldModuleList === modules) {
|
||||
isRefreshing = false
|
||||
}
|
||||
|
||||
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
||||
}
|
||||
}
|
||||
|
||||
private fun sanitizeVersionString(version: String): String {
|
||||
return version.replace(Regex("[^a-zA-Z0-9.\\-_]"), "_")
|
||||
}
|
||||
|
||||
fun checkUpdate(m: ModuleInfo): Triple<String, String, String> {
|
||||
val empty = Triple("", "", "")
|
||||
if (m.updateJson.isEmpty() || m.remove || m.update || !m.enabled) {
|
||||
@@ -165,7 +204,8 @@ class ModuleViewModel : ViewModel() {
|
||||
JSONObject(result)
|
||||
}.getOrNull() ?: return empty
|
||||
|
||||
val version = updateJson.optString("version", "")
|
||||
var version = updateJson.optString("version", "")
|
||||
version = sanitizeVersionString(version)
|
||||
val versionCode = updateJson.optInt("versionCode", 0)
|
||||
val zipUrl = updateJson.optString("zipUrl", "")
|
||||
val changelog = updateJson.optString("changelog", "")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.rifsxd.ksunext.ui.viewmodel
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.ApplicationInfo
|
||||
@@ -9,6 +10,7 @@ import android.os.IBinder
|
||||
import android.os.Parcelable
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -24,19 +26,20 @@ import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.ui.KsuService
|
||||
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
||||
import com.rifsxd.ksunext.ui.util.KsuCli
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import androidx.core.content.edit
|
||||
|
||||
class SuperUserViewModel : ViewModel() {
|
||||
|
||||
var refreshOnReturn by mutableStateOf(false)
|
||||
public set
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SuperUserViewModel"
|
||||
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
||||
private var profileOverrides by mutableStateOf<Map<String, Natives.Profile>>(emptyMap())
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
@@ -66,16 +69,26 @@ class SuperUserViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private val prefs = ksuApp.getSharedPreferences("settings", Context.MODE_PRIVATE)!!
|
||||
|
||||
var search by mutableStateOf("")
|
||||
var showSystemApps by mutableStateOf(false)
|
||||
var showSystemApps by mutableStateOf(prefs.getBoolean("show_system_apps", false))
|
||||
private set
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
fun updateShowSystemApps(newValue: Boolean) {
|
||||
showSystemApps = newValue
|
||||
prefs.edit { putBoolean("show_system_apps", newValue) }
|
||||
}
|
||||
|
||||
private val sortedList by derivedStateOf {
|
||||
val comparator = compareBy<AppInfo> {
|
||||
when {
|
||||
it.allowSu -> 0
|
||||
it.hasCustomProfile -> 1
|
||||
it.profile != null && it.profile.allowSu -> 0
|
||||
it.profile != null && (
|
||||
if (it.profile.allowSu) !it.profile.rootUseDefault else !it.profile.nonRootUseDefault
|
||||
) -> 1
|
||||
else -> 2
|
||||
}
|
||||
}.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))
|
||||
@@ -85,7 +98,9 @@ class SuperUserViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
val appList by derivedStateOf {
|
||||
sortedList.filter {
|
||||
sortedList.map { app ->
|
||||
profileOverrides[app.packageName]?.let { app.copy(profile = it) } ?: app
|
||||
}.filter {
|
||||
it.label.contains(search, true) || it.packageName.contains(
|
||||
search,
|
||||
true
|
||||
@@ -97,6 +112,12 @@ class SuperUserViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAppProfile(packageName: String, newProfile: Natives.Profile) {
|
||||
profileOverrides = profileOverrides.toMutableMap().apply {
|
||||
put(packageName, newProfile)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun connectKsuService(
|
||||
crossinline onDisconnect: () -> Unit = {}
|
||||
): Pair<IBinder, ServiceConnection> = suspendCoroutine {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.rifsxd.ksunext.ui.webui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AppIconUtil {
|
||||
private static final Map<String, Bitmap> iconCache = new HashMap<>();
|
||||
|
||||
public static Bitmap loadAppIconSync(Context context, String packageName, int sizePx) {
|
||||
Bitmap cached = iconCache.get(packageName);
|
||||
if (cached != null) return cached;
|
||||
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
|
||||
Drawable drawable = pm.getApplicationIcon(appInfo);
|
||||
Bitmap raw = drawableToBitmap(drawable, sizePx);
|
||||
Bitmap icon = Bitmap.createScaledBitmap(raw, sizePx, sizePx, true);
|
||||
iconCache.put(packageName, icon);
|
||||
return icon;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap drawableToBitmap(Drawable drawable, int size) {
|
||||
if (drawable instanceof BitmapDrawable) return ((BitmapDrawable) drawable).getBitmap();
|
||||
|
||||
int width = drawable.getIntrinsicWidth() > 0 ? drawable.getIntrinsicWidth() : size;
|
||||
int height = drawable.getIntrinsicHeight() > 0 ? drawable.getIntrinsicHeight() : size;
|
||||
|
||||
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bmp);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
return bmp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.rifsxd.ksunext.ui.webui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
import com.rifsxd.ksunext.ui.theme.AMOLED_BLACK
|
||||
|
||||
/**
|
||||
* @author rifsxd
|
||||
* @date 2025/6/2.
|
||||
*/
|
||||
object MonetColorsProvider {
|
||||
fun getColorsCss(context: Context): String {
|
||||
|
||||
val isDark = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val amoledMode = prefs.getBoolean("enable_amoled", false)
|
||||
|
||||
val colorScheme = if (isDark) {
|
||||
dynamicDarkColorScheme(context)
|
||||
} else {
|
||||
dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
fun blend(c1: Color, c2: Color, ratio: Float): Color {
|
||||
val inv = 1f - ratio
|
||||
return Color(
|
||||
red = c1.red * inv + c2.red * ratio,
|
||||
green = c1.green * inv + c2.green * ratio,
|
||||
blue = c1.blue * inv + c2.blue * ratio,
|
||||
alpha = c1.alpha
|
||||
)
|
||||
}
|
||||
|
||||
val monetColors = if (isDark && amoledMode) {
|
||||
mapOf(
|
||||
"primary" to colorScheme.primary.toArgb().toHex(),
|
||||
"onPrimary" to colorScheme.onPrimary.toArgb().toHex(),
|
||||
"primaryContainer" to blend(colorScheme.primaryContainer, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"onPrimaryContainer" to colorScheme.onPrimaryContainer.toArgb().toHex(),
|
||||
"inversePrimary" to colorScheme.inversePrimary.toArgb().toHex(),
|
||||
"secondary" to colorScheme.secondary.toArgb().toHex(),
|
||||
"onSecondary" to colorScheme.onSecondary.toArgb().toHex(),
|
||||
"secondaryContainer" to blend(colorScheme.secondaryContainer, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"onSecondaryContainer" to colorScheme.onSecondaryContainer.toArgb().toHex(),
|
||||
"tertiary" to colorScheme.tertiary.toArgb().toHex(),
|
||||
"onTertiary" to colorScheme.onTertiary.toArgb().toHex(),
|
||||
"tertiaryContainer" to blend(colorScheme.tertiaryContainer, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"onTertiaryContainer" to colorScheme.onTertiaryContainer.toArgb().toHex(),
|
||||
"background" to AMOLED_BLACK.toArgb().toHex(),
|
||||
"onBackground" to colorScheme.onBackground.toArgb().toHex(),
|
||||
"surface" to AMOLED_BLACK.toArgb().toHex(),
|
||||
"tonalSurface" to blend(colorScheme.surfaceColorAtElevation(1.dp), AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"onSurface" to colorScheme.onSurface.toArgb().toHex(),
|
||||
"surfaceVariant" to blend(colorScheme.surfaceVariant, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"onSurfaceVariant" to colorScheme.onSurfaceVariant.toArgb().toHex(),
|
||||
"surfaceTint" to colorScheme.surfaceTint.toArgb().toHex(),
|
||||
"inverseSurface" to colorScheme.inverseSurface.toArgb().toHex(),
|
||||
"inverseOnSurface" to colorScheme.inverseOnSurface.toArgb().toHex(),
|
||||
"error" to colorScheme.error.toArgb().toHex(),
|
||||
"onError" to colorScheme.onError.toArgb().toHex(),
|
||||
"errorContainer" to colorScheme.errorContainer.toArgb().toHex(),
|
||||
"onErrorContainer" to colorScheme.onErrorContainer.toArgb().toHex(),
|
||||
"outline" to colorScheme.outline.toArgb().toHex(),
|
||||
"outlineVariant" to colorScheme.outlineVariant.toArgb().toHex(),
|
||||
"scrim" to colorScheme.scrim.toArgb().toHex(),
|
||||
"surfaceBright" to blend(colorScheme.surfaceBright, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"surfaceDim" to blend(colorScheme.surfaceDim, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"surfaceContainer" to blend(colorScheme.surfaceContainer, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"surfaceContainerHigh" to blend(colorScheme.surfaceContainerHigh, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"surfaceContainerHighest" to blend(colorScheme.surfaceContainerHighest, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"surfaceContainerLow" to blend(colorScheme.surfaceContainerLow, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"surfaceContainerLowest" to blend(colorScheme.surfaceContainerLowest, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"filledTonalButtonContentColor" to colorScheme.onPrimaryContainer.toArgb().toHex(),
|
||||
"filledTonalButtonContainerColor" to blend(colorScheme.secondaryContainer, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"filledTonalButtonDisabledContentColor" to colorScheme.onSurfaceVariant.toArgb().toHex(),
|
||||
"filledTonalButtonDisabledContainerColor" to blend(colorScheme.surfaceVariant, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"filledCardContentColor" to colorScheme.onPrimaryContainer.toArgb().toHex(),
|
||||
"filledCardContainerColor" to blend(colorScheme.primaryContainer, AMOLED_BLACK, 0.6f).toArgb().toHex(),
|
||||
"filledCardDisabledContentColor" to colorScheme.onSurfaceVariant.toArgb().toHex(),
|
||||
"filledCardDisabledContainerColor" to blend(colorScheme.surfaceVariant, AMOLED_BLACK, 0.6f).toArgb().toHex()
|
||||
)
|
||||
} else {
|
||||
mapOf(
|
||||
"primary" to colorScheme.primary.toArgb().toHex(),
|
||||
"onPrimary" to colorScheme.onPrimary.toArgb().toHex(),
|
||||
"primaryContainer" to colorScheme.primaryContainer.toArgb().toHex(),
|
||||
"onPrimaryContainer" to colorScheme.onPrimaryContainer.toArgb().toHex(),
|
||||
"inversePrimary" to colorScheme.inversePrimary.toArgb().toHex(),
|
||||
"secondary" to colorScheme.secondary.toArgb().toHex(),
|
||||
"onSecondary" to colorScheme.onSecondary.toArgb().toHex(),
|
||||
"secondaryContainer" to colorScheme.secondaryContainer.toArgb().toHex(),
|
||||
"onSecondaryContainer" to colorScheme.onSecondaryContainer.toArgb().toHex(),
|
||||
"tertiary" to colorScheme.tertiary.toArgb().toHex(),
|
||||
"onTertiary" to colorScheme.onTertiary.toArgb().toHex(),
|
||||
"tertiaryContainer" to colorScheme.tertiaryContainer.toArgb().toHex(),
|
||||
"onTertiaryContainer" to colorScheme.onTertiaryContainer.toArgb().toHex(),
|
||||
"background" to colorScheme.background.toArgb().toHex(),
|
||||
"onBackground" to colorScheme.onBackground.toArgb().toHex(),
|
||||
"surface" to colorScheme.surface.toArgb().toHex(),
|
||||
"tonalSurface" to colorScheme.surfaceColorAtElevation(1.dp).toArgb().toHex(),
|
||||
"onSurface" to colorScheme.onSurface.toArgb().toHex(),
|
||||
"surfaceVariant" to colorScheme.surfaceVariant.toArgb().toHex(),
|
||||
"onSurfaceVariant" to colorScheme.onSurfaceVariant.toArgb().toHex(),
|
||||
"surfaceTint" to colorScheme.surfaceTint.toArgb().toHex(),
|
||||
"inverseSurface" to colorScheme.inverseSurface.toArgb().toHex(),
|
||||
"inverseOnSurface" to colorScheme.inverseOnSurface.toArgb().toHex(),
|
||||
"error" to colorScheme.error.toArgb().toHex(),
|
||||
"onError" to colorScheme.onError.toArgb().toHex(),
|
||||
"errorContainer" to colorScheme.errorContainer.toArgb().toHex(),
|
||||
"onErrorContainer" to colorScheme.onErrorContainer.toArgb().toHex(),
|
||||
"outline" to colorScheme.outline.toArgb().toHex(),
|
||||
"outlineVariant" to colorScheme.outlineVariant.toArgb().toHex(),
|
||||
"scrim" to colorScheme.scrim.toArgb().toHex(),
|
||||
"surfaceBright" to colorScheme.surfaceBright.toArgb().toHex(),
|
||||
"surfaceDim" to colorScheme.surfaceDim.toArgb().toHex(),
|
||||
"surfaceContainer" to colorScheme.surfaceContainer.toArgb().toHex(),
|
||||
"surfaceContainerHigh" to colorScheme.surfaceContainerHigh.toArgb().toHex(),
|
||||
"surfaceContainerHighest" to colorScheme.surfaceContainerHighest.toArgb().toHex(),
|
||||
"surfaceContainerLow" to colorScheme.surfaceContainerLow.toArgb().toHex(),
|
||||
"surfaceContainerLowest" to colorScheme.surfaceContainerLowest.toArgb().toHex(),
|
||||
"filledTonalButtonContentColor" to colorScheme.onPrimaryContainer.toArgb().toHex(),
|
||||
"filledTonalButtonContainerColor" to colorScheme.secondaryContainer.toArgb().toHex(),
|
||||
"filledTonalButtonDisabledContentColor" to colorScheme.onSurfaceVariant.toArgb().toHex(),
|
||||
"filledTonalButtonDisabledContainerColor" to colorScheme.surfaceVariant.toArgb().toHex(),
|
||||
"filledCardContentColor" to colorScheme.onPrimaryContainer.toArgb().toHex(),
|
||||
"filledCardContainerColor" to colorScheme.primaryContainer.toArgb().toHex(),
|
||||
"filledCardDisabledContentColor" to colorScheme.onSurfaceVariant.toArgb().toHex(),
|
||||
"filledCardDisabledContainerColor" to colorScheme.surfaceVariant.toArgb().toHex()
|
||||
)
|
||||
}
|
||||
return monetColors.toCssVars()
|
||||
}
|
||||
|
||||
private fun Map<String, String>.toCssVars(): String {
|
||||
return buildString {
|
||||
append(":root {\n")
|
||||
for ((k, v) in this@toCssVars) {
|
||||
append(" --$k: $v;\n")
|
||||
}
|
||||
append("}\n")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.toHex(): String {
|
||||
return String.format("#%06X", 0xFFFFFF and this)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.rifsxd.ksunext.ui.webui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebResourceResponse;
|
||||
|
||||
@@ -15,8 +16,12 @@ import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import com.rifsxd.ksunext.ui.webui.MonetColorsProvider;
|
||||
|
||||
/**
|
||||
* Handler class to open files from file system by root access
|
||||
* For more information about android storage please refer to
|
||||
@@ -81,8 +86,11 @@ public final class SuFilePathHandler implements WebViewAssetLoader.PathHandler {
|
||||
* which files can be loaded.
|
||||
* @throws IllegalArgumentException if the directory is not allowed.
|
||||
*/
|
||||
private final Context mContext;
|
||||
|
||||
public SuFilePathHandler(@NonNull Context context, @NonNull File directory, Shell rootShell) {
|
||||
try {
|
||||
mContext = context;
|
||||
mDirectory = new File(getCanonicalDirPath(directory));
|
||||
if (!isAllowedInternalStorageDir(context)) {
|
||||
throw new IllegalArgumentException("The given directory \"" + directory
|
||||
@@ -130,6 +138,16 @@ public final class SuFilePathHandler implements WebViewAssetLoader.PathHandler {
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
public WebResourceResponse handle(@NonNull String path) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if ("internal/colors.css".equals(path)) {
|
||||
String css = MonetColorsProvider.INSTANCE.getColorsCss(mContext);
|
||||
return new WebResourceResponse(
|
||||
"text/css",
|
||||
"utf-8",
|
||||
new ByteArrayInputStream(css.getBytes(StandardCharsets.UTF_8))
|
||||
);
|
||||
}
|
||||
}
|
||||
try {
|
||||
File file = getCanonicalFileIfChild(mDirectory, path);
|
||||
if (file != null) {
|
||||
|
||||
@@ -25,6 +25,10 @@ class WebUIActivity : ComponentActivity() {
|
||||
|
||||
private var rootShell: Shell? = null
|
||||
|
||||
fun erudaConsole(context: android.content.Context): String {
|
||||
return context.assets.open("eruda.min.js").bufferedReader().use { it.readText() }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
// Enable edge to edge
|
||||
@@ -39,14 +43,17 @@ class WebUIActivity : ComponentActivity() {
|
||||
val name = intent.getStringExtra("name")!!
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
@Suppress("DEPRECATION")
|
||||
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
|
||||
setTaskDescription(ActivityManager.TaskDescription("WebUI-Next | $name"))
|
||||
} else {
|
||||
val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
||||
val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("WebUI-Next | $name").build()
|
||||
setTaskDescription(taskDescription)
|
||||
}
|
||||
|
||||
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||
WebView.setWebContentsDebuggingEnabled(prefs.getBoolean("enable_web_debugging", false))
|
||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||
val enableWebDebugging = prefs.getBoolean("enable_web_debugging", false)
|
||||
|
||||
WebView.setWebContentsDebuggingEnabled(developerOptionsEnabled && enableWebDebugging)
|
||||
|
||||
val moduleDir = "/data/adb/modules/${moduleId}"
|
||||
val webRoot = File("${moduleDir}/webroot")
|
||||
@@ -84,7 +91,40 @@ class WebUIActivity : ComponentActivity() {
|
||||
settings.allowFileAccess = false
|
||||
webviewInterface = WebViewInterface(this@WebUIActivity, this, moduleDir)
|
||||
addJavascriptInterface(webviewInterface, "ksu")
|
||||
setWebViewClient(webViewClient)
|
||||
setWebViewClient(object : WebViewClient() {
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
request: WebResourceRequest
|
||||
): WebResourceResponse? {
|
||||
val url = request.url
|
||||
|
||||
//POC: Handle ksu://icon/[packageName] to serve app icon via WebView
|
||||
if (url.scheme.equals("ksu", ignoreCase = true) && url.host.equals("icon", ignoreCase = true)) {
|
||||
val packageName = url.path?.substring(1)
|
||||
if (!packageName.isNullOrEmpty()) {
|
||||
val icon = AppIconUtil.loadAppIconSync(this@WebUIActivity, packageName, 512)
|
||||
if (icon != null) {
|
||||
val stream = java.io.ByteArrayOutputStream()
|
||||
icon.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, stream)
|
||||
val inputStream = java.io.ByteArrayInputStream(stream.toByteArray())
|
||||
return WebResourceResponse("image/png", null, inputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return webViewAssetLoader.shouldInterceptRequest(url)
|
||||
}
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
if (developerOptionsEnabled && enableWebDebugging) {
|
||||
view?.evaluateJavascript(
|
||||
erudaConsole(this@WebUIActivity),
|
||||
null
|
||||
)
|
||||
view?.evaluateJavascript("eruda.init();", null)
|
||||
}
|
||||
}
|
||||
})
|
||||
loadUrl("https://mui.kernelsu.org/index.html")
|
||||
}
|
||||
|
||||
@@ -95,4 +135,4 @@ class WebUIActivity : ComponentActivity() {
|
||||
super.onDestroy()
|
||||
runCatching { rootShell?.close() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package com.rifsxd.ksunext.ui.webui
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Base64
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.TextUtils
|
||||
@@ -197,6 +203,151 @@ class WebViewInterface(
|
||||
}
|
||||
return currentModuleInfo.toString()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun listSystemPackages(): String {
|
||||
val pm = context.packageManager
|
||||
val packages = pm.getInstalledPackages(0)
|
||||
val packageNames = packages
|
||||
.mapNotNull { pkg ->
|
||||
val appInfo = pkg.applicationInfo
|
||||
if (appInfo != null && (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0) {
|
||||
pkg.packageName
|
||||
} else null
|
||||
}
|
||||
.sorted()
|
||||
val jsonArray = JSONArray()
|
||||
for (pkgName in packageNames) {
|
||||
jsonArray.put(pkgName)
|
||||
}
|
||||
return jsonArray.toString()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun listUserPackages(): String {
|
||||
val pm = context.packageManager
|
||||
val packages = pm.getInstalledPackages(0)
|
||||
val packageNames = packages
|
||||
.mapNotNull { pkg ->
|
||||
val appInfo = pkg.applicationInfo
|
||||
if (appInfo != null && (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0) {
|
||||
pkg.packageName
|
||||
} else null
|
||||
}
|
||||
.sorted()
|
||||
val jsonArray = JSONArray()
|
||||
for (pkgName in packageNames) {
|
||||
jsonArray.put(pkgName)
|
||||
}
|
||||
return jsonArray.toString()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun listAllPackages(): String {
|
||||
val pm = context.packageManager
|
||||
val packages = pm.getInstalledPackages(0)
|
||||
val packageNames = packages.map { it.packageName }.sorted()
|
||||
val jsonArray = JSONArray()
|
||||
for (pkgName in packageNames) {
|
||||
jsonArray.put(pkgName)
|
||||
}
|
||||
return jsonArray.toString()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun getPackagesInfo(packageNamesJson: String): String {
|
||||
val pm = context.packageManager
|
||||
val packageNames = JSONArray(packageNamesJson)
|
||||
val jsonArray = JSONArray()
|
||||
for (i in 0 until packageNames.length()) {
|
||||
val pkgName = packageNames.getString(i)
|
||||
try {
|
||||
val pkg = pm.getPackageInfo(pkgName, 0)
|
||||
val appInfo = pkg.applicationInfo
|
||||
val obj = JSONObject()
|
||||
obj.put("packageName", pkg.packageName)
|
||||
obj.put("versionName", pkg.versionName ?: "")
|
||||
obj.put("versionCode", pkg.longVersionCode)
|
||||
obj.put("appLabel", if (appInfo != null) pm.getApplicationLabel(appInfo).toString() else "")
|
||||
obj.put("isSystem", appInfo != null && (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0)
|
||||
obj.put("uid", appInfo?.uid ?: JSONObject.NULL)
|
||||
jsonArray.put(obj)
|
||||
} catch (e: Exception) {
|
||||
val obj = JSONObject()
|
||||
obj.put("packageName", pkgName)
|
||||
obj.put("error", "Package not found or inaccessible")
|
||||
jsonArray.put(obj)
|
||||
}
|
||||
}
|
||||
return jsonArray.toString()
|
||||
}
|
||||
|
||||
private val packageIconCache = HashMap<String, String>()
|
||||
|
||||
@JavascriptInterface
|
||||
fun cacheAllPackageIcons(size: Int) {
|
||||
val pm = context.packageManager
|
||||
val packages = pm.getInstalledPackages(0)
|
||||
val outputStream = java.io.ByteArrayOutputStream()
|
||||
for (pkg in packages) {
|
||||
val pkgName = pkg.packageName
|
||||
if (packageIconCache.containsKey(pkgName)) continue
|
||||
try {
|
||||
val appInfo = pm.getApplicationInfo(pkgName, 0)
|
||||
val drawable = pm.getApplicationIcon(appInfo)
|
||||
val bitmap = drawableToBitmap(drawable, size)
|
||||
outputStream.reset()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
val byteArray = outputStream.toByteArray()
|
||||
val iconBase64 = "data:image/png;base64," + Base64.encodeToString(byteArray, Base64.NO_WRAP)
|
||||
packageIconCache[pkgName] = iconBase64
|
||||
} catch (_: Exception) {
|
||||
packageIconCache[pkgName] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun getPackagesIcons(packageNamesJson: String, size: Int): String {
|
||||
val pm = context.packageManager
|
||||
val packageNames = JSONArray(packageNamesJson)
|
||||
val jsonArray = JSONArray()
|
||||
val outputStream = java.io.ByteArrayOutputStream()
|
||||
for (i in 0 until packageNames.length()) {
|
||||
val pkgName = packageNames.getString(i)
|
||||
val obj = JSONObject()
|
||||
obj.put("packageName", pkgName)
|
||||
var iconBase64 = packageIconCache[pkgName]
|
||||
if (iconBase64 == null) {
|
||||
try {
|
||||
val appInfo = pm.getApplicationInfo(pkgName, 0)
|
||||
val drawable = pm.getApplicationIcon(appInfo)
|
||||
val bitmap = drawableToBitmap(drawable, size)
|
||||
outputStream.reset()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
val byteArray = outputStream.toByteArray()
|
||||
iconBase64 = "data:image/png;base64," + Base64.encodeToString(byteArray, Base64.NO_WRAP)
|
||||
} catch (_: Exception) {
|
||||
iconBase64 = ""
|
||||
}
|
||||
packageIconCache[pkgName] = iconBase64
|
||||
}
|
||||
obj.put("icon", iconBase64)
|
||||
jsonArray.put(obj)
|
||||
}
|
||||
return jsonArray.toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun drawableToBitmap(drawable: Drawable, size: Int): Bitmap {
|
||||
if (drawable is BitmapDrawable && drawable.bitmap.width == size && drawable.bitmap.height == size) {
|
||||
return drawable.bitmap
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, size, size)
|
||||
drawable.draw(canvas)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
fun hideSystemUI(window: Window) =
|
||||
@@ -206,4 +357,4 @@ fun hideSystemUI(window: Window) =
|
||||
}
|
||||
|
||||
fun showSystemUI(window: Window) =
|
||||
WindowInsetsControllerCompat(window, window.decorView).show(WindowInsetsCompat.Type.systemBars())
|
||||
WindowInsetsControllerCompat(window, window.decorView).show(WindowInsetsCompat.Type.systemBars())
|
||||
Binary file not shown.
BIN
manager/app/src/main/jniLibs/armeabi-v7a/libmagiskboot.so
Normal file
BIN
manager/app/src/main/jniLibs/armeabi-v7a/libmagiskboot.so
Normal file
Binary file not shown.
BIN
manager/app/src/main/jniLibs/x86_64/libmagiskboot.so
Normal file
BIN
manager/app/src/main/jniLibs/x86_64/libmagiskboot.so
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
||||
238
manager/app/src/main/res/values-ar-rSA/strings.xml
Normal file
238
manager/app/src/main/res/values-ar-rSA/strings.xml
Normal file
@@ -0,0 +1,238 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="issue_report_title">هل لديك مشكلة؟</string>
|
||||
<string name="issue_report_body">هل واجهت خطأ أو لديك تعليقات؟</string>
|
||||
<string name="issue_report_body_2">أبلغ عن ذلك في أقرب وقت ممكن!</string>
|
||||
<string name="issue_report_github">ابلغ عنه في github</string>
|
||||
<string name="issue_report_telegram">التواصل عبر تليجرام</string>
|
||||
<string name="issue_report_github_link">https://github.com/KernelSU-Next/KernelSU-Next/issues</string>
|
||||
<string name="issue_report_telegram_link">https://t.me/ksunext</string>
|
||||
<string name="confirm">تأكيد</string>
|
||||
<string name="app_name">KernelSU Next</string>
|
||||
<string name="home">الصفحة الرئيسية</string>
|
||||
<string name="home_not_installed">غير مثبت</string>
|
||||
<string name="home_click_to_install">إضغط للتثبيت</string>
|
||||
<string name="lkm_mode_deprecated">وضع LKM قد تم إهماله!</string>
|
||||
<string name="lkm_alternative_suggestion">قم بتثبيت نواة GKI أو دمج KernelSU Next في جهازك.</string>
|
||||
<string name="home_working">جارِ العمل</string>
|
||||
<string name="home_working_version">الإصدار: %d</string>
|
||||
<string name="home_module_update_count">تحديثات: %d</string>
|
||||
<string name="home_failure">لم يتم العثور على توقيع KernelSU Next v2 في النواة ! [ !KSU_NEXT <unk> != الحجم/الهاش ]</string>
|
||||
<string name="home_failure_tip">اطلب من مطور kernel الخاص بك دمج KernelSU Next!</string>
|
||||
<string name="home_kernel">اصدار النواة</string>
|
||||
<string name="hook_mode">وضع الخطاف </string>
|
||||
<string name="enable">تمكين</string>
|
||||
<string name="disable">تعطيل</string>
|
||||
<string name="enabled">مفعّل</string>
|
||||
<string name="disabled">معطَّل</string>
|
||||
<string name="susfs_supported">مدعوم</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_android">إصدار الـ Android</string>
|
||||
<string name="home_manager_version">إصدار المدير</string>
|
||||
<string name="home_abi">ABI</string>
|
||||
<string name="home_selinux_status">حالة SELinux</string>
|
||||
<string name="selinux_status_disabled">معطَّل</string>
|
||||
<string name="selinux_status_enforcing">مفروض</string>
|
||||
<string name="selinux_status_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_empty">لا توجد وحدة مثبتة</string>
|
||||
<string name="module">وحدة</string>
|
||||
<string name="module_install_prompt_with_name">سيتم تثبيت الوحدات التالية: %1$s</string>
|
||||
<string name="module_sort_a_to_z">الترتيب (A → Z)</string>
|
||||
<string name="module_sort_z_to_a">الترتيب (A → Z)</string>
|
||||
<string name="module_size_low_to_high">الترتيب (منخفض → عالي)</string>
|
||||
<string name="module_size_high_to_low">الترتيب (عالي → منخفض)</string>
|
||||
<string name="uninstall">إلغاء التثبيت</string>
|
||||
<string name="restore">استعادة</string>
|
||||
<string name="module_install">تثبيت</string>
|
||||
<string name="install">تثبيت</string>
|
||||
<string name="reboot">إعادة التشغيل</string>
|
||||
<string name="uninstalled">غير مثبت</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="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_restore_confirm">هل انت متأكد من أنك تريد استعادة وحدة %s ؟</string>
|
||||
<string name="module_restore_success">تم إستعادة %s</string>
|
||||
<string name="module_restore_failed">فشل في إستعادة: %s</string>
|
||||
<string name="module_version">الإصدار</string>
|
||||
<string name="module_author">المطور</string>
|
||||
<string name="module_id">معرف الوحدة</string>
|
||||
<string name="module_version_code">رقم الإصدار</string>
|
||||
<string name="module_update_json">UpdateJson</string>
|
||||
<string name="module_update_json_empty">فارغ</string>
|
||||
<string name="enable_developer_options">تفعيل خيارات المطور</string>
|
||||
<string name="enable_developer_options_summary">إظهار الإعدادات المخفية و معلومات التصحيح الخاصة بالمطورين فقط.</string>
|
||||
<string name="module_overlay_fs_not_available">الوحدات غير متوفرة لان ال OverlayFS معطل بواسطة ال Kernel!</string>
|
||||
<string name="refresh">تحديث</string>
|
||||
<string name="show_system_apps">إظهار تطبيقات النظام</string>
|
||||
<string name="hide_system_apps">إخفاء تطبيقات النظام</string>
|
||||
<string name="export_log">تصدير السجلات</string>
|
||||
<string name="safe_mode">الوضع الآمن</string>
|
||||
<string name="reboot_to_apply">أعد التشغيل لتطبيق التغييرات</string>
|
||||
<string name="module_magisk_conflict">الوحدات غير متاحة بسبب تعارضها مع Magisk!</string>
|
||||
<string name="home_mount_system">تركيب النظام</string>
|
||||
<string name="home_magic_mount">تركيب النظام</string>
|
||||
<string name="home_overlayfs_mount">OverlayFS</string>
|
||||
<string name="unavailable">غير متاح</string>
|
||||
<string name="use_overlay_fs">استخدام OverlayFS</string>
|
||||
<string name="use_overlay_fs_summary">التبديل بين استخدام OverlayFS و Magic Mount لتركيب نظام KernelSU Next.</string>
|
||||
<string name="reboot_required">مطلوب إعادة التشغيل</string>
|
||||
<string name="reboot_message">ستصبح التغييرات سارية المفعول بعد إعادة تشغيل النظام. هل تريد إعادة التشغيل الآن؟</string>
|
||||
<string name="module_restore">استعادة الوحدة</string>
|
||||
<string name="module_restore_message">استعادة الوحدات من النسخة الاحتياطية الأخيرة.</string>
|
||||
<string name="backup_restore">النسخ الاحتياطي والاستعادة</string>
|
||||
<string name="module_backup">نسخ احتياطي للوحدات</string>
|
||||
<string name="allowlist_restore">استعادة القائمة البيضاء</string>
|
||||
<string name="allowlist_restore_message">استعادة الوحدات النمطية من النسخة الاحتياطية الأخيرة.</string>
|
||||
<string name="allowlist_backup">نسخ احتياطي للقائمة البيضاء</string>
|
||||
<string name="allowlist_backup_message">النسخ الاحتياطي للقائمة البيضاء المُعدة حاليا.</string>
|
||||
<string name="warning">تحذير</string>
|
||||
<string name="warning_message">هذه الميزة لا تزال في المرحلة التجريبية و قيد التطوير. الرجاء التأكد من النسخ الاحتياطي لوحداتك قبل المتابعة. استخدم هذه الميزة فقط إذا كنت تفهم المخاطر المحتملة. المتابعة بحذر.</string>
|
||||
<string name="proceed">متابعة</string>
|
||||
<string name="cancel">إلغاء</string>
|
||||
<string name="later">لاحقاً</string>
|
||||
<string name="lkm_warning_message">تصحيح LKM يعتمد على مكونات مغلقة المصدر. هل تريد الاستمرار؟</string>
|
||||
<string name="home_next_kernelsu">🔥 الاصدار التالي</string>
|
||||
<string name="home_next_kernelsu_repo">https://github.com/KernelSU-Next/KernelSU-Next</string>
|
||||
<string name="home_next_kernelsu_body">الفرع التجريبي التالي. تحقق من ذلك على GitHub!</string>
|
||||
<string name="home_experimental_kernelsu">⚠️ تحذير التطوير التجريبي!</string>
|
||||
<string name="home_experimental_kernelsu_repo">127.0.0.1</string>
|
||||
<string name="home_experimental_kernelsu_body">KernelSU Next هي نسخة غير رسمية تخضع دائماً للتطوير التجريبي النشط. وهي تقدم كما هي، بدون ضمانات للاستقرار، أو الأداء، أو الموثوقية.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_1"> • استخدمه على مسؤوليتك الخاصة: قد تحدث أعطال، أو سلوك غير متوقع، أو مشاكل في النظام.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_2"> • لا ضمان: المطورون غير مسؤولين عن أي خسارة في البيانات أو تلف للنظام، أو أي عواقب أخرى ناجمة عن استخدامها.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_3"> • لأغراض الاختبار فقط: موجهة للمستعملين الذين يفهمون المخاطر ويواجهون مشاكل مريحة في استكشاف الأخطاء وإصلاحها.</string>
|
||||
<string name="about_source_code">عرض الكود المصدر في %1$s</string>
|
||||
<string name="profile">ملف تعريف التطبيق</string>
|
||||
<string name="profile_default">الافتراضي</string>
|
||||
<string name="profile_template">قالب</string>
|
||||
<string name="profile_custom">مخصص</string>
|
||||
<string name="profile_name">اسم ملف التعريف</string>
|
||||
<string name="profile_namespace">تحميل ال namespace</string>
|
||||
<string name="profile_namespace_inherited">متوارث</string>
|
||||
<string name="profile_namespace_global">عام</string>
|
||||
<string name="profile_namespace_individual">فردي</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">الإصدار KernelSU Next الحالي %1$d منخفض جدا لكي يعمل المدير بشكل صحيح. الرجاء الترقية إلى الإصدار %2$d أو أعلى!</string>
|
||||
<string name="settings_umount_modules_default">الغاء تحميل الإضافات</string>
|
||||
<string name="settings_umount_modules_default_summary">القيمة الافتراضية العامة لـ\"إلغاء تحميل الإضافات\" في ملفات تعريف التطبيقات. إذا تم تمكينه، إزالة جميع تعديلات الإضافات على النظام للتطبيقات التي لا تحتوي على مجموعة ملف تعريف.</string>
|
||||
<string name="settings_susfs_toggle">أخفاء خطاف kprobes</string>
|
||||
<string name="settings_susfs_toggle_summary">يعطل هذا الخيار خطاف kprobes الذي أنشأه المدير وبدلا من ذلك، يقوم بتفعيل الخطاف المدمج، تنفيذ نفس الوظيفة التي سيتم تطبيقها على كيرنيل غير GKI ، الذي لا يدعم kprobes.</string>
|
||||
<string name="profile_umount_modules_summary">تمكين هذا الخيار سيسمح KernelSU Next لاستعادة أي ملفات معدلة بواسطة وحدات هذا التطبيق.</string>
|
||||
<string name="profile_selinux_domain">النطاق</string>
|
||||
<string name="profile_selinux_rules">القواعد</string>
|
||||
<string name="module_update">تحديث</string>
|
||||
<string name="module_update_available">تحديث</string>
|
||||
<string name="module_updated">محدث</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="close">إغلاق</string>
|
||||
<string name="force_stop_app">ايقاف اجباري</string>
|
||||
<string name="restart_app">إعادة تشغيل</string>
|
||||
<string name="settings_amoled_mode">وضع AMOLED</string>
|
||||
<string name="settings_amoled_mode_summary">تمكين سمة سوداء نقية مفيدة لشاشات AMOLED لتقليل إجهاد العين وحفظ البطارية.</string>
|
||||
<string name="restart_required">مطلوب إعادة التشغيل</string>
|
||||
<string name="restart_app_message">يحتاج التطبيق إلى إعادة تشغيل حتى يصبح هذا التغيير ساري المفعول.</string>
|
||||
<string name="failed_to_update_sepolicy">فشل تحديث قواعد SELinux لـ %s</string>
|
||||
<string name="su_not_allowed">تعذر منح المستخدم الخارق الوصول إلى %s</string>
|
||||
<string name="module_changelog">سِجل التغييرات</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_edit">تحرير القالب</string>
|
||||
<string name="app_profile_template_id">الرقم التعريفي</string>
|
||||
<string name="app_profile_template_id_invalid">معرف القالب غير صالح</string>
|
||||
<string name="app_profile_template_name">الاسم</string>
|
||||
<string name="app_profile_template_description">الوصف</string>
|
||||
<string name="app_profile_template_save">حفظ</string>
|
||||
<string name="app_profile_template_delete">حذف</string>
|
||||
<string name="app_profile_template_view">عرض القالب</string>
|
||||
<string name="app_profile_template_readonly">للقراءة فقط</string>
|
||||
<string name="app_profile_template_id_exist">معرف القالب موجود بالفعل!</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">فشل في منح صلاحية الجذر!</string>
|
||||
<string name="action">إجراء</string>
|
||||
<string name="webui">WebUI</string>
|
||||
<string name="open">فتح</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استخدم هذا الخيار فقط بعد انتهاء التحديث.
|
||||
\nأستمرار؟</string>
|
||||
<string name="install_next">التالي</string>
|
||||
<string name="select_file_tip">يوصي بصورة القسم %1$s</string>
|
||||
<string name="select_kmi">تحديد KMI</string>
|
||||
<string name="shrink_sparse_image">تقليص ال sparse image</string>
|
||||
<string name="shrink_sparse_image_message">غيّر حجم ال sparse image التي توجد بها الوحدة إلى حجمها الحقيقي. يُرجى ملاحظة أن هذا قد يُسبب خللاً في عمل الوحدة، لذا يُرجى استخدامها فقط عند الضرورة (مثل النسخ الاحتياطي).</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_message">الغاء تثبيت KernelSU Next مؤقتا، استعادة الحالة الاصلية بعد اعادة التشغيل.</string>
|
||||
<string name="settings_uninstall_permanent_message">إلغاء تثبيت KernelSU Next (الرووت و جميع الوحدات) بشكلٍ كامل و نهائياً.</string>
|
||||
<string name="settings_restore_stock_image_message">استعادة صورة المصنع الاصلية (لو النسخة الاحتياطية موجودة)، تستخدم عادةً قبل OTA; ان كنت تريد الغاء تثبيت KernelSU Next، الرجاء استخدام \"الغاء التثبيت نهائياً\".</string>
|
||||
<string name="flashing">جاري الحرق.</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="log_saved">تم حفظ السجلات</string>
|
||||
<string name="send_log">مشاركة السجلات</string>
|
||||
<string name="settings_disable_su">تعطيل وضع التكامل مع المستخدم الخارق</string>
|
||||
<string name="settings_disable_su_summary">تعطيل قدرة اي تطبيق على الحصول على إذن الرووت عن طريق الامر su (عمليات الرووت الحالية لن تتاثر).</string>
|
||||
<string name="settings_language">اللغة</string>
|
||||
<string name="system_default">افتراضيات النظام</string>
|
||||
<string name="settings_legacyui">استخدام واجهة المستخدم القديمة</string>
|
||||
<string name="settings_legacyui_summary">التبديل الى شكل واجهة المستخدم السابقة.</string>
|
||||
<string name="settings_banner">تمكين اللافتات</string>
|
||||
<string name="settings_banner_summary">اظهار خلفيات اللافتات للوحدات.</string>
|
||||
<string name="use_webuix">إستخدام WebUI X</string>
|
||||
<string name="use_webuix_summary">إستخدام WebUI X بدلاً من WebUI، التي تدعم المزيد من ال APIs.</string>
|
||||
<string name="use_webuix_eruda">حقن Eruda في WebUI X</string>
|
||||
<string name="use_webuix_eruda_summary">حقن وحدة التصحيح في WebUI X لجعل تصحيح الأخطاء أسهل. يتطلب تصحيح أخطاء الويب ن يكون مفعل.</string>
|
||||
<string name="customization">تخصيص</string>
|
||||
<string name="developer">المطور</string>
|
||||
<string name="sucompat_disabled">تعطيل العرض</string>
|
||||
<string name="zygisk_required">Zygisk مطلوب</string>
|
||||
<string name="zygisk_status">حقن Zygisk</string>
|
||||
<string name="home_superuser_count_singular">مستخدمون خارقون</string>
|
||||
<string name="home_superuser_count_plural">مستخدم خارق</string>
|
||||
<string name="home_module_count_singular">وحدة</string>
|
||||
<string name="home_module_count_plural">وحدات</string>
|
||||
<string name="module_backup_message">نسخ احتياطي للوحدات المثبتة حالياً.</string>
|
||||
<string name="module_sort_enabled_first">Sort (Enabled first)</string>
|
||||
<string name="module_sort_action_first">Sort (Action first)</string>
|
||||
<string name="module_sort_webui_first">Sort (WebUI first)</string>
|
||||
<string name="settings_global_namespace_mode">Global Namespace Mode</string>
|
||||
<string name="settings_global_namespace_mode_summary">All root sessions use the global mount namespace</string>
|
||||
</resources>
|
||||
@@ -1,197 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="issue_report_title">تواجه مشكلة؟</string>
|
||||
<string name="issue_report_body">حصل خطأ ما او لديك تعليقات؟</string>
|
||||
<string name="issue_report_body_2">بلغنا بها على الفور!</string>
|
||||
<string name="issue_report_github">ابلغنا عن طريق Github</string>
|
||||
<string name="issue_report_telegram">تواصل من خلال التليجرام</string>
|
||||
<string name="issue_report_github_link">https://github.com/KernelSU-Next/KernelSU-Next/issues</string>
|
||||
<string name="issue_report_telegram_link">https://t.me/ksunext</string>
|
||||
<string name="confirm">تأكيد</string>
|
||||
<string name="app_name" translatable="false">KernelSU Next</string>
|
||||
<string name="home">الصفحة الرئيسية</string>
|
||||
<string name="home_not_installed">غير مثبت</string>
|
||||
<string name="home_click_to_install">انقر للتثبيت</string>
|
||||
<string name="home_working">يعمل</string>
|
||||
<string name="home_working_version">الإصدار: %d</string>
|
||||
<string name="home_superuser_count">عدد التطبيقات ذات صلاحيات الروت: %d</string>
|
||||
<string name="home_module_count">عدد الإضافات: %d</string>
|
||||
<string name="home_failure">توقيع KSU Next v2 غير موجود [ !KSU_NEXT || != size/hash ]</string>
|
||||
<string name="home_failure_tip">اطلب من مطور بتهيئة KSU Next لجهازك</string>
|
||||
<string name="home_kernel">اصدار Kernel</string>
|
||||
<string name="enabled">مفعل</string>
|
||||
<string name="disabled">معطل</string>
|
||||
<string name="susfs_supported">مدعوم</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_android">إصدار الأندرويد</string>
|
||||
<string name="home_manager_version">اصدار مدير الروت</string>
|
||||
<string name="home_selinux_status">حالة SELinux</string>
|
||||
<string name="selinux_status_disabled">معطل</string>
|
||||
<string name="selinux_status_enforcing">مفعل</string>
|
||||
<string name="selinux_status_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_empty">لا توجد إضافات مثبته</string>
|
||||
<string name="module">الإضافة</string>
|
||||
<string name="module_install_prompt_with_name">الإضافة/ات التالية سيتم تثبيتها: %1$s</string>
|
||||
<string name="module_sort_a_to_z">تصنيف (أ-ي)</string>
|
||||
<string name="module_sort_z_to_a">تصنيف (ي-أ)</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">إعادة التشغيل إلى وضع 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_restore_confirm">هل انت متأكد من استرجاع الإضافة؟ %s؟</string>
|
||||
<string name="module_restore_success">%s تم استعادة</string>
|
||||
<string name="module_restore_failed">فشل استعادة: %s</string>
|
||||
<string name="module_version">الإصدار</string>
|
||||
<string name="module_author">المطور</string>
|
||||
<string name="module_id">ID</string>
|
||||
<string name="module_version_code">كود الإصدار</string>
|
||||
<string name="module_update_json">UpdateJson</string>
|
||||
<string name="module_update_json_empty">فارغ</string>
|
||||
<string name="enable_developer_options">تفعيل خيارات المطور</string>
|
||||
<string name="enable_developer_options_summary">عرض الإعدادات المخفية ومعلومات التصحيح ذات الصلة فقط للمطورين.</string>
|
||||
<string name="module_overlay_fs_not_available">الإضافات غير متاحة حيث أن OverlayFS معطلة بواسطة النواة.</string>
|
||||
<string name="refresh">تحديث</string>
|
||||
<string name="show_system_apps">عرض تطبيقات النظام</string>
|
||||
<string name="hide_system_apps">إخفاء تطبيقات النظام</string>
|
||||
<string name="export_log">تصدير السجلات</string>
|
||||
<string name="safe_mode">وضع الأمان</string>
|
||||
<string name="reboot_to_apply">إعادة التشغيل لتطبيق التغييرات</string>
|
||||
<string name="module_magisk_conflict">الإضافات غير متاحة بسبب تعارض مع Magisk!</string>
|
||||
<string name="home_mount_system">نظام الإضافات</string>
|
||||
<string name="home_magic_mount">Mount السحري</string>
|
||||
<string name="home_overlayfs_mount">OverlayFS</string>
|
||||
<string name="unavailable">غير متاح</string>
|
||||
<string name="use_overlay_fs">استخدام OverlayFS</string>
|
||||
<string name="use_overlay_fs_summary">التبديل بين استخدام OverlayFS أو Mount السحري لنظام Mount الخاص بـ KernelSU Next.</string>
|
||||
<string name="reboot_required">إعادة التشغيل مطلوبة</string>
|
||||
<string name="reboot_message">ستدخل التغييرات حيز التنفيذ بعد إعادة تشغيل النظام. هل تريد إعادة التشغيل الآن؟</string>
|
||||
<string name="module_restore">استعادة الإضافة</string>
|
||||
<string name="module_restore_message">استعادة الإضافات من النسخة الاحتياطية الأخيرة.</string>
|
||||
<string name="backup_restore">نسخ احتياطي واستعادة</string>
|
||||
<string name="module_backup">نسخ احتياطي للإضافة</string>
|
||||
<string name="module_backup_message">نسخ احتياطي للإضافات المثبتة حاليًا.</string>
|
||||
<string name="allowlist_restore">استعادة القائمة المسموح بها</string>
|
||||
<string name="allowlist_restore_message">استعادة القائمة المسموح بها من النسخة الاحتياطية الأخيرة.</string>
|
||||
<string name="allowlist_backup">نسخ احتياطي للقائمة المسموح بها</string>
|
||||
<string name="allowlist_backup_message">نسخ احتياطي للقائمة المسموح بها المكونة حاليًا.</string>
|
||||
<string name="warning">تحذير</string>
|
||||
<string name="warning_message">هذه الميزة لا تزال في مرحلة البيتا وتحت التطوير. يرجى التأكد من عمل نسخة احتياطية من الإضافات الخاصة بك قبل المتابعة. استخدم هذه الميزة فقط إذا كنت تفهم المخاطر المحتملة. أكمل بحذر.</string>
|
||||
<string name="proceed">تابع</string>
|
||||
<string name="cancel">إلغاء</string>
|
||||
<string name="later">لاحقًا</string>
|
||||
<string name="home_next_kernelsu">🔥 الإصدار التالي</string>
|
||||
<string name="home_next_kernelsu_repo">https://github.com/KernelSU-Next/KernelSU-Next</string>
|
||||
<string name="home_next_kernelsu_body">فرع تجريبي قادم. تحقق منه على GitHub!</string>
|
||||
<string name="home_experimental_kernelsu">⚠️ تجريبي!</string>
|
||||
<string name="home_experimental_kernelsu_repo">127.0.0.1</string>
|
||||
<string name="home_experimental_kernelsu_body">KernelSU Next هو إصدار غير رسمي دائمًا تحت التطوير التجريبي النشط. يتم تقديمه كما هو، دون ضمانات للاستقرار أو الأداء أو الموثوقية.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_1"> • استخدم على مسؤوليتك: قد تحدث أعطال أو سلوك غير متوقع أو مشاكل في النظام.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_2"> • لا ضمان: المطورون غير مسؤولين عن أي فقدان للبيانات أو تلف النظام أو عواقب أخرى ناتجة عن استخدامه.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_3"> • لأغراض الاختبار فقط: مخصص للمستخدمين الذين يفهمون المخاطر ويشعرون بالراحة في حل المشكلات.</string>
|
||||
<string name="about_source_code"><![CDATA[عرض الشيفرة المصدرية في %1$s]]></string>
|
||||
<string name="profile" translatable="false">ملف التطبيق</string>
|
||||
<string name="profile_default">افتراضي</string>
|
||||
<string name="profile_template">قالب</string>
|
||||
<string name="profile_custom">مخصص</string>
|
||||
<string name="profile_name">اسم الملف الشخصي</string>
|
||||
<string name="profile_namespace">مساحة التثبيت</string>
|
||||
<string name="profile_namespace_inherited">موروث</string>
|
||||
<string name="profile_namespace_global">عالمي</string>
|
||||
<string name="profile_namespace_individual">فردي</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">إصدار KernelSU Next الحالي %1$d منخفض جدًا لكي يعمل المدير بشكل صحيح. يرجى الترقية إلى الإصدار %2$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">تعطيل خطافات kprobe التي أنشأها ksu، وبدلاً من ذلك، تفعيل الخطافات غير الكروب المدمجة، مما ينفذ نفس الوظائف التي ستطبق على نواة غير GKI، والتي لا تدعم kprobe.</string>
|
||||
<string name="profile_umount_modules_summary">تفعيل هذا الخيار سيسمح لـ KernelSU Next باستعادة أي ملفات معدلة بواسطة الإضافات لهذا التطبيق.</string>
|
||||
<string name="profile_selinux_domain">النطاق</string>
|
||||
<string name="profile_selinux_rules">القواعد</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="close">إغلاق</string>
|
||||
<string name="force_stop_app">إيقاف إجباري</string>
|
||||
<string name="restart_app">إعادة تشغيل</string>
|
||||
<string name="failed_to_update_sepolicy">فشل في تحديث قواعد SELinux لـ: %s</string>
|
||||
<string name="su_not_allowed">لا يُسمح بمنح صلاحيات المستخدم الخارق لـ: %s</string>
|
||||
<string name="module_changelog">سجل التغييرات</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_edit">تحرير القالب</string>
|
||||
<string name="app_profile_template_id">ID</string>
|
||||
<string name="app_profile_template_id_invalid">معرف القالب غير صالح</string>
|
||||
<string name="app_profile_template_name">الاسم</string>
|
||||
<string name="app_profile_template_description">الوصف</string>
|
||||
<string name="app_profile_template_save">حفظ</string>
|
||||
<string name="app_profile_template_delete">حذف</string>
|
||||
<string name="app_profile_template_view">عرض القالب</string>
|
||||
<string name="app_profile_template_readonly">للقراءة فقط</string>
|
||||
<string name="app_profile_template_id_exist">معرف القالب موجود بالفعل!</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">فشل في منح صلاحيات الجذر!</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="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="install_next">التالي</string>
|
||||
<string name="select_file_tip">%1$s Partition موصى به</string>
|
||||
<string name="select_kmi">اختر KMI</string>
|
||||
<string name="shrink_sparse_image">تقليل حجم partition المتناثرة</string>
|
||||
<string name="shrink_sparse_image_message">إعادة حجم partition حيث توجد الإضافة إلى حجمها الفعلي. لاحظ أن هذا قد يتسبب في عمل الإضافة بشكل غير طبيعي، لذا يرجى استخدامه فقط عند الضرورة (مثل النسخ الاحتياطي).</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_message">إلغاء تثبيت KernelSU Next مؤقتًا، استعادة الحالة الأصلية بعد إعادة التشغيل التالية.</string>
|
||||
<string name="settings_uninstall_permanent_message">إلغاء تثبيت KernelSU Next (الجذر وجميع الإضافات) بالكامل ودائمًا.</string>
|
||||
<string name="settings_restore_stock_image_message">استعادة الصورة الأصلية (إذا كانت النسخة الاحتياطية موجودة)، عادة ما تستخدم قبل OTA؛ إذا كنت بحاجة إلى إلغاء تثبيت KernelSU Next، يرجى استخدام "إلغاء التثبيت نهائيًا".</string>
|
||||
<string name="flashing">تثبيت</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="log_saved">تم حفظ السجلات</string>
|
||||
<string name="send_log">مشاركة السجلات</string>
|
||||
<string name="settings_disable_su">تعطيل توافق su</string>
|
||||
<string name="settings_disable_su_summary">تعطيل مؤقت لقدرة أي تطبيق على الحصول على صلاحيات الجذر عبر أمر su (لن تتأثر العمليات الجذرية الحالية).</string>
|
||||
<string name="settings_language">اللغة</string>
|
||||
</resources>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user