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
462 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
6f6b797e22 | ||
|
|
d5f25590a5 | ||
|
|
bf81c9a17b | ||
|
|
5cec5aa656 | ||
|
|
02dbb7d0f7 | ||
|
|
c5aa6ffffc | ||
|
|
335ea3190f | ||
|
|
35c98aee76 | ||
|
|
dc992818de | ||
|
|
dfea38e0f2 | ||
|
|
6452427b6c | ||
|
|
feb3c47bdc | ||
|
|
de44373599 | ||
|
|
4bd7e7cc9f | ||
|
|
39014fb89c | ||
|
|
eeecbf255a | ||
|
|
11e4729519 | ||
|
|
f3093eace6 | ||
|
|
3a7edf48bf | ||
|
|
de189fe426 | ||
|
|
30835787c9 | ||
|
|
9fc1bd1876 | ||
|
|
a223781fae | ||
|
|
2a154c1985 | ||
|
|
110b8e92dc | ||
|
|
3b87014bf9 | ||
|
|
bd0bcc4337 | ||
|
|
916f8151ea | ||
|
|
59ca8fa2c2 | ||
|
|
238f2ee008 | ||
|
|
7eaf37bc4a | ||
|
|
022ee4bb1c | ||
|
|
d209dc087e | ||
|
|
d79ea50c6e | ||
|
|
d8944d641c | ||
|
|
1c9705fdd0 | ||
|
|
f369297be9 | ||
|
|
a68b4fe7e0 | ||
|
|
52399f7fd1 | ||
|
|
7218a504c9 | ||
|
|
9d5999c8c3 | ||
|
|
204805852c | ||
|
|
36b42e611e | ||
|
|
c50bbd32aa | ||
|
|
d2f6d00327 | ||
|
|
c9e4c8e186 | ||
|
|
3a6c30fba1 | ||
|
|
d5d4304120 | ||
|
|
4eac2f783e | ||
|
|
2cc765ee0a | ||
|
|
453524d382 | ||
|
|
fccc3db5c5 | ||
|
|
7a08683c74 | ||
|
|
2f3f444905 | ||
|
|
7746569fe9 | ||
|
|
3a601f86a5 | ||
|
|
e7dab63837 | ||
|
|
487e7d0012 | ||
|
|
ee71a992a3 | ||
|
|
195e1ba494 | ||
|
|
a0e4d01269 | ||
|
|
b8b0dc724a | ||
|
|
39777f301d | ||
|
|
f20662c6b6 | ||
|
|
3532f20b2f | ||
|
|
14218e81be | ||
|
|
d104250770 | ||
|
|
6100df6f8c | ||
|
|
e597bd1c66 | ||
|
|
0038ba6566 | ||
|
|
7bebb2a461 | ||
|
|
f3fee49f8c | ||
|
|
e8c080ba09 | ||
|
|
785d8143fb | ||
|
|
cd0031e8a5 | ||
|
|
c1273d35f4 | ||
|
|
15371bb1d6 | ||
|
|
097451d578 | ||
|
|
85866848ea | ||
|
|
d05d16c15d | ||
|
|
bb6c20339c | ||
|
|
af012ce349 | ||
|
|
43d30c8f2b | ||
|
|
a89985f33e | ||
|
|
42896dfab1 | ||
|
|
36111f4b89 | ||
|
|
74171a2930 | ||
|
|
c40bfd694c | ||
|
|
aa22fd880d | ||
|
|
fe9578433c | ||
|
|
a0278b1e45 | ||
|
|
0c7ba4dc6e | ||
|
|
b98c531ff8 | ||
|
|
373025248d | ||
|
|
60d2ad39d6 | ||
|
|
b807b38892 | ||
|
|
5ca5e2b027 | ||
|
|
608e949e23 | ||
|
|
c9dd12d50b | ||
|
|
7319dd25a3 | ||
|
|
fdd307fe8b | ||
|
|
b6c8203a97 | ||
|
|
b2a8fb66d3 | ||
|
|
74f55dd807 | ||
|
|
8560f35c8b | ||
|
|
b0f01cf7ff | ||
|
|
8ae0192b47 | ||
|
|
f247a6d5d8 | ||
|
|
d85bff2943 | ||
|
|
f5ac0f3589 | ||
|
|
a2fcd157a2 | ||
|
|
70ce97660e | ||
|
|
67967a8251 | ||
|
|
b25a21770d | ||
|
|
bbb05b292a | ||
|
|
8696ed8f36 | ||
|
|
682f93667b | ||
|
|
00cab2209e | ||
|
|
b6fcea9277 | ||
|
|
623f5e3f64 | ||
|
|
b18f89b434 | ||
|
|
2a152fdb22 | ||
|
|
dbadad1027 | ||
|
|
fcc4f0d206 | ||
|
|
c20fe6c886 | ||
|
|
19a15a71eb | ||
|
|
c446ee76f5 | ||
|
|
5435992911 | ||
|
|
c2ae844f65 | ||
|
|
8832532f09 | ||
|
|
cba275cde2 | ||
|
|
4401e28f57 | ||
|
|
ece6e3f694 | ||
|
|
d4db74f0fb | ||
|
|
e931792a4f | ||
|
|
3876b6b474 | ||
|
|
ae36e2085c | ||
|
|
3a8f4a2596 | ||
|
|
87fc6b2784 | ||
|
|
25b57204ef | ||
|
|
4ed362bea4 | ||
|
|
a2976fd926 | ||
|
|
b2bcc93f9a | ||
|
|
57b8dd54c5 | ||
|
|
ef32f3f9d2 | ||
|
|
4ac264135d | ||
|
|
a05b790da6 |
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -60,8 +60,9 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
- Device:
|
- Device:
|
||||||
- OS Version:
|
- OS Version:
|
||||||
- KernelSU Version:
|
|
||||||
- Kernel Version:
|
- Kernel Version:
|
||||||
|
- KSUN Driver Version:
|
||||||
|
- KSUN Manager Version:
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
|
||||||
- name: Feature Request
|
|
||||||
url: https://t.me/ksunext_discussions
|
|
||||||
about: "We accept external Feature Requests, see this link for more details."
|
|
||||||
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:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build ${{ inputs.version_name }}
|
name: Build ${{ inputs.version_name }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Maximize build space
|
- name: Maximize build space
|
||||||
uses: easimon/maximize-build-space@master
|
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
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: android12-5.10
|
version: android12-5.10
|
||||||
version_name: android12-5.10.226
|
version_name: android12-5.10.236
|
||||||
tag: android12-5.10-2024-11
|
tag: android12-5.10-2025-05
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
patch_path: "5.10"
|
patch_path: "5.10"
|
||||||
debug: true
|
debug: true
|
||||||
build-debug-kernel-a13:
|
build-debug-kernel-a13:
|
||||||
@@ -17,11 +17,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- version: "5.10"
|
- version: "5.10"
|
||||||
sub_level: 223
|
sub_level: 236
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
- version: "5.15"
|
- version: "5.15"
|
||||||
sub_level: 167
|
sub_level: 180
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: android13-${{ matrix.version }}
|
version: android13-${{ matrix.version }}
|
||||||
@@ -34,11 +34,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- version: "5.15"
|
- version: "5.15"
|
||||||
sub_level: 167
|
sub_level: 180
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
- version: "6.1"
|
- version: "6.1"
|
||||||
sub_level: 115
|
sub_level: 138
|
||||||
os_patch_level: 2024-12
|
os_patch_level: 2025-06
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: android14-${{ matrix.version }}
|
version: android14-${{ matrix.version }}
|
||||||
@@ -51,8 +51,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- version: "6.6"
|
- version: "6.6"
|
||||||
sub_level: 57
|
sub_level: 89
|
||||||
os_patch_level: 2024-12
|
os_patch_level: 2025-06
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: android15-${{ matrix.version }}
|
version: android15-${{ matrix.version }}
|
||||||
|
|||||||
12
.github/workflows/build-kernel-a12.yml
vendored
12
.github/workflows/build-kernel-a12.yml
vendored
@@ -21,12 +21,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- sub_level: 209
|
|
||||||
os_patch_level: 2024-05
|
|
||||||
- sub_level: 218
|
- sub_level: 218
|
||||||
os_patch_level: 2024-08
|
os_patch_level: 2024-08
|
||||||
- sub_level: 226
|
- sub_level: 226
|
||||||
os_patch_level: 2024-11
|
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
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
@@ -112,7 +114,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: android12-5.10
|
version: android12-5.10
|
||||||
version_name: android12-5.10.223
|
version_name: android12-5.10.236
|
||||||
tag: android12-5.10-2024-11
|
tag: android12-5.10-2025-05
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
patch_path: "5.10"
|
patch_path: "5.10"
|
||||||
32
.github/workflows/build-kernel-a13.yml
vendored
32
.github/workflows/build-kernel-a13.yml
vendored
@@ -21,9 +21,6 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 209
|
|
||||||
os_patch_level: 2024-05
|
|
||||||
- version: "5.10"
|
- version: "5.10"
|
||||||
sub_level: 210
|
sub_level: 210
|
||||||
os_patch_level: 2024-06
|
os_patch_level: 2024-06
|
||||||
@@ -36,9 +33,15 @@ jobs:
|
|||||||
- version: "5.10"
|
- version: "5.10"
|
||||||
sub_level: 223
|
sub_level: 223
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2024-11
|
||||||
- version: "5.15"
|
- version: "5.10"
|
||||||
sub_level: 148
|
sub_level: 228
|
||||||
os_patch_level: 2024-05
|
os_patch_level: 2025-01
|
||||||
|
- 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"
|
- version: "5.15"
|
||||||
sub_level: 149
|
sub_level: 149
|
||||||
os_patch_level: 2024-07
|
os_patch_level: 2024-07
|
||||||
@@ -51,6 +54,15 @@ jobs:
|
|||||||
- version: "5.15"
|
- version: "5.15"
|
||||||
sub_level: 167
|
sub_level: 167
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2024-11
|
||||||
|
- version: "5.15"
|
||||||
|
sub_level: 170
|
||||||
|
os_patch_level: 2025-01
|
||||||
|
- version: "5.15"
|
||||||
|
sub_level: 178
|
||||||
|
os_patch_level: 2025-03
|
||||||
|
- version: "5.15"
|
||||||
|
sub_level: 180
|
||||||
|
os_patch_level: 2025-05
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
@@ -137,11 +149,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- version: "5.10"
|
- version: "5.10"
|
||||||
sub_level: 223
|
sub_level: 236
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
- version: "5.15"
|
- version: "5.15"
|
||||||
sub_level: 167
|
sub_level: 180
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: android13-${{ matrix.version }}
|
version: android13-${{ matrix.version }}
|
||||||
|
|||||||
41
.github/workflows/build-kernel-a14.yml
vendored
41
.github/workflows/build-kernel-a14.yml
vendored
@@ -21,9 +21,6 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 148
|
|
||||||
os_patch_level: 2024-05
|
|
||||||
- version: "5.15"
|
- version: "5.15"
|
||||||
sub_level: 149
|
sub_level: 149
|
||||||
os_patch_level: 2024-06
|
os_patch_level: 2024-06
|
||||||
@@ -39,9 +36,15 @@ jobs:
|
|||||||
- version: "5.15"
|
- version: "5.15"
|
||||||
sub_level: 167
|
sub_level: 167
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2024-11
|
||||||
- version: "6.1"
|
- version: "5.15"
|
||||||
sub_level: 75
|
sub_level: 170
|
||||||
os_patch_level: 2024-05
|
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
|
||||||
- version: "6.1"
|
- version: "6.1"
|
||||||
sub_level: 78
|
sub_level: 78
|
||||||
os_patch_level: 2024-06
|
os_patch_level: 2024-06
|
||||||
@@ -63,6 +66,24 @@ jobs:
|
|||||||
- version: "6.1"
|
- version: "6.1"
|
||||||
sub_level: 115
|
sub_level: 115
|
||||||
os_patch_level: 2024-12
|
os_patch_level: 2024-12
|
||||||
|
- version: "6.1"
|
||||||
|
sub_level: 118
|
||||||
|
os_patch_level: 2025-01
|
||||||
|
- 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
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
@@ -149,11 +170,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- version: "5.15"
|
- version: "5.15"
|
||||||
sub_level: 167
|
sub_level: 180
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
- version: "6.1"
|
- version: "6.1"
|
||||||
sub_level: 115
|
sub_level: 138
|
||||||
os_patch_level: 2024-12
|
os_patch_level: 2025-06
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: android14-${{ matrix.version }}
|
version: android14-${{ matrix.version }}
|
||||||
|
|||||||
22
.github/workflows/build-kernel-a15.yml
vendored
22
.github/workflows/build-kernel-a15.yml
vendored
@@ -36,6 +36,24 @@ jobs:
|
|||||||
- version: "6.6"
|
- version: "6.6"
|
||||||
sub_level: 57
|
sub_level: 57
|
||||||
os_patch_level: 2024-12
|
os_patch_level: 2024-12
|
||||||
|
- version: "6.6"
|
||||||
|
sub_level: 58
|
||||||
|
os_patch_level: 2025-01
|
||||||
|
- version: "6.6"
|
||||||
|
sub_level: 66
|
||||||
|
os_patch_level: 2025-02
|
||||||
|
- version: "6.6"
|
||||||
|
sub_level: 77
|
||||||
|
os_patch_level: 2025-03
|
||||||
|
- version: "6.6"
|
||||||
|
sub_level: 82
|
||||||
|
os_patch_level: 2025-04
|
||||||
|
- version: "6.6"
|
||||||
|
sub_level: 87
|
||||||
|
os_patch_level: 2025-05
|
||||||
|
- version: "6.6"
|
||||||
|
sub_level: 89
|
||||||
|
os_patch_level: 2025-06
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
@@ -122,8 +140,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- version: "6.6"
|
- version: "6.6"
|
||||||
sub_level: 57
|
sub_level: 89
|
||||||
os_patch_level: 2024-12
|
os_patch_level: 2025-06
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: android15-${{ matrix.version }}
|
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:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- version: "android12-5.10"
|
- version: "android12-5.10"
|
||||||
sub_level: 226
|
sub_level: 236
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
- version: "android13-5.10"
|
- version: "android13-5.10"
|
||||||
sub_level: 223
|
sub_level: 236
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
- version: "android13-5.15"
|
- version: "android13-5.15"
|
||||||
sub_level: 167
|
sub_level: 180
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
- version: "android14-5.15"
|
- version: "android14-5.15"
|
||||||
sub_level: 167
|
sub_level: 180
|
||||||
os_patch_level: 2024-11
|
os_patch_level: 2025-05
|
||||||
- version: "android14-6.1"
|
- version: "android14-6.1"
|
||||||
sub_level: 115
|
sub_level: 138
|
||||||
os_patch_level: 2024-12
|
os_patch_level: 2025-06
|
||||||
- version: "android15-6.6"
|
- version: "android15-6.6"
|
||||||
sub_level: 57
|
sub_level: 89
|
||||||
os_patch_level: 2024-12
|
os_patch_level: 2025-06
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
uses: ./.github/workflows/gki-kernel.yml
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.version }}
|
version: ${{ matrix.version }}
|
||||||
|
|||||||
107
.github/workflows/build-manager-ci.yml
vendored
107
.github/workflows/build-manager-ci.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Build Manager
|
name: Build Manager CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -13,6 +13,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "next" ]
|
branches: [ "next" ]
|
||||||
paths:
|
paths:
|
||||||
|
- '.github/workflows/build-manager-ci.yml'
|
||||||
- 'manager/**'
|
- 'manager/**'
|
||||||
workflow_call:
|
workflow_call:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -27,46 +28,36 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- target: aarch64-linux-android
|
- os: ubuntu-latest
|
||||||
os: ubuntu-latest
|
|
||||||
uses: ./.github/workflows/susfsd.yml
|
uses: ./.github/workflows/susfsd.yml
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
|
||||||
os: ${{ matrix.os }}
|
os: ${{ matrix.os }}
|
||||||
|
|
||||||
build-ksud_overlayfs:
|
build-ksud:
|
||||||
needs: build-susfsd
|
needs: build-susfsd
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- target: aarch64-linux-android
|
- target: aarch64-linux-android
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
uses: ./.github/workflows/ksud_overlayfs.yml
|
- target: armv7-linux-androideabi
|
||||||
with:
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
os: ${{ matrix.os }}
|
|
||||||
|
|
||||||
build-ksud_magic:
|
|
||||||
needs: build-ksud_overlayfs
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- target: aarch64-linux-android
|
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
uses: ./.github/workflows/ksud_magic.yml
|
- target: x86_64-linux-android
|
||||||
|
os: ubuntu-latest
|
||||||
|
uses: ./.github/workflows/ksud.yml
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
os: ${{ matrix.os }}
|
os: ${{ matrix.os }}
|
||||||
|
|
||||||
build-manager:
|
build-manager:
|
||||||
needs: build-ksud_magic
|
needs: build-ksud
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./manager
|
working-directory: ./manager
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -80,7 +71,7 @@ jobs:
|
|||||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Write key
|
- name: Write Key
|
||||||
run: |
|
run: |
|
||||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||||
{
|
{
|
||||||
@@ -101,40 +92,87 @@ jobs:
|
|||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
- name: Setup Android SDK
|
- name: Download susfsd
|
||||||
uses: android-actions/setup-android@v3
|
|
||||||
|
|
||||||
- name: Download arm64 susfsd
|
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: susfsd-aarch64-linux-android
|
name: susfsd-linux-android
|
||||||
path: .
|
path: .
|
||||||
|
|
||||||
- name: Copy susfsd to app jniLibs
|
- name: Copy susfsd to app jniLibs
|
||||||
run: |
|
run: |
|
||||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
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
|
- name: Download arm64 ksud_overlayfs
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ksud_overlayfs-aarch64-linux-android
|
name: ksud_overlayfs-aarch64-linux-android
|
||||||
path: ksud_overlayfs
|
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
|
- name: Copy ksud_overlayfs to app jniLibs
|
||||||
run: |
|
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
|
||||||
|
|
||||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
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
|
- name: Download arm64 ksud_magic
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ksud_magic-aarch64-linux-android
|
name: ksud_magic-aarch64-linux-android
|
||||||
path: ksud_magic
|
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
|
- name: Copy ksud_magic to app jniLibs
|
||||||
run: |
|
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
|
||||||
|
|
||||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
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
|
- name: Build with Gradle
|
||||||
run: |
|
run: |
|
||||||
@@ -148,19 +186,19 @@ jobs:
|
|||||||
chmod +x gradlew
|
chmod +x gradlew
|
||||||
./gradlew clean assembleRelease
|
./gradlew clean assembleRelease
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload Build Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: manager
|
name: Manager
|
||||||
path: manager/app/build/outputs/apk/release/*.apk
|
path: manager/app/build/outputs/apk/release/*.apk
|
||||||
|
|
||||||
- name: Upload mappings
|
- name: Upload Mappings
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "mappings"
|
name: Mappings
|
||||||
path: "manager/app/build/outputs/mapping/release/"
|
path: manager/app/build/outputs/mapping/release/
|
||||||
|
|
||||||
- name: Bot session cache
|
- name: Bot Session Cache
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||||
id: bot_session_cache
|
id: bot_session_cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -168,7 +206,7 @@ jobs:
|
|||||||
path: scripts/ksunextbot.session
|
path: scripts/ksunextbot.session
|
||||||
key: ${{ runner.os }}-bot-session
|
key: ${{ runner.os }}-bot-session
|
||||||
|
|
||||||
- name: Upload to telegram
|
- name: Upload to Telegram
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||||
env:
|
env:
|
||||||
API_ID: ${{ secrets.API_ID }}
|
API_ID: ${{ secrets.API_ID }}
|
||||||
@@ -187,4 +225,3 @@ jobs:
|
|||||||
pip3 install telethon
|
pip3 install telethon
|
||||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
222
.github/workflows/build-manager-spoofed.yml
vendored
Normal file
222
.github/workflows/build-manager-spoofed.yml
vendored
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
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:
|
||||||
|
build-lkm:
|
||||||
|
uses: ./.github/workflows/build-lkm.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
build-susfsd:
|
||||||
|
needs: build-lkm
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
uses: ./.github/workflows/susfsd.yml
|
||||||
|
with:
|
||||||
|
os: ${{ matrix.os }}
|
||||||
|
|
||||||
|
build-ksud:
|
||||||
|
needs: build-susfsd
|
||||||
|
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 }}
|
||||||
|
|
||||||
|
build-manager:
|
||||||
|
needs: build-ksud
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: Download susfsd
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
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: |
|
||||||
|
{
|
||||||
|
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
|
||||||
93
.github/workflows/build-manager.yml
vendored
93
.github/workflows/build-manager.yml
vendored
@@ -13,6 +13,7 @@ on:
|
|||||||
# pull_request:
|
# pull_request:
|
||||||
# branches: [ "next" ]
|
# branches: [ "next" ]
|
||||||
# paths:
|
# paths:
|
||||||
|
# - '.github/workflows/build-manager-ci.yml'
|
||||||
# - 'manager/**'
|
# - 'manager/**'
|
||||||
workflow_call:
|
workflow_call:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -27,46 +28,36 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- target: aarch64-linux-android
|
- os: ubuntu-latest
|
||||||
os: ubuntu-latest
|
|
||||||
uses: ./.github/workflows/susfsd.yml
|
uses: ./.github/workflows/susfsd.yml
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
|
||||||
os: ${{ matrix.os }}
|
os: ${{ matrix.os }}
|
||||||
|
|
||||||
build-ksud_overlayfs:
|
build-ksud:
|
||||||
needs: build-susfsd
|
needs: build-susfsd
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- target: aarch64-linux-android
|
- target: aarch64-linux-android
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
uses: ./.github/workflows/ksud_overlayfs.yml
|
- target: armv7-linux-androideabi
|
||||||
with:
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
os: ${{ matrix.os }}
|
|
||||||
|
|
||||||
build-ksud_magic:
|
|
||||||
needs: build-ksud_overlayfs
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- target: aarch64-linux-android
|
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
uses: ./.github/workflows/ksud_magic.yml
|
- target: x86_64-linux-android
|
||||||
|
os: ubuntu-latest
|
||||||
|
uses: ./.github/workflows/ksud.yml
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
os: ${{ matrix.os }}
|
os: ${{ matrix.os }}
|
||||||
|
|
||||||
build-manager:
|
build-manager:
|
||||||
needs: build-ksud_magic
|
needs: build-ksud
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./manager
|
working-directory: ./manager
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -80,7 +71,7 @@ jobs:
|
|||||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Write key
|
- name: Write Key
|
||||||
run: |
|
run: |
|
||||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||||
{
|
{
|
||||||
@@ -101,40 +92,75 @@ jobs:
|
|||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
- name: Setup Android SDK
|
- name: Download susfsd
|
||||||
uses: android-actions/setup-android@v3
|
|
||||||
|
|
||||||
- name: Download arm64 susfsd
|
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: susfsd-aarch64-linux-android
|
name: susfsd-linux-android
|
||||||
path: .
|
path: .
|
||||||
|
|
||||||
- name: Copy susfsd to app jniLibs
|
- name: Copy susfsd to app jniLibs
|
||||||
run: |
|
run: |
|
||||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
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
|
- name: Download arm64 ksud_overlayfs
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ksud_overlayfs-aarch64-linux-android
|
name: ksud_overlayfs-aarch64-linux-android
|
||||||
path: ksud_overlayfs
|
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
|
- name: Copy ksud_overlayfs to app jniLibs
|
||||||
run: |
|
run: |
|
||||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
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
|
- name: Download arm64 ksud_magic
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ksud_magic-aarch64-linux-android
|
name: ksud_magic-aarch64-linux-android
|
||||||
path: ksud_magic
|
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
|
- name: Copy ksud_magic to app jniLibs
|
||||||
run: |
|
run: |
|
||||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
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
|
- name: Build with Gradle
|
||||||
run: |
|
run: |
|
||||||
@@ -148,19 +174,19 @@ jobs:
|
|||||||
chmod +x gradlew
|
chmod +x gradlew
|
||||||
./gradlew clean assembleRelease
|
./gradlew clean assembleRelease
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload Build Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: manager
|
name: Manager
|
||||||
path: manager/app/build/outputs/apk/release/*.apk
|
path: manager/app/build/outputs/apk/release/*.apk
|
||||||
|
|
||||||
- name: Upload mappings
|
- name: Upload Mappings
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "mappings"
|
name: Mappings
|
||||||
path: "manager/app/build/outputs/mapping/release/"
|
path: manager/app/build/outputs/mapping/release/
|
||||||
|
|
||||||
- name: Bot session cache
|
- name: Bot Session Cache
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||||
id: bot_session_cache
|
id: bot_session_cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -168,7 +194,7 @@ jobs:
|
|||||||
path: scripts/ksunextbot.session
|
path: scripts/ksunextbot.session
|
||||||
key: ${{ runner.os }}-bot-session
|
key: ${{ runner.os }}-bot-session
|
||||||
|
|
||||||
- name: Upload to telegram
|
- name: Upload to Telegram
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||||
env:
|
env:
|
||||||
API_ID: ${{ secrets.API_ID }}
|
API_ID: ${{ secrets.API_ID }}
|
||||||
@@ -187,4 +213,3 @@ jobs:
|
|||||||
pip3 install telethon
|
pip3 install telethon
|
||||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
43
.github/workflows/clippy.yml
vendored
43
.github/workflows/clippy.yml
vendored
@@ -6,13 +6,15 @@ on:
|
|||||||
- next
|
- next
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/clippy.yml'
|
- '.github/workflows/clippy.yml'
|
||||||
- 'userspace/ksud/**'
|
- 'userspace/ksud_magic/**'
|
||||||
|
- 'userspace/ksud_overlayfs/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- next
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/clippy.yml'
|
- '.github/workflows/clippy.yml'
|
||||||
- 'userspace/ksud/**'
|
- 'userspace/ksud_magic/**'
|
||||||
|
- 'userspace/ksud_overlayfs/**'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: '-Dwarnings'
|
RUSTFLAGS: '-Dwarnings'
|
||||||
@@ -21,17 +23,32 @@ jobs:
|
|||||||
clippy:
|
clippy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout Repository
|
||||||
- run: rustup update --force-non-host stable-x86_64-unknown-linux-gnu
|
uses: actions/checkout@v4
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
|
- name: Setup Rust
|
||||||
|
run: rustup update stable
|
||||||
|
|
||||||
|
- name: Setup Cross
|
||||||
|
run: RUSTFLAGS="" cargo install cross
|
||||||
|
|
||||||
|
- name: Cache ksud_overlayfs
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: userspace/ksud
|
workspaces: userspace/ksud_overlayfs
|
||||||
|
|
||||||
- name: Install cross
|
- name: Cache ksud_magic
|
||||||
run: |
|
uses: Swatinem/rust-cache@v2
|
||||||
cargo install cross --git https://github.com/cross-rs/cross --rev 66845c1
|
with:
|
||||||
|
workspaces: userspace/ksud_magic
|
||||||
|
|
||||||
- name: Run clippy
|
- name: Run Clippy
|
||||||
run: |
|
run: |
|
||||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target aarch64-linux-android --release
|
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target aarch64-linux-android --release
|
||||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android --release
|
cross clippy --manifest-path userspace/ksud_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
|
||||||
5
.github/workflows/gki-kernel.yml
vendored
5
.github/workflows/gki-kernel.yml
vendored
@@ -198,6 +198,9 @@ jobs:
|
|||||||
- name: Make working directory clean to avoid dirty
|
- name: Make working directory clean to avoid dirty
|
||||||
working-directory: android-kernel
|
working-directory: android-kernel
|
||||||
run: |
|
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!"
|
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
|
||||||
git config --global user.email "bot@kernelsu.org"
|
git config --global user.email "bot@kernelsu.org"
|
||||||
git config --global user.name "KernelSU-NextBot"
|
git config --global user.name "KernelSU-NextBot"
|
||||||
@@ -255,4 +258,4 @@ jobs:
|
|||||||
if: ${{ inputs.build_lkm == true }}
|
if: ${{ inputs.build_lkm == true }}
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.version }}-lkm
|
name: ${{ inputs.version }}-lkm
|
||||||
path: ./output/*_kernelsu.ko
|
path: ./output/*_kernelsu.ko
|
||||||
|
|||||||
84
.github/workflows/ksud.yml
vendored
Normal file
84
.github/workflows/ksud.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
name: Build ksud
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
target:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
os:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ubuntu-latest
|
||||||
|
pack_lkm:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
use_cache:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{ inputs.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Download Artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
|
- name: Prepare LKM Files
|
||||||
|
if: ${{ inputs.pack_lkm }}
|
||||||
|
run: |
|
||||||
|
cp android*-lkm/*_kernelsu.ko ./userspace/ksud_overlayfs/bin/aarch64/
|
||||||
|
cp android*-lkm/*_kernelsu.ko ./userspace/ksud_magic/bin/aarch64/
|
||||||
|
|
||||||
|
- name: Import susfsd Libraries
|
||||||
|
run: |
|
||||||
|
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
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: userspace/ksud_overlayfs
|
||||||
|
cache-targets: false
|
||||||
|
|
||||||
|
- name: Cache ksud_magic
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: userspace/ksud_magic
|
||||||
|
cache-targets: false
|
||||||
|
|
||||||
|
- name: Setup Cross
|
||||||
|
run: |
|
||||||
|
RUSTFLAGS="" cargo install cross --git https://github.com/cross-rs/cross --rev 66845c1 --force
|
||||||
|
|
||||||
|
- name: Build ksud
|
||||||
|
run: |
|
||||||
|
CROSS_NO_WARNINGS=0 cross build --target ${{ inputs.target }} --release --manifest-path ./userspace/ksud_overlayfs/Cargo.toml
|
||||||
|
CROSS_NO_WARNINGS=0 cross build --target ${{ inputs.target }} --release --manifest-path ./userspace/ksud_magic/Cargo.toml
|
||||||
|
|
||||||
|
- name: Upload ksud_overlayfs artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ksud_overlayfs-${{ inputs.target }}
|
||||||
|
path: userspace/ksud_overlayfs/target/**/release/ksud*
|
||||||
|
|
||||||
|
- name: Upload ksud_magic artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ksud_magic-${{ inputs.target }}
|
||||||
|
path: userspace/ksud_magic/target/**/release/ksud*
|
||||||
61
.github/workflows/ksud_magic.yml
vendored
61
.github/workflows/ksud_magic.yml
vendored
@@ -1,61 +0,0 @@
|
|||||||
name: Build ksud_magic
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
target:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
os:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: ubuntu-latest
|
|
||||||
pack_lkm:
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
use_cache:
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ${{ inputs.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- name: Prepare LKM fies
|
|
||||||
if: ${{ inputs.pack_lkm }}
|
|
||||||
run: |
|
|
||||||
cp android*-lkm/*_kernelsu.ko ./userspace/ksud_magic/bin/aarch64/
|
|
||||||
|
|
||||||
- name: Import susfsd lib
|
|
||||||
run: |
|
|
||||||
cp susfsd-aarch64-linux-android/arm64-v8a/susfsd ./userspace/ksud_magic/bin/aarch64/
|
|
||||||
|
|
||||||
- name: Setup rustup
|
|
||||||
run: |
|
|
||||||
rustup update stable
|
|
||||||
rustup target add x86_64-apple-darwin
|
|
||||||
rustup target add aarch64-apple-darwin
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: userspace/ksud_magic
|
|
||||||
cache-targets: false
|
|
||||||
|
|
||||||
- name: Install cross
|
|
||||||
run: |
|
|
||||||
cargo install cross --git https://github.com/cross-rs/cross --rev 66845c1 --force
|
|
||||||
|
|
||||||
- name: Build ksud_magic
|
|
||||||
run: CROSS_NO_WARNINGS=0 cross build --target ${{ inputs.target }} --release --manifest-path ./userspace/ksud_magic/Cargo.toml
|
|
||||||
|
|
||||||
- name: Upload ksud_magic artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-${{ inputs.target }}
|
|
||||||
path: userspace/ksud_magic/target/**/release/ksud*
|
|
||||||
61
.github/workflows/ksud_overlayfs.yml
vendored
61
.github/workflows/ksud_overlayfs.yml
vendored
@@ -1,61 +0,0 @@
|
|||||||
name: Build ksud_overlayfs
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
target:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
os:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: ubuntu-latest
|
|
||||||
pack_lkm:
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
use_cache:
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ${{ inputs.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- name: Prepare LKM fies
|
|
||||||
if: ${{ inputs.pack_lkm }}
|
|
||||||
run: |
|
|
||||||
cp android*-lkm/*_kernelsu.ko ./userspace/ksud_overlayfs/bin/aarch64/
|
|
||||||
|
|
||||||
- name: Import susfsd lib
|
|
||||||
run: |
|
|
||||||
cp susfsd-aarch64-linux-android/arm64-v8a/susfsd ./userspace/ksud_overlayfs/bin/aarch64/
|
|
||||||
|
|
||||||
- name: Setup rustup
|
|
||||||
run: |
|
|
||||||
rustup update stable
|
|
||||||
rustup target add x86_64-apple-darwin
|
|
||||||
rustup target add aarch64-apple-darwin
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: userspace/ksud_overlayfs
|
|
||||||
cache-targets: false
|
|
||||||
|
|
||||||
- name: Install cross
|
|
||||||
run: |
|
|
||||||
cargo install cross --git https://github.com/cross-rs/cross --rev 66845c1 --force
|
|
||||||
|
|
||||||
- name: Build ksud_overlayfs
|
|
||||||
run: CROSS_NO_WARNINGS=0 cross build --target ${{ inputs.target }} --release --manifest-path ./userspace/ksud_overlayfs/Cargo.toml
|
|
||||||
|
|
||||||
- name: Upload ksud_overlayfs artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-${{ inputs.target }}
|
|
||||||
path: userspace/ksud_overlayfs/target/**/release/ksud*
|
|
||||||
37
.github/workflows/release.yml
vendored
37
.github/workflows/release.yml
vendored
@@ -21,12 +21,21 @@ jobs:
|
|||||||
build-a15-kernel:
|
build-a15-kernel:
|
||||||
uses: ./.github/workflows/build-kernel-a15.yml
|
uses: ./.github/workflows/build-kernel-a15.yml
|
||||||
secrets: inherit
|
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:
|
release:
|
||||||
needs:
|
needs:
|
||||||
- build-manager
|
- build-manager
|
||||||
- build-a12-kernel
|
- build-a12-kernel
|
||||||
- build-a13-kernel
|
- build-a13-kernel
|
||||||
- build-a14-kernel
|
- build-a14-kernel
|
||||||
|
- build-a15-kernel
|
||||||
|
- build-wsa-kernel
|
||||||
|
- build-arcvm-kernel
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
@@ -40,6 +49,24 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
done
|
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
|
- name: Display structure of downloaded files
|
||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
@@ -47,10 +74,12 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
manager/*.apk
|
Manager/*.apk
|
||||||
android*-lkm/*_kernelsu.ko
|
android*-lkm/*_kernelsu.ko
|
||||||
AnyKernel3-*.zip
|
AnyKernel3-*.zip
|
||||||
boot-images-*/Image-*/*.img.gz
|
boot-images-*/Image-*/*.img.gz
|
||||||
ksud_magic-*
|
kernel-WSA*.zip
|
||||||
ksud_overlayfs-*
|
kernel-ARCVM*.zip
|
||||||
susfsd-*
|
ksud_magic*.zip
|
||||||
|
ksud_overlayfs*.zip
|
||||||
|
susfsd*.zip
|
||||||
13
.github/workflows/rustfmt.yml
vendored
13
.github/workflows/rustfmt.yml
vendored
@@ -6,13 +6,15 @@ on:
|
|||||||
- 'next'
|
- 'next'
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/rustfmt.yml'
|
- '.github/workflows/rustfmt.yml'
|
||||||
- 'userspace/ksud/**'
|
- 'userspace/ksud_magic/**'
|
||||||
|
- 'userspace/ksud_overlayfs/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'next'
|
- 'next'
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/rustfmt.yml'
|
- '.github/workflows/rustfmt.yml'
|
||||||
- 'userspace/ksud/**'
|
- 'userspace/ksud_magic/**'
|
||||||
|
- 'userspace/ksud_overlayfs/**'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
checks: write
|
checks: write
|
||||||
@@ -30,4 +32,9 @@ jobs:
|
|||||||
- uses: LoliGothick/rustfmt-check@master
|
- uses: LoliGothick/rustfmt-check@master
|
||||||
with:
|
with:
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
working-directory: userspace/ksud
|
working-directory: userspace/ksud_magic
|
||||||
|
|
||||||
|
- uses: LoliGothick/rustfmt-check@master
|
||||||
|
with:
|
||||||
|
token: ${{ github.token }}
|
||||||
|
working-directory: userspace/ksud_overlayfs
|
||||||
2
.github/workflows/shellcheck.yml
vendored
2
.github/workflows/shellcheck.yml
vendored
@@ -24,4 +24,4 @@ jobs:
|
|||||||
uses: ludeeus/action-shellcheck@2.0.0
|
uses: ludeeus/action-shellcheck@2.0.0
|
||||||
with:
|
with:
|
||||||
ignore_names: gradlew
|
ignore_names: gradlew
|
||||||
ignore_paths: ./userspace/ksud/src/installer.sh
|
ignore_paths: ./userspace/ksud_magic/src/installer.sh ./userspace/ksud_overlayfs/src/installer.sh
|
||||||
9
.github/workflows/susfsd.yml
vendored
9
.github/workflows/susfsd.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Build susfsd
|
name: Build susfsd aarch64
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "next" ]
|
branches: [ "next" ]
|
||||||
@@ -8,9 +8,6 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
target:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
os:
|
os:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
@@ -26,9 +23,9 @@ jobs:
|
|||||||
- name: Build susfsd
|
- name: Build susfsd
|
||||||
working-directory: ./userspace/susfsd
|
working-directory: ./userspace/susfsd
|
||||||
run: $ANDROID_NDK/ndk-build
|
run: $ANDROID_NDK/ndk-build
|
||||||
- name: Upload a Build Artifact
|
- name: Upload Build Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: susfsd-aarch64-linux-android
|
name: susfsd-linux-android
|
||||||
path: ./userspace/susfsd/libs
|
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 }}"
|
||||||
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,49 +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)
|
**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/rifsxd/KernelSU-Next/releases/latest)
|
<p>
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||||
[](/LICENSE)
|
</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.
|
## 🚀 Features
|
||||||
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.
|
|
||||||
|
|
||||||
## 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 `arm64-v8a` is supported.
|
## ✅ Compatibility
|
||||||
|
|
||||||
## Usage
|
KernelSU Next supports Android kernels from **4.4 up to 6.6**:
|
||||||
|
|
||||||
- [Installation instruction](https://rifsxd.github.io/KernelSU-Next/)
|
| 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).
|
Please refer to the [Installation](https://kernelsu-next.github.io/webpage/pages/installation.html) guide for setup instructions.
|
||||||
- All other parts except the `kernel` directory are [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
## Credits
|
---
|
||||||
|
|
||||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): the KernelSU idea.
|
## 🔐 Security
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): the powerful root tool.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): apk v2 signature validation.
|
To report security issues, please see [SECURITY.md](/SECURITY.md).
|
||||||
- [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!
|
|
||||||
|
## 📜 License
|
||||||
|
|
||||||
|
- **`/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).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💸 Donations
|
||||||
|
|
||||||
|
If you’d like to support the project:
|
||||||
|
|
||||||
|
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||||
|
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||||
|
- **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) – 💜 to 5ec1cff for keeping KernelSU alive
|
||||||
|
- [Crowdin Translators](https://crowdin.com/project/kernelsu-next/members) – 💬 Thanks to everyone for helping make KernelSU Next multi-lingual!
|
||||||
|
|||||||
58
docs/README_BG.md
Normal file
58
docs/README_BG.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
[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
|
||||||
|
|
||||||
|
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="лого">
|
||||||
|
|
||||||
|
Ядрено решение за root достъп за Android устройства.
|
||||||
|
|
||||||
|
[](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 официално поддържа повечето Android ядра от версия 4.4 до 6.6:
|
||||||
|
- Ядра GKI 2.0 (5.10+) могат да използват предварително компилирани изображения и LKM/KMI
|
||||||
|
- Ядра GKI 1.0 (4.19 - 5.4) изискват прекомпилиране с драйвера на KernelSU
|
||||||
|
- Остарели ядра (<4.14) също изискват прекомпилиране (3.18+ е експериментална поддръжка)
|
||||||
|
|
||||||
|
В момента се поддържа само архитектурата `arm64-v8a`, `armeabi-v7a` & `x86_64`.
|
||||||
|
|
||||||
|
## Инсталация
|
||||||
|
|
||||||
|
- [Инструкции за инсталиране](https://ksunext.org/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)
|
||||||
|
|
||||||
|
## Дарения
|
||||||
|
|
||||||
|
- 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
|
||||||
|
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff за спасяването на KernelSU
|
||||||
@@ -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)
|
[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/rifsxd/KernelSU-Next/releases/latest)
|
---
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## 特性
|
## 🚀 特性
|
||||||
|
|
||||||
1. 基于内核的 `SU` 和权限管理
|
- 基于内核的 `su` 和超级用户权限管理
|
||||||
2. 基于动态挂载系统 [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模块系统。
|
- 动态挂载系统基于 **[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 权限关进笼子里
|
- [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://rifsxd.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).
|
有关报告 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)
|
[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
|
# KernelSU Next
|
||||||
|
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
Une solution root basée sur le noyau pour les appareils Android.
|
Une solution root basée sur le noyau pour les appareils Android.
|
||||||
|
|
||||||
[](https://github.com/rifsxd/KernelSU-Next/releases/latest)
|
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
[](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)
|
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||||
[](/LICENSE)
|
[](/LICENSE)
|
||||||
|
|
||||||
## Fonctionnalités
|
## Fonctionnalités
|
||||||
|
|
||||||
@@ -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 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).
|
- 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
|
## Utilisation
|
||||||
|
|
||||||
- [Instructions d'installation](https://rifsxd.github.io/KernelSU-Next/)
|
- [Instructions d'installation](https://ksunext.org/pages/installation.html)
|
||||||
|
|
||||||
## Sécurité
|
## Sécurité
|
||||||
|
|
||||||
@@ -41,9 +41,9 @@ Pour signaler des vulnérabilités de sécurité dans KernelSU, consultez [SECUR
|
|||||||
|
|
||||||
## Crédits
|
## Crédits
|
||||||
|
|
||||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) : l'idée de KernelSU.
|
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) : L'idée de KernelSU.
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk) : l'outil root puissant.
|
- [Magisk](https://github.com/topjohnwu/Magisk) : L'outil root puissant.
|
||||||
- [genuine](https://github.com/brevent/genuine/) : validation de signature apk v2.
|
- [genuine](https://github.com/brevent/genuine/) : Validation de signature APK v2.
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) : quelques techniques de rootkit.
|
- [Diamorphine](https://github.com/m0nad/Diamorphine) : Quelques techniques de rootkit.
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU) : merci à tiann, sans qui KernelSU Next n'existerait même pas.
|
- [KernelSU](https://github.com/tiann/KernelSU) : Merci à tiann, sans qui KernelSU Next n'existerait même pas.
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) : 💜 5ec1cff pour avoir sauvé KernelSU !
|
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) : 💜 5ec1cff pour avoir sauvé 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_RU.md) | [ภาษาไทย](README_TH.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
|
# KernelSU Next
|
||||||
|
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
Sebuah solusi root berbasis Kernel untuk perangkat Android.
|
Sebuah solusi root berbasis Kernel untuk perangkat Android.
|
||||||
|
|
||||||
[](https://github.com/rifsxd/KernelSU-Next/releases/latest)
|
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
[](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)
|
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||||
[](/LICENSE)
|
[](/LICENSE)
|
||||||
|
|
||||||
## Fitur
|
## Fitur
|
||||||
|
|
||||||
@@ -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 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).
|
- 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
|
## Penggunaan
|
||||||
|
|
||||||
- [Petunjuk instalasi](https://rifsxd.github.io/KernelSU-Next/)
|
- [Petunjuk instalasi](https://ksunext.org/pages/installation.html)
|
||||||
|
|
||||||
## Keamanan
|
## Keamanan
|
||||||
|
|
||||||
@@ -41,9 +41,9 @@ Untuk informasi tentang melaporkan kerentanannya di KernelSU, lihat [SECURITY.md
|
|||||||
|
|
||||||
## Kredit
|
## Kredit
|
||||||
|
|
||||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): ide KernelSU.
|
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): Ide KernelSU.
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): alat root yang kuat.
|
- [Magisk](https://github.com/topjohnwu/Magisk): Alat root yang kuat.
|
||||||
- [genuine](https://github.com/brevent/genuine/): validasi tanda tangan apk v2.
|
- [genuine](https://github.com/brevent/genuine/): Validasi tanda tangan APK v2.
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): beberapa keterampilan rootkit.
|
- [Diamorphine](https://github.com/m0nad/Diamorphine): Beberapa keterampilan rootkit.
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): terima kasih kepada tiann, jika tidak, KernelSU Next bahkan tidak akan ada.
|
- [KernelSU](https://github.com/tiann/KernelSU): Terima kasih kepada tiann, jika tidak, KernelSU Next bahkan tidak akan ada.
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff karena menyelamatkan KernelSU!
|
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff karena menyelamatkan KernelSU!
|
||||||
|
|||||||
63
docs/README_IT.md
Normal file
63
docs/README_IT.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** | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||||
|
|
||||||
|
# KernelSU Next
|
||||||
|
|
||||||
|
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||||
|
|
||||||
|
Una soluzione root basata sul kernel per dispositivi Android.
|
||||||
|
|
||||||
|
[](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)
|
||||||
|
|
||||||
|
## Caratteristiche
|
||||||
|
|
||||||
|
1. Gestione degli accessi `su` e root basata sul kernel.
|
||||||
|
2. Sistema modulare basato sul sistema di montaggio dinamico [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): Rinchiudi il potere della radice in una gabbia.
|
||||||
|
|
||||||
|
## Stato compatibilità
|
||||||
|
|
||||||
|
KernelSU Next supporta ufficialmente la maggior parte dei kernel Android dalla versione 4.4 alla 6.6.
|
||||||
|
- I kernel GKI 2.0 (5.10+) possono eseguire immagini precostruite e LKM/KMI.
|
||||||
|
- 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`, `armeabi-v7a` & `x86_64`.
|
||||||
|
|
||||||
|
## Utilizzo
|
||||||
|
|
||||||
|
- [Istruzioni per l'installazione](https://ksunext.org/pages/installation.html)
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Per informazioni sulla segnalazione delle vulnerabilità di sicurezza in KernelSU, vedere [SECURITY.md](/SECURITY.md).
|
||||||
|
|
||||||
|
## Licenza
|
||||||
|
|
||||||
|
- I file nella directory `kernel` sono [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||||
|
- Tutte le altre parti eccetto la directory `kernel` sono [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||||
|
|
||||||
|
## Donazioni
|
||||||
|
|
||||||
|
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
||||||
|
|
||||||
|
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
||||||
|
|
||||||
|
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
||||||
|
|
||||||
|
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
||||||
|
|
||||||
|
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
||||||
|
|
||||||
|
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
||||||
|
|
||||||
|
## Crediti
|
||||||
|
|
||||||
|
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): L'idea di KernelSU.
|
||||||
|
- [Magisk](https://github.com/topjohnwu/Magisk): Il potente strumento di root.
|
||||||
|
- [genuine](https://github.com/brevent/genuine/): Convalida della firma APK v2.
|
||||||
|
- [Diamorphine](https://github.com/m0nad/Diamorphine): Alcune competenze sui rootkit.
|
||||||
|
- [KernelSU](https://github.com/tiann/KernelSU): Grazie a tiann, altrimenti KernelSU Next non esisterebbe nemmeno.
|
||||||
|
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff per aver salvato KernelSU!
|
||||||
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)
|
[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
|
# KernelSU Next
|
||||||
|
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
안드로이드 기기들을 위한 커널 기반 루팅 솔루션입니다.
|
안드로이드 기기들을 위한 커널 기반 루팅 솔루션입니다.
|
||||||
|
|
||||||
[](https://github.com/rifsxd/KernelSU-Next/releases/latest)
|
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
[](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)
|
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||||
[](/LICENSE)
|
[](/LICENSE)
|
||||||
|
|
||||||
## 기능
|
## 기능
|
||||||
|
|
||||||
@@ -24,11 +24,11 @@ KernelSU Next는 공식적으로 대부분의 4.4부터 6.6의 안드로이드
|
|||||||
- GKI 1.0 (4.19 - 5.4) 커널은 KernelSU 드라이버로 다시 빌드해야 합니다.
|
- GKI 1.0 (4.19 - 5.4) 커널은 KernelSU 드라이버로 다시 빌드해야 합니다.
|
||||||
- EOL (<4.14) 커널도 역시 KernelSU 드라이버로 다시 빌드해야 합니다.(3.18+는 실험적이며 일부 함수의 이식이 필요할 수 있습니다.).
|
- EOL (<4.14) 커널도 역시 KernelSU 드라이버로 다시 빌드해야 합니다.(3.18+는 실험적이며 일부 함수의 이식이 필요할 수 있습니다.).
|
||||||
|
|
||||||
현재는, `arm64-v8a`만 지원됩니다.
|
현재는, `arm64-v8a`, `armeabi-v7a` & `x86_64` 만 지원됩니다.
|
||||||
|
|
||||||
## 사용 방법
|
## 사용 방법
|
||||||
|
|
||||||
- [설치 방법](https://rifsxd.github.io/KernelSU-Next/)
|
- [설치 방법](https://ksunext.org/pages/installation.html)
|
||||||
|
|
||||||
## 보안
|
## 보안
|
||||||
|
|
||||||
@@ -41,9 +41,9 @@ KernelSU의 보안 취약점 보고에 대한 자세한 내용은 [SECURITY.md](
|
|||||||
|
|
||||||
## 크레딧
|
## 크레딧
|
||||||
|
|
||||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU의 아이디어
|
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU의 아이디어
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): 강력한 루팅 도구
|
- [Magisk](https://github.com/topjohnwu/Magisk): 강력한 루팅 도구
|
||||||
- [genuine](https://github.com/brevent/genuine/): apk v2 서명 검사
|
- [genuine](https://github.com/brevent/genuine/): APK v2 서명 검사
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): 일부 rootkit 기술
|
- [Diamorphine](https://github.com/m0nad/Diamorphine): 일부 rootkit 기술
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): KernelSU Next가 존재할 수 있게 해 준 tiann에게 감사드립니다.
|
- [KernelSU](https://github.com/tiann/KernelSU): KernelSU Next가 존재할 수 있게 해 준 tiann에게 감사드립니다.
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): KernelSU를 구해준 5ec1cff에게 감사드립니다!
|
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): KernelSU를 구해준 5ec1cff에게 감사드립니다!
|
||||||
|
|||||||
89
docs/README_PL.md
Normal file
89
docs/README_PL.md
Normal file
@@ -0,0 +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_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | **Polski** | [Български](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>Bazujące na jądrze rozwiązanie root dla urządzeń z Androidem.</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>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Funkcjonalnoś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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Kompatybilność
|
||||||
|
|
||||||
|
KernelSU Next obsługuje jądra Androida od wersji **4.4 do 6.6**:
|
||||||
|
|
||||||
|
| 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) |
|
||||||
|
|
||||||
|
**Obsługiwane architektury:** `arm64-v8a`, `armeabi-v7a` i `x86_64`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Instalacja
|
||||||
|
|
||||||
|
Instrukcje dotyczące instalacji można znaleźć w przewodniku [Instalacja](https://kernelsu-next.github.io/webpage/pages/installation.html).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Bezpieczeństwo
|
||||||
|
|
||||||
|
Aby zgłosić problemy związane z bezpieczeństwem, zapoznaj się z [SECURITY.md](/SECURITY.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 Licencje
|
||||||
|
|
||||||
|
- **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).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💸 Darowizny
|
||||||
|
|
||||||
|
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,49 +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)
|
[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/rifsxd/KernelSU-Next/releases/latest)
|
<p>
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||||
[](/LICENSE)
|
</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.
|
## 🚀 Características
|
||||||
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.
|
|
||||||
|
|
||||||
## 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 `arm64-v8a` é suportado.
|
## ✅ Compatibilidade
|
||||||
|
|
||||||
## Uso
|
O KernelSU Next oferece suporte a kernels Android **4.4 até 6.6**:
|
||||||
|
|
||||||
- [Instruções de instalação](https://rifsxd.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).
|
Consulte o guia de [Instalação](https://kernelsu-next.github.io/webpage/pt_BR/pages/installation.html) para obter instruções de configuração.
|
||||||
- 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).
|
|
||||||
|
|
||||||
## Créditos
|
---
|
||||||
|
|
||||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): a ideia do KernelSU.
|
## 🔐 Segurança
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): a poderosa ferramenta root.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): validação de assinatura apk v2.
|
Para relatar problemas de segurança, consulte [SECURITY.md](/SECURITY.md).
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): algumas habilidades de rootkit.
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): obrigado a 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!
|
|
||||||
|
## 📜 Licença
|
||||||
|
|
||||||
|
- **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).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💸 Doações
|
||||||
|
|
||||||
|
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)
|
[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
|
# KernelSU Next
|
||||||
|
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
Root-решение для Android на базе ядра.
|
Root-решение для Android на базе ядра.
|
||||||
|
|
||||||
[](https://github.com/rifsxd/KernelSU-Next/releases/latest)
|
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
[](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)
|
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||||
[](/LICENSE)
|
[](/LICENSE)
|
||||||
|
|
||||||
## Функции
|
## Функции
|
||||||
|
|
||||||
@@ -24,11 +24,11 @@ KernelSU Next работает с большинством ядер Android (4.4
|
|||||||
- GKI 1.0 (4.19 - 5.4) требуют пересборки с драйвером KernelSU.
|
- GKI 1.0 (4.19 - 5.4) требуют пересборки с драйвером KernelSU.
|
||||||
- EOL (<4.14) также требуют пересборки с драйвером KernelSU (версии 3.18+ экспериментальные и могут потребовать некоторые функции бэкпортов).
|
- EOL (<4.14) также требуют пересборки с драйвером KernelSU (версии 3.18+ экспериментальные и могут потребовать некоторые функции бэкпортов).
|
||||||
|
|
||||||
Сейчас поддерживается только `arm64-v8a`.
|
Сейчас поддерживается только `arm64-v8a`, `armeabi-v7a` & `x86_64`.
|
||||||
|
|
||||||
## Использование
|
## Использование
|
||||||
|
|
||||||
- [Инструкция по установке](https://rifsxd.github.io/KernelSU-Next/)
|
- [Инструкция по установке](https://ksunext.org/pages/installation.html)
|
||||||
|
|
||||||
## Безопасность
|
## Безопасность
|
||||||
|
|
||||||
@@ -39,11 +39,25 @@ KernelSU Next работает с большинством ядер Android (4.4
|
|||||||
- Всё, что в директории `kernel`, — [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.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`, под [GPL-3.0-or-later](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.
|
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): Идея KernelSU.
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): топовый инструмент для root.
|
- [Magisk](https://github.com/topjohnwu/Magisk): Топовый инструмент для root.
|
||||||
- [genuine](https://github.com/brevent/genuine/): валидация подписи APK v2.
|
- [genuine](https://github.com/brevent/genuine/): Валидация подписи APK v2.
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): некоторые навыки rootkit.
|
- [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!
|
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff за сохранение KernelSU!
|
||||||
|
|||||||
@@ -1,49 +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) | **ภาษาไทย**
|
[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
|
# KernelSU Next
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||||
|
|
||||||
โซลูชันรูทบนพื้นฐานเคอร์เนลสำหรับอุปกรณ์ Android
|
โซลูชั่นรูทบนพื้นฐานเคอร์เนลสำหรับอุปกรณ์ Android
|
||||||
|
|
||||||
[](https://github.com/rifsxd/KernelSU-Next/releases/latest)
|
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
[](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)
|
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||||
[](/LICENSE)
|
[](/LICENSE)
|
||||||
|
|
||||||
## คุณสมบัติ
|
## คุณสมบัติ
|
||||||
|
|
||||||
1. การจัดการการเข้าถึงรูทและ `su` บนเคอร์เนล
|
1. จัดการการเข้าถึงรูท และ `su` บนพื้นฐานเคอร์เนล
|
||||||
2. ระบบโมดูลที่ใช้ระบบการติดตั้งแบบไดนามิก [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)
|
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/guide/app-profile.html): จำกัดพลังรูทไว้สำหรับแอปต่างๆ
|
3. [App Profile](https://kernelsu.org/guide/app-profile.html): จำกัดสิทธิ์เข้าถึงรูทสำหรับแอปต่างๆ
|
||||||
|
|
||||||
## การเข้ากันในอุปกรณ์ต่างๆ
|
## การเข้ากันในอุปกรณ์ต่างๆ
|
||||||
|
|
||||||
KernelSU Next รองรับแบบเป็นทางการตั้งแต่เคอร์เนลแอนดรอยด์ 4.4 ถึง 6.6
|
KernelSU Next รองรับแบบเป็นทางการตั้งแต่เคอร์เนลแอนดรอยด์ 4.4 ถึง 6.6
|
||||||
- GKI 2.0 (5.10+) เคอร์เนลสามารถรันแบบไฟล์สำเร็จรูปและ LKM/KMI ได้
|
- GKI 2.0 (5.10+) เคอร์เนลสามารถรันไฟล์อิมเมจสำเร็จรูป และ LKM/KMI ได้
|
||||||
- GKI 1.0 (4.19 - 5.4) เคอร์เนลต้องการ build ร่วมกับไดร์เวอร์ของทาง KernelSU
|
- GKI 1.0 (4.19 - 5.4) เคอร์เนลจะต้องรีบิ้วร่วมกับไดร์เวอร์ของ KernelSU
|
||||||
- EOL (<4.14) เคอร์เนลก็ต้องการ build ร่วมกับไดร์เวอร์ของทาง KernelSU เช่นกัน (3.18+ ยังเป็นการทดลองอยู่และยังต้องการเขียนในหลังบ้านเพิ่มเติม)
|
- EOL (<4.14) เคอร์เนลก็ต้องรีบิ้วร่วมกับไดร์เวอร์ของ KernelSU เช่นกัน (3.18+ ยังเป็นเวอร์ชั่นทดลอง และยังต้องเขียนฟังก์ชั่นหลังบ้านเพิ่มเติม)
|
||||||
|
|
||||||
ในขณะนี้, มีแค่สถาปัตยกรรม `arm64-v8a` ที่รองรับเท่านั้น
|
ในขณะนี้, มีแค่สถาปัตยกรรม `arm64-v8a`, `armeabi-v7a` & `x86_64` ที่รองรับเท่านั้น
|
||||||
|
|
||||||
## การใช้งาน
|
## การใช้งาน
|
||||||
|
|
||||||
- [คำแนะนำในการติดตั้ง](https://rifsxd.github.io/KernelSU-Next/)
|
- [คำแนะนำในการติดตั้ง](https://ksunext.org/pages/installation.html)
|
||||||
|
|
||||||
## ความปลอดภัย
|
## ความปลอดภัย
|
||||||
|
|
||||||
สำหรับข้อมูลเกี่ยวกับการรายงานช่องโหว่ด้านความปลอดภัยใน KernelSU โปรดดูที่ [SECURITY.md](/SECURITY.md).
|
สำหรับข้อมูลการรายงานช่องโหว่ด้านความปลอดภัยของ KernelSU โปรดดูที่ [SECURITY.md](/SECURITY.md).
|
||||||
|
|
||||||
## ใบอนุญาต
|
## สัญญาอนุญาต
|
||||||
|
|
||||||
- ไฟล์ที่ภายใต้โฟลเดอร์ `kernel` ถือว่าเป็น [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.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` ถือว่าเป็นสัญญาอนุญาต [GPL-3.0-or-later](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
|
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): ที่เป็นคนริเริ่มไอเดีย KernelSU
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): อุปกรณ์มือเกี่ยวกับรูทที่ทรงพลัง
|
- [Magisk](https://github.com/topjohnwu/Magisk): เครื่องมือรูทที่ทรงพลัง
|
||||||
- [genuine](https://github.com/brevent/genuine/): การออกลายเซ็นให้กับไฟล์ apk v2
|
- [genuine](https://github.com/brevent/genuine/): การตรวจสอบลายเซ็น APK v2
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): ความรู้ความสามารถเกี่ยวกับ rootkit
|
- [Diamorphine](https://github.com/m0nad/Diamorphine): ความรู้เกี่ยวกับ rootkit
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): ต้องขอบคุณ tiann ถ้าไม่มีคนนั้นก็ไม่มีสิ่งที่เรียกว่า KernelSU เกิดขึ้น
|
- [KernelSU](https://github.com/tiann/KernelSU): ต้องขอบคุณ tiann ไม่งั้นจะไม่มี KernelSU ขึ้นมา
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff ที่ช่วย KernelSU ไว้
|
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff ที่ช่วย KernelSU เอาไว้!
|
||||||
|
|||||||
@@ -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)
|
[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/rifsxd/KernelSU-Next/releases/latest)
|
<p>
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||||
[](/LICENSE)
|
</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.
|
## 🚀 Özellikler
|
||||||
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.
|
|
||||||
|
|
||||||
## 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://rifsxd.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.
|
Kurulum talimatları için [Kurulum Kılavuzu](https://kernelsu-next.github.io/webpage/pages/installation.html) sayfasına bakınız.
|
||||||
- `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.
|
|
||||||
|
|
||||||
## Krediler
|
---
|
||||||
|
|
||||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU fikri.
|
## 🔐 Güvenlik
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): güçlü kök aracı.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): apk v2 imza doğrulama.
|
Güvenlik açıklarını bildirmek için lütfen [SECURITY.md](/SECURITY.md) dosyasına bakınız.
|
||||||
- [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!
|
|
||||||
|
## 📜 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)
|
[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/rifsxd/KernelSU-Next/releases/latest)
|
---
|
||||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## 特性
|
## 🚀 特性
|
||||||
|
|
||||||
1. 基於內核的 `su` 和 root 權限管理
|
- 基於內核的 `su` 和 Root 權限管理
|
||||||
2. 基於動態掛載系統 [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模塊系統。
|
- 模塊系統基於 **[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 權限關進籠子裡
|
- [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://rifsxd.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 的靈感.
|
有關報告 KernelSU Next 漏洞的信息,請參閱 [SECURITY.md](/SECURITY.md)
|
||||||
- [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!
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 許可證
|
||||||
|
|
||||||
|
- **目錄 `/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
|
||||||
63
docs/README_VI.md
Normal file
63
docs/README_VI.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** | [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">
|
||||||
|
|
||||||
|
Một giải pháp root từ nhân linux dành cho các thiết bị chạy Android
|
||||||
|
|
||||||
|
[](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)
|
||||||
|
|
||||||
|
## Tính năng
|
||||||
|
|
||||||
|
1. Quản lý quyền truy cập SU dựa trên kernel android.
|
||||||
|
2. Hệ thống mount module dựa trên 1 trong 2 cơ chế mount [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): Quản lý quyền truy cập root 1 cách chặt chẽ
|
||||||
|
|
||||||
|
## Danh sách tương thích
|
||||||
|
|
||||||
|
KernelSU Next hỗ trợ chính thức các kernel Android từ phiên bản 4.4 đến 6.6
|
||||||
|
- GKI 2.0 (5.10+) kernels có thể cài đặt qua những .img/.zip đã được build sẵn và LKM/KMI hoặc tự vá qua manager (nếu được)
|
||||||
|
- 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`, `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://ksunext.org/pages/installation.html)
|
||||||
|
|
||||||
|
## Bảo mật
|
||||||
|
|
||||||
|
Để biết thêm thông tin về việc báo cáo lỗ hổng bảo mật trong KernelSU Next vui lòng đọc (Thông tin sẽ dược gửi về KernelSU)[SECURITY.md](/SECURITY.md).
|
||||||
|
|
||||||
|
## Gíây phép
|
||||||
|
|
||||||
|
- Những thư mục/tập tin trong `kernel` là giấy phép [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||||
|
- Những thư mục/tập tin ngoài `kernel` là giấy phép [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||||
|
|
||||||
|
## Quyên góp/Hỗ trợ
|
||||||
|
|
||||||
|
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
||||||
|
|
||||||
|
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
||||||
|
|
||||||
|
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
||||||
|
|
||||||
|
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
||||||
|
|
||||||
|
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
||||||
|
|
||||||
|
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
||||||
|
|
||||||
|
## Lời cảm ơn tới...
|
||||||
|
|
||||||
|
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): Ý tưởng cho sự ra đời của KernelSU.
|
||||||
|
- [Magisk](https://github.com/topjohnwu/Magisk): Công cụ root mạnh mẽ, quen thuộc và tương thích cao cho các thiết bị chạy Android.
|
||||||
|
- [genuine](https://github.com/brevent/genuine/): Chữ kí apk v2.
|
||||||
|
- [Diamorphine](https://github.com/m0nad/Diamorphine): Một vài kỹ năng rootkit.
|
||||||
|
- [KernelSU](https://github.com/tiann/KernelSU): Nguồn gốc của KernelSU Next, thanks to tiann.
|
||||||
|
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 5ec1cff - người đã cứu lấy KernelSU💜 !
|
||||||
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,6 +9,14 @@ config KSU
|
|||||||
To compile as a module, choose M here: the
|
To compile as a module, choose M here: the
|
||||||
module will be called kernelsu.
|
module will be called kernelsu.
|
||||||
|
|
||||||
|
config KSU_KPROBES_HOOK
|
||||||
|
bool "Use kprobes for kernelsu"
|
||||||
|
depends on KSU
|
||||||
|
depends on KPROBES
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Disable if you use manual hooks.
|
||||||
|
|
||||||
config KSU_DEBUG
|
config KSU_DEBUG
|
||||||
bool "KernelSU debug mode"
|
bool "KernelSU debug mode"
|
||||||
depends on KSU
|
depends on KSU
|
||||||
@@ -24,4 +32,12 @@ config KSU_ALLOWLIST_WORKAROUND
|
|||||||
Enable session keyring init workaround for problematic devices.
|
Enable session keyring init workaround for problematic devices.
|
||||||
Useful for situations where the SU allowlist is not kept after a reboot.
|
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.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ $(info -- KernelSU-Next version: $(KSU_VERSION))
|
|||||||
ccflags-y += -DKSU_VERSION=$(KSU_VERSION)
|
ccflags-y += -DKSU_VERSION=$(KSU_VERSION)
|
||||||
else # If there is no .git file, the default version will be passed.
|
else # If there is no .git file, the default version will be passed.
|
||||||
$(warning "KSU_GIT_VERSION not defined! It is better to make KernelSU-Next a git submodule!")
|
$(warning "KSU_GIT_VERSION not defined! It is better to make KernelSU-Next a git submodule!")
|
||||||
ccflags-y += -DKSU_VERSION=16
|
ccflags-y += -DKSU_VERSION=11998
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0)
|
ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0)
|
||||||
@@ -37,6 +37,18 @@ ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/includ
|
|||||||
ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE
|
ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(shell grep -q "strncpy_from_user_nofault" $(srctree)/include/linux/uaccess.h; echo $$?),0)
|
||||||
|
ccflags-y += -DKSU_STRNCPY_FROM_USER_NOFAULT
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(shell grep -q "ssize_t kernel_read" $(srctree)/fs/read_write.c; echo $$?),0)
|
||||||
|
ccflags-y += -DKSU_KERNEL_READ
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(shell grep "ssize_t kernel_write" $(srctree)/fs/read_write.c | grep -q "const void" ; echo $$?),0)
|
||||||
|
ccflags-y += -DKSU_KERNEL_WRITE
|
||||||
|
endif
|
||||||
|
|
||||||
ifndef KSU_NEXT_EXPECTED_SIZE
|
ifndef KSU_NEXT_EXPECTED_SIZE
|
||||||
KSU_NEXT_EXPECTED_SIZE := 0x3e6
|
KSU_NEXT_EXPECTED_SIZE := 0x3e6
|
||||||
endif
|
endif
|
||||||
@@ -56,42 +68,8 @@ $(info -- KernelSU-Next Manager signature hash: $(KSU_NEXT_EXPECTED_HASH))
|
|||||||
ccflags-y += -DEXPECTED_NEXT_SIZE=$(KSU_NEXT_EXPECTED_SIZE)
|
ccflags-y += -DEXPECTED_NEXT_SIZE=$(KSU_NEXT_EXPECTED_SIZE)
|
||||||
ccflags-y += -DEXPECTED_NEXT_HASH=\"$(KSU_NEXT_EXPECTED_HASH)\"
|
ccflags-y += -DEXPECTED_NEXT_HASH=\"$(KSU_NEXT_EXPECTED_HASH)\"
|
||||||
|
|
||||||
ccflags-y += -DKSU_COMPAT_GET_CRED_RCU
|
|
||||||
|
|
||||||
ccflags-y += -DKSU_UMOUNT
|
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)
|
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)
|
$(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\
|
CAN_UMOUNT = static int can_umount(const struct path *path, int flags)\n\
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include <linux/capability.h>
|
||||||
#include <linux/compiler.h>
|
#include <linux/compiler.h>
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <linux/gfp.h>
|
#include <linux/gfp.h>
|
||||||
@@ -64,12 +65,14 @@ static void remove_uid_from_arr(uid_t uid)
|
|||||||
|
|
||||||
static void init_default_profiles()
|
static void init_default_profiles()
|
||||||
{
|
{
|
||||||
|
kernel_cap_t full_cap = CAP_FULL_SET;
|
||||||
|
|
||||||
default_root_profile.uid = 0;
|
default_root_profile.uid = 0;
|
||||||
default_root_profile.gid = 0;
|
default_root_profile.gid = 0;
|
||||||
default_root_profile.groups_count = 1;
|
default_root_profile.groups_count = 1;
|
||||||
default_root_profile.groups[0] = 0;
|
default_root_profile.groups[0] = 0;
|
||||||
memset(&default_root_profile.capabilities, 0xff,
|
memcpy(&default_root_profile.capabilities.effective, &full_cap,
|
||||||
sizeof(default_root_profile.capabilities));
|
sizeof(default_root_profile.capabilities.effective));
|
||||||
default_root_profile.namespaces = 0;
|
default_root_profile.namespaces = 0;
|
||||||
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||||
|
|
||||||
@@ -110,6 +113,7 @@ void ksu_show_allow_list(void)
|
|||||||
static void ksu_grant_root_to_shell()
|
static void ksu_grant_root_to_shell()
|
||||||
{
|
{
|
||||||
struct app_profile profile = {
|
struct app_profile profile = {
|
||||||
|
.version = KSU_APP_PROFILE_VER,
|
||||||
.allow_su = true,
|
.allow_su = true,
|
||||||
.current_uid = 2000,
|
.current_uid = 2000,
|
||||||
};
|
};
|
||||||
@@ -152,11 +156,6 @@ static bool profile_valid(struct app_profile *profile)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forbid_system_uid(profile->current_uid)) {
|
|
||||||
pr_err("uid lower than 2000 is unsupported: %d\n", profile->current_uid);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (profile->version < KSU_APP_PROFILE_VER) {
|
if (profile->version < KSU_APP_PROFILE_VER) {
|
||||||
pr_info("Unsupported profile version: %d\n", profile->version);
|
pr_info("Unsupported profile version: %d\n", profile->version);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "apk_sign.h"
|
#include "apk_sign.h"
|
||||||
#include "klog.h" // IWYU pragma: keep
|
#include "klog.h" // IWYU pragma: keep
|
||||||
#include "kernel_compat.h"
|
#include "kernel_compat.h"
|
||||||
|
#include "throne_tracker.h"
|
||||||
|
|
||||||
|
|
||||||
struct sdesc {
|
struct sdesc {
|
||||||
@@ -316,5 +317,21 @@ module_param_cb(ksu_debug_manager_uid, &expected_size_ops,
|
|||||||
|
|
||||||
bool is_manager_apk(char *path)
|
bool is_manager_apk(char *path)
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
return check_v2_signature(path, EXPECTED_NEXT_SIZE, EXPECTED_NEXT_HASH);
|
return check_v2_signature(path, EXPECTED_NEXT_SIZE, EXPECTED_NEXT_HASH);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
#include <linux/kallsyms.h>
|
#include <linux/kallsyms.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/kprobes.h>
|
#include <linux/kprobes.h>
|
||||||
|
#ifdef CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||||
#include <linux/lsm_hooks.h>
|
#include <linux/lsm_hooks.h>
|
||||||
|
#endif
|
||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/nsproxy.h>
|
#include <linux/nsproxy.h>
|
||||||
#include <linux/path.h>
|
#include <linux/path.h>
|
||||||
@@ -45,14 +47,14 @@
|
|||||||
#include "throne_tracker.h"
|
#include "throne_tracker.h"
|
||||||
#include "kernel_compat.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;
|
static bool ksu_module_mounted = false;
|
||||||
|
|
||||||
extern int handle_sepolicy(unsigned long arg3, void __user *arg4);
|
extern int handle_sepolicy(unsigned long arg3, void __user *arg4);
|
||||||
|
|
||||||
|
static bool ksu_su_compat_enabled = true;
|
||||||
|
extern void ksu_sucompat_init();
|
||||||
|
extern void ksu_sucompat_exit();
|
||||||
|
|
||||||
static inline bool is_allow_su()
|
static inline bool is_allow_su()
|
||||||
{
|
{
|
||||||
if (is_manager()) {
|
if (is_manager()) {
|
||||||
@@ -112,6 +114,7 @@ static void setup_groups(struct root_profile *profile, struct cred *cred)
|
|||||||
|
|
||||||
groups_sort(group_info);
|
groups_sort(group_info);
|
||||||
set_groups(cred, group_info);
|
set_groups(cred, group_info);
|
||||||
|
put_group_info(group_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void disable_seccomp(void)
|
static void disable_seccomp(void)
|
||||||
@@ -136,27 +139,17 @@ void escape_to_root(void)
|
|||||||
{
|
{
|
||||||
struct cred *cred;
|
struct cred *cred;
|
||||||
|
|
||||||
#ifdef KSU_GET_CRED_RCU
|
cred = prepare_creds();
|
||||||
rcu_read_lock();
|
if (!cred) {
|
||||||
|
pr_warn("prepare_creds failed!\n");
|
||||||
do {
|
return;
|
||||||
cred = (struct cred *)__task_cred((current));
|
}
|
||||||
BUG_ON(!cred);
|
|
||||||
} while (!get_cred_rcu(cred));
|
|
||||||
|
|
||||||
if (cred->euid.val == 0) {
|
if (cred->euid.val == 0) {
|
||||||
pr_warn("Already root, don't escape!\n");
|
pr_warn("Already root, don't escape!\n");
|
||||||
rcu_read_unlock();
|
abort_creds(cred);
|
||||||
return;
|
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);
|
struct root_profile *profile = ksu_get_root_profile(cred->uid.val);
|
||||||
|
|
||||||
@@ -169,6 +162,7 @@ void escape_to_root(void)
|
|||||||
cred->fsgid.val = profile->gid;
|
cred->fsgid.val = profile->gid;
|
||||||
cred->sgid.val = profile->gid;
|
cred->sgid.val = profile->gid;
|
||||||
cred->egid.val = profile->gid;
|
cred->egid.val = profile->gid;
|
||||||
|
cred->securebits = 0;
|
||||||
|
|
||||||
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
|
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
|
||||||
sizeof(kernel_cap_t));
|
sizeof(kernel_cap_t));
|
||||||
@@ -180,24 +174,18 @@ void escape_to_root(void)
|
|||||||
profile->capabilities.effective | CAP_DAC_READ_SEARCH;
|
profile->capabilities.effective | CAP_DAC_READ_SEARCH;
|
||||||
memcpy(&cred->cap_effective, &cap_for_ksud,
|
memcpy(&cred->cap_effective, &cap_for_ksud,
|
||||||
sizeof(cred->cap_effective));
|
sizeof(cred->cap_effective));
|
||||||
memcpy(&cred->cap_inheritable, &profile->capabilities.effective,
|
|
||||||
sizeof(cred->cap_inheritable));
|
|
||||||
memcpy(&cred->cap_permitted, &profile->capabilities.effective,
|
memcpy(&cred->cap_permitted, &profile->capabilities.effective,
|
||||||
sizeof(cred->cap_permitted));
|
sizeof(cred->cap_permitted));
|
||||||
memcpy(&cred->cap_bset, &profile->capabilities.effective,
|
memcpy(&cred->cap_bset, &profile->capabilities.effective,
|
||||||
sizeof(cred->cap_bset));
|
sizeof(cred->cap_bset));
|
||||||
memcpy(&cred->cap_ambient, &profile->capabilities.effective,
|
|
||||||
sizeof(cred->cap_ambient));
|
|
||||||
// set ambient caps to all-zero
|
// set ambient caps to all-zero
|
||||||
// fixes "operation not permitted" on dbus cap dropping
|
// fixes "operation not permitted" on dbus cap dropping
|
||||||
memset(&cred->cap_ambient, 0,
|
memset(&cred->cap_ambient, 0,
|
||||||
sizeof(cred->cap_ambient));
|
sizeof(cred->cap_ambient));
|
||||||
|
|
||||||
setup_groups(profile, cred);
|
setup_groups(profile, cred);
|
||||||
|
|
||||||
#ifdef KSU_GET_CRED_RCU
|
commit_creds(cred);
|
||||||
rcu_read_unlock();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Refer to kernel/seccomp.c: seccomp_set_mode_strict
|
// Refer to kernel/seccomp.c: seccomp_set_mode_strict
|
||||||
// When disabling Seccomp, ensure that current->sighand->siglock is held during the operation.
|
// When disabling Seccomp, ensure that current->sighand->siglock is held during the operation.
|
||||||
@@ -247,6 +235,26 @@ int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void nuke_ext4_sysfs() {
|
||||||
|
struct path path;
|
||||||
|
int err = kern_path("/data/adb/modules", 0, &path);
|
||||||
|
if (err) {
|
||||||
|
pr_err("nuke path err: %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct super_block* sb = path.dentry->d_inode->i_sb;
|
||||||
|
const char* name = sb->s_type->name;
|
||||||
|
if (strcmp(name, "ext4") != 0) {
|
||||||
|
pr_info("nuke but module aren't mounted\n");
|
||||||
|
path_put(&path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ext4_unregister_sysfs(sb);
|
||||||
|
path_put(&path);
|
||||||
|
}
|
||||||
|
|
||||||
int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||||
unsigned long arg4, unsigned long arg5)
|
unsigned long arg4, unsigned long arg5)
|
||||||
{
|
{
|
||||||
@@ -305,17 +313,40 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
|||||||
if (copy_to_user(arg3, &version, sizeof(version))) {
|
if (copy_to_user(arg3, &version, sizeof(version))) {
|
||||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||||
}
|
}
|
||||||
|
u32 version_flags = 0;
|
||||||
#ifdef MODULE
|
#ifdef MODULE
|
||||||
u32 is_lkm = 0x1;
|
version_flags |= 0x1;
|
||||||
#else
|
|
||||||
u32 is_lkm = 0x0;
|
|
||||||
#endif
|
#endif
|
||||||
if (arg4 && copy_to_user(arg4, &is_lkm, sizeof(is_lkm))) {
|
if (arg4 &&
|
||||||
|
copy_to_user(arg4, &version_flags, sizeof(version_flags))) {
|
||||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||||
}
|
}
|
||||||
return 0;
|
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 (arg2 == CMD_REPORT_EVENT) {
|
||||||
if (!from_root) {
|
if (!from_root) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -341,6 +372,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
|||||||
case EVENT_MODULE_MOUNTED: {
|
case EVENT_MODULE_MOUNTED: {
|
||||||
ksu_module_mounted = true;
|
ksu_module_mounted = true;
|
||||||
pr_info("module mounted!\n");
|
pr_info("module mounted!\n");
|
||||||
|
nuke_ext4_sysfs();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -456,6 +488,42 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg2 == CMD_IS_SU_ENABLED) {
|
||||||
|
if (copy_to_user(arg3, &ksu_su_compat_enabled,
|
||||||
|
sizeof(ksu_su_compat_enabled))) {
|
||||||
|
pr_err("copy su compat failed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||||
|
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,11 +576,13 @@ static void try_umount(const char *mnt, bool check_mnt, int flags)
|
|||||||
|
|
||||||
if (path.dentry != path.mnt->mnt_root) {
|
if (path.dentry != path.mnt->mnt_root) {
|
||||||
// it is not root mountpoint, maybe umounted by others already.
|
// it is not root mountpoint, maybe umounted by others already.
|
||||||
|
path_put(&path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we are only interest in some specific mounts
|
// we are only interest in some specific mounts
|
||||||
if (check_mnt && !should_umount(&path)) {
|
if (check_mnt && !should_umount(&path)) {
|
||||||
|
path_put(&path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,6 +646,7 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
|||||||
|
|
||||||
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
|
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
|
||||||
// filter the mountpoint whose target is `/data/adb`
|
// filter the mountpoint whose target is `/data/adb`
|
||||||
|
try_umount("/odm", true, 0);
|
||||||
try_umount("/system", true, 0);
|
try_umount("/system", true, 0);
|
||||||
try_umount("/system_ext", true, 0);
|
try_umount("/system_ext", true, 0);
|
||||||
try_umount("/vendor", true, 0);
|
try_umount("/vendor", true, 0);
|
||||||
@@ -585,10 +656,14 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
|||||||
// try umount ksu temp path
|
// try umount ksu temp path
|
||||||
try_umount("/debug_ramdisk", false, MNT_DETACH);
|
try_umount("/debug_ramdisk", false, MNT_DETACH);
|
||||||
try_umount("/sbin", false, MNT_DETACH);
|
try_umount("/sbin", false, MNT_DETACH);
|
||||||
|
|
||||||
// try umount hosts file
|
// try umount hosts file
|
||||||
try_umount("/system/etc/hosts", false, MNT_DETACH);
|
try_umount("/system/etc/hosts", false, MNT_DETACH);
|
||||||
|
|
||||||
|
// try umount lsposed dex2oat bins
|
||||||
|
try_umount("/apex/com.android.art/bin/dex2oat64", false, MNT_DETACH);
|
||||||
|
try_umount("/apex/com.android.art/bin/dex2oat32", false, MNT_DETACH);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,15 +736,22 @@ __maybe_unused int ksu_kprobe_exit(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3,
|
extern int ksu_handle_devpts(struct inode *inode); // sucompat.c
|
||||||
unsigned long arg4, unsigned long arg5)
|
|
||||||
|
static int ksu_inode_permission(struct inode *inode, int mask)
|
||||||
{
|
{
|
||||||
ksu_handle_prctl(option, arg2, arg3, arg4, arg5);
|
if (unlikely(inode->i_sb && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC)) {
|
||||||
return -ENOSYS;
|
#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)
|
#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)
|
unsigned perm)
|
||||||
{
|
{
|
||||||
if (init_session_keyring != NULL) {
|
if (init_session_keyring != NULL) {
|
||||||
@@ -684,6 +766,15 @@ static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#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,
|
static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
|
||||||
struct inode *new_inode, struct dentry *new_dentry)
|
struct inode *new_inode, struct dentry *new_dentry)
|
||||||
{
|
{
|
||||||
@@ -701,6 +792,7 @@ static struct security_hook_list ksu_hooks[] = {
|
|||||||
LSM_HOOK_INIT(task_prctl, ksu_task_prctl),
|
LSM_HOOK_INIT(task_prctl, ksu_task_prctl),
|
||||||
LSM_HOOK_INIT(inode_rename, ksu_inode_rename),
|
LSM_HOOK_INIT(inode_rename, ksu_inode_rename),
|
||||||
LSM_HOOK_INIT(task_fix_setuid, ksu_task_fix_setuid),
|
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)
|
#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)
|
LSM_HOOK_INIT(key_permission, ksu_key_permission)
|
||||||
#endif
|
#endif
|
||||||
@@ -882,16 +974,21 @@ void __init ksu_lsm_hook_init(void)
|
|||||||
}
|
}
|
||||||
smp_mb();
|
smp_mb();
|
||||||
}
|
}
|
||||||
#endif
|
#endif // MODULE
|
||||||
|
#endif // CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||||
|
|
||||||
void __init ksu_core_init(void)
|
void __init ksu_core_init(void)
|
||||||
{
|
{
|
||||||
|
#ifdef CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||||
ksu_lsm_hook_init();
|
ksu_lsm_hook_init();
|
||||||
|
#else
|
||||||
|
pr_info("ksu_core_init: LSM hooks not in use.\n");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ksu_core_exit(void)
|
void ksu_core_exit(void)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
pr_info("ksu_core_kprobe_exit\n");
|
pr_info("ksu_core_kprobe_exit\n");
|
||||||
// we dont use this now
|
// we dont use this now
|
||||||
// ksu_kprobe_exit();
|
// ksu_kprobe_exit();
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode)
|
|||||||
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
|
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
|
||||||
loff_t *pos)
|
loff_t *pos)
|
||||||
{
|
{
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) || defined(KSU_KERNEL_READ)
|
||||||
return kernel_read(p, buf, count, pos);
|
return kernel_read(p, buf, count, pos);
|
||||||
#else
|
#else
|
||||||
loff_t offset = pos ? *pos : 0;
|
loff_t offset = pos ? *pos : 0;
|
||||||
@@ -122,7 +122,7 @@ ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
|
|||||||
ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count,
|
ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count,
|
||||||
loff_t *pos)
|
loff_t *pos)
|
||||||
{
|
{
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) || defined(KSU_KERNEL_WRITE)
|
||||||
return kernel_write(p, buf, count, pos);
|
return kernel_write(p, buf, count, pos);
|
||||||
#else
|
#else
|
||||||
loff_t offset = pos ? *pos : 0;
|
loff_t offset = pos ? *pos : 0;
|
||||||
@@ -134,7 +134,7 @@ ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) || defined(KSU_STRNCPY_FROM_USER_NOFAULT)
|
||||||
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
||||||
long count)
|
long count)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ int __init kernelsu_init(void)
|
|||||||
|
|
||||||
ksu_throne_tracker_init();
|
ksu_throne_tracker_init();
|
||||||
|
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
ksu_sucompat_init();
|
ksu_sucompat_init();
|
||||||
ksu_ksud_init();
|
ksu_ksud_init();
|
||||||
#else
|
#else
|
||||||
@@ -80,7 +80,7 @@ void kernelsu_exit(void)
|
|||||||
|
|
||||||
destroy_workqueue(ksu_workqueue);
|
destroy_workqueue(ksu_workqueue);
|
||||||
|
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
ksu_ksud_exit();
|
ksu_ksud_exit();
|
||||||
ksu_sucompat_exit();
|
ksu_sucompat_exit();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
#define CMD_SET_APP_PROFILE 11
|
#define CMD_SET_APP_PROFILE 11
|
||||||
#define CMD_UID_GRANTED_ROOT 12
|
#define CMD_UID_GRANTED_ROOT 12
|
||||||
#define CMD_UID_SHOULD_UMOUNT 13
|
#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_POST_FS_DATA 1
|
||||||
#define EVENT_BOOT_COMPLETED 2
|
#define EVENT_BOOT_COMPLETED 2
|
||||||
|
|||||||
@@ -54,18 +54,23 @@ static void stop_vfs_read_hook();
|
|||||||
static void stop_execve_hook();
|
static void stop_execve_hook();
|
||||||
static void stop_input_hook();
|
static void stop_input_hook();
|
||||||
|
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
static struct work_struct stop_vfs_read_work;
|
static struct work_struct stop_vfs_read_work;
|
||||||
static struct work_struct stop_execve_hook_work;
|
static struct work_struct stop_execve_hook_work;
|
||||||
static struct work_struct stop_input_hook_work;
|
static struct work_struct stop_input_hook_work;
|
||||||
#else
|
#endif
|
||||||
|
|
||||||
bool ksu_vfs_read_hook __read_mostly = true;
|
bool ksu_vfs_read_hook __read_mostly = true;
|
||||||
bool ksu_execveat_hook __read_mostly = true;
|
bool ksu_execveat_hook __read_mostly = true;
|
||||||
bool ksu_input_hook __read_mostly = true;
|
bool ksu_input_hook __read_mostly = true;
|
||||||
#endif
|
|
||||||
|
|
||||||
u32 ksu_devpts_sid;
|
u32 ksu_devpts_sid;
|
||||||
|
|
||||||
|
#ifdef CONFIG_COMPAT
|
||||||
|
bool ksu_is_compat __read_mostly = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
void on_post_fs_data(void)
|
void on_post_fs_data(void)
|
||||||
{
|
{
|
||||||
static bool done = false;
|
static bool done = false;
|
||||||
@@ -107,6 +112,7 @@ static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
|
|||||||
if (get_user(compat, argv.ptr.compat + nr))
|
if (get_user(compat, argv.ptr.compat + nr))
|
||||||
return ERR_PTR(-EFAULT);
|
return ERR_PTR(-EFAULT);
|
||||||
|
|
||||||
|
ksu_is_compat = true;
|
||||||
return compat_ptr(compat);
|
return compat_ptr(compat);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -157,7 +163,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
|||||||
struct user_arg_ptr *argv,
|
struct user_arg_ptr *argv,
|
||||||
struct user_arg_ptr *envp, int *flags)
|
struct user_arg_ptr *envp, int *flags)
|
||||||
{
|
{
|
||||||
#ifndef CONFIG_KPROBES
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
if (!ksu_execveat_hook) {
|
if (!ksu_execveat_hook) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -195,7 +201,8 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
|||||||
first_arg, p, sizeof(first_arg));
|
first_arg, p, sizeof(first_arg));
|
||||||
pr_info("/system/bin/init first arg: %s\n",
|
pr_info("/system/bin/init first arg: %s\n",
|
||||||
first_arg);
|
first_arg);
|
||||||
if (!strcmp(first_arg, "second_stage")) {
|
if (!strcmp(first_arg, "second_stage") ||
|
||||||
|
(argc == 2 && !strcmp(first_arg, ""))) {
|
||||||
pr_info("/system/bin/init second_stage executed\n");
|
pr_info("/system/bin/init second_stage executed\n");
|
||||||
apply_kernelsu_rules();
|
apply_kernelsu_rules();
|
||||||
init_second_stage_executed = true;
|
init_second_stage_executed = true;
|
||||||
@@ -313,7 +320,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,
|
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||||
size_t *count_ptr, loff_t **pos)
|
size_t *count_ptr, loff_t **pos)
|
||||||
{
|
{
|
||||||
#ifndef CONFIG_KPROBES
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
if (!ksu_vfs_read_hook) {
|
if (!ksu_vfs_read_hook) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -332,7 +339,7 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!d_is_reg(file->f_path.dentry)) {
|
if (!S_ISREG(file->f_path.dentry->d_inode->i_mode)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,10 +397,12 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
|||||||
if (orig_read) {
|
if (orig_read) {
|
||||||
fops_proxy.read = read_proxy;
|
fops_proxy.read = read_proxy;
|
||||||
}
|
}
|
||||||
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
|
||||||
orig_read_iter = file->f_op->read_iter;
|
orig_read_iter = file->f_op->read_iter;
|
||||||
if (orig_read_iter) {
|
if (orig_read_iter) {
|
||||||
fops_proxy.read_iter = read_iter_proxy;
|
fops_proxy.read_iter = read_iter_proxy;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
// replace the file_operations
|
// replace the file_operations
|
||||||
file->f_op = &fops_proxy;
|
file->f_op = &fops_proxy;
|
||||||
read_count_append = rc_count;
|
read_count_append = rc_count;
|
||||||
@@ -426,7 +435,7 @@ static bool is_volumedown_enough(unsigned int count)
|
|||||||
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||||
int *value)
|
int *value)
|
||||||
{
|
{
|
||||||
#ifndef CONFIG_KPROBES
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
if (!ksu_input_hook) {
|
if (!ksu_input_hook) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -468,7 +477,37 @@ bool ksu_is_safe_mode()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_KPROBES
|
/*
|
||||||
|
* ksu_handle_execve_ksud, execve_ksud handler for non kprobe
|
||||||
|
* adapted from sys_execve_handler_pre
|
||||||
|
* https://github.com/tiann/KernelSU/commit/2027ac3
|
||||||
|
*/
|
||||||
|
__maybe_unused int ksu_handle_execve_ksud(const char __user *filename_user,
|
||||||
|
const char __user *const __user *__argv)
|
||||||
|
{
|
||||||
|
struct user_arg_ptr argv = { .ptr.native = __argv };
|
||||||
|
struct filename filename_in, *filename_p;
|
||||||
|
char path[32];
|
||||||
|
|
||||||
|
// return early if disabled.
|
||||||
|
if (!ksu_execveat_hook) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filename_user)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
memset(path, 0, sizeof(path));
|
||||||
|
ksu_strncpy_from_user_nofault(path, filename_user, 32);
|
||||||
|
|
||||||
|
// this is because ksu_handle_execveat_ksud calls it filename->name
|
||||||
|
filename_in.name = path;
|
||||||
|
filename_p = &filename_in;
|
||||||
|
|
||||||
|
return ksu_handle_execveat_ksud(AT_FDCWD, &filename_p, &argv, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
|
||||||
// https://elixir.bootlin.com/linux/v5.10.158/source/fs/exec.c#L1864
|
// 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)
|
static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
@@ -596,9 +635,32 @@ static void do_stop_input_hook(struct work_struct *work)
|
|||||||
}
|
}
|
||||||
#endif
|
#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()
|
static void stop_vfs_read_hook()
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
bool ret = schedule_work(&stop_vfs_read_work);
|
bool ret = schedule_work(&stop_vfs_read_work);
|
||||||
pr_info("unregister vfs_read kprobe: %d!\n", ret);
|
pr_info("unregister vfs_read kprobe: %d!\n", ret);
|
||||||
#else
|
#else
|
||||||
@@ -609,7 +671,7 @@ static void stop_vfs_read_hook()
|
|||||||
|
|
||||||
static void stop_execve_hook()
|
static void stop_execve_hook()
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
bool ret = schedule_work(&stop_execve_hook_work);
|
bool ret = schedule_work(&stop_execve_hook_work);
|
||||||
pr_info("unregister execve kprobe: %d!\n", ret);
|
pr_info("unregister execve kprobe: %d!\n", ret);
|
||||||
#else
|
#else
|
||||||
@@ -620,15 +682,16 @@ static void stop_execve_hook()
|
|||||||
|
|
||||||
static void stop_input_hook()
|
static void stop_input_hook()
|
||||||
{
|
{
|
||||||
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
static bool input_hook_stopped = false;
|
static bool input_hook_stopped = false;
|
||||||
if (input_hook_stopped) {
|
if (input_hook_stopped) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
input_hook_stopped = true;
|
input_hook_stopped = true;
|
||||||
#ifdef CONFIG_KPROBES
|
|
||||||
bool ret = schedule_work(&stop_input_hook_work);
|
bool ret = schedule_work(&stop_input_hook_work);
|
||||||
pr_info("unregister input kprobe: %d!\n", ret);
|
pr_info("unregister input kprobe: %d!\n", ret);
|
||||||
#else
|
#else
|
||||||
|
if (!ksu_input_hook) { return; }
|
||||||
ksu_input_hook = false;
|
ksu_input_hook = false;
|
||||||
pr_info("stop input_hook\n");
|
pr_info("stop input_hook\n");
|
||||||
#endif
|
#endif
|
||||||
@@ -637,7 +700,7 @@ static void stop_input_hook()
|
|||||||
// ksud: module support
|
// ksud: module support
|
||||||
void ksu_ksud_init()
|
void ksu_ksud_init()
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = register_kprobe(&execve_kp);
|
ret = register_kprobe(&execve_kp);
|
||||||
@@ -657,10 +720,10 @@ void ksu_ksud_init()
|
|||||||
|
|
||||||
void ksu_ksud_exit()
|
void ksu_ksud_exit()
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
unregister_kprobe(&execve_kp);
|
unregister_kprobe(&execve_kp);
|
||||||
// this should be done before unregister vfs_read_kp
|
// this should be done before unregister vfs_read_kp
|
||||||
// unregister_kprobe(&vfs_read_kp);
|
// unregister_kprobe(&vfs_read_kp);
|
||||||
unregister_kprobe(&input_event_kp);
|
unregister_kprobe(&input_event_kp);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,14 +36,19 @@ static struct policydb *get_policydb(void)
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DEFINE_MUTEX(ksu_rules);
|
||||||
|
|
||||||
void apply_kernelsu_rules()
|
void apply_kernelsu_rules()
|
||||||
{
|
{
|
||||||
|
struct policydb *db;
|
||||||
|
|
||||||
if (!getenforce()) {
|
if (!getenforce()) {
|
||||||
pr_info("SELinux permissive or disabled, apply rules!\n");
|
pr_info("SELinux permissive or disabled, apply rules!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
rcu_read_lock();
|
mutex_lock(&ksu_rules);
|
||||||
struct policydb *db = get_policydb();
|
|
||||||
|
db = get_policydb();
|
||||||
|
|
||||||
ksu_permissive(db, KERNEL_SU_DOMAIN);
|
ksu_permissive(db, KERNEL_SU_DOMAIN);
|
||||||
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject");
|
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject");
|
||||||
@@ -130,11 +135,11 @@ void apply_kernelsu_rules()
|
|||||||
// Allow all binder transactions
|
// Allow all binder transactions
|
||||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL);
|
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL);
|
||||||
|
|
||||||
// Allow system server kill su process
|
// 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", "getpgid");
|
||||||
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill");
|
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill");
|
||||||
|
|
||||||
rcu_read_unlock();
|
mutex_unlock(&ksu_rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MAX_SEPOL_LEN 128
|
#define MAX_SEPOL_LEN 128
|
||||||
@@ -149,17 +154,45 @@ void apply_kernelsu_rules()
|
|||||||
#define CMD_TYPE_CHANGE 8
|
#define CMD_TYPE_CHANGE 8
|
||||||
#define CMD_GENFSCON 9
|
#define CMD_GENFSCON 9
|
||||||
|
|
||||||
|
#ifdef CONFIG_64BIT
|
||||||
struct sepol_data {
|
struct sepol_data {
|
||||||
u32 cmd;
|
u32 cmd;
|
||||||
u32 subcmd;
|
u32 subcmd;
|
||||||
char __user *sepol1;
|
u64 field_sepol1;
|
||||||
char __user *sepol2;
|
u64 field_sepol2;
|
||||||
char __user *sepol3;
|
u64 field_sepol3;
|
||||||
char __user *sepol4;
|
u64 field_sepol4;
|
||||||
char __user *sepol5;
|
u64 field_sepol5;
|
||||||
char __user *sepol6;
|
u64 field_sepol6;
|
||||||
char __user *sepol7;
|
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,
|
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||||
char **object)
|
char **object)
|
||||||
@@ -204,15 +237,59 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
if (!getenforce()) {
|
if (!getenforce()) {
|
||||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
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;
|
struct sepol_data data;
|
||||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||||
pr_err("sepol: copy sepol_data failed.\n");
|
pr_err("sepol: copy sepol_data failed.\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
sepol1 = data.field_sepol1;
|
||||||
u32 cmd = data.cmd;
|
sepol2 = data.field_sepol2;
|
||||||
u32 subcmd = data.subcmd;
|
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();
|
rcu_read_lock();
|
||||||
|
|
||||||
@@ -226,22 +303,22 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char perm_buf[MAX_SEPOL_LEN];
|
char perm_buf[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
char *s, *t, *c, *p;
|
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");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) <
|
if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) <
|
||||||
0) {
|
0) {
|
||||||
pr_err("sepol: copy perm failed.\n");
|
pr_err("sepol: copy perm failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
@@ -271,24 +348,24 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char perm_set[MAX_SEPOL_LEN];
|
char perm_set[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
char *s, *t, *c;
|
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");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(operation, data.sepol4,
|
if (strncpy_from_user(operation, sepol4,
|
||||||
sizeof(operation)) < 0) {
|
sizeof(operation)) < 0) {
|
||||||
pr_err("sepol: copy operation failed.\n");
|
pr_err("sepol: copy operation failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(perm_set, data.sepol5, sizeof(perm_set)) <
|
if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
|
||||||
0) {
|
0) {
|
||||||
pr_err("sepol: copy perm_set failed.\n");
|
pr_err("sepol: copy perm_set failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
@@ -308,7 +385,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
} else if (cmd == CMD_TYPE_STATE) {
|
} else if (cmd == CMD_TYPE_STATE) {
|
||||||
char src[MAX_SEPOL_LEN];
|
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");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
@@ -328,11 +405,11 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char type[MAX_SEPOL_LEN];
|
char type[MAX_SEPOL_LEN];
|
||||||
char attr[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");
|
pr_err("sepol: copy type failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy attr failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
@@ -352,7 +429,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
} else if (cmd == CMD_ATTR) {
|
} else if (cmd == CMD_ATTR) {
|
||||||
char attr[MAX_SEPOL_LEN];
|
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");
|
pr_err("sepol: copy attr failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
@@ -369,28 +446,28 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char default_type[MAX_SEPOL_LEN];
|
char default_type[MAX_SEPOL_LEN];
|
||||||
char object[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");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(default_type, data.sepol4,
|
if (strncpy_from_user(default_type, sepol4,
|
||||||
sizeof(default_type)) < 0) {
|
sizeof(default_type)) < 0) {
|
||||||
pr_err("sepol: copy default_type failed.\n");
|
pr_err("sepol: copy default_type failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
char *real_object;
|
char *real_object;
|
||||||
if (data.sepol5 == NULL) {
|
if (sepol5 == NULL) {
|
||||||
real_object = NULL;
|
real_object = NULL;
|
||||||
} else {
|
} else {
|
||||||
if (strncpy_from_user(object, data.sepol5,
|
if (strncpy_from_user(object, sepol5,
|
||||||
sizeof(object)) < 0) {
|
sizeof(object)) < 0) {
|
||||||
pr_err("sepol: copy object failed.\n");
|
pr_err("sepol: copy object failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
@@ -409,19 +486,19 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char cls[MAX_SEPOL_LEN];
|
char cls[MAX_SEPOL_LEN];
|
||||||
char default_type[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");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(default_type, data.sepol4,
|
if (strncpy_from_user(default_type, sepol4,
|
||||||
sizeof(default_type)) < 0) {
|
sizeof(default_type)) < 0) {
|
||||||
pr_err("sepol: copy default_type failed.\n");
|
pr_err("sepol: copy default_type failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
@@ -442,15 +519,15 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char name[MAX_SEPOL_LEN];
|
char name[MAX_SEPOL_LEN];
|
||||||
char path[MAX_SEPOL_LEN];
|
char path[MAX_SEPOL_LEN];
|
||||||
char context[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");
|
pr_err("sepol: copy name failed.\n");
|
||||||
goto exit;
|
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");
|
pr_err("sepol: copy path failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(context, data.sepol3, sizeof(context)) <
|
if (strncpy_from_user(context, sepol3, sizeof(context)) <
|
||||||
0) {
|
0) {
|
||||||
pr_err("sepol: copy context failed.\n");
|
pr_err("sepol: copy context failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ perform_cleanup() {
|
|||||||
# Sets up or update KernelSU-Next environment
|
# Sets up or update KernelSU-Next environment
|
||||||
setup_kernelsu() {
|
setup_kernelsu() {
|
||||||
echo "[+] Setting up KernelSU-Next..."
|
echo "[+] Setting up KernelSU-Next..."
|
||||||
test -d "$GKI_ROOT/KernelSU-Next" || git clone https://github.com/rifsxd/KernelSU-Next && echo "[+] Repository cloned."
|
test -d "$GKI_ROOT/KernelSU-Next" || git clone https://github.com/KernelSU-Next/KernelSU-Next && echo "[+] Repository cloned."
|
||||||
cd "$GKI_ROOT/KernelSU-Next"
|
cd "$GKI_ROOT/KernelSU-Next"
|
||||||
git stash && echo "[-] Stashed current changes."
|
git stash && echo "[-] Stashed current changes."
|
||||||
if [ "$(git status | grep -Po 'v\d+(\.\d+)*' | head -n1)" ]; then
|
if [ "$(git status | grep -Po 'v\d+(\.\d+)*' | head -n1)" ]; then
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
#define SU_PATH "/system/bin/su"
|
#define SU_PATH "/system/bin/su"
|
||||||
#define SH_PATH "/system/bin/sh"
|
#define SH_PATH "/system/bin/sh"
|
||||||
|
|
||||||
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
static bool ksu_sucompat_non_kp __read_mostly = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
extern void escape_to_root();
|
extern void escape_to_root();
|
||||||
|
|
||||||
static void __user *userspace_stack_buffer(const void *d, size_t len)
|
static void __user *userspace_stack_buffer(const void *d, size_t len)
|
||||||
@@ -54,6 +58,12 @@ int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
|||||||
{
|
{
|
||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
|
|
||||||
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_non_kp) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -75,6 +85,12 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
|||||||
// const char sh[] = SH_PATH;
|
// const char sh[] = SH_PATH;
|
||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
|
|
||||||
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_non_kp){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -119,6 +135,12 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
|||||||
const char sh[] = KSUD_PATH;
|
const char sh[] = KSUD_PATH;
|
||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
|
|
||||||
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_non_kp) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (unlikely(!filename_ptr))
|
if (unlikely(!filename_ptr))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@@ -148,11 +170,32 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
|||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
char path[sizeof(su) + 1];
|
char path[sizeof(su) + 1];
|
||||||
|
|
||||||
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_non_kp) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (unlikely(!filename_user))
|
if (unlikely(!filename_user))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
memset(path, 0, sizeof(path));
|
// nofault variant fails probably due to pagefault_disable
|
||||||
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
// some cpus dont really have that good speculative execution
|
||||||
|
// substitute set_fs, check if pointer is valid
|
||||||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0)
|
||||||
|
if (!access_ok(VERIFY_READ, *filename_user, sizeof(path)))
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
if (!access_ok(*filename_user, sizeof(path)))
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
// success = returns number of bytes and should be less than path
|
||||||
|
long len = strncpy_from_user(path, *filename_user, sizeof(path));
|
||||||
|
if (len <= 0 || len > sizeof(path))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// strncpy_from_user_nofault does this too
|
||||||
|
path[sizeof(path) - 1] = '\0';
|
||||||
|
|
||||||
if (likely(memcmp(path, su, sizeof(su))))
|
if (likely(memcmp(path, su, sizeof(su))))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -170,6 +213,12 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
|||||||
|
|
||||||
int ksu_handle_devpts(struct inode *inode)
|
int ksu_handle_devpts(struct inode *inode)
|
||||||
{
|
{
|
||||||
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_non_kp) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!current->mm) {
|
if (!current->mm) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -198,21 +247,9 @@ int ksu_handle_devpts(struct inode *inode)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
|
||||||
__maybe_unused static int faccessat_handler_pre(struct kprobe *p,
|
static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
|
||||||
const char __user **filename_user = (const char **)&PT_REGS_PARM2(regs);
|
|
||||||
int *mode = (int *)&PT_REGS_PARM3(regs);
|
|
||||||
// Both sys_ and do_ is C function
|
|
||||||
int *flags = (int *)&PT_REGS_CCALL_PARM4(regs);
|
|
||||||
|
|
||||||
return ksu_handle_faccessat(dfd, filename_user, mode, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int sys_faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
|
||||||
{
|
{
|
||||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||||
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||||
@@ -223,23 +260,7 @@ static int sys_faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
|||||||
return ksu_handle_faccessat(dfd, filename_user, mode, NULL);
|
return ksu_handle_faccessat(dfd, filename_user, mode, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
__maybe_unused static int newfstatat_handler_pre(struct kprobe *p,
|
static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
|
||||||
const char __user **filename_user = (const char **)&PT_REGS_PARM2(regs);
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
|
|
||||||
// static int vfs_statx(int dfd, const char __user *filename, int flags, struct kstat *stat, u32 request_mask)
|
|
||||||
int *flags = (int *)&PT_REGS_PARM3(regs);
|
|
||||||
#else
|
|
||||||
// int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,int flag)
|
|
||||||
int *flags = (int *)&PT_REGS_CCALL_PARM4(regs);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return ksu_handle_stat(dfd, filename_user, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int sys_newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
|
||||||
{
|
{
|
||||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||||
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||||
@@ -250,17 +271,7 @@ static int sys_newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
|||||||
return ksu_handle_stat(dfd, filename_user, flags);
|
return ksu_handle_stat(dfd, filename_user, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
{
|
|
||||||
int *fd = (int *)&PT_REGS_PARM1(regs);
|
|
||||||
struct filename **filename_ptr =
|
|
||||||
(struct filename **)&PT_REGS_PARM2(regs);
|
|
||||||
|
|
||||||
return ksu_handle_execveat_sucompat(fd, filename_ptr, NULL, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
|
||||||
{
|
{
|
||||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||||
const char __user **filename_user =
|
const char __user **filename_user =
|
||||||
@@ -270,56 +281,6 @@ static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
|||||||
NULL);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 1
|
|
||||||
static struct kprobe faccessat_kp = {
|
|
||||||
.symbol_name = SYS_FACCESSAT_SYMBOL,
|
|
||||||
.pre_handler = sys_faccessat_handler_pre,
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
static struct kprobe faccessat_kp = {
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
|
|
||||||
.symbol_name = "do_faccessat",
|
|
||||||
#else
|
|
||||||
.symbol_name = "sys_faccessat",
|
|
||||||
#endif
|
|
||||||
.pre_handler = faccessat_handler_pre,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if 1
|
|
||||||
static struct kprobe newfstatat_kp = {
|
|
||||||
.symbol_name = SYS_NEWFSTATAT_SYMBOL,
|
|
||||||
.pre_handler = sys_newfstatat_handler_pre,
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
static struct kprobe newfstatat_kp = {
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
|
|
||||||
.symbol_name = "vfs_statx",
|
|
||||||
#else
|
|
||||||
.symbol_name = "vfs_fstatat",
|
|
||||||
#endif
|
|
||||||
.pre_handler = newfstatat_handler_pre,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if 1
|
|
||||||
static struct kprobe execve_kp = {
|
|
||||||
.symbol_name = SYS_EXECVE_SYMBOL,
|
|
||||||
.pre_handler = sys_execve_handler_pre,
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
static struct kprobe execve_kp = {
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
|
|
||||||
.symbol_name = "do_execveat_common",
|
|
||||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
|
|
||||||
.symbol_name = "__do_execve_file",
|
|
||||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
|
|
||||||
.symbol_name = "do_execveat_common",
|
|
||||||
#endif
|
|
||||||
.pre_handler = execve_handler_pre,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int pts_unix98_lookup_pre(struct kprobe *p, struct pt_regs *regs)
|
static int pts_unix98_lookup_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
struct inode *inode;
|
struct inode *inode;
|
||||||
@@ -333,35 +294,61 @@ static int pts_unix98_lookup_pre(struct kprobe *p, struct pt_regs *regs)
|
|||||||
return ksu_handle_devpts(inode);
|
return ksu_handle_devpts(inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct kprobe pts_unix98_lookup_kp = { .symbol_name =
|
static struct kprobe *init_kprobe(const char *name,
|
||||||
"pts_unix98_lookup",
|
kprobe_pre_handler_t handler)
|
||||||
.pre_handler =
|
{
|
||||||
pts_unix98_lookup_pre };
|
struct kprobe *kp = kzalloc(sizeof(struct kprobe), GFP_KERNEL);
|
||||||
|
if (!kp)
|
||||||
|
return NULL;
|
||||||
|
kp->symbol_name = name;
|
||||||
|
kp->pre_handler = handler;
|
||||||
|
|
||||||
|
int ret = register_kprobe(kp);
|
||||||
|
pr_info("sucompat: register_%s kprobe: %d\n", name, ret);
|
||||||
|
if (ret) {
|
||||||
|
kfree(kp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy_kprobe(struct kprobe **kp_ptr)
|
||||||
|
{
|
||||||
|
struct kprobe *kp = *kp_ptr;
|
||||||
|
if (!kp)
|
||||||
|
return;
|
||||||
|
unregister_kprobe(kp);
|
||||||
|
synchronize_rcu();
|
||||||
|
kfree(kp);
|
||||||
|
*kp_ptr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct kprobe *su_kps[4];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// sucompat: permited process can execute 'su' to gain root access.
|
// sucompat: permited process can execute 'su' to gain root access.
|
||||||
void ksu_sucompat_init()
|
void ksu_sucompat_init()
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
int ret;
|
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
|
||||||
ret = register_kprobe(&execve_kp);
|
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
|
||||||
pr_info("sucompat: execve_kp: %d\n", ret);
|
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
|
||||||
ret = register_kprobe(&newfstatat_kp);
|
su_kps[3] = init_kprobe("pts_unix98_lookup", pts_unix98_lookup_pre);
|
||||||
pr_info("sucompat: newfstatat_kp: %d\n", ret);
|
#else
|
||||||
ret = register_kprobe(&faccessat_kp);
|
ksu_sucompat_non_kp = true;
|
||||||
pr_info("sucompat: faccessat_kp: %d\n", ret);
|
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat, devpts\n");
|
||||||
ret = register_kprobe(&pts_unix98_lookup_kp);
|
|
||||||
pr_info("sucompat: devpts_kp: %d\n", ret);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ksu_sucompat_exit()
|
void ksu_sucompat_exit()
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
unregister_kprobe(&execve_kp);
|
for (int i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
||||||
unregister_kprobe(&newfstatat_kp);
|
destroy_kprobe(&su_kps[i]);
|
||||||
unregister_kprobe(&faccessat_kp);
|
}
|
||||||
unregister_kprobe(&pts_unix98_lookup_kp);
|
#else
|
||||||
|
ksu_sucompat_non_kp = false;
|
||||||
|
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat, devpts\n");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,12 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
|||||||
if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen))
|
if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen))
|
||||||
return FILLDIR_ACTOR_CONTINUE; // Skip "." and ".."
|
return FILLDIR_ACTOR_CONTINUE; // Skip "." and ".."
|
||||||
|
|
||||||
|
if (d_type == DT_DIR && namelen >= 8 && !strncmp(name, "vmdl", 4) &&
|
||||||
|
!strncmp(name + namelen - 4, ".tmp", 4)) {
|
||||||
|
pr_info("Skipping directory: %.*s\n", namelen, name);
|
||||||
|
return FILLDIR_ACTOR_CONTINUE; // Skip staging package
|
||||||
|
}
|
||||||
|
|
||||||
if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir,
|
if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir,
|
||||||
namelen, name) >= DATA_PATH_LEN) {
|
namelen, name) >= DATA_PATH_LEN) {
|
||||||
pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen,
|
pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen,
|
||||||
@@ -164,7 +170,11 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
|||||||
return FILLDIR_ACTOR_CONTINUE;
|
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);
|
strscpy(data->dirpath, dirpath, DATA_PATH_LEN);
|
||||||
|
#endif
|
||||||
data->depth = my_ctx->depth - 1;
|
data->depth = my_ctx->depth - 1;
|
||||||
list_add_tail(&data->list, my_ctx->data_path_list);
|
list_add_tail(&data->list, my_ctx->data_path_list);
|
||||||
} else {
|
} else {
|
||||||
@@ -206,12 +216,53 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
|||||||
return FILLDIR_ACTOR_CONTINUE;
|
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)
|
void search_manager(const char *path, int depth, struct list_head *uid_data)
|
||||||
{
|
{
|
||||||
int i, stop = 0;
|
int i, stop = 0;
|
||||||
struct list_head data_path_list;
|
struct list_head data_path_list;
|
||||||
INIT_LIST_HEAD(&data_path_list);
|
INIT_LIST_HEAD(&data_path_list);
|
||||||
|
unsigned long data_app_magic = 0;
|
||||||
|
|
||||||
// Initialize APK cache list
|
// Initialize APK cache list
|
||||||
struct apk_path_hash *pos, *n;
|
struct apk_path_hash *pos, *n;
|
||||||
list_for_each_entry(pos, &apk_path_hash_list, list) {
|
list_for_each_entry(pos, &apk_path_hash_list, list) {
|
||||||
@@ -220,11 +271,15 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
|
|||||||
|
|
||||||
// First depth
|
// First depth
|
||||||
struct data_path data;
|
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);
|
strscpy(data.dirpath, path, DATA_PATH_LEN);
|
||||||
|
#endif
|
||||||
data.depth = depth;
|
data.depth = depth;
|
||||||
list_add_tail(&data.list, &data_path_list);
|
list_add_tail(&data.list, &data_path_list);
|
||||||
|
|
||||||
for (i = depth; i > 0; i--) {
|
for (i = depth; i >= 0; i--) {
|
||||||
struct data_path *pos, *n;
|
struct data_path *pos, *n;
|
||||||
|
|
||||||
list_for_each_entry_safe(pos, n, &data_path_list, list) {
|
list_for_each_entry_safe(pos, n, &data_path_list, list) {
|
||||||
@@ -242,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));
|
pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file));
|
||||||
goto skip_iterate;
|
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);
|
iterate_dir(file, &ctx.ctx);
|
||||||
filp_close(file, NULL);
|
filp_close(file, NULL);
|
||||||
@@ -280,13 +353,25 @@ static bool is_uid_exist(uid_t uid, char *package, void *data)
|
|||||||
|
|
||||||
void track_throne()
|
void track_throne()
|
||||||
{
|
{
|
||||||
struct file *fp =
|
struct file *fp;
|
||||||
ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
|
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)) {
|
if (IS_ERR(fp)) {
|
||||||
pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n",
|
pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n", __func__, PTR_ERR(fp));
|
||||||
__func__, PTR_ERR(fp));
|
|
||||||
return;
|
return;
|
||||||
}
|
} else
|
||||||
|
pr_info("%s: %s found!\n", __func__, SYSTEM_PACKAGES_LIST_PATH);
|
||||||
|
|
||||||
struct list_head uid_list;
|
struct list_head uid_list;
|
||||||
INIT_LIST_HEAD(&uid_list);
|
INIT_LIST_HEAD(&uid_list);
|
||||||
@@ -355,12 +440,14 @@ void track_throne()
|
|||||||
if (ksu_is_manager_uid_valid()) {
|
if (ksu_is_manager_uid_valid()) {
|
||||||
pr_info("manager is uninstalled, invalidate it!\n");
|
pr_info("manager is uninstalled, invalidate it!\n");
|
||||||
ksu_invalidate_manager_uid();
|
ksu_invalidate_manager_uid();
|
||||||
|
goto prune;
|
||||||
}
|
}
|
||||||
pr_info("Searching manager...\n");
|
pr_info("Searching manager...\n");
|
||||||
search_manager("/data/app", 2, &uid_list);
|
search_manager("/data/app", 2, &uid_list);
|
||||||
pr_info("Search manager finished\n");
|
pr_info("Search manager finished\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prune:
|
||||||
// then prune the allowlist
|
// then prune the allowlist
|
||||||
ksu_prune_allowlist(is_uid_exist, &uid_list);
|
ksu_prune_allowlist(is_uid_exist, &uid_list);
|
||||||
out:
|
out:
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ void ksu_throne_tracker_exit();
|
|||||||
|
|
||||||
void track_throne();
|
void track_throne();
|
||||||
|
|
||||||
|
bool is_lock_held(const char *path);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -133,4 +133,6 @@ dependencies {
|
|||||||
implementation(libs.androidx.webkit)
|
implementation(libs.androidx.webkit)
|
||||||
|
|
||||||
implementation(libs.lsposed.cxx)
|
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">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".KernelSUApplication"
|
android:name=".KernelSUApplication"
|
||||||
@@ -24,6 +25,15 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</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>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -33,6 +43,13 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
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
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.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();
|
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"
|
extern "C"
|
||||||
JNIEXPORT jintArray JNICALL
|
JNIEXPORT jintArray JNICALL
|
||||||
Java_com_rifsxd_ksunext_Natives_getAllowList(JNIEnv *env, jobject) {
|
Java_com_rifsxd_ksunext_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||||
@@ -296,3 +310,19 @@ JNIEXPORT jboolean JNICALL
|
|||||||
Java_com_rifsxd_ksunext_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
|
Java_com_rifsxd_ksunext_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
|
||||||
return uid_should_umount(uid);
|
return uid_should_umount(uid);
|
||||||
}
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_com_rifsxd_ksunext_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
|
||||||
|
return is_su_enabled();
|
||||||
|
}
|
||||||
|
extern "C"
|
||||||
|
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 <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "ksu.h"
|
#include "ksu.h"
|
||||||
@@ -27,6 +28,11 @@
|
|||||||
|
|
||||||
#define CMD_IS_UID_GRANTED_ROOT 12
|
#define CMD_IS_UID_GRANTED_ROOT 12
|
||||||
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
#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) {
|
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||||
int32_t result = 0;
|
int32_t result = 0;
|
||||||
@@ -48,17 +54,31 @@ bool become_manager(const char* pkg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cache the result to avoid unnecessary syscall
|
// cache the result to avoid unnecessary syscall
|
||||||
static bool is_lkm;
|
static bool is_lkm = false;
|
||||||
int get_version() {
|
|
||||||
|
int get_version(void) {
|
||||||
int32_t version = -1;
|
int32_t version = -1;
|
||||||
int32_t lkm = 0;
|
int32_t flags = 0;
|
||||||
ksuctl(CMD_GET_VERSION, &version, &lkm);
|
ksuctl(CMD_GET_VERSION, &version, &flags);
|
||||||
if (!is_lkm && lkm != 0) {
|
if (!is_lkm && (flags & 0x1)) {
|
||||||
is_lkm = true;
|
is_lkm = true;
|
||||||
}
|
}
|
||||||
return version;
|
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) {
|
bool get_allow_list(int *uids, int *size) {
|
||||||
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
||||||
}
|
}
|
||||||
@@ -84,3 +104,18 @@ bool set_app_profile(const app_profile *profile) {
|
|||||||
bool get_app_profile(p_key_t key, app_profile *profile) {
|
bool get_app_profile(p_key_t key, app_profile *profile) {
|
||||||
return ksuctl(CMD_GET_APP_PROFILE, (void*) profile, nullptr);
|
return ksuctl(CMD_GET_APP_PROFILE, (void*) profile, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool set_su_enabled(bool enabled) {
|
||||||
|
return ksuctl(CMD_ENABLE_SU, (void*) enabled, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_su_enabled() {
|
||||||
|
bool enabled = true;
|
||||||
|
// 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();
|
int get_version();
|
||||||
|
|
||||||
|
uid_t get_manager_uid();
|
||||||
|
|
||||||
|
const char* get_hook_mode();
|
||||||
|
|
||||||
bool get_allow_list(int *uids, int *size);
|
bool get_allow_list(int *uids, int *size);
|
||||||
|
|
||||||
bool uid_should_umount(int uid);
|
bool uid_should_umount(int uid);
|
||||||
@@ -79,4 +83,10 @@ bool set_app_profile(const app_profile *profile);
|
|||||||
|
|
||||||
bool get_app_profile(p_key_t key, app_profile *profile);
|
bool get_app_profile(p_key_t key, app_profile *profile);
|
||||||
|
|
||||||
|
bool set_su_enabled(bool enabled);
|
||||||
|
|
||||||
|
bool is_su_enabled();
|
||||||
|
|
||||||
|
bool is_zygisk_enabled();
|
||||||
|
|
||||||
#endif //KERNELSU_KSU_H
|
#endif //KERNELSU_KSU_H
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
package com.rifsxd.ksunext
|
package com.rifsxd.ksunext
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.system.Os
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||||
|
import okhttp3.Cache
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
lateinit var ksuApp: KernelSUApplication
|
lateinit var ksuApp: KernelSUApplication
|
||||||
|
|
||||||
class KernelSUApplication : Application() {
|
class KernelSUApplication : Application() {
|
||||||
|
|
||||||
|
lateinit var okhttpClient: OkHttpClient
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
ksuApp = this
|
ksuApp = this
|
||||||
@@ -30,7 +36,20 @@ class KernelSUApplication : Application() {
|
|||||||
if (!webroot.exists()) {
|
if (!webroot.exists()) {
|
||||||
webroot.mkdir()
|
webroot.mkdir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Provide working env for rust's temp_dir()
|
||||||
|
Os.setenv("TMPDIR", cacheDir.absolutePath, true)
|
||||||
|
|
||||||
|
okhttpClient =
|
||||||
|
OkHttpClient.Builder().cache(Cache(File(cacheDir, "okhttp"), 10 * 1024 * 1024))
|
||||||
|
.addInterceptor { block ->
|
||||||
|
block.proceed(
|
||||||
|
block.request().newBuilder()
|
||||||
|
.header("User-Agent", "KernelSU/${BuildConfig.VERSION_CODE}")
|
||||||
|
.header("Accept-Language", Locale.getDefault().toLanguageTag()).build()
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,19 @@ data class KernelVersion(val major: Int, val patchLevel: Int, val subLevel: Int)
|
|||||||
|
|
||||||
return false
|
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 {
|
fun parseKernelVersion(version: String): KernelVersion {
|
||||||
|
|||||||
@@ -74,4 +74,4 @@ public class KsuService extends RootService {
|
|||||||
|
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,16 @@ object Natives {
|
|||||||
// 11640: Support query working mode, LKM or GKI
|
// 11640: Support query working mode, LKM or GKI
|
||||||
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
||||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
||||||
|
|
||||||
|
// 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 KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||||
|
|
||||||
const val ROOT_UID = 0
|
const val ROOT_UID = 0
|
||||||
@@ -47,6 +57,27 @@ object Natives {
|
|||||||
|
|
||||||
external fun uidShouldUmount(uid: Int): Boolean
|
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.
|
* Get the profile of the given package.
|
||||||
* @param key usually the package name
|
* @param key usually the package name
|
||||||
@@ -55,6 +86,15 @@ object Natives {
|
|||||||
external fun getAppProfile(key: String?, uid: Int): Profile
|
external fun getAppProfile(key: String?, uid: Int): Profile
|
||||||
external fun setAppProfile(profile: Profile?): Boolean
|
external fun setAppProfile(profile: Profile?): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `su` compat mode can be disabled temporarily.
|
||||||
|
* 0: disabled
|
||||||
|
* 1: enabled
|
||||||
|
* negative : error
|
||||||
|
*/
|
||||||
|
external fun isSuEnabled(): Boolean
|
||||||
|
external fun setSuEnabled(enabled: Boolean): Boolean
|
||||||
|
|
||||||
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
|
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
|
||||||
private const val NOBODY_UID = 9999
|
private const val NOBODY_UID = 9999
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.rifsxd.ksunext.ui
|
package com.rifsxd.ksunext.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
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.padding
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.union
|
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.Icon
|
||||||
import androidx.compose.material3.NavigationBar
|
import androidx.compose.material3.NavigationBar
|
||||||
import androidx.compose.material3.NavigationBarItem
|
import androidx.compose.material3.NavigationBarItem
|
||||||
@@ -29,11 +34,13 @@ import androidx.compose.material3.SnackbarHostState
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
@@ -49,13 +56,22 @@ import com.rifsxd.ksunext.Natives
|
|||||||
import com.rifsxd.ksunext.ksuApp
|
import com.rifsxd.ksunext.ksuApp
|
||||||
import com.rifsxd.ksunext.ui.screen.BottomBarDestination
|
import com.rifsxd.ksunext.ui.screen.BottomBarDestination
|
||||||
import com.rifsxd.ksunext.ui.theme.KernelSUTheme
|
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.LocalSnackbarHost
|
||||||
|
import com.rifsxd.ksunext.ui.util.LocaleHelper
|
||||||
import com.rifsxd.ksunext.ui.util.rootAvailable
|
import com.rifsxd.ksunext.ui.util.rootAvailable
|
||||||
import com.rifsxd.ksunext.ui.util.install
|
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() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
override fun attachBaseContext(newBase: Context?) {
|
||||||
|
super.attachBaseContext(newBase?.let { LocaleHelper.applyLanguage(it) })
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
||||||
// Enable edge to edge
|
// Enable edge to edge
|
||||||
@@ -67,14 +83,73 @@ class MainActivity : ComponentActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
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 {
|
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 navController = rememberNavController()
|
||||||
val snackBarHostState = remember { SnackbarHostState() }
|
val snackBarHostState = remember { SnackbarHostState() }
|
||||||
val currentDestination = navController.currentBackStackEntryAsState()?.value?.destination
|
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) {
|
val showBottomBar = when (currentDestination?.route) {
|
||||||
FlashScreenDestination.route -> false // Hide for FlashScreenDestination
|
FlashScreenDestination.route -> false // Hide for FlashScreenDestination
|
||||||
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen
|
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen
|
||||||
@@ -88,7 +163,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
||||||
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
||||||
) {
|
) {
|
||||||
BottomBar(navController)
|
BottomBar(navController, moduleUpdateCount)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||||
@@ -115,43 +190,59 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomBar(navController: NavHostController) {
|
private fun BottomBar(navController: NavHostController, moduleUpdateCount: Int) {
|
||||||
val navigator = navController.rememberDestinationsNavigator()
|
val navigator = navController.rememberDestinationsNavigator()
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
||||||
|
val suCompatDisabled = isSuCompatDisabled()
|
||||||
|
val suSFS = getSuSFS()
|
||||||
|
val susSUMode = susfsSUS_SU_Mode()
|
||||||
|
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
tonalElevation = 8.dp,
|
tonalElevation = 8.dp,
|
||||||
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
||||||
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
|
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
BottomBarDestination.entries.forEach { destination ->
|
BottomBarDestination.entries
|
||||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
.forEach { destination ->
|
||||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||||
NavigationBarItem(
|
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||||
selected = isCurrentDestOnBackStack,
|
NavigationBarItem(
|
||||||
onClick = {
|
selected = isCurrentDestOnBackStack,
|
||||||
if (isCurrentDestOnBackStack) {
|
onClick = {
|
||||||
navigator.popBackStack(destination.direction, false)
|
if (isCurrentDestOnBackStack) {
|
||||||
}
|
navigator.popBackStack(destination.direction, false)
|
||||||
navigator.navigate(destination.direction) {
|
|
||||||
popUpTo(NavGraphs.root) {
|
|
||||||
saveState = true
|
|
||||||
}
|
}
|
||||||
launchSingleTop = true
|
navigator.navigate(destination.direction) {
|
||||||
restoreState = true
|
popUpTo(NavGraphs.root) {
|
||||||
}
|
saveState = true
|
||||||
},
|
}
|
||||||
icon = {
|
launchSingleTop = true
|
||||||
if (isCurrentDestOnBackStack) {
|
restoreState = true
|
||||||
Icon(destination.iconSelected, stringResource(destination.label))
|
}
|
||||||
} else {
|
},
|
||||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
icon = {
|
||||||
}
|
// Show badge for Module icon if moduleUpdateCount > 0
|
||||||
},
|
if (destination == BottomBarDestination.Module && moduleUpdateCount > 0) {
|
||||||
label = { Text(stringResource(destination.label)) },
|
BadgedBox(badge = { Badge { Text(moduleUpdateCount.toString()) } }) {
|
||||||
alwaysShowLabel = true
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import com.rifsxd.ksunext.BuildConfig
|
import com.rifsxd.ksunext.BuildConfig
|
||||||
import com.rifsxd.ksunext.R
|
import com.rifsxd.ksunext.R
|
||||||
|
|
||||||
@@ -83,8 +84,9 @@ private fun AboutCardContent() {
|
|||||||
Column {
|
Column {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
stringResource(id = R.string.app_name),
|
text = stringResource(id = R.string.app_name),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
@@ -98,7 +100,7 @@ private fun AboutCardContent() {
|
|||||||
val annotatedString = AnnotatedString.Companion.fromHtml(
|
val annotatedString = AnnotatedString.Companion.fromHtml(
|
||||||
htmlString = stringResource(
|
htmlString = stringResource(
|
||||||
id = R.string.about_source_code,
|
id = R.string.about_source_code,
|
||||||
"<b><a href=\"https://github.com/rifsxd/KernelSU-Next\">GitHub</a></b>"
|
"<b><a href=\"https://github.com/KernelSU-Next/KernelSU-Next\">GitHub</a></b>"
|
||||||
),
|
),
|
||||||
linkStyles = TextLinkStyles(
|
linkStyles = TextLinkStyles(
|
||||||
style = SpanStyle(
|
style = SpanStyle(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.Saver
|
import androidx.compose.runtime.saveable.Saver
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
@@ -402,7 +403,11 @@ private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, di
|
|||||||
dismiss()
|
dismiss()
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = visuals.title)
|
Text(
|
||||||
|
text = visuals.title,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
if (visuals.isMarkdown) {
|
if (visuals.isMarkdown) {
|
||||||
|
|||||||
@@ -5,14 +5,20 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.selection.toggleable
|
import androidx.compose.foundation.selection.toggleable
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||||
|
import com.dergoogler.mmrl.ui.component.text.TextRow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchItem(
|
fun SwitchItem(
|
||||||
@@ -21,9 +27,11 @@ fun SwitchItem(
|
|||||||
summary: String? = null,
|
summary: String? = null,
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
onCheckedChange: (Boolean) -> Unit
|
beta: Boolean = false,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val stateAlpha = remember(checked, enabled) { Modifier.alpha(if (enabled) 1f else 0.5f) }
|
||||||
|
|
||||||
ListItem(
|
ListItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -36,10 +44,32 @@ fun SwitchItem(
|
|||||||
onValueChange = onCheckedChange
|
onValueChange = onCheckedChange
|
||||||
),
|
),
|
||||||
headlineContent = {
|
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 {
|
leadingContent = icon?.let {
|
||||||
{ Icon(icon, title) }
|
{
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = title
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Switch(
|
Switch(
|
||||||
@@ -51,7 +81,10 @@ fun SwitchItem(
|
|||||||
},
|
},
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
if (summary != null) {
|
if (summary != null) {
|
||||||
Text(summary)
|
Text(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
text = summary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ fun RootProfileConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
val currentNamespace = when (profile.namespace) {
|
val currentNamespace = when (profile.namespace) {
|
||||||
Natives.Profile.Namespace.INHERITED.ordinal -> stringResource(R.string.profile_namespace_inherited)
|
Natives.Profile.Namespace.INHERITED.ordinal -> stringResource(R.string.profile_namespace_inherited)
|
||||||
@@ -126,6 +127,7 @@ fun RootProfileConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
UidPanel(uid = profile.uid, label = "uid", onUidChange = {
|
UidPanel(uid = profile.uid, label = "uid", onUidChange = {
|
||||||
onProfileChange(
|
onProfileChange(
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -44,6 +45,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
@@ -55,6 +57,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.dropUnlessResumed
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
@@ -93,8 +97,10 @@ fun AppProfileScreen(
|
|||||||
val snackBarHost = LocalSnackbarHost.current
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val viewModel: SuperUserViewModel = viewModel()
|
||||||
val failToUpdateAppProfile = stringResource(R.string.failed_to_update_app_profile).format(appInfo.label)
|
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 failToUpdateSepolicy = stringResource(R.string.failed_to_update_sepolicy).format(appInfo.label)
|
||||||
|
val suNotAllowed = stringResource(R.string.su_not_allowed).format(appInfo.label)
|
||||||
|
|
||||||
val packageName = appInfo.packageName
|
val packageName = appInfo.packageName
|
||||||
val initialProfile = Natives.getAppProfile(packageName, appInfo.uid)
|
val initialProfile = Natives.getAppProfile(packageName, appInfo.uid)
|
||||||
@@ -108,7 +114,7 @@ fun AppProfileScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
onBack = { navigator.popBackStack() },
|
onBack = dropUnlessResumed { navigator.popBackStack() },
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -143,8 +149,13 @@ fun AppProfileScreen(
|
|||||||
},
|
},
|
||||||
onProfileChange = {
|
onProfileChange = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (it.allowSu && !it.rootUseDefault && it.rules.isNotEmpty()) {
|
if (it.allowSu) {
|
||||||
if (!setSepolicy(profile.name, it.rules)) {
|
// sync with allowlist.c - forbid_system_uid
|
||||||
|
if (appInfo.uid < 2000 && appInfo.uid != 1000) {
|
||||||
|
snackBarHost.showSnackbar(suNotAllowed)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
if (!it.rootUseDefault && it.rules.isNotEmpty() && !setSepolicy(profile.name, it.rules)) {
|
||||||
snackBarHost.showSnackbar(failToUpdateSepolicy)
|
snackBarHost.showSnackbar(failToUpdateSepolicy)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
@@ -153,6 +164,7 @@ fun AppProfileScreen(
|
|||||||
snackBarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
|
snackBarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
|
||||||
} else {
|
} else {
|
||||||
profile = it
|
profile = it
|
||||||
|
viewModel.updateAppProfile(packageName, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -176,7 +188,11 @@ private fun AppProfileInner(
|
|||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
AppMenuBox(packageName) {
|
AppMenuBox(packageName) {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(appLabel) },
|
headlineContent = { Text(
|
||||||
|
text = appLabel,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
) },
|
||||||
supportingContent = { Text(packageName) },
|
supportingContent = { Text(packageName) },
|
||||||
leadingContent = appIcon,
|
leadingContent = appIcon,
|
||||||
)
|
)
|
||||||
@@ -262,7 +278,11 @@ private fun TopBar(
|
|||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(stringResource(R.string.profile))
|
Text(
|
||||||
|
text = stringResource(R.string.profile),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -281,7 +301,11 @@ private fun ProfileBox(
|
|||||||
onModeChange: (Mode) -> Unit,
|
onModeChange: (Mode) -> Unit,
|
||||||
) {
|
) {
|
||||||
ListItem(
|
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) },
|
supportingContent = { Text(mode.text) },
|
||||||
leadingContent = { Icon(Icons.Filled.AccountCircle, null) },
|
leadingContent = { Icon(Icons.Filled.AccountCircle, null) },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
package com.rifsxd.ksunext.ui.screen
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
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.HorizontalDivider
|
||||||
|
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.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
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 androidx.lifecycle.compose.dropUnlessResumed
|
||||||
|
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 kotlinx.coroutines.withContext
|
||||||
|
import com.rifsxd.ksunext.Natives
|
||||||
|
import com.rifsxd.ksunext.ksuApp
|
||||||
|
import com.rifsxd.ksunext.R
|
||||||
|
import com.rifsxd.ksunext.ui.component.ConfirmResult
|
||||||
|
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
||||||
|
import com.rifsxd.ksunext.ui.component.rememberLoadingDialog
|
||||||
|
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||||
|
import com.rifsxd.ksunext.ui.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author rifsxd
|
||||||
|
* @date 2025/1/14.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Destination<RootGraph>
|
||||||
|
@Composable
|
||||||
|
fun BackupRestoreScreen(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 ->
|
||||||
|
val loadingDialog = rememberLoadingDialog()
|
||||||
|
val restoreDialog = rememberConfirmDialog()
|
||||||
|
val backupDialog = rememberConfirmDialog()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var showRebootDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showRebootDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showRebootDialog = false },
|
||||||
|
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 = {
|
||||||
|
showRebootDialog = false
|
||||||
|
reboot()
|
||||||
|
}) {
|
||||||
|
Text(stringResource(R.string.reboot))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showRebootDialog = false }) {
|
||||||
|
Text(stringResource(R.string.later))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val moduleBackup = stringResource(id = R.string.module_backup)
|
||||||
|
val backupMessage = stringResource(id = R.string.module_backup_message)
|
||||||
|
ListItem(
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Backup,
|
||||||
|
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)
|
||||||
|
if (result == ConfirmResult.Confirmed) {
|
||||||
|
loadingDialog.withLoading {
|
||||||
|
moduleBackup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (showRebootDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showRebootDialog = false },
|
||||||
|
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 = {
|
||||||
|
showRebootDialog = false
|
||||||
|
reboot()
|
||||||
|
}) {
|
||||||
|
Text(stringResource(R.string.reboot))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showRebootDialog = false }) {
|
||||||
|
Text(stringResource(R.string.later))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
var useOverlayFs by rememberSaveable {
|
||||||
|
mutableStateOf(readMountSystemFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
val moduleRestore = stringResource(id = R.string.module_restore)
|
||||||
|
val restoreMessage = stringResource(id = R.string.module_restore_message)
|
||||||
|
|
||||||
|
ListItem(
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Restore,
|
||||||
|
moduleRestore,
|
||||||
|
tint = if (useOverlayFs) MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) else MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headlineContent = {
|
||||||
|
Text(
|
||||||
|
moduleRestore,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = if (useOverlayFs) MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) else MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(
|
||||||
|
enabled = !useOverlayFs,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
val result = restoreDialog.awaitConfirm(title = moduleRestore, content = restoreMessage)
|
||||||
|
if (result == ConfirmResult.Confirmed) {
|
||||||
|
loadingDialog.withLoading {
|
||||||
|
moduleRestore()
|
||||||
|
showRebootDialog = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(thickness = Dp.Hairline)
|
||||||
|
|
||||||
|
val allowlistBackup = stringResource(id = R.string.allowlist_backup)
|
||||||
|
val allowlistbackupMessage = stringResource(id = R.string.allowlist_backup_message)
|
||||||
|
ListItem(
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Backup,
|
||||||
|
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)
|
||||||
|
if (result == ConfirmResult.Confirmed) {
|
||||||
|
loadingDialog.withLoading {
|
||||||
|
allowlistBackup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val allowlistRestore = stringResource(id = R.string.allowlist_restore)
|
||||||
|
val allowlistrestoreMessage = stringResource(id = R.string.allowlist_restore_message)
|
||||||
|
ListItem(
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Restore,
|
||||||
|
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)
|
||||||
|
if (result == ConfirmResult.Confirmed) {
|
||||||
|
loadingDialog.withLoading {
|
||||||
|
allowlistRestore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun TopBar(
|
||||||
|
onBack: () -> Unit = {},
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
|
) {
|
||||||
|
TopAppBar(
|
||||||
|
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) }
|
||||||
|
},
|
||||||
|
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||||
|
scrollBehavior = scrollBehavior
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun BackupPreview() {
|
||||||
|
BackupRestoreScreen(EmptyDestinationsNavigator)
|
||||||
|
}
|
||||||
@@ -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
|
package com.rifsxd.ksunext.ui.screen
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -27,18 +28,23 @@ import androidx.compose.material3.SnackbarHost
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.Modifier
|
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.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.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.dropUnlessResumed
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
@@ -66,6 +72,19 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
|||||||
var actionResult: Boolean
|
var actionResult: Boolean
|
||||||
var isActionRunning by rememberSaveable { mutableStateOf(true) }
|
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) {
|
BackHandler(enabled = isActionRunning) {
|
||||||
// Disable back button if action is running
|
// Disable back button if action is running
|
||||||
}
|
}
|
||||||
@@ -100,7 +119,7 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
|||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
isActionRunning = isActionRunning,
|
isActionRunning = isActionRunning,
|
||||||
onBack = {
|
onBack = dropUnlessResumed {
|
||||||
navigator.popBackStack()
|
navigator.popBackStack()
|
||||||
},
|
},
|
||||||
onSave = {
|
onSave = {
|
||||||
@@ -110,7 +129,7 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
|||||||
val date = format.format(Date())
|
val date = format.format(Date())
|
||||||
val file = File(
|
val file = File(
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
"KernelSU_module_action_log_${date}.log"
|
"KernelSU_Next_module_action_log_${date}.log"
|
||||||
)
|
)
|
||||||
file.writeText(logContent.toString())
|
file.writeText(logContent.toString())
|
||||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
||||||
@@ -147,7 +166,7 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
|||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(8.dp),
|
modifier = Modifier.padding(8.dp),
|
||||||
text = text,
|
text = if (developerOptionsEnabled) logContent.toString() else text,
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||||
@@ -160,7 +179,11 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
|||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(isActionRunning: Boolean, onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
private fun TopBar(isActionRunning: Boolean, onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.action)) },
|
title = { Text(
|
||||||
|
text = stringResource(R.string.action),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onBack,
|
onClick = onBack,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
package com.rifsxd.ksunext.ui.screen
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
@@ -33,21 +35,26 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
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.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.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.dropUnlessResumed
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
@@ -58,7 +65,13 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import com.rifsxd.ksunext.R
|
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.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.LkmSelection
|
||||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||||
import com.rifsxd.ksunext.ui.util.flashModule
|
import com.rifsxd.ksunext.ui.util.flashModule
|
||||||
@@ -77,41 +90,26 @@ enum class FlashingStatus {
|
|||||||
FAILED
|
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
|
// Lets you flash modules sequentially when mutiple zipUris are selected
|
||||||
fun flashModulesSequentially(
|
fun flashModulesSequentially(
|
||||||
uris: List<Uri>,
|
uris: List<Uri>,
|
||||||
onFinish: (Boolean, Int) -> Unit,
|
|
||||||
onStdout: (String) -> Unit,
|
onStdout: (String) -> Unit,
|
||||||
onStderr: (String) -> Unit
|
onStderr: (String) -> Unit
|
||||||
) {
|
): FlashResult {
|
||||||
val iterator = uris.iterator()
|
for (uri in uris) {
|
||||||
|
flashModule(uri, onStdout, onStderr).apply {
|
||||||
// Start processing from the first module inside a coroutine
|
if (code != 0) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
return FlashResult(code, err, showReboot)
|
||||||
// Define the recursive function within the coroutine
|
|
||||||
suspend fun processNext() {
|
|
||||||
if (iterator.hasNext()) {
|
|
||||||
// Flash the current module
|
|
||||||
flashModule(iterator.next(), onFinish = { showReboot, code ->
|
|
||||||
// If successful, continue to the next one
|
|
||||||
if (code == 0) {
|
|
||||||
// Recursively call to process the next module
|
|
||||||
launch {
|
|
||||||
processNext()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onFinish(showReboot, code) // If failed, finish the process
|
|
||||||
}
|
|
||||||
}, onStdout, onStderr)
|
|
||||||
} else {
|
|
||||||
// No more modules to process, finish the process
|
|
||||||
onFinish(true, 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the process
|
|
||||||
processNext()
|
|
||||||
}
|
}
|
||||||
|
return FlashResult(0, "", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,10 +119,14 @@ fun flashModulesSequentially(
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
fun FlashScreen(
|
||||||
|
navigator: DestinationsNavigator,
|
||||||
|
flashIt: FlashIt,
|
||||||
|
finishIntent: Boolean = false
|
||||||
|
) {
|
||||||
|
|
||||||
var text by rememberSaveable { mutableStateOf("") }
|
var text by rememberSaveable { mutableStateOf("") }
|
||||||
var tempText : String
|
var tempText: String
|
||||||
val logContent = rememberSaveable { StringBuilder() }
|
val logContent = rememberSaveable { StringBuilder() }
|
||||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -136,25 +138,68 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
mutableStateOf(FlashingStatus.FLASHING)
|
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) {
|
BackHandler(enabled = flashing == FlashingStatus.FLASHING) {
|
||||||
// Disable back button if flashing is running
|
// Disable back button if flashing is running
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
BackHandler(enabled = flashing != FlashingStatus.FLASHING) {
|
||||||
if (text.isNotEmpty()) {
|
navigator.popBackStack()
|
||||||
return@LaunchedEffect
|
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) {
|
withContext(Dispatchers.IO) {
|
||||||
flashIt(flashIt, onFinish = { showReboot, code ->
|
flashIt(pendingFlashIt!!, onStdout = {
|
||||||
if (code != 0) {
|
|
||||||
text += "Error: exit code = $code.\nPlease save and check the log.\n"
|
|
||||||
}
|
|
||||||
if (showReboot) {
|
|
||||||
text += "\n\n\n"
|
|
||||||
showFloatAction = true
|
|
||||||
}
|
|
||||||
flashing = if (code == 0) FlashingStatus.SUCCESS else FlashingStatus.FAILED
|
|
||||||
}, onStdout = {
|
|
||||||
tempText = "$it\n"
|
tempText = "$it\n"
|
||||||
if (tempText.startsWith("[H[J")) { // clear command
|
if (tempText.startsWith("[H[J")) { // clear command
|
||||||
text = tempText.substring(6)
|
text = tempText.substring(6)
|
||||||
@@ -164,7 +209,16 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
logContent.append(it).append("\n")
|
logContent.append(it).append("\n")
|
||||||
}, onStderr = {
|
}, onStderr = {
|
||||||
logContent.append(it).append("\n")
|
logContent.append(it).append("\n")
|
||||||
})
|
}).apply {
|
||||||
|
if (code != 0) {
|
||||||
|
text += "Error code: $code.\n $err Please save and check the log.\n"
|
||||||
|
}
|
||||||
|
if (showReboot) {
|
||||||
|
text += "\n\n\n"
|
||||||
|
showFloatAction = true
|
||||||
|
}
|
||||||
|
flashing = if (code == 0) FlashingStatus.SUCCESS else FlashingStatus.FAILED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,8 +226,9 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
flashing,
|
flashing,
|
||||||
onBack = {
|
onBack = dropUnlessResumed {
|
||||||
navigator.popBackStack()
|
navigator.popBackStack()
|
||||||
|
if (finishIntent) activity?.finish()
|
||||||
},
|
},
|
||||||
onSave = {
|
onSave = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -181,7 +236,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
val date = format.format(Date())
|
val date = format.format(Date())
|
||||||
val file = File(
|
val file = File(
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
"KernelSU_install_log_${date}.log"
|
"KernelSU_Next_install_log_${date}.log"
|
||||||
)
|
)
|
||||||
file.writeText(logContent.toString())
|
file.writeText(logContent.toString())
|
||||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
||||||
@@ -191,8 +246,8 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (showFloatAction) {
|
if (flashIt is FlashIt.FlashModules && (flashing == FlashingStatus.SUCCESS)) {
|
||||||
// Reboot button (bottom left)
|
// Reboot button for modules flashing
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -205,6 +260,58 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
text = { Text(text = stringResource(R.string.reboot)) }
|
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,
|
contentWindowInsets = WindowInsets.safeDrawing,
|
||||||
snackbarHost = { SnackbarHost(hostState = snackBarHost) }
|
snackbarHost = { SnackbarHost(hostState = snackBarHost) }
|
||||||
@@ -224,7 +331,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(8.dp),
|
modifier = Modifier.padding(8.dp),
|
||||||
text = text,
|
text = if (developerOptionsEnabled) logContent.toString() else text,
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||||
@@ -233,13 +340,24 @@ 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
|
@Parcelize
|
||||||
sealed class FlashIt : Parcelable {
|
sealed class FlashIt : Parcelable {
|
||||||
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :
|
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :
|
||||||
FlashIt()
|
FlashIt()
|
||||||
|
|
||||||
data class FlashModule(val uri: Uri) : FlashIt()
|
|
||||||
|
|
||||||
data class FlashModules(val uris: List<Uri>) : FlashIt()
|
data class FlashModules(val uris: List<Uri>) : FlashIt()
|
||||||
|
|
||||||
data object FlashRestore : FlashIt()
|
data object FlashRestore : FlashIt()
|
||||||
@@ -248,29 +366,26 @@ sealed class FlashIt : Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun flashIt(
|
fun flashIt(
|
||||||
flashIt: FlashIt, onFinish: (Boolean, Int) -> Unit,
|
flashIt: FlashIt,
|
||||||
onStdout: (String) -> Unit,
|
onStdout: (String) -> Unit,
|
||||||
onStderr: (String) -> Unit
|
onStderr: (String) -> Unit
|
||||||
) {
|
): FlashResult {
|
||||||
when (flashIt) {
|
return when (flashIt) {
|
||||||
is FlashIt.FlashBoot -> installBoot(
|
is FlashIt.FlashBoot -> installBoot(
|
||||||
flashIt.boot,
|
flashIt.boot,
|
||||||
flashIt.lkm,
|
flashIt.lkm,
|
||||||
flashIt.ota,
|
flashIt.ota,
|
||||||
onFinish,
|
|
||||||
onStdout,
|
onStdout,
|
||||||
onStderr
|
onStderr
|
||||||
)
|
)
|
||||||
|
|
||||||
is FlashIt.FlashModule -> flashModule(flashIt.uri, onFinish, onStdout, onStderr)
|
|
||||||
|
|
||||||
is FlashIt.FlashModules -> {
|
is FlashIt.FlashModules -> {
|
||||||
flashModulesSequentially(flashIt.uris, onFinish, onStdout, onStderr)
|
flashModulesSequentially(flashIt.uris, onStdout, onStderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
FlashIt.FlashRestore -> restoreBoot(onFinish, onStdout, onStderr)
|
FlashIt.FlashRestore -> restoreBoot(onStdout, onStderr)
|
||||||
|
|
||||||
FlashIt.FlashUninstall -> uninstallPermanently(onFinish, onStdout, onStderr)
|
FlashIt.FlashUninstall -> uninstallPermanently(onStdout, onStderr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +406,14 @@ private fun TopBar(
|
|||||||
FlashingStatus.SUCCESS -> R.string.flash_success
|
FlashingStatus.SUCCESS -> R.string.flash_success
|
||||||
FlashingStatus.FAILED -> R.string.flash_failed
|
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 = {
|
navigationIcon = {
|
||||||
|
|||||||
@@ -6,9 +6,15 @@ import android.os.PowerManager
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.StringRes
|
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.animation.*
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
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.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.pm.PackageInfoCompat
|
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.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
||||||
@@ -55,6 +68,10 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
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(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
@@ -83,6 +100,19 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
StatusCard(kernelVersion, ksuVersion, lkmMode) {
|
StatusCard(kernelVersion, ksuVersion, lkmMode) {
|
||||||
navigator.navigate(InstallScreenDestination)
|
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()) {
|
if (isManager && Natives.requireNewKernel()) {
|
||||||
WarningCard(
|
WarningCard(
|
||||||
stringResource(id = R.string.require_kernel_version).format(
|
stringResource(id = R.string.require_kernel_version).format(
|
||||||
@@ -97,12 +127,12 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
val checkUpdate =
|
val checkUpdate =
|
||||||
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
.getBoolean("check_update", true)
|
.getBoolean("check_update", false)
|
||||||
if (checkUpdate) {
|
if (checkUpdate) {
|
||||||
UpdateCard()
|
UpdateCard()
|
||||||
}
|
}
|
||||||
//NextCard()
|
//NextCard()
|
||||||
InfoCard()
|
InfoCard(autoExpand = developerOptionsEnabled)
|
||||||
IssueReportCard()
|
IssueReportCard()
|
||||||
//EXperimentalCard()
|
//EXperimentalCard()
|
||||||
Spacer(Modifier)
|
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
|
@Composable
|
||||||
fun UpdateCard() {
|
fun UpdateCard() {
|
||||||
val context = LocalContext.current
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(
|
private fun TopBar(
|
||||||
@@ -170,15 +285,55 @@ private fun TopBar(
|
|||||||
onInstallClick: () -> Unit,
|
onInstallClick: () -> Unit,
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
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(
|
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 = {
|
actions = {
|
||||||
if (kernelVersion.isGKI()) {
|
if (ksuVersion != null) {
|
||||||
IconButton(onClick = onInstallClick) {
|
if (kernelVersion.isGKI()) {
|
||||||
Icon(
|
IconButton(onClick = onInstallClick) {
|
||||||
imageVector = Icons.Filled.Archive,
|
Icon(
|
||||||
contentDescription = stringResource(id = R.string.install)
|
imageVector = Icons.Filled.Archive,
|
||||||
)
|
contentDescription = stringResource(id = R.string.install)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +343,7 @@ private fun TopBar(
|
|||||||
showDropdown = true
|
showDropdown = true
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Refresh,
|
imageVector = Icons.Filled.PowerSettingsNew,
|
||||||
contentDescription = stringResource(id = R.string.reboot)
|
contentDescription = stringResource(id = R.string.reboot)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -197,7 +352,8 @@ private fun TopBar(
|
|||||||
}) {
|
}) {
|
||||||
RebootDropdownItem(id = R.string.reboot)
|
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")
|
@Suppress("DEPRECATION")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
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
|
@Composable
|
||||||
private fun StatusCard(
|
private fun StatusCard(
|
||||||
kernelVersion: KernelVersion,
|
kernelVersion: KernelVersion,
|
||||||
ksuVersion: Int?,
|
ksuVersion: Int?,
|
||||||
lkmMode: Boolean?,
|
lkmMode: Boolean?,
|
||||||
|
moduleUpdateCount: Int = 0,
|
||||||
onClickInstall: () -> Unit = {}
|
onClickInstall: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var tapCount by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
||||||
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
|
if (ksuVersion != null) MaterialTheme.colorScheme.primaryContainer
|
||||||
else MaterialTheme.colorScheme.errorContainer
|
else MaterialTheme.colorScheme.errorContainer
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier
|
Row(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.clickable {
|
.fillMaxWidth()
|
||||||
if (kernelVersion.isGKI()) {
|
.clickable {
|
||||||
onClickInstall()
|
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 {
|
when {
|
||||||
ksuVersion != null -> {
|
ksuVersion != null -> {
|
||||||
val safeMode = when {
|
val workingMode = when {
|
||||||
Natives.isSafeMode -> " [${stringResource(id = R.string.safe_mode)}]"
|
lkmMode == true -> "LKM"
|
||||||
else -> ""
|
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(
|
Icon(
|
||||||
getSeasonalIcon(), // Use dynamic seasonal icon
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
contentDescription = stringResource(R.string.home_working)
|
contentDescription = stringResource(R.string.home_working)
|
||||||
)
|
)
|
||||||
Column(Modifier.padding(start = 20.dp)) {
|
Column(
|
||||||
Text(
|
modifier = Modifier.padding(start = 20.dp),
|
||||||
text = workingText,
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
style = MaterialTheme.typography.titleMedium
|
) {
|
||||||
)
|
val labelStyle = LabelItemDefaults.style
|
||||||
Spacer(Modifier.height(4.dp))
|
TextRow(
|
||||||
Text(
|
trailingContent = {
|
||||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
Row(
|
||||||
style = MaterialTheme.typography.bodyMedium
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
)
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
Spacer(Modifier.height(4.dp))
|
) {
|
||||||
Text(
|
LabelItem(
|
||||||
text = stringResource(
|
icon = if (Natives.isSafeMode) {
|
||||||
R.string.home_superuser_count, getSuperuserCount()
|
{
|
||||||
), style = MaterialTheme.typography.bodyMedium
|
Icon(
|
||||||
)
|
tint = labelStyle.contentColor,
|
||||||
Spacer(Modifier.height(4.dp))
|
imageVector = Icons.Filled.Security,
|
||||||
Text(
|
contentDescription = null
|
||||||
text = stringResource(R.string.home_module_count, getModuleCount()),
|
)
|
||||||
style = MaterialTheme.typography.bodyMedium
|
}
|
||||||
)
|
} else {
|
||||||
Spacer(Modifier.height(4.dp))
|
null
|
||||||
val suSFS = getSuSFS()
|
},
|
||||||
if (suSFS == "Supported") {
|
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(
|
||||||
text = stringResource(R.string.home_susfs, getSuSFS()),
|
text = stringResource(id = R.string.home_working),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.home_working_version, ksuVersion),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kernelVersion.isGKI() -> {
|
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)) {
|
Column(Modifier.padding(start = 20.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.home_not_installed),
|
text = stringResource(R.string.home_not_installed),
|
||||||
@@ -316,7 +510,7 @@ private fun StatusCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
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)) {
|
Column(Modifier.padding(start = 20.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.home_failure),
|
text = stringResource(R.string.home_failure),
|
||||||
@@ -357,31 +551,30 @@ fun WarningCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoCard() {
|
private fun InfoCard(autoExpand: Boolean = false) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
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 isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
val ksuVersion = if (isManager) Natives.version else null
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||||
useOverlayFs = prefs.getBoolean("use_overlay_fs", false)
|
|
||||||
}
|
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||||
|
|
||||||
|
LaunchedEffect(autoExpand) {
|
||||||
|
if (autoExpand) {
|
||||||
|
expanded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ElevatedCard {
|
ElevatedCard {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.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
|
@Composable
|
||||||
fun InfoCardItem(label: String, content: String, icon: Any? = null) {
|
fun InfoCardItem(label: String, content: String, icon: Any? = null) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -402,68 +595,137 @@ private fun InfoCard() {
|
|||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
style = MaterialTheme.typography.bodyLarge
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = content,
|
text = content,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
modifier = Modifier.padding(top = 4.dp)
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
InfoCardItem(
|
val managerVersion = getManagerVersion(context)
|
||||||
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_module_mount),
|
|
||||||
content = when {
|
|
||||||
ksuVersion == null -> stringResource(R.string.unavailable)
|
|
||||||
useOverlayFs -> stringResource(R.string.home_overlayfs_mount)
|
|
||||||
else -> stringResource(R.string.home_magic_mount)
|
|
||||||
},
|
|
||||||
icon = Icons.Filled.SettingsSuggest,
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
|
|
||||||
val suSFS = getSuSFS()
|
|
||||||
if (suSFS == "Supported") {
|
|
||||||
val susSUMode = if (isSUS_SU) "| SuS SU mode: ${susfsSUS_SU_Mode()}" else ""
|
|
||||||
InfoCardItem(
|
InfoCardItem(
|
||||||
label = stringResource(R.string.home_susfs_version),
|
label = stringResource(R.string.home_manager_version),
|
||||||
content = "${getSuSFSVersion()} (${getSuSFSVariant()}) $susSUMode",
|
content = if (
|
||||||
icon = painterResource(R.drawable.ic_sus),
|
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 = 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))
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,12 +738,13 @@ fun NextCard() {
|
|||||||
|
|
||||||
ElevatedCard {
|
ElevatedCard {
|
||||||
|
|
||||||
Row(modifier = Modifier
|
Row(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.clickable {
|
.fillMaxWidth()
|
||||||
uriHandler.openUri(url)
|
.clickable {
|
||||||
}
|
uriHandler.openUri(url)
|
||||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
}
|
||||||
|
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.home_next_kernelsu),
|
text = stringResource(R.string.home_next_kernelsu),
|
||||||
@@ -505,13 +768,15 @@ fun EXperimentalCard() {
|
|||||||
|
|
||||||
ElevatedCard {
|
ElevatedCard {
|
||||||
|
|
||||||
Row(modifier = Modifier
|
Row(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
/*.clickable {
|
.fillMaxWidth()
|
||||||
uriHandler.openUri(url)
|
/*.clickable {
|
||||||
}
|
uriHandler.openUri(url)
|
||||||
*/
|
}
|
||||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
*/
|
||||||
|
.padding(24.dp), verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.home_experimental_kernelsu),
|
text = stringResource(R.string.home_experimental_kernelsu),
|
||||||
@@ -558,17 +823,18 @@ fun IssueReportCard() {
|
|||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.issue_report_title),
|
text = stringResource(R.string.issue_report_title),
|
||||||
style = MaterialTheme.typography.titleSmall
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.issue_report_body),
|
text = stringResource(R.string.issue_report_body),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.issue_report_body_2),
|
text = stringResource(R.string.issue_report_body_2),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
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.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.FileUpload
|
import androidx.compose.material.icons.filled.FileUpload
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -30,6 +31,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
@@ -39,7 +41,9 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
@@ -47,6 +51,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.dropUnlessResumed
|
||||||
import com.maxkeppeker.sheets.core.models.base.Header
|
import com.maxkeppeker.sheets.core.models.base.Header
|
||||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||||
import com.maxkeppeler.sheets.list.ListDialog
|
import com.maxkeppeler.sheets.list.ListDialog
|
||||||
@@ -76,6 +81,36 @@ import com.rifsxd.ksunext.ui.util.rootAvailable
|
|||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@Composable
|
@Composable
|
||||||
fun InstallScreen(navigator: DestinationsNavigator) {
|
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 {
|
var installMethod by remember {
|
||||||
mutableStateOf<InstallMethod?>(null)
|
mutableStateOf<InstallMethod?>(null)
|
||||||
}
|
}
|
||||||
@@ -133,7 +168,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
onBack = { navigator.popBackStack() },
|
onBack = dropUnlessResumed { navigator.popBackStack() },
|
||||||
onLkmUpload = onLkmUpload,
|
onLkmUpload = onLkmUpload,
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
@@ -204,7 +239,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
|||||||
val rootAvailable = rootAvailable()
|
val rootAvailable = rootAvailable()
|
||||||
val isAbDevice = isAbDevice()
|
val isAbDevice = isAbDevice()
|
||||||
val selectFileTip = stringResource(
|
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 =
|
val radioOptions =
|
||||||
mutableListOf<InstallMethod>(InstallMethod.SelectFile(summary = selectFileTip))
|
mutableListOf<InstallMethod>(InstallMethod.SelectFile(summary = selectFileTip))
|
||||||
@@ -340,7 +375,11 @@ private fun TopBar(
|
|||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.install)) }, navigationIcon = {
|
title = { Text(
|
||||||
|
text = stringResource(R.string.install),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
) }, navigationIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onBack
|
onClick = onBack
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
) { 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.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -34,6 +35,7 @@ import androidx.compose.material3.TopAppBar
|
|||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -42,6 +44,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@@ -63,6 +66,9 @@ import com.ramcosta.composedestinations.annotation.Destination
|
|||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
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.DestinationsNavigator
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -113,8 +119,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
val loadingDialog = rememberLoadingDialog()
|
val loadingDialog = rememberLoadingDialog()
|
||||||
val shrinkDialog = rememberConfirmDialog()
|
val shrinkDialog = rememberConfirmDialog()
|
||||||
val restoreDialog = rememberConfirmDialog()
|
|
||||||
val backupDialog = rememberConfirmDialog()
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -146,7 +150,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
if (ksuVersion != null) {
|
if (ksuVersion != null) {
|
||||||
ListItem(
|
ListItem(
|
||||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
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)) },
|
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||||
@@ -159,10 +167,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
if (ksuVersion != null) {
|
if (ksuVersion != null) {
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
icon = Icons.Filled.RemoveModerator,
|
icon = Icons.Filled.FolderDelete,
|
||||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||||
checked = umountChecked
|
checked = umountChecked
|
||||||
|
|
||||||
) {
|
) {
|
||||||
if (Natives.setDefaultUmountModules(it)) {
|
if (Natives.setDefaultUmountModules(it)) {
|
||||||
umountChecked = it
|
umountChecked = it
|
||||||
@@ -170,6 +179,25 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ksuVersion != null) {
|
||||||
|
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
||||||
|
var isSuDisabled by rememberSaveable {
|
||||||
|
mutableStateOf(!Natives.isSuEnabled())
|
||||||
|
}
|
||||||
|
SwitchItem(
|
||||||
|
icon = Icons.Filled.RemoveModerator,
|
||||||
|
title = stringResource(id = R.string.settings_disable_su),
|
||||||
|
summary = stringResource(id = R.string.settings_disable_su_summary),
|
||||||
|
checked = isSuDisabled
|
||||||
|
) { checked ->
|
||||||
|
val shouldEnable = !checked
|
||||||
|
if (Natives.setSuEnabled(shouldEnable)) {
|
||||||
|
isSuDisabled = !shouldEnable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
val suSFS = getSuSFS()
|
val suSFS = getSuSFS()
|
||||||
@@ -201,66 +229,47 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasShownWarning = rememberSaveable { mutableStateOf(prefs.getBoolean("has_shown_warning", false)) }
|
|
||||||
|
|
||||||
var useOverlayFs by rememberSaveable {
|
var useOverlayFs by rememberSaveable {
|
||||||
mutableStateOf(
|
mutableStateOf(readMountSystemFile())
|
||||||
prefs.getBoolean("use_overlay_fs", false)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
LaunchedEffect(Unit) {
|
||||||
|
useOverlayFs = readMountSystemFile()
|
||||||
|
}
|
||||||
|
|
||||||
var showRebootDialog by remember { mutableStateOf(false) }
|
var showRebootDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
var showWarningDialog by remember { mutableStateOf(false) }
|
val isOverlayAvailable = overlayFsAvailable()
|
||||||
|
|
||||||
if (ksuVersion != null) {
|
if (ksuVersion != null && isOverlayAvailable) {
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
icon = Icons.Filled.Build,
|
icon = Icons.Filled.Build,
|
||||||
title = stringResource(id = R.string.use_overlay_fs),
|
title = stringResource(id = R.string.use_overlay_fs),
|
||||||
summary = stringResource(id = R.string.use_overlay_fs_summary),
|
summary = stringResource(id = R.string.use_overlay_fs_summary),
|
||||||
checked = useOverlayFs
|
checked = useOverlayFs
|
||||||
) {
|
) {
|
||||||
if (!hasShownWarning.value) {
|
prefs.edit().putBoolean("use_overlay_fs", it).apply()
|
||||||
showWarningDialog = true
|
useOverlayFs = it
|
||||||
|
if (useOverlayFs) {
|
||||||
|
moduleBackup()
|
||||||
|
updateMountSystemFile(true)
|
||||||
|
} else {
|
||||||
|
moduleMigration()
|
||||||
|
updateMountSystemFile(false)
|
||||||
}
|
}
|
||||||
|
if (isManager) install()
|
||||||
|
showRebootDialog = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showWarningDialog) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showWarningDialog = false },
|
|
||||||
title = { Text(stringResource(R.string.warning)) },
|
|
||||||
text = { Text(stringResource(R.string.warning_message)) },
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
showWarningDialog = false
|
|
||||||
prefs.edit().putBoolean("use_overlay_fs", !useOverlayFs).apply()
|
|
||||||
useOverlayFs = !useOverlayFs
|
|
||||||
if (useOverlayFs) {
|
|
||||||
moduleBackup()
|
|
||||||
} else {
|
|
||||||
moduleMigration()
|
|
||||||
}
|
|
||||||
if (isManager) install()
|
|
||||||
showRebootDialog = true
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.proceed))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { showWarningDialog = false }) {
|
|
||||||
Text(stringResource(R.string.cancel))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showRebootDialog) {
|
if (showRebootDialog) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showRebootDialog = false },
|
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)) },
|
text = { Text(stringResource(R.string.reboot_message)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
@@ -281,7 +290,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
var checkUpdate by rememberSaveable {
|
var checkUpdate by rememberSaveable {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
prefs.getBoolean("check_update", true)
|
prefs.getBoolean("check_update", false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
@@ -294,19 +303,97 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
checkUpdate = it
|
checkUpdate = it
|
||||||
}
|
}
|
||||||
|
|
||||||
var enableWebDebugging by rememberSaveable {
|
if (isOverlayAvailable && useOverlayFs) {
|
||||||
mutableStateOf(
|
val shrink = stringResource(id = R.string.shrink_sparse_image)
|
||||||
prefs.getBoolean("enable_web_debugging", false)
|
val shrinkMessage = stringResource(id = R.string.shrink_sparse_image_message)
|
||||||
|
ListItem(
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Compress,
|
||||||
|
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)
|
||||||
|
if (result == ConfirmResult.Confirmed) {
|
||||||
|
loadingDialog.withLoading {
|
||||||
|
shrinkModules()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.DeveloperMode,
|
val customization = stringResource(id = R.string.customization)
|
||||||
title = stringResource(id = R.string.enable_web_debugging),
|
ListItem(
|
||||||
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
leadingContent = {
|
||||||
checked = enableWebDebugging
|
Icon(
|
||||||
) {
|
Icons.Filled.Palette,
|
||||||
prefs.edit().putBoolean("enable_web_debugging", it).apply()
|
customization
|
||||||
enableWebDebugging = it
|
)
|
||||||
|
},
|
||||||
|
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) {
|
||||||
|
UninstallItem(navigator) {
|
||||||
|
loadingDialog.withLoading(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var showBottomsheet by remember { mutableStateOf(false) }
|
var showBottomsheet by remember { mutableStateOf(false) }
|
||||||
@@ -318,7 +405,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
stringResource(id = R.string.export_log)
|
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 {
|
modifier = Modifier.clickable {
|
||||||
showBottomsheet = true
|
showBottomsheet = true
|
||||||
}
|
}
|
||||||
@@ -340,7 +431,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
.clickable {
|
.clickable {
|
||||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
||||||
val current = LocalDateTime.now().format(formatter)
|
val current = LocalDateTime.now().format(formatter)
|
||||||
exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz")
|
exportBugreportLauncher.launch("KernelSU_Next_bugreport_${current}.tar.gz")
|
||||||
showBottomsheet = false
|
showBottomsheet = false
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -418,87 +509,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
val moduleBackup = stringResource(id = R.string.module_backup)
|
|
||||||
val backupMessage = stringResource(id = R.string.module_backup_message)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Backup,
|
|
||||||
moduleBackup
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(moduleBackup) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val result = backupDialog.awaitConfirm(title = moduleBackup, content = backupMessage)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
loadingDialog.withLoading {
|
|
||||||
moduleBackup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
val moduleRestore = stringResource(id = R.string.module_restore)
|
|
||||||
val restoreMessage = stringResource(id = R.string.module_restore_message)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Restore,
|
|
||||||
moduleRestore
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(moduleRestore) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val result = restoreDialog.awaitConfirm(title = moduleRestore, content = restoreMessage)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
loadingDialog.withLoading {
|
|
||||||
moduleRestore()
|
|
||||||
showRebootDialog = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useOverlayFs) {
|
|
||||||
val shrink = stringResource(id = R.string.shrink_sparse_image)
|
|
||||||
val shrinkMessage = stringResource(id = R.string.shrink_sparse_image_message)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Compress,
|
|
||||||
shrink
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(shrink) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val result = shrinkDialog.awaitConfirm(title = shrink, content = shrinkMessage)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
loadingDialog.withLoading {
|
|
||||||
shrinkModules()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
|
||||||
if (lkmMode) {
|
|
||||||
UninstallItem(navigator) {
|
|
||||||
loadingDialog.withLoading(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val about = stringResource(id = R.string.about)
|
val about = stringResource(id = R.string.about)
|
||||||
ListItem(
|
ListItem(
|
||||||
leadingContent = {
|
leadingContent = {
|
||||||
@@ -507,7 +517,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
about
|
about
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
headlineContent = { Text(about) },
|
headlineContent = { Text(
|
||||||
|
text = about,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
aboutDialog.show()
|
aboutDialog.show()
|
||||||
}
|
}
|
||||||
@@ -519,7 +533,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun UninstallItem(
|
fun UninstallItem(
|
||||||
navigator: DestinationsNavigator,
|
navigator: DestinationsNavigator,
|
||||||
withLoading: suspend (suspend () -> Unit) -> Unit
|
withLoading: suspend (suspend () -> Unit) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -557,7 +571,11 @@ fun UninstallItem(
|
|||||||
uninstall
|
uninstall
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
headlineContent = { Text(uninstall) },
|
headlineContent = { Text(
|
||||||
|
text = uninstall,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
uninstallDialog.show()
|
uninstallDialog.show()
|
||||||
}
|
}
|
||||||
@@ -621,10 +639,14 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(
|
private fun TopBar(
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
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),
|
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
@@ -634,4 +656,4 @@ private fun TopBar(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SettingsPreview() {
|
private fun SettingsPreview() {
|
||||||
SettingScreen(EmptyDestinationsNavigator)
|
SettingScreen(EmptyDestinationsNavigator)
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ import androidx.compose.material.icons.filled.MoreVert
|
|||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
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.text.TextStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
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.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
|
||||||
@@ -43,30 +47,21 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
LaunchedEffect(key1 = navigator) {
|
LaunchedEffect(navigator) {
|
||||||
viewModel.search = ""
|
viewModel.search = ""
|
||||||
if (viewModel.appList.isEmpty()) {
|
if (viewModel.appList.isEmpty()) {
|
||||||
viewModel.fetchAppList()
|
viewModel.fetchAppList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(viewModel.search) {
|
|
||||||
if (viewModel.search.isEmpty()) {
|
|
||||||
listState.scrollToItem(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (viewModel.refreshOnReturn) {
|
|
||||||
viewModel.fetchAppList()
|
|
||||||
viewModel.refreshOnReturn = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
SearchAppBar(
|
SearchAppBar(
|
||||||
title = { Text(stringResource(R.string.superuser)) },
|
title = { Text(
|
||||||
|
text = stringResource(R.string.superuser),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
) },
|
||||||
searchText = viewModel.search,
|
searchText = viewModel.search,
|
||||||
onSearchTextChange = { viewModel.search = it },
|
onSearchTextChange = { viewModel.search = it },
|
||||||
onClearClick = { viewModel.search = "" },
|
onClearClick = { viewModel.search = "" },
|
||||||
@@ -101,7 +96,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}, onClick = {
|
}, onClick = {
|
||||||
viewModel.showSystemApps = !viewModel.showSystemApps
|
viewModel.updateShowSystemApps(!viewModel.showSystemApps)
|
||||||
showDropdown = false
|
showDropdown = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -127,7 +122,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
) {
|
) {
|
||||||
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
||||||
AppItem(app) {
|
AppItem(app) {
|
||||||
viewModel.refreshOnReturn = true
|
|
||||||
navigator.navigate(AppProfileScreenDestination(app))
|
navigator.navigate(AppProfileScreenDestination(app))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,20 +138,54 @@ private fun AppItem(
|
|||||||
) {
|
) {
|
||||||
ListItem(
|
ListItem(
|
||||||
modifier = Modifier.clickable(onClick = onClickListener),
|
modifier = Modifier.clickable(onClick = onClickListener),
|
||||||
headlineContent = { Text(app.label) },
|
headlineContent = { Text(
|
||||||
|
text = app.label,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
) },
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
Column {
|
Column {
|
||||||
Text(app.packageName)
|
Text(
|
||||||
FlowRow {
|
text = app.packageName,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
FlowRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
if (app.allowSu) {
|
if (app.allowSu) {
|
||||||
LabelText(label = "ROOT")
|
LabelItem(
|
||||||
|
text = "ROOT",
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
if (Natives.uidShouldUmount(app.uid)) {
|
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) {
|
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.layout.safeDrawing
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
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.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
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.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
@@ -49,7 +53,9 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.dropUnlessResumed
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
|
||||||
@@ -61,6 +67,15 @@ import kotlinx.coroutines.launch
|
|||||||
import com.rifsxd.ksunext.R
|
import com.rifsxd.ksunext.R
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.TemplateViewModel
|
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
|
* @author weishu
|
||||||
* @date 2023/10/20.
|
* @date 2023/10/20.
|
||||||
@@ -90,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(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
@@ -100,7 +139,7 @@ fun AppProfileTemplateScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TopBar(
|
TopBar(
|
||||||
onBack = { navigator.popBackStack() },
|
onBack = dropUnlessResumed { navigator.popBackStack() },
|
||||||
onSync = {
|
onSync = {
|
||||||
scope.launch { viewModel.fetchTemplates(true) }
|
scope.launch { viewModel.fetchTemplates(true) }
|
||||||
},
|
},
|
||||||
@@ -136,18 +175,30 @@ fun AppProfileTemplateScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
ExtendedFloatingActionButton(
|
AnimatedVisibility(
|
||||||
onClick = {
|
visible = showFab,
|
||||||
navigator.navigate(
|
enter = scaleIn(
|
||||||
TemplateEditorScreenDestination(
|
animationSpec = tween(200),
|
||||||
TemplateViewModel.TemplateInfo(),
|
initialScale = 0.8f
|
||||||
false
|
) + 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) },
|
||||||
icon = { Icon(Icons.Filled.Add, null) },
|
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
||||||
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
)
|
||||||
)
|
}
|
||||||
},
|
},
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
@@ -159,11 +210,12 @@ fun AppProfileTemplateScreen(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
contentPadding = remember {
|
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 ->
|
items(viewModel.templateList, key = { it.id }) { app ->
|
||||||
@@ -185,7 +237,11 @@ private fun TemplateItem(
|
|||||||
.clickable {
|
.clickable {
|
||||||
navigator.navigate(TemplateEditorScreenDestination(template, !template.local))
|
navigator.navigate(TemplateEditorScreenDestination(template, !template.local))
|
||||||
},
|
},
|
||||||
headlineContent = { Text(template.name) },
|
headlineContent = { Text(
|
||||||
|
text = template.name,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
) },
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
@@ -194,14 +250,19 @@ private fun TemplateItem(
|
|||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||||
)
|
)
|
||||||
Text(template.description)
|
Text(template.description)
|
||||||
FlowRow {
|
|
||||||
LabelText(label = "UID: ${template.uid}")
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
LabelText(label = "GID: ${template.gid}")
|
|
||||||
LabelText(label = template.context)
|
FlowRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
LabelItem(text = "UID: ${template.uid}")
|
||||||
|
LabelItem(text = "GID: ${template.gid}")
|
||||||
|
LabelItem(text = template.context)
|
||||||
if (template.local) {
|
if (template.local) {
|
||||||
LabelText(label = "local")
|
LabelItem(text = "local")
|
||||||
} else {
|
} else {
|
||||||
LabelText(label = "remote")
|
LabelItem(text = "remote")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +281,11 @@ private fun TopBar(
|
|||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(stringResource(R.string.settings_profile_template))
|
Text(
|
||||||
|
text = stringResource(R.string.settings_profile_template),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
@@ -44,6 +45,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.lifecycle.compose.dropUnlessResumed
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
||||||
@@ -105,7 +107,7 @@ fun TemplateEditorScreen(
|
|||||||
},
|
},
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
summary = titleSummary,
|
summary = titleSummary,
|
||||||
onBack = { navigator.navigateBack(result = !readOnly) },
|
onBack = dropUnlessResumed { navigator.navigateBack(result = !readOnly) },
|
||||||
onDelete = {
|
onDelete = {
|
||||||
if (deleteAppProfileTemplate(template.id)) {
|
if (deleteAppProfileTemplate(template.id)) {
|
||||||
navigator.navigateBack(result = true)
|
navigator.navigateBack(result = true)
|
||||||
@@ -261,7 +263,11 @@ private fun TopBar(
|
|||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Column {
|
Column {
|
||||||
Text(title)
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
)
|
||||||
if (summary.isNotBlank()) {
|
if (summary.isNotBlank()) {
|
||||||
Text(
|
Text(
|
||||||
text = summary,
|
text = summary,
|
||||||
|
|||||||
@@ -2,9 +2,19 @@ package com.rifsxd.ksunext.ui.theme
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val YELLOW = Color(0xFFeed502)
|
val PRIMARY = Color(0xFF8AADF4) // Catppuccin Blue
|
||||||
val YELLOW_LIGHT = Color(0xFFffff52)
|
val PRIMARY_LIGHT = Color(0xFFB7BDF8) // Catppuccin Lavender
|
||||||
val SECONDARY_LIGHT = Color(0xffa9817f)
|
val SECONDARY_LIGHT = Color(0xFFA6DA95) // Catppuccin Green
|
||||||
|
|
||||||
val YELLOW_DARK = Color(0xFFb7a400)
|
val PRIMARY_DARK = Color(0xFF7DC4E4) // Catppuccin Sky
|
||||||
val SECONDARY_DARK = Color(0xFF4c2b2b)
|
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
|
package com.rifsxd.ksunext.ui.theme
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
@@ -8,39 +11,115 @@ import androidx.compose.material3.dynamicDarkColorScheme
|
|||||||
import androidx.compose.material3.dynamicLightColorScheme
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
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
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
private val DarkColorScheme = darkColorScheme(
|
||||||
primary = YELLOW,
|
primary = PRIMARY,
|
||||||
secondary = YELLOW_DARK,
|
secondary = PRIMARY_DARK,
|
||||||
tertiary = SECONDARY_DARK
|
tertiary = SECONDARY_DARK
|
||||||
)
|
)
|
||||||
|
|
||||||
private val LightColorScheme = lightColorScheme(
|
private val LightColorScheme = lightColorScheme(
|
||||||
primary = YELLOW,
|
primary = PRIMARY,
|
||||||
secondary = YELLOW_LIGHT,
|
secondary = PRIMARY_LIGHT,
|
||||||
tertiary = SECONDARY_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
|
@Composable
|
||||||
fun KernelSUTheme(
|
fun KernelSUTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
// Dynamic color is available on Android 12+
|
// Dynamic color is available on Android 12+
|
||||||
dynamicColor: Boolean = true,
|
dynamicColor: Boolean = true,
|
||||||
|
amoledMode: Boolean = false,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = when {
|
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 -> {
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
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
|
darkTheme -> DarkColorScheme
|
||||||
else -> LightColorScheme
|
else -> LightColorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SystemBarStyle(
|
||||||
|
darkMode = darkTheme
|
||||||
|
)
|
||||||
|
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = colorScheme,
|
colorScheme = colorScheme,
|
||||||
typography = Typography,
|
typography = Typography,
|
||||||
content = content
|
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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import android.os.Environment
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.rifsxd.ksunext.ksuApp
|
||||||
import com.rifsxd.ksunext.ui.util.module.LatestVersionInfo
|
import com.rifsxd.ksunext.ui.util.module.LatestVersionInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,11 +64,11 @@ fun download(
|
|||||||
|
|
||||||
fun checkNewVersion(): LatestVersionInfo {
|
fun checkNewVersion(): LatestVersionInfo {
|
||||||
// Next version updates
|
// Next version updates
|
||||||
val url = "https://api.github.com/repos/rifsxd/KernelSU-Next/releases/latest"
|
val url = "https://api.github.com/repos/KernelSU-Next/KernelSU-Next/releases/latest"
|
||||||
// default null value if failed
|
// default null value if failed
|
||||||
val defaultValue = LatestVersionInfo()
|
val defaultValue = LatestVersionInfo()
|
||||||
runCatching {
|
runCatching {
|
||||||
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
ksuApp.okhttpClient.newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
||||||
.use { response ->
|
.use { response ->
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.io.File
|
|||||||
* @date 2023/1/1.
|
* @date 2023/1/1.
|
||||||
*/
|
*/
|
||||||
private const val TAG = "KsuCli"
|
private const val TAG = "KsuCli"
|
||||||
|
private const val BUSYBOX = "/data/adb/ksu/bin/busybox"
|
||||||
|
|
||||||
private fun ksuDaemonMagicPath(): String {
|
private fun ksuDaemonMagicPath(): String {
|
||||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud_magic.so"
|
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"
|
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
|
// Get the path based on the user's choice
|
||||||
fun getKsuDaemonPath(): String {
|
fun getKsuDaemonPath(): String {
|
||||||
val prefs = ksuApp.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val useOverlayFs = readMountSystemFile()
|
||||||
val useOverlayFs = prefs.getBoolean("use_overlay_fs", false)
|
|
||||||
|
|
||||||
return if (useOverlayFs) {
|
return if (useOverlayFs) {
|
||||||
ksuDaemonOverlayfsPath()
|
ksuDaemonOverlayfsPath()
|
||||||
@@ -49,6 +56,21 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
object KsuCli {
|
object KsuCli {
|
||||||
val SHELL: Shell = createRootShell()
|
val SHELL: Shell = createRootShell()
|
||||||
val GLOBAL_MNT_SHELL: Shell = createRootShell(true)
|
val GLOBAL_MNT_SHELL: Shell = createRootShell(true)
|
||||||
@@ -84,17 +106,17 @@ fun createRootShell(globalMnt: Boolean = false): Shell {
|
|||||||
val builder = Shell.Builder.create()
|
val builder = Shell.Builder.create()
|
||||||
return try {
|
return try {
|
||||||
if (globalMnt) {
|
if (globalMnt) {
|
||||||
builder.build(getKsuDaemonPath(), "debug", "su", "-g")
|
builder.build(ksuDaemonMagicPath(), "debug", "su", "-g")
|
||||||
} else {
|
} else {
|
||||||
builder.build(getKsuDaemonPath(), "debug", "su")
|
builder.build(ksuDaemonMagicPath(), "debug", "su")
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.w(TAG, "ksu failed: ", e)
|
Log.w(TAG, "ksu failed: ", e)
|
||||||
try {
|
try {
|
||||||
if (globalMnt) {
|
if (globalMnt) {
|
||||||
builder.build("su")
|
|
||||||
} else {
|
|
||||||
builder.build("su", "-mm")
|
builder.build("su", "-mm")
|
||||||
|
} else {
|
||||||
|
builder.build("su")
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e(TAG, "su failed: ", e)
|
Log.e(TAG, "su failed: ", e)
|
||||||
@@ -190,10 +212,9 @@ private fun flashWithIO(
|
|||||||
|
|
||||||
fun flashModule(
|
fun flashModule(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
onFinish: (Boolean, Int) -> Unit,
|
|
||||||
onStdout: (String) -> Unit,
|
onStdout: (String) -> Unit,
|
||||||
onStderr: (String) -> Unit
|
onStderr: (String) -> Unit
|
||||||
): Boolean {
|
): FlashResult {
|
||||||
val resolver = ksuApp.contentResolver
|
val resolver = ksuApp.contentResolver
|
||||||
with(resolver.openInputStream(uri)) {
|
with(resolver.openInputStream(uri)) {
|
||||||
val file = File(ksuApp.cacheDir, "module.zip")
|
val file = File(ksuApp.cacheDir, "module.zip")
|
||||||
@@ -206,15 +227,14 @@ fun flashModule(
|
|||||||
|
|
||||||
file.delete()
|
file.delete()
|
||||||
|
|
||||||
onFinish(result.isSuccess, result.code)
|
return FlashResult(result)
|
||||||
return result.isSuccess
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runModuleAction(
|
fun runModuleAction(
|
||||||
moduleId: String, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
moduleId: String, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val shell = getRootShell()
|
val shell = createRootShell(true)
|
||||||
|
|
||||||
val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||||
override fun onAddElement(s: String?) {
|
override fun onAddElement(s: String?) {
|
||||||
@@ -236,21 +256,19 @@ fun runModuleAction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun restoreBoot(
|
fun restoreBoot(
|
||||||
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||||
): Boolean {
|
): FlashResult {
|
||||||
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
||||||
val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr)
|
val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr)
|
||||||
onFinish(result.isSuccess, result.code)
|
return FlashResult(result)
|
||||||
return result.isSuccess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uninstallPermanently(
|
fun uninstallPermanently(
|
||||||
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||||
): Boolean {
|
): FlashResult {
|
||||||
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
||||||
val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr)
|
val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr)
|
||||||
onFinish(result.isSuccess, result.code)
|
return FlashResult(result)
|
||||||
return result.isSuccess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) {
|
suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) {
|
||||||
@@ -268,10 +286,9 @@ fun installBoot(
|
|||||||
bootUri: Uri?,
|
bootUri: Uri?,
|
||||||
lkm: LkmSelection,
|
lkm: LkmSelection,
|
||||||
ota: Boolean,
|
ota: Boolean,
|
||||||
onFinish: (Boolean, Int) -> Unit,
|
|
||||||
onStdout: (String) -> Unit,
|
onStdout: (String) -> Unit,
|
||||||
onStderr: (String) -> Unit,
|
onStderr: (String) -> Unit,
|
||||||
): Boolean {
|
): FlashResult {
|
||||||
val resolver = ksuApp.contentResolver
|
val resolver = ksuApp.contentResolver
|
||||||
|
|
||||||
val bootFile = bootUri?.let { uri ->
|
val bootFile = bootUri?.let { uri ->
|
||||||
@@ -334,15 +351,14 @@ fun installBoot(
|
|||||||
lkmFile?.delete()
|
lkmFile?.delete()
|
||||||
|
|
||||||
// if boot uri is empty, it is direct install, when success, we should show reboot button
|
// if boot uri is empty, it is direct install, when success, we should show reboot button
|
||||||
onFinish(bootUri == null && result.isSuccess, result.code)
|
return FlashResult(result, bootUri == null && result.isSuccess)
|
||||||
return result.isSuccess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reboot(reason: String = "") {
|
fun reboot(reason: String = "") {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
if (reason == "recovery") {
|
if (reason == "recovery") {
|
||||||
// KEYCODE_POWER = 26, hide incorrect "Factory data reset" message
|
// KEYCODE_POWER = 26, hide incorrect "Factory data reset" message
|
||||||
ShellUtils.fastCmd(shell, "/system/bin/input keyevent 26")
|
ShellUtils.fastCmd(shell, "/system/bin/reboot $reason")
|
||||||
}
|
}
|
||||||
ShellUtils.fastCmd(shell, "/system/bin/svc power reboot $reason || /system/bin/reboot $reason")
|
ShellUtils.fastCmd(shell, "/system/bin/svc power reboot $reason || /system/bin/reboot $reason")
|
||||||
}
|
}
|
||||||
@@ -444,7 +460,7 @@ fun getFileName(context: Context, uri: Uri): String {
|
|||||||
|
|
||||||
fun moduleBackupDir(): String? {
|
fun moduleBackupDir(): String? {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
val baseBackupDir = "/data/adb/modules_bak"
|
val baseBackupDir = "/data/adb/ksu/backup/modules"
|
||||||
val resultBase = ShellUtils.fastCmd(shell, "mkdir -p $baseBackupDir").trim()
|
val resultBase = ShellUtils.fastCmd(shell, "mkdir -p $baseBackupDir").trim()
|
||||||
if (resultBase.isNotEmpty()) return null
|
if (resultBase.isNotEmpty()) return null
|
||||||
|
|
||||||
@@ -463,16 +479,87 @@ fun moduleBackup(): Boolean {
|
|||||||
|
|
||||||
val checkEmptyCommand = "if [ -z \"$(ls -A /data/adb/modules)\" ]; then echo 'empty'; fi"
|
val checkEmptyCommand = "if [ -z \"$(ls -A /data/adb/modules)\" ]; then echo 'empty'; fi"
|
||||||
val resultCheckEmpty = ShellUtils.fastCmd(shell, checkEmptyCommand).trim()
|
val resultCheckEmpty = ShellUtils.fastCmd(shell, checkEmptyCommand).trim()
|
||||||
|
|
||||||
if (resultCheckEmpty == "empty") {
|
if (resultCheckEmpty == "empty") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val backupDir = moduleBackupDir() ?: return false
|
val timestamp = ShellUtils.fastCmd(shell, "date +%Y%m%d_%H%M%S").trim()
|
||||||
val command = "cp -rp /data/adb/modules/* $backupDir"
|
if (timestamp.isEmpty()) return false
|
||||||
val result = ShellUtils.fastCmd(shell, command).trim()
|
|
||||||
|
|
||||||
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"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 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
|
||||||
|
|
||||||
|
val extractCmd = "$BUSYBOX tar -xpf $tarPath -C /data/adb/modules_update"
|
||||||
|
val extractResult = ShellUtils.fastCmd(shell, extractCmd).trim()
|
||||||
|
return extractResult.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allowlistBackup(): Boolean {
|
||||||
|
val shell = getRootShell()
|
||||||
|
|
||||||
|
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 timestamp = ShellUtils.fastCmd(shell, "date +%Y%m%d_%H%M%S").trim()
|
||||||
|
if (timestamp.isEmpty()) return false
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moduleMigration(): Boolean {
|
fun moduleMigration(): Boolean {
|
||||||
@@ -483,26 +570,6 @@ fun moduleMigration(): Boolean {
|
|||||||
return result.isEmpty()
|
return result.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moduleRestore(): Boolean {
|
|
||||||
val shell = getRootShell()
|
|
||||||
|
|
||||||
val command = "ls -t /data/adb/modules_bak | head -n 1"
|
|
||||||
val latestBackupDir = ShellUtils.fastCmd(shell, command).trim()
|
|
||||||
|
|
||||||
if (latestBackupDir.isEmpty()) return false
|
|
||||||
|
|
||||||
val sourceDir = "/data/adb/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()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSuSFSDaemonPath(): String {
|
private fun getSuSFSDaemonPath(): String {
|
||||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libsusfsd.so"
|
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libsusfsd.so"
|
||||||
}
|
}
|
||||||
@@ -548,6 +615,32 @@ fun susfsSUS_SU_Mode(): String {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun currentMountSystem(): String {
|
||||||
|
val shell = getRootShell()
|
||||||
|
val cmd = "module mount"
|
||||||
|
val result = ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim()
|
||||||
|
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 {
|
fun setAppProfileTemplate(id: String, template: String): Boolean {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
val escapedTemplate = template.replace("\"", "\\\"")
|
val escapedTemplate = template.replace("\"", "\\\"")
|
||||||
@@ -581,4 +674,4 @@ fun launchApp(packageName: String) {
|
|||||||
fun restartApp(packageName: String) {
|
fun restartApp(packageName: String) {
|
||||||
forceStopApp(packageName)
|
forceStopApp(packageName)
|
||||||
launchApp(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,7 +101,7 @@ fun getBugreportFile(context: Context): File {
|
|||||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
||||||
val current = LocalDateTime.now().format(formatter)
|
val current = LocalDateTime.now().format(formatter)
|
||||||
|
|
||||||
val targetFile = File(context.cacheDir, "KernelSU_bugreport_${current}.tar.gz")
|
val targetFile = File(context.cacheDir, "KernelSU_Next_bugreport_${current}.tar.gz")
|
||||||
|
|
||||||
shell.newJob().add("tar czf ${targetFile.absolutePath} -C ${bugreportDir.absolutePath} .").exec()
|
shell.newJob().add("tar czf ${targetFile.absolutePath} -C ${bugreportDir.absolutePath} .").exec()
|
||||||
shell.newJob().add("rm -rf ${bugreportDir.absolutePath}").exec()
|
shell.newJob().add("rm -rf ${bugreportDir.absolutePath}").exec()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.rifsxd.ksunext.ui.viewmodel
|
package com.rifsxd.ksunext.ui.viewmodel
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
@@ -8,13 +9,19 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import java.io.File
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import com.rifsxd.ksunext.ksuApp
|
||||||
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
||||||
import com.rifsxd.ksunext.ui.util.listModules
|
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.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
@@ -38,7 +45,10 @@ class ModuleViewModel : ViewModel() {
|
|||||||
val updateJson: String,
|
val updateJson: String,
|
||||||
val hasWebUi: Boolean,
|
val hasWebUi: Boolean,
|
||||||
val hasActionScript: Boolean,
|
val hasActionScript: Boolean,
|
||||||
val dirId: String
|
val dirId: String,
|
||||||
|
val size: Long,
|
||||||
|
val banner: String,
|
||||||
|
val zygiskRequired: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ModuleUpdateInfo(
|
data class ModuleUpdateInfo(
|
||||||
@@ -48,9 +58,6 @@ class ModuleViewModel : ViewModel() {
|
|||||||
val changelog: String,
|
val changelog: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
var isOverlayAvailable by mutableStateOf(overlayFsAvailable())
|
|
||||||
private set
|
|
||||||
|
|
||||||
var isRefreshing by mutableStateOf(false)
|
var isRefreshing by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -58,11 +65,21 @@ class ModuleViewModel : ViewModel() {
|
|||||||
|
|
||||||
var sortAToZ by mutableStateOf(false)
|
var sortAToZ by mutableStateOf(false)
|
||||||
var sortZToA 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 moduleList by derivedStateOf {
|
||||||
val comparator = when {
|
val comparator = when {
|
||||||
|
sortWebUiFirst -> compareByDescending<ModuleInfo> { it.hasWebUi }
|
||||||
|
sortEnabledFirst -> compareByDescending<ModuleInfo> { it.enabled }
|
||||||
|
sortActionFirst -> compareByDescending<ModuleInfo> { it.hasActionScript }
|
||||||
sortAToZ -> compareBy<ModuleInfo> { it.name.lowercase() }
|
sortAToZ -> compareBy<ModuleInfo> { it.name.lowercase() }
|
||||||
sortZToA -> compareByDescending<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 }
|
else -> compareBy<ModuleInfo> { it.dirId }
|
||||||
}.thenBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
|
}.thenBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
|
||||||
|
|
||||||
@@ -83,58 +100,81 @@ class ModuleViewModel : ViewModel() {
|
|||||||
isNeedRefresh = true
|
isNeedRefresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var zipUris by mutableStateOf<List<Uri>>(emptyList())
|
||||||
|
|
||||||
|
fun updateZipUris(uris: List<Uri>) {
|
||||||
|
zipUris = uris
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearZipUris() {
|
||||||
|
zipUris = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
fun fetchModuleList() {
|
fun fetchModuleList() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
|
||||||
isRefreshing = true
|
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 {
|
val array = JSONArray(result)
|
||||||
isOverlayAvailable = overlayFsAvailable()
|
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)
|
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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> {
|
fun checkUpdate(m: ModuleInfo): Triple<String, String, String> {
|
||||||
val empty = Triple("", "", "")
|
val empty = Triple("", "", "")
|
||||||
if (m.updateJson.isEmpty() || m.remove || m.update || !m.enabled) {
|
if (m.updateJson.isEmpty() || m.remove || m.update || !m.enabled) {
|
||||||
@@ -144,11 +184,8 @@ class ModuleViewModel : ViewModel() {
|
|||||||
val result = kotlin.runCatching {
|
val result = kotlin.runCatching {
|
||||||
val url = m.updateJson
|
val url = m.updateJson
|
||||||
Log.i(TAG, "checkUpdate url: $url")
|
Log.i(TAG, "checkUpdate url: $url")
|
||||||
val response = okhttp3.OkHttpClient()
|
val response = ksuApp.okhttpClient.newCall(
|
||||||
.newCall(
|
okhttp3.Request.Builder().url(url).build()
|
||||||
okhttp3.Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.build()
|
|
||||||
).execute()
|
).execute()
|
||||||
Log.d(TAG, "checkUpdate code: ${response.code}")
|
Log.d(TAG, "checkUpdate code: ${response.code}")
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
@@ -167,7 +204,8 @@ class ModuleViewModel : ViewModel() {
|
|||||||
JSONObject(result)
|
JSONObject(result)
|
||||||
}.getOrNull() ?: return empty
|
}.getOrNull() ?: return empty
|
||||||
|
|
||||||
val version = updateJson.optString("version", "")
|
var version = updateJson.optString("version", "")
|
||||||
|
version = sanitizeVersionString(version)
|
||||||
val versionCode = updateJson.optInt("versionCode", 0)
|
val versionCode = updateJson.optInt("versionCode", 0)
|
||||||
val zipUrl = updateJson.optString("zipUrl", "")
|
val zipUrl = updateJson.optString("zipUrl", "")
|
||||||
val changelog = updateJson.optString("changelog", "")
|
val changelog = updateJson.optString("changelog", "")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.rifsxd.ksunext.ui.viewmodel
|
package com.rifsxd.ksunext.ui.viewmodel
|
||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
@@ -9,6 +10,7 @@ import android.os.IBinder
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
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.KsuService
|
||||||
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
||||||
import com.rifsxd.ksunext.ui.util.KsuCli
|
import com.rifsxd.ksunext.ui.util.KsuCli
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
class SuperUserViewModel : ViewModel() {
|
class SuperUserViewModel : ViewModel() {
|
||||||
|
|
||||||
var refreshOnReturn by mutableStateOf(false)
|
|
||||||
public set
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "SuperUserViewModel"
|
private const val TAG = "SuperUserViewModel"
|
||||||
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
||||||
|
private var profileOverrides by mutableStateOf<Map<String, Natives.Profile>>(emptyMap())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -66,16 +69,26 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val prefs = ksuApp.getSharedPreferences("settings", Context.MODE_PRIVATE)!!
|
||||||
|
|
||||||
var search by mutableStateOf("")
|
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)
|
var isRefreshing by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
fun updateShowSystemApps(newValue: Boolean) {
|
||||||
|
showSystemApps = newValue
|
||||||
|
prefs.edit { putBoolean("show_system_apps", newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
private val sortedList by derivedStateOf {
|
private val sortedList by derivedStateOf {
|
||||||
val comparator = compareBy<AppInfo> {
|
val comparator = compareBy<AppInfo> {
|
||||||
when {
|
when {
|
||||||
it.allowSu -> 0
|
it.profile != null && it.profile.allowSu -> 0
|
||||||
it.hasCustomProfile -> 1
|
it.profile != null && (
|
||||||
|
if (it.profile.allowSu) !it.profile.rootUseDefault else !it.profile.nonRootUseDefault
|
||||||
|
) -> 1
|
||||||
else -> 2
|
else -> 2
|
||||||
}
|
}
|
||||||
}.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))
|
}.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))
|
||||||
@@ -85,7 +98,9 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val appList by derivedStateOf {
|
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(
|
it.label.contains(search, true) || it.packageName.contains(
|
||||||
search,
|
search,
|
||||||
true
|
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(
|
private suspend inline fun connectKsuService(
|
||||||
crossinline onDisconnect: () -> Unit = {}
|
crossinline onDisconnect: () -> Unit = {}
|
||||||
): Pair<IBinder, ServiceConnection> = suspendCoroutine {
|
): Pair<IBinder, ServiceConnection> = suspendCoroutine {
|
||||||
|
|||||||
@@ -11,18 +11,17 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import com.rifsxd.ksunext.Natives
|
import com.rifsxd.ksunext.Natives
|
||||||
|
import com.rifsxd.ksunext.ksuApp
|
||||||
import com.rifsxd.ksunext.profile.Capabilities
|
import com.rifsxd.ksunext.profile.Capabilities
|
||||||
import com.rifsxd.ksunext.profile.Groups
|
import com.rifsxd.ksunext.profile.Groups
|
||||||
import com.rifsxd.ksunext.ui.util.getAppProfileTemplate
|
import com.rifsxd.ksunext.ui.util.getAppProfileTemplate
|
||||||
import com.rifsxd.ksunext.ui.util.listAppProfileTemplates
|
import com.rifsxd.ksunext.ui.util.listAppProfileTemplates
|
||||||
import com.rifsxd.ksunext.ui.util.setAppProfileTemplate
|
import com.rifsxd.ksunext.ui.util.setAppProfileTemplate
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,13 +137,7 @@ class TemplateViewModel : ViewModel() {
|
|||||||
|
|
||||||
private fun fetchRemoteTemplates() {
|
private fun fetchRemoteTemplates() {
|
||||||
runCatching {
|
runCatching {
|
||||||
val client: OkHttpClient = OkHttpClient.Builder()
|
ksuApp.okhttpClient.newCall(
|
||||||
.connectTimeout(5, TimeUnit.SECONDS)
|
|
||||||
.writeTimeout(5, TimeUnit.SECONDS)
|
|
||||||
.readTimeout(10, TimeUnit.SECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(
|
|
||||||
Request.Builder().url(TEMPLATE_INDEX_URL).build()
|
Request.Builder().url(TEMPLATE_INDEX_URL).build()
|
||||||
).execute().use { response ->
|
).execute().use { response ->
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
@@ -155,7 +148,7 @@ private fun fetchRemoteTemplates() {
|
|||||||
0.until(remoteTemplateIds.length()).forEach { i ->
|
0.until(remoteTemplateIds.length()).forEach { i ->
|
||||||
val id = remoteTemplateIds.getString(i)
|
val id = remoteTemplateIds.getString(i)
|
||||||
Log.i(TAG, "fetch template: $id")
|
Log.i(TAG, "fetch template: $id")
|
||||||
val templateJson = client.newCall(
|
val templateJson = ksuApp.okhttpClient.newCall(
|
||||||
Request.Builder().url(TEMPLATE_URL.format(id)).build()
|
Request.Builder().url(TEMPLATE_URL.format(id)).build()
|
||||||
).runCatching {
|
).runCatching {
|
||||||
execute().use { response ->
|
execute().use { response ->
|
||||||
|
|||||||
@@ -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;
|
package com.rifsxd.ksunext.ui.webui;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.webkit.WebResourceResponse;
|
import android.webkit.WebResourceResponse;
|
||||||
|
|
||||||
@@ -15,8 +16,12 @@ import com.topjohnwu.superuser.io.SuFileInputStream;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import com.rifsxd.ksunext.ui.webui.MonetColorsProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler class to open files from file system by root access
|
* Handler class to open files from file system by root access
|
||||||
* For more information about android storage please refer to
|
* 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.
|
* which files can be loaded.
|
||||||
* @throws IllegalArgumentException if the directory is not allowed.
|
* @throws IllegalArgumentException if the directory is not allowed.
|
||||||
*/
|
*/
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
public SuFilePathHandler(@NonNull Context context, @NonNull File directory, Shell rootShell) {
|
public SuFilePathHandler(@NonNull Context context, @NonNull File directory, Shell rootShell) {
|
||||||
try {
|
try {
|
||||||
|
mContext = context;
|
||||||
mDirectory = new File(getCanonicalDirPath(directory));
|
mDirectory = new File(getCanonicalDirPath(directory));
|
||||||
if (!isAllowedInternalStorageDir(context)) {
|
if (!isAllowedInternalStorageDir(context)) {
|
||||||
throw new IllegalArgumentException("The given directory \"" + directory
|
throw new IllegalArgumentException("The given directory \"" + directory
|
||||||
@@ -130,6 +138,16 @@ public final class SuFilePathHandler implements WebViewAssetLoader.PathHandler {
|
|||||||
@WorkerThread
|
@WorkerThread
|
||||||
@NonNull
|
@NonNull
|
||||||
public WebResourceResponse handle(@NonNull String path) {
|
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 {
|
try {
|
||||||
File file = getCanonicalFileIfChild(mDirectory, path);
|
File file = getCanonicalFileIfChild(mDirectory, path);
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ class WebUIActivity : ComponentActivity() {
|
|||||||
|
|
||||||
private var rootShell: Shell? = null
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
||||||
// Enable edge to edge
|
// Enable edge to edge
|
||||||
@@ -39,14 +43,17 @@ class WebUIActivity : ComponentActivity() {
|
|||||||
val name = intent.getStringExtra("name")!!
|
val name = intent.getStringExtra("name")!!
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
|
setTaskDescription(ActivityManager.TaskDescription("WebUI-Next | $name"))
|
||||||
} else {
|
} else {
|
||||||
val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("WebUI-Next | $name").build()
|
||||||
setTaskDescription(taskDescription)
|
setTaskDescription(taskDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
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 moduleDir = "/data/adb/modules/${moduleId}"
|
||||||
val webRoot = File("${moduleDir}/webroot")
|
val webRoot = File("${moduleDir}/webroot")
|
||||||
@@ -84,7 +91,24 @@ class WebUIActivity : ComponentActivity() {
|
|||||||
settings.allowFileAccess = false
|
settings.allowFileAccess = false
|
||||||
webviewInterface = WebViewInterface(this@WebUIActivity, this, moduleDir)
|
webviewInterface = WebViewInterface(this@WebUIActivity, this, moduleDir)
|
||||||
addJavascriptInterface(webviewInterface, "ksu")
|
addJavascriptInterface(webviewInterface, "ksu")
|
||||||
setWebViewClient(webViewClient)
|
setWebViewClient(object : WebViewClient() {
|
||||||
|
override fun shouldInterceptRequest(
|
||||||
|
view: WebView,
|
||||||
|
request: WebResourceRequest
|
||||||
|
): WebResourceResponse? {
|
||||||
|
return webViewAssetLoader.shouldInterceptRequest(request.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")
|
loadUrl("https://mui.kernelsu.org/index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user