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
488 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1de68a8ed2 | ||
|
|
e0c461322b | ||
|
|
edb99a2c1a | ||
|
|
eaab98b7ec | ||
|
|
aa37bcc368 | ||
|
|
107cd4add0 | ||
|
|
94c4b41ea3 | ||
|
|
0bde9047b9 | ||
|
|
bc9927b9b6 | ||
|
|
d4f4c0a0cc | ||
|
|
0aaae919c0 | ||
|
|
3f4c23a34f | ||
|
|
d69a72c658 | ||
|
|
adbff41a22 | ||
|
|
512f84504e | ||
|
|
c44f48c8a4 | ||
|
|
5c6c3870a0 | ||
|
|
c6b5440682 | ||
|
|
a917314e84 | ||
|
|
948975ba35 | ||
|
|
45ad73e9dd | ||
|
|
892a62afdf | ||
|
|
d61de07c21 | ||
|
|
4382dca515 | ||
|
|
2c9078e038 | ||
|
|
81f4f09d0c | ||
|
|
2241696498 | ||
|
|
514c5458ed | ||
|
|
d30bb33a9c | ||
|
|
ec7fe6b039 | ||
|
|
3fd61e0ee5 | ||
|
|
8fc85993f1 | ||
|
|
df07860e44 | ||
|
|
bd03c296a1 | ||
|
|
14b7861f44 | ||
|
|
3c421b1362 | ||
|
|
c6b60a24e8 | ||
|
|
1baedd89b7 | ||
|
|
b567e9b275 | ||
|
|
3ba5b028d4 | ||
|
|
4d633a1e0e | ||
|
|
f08fcec777 | ||
|
|
afe6ad7261 | ||
|
|
54b26fd32c | ||
|
|
4b56b14a4f | ||
|
|
9b4c6057a0 | ||
|
|
873740ef1c | ||
|
|
ad80c1ea77 | ||
|
|
d5c6f0affb | ||
|
|
162056f9ff | ||
|
|
3af1d0354d | ||
|
|
6adb2eaf51 | ||
|
|
ce91e4fedb | ||
|
|
a36390ea03 | ||
|
|
e31e0271cb | ||
|
|
b97fc2bec2 | ||
|
|
10875ee190 | ||
|
|
fdb01c918a | ||
|
|
6afa86d2ae | ||
|
|
25fa6b7b9b | ||
|
|
a361fa3272 | ||
|
|
4a9733c078 | ||
|
|
31aa571bc2 | ||
|
|
58167a4289 | ||
|
|
a8bfd1cc7d | ||
|
|
58aeb2697a | ||
|
|
7f68766dc4 | ||
|
|
310c0573c6 | ||
|
|
d1aad01df3 | ||
|
|
9733b92d30 | ||
|
|
aaf776f421 | ||
|
|
5e33aee99f | ||
|
|
4e3f06d405 | ||
|
|
98b9863041 | ||
|
|
29ae76d1fb | ||
|
|
2c3841558e | ||
|
|
c8b357e31b | ||
|
|
3ad02ff50b | ||
|
|
6a54b30a9d | ||
|
|
b0cb3bb4c2 | ||
|
|
7f957be99b | ||
|
|
a54c319d55 | ||
|
|
2c71531533 | ||
|
|
5c61a70e5a | ||
|
|
fdf1d61735 | ||
|
|
93dc61e113 | ||
|
|
d80a3ebcda | ||
|
|
4270fd8b1e | ||
|
|
764dbc3782 | ||
|
|
080ab9a952 | ||
|
|
a9cab5ccfd | ||
|
|
3d44602537 | ||
|
|
88eb2a2723 | ||
|
|
e272e557b0 | ||
|
|
2a4794e422 | ||
|
|
818bdbead6 | ||
|
|
76249fa67d | ||
|
|
ed50b57b57 | ||
|
|
f05f776a08 | ||
|
|
c9b79c3016 | ||
|
|
3ff10d6622 | ||
|
|
b95d2b69b6 | ||
|
|
2cd8453877 | ||
|
|
b7300b0525 | ||
|
|
3a278d560f | ||
|
|
03fa2eddb2 | ||
|
|
b74e953ad2 | ||
|
|
39717b0a3f | ||
|
|
7f0eccd3d5 | ||
|
|
78eb3b0b22 | ||
|
|
39f20bf573 | ||
|
|
092eb1b23d | ||
|
|
30e2ed5db5 | ||
|
|
dc7ae2db5f | ||
|
|
b3b7ef1cb3 | ||
|
|
4de4d1e091 | ||
|
|
0beea57ab7 | ||
|
|
49aee1ff4c | ||
|
|
f7a3699fe3 | ||
|
|
66d42de599 | ||
|
|
d562594ae1 | ||
|
|
02afc6710c | ||
|
|
9d9f9ed5d5 | ||
|
|
b3b6320946 | ||
|
|
d43b8320ad | ||
|
|
a2260ae330 | ||
|
|
aa221acad1 | ||
|
|
c41d35db62 | ||
|
|
e5f0c733b8 | ||
|
|
70b52f85e9 | ||
|
|
303b5192ec | ||
|
|
443d3a6abe | ||
|
|
a2a7383343 | ||
|
|
ed64f933f5 | ||
|
|
4c7ad8eac4 | ||
|
|
6a922febcd | ||
|
|
71eba5df8b | ||
|
|
f1ef0afc20 | ||
|
|
844c9f94e9 | ||
|
|
07a4d3457c | ||
|
|
8c9728df95 | ||
|
|
14ec1194e4 | ||
|
|
a37f398cc7 | ||
|
|
80bd797737 | ||
|
|
600d9ce5d2 | ||
|
|
f15d9b18e9 | ||
|
|
aa19e8c609 | ||
|
|
2b2320000c | ||
|
|
06135cc827 | ||
|
|
fc58bdf0e2 | ||
|
|
9b5e60912d | ||
|
|
c108a8ed32 | ||
|
|
adce657583 | ||
|
|
d6601e1e54 | ||
|
|
0d4efa649f | ||
|
|
85f4e6ac27 | ||
|
|
c91f9c18ec | ||
|
|
bf35f73430 | ||
|
|
ad290a51a0 | ||
|
|
d218346613 | ||
|
|
502e5599fe | ||
|
|
11fb52b929 | ||
|
|
057388ccef | ||
|
|
67759ad723 | ||
|
|
f231cbdba7 | ||
|
|
7abc9bc821 | ||
|
|
e7697d86fe | ||
|
|
68394fddd5 | ||
|
|
90d34bf511 | ||
|
|
236fbc7615 | ||
|
|
6871cbdba7 | ||
|
|
38a9949211 | ||
|
|
9fba0faa43 | ||
|
|
b1250b002e | ||
|
|
10f7d5cf50 | ||
|
|
0c883ddfd6 | ||
|
|
3921175e4c | ||
|
|
84fdcf8bf5 | ||
|
|
886cfd5a33 | ||
|
|
aea384bdd4 | ||
|
|
84695cea71 | ||
|
|
f91afe6c46 | ||
|
|
3f7e731df6 | ||
|
|
582662dce9 | ||
|
|
f3b49723e8 | ||
|
|
c4deee1e49 | ||
|
|
6a6fc07cd4 | ||
|
|
d8cb7ef772 | ||
|
|
dddf2f06a0 | ||
|
|
e7e935aeb2 | ||
|
|
cb146200aa | ||
|
|
609926bff1 | ||
|
|
8803058521 | ||
|
|
c74805b12b | ||
|
|
43870237fc | ||
|
|
ca24085b5b | ||
|
|
93191c2b8b | ||
|
|
ebc3ded2b2 | ||
|
|
d9d1c874ab | ||
|
|
08477fc361 | ||
|
|
11836b876f | ||
|
|
bf20965c46 | ||
|
|
22a48e52eb | ||
|
|
15b703b5f2 | ||
|
|
18f0eb8a36 | ||
|
|
32cdcc6fbe | ||
|
|
437c4b9bc5 | ||
|
|
0a42dbf5ba | ||
|
|
ea4f319898 | ||
|
|
7b6b944106 | ||
|
|
59c966771e | ||
|
|
a83c20b667 | ||
|
|
d5c4f85d73 | ||
|
|
36f683a299 | ||
|
|
652e9719ed | ||
|
|
011b658422 | ||
|
|
5fa1050e1b | ||
|
|
39617497ca | ||
|
|
44ad960da7 | ||
|
|
db223a1e92 | ||
|
|
ea4b8f51c2 | ||
|
|
407826396b | ||
|
|
124547c9c5 | ||
|
|
db7a9df880 | ||
|
|
29725214f7 | ||
|
|
9189de4db1 | ||
|
|
fec0032883 | ||
|
|
1ee737b1aa | ||
|
|
2a32e1d746 | ||
|
|
bdaf50ab0f | ||
|
|
37a129080a | ||
|
|
024b6ce292 | ||
|
|
b13a12e3d5 | ||
|
|
9d9738eed0 | ||
|
|
135fe1e5d4 | ||
|
|
2d033b6b87 | ||
|
|
9048ffbdab | ||
|
|
d43fdd3cb4 | ||
|
|
14d16eff46 | ||
|
|
1a603c2b48 | ||
|
|
2092161fe0 | ||
|
|
a8b92f50cc | ||
|
|
4556c9a5d1 | ||
|
|
e6fcae4f02 | ||
|
|
74ab5488e1 | ||
|
|
5cdec242eb | ||
|
|
e938a499f6 | ||
|
|
84b5915eea | ||
|
|
540ce1d2c3 | ||
|
|
b40f2af0f0 | ||
|
|
324d09a61e | ||
|
|
d5dfecefea | ||
|
|
ce37e17c87 | ||
|
|
3b1d5f15f4 | ||
|
|
714ec4695b | ||
|
|
3795d92d7a | ||
|
|
eed685507a | ||
|
|
26d3ec14a6 | ||
|
|
96d475407a | ||
|
|
27d8bc458f | ||
|
|
519f86c47e | ||
|
|
980f71c1bd | ||
|
|
7692665428 | ||
|
|
aaca0b5283 | ||
|
|
e5a495489d | ||
|
|
e07a6fb3ff | ||
|
|
b8c2660996 | ||
|
|
ec2ecdcacb | ||
|
|
3c3ab77f65 | ||
|
|
ffb2c89c36 | ||
|
|
bda62cc8a1 | ||
|
|
a052af4180 | ||
|
|
8835c37536 | ||
|
|
69f3c9f6ab | ||
|
|
af3e0bd6a5 | ||
|
|
5b14512323 | ||
|
|
6f176ad1c4 | ||
|
|
bc9d720a3c | ||
|
|
93c5013251 | ||
|
|
bc5c993093 | ||
|
|
759b3c5baf | ||
|
|
e1303d13a3 | ||
|
|
8824115697 | ||
|
|
a78a1e7d2e | ||
|
|
9e150b2c44 | ||
|
|
217d230b61 | ||
|
|
ba1b3c4fc7 | ||
|
|
5f871cd713 | ||
|
|
4a37422af5 | ||
|
|
a081fc87c9 | ||
|
|
0e8286e195 | ||
|
|
e9d53c4084 | ||
|
|
697a0ac9fc | ||
|
|
7f12e1c19a | ||
|
|
bf99cb50fd | ||
|
|
1333f0113f | ||
|
|
1b544bd22d | ||
|
|
24f514c949 | ||
|
|
8eed26e0a1 | ||
|
|
3c5b3f0a49 | ||
|
|
1d23b4bb67 | ||
|
|
48e533f660 | ||
|
|
86fbed60eb | ||
|
|
164341b543 | ||
|
|
94942fe488 | ||
|
|
ca64f6c8ac | ||
|
|
e8ef483098 | ||
|
|
85faf43fdd | ||
|
|
09620c269e | ||
|
|
0c05a4c375 | ||
|
|
298aa7960e | ||
|
|
b112513df0 | ||
|
|
f19b5a453a | ||
|
|
72e54653a2 | ||
|
|
5696d72a3f | ||
|
|
757d20166a | ||
|
|
1336996129 | ||
|
|
4f35240203 | ||
|
|
5565a6b3f8 | ||
|
|
9dbe135e66 | ||
|
|
fe5c031701 | ||
|
|
0511a90640 | ||
|
|
2c87dfbb35 | ||
|
|
9b4d07008d | ||
|
|
c40218a919 | ||
|
|
0c410b87ec | ||
|
|
114a07a440 | ||
|
|
babae3b2ad | ||
|
|
a7a9f86a71 | ||
|
|
c65fcd5133 | ||
|
|
b298b4203a | ||
|
|
250310fc9f | ||
|
|
89cf05d3a4 | ||
|
|
1d258196b1 | ||
|
|
63ebf730f9 | ||
|
|
810889a964 | ||
|
|
0a97eee7ad | ||
|
|
cc8f0e456a | ||
|
|
0f7e8d3214 | ||
|
|
2c193e0dd4 | ||
|
|
2512239ea7 | ||
|
|
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: |
|
||||
- Device:
|
||||
- OS Version:
|
||||
- KernelSU Version:
|
||||
- Kernel Version:
|
||||
- KSUN Driver Version:
|
||||
- KSUN Manager Version:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature Request
|
||||
url: https://t.me/ksunext_discussions
|
||||
about: "We accept external Feature Requests, see this link for more details."
|
||||
blank_issues_enabled: false
|
||||
41
.github/ISSUE_TEMPLATE/request.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/request.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature or improvement for KernelSU Next.
|
||||
title: "[Feature] <short description>"
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to suggest a feature! Please fill out the following details.
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: "Is your feature request related to a problem?"
|
||||
description: "A clear and concise description of what the problem is. Ex: I'm always frustrated when..."
|
||||
placeholder: "Describe the problem or need."
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: "Describe the solution you'd like"
|
||||
description: "A clear and concise description of what you want to happen."
|
||||
placeholder: "Describe your proposed solution."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: "Describe alternatives you've considered"
|
||||
description: "A clear and concise description of any alternative solutions or features you've considered."
|
||||
placeholder: "List any alternatives."
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: "Additional context"
|
||||
description: "Add any other context or screenshots about the feature request here."
|
||||
placeholder: "Additional context or screenshots."
|
||||
validations:
|
||||
required: false
|
||||
2
.github/workflows/avd-kernel.yml
vendored
2
.github/workflows/avd-kernel.yml
vendored
@@ -37,7 +37,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.version_name }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Maximize build space
|
||||
uses: easimon/maximize-build-space@master
|
||||
|
||||
26
.github/workflows/build-debug-kernel.yml
vendored
26
.github/workflows/build-debug-kernel.yml
vendored
@@ -7,9 +7,9 @@ jobs:
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android12-5.10
|
||||
version_name: android12-5.10.226
|
||||
tag: android12-5.10-2024-11
|
||||
os_patch_level: 2024-11
|
||||
version_name: android12-5.10.236
|
||||
tag: android12-5.10-2025-05
|
||||
os_patch_level: 2025-05
|
||||
patch_path: "5.10"
|
||||
debug: true
|
||||
build-debug-kernel-a13:
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 223
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
- version: "5.15"
|
||||
sub_level: 167
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android13-${{ matrix.version }}
|
||||
@@ -34,11 +34,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.15"
|
||||
sub_level: 167
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
- version: "6.1"
|
||||
sub_level: 115
|
||||
os_patch_level: 2024-12
|
||||
sub_level: 138
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android14-${{ matrix.version }}
|
||||
@@ -51,8 +51,8 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "6.6"
|
||||
sub_level: 57
|
||||
os_patch_level: 2024-12
|
||||
sub_level: 89
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
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:
|
||||
matrix:
|
||||
include:
|
||||
- sub_level: 209
|
||||
os_patch_level: 2024-05
|
||||
- sub_level: 218
|
||||
os_patch_level: 2024-08
|
||||
- sub_level: 226
|
||||
os_patch_level: 2024-11
|
||||
- sub_level: 233
|
||||
os_patch_level: 2025-02
|
||||
- sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -112,7 +114,7 @@ jobs:
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android12-5.10
|
||||
version_name: android12-5.10.223
|
||||
tag: android12-5.10-2024-11
|
||||
os_patch_level: 2024-11
|
||||
version_name: android12-5.10.236
|
||||
tag: android12-5.10-2025-05
|
||||
os_patch_level: 2025-05
|
||||
patch_path: "5.10"
|
||||
32
.github/workflows/build-kernel-a13.yml
vendored
32
.github/workflows/build-kernel-a13.yml
vendored
@@ -21,9 +21,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 209
|
||||
os_patch_level: 2024-05
|
||||
- version: "5.10"
|
||||
sub_level: 210
|
||||
os_patch_level: 2024-06
|
||||
@@ -36,9 +33,15 @@ jobs:
|
||||
- version: "5.10"
|
||||
sub_level: 223
|
||||
os_patch_level: 2024-11
|
||||
- version: "5.15"
|
||||
sub_level: 148
|
||||
os_patch_level: 2024-05
|
||||
- version: "5.10"
|
||||
sub_level: 228
|
||||
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"
|
||||
sub_level: 149
|
||||
os_patch_level: 2024-07
|
||||
@@ -51,6 +54,15 @@ jobs:
|
||||
- version: "5.15"
|
||||
sub_level: 167
|
||||
os_patch_level: 2024-11
|
||||
- version: "5.15"
|
||||
sub_level: 170
|
||||
os_patch_level: 2025-01
|
||||
- version: "5.15"
|
||||
sub_level: 178
|
||||
os_patch_level: 2025-03
|
||||
- version: "5.15"
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -137,11 +149,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 223
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
- version: "5.15"
|
||||
sub_level: 167
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
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:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.15"
|
||||
sub_level: 148
|
||||
os_patch_level: 2024-05
|
||||
- version: "5.15"
|
||||
sub_level: 149
|
||||
os_patch_level: 2024-06
|
||||
@@ -39,9 +36,15 @@ jobs:
|
||||
- version: "5.15"
|
||||
sub_level: 167
|
||||
os_patch_level: 2024-11
|
||||
- version: "6.1"
|
||||
sub_level: 75
|
||||
os_patch_level: 2024-05
|
||||
- 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
|
||||
- version: "6.1"
|
||||
sub_level: 78
|
||||
os_patch_level: 2024-06
|
||||
@@ -63,6 +66,24 @@ jobs:
|
||||
- version: "6.1"
|
||||
sub_level: 115
|
||||
os_patch_level: 2024-12
|
||||
- version: "6.1"
|
||||
sub_level: 118
|
||||
os_patch_level: 2025-01
|
||||
- version: "6.1"
|
||||
sub_level: 124
|
||||
os_patch_level: 2025-02
|
||||
- version: "6.1"
|
||||
sub_level: 128
|
||||
os_patch_level: 2025-03
|
||||
- version: "6.1"
|
||||
sub_level: 129
|
||||
os_patch_level: 2025-04
|
||||
- version: "6.1"
|
||||
sub_level: 134
|
||||
os_patch_level: 2025-05
|
||||
- version: "6.1"
|
||||
sub_level: 138
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -149,11 +170,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.15"
|
||||
sub_level: 167
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
- version: "6.1"
|
||||
sub_level: 115
|
||||
os_patch_level: 2024-12
|
||||
sub_level: 138
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
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"
|
||||
sub_level: 57
|
||||
os_patch_level: 2024-12
|
||||
- version: "6.6"
|
||||
sub_level: 58
|
||||
os_patch_level: 2025-01
|
||||
- version: "6.6"
|
||||
sub_level: 66
|
||||
os_patch_level: 2025-02
|
||||
- version: "6.6"
|
||||
sub_level: 77
|
||||
os_patch_level: 2025-03
|
||||
- version: "6.6"
|
||||
sub_level: 82
|
||||
os_patch_level: 2025-04
|
||||
- version: "6.6"
|
||||
sub_level: 87
|
||||
os_patch_level: 2025-05
|
||||
- version: "6.6"
|
||||
sub_level: 89
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -122,8 +140,8 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "6.6"
|
||||
sub_level: 57
|
||||
os_patch_level: 2024-12
|
||||
sub_level: 89
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android15-${{ matrix.version }}
|
||||
|
||||
137
.github/workflows/build-kernel-arcvm.yml
vendored
Normal file
137
.github/workflows/build-kernel-arcvm.yml
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
name: Build Kernel - ChromeOS ARCVM
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-arcvm.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-arcvm.yml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
git_tag: chromeos-5.10-arcvm
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: x86_64
|
||||
kernel_image_name: bzImage
|
||||
build_config: build.config.gki.x86_64
|
||||
defconfig: x86_64_arcvm_defconfig
|
||||
- arch: arm64
|
||||
kernel_image_name: Image
|
||||
build_config: build.config.gki.aarch64
|
||||
defconfig: arm64_arcvm_defconfig
|
||||
|
||||
name: Build ChromeOS ARCVM kernel
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
LTO: thin
|
||||
ROOT_DIR: /
|
||||
KERNEL_DIR: ${{ github.workspace }}/kernel
|
||||
|
||||
steps:
|
||||
- name: Install Build Tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends bc \
|
||||
bison build-essential ca-certificates flex git gnupg \
|
||||
libelf-dev libssl-dev lsb-release software-properties-common wget \
|
||||
libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget gzip \
|
||||
rsync python3 device-tree-compiler
|
||||
|
||||
sudo ln -s --force python3 /usr/bin/python
|
||||
|
||||
export LLVM_VERSION=14
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh $LLVM_VERSION
|
||||
rm ./llvm.sh
|
||||
sudo ln -s --force /usr/bin/clang-$LLVM_VERSION /usr/bin/clang
|
||||
sudo ln -s --force /usr/bin/ld.lld-$LLVM_VERSION /usr/bin/ld.lld
|
||||
sudo ln -s --force /usr/bin/llvm-objdump-$LLVM_VERSION /usr/bin/llvm-objdump
|
||||
sudo ln -s --force /usr/bin/llvm-ar-$LLVM_VERSION /usr/bin/llvm-ar
|
||||
sudo ln -s --force /usr/bin/llvm-nm-$LLVM_VERSION /usr/bin/llvm-nm
|
||||
sudo ln -s --force /usr/bin/llvm-strip-$LLVM_VERSION /usr/bin/llvm-strip
|
||||
sudo ln -s --force /usr/bin/llvm-objcopy-$LLVM_VERSION /usr/bin/llvm-objcopy
|
||||
sudo ln -s --force /usr/bin/llvm-readelf-$LLVM_VERSION /usr/bin/llvm-readelf
|
||||
sudo ln -s --force /usr/bin/clang++-$LLVM_VERSION /usr/bin/clang++
|
||||
|
||||
- name: Checkout KernelSU-Next
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU-Next
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup kernel source
|
||||
run: git clone https://chromium.googlesource.com/chromiumos/third_party/kernel.git -b ${{ env.git_tag }} --depth=1
|
||||
|
||||
- name: Extract version from Makefile
|
||||
working-directory: kernel
|
||||
run: |
|
||||
VERSION=$(grep -E '^VERSION = ' Makefile | awk '{print $3}')
|
||||
PATCHLEVEL=$(grep -E '^PATCHLEVEL = ' Makefile | awk '{print $3}')
|
||||
SUBLEVEL=$(grep -E '^SUBLEVEL = ' Makefile | awk '{print $3}')
|
||||
echo "ChromeOS ARCVM Linux kernel version: $VERSION.$PATCHLEVEL.$SUBLEVEL"
|
||||
echo "version=$VERSION.$PATCHLEVEL.$SUBLEVEL" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup KernelSU-Next
|
||||
working-directory: kernel
|
||||
run: |
|
||||
echo "[+] KernelSU-Next setup"
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/kernel
|
||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
||||
echo "[+] Copy KernelSU-Next driver to $KERNEL_ROOT/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU-Next/kernel $KERNEL_ROOT/drivers/kernelsu-next
|
||||
|
||||
echo "[+] Add KernelSU-Next driver to Makefile"
|
||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
||||
DRIVER_KCONFIG=$KERNEL_ROOT/drivers/Kconfig
|
||||
grep -q "kernelsu-next" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu-next/\n" >> "$DRIVER_MAKEFILE"
|
||||
grep -q "kernelsu-next" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu-next/Kconfig\"" "$DRIVER_KCONFIG"
|
||||
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU-Next/.github/patches/5.10/*.patch || echo "[-] No patch found"
|
||||
|
||||
echo "[+] Patch script/setlocalversion"
|
||||
sed -i 's/-dirty//g' $KERNEL_ROOT/scripts/setlocalversion
|
||||
|
||||
echo "[+] KernelSU-Next setup done."
|
||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
||||
KSU_VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "KernelSU-Next version: $KSU_VERSION"
|
||||
echo "kernelsu-next_version=$KSU_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Kernel
|
||||
working-directory: kernel
|
||||
env:
|
||||
KERNEL_IMAGE_NAME: ${{ matrix.kernel_image_name }}
|
||||
ARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
set -a && . ${{ matrix.build_config }}; set +a
|
||||
export DEFCONFIG=${{ matrix.defconfig }}
|
||||
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
|
||||
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
|
||||
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
|
||||
fi
|
||||
|
||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} mrproper
|
||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} ${DEFCONFIG} < /dev/null
|
||||
scripts/config --file .config -e LTO_CLANG -d LTO_NONE -e LTO_CLANG_THIN -d LTO_CLANG_FULL -e THINLTO
|
||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} -j$(nproc) ${KERNEL_IMAGE_NAME} modules prepare-objtool
|
||||
ls -l -h ${PWD}/arch/${ARCH}/boot
|
||||
echo "file_path=${PWD}/arch/${ARCH}/boot/${KERNEL_IMAGE_NAME}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload kernel-ARCVM-${{ matrix.arch }}-${{ env.version }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kernel-ARCVM-${{ matrix.arch }}-${{ env.version }}
|
||||
path: "${{ env.file_path }}"
|
||||
39
.github/workflows/build-kernel-avd.yml
vendored
Normal file
39
.github/workflows/build-kernel-avd.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Build Kernel - AVD
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-avd.yml"
|
||||
- ".github/workflows/avd-kernel.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-avd.yml"
|
||||
- ".github/workflows/avd-kernel.yml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-kernel:
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
||||
uses: ./.github/workflows/avd-kernel.yml
|
||||
secrets: inherit
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "android-14-avd_x86_64"
|
||||
manifest: "android-14-avd_x86_64.xml"
|
||||
arch: "x86_64"
|
||||
- version: "android-15-avd_aarch64"
|
||||
manifest: "android-15-avd_aarch64.xml"
|
||||
arch: "aarch64"
|
||||
- version: "android-15-avd_x86_64"
|
||||
manifest: "android-15-avd_x86_64.xml"
|
||||
arch: "x86_64"
|
||||
with:
|
||||
version_name: ${{ matrix.version }}
|
||||
manifest_name: ${{ matrix.manifest }}
|
||||
arch: ${{ matrix.arch }}
|
||||
debug: true
|
||||
38
.github/workflows/build-kernel-wsa.yml
vendored
Normal file
38
.github/workflows/build-kernel-wsa.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Build Kernel - WSA
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-wsa.yml"
|
||||
- ".github/workflows/wsa-kernel.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["next"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-wsa.yml"
|
||||
- ".github/workflows/wsa-kernel.yml"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64, arm64]
|
||||
version: ["5.15.94.2", "5.15.104.1", "5.15.104.2", "5.15.104.3", "5.15.104.4"]
|
||||
uses: ./.github/workflows/wsa-kernel.yml
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
version: ${{ matrix.version }}
|
||||
|
||||
check_build:
|
||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
|
||||
uses: ./.github/workflows/wsa-kernel.yml
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64, arm64]
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
version: "5.15.104.4"
|
||||
24
.github/workflows/build-lkm.yml
vendored
24
.github/workflows/build-lkm.yml
vendored
@@ -15,23 +15,23 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "android12-5.10"
|
||||
sub_level: 226
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
- version: "android13-5.10"
|
||||
sub_level: 223
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 236
|
||||
os_patch_level: 2025-05
|
||||
- version: "android13-5.15"
|
||||
sub_level: 167
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
- version: "android14-5.15"
|
||||
sub_level: 167
|
||||
os_patch_level: 2024-11
|
||||
sub_level: 180
|
||||
os_patch_level: 2025-05
|
||||
- version: "android14-6.1"
|
||||
sub_level: 115
|
||||
os_patch_level: 2024-12
|
||||
sub_level: 138
|
||||
os_patch_level: 2025-06
|
||||
- version: "android15-6.6"
|
||||
sub_level: 57
|
||||
os_patch_level: 2024-12
|
||||
sub_level: 89
|
||||
os_patch_level: 2025-06
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: ${{ matrix.version }}
|
||||
|
||||
254
.github/workflows/build-manager-ci.yml
vendored
254
.github/workflows/build-manager-ci.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build Manager
|
||||
name: Build Manager CI
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -13,60 +13,123 @@ on:
|
||||
pull_request:
|
||||
branches: [ "next" ]
|
||||
paths:
|
||||
- '.github/workflows/build-manager-ci.yml'
|
||||
- 'manager/**'
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-cache:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
cache-hit: ${{ steps.cache-artifacts.outputs.cache-hit }}
|
||||
cache-key: ${{ steps.generate-cache-key.outputs.cache-key }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate cache key from source files
|
||||
id: generate-cache-key
|
||||
run: |
|
||||
# Calculate hash of all files except manager directory
|
||||
HASH=$(find . -type f \
|
||||
-not -path "./manager/*" \
|
||||
-not -path "./.git/*" \
|
||||
-not -path "./.github/workflows/build-manager-ci.yml" \
|
||||
-exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1)
|
||||
echo "cache-key=artifacts-$HASH" >> $GITHUB_OUTPUT
|
||||
echo "Generated cache key: artifacts-$HASH"
|
||||
|
||||
- name: Check for cached artifacts
|
||||
id: cache-artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ steps.generate-cache-key.outputs.cache-key }}
|
||||
|
||||
build-lkm:
|
||||
needs: check-cache
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: ./.github/workflows/build-lkm.yml
|
||||
secrets: inherit
|
||||
|
||||
build-susfsd:
|
||||
needs: build-lkm
|
||||
needs: [check-cache, build-lkm]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
- os: ubuntu-latest
|
||||
uses: ./.github/workflows/susfsd.yml
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud:
|
||||
needs: [check-cache, build-susfsd]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
- target: armv7-linux-androideabi
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud_overlayfs:
|
||||
needs: build-susfsd
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud_overlayfs.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud_magic:
|
||||
needs: build-ksud_overlayfs
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud_magic.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
cache-artifacts:
|
||||
needs: [check-cache, build-ksud]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download susfsd artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-linux-android
|
||||
path: cached-artifacts/susfsd
|
||||
|
||||
- name: Download ksud_overlayfs artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: ksud_overlayfs-*
|
||||
path: cached-artifacts/ksud_overlayfs
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download ksud_magic artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: ksud_magic-*
|
||||
path: cached-artifacts/ksud_magic
|
||||
merge-multiple: true
|
||||
|
||||
- name: Cache artifacts
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ needs.check-cache.outputs.cache-key }}
|
||||
|
||||
build-manager:
|
||||
needs: build-ksud_magic
|
||||
needs: [check-cache, build-ksud]
|
||||
if: always() && needs.check-cache.result == 'success' && (needs.check-cache.outputs.cache-hit == 'true' || needs.build-ksud.result == 'success')
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./manager
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
@@ -80,7 +143,7 @@ jobs:
|
||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Write key
|
||||
- name: Write Key
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||
{
|
||||
@@ -101,40 +164,122 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Download arm64 susfsd
|
||||
# Restore cached artifacts if cache hit
|
||||
- name: Restore cached artifacts
|
||||
if: needs.check-cache.outputs.cache-hit == 'true'
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ needs.check-cache.outputs.cache-key }}
|
||||
|
||||
# Download fresh artifacts if cache miss
|
||||
- name: Download susfsd
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-aarch64-linux-android
|
||||
name: susfsd-linux-android
|
||||
path: .
|
||||
|
||||
- name: Copy susfsd to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
|
||||
# Download fresh ksud artifacts if cache miss
|
||||
- name: Download arm64 ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-aarch64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Copy ksud_overlayfs to app jniLibs
|
||||
run: |
|
||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
|
||||
|
||||
- name: Download arm ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-armv7-linux-androideabi
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download x86_64 ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-x86_64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download arm64 ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-aarch64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download arm ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-armv7-linux-androideabi
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download x86_64 ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-x86_64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
# Copy artifacts to jniLibs (works for both cached and fresh)
|
||||
- name: Copy susfsd to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/susfsd/arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
cp -f ../cached-artifacts/susfsd/armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
cp -f ../cached-artifacts/susfsd/x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
cp -f ../armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
cp -f ../x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
fi
|
||||
|
||||
- name: Copy ksud_overlayfs to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
cp -f ../ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
cp -f ../ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
fi
|
||||
|
||||
- name: Copy ksud_magic to app jniLibs
|
||||
run: |
|
||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
cp -f ../cached-artifacts/ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
cp -f ../cached-artifacts/ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
cp -f ../ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
cp -f ../ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
fi
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
@@ -148,19 +293,19 @@ jobs:
|
||||
chmod +x gradlew
|
||||
./gradlew clean assembleRelease
|
||||
|
||||
- name: Upload build artifact
|
||||
- name: Upload Build Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: manager
|
||||
name: Manager
|
||||
path: manager/app/build/outputs/apk/release/*.apk
|
||||
|
||||
- name: Upload mappings
|
||||
- name: Upload Mappings
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "mappings"
|
||||
path: "manager/app/build/outputs/mapping/release/"
|
||||
name: Mappings
|
||||
path: manager/app/build/outputs/mapping/release/
|
||||
|
||||
- name: Bot session cache
|
||||
- name: Bot Session Cache
|
||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v4
|
||||
@@ -168,7 +313,7 @@ jobs:
|
||||
path: scripts/ksunextbot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Upload to telegram
|
||||
- name: Upload to Telegram
|
||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||
env:
|
||||
API_ID: ${{ secrets.API_ID }}
|
||||
@@ -187,4 +332,3 @@ jobs:
|
||||
pip3 install telethon
|
||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
||||
fi
|
||||
|
||||
|
||||
341
.github/workflows/build-manager-spoofed.yml
vendored
Normal file
341
.github/workflows/build-manager-spoofed.yml
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
name: Build Manager Spoofed
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "next" ]
|
||||
paths:
|
||||
- '.github/workflows/build-manager-ci.yml'
|
||||
- 'manager/**'
|
||||
- 'kernel/**'
|
||||
- 'userspace/ksud_overlayfs**'
|
||||
- 'userspace/ksud_magic/**'
|
||||
- 'userspace/susfsd/**'
|
||||
pull_request:
|
||||
branches: [ "next" ]
|
||||
paths:
|
||||
- '.github/workflows/build-manager-ci.yml'
|
||||
- 'manager/**'
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 12 * * *" # 6 PM UTC+6 | 12 PM UTC
|
||||
|
||||
jobs:
|
||||
check-cache:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
cache-hit: ${{ steps.cache-artifacts.outputs.cache-hit }}
|
||||
cache-key: ${{ steps.generate-cache-key.outputs.cache-key }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate cache key from source files
|
||||
id: generate-cache-key
|
||||
run: |
|
||||
# Calculate hash of all files except manager directory
|
||||
HASH=$(find . -type f \
|
||||
-not -path "./manager/*" \
|
||||
-not -path "./.git/*" \
|
||||
-not -path "./.github/workflows/build-manager-spoofed.yml" \
|
||||
-exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1)
|
||||
echo "cache-key=artifacts-spoofed-$HASH" >> $GITHUB_OUTPUT
|
||||
echo "Generated cache key: artifacts-spoofed-$HASH"
|
||||
|
||||
- name: Check for cached artifacts
|
||||
id: cache-artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ steps.generate-cache-key.outputs.cache-key }}
|
||||
|
||||
build-lkm:
|
||||
needs: check-cache
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: ./.github/workflows/build-lkm.yml
|
||||
secrets: inherit
|
||||
|
||||
build-susfsd:
|
||||
needs: [check-cache, build-lkm]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
uses: ./.github/workflows/susfsd.yml
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud:
|
||||
needs: [check-cache, build-susfsd]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
- target: armv7-linux-androideabi
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
cache-artifacts:
|
||||
needs: [check-cache, build-ksud]
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download susfsd artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-linux-android
|
||||
path: cached-artifacts/susfsd
|
||||
|
||||
- name: Download ksud_overlayfs artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: ksud_overlayfs-*
|
||||
path: cached-artifacts/ksud_overlayfs
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download ksud_magic artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: ksud_magic-*
|
||||
path: cached-artifacts/ksud_magic
|
||||
merge-multiple: true
|
||||
|
||||
- name: Cache artifacts
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ needs.check-cache.outputs.cache-key }}
|
||||
|
||||
build-manager:
|
||||
needs: [check-cache, build-ksud]
|
||||
if: always() && needs.check-cache.result == 'success' && (needs.check-cache.outputs.cache-hit == 'true' || needs.build-ksud.result == 'success')
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./manager
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Spoof Package ID
|
||||
run: |
|
||||
chmod +x spoof
|
||||
./spoof
|
||||
|
||||
- name: Setup need_upload
|
||||
id: need_upload
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
echo "UPLOAD=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Write Key
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||
{
|
||||
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}'
|
||||
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}'
|
||||
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}'
|
||||
echo KEYSTORE_FILE='key.jks'
|
||||
} >> gradle.properties
|
||||
echo ${{ secrets.KEYSTORE }} | base64 -d > key.jks
|
||||
fi
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
# Restore cached artifacts if cache hit
|
||||
- name: Restore cached artifacts
|
||||
if: needs.check-cache.outputs.cache-hit == 'true'
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
cached-artifacts/susfsd
|
||||
cached-artifacts/ksud_overlayfs
|
||||
cached-artifacts/ksud_magic
|
||||
key: ${{ needs.check-cache.outputs.cache-key }}
|
||||
|
||||
# Download fresh artifacts if cache miss
|
||||
- name: Download susfsd
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-linux-android
|
||||
path: .
|
||||
|
||||
# Download fresh ksud artifacts if cache miss
|
||||
- name: Download arm64 ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-aarch64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download arm ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-armv7-linux-androideabi
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download x86_64 ksud_overlayfs
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-x86_64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download arm64 ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-aarch64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download arm ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-armv7-linux-androideabi
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download x86_64 ksud_magic
|
||||
if: needs.check-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-x86_64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
# Copy artifacts to jniLibs (works for both cached and fresh)
|
||||
- name: Copy susfsd to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/susfsd/arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
cp -f ../cached-artifacts/susfsd/armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
cp -f ../cached-artifacts/susfsd/x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
cp -f ../armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
cp -f ../x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
fi
|
||||
|
||||
- name: Copy ksud_overlayfs to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
cp -f ../cached-artifacts/ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
cp -f ../ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
cp -f ../ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
fi
|
||||
|
||||
- name: Copy ksud_magic to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
|
||||
if [ "${{ needs.check-cache.outputs.cache-hit }}" == "true" ]; then
|
||||
# Copy from cached artifacts
|
||||
cp -f ../cached-artifacts/ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
cp -f ../cached-artifacts/ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
cp -f ../cached-artifacts/ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
else
|
||||
# Copy from fresh artifacts
|
||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
cp -f ../ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
cp -f ../ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
fi
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
{
|
||||
echo 'org.gradle.parallel=true'
|
||||
echo 'org.gradle.vfs.watch=true'
|
||||
echo 'org.gradle.jvmargs=-Xmx2048m'
|
||||
echo 'android.native.buildOutput=verbose'
|
||||
} >> gradle.properties
|
||||
sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties
|
||||
chmod +x gradlew
|
||||
./gradlew clean assembleRelease
|
||||
|
||||
- name: Upload Build Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Manager
|
||||
path: manager/app/build/outputs/apk/release/*.apk
|
||||
|
||||
- name: Upload Mappings
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Mappings
|
||||
path: manager/app/build/outputs/mapping/release/
|
||||
|
||||
- name: Bot Session Cache
|
||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: scripts/ksunextbot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Upload to Telegram
|
||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||
env:
|
||||
API_ID: ${{ secrets.API_ID }}
|
||||
API_HASH: ${{ secrets.API_HASH }}
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
TITLE: CI Manager (SPOOFED BUILD)
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
export VERSION=$(git rev-list --count HEAD)
|
||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
||||
pip3 install telethon
|
||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
||||
fi
|
||||
93
.github/workflows/build-manager.yml
vendored
93
.github/workflows/build-manager.yml
vendored
@@ -13,6 +13,7 @@ on:
|
||||
# pull_request:
|
||||
# branches: [ "next" ]
|
||||
# paths:
|
||||
# - '.github/workflows/build-manager-ci.yml'
|
||||
# - 'manager/**'
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
@@ -27,46 +28,36 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
- os: ubuntu-latest
|
||||
uses: ./.github/workflows/susfsd.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud_overlayfs:
|
||||
build-ksud:
|
||||
needs: build-susfsd
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud_overlayfs.yml
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
os: ${{ matrix.os }}
|
||||
|
||||
build-ksud_magic:
|
||||
needs: build-ksud_overlayfs
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
- target: armv7-linux-androideabi
|
||||
os: ubuntu-latest
|
||||
uses: ./.github/workflows/ksud_magic.yml
|
||||
- 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_magic
|
||||
needs: build-ksud
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./manager
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
@@ -80,7 +71,7 @@ jobs:
|
||||
echo "UPLOAD=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Write key
|
||||
- name: Write Key
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||
{
|
||||
@@ -101,40 +92,75 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Download arm64 susfsd
|
||||
- name: Download susfsd
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: susfsd-aarch64-linux-android
|
||||
name: susfsd-linux-android
|
||||
path: .
|
||||
|
||||
- name: Copy susfsd to app jniLibs
|
||||
run: |
|
||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
|
||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||
cp -f ../armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
||||
|
||||
mkdir -p app/src/main/jniLibs/x86_64
|
||||
cp -f ../x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
||||
|
||||
|
||||
- name: Download arm64 ksud_overlayfs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-aarch64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
|
||||
- name: Download arm ksud_overlayfs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-armv7-linux-androideabi
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Download x86_64 ksud_overlayfs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_overlayfs-x86_64-linux-android
|
||||
path: ksud_overlayfs
|
||||
|
||||
- name: Copy ksud_overlayfs to app jniLibs
|
||||
run: |
|
||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
|
||||
|
||||
cp -f ../ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
||||
|
||||
cp -f ../ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
||||
|
||||
- name: Download arm64 ksud_magic
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-aarch64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download arm ksud_magic
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-armv7-linux-androideabi
|
||||
path: ksud_magic
|
||||
|
||||
- name: Download x86_64 ksud_magic
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud_magic-x86_64-linux-android
|
||||
path: ksud_magic
|
||||
|
||||
- name: Copy ksud_magic to app jniLibs
|
||||
run: |
|
||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
|
||||
cp -f ../ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
||||
|
||||
cp -f ../ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
@@ -148,19 +174,19 @@ jobs:
|
||||
chmod +x gradlew
|
||||
./gradlew clean assembleRelease
|
||||
|
||||
- name: Upload build artifact
|
||||
- name: Upload Build Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: manager
|
||||
name: Manager
|
||||
path: manager/app/build/outputs/apk/release/*.apk
|
||||
|
||||
- name: Upload mappings
|
||||
- name: Upload Mappings
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "mappings"
|
||||
path: "manager/app/build/outputs/mapping/release/"
|
||||
name: Mappings
|
||||
path: manager/app/build/outputs/mapping/release/
|
||||
|
||||
- name: Bot session cache
|
||||
- name: Bot Session Cache
|
||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v4
|
||||
@@ -168,7 +194,7 @@ jobs:
|
||||
path: scripts/ksunextbot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Upload to telegram
|
||||
- name: Upload to Telegram
|
||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
||||
env:
|
||||
API_ID: ${{ secrets.API_ID }}
|
||||
@@ -187,4 +213,3 @@ jobs:
|
||||
pip3 install telethon
|
||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
||||
fi
|
||||
|
||||
|
||||
43
.github/workflows/clippy.yml
vendored
43
.github/workflows/clippy.yml
vendored
@@ -6,13 +6,15 @@ on:
|
||||
- next
|
||||
paths:
|
||||
- '.github/workflows/clippy.yml'
|
||||
- 'userspace/ksud/**'
|
||||
- 'userspace/ksud_magic/**'
|
||||
- 'userspace/ksud_overlayfs/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
paths:
|
||||
- '.github/workflows/clippy.yml'
|
||||
- 'userspace/ksud/**'
|
||||
- 'userspace/ksud_magic/**'
|
||||
- 'userspace/ksud_overlayfs/**'
|
||||
|
||||
env:
|
||||
RUSTFLAGS: '-Dwarnings'
|
||||
@@ -21,17 +23,32 @@ jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup update --force-non-host stable-x86_64-unknown-linux-gnu
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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:
|
||||
workspaces: userspace/ksud
|
||||
workspaces: userspace/ksud_overlayfs
|
||||
|
||||
- name: Install cross
|
||||
run: |
|
||||
cargo install cross --git https://github.com/cross-rs/cross --rev 66845c1
|
||||
- name: Cache ksud_magic
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: userspace/ksud_magic
|
||||
|
||||
- name: Run clippy
|
||||
- name: Run Clippy
|
||||
run: |
|
||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target aarch64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target aarch64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target aarch64-linux-android --release
|
||||
|
||||
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target armv7-linux-androideabi --release
|
||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target armv7-linux-androideabi --release
|
||||
|
||||
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target x86_64-linux-android --release
|
||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target x86_64-linux-android --release
|
||||
17
.github/workflows/gki-kernel.yml
vendored
17
.github/workflows/gki-kernel.yml
vendored
@@ -195,9 +195,24 @@ jobs:
|
||||
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
|
||||
fi
|
||||
|
||||
- name: Setup Syscall Hooks
|
||||
if: ${{ inputs.build_lkm == false }}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/android-kernel/common
|
||||
echo "[+] Patch Kernel"
|
||||
curl -L https://github.com/KernelSU-Next/kernel_patches/raw/main/syscall_hook/min_scope_syscall_hooks_v1.4.patch | patch -p1 --fuzz=3
|
||||
echo "[+] Set Config"
|
||||
echo "CONFIG_KSU=y" >> ./arch/arm64/configs/gki_defconfig
|
||||
echo "CONFIG_KSU_KPROBES_HOOK=n" >> ./arch/arm64/configs/gki_defconfig
|
||||
echo "[+] Disable Config Check"
|
||||
sed -i 's/check_defconfig//' ./build.config.gki
|
||||
|
||||
- name: Make working directory clean to avoid dirty
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
if [ -f ./common/BUILD.bazel ]; then
|
||||
sed -i '/^[[:space:]]*"protected_exports_list"[[:space:]]*:[[:space:]]*"android\/abi_gki_protected_exports_aarch64",$/d' ./common/BUILD.bazel
|
||||
fi
|
||||
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
|
||||
git config --global user.email "bot@kernelsu.org"
|
||||
git config --global user.name "KernelSU-NextBot"
|
||||
@@ -255,4 +270,4 @@ jobs:
|
||||
if: ${{ inputs.build_lkm == true }}
|
||||
with:
|
||||
name: ${{ inputs.version }}-lkm
|
||||
path: ./output/*_kernelsu.ko
|
||||
path: ./output/*_kernelsu.ko
|
||||
|
||||
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:
|
||||
uses: ./.github/workflows/build-kernel-a15.yml
|
||||
secrets: inherit
|
||||
build-wsa-kernel:
|
||||
uses: ./.github/workflows/build-kernel-wsa.yml
|
||||
secrets: inherit
|
||||
build-arcvm-kernel:
|
||||
uses: ./.github/workflows/build-kernel-arcvm.yml
|
||||
secrets: inherit
|
||||
release:
|
||||
needs:
|
||||
- build-manager
|
||||
- build-a12-kernel
|
||||
- build-a13-kernel
|
||||
- build-a14-kernel
|
||||
- build-a15-kernel
|
||||
- build-wsa-kernel
|
||||
- build-arcvm-kernel
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
@@ -40,6 +49,24 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Zip WSA kernel
|
||||
run: |
|
||||
for dir in kernel-WSA-*; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "------ Zip $dir ----------"
|
||||
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Zip ChromeOS ARCVM kernel
|
||||
run: |
|
||||
for dir in kernel-ARCVM-*; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "------ Zip $dir ----------"
|
||||
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
@@ -47,10 +74,12 @@ jobs:
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
manager/*.apk
|
||||
Manager/*.apk
|
||||
android*-lkm/*_kernelsu.ko
|
||||
AnyKernel3-*.zip
|
||||
boot-images-*/Image-*/*.img.gz
|
||||
ksud_magic-*
|
||||
ksud_overlayfs-*
|
||||
susfsd-*
|
||||
kernel-WSA*.zip
|
||||
kernel-ARCVM*.zip
|
||||
ksud_magic*.zip
|
||||
ksud_overlayfs*.zip
|
||||
susfsd*.zip
|
||||
13
.github/workflows/rustfmt.yml
vendored
13
.github/workflows/rustfmt.yml
vendored
@@ -6,13 +6,15 @@ on:
|
||||
- 'next'
|
||||
paths:
|
||||
- '.github/workflows/rustfmt.yml'
|
||||
- 'userspace/ksud/**'
|
||||
- 'userspace/ksud_magic/**'
|
||||
- 'userspace/ksud_overlayfs/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'next'
|
||||
paths:
|
||||
- '.github/workflows/rustfmt.yml'
|
||||
- 'userspace/ksud/**'
|
||||
- 'userspace/ksud_magic/**'
|
||||
- 'userspace/ksud_overlayfs/**'
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
@@ -30,4 +32,9 @@ jobs:
|
||||
- uses: LoliGothick/rustfmt-check@master
|
||||
with:
|
||||
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
|
||||
with:
|
||||
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:
|
||||
push:
|
||||
branches: [ "next" ]
|
||||
@@ -8,9 +8,6 @@ on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
os:
|
||||
required: false
|
||||
type: string
|
||||
@@ -26,9 +23,9 @@ jobs:
|
||||
- name: Build susfsd
|
||||
working-directory: ./userspace/susfsd
|
||||
run: $ANDROID_NDK/ndk-build
|
||||
- name: Upload a Build Artifact
|
||||
- name: Upload Build Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: susfsd-aarch64-linux-android
|
||||
name: susfsd-linux-android
|
||||
path: ./userspace/susfsd/libs
|
||||
|
||||
|
||||
106
.github/workflows/wsa-kernel.yml
vendored
Normal file
106
.github/workflows/wsa-kernel.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
name: Build Kernel - WSA
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
arch:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
Build arch: x86_64 / arm64
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
description: >
|
||||
Build version
|
||||
jobs:
|
||||
build:
|
||||
name: Build WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }}
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_HARDLINK: "true"
|
||||
|
||||
steps:
|
||||
- name: Install Build Tools
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
with:
|
||||
packages: bc bison build-essential flex libelf-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu gzip ccache
|
||||
version: 1.0
|
||||
|
||||
- name: Cache LLVM
|
||||
id: cache-llvm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./llvm
|
||||
key: llvm-12.0.1
|
||||
|
||||
- name: Setup LLVM
|
||||
uses: KyleMayes/install-llvm-action@v1
|
||||
with:
|
||||
version: "12.0.1"
|
||||
force-version: true
|
||||
ubuntu-version: "16.04"
|
||||
cached: ${{ steps.cache-llvm.outputs.cache-hit }}
|
||||
|
||||
- name: Checkout KernelSU-Next
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU-Next
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup kernel source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: microsoft/WSA-Linux-Kernel
|
||||
ref: android-lts/latte-2/${{ inputs.version }}
|
||||
path: WSA-Linux-Kernel
|
||||
|
||||
- name: Setup Ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }}
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
max-size: 2G
|
||||
|
||||
- name: Setup KernelSU-Next
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
echo "[+] KernelSU-Next setup"
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel
|
||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
||||
echo "[+] Copy KernelSU-Next driver to $KERNEL_ROOT/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU-Next/kernel $KERNEL_ROOT/drivers/kernelsu
|
||||
echo "[+] Add KernelSU-Next driver to Makefile"
|
||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
||||
DRIVER_KCONFIG=$KERNEL_ROOT/drivers/Kconfig
|
||||
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
|
||||
grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG"
|
||||
echo "[+] Apply KernelSU-Next patches"
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU-Next/.github/patches/5.15/*.patch || echo "[-] No patch found"
|
||||
echo "[+] KernelSU-Next setup done."
|
||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
||||
VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "VERSION: $VERSION"
|
||||
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Kernel
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
|
||||
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
|
||||
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
|
||||
fi
|
||||
declare -A ARCH_MAP=(["x86_64"]="x64" ["arm64"]="arm64")
|
||||
cp configs/wsa/config-wsa-${ARCH_MAP[${{ inputs.arch }}]} .config
|
||||
make olddefconfig
|
||||
declare -A FILE_NAME=(["x86_64"]="bzImage" ["arm64"]="Image")
|
||||
make -j`nproc` LLVM=1 ARCH=${{ inputs.arch }} $(if [ "${{ inputs.arch }}" == "arm64" ]; then echo CROSS_COMPILE=aarch64-linux-gnu; fi) ${FILE_NAME[${{ inputs.arch }}]} CCACHE="/usr/bin/ccache"
|
||||
declare -A ARCH_MAP_FILE=(["x86_64"]="x86" ["arm64"]="arm64")
|
||||
echo "file_path=WSA-Linux-Kernel/arch/${ARCH_MAP_FILE[${{ inputs.arch }}]}/boot/${FILE_NAME[${{ inputs.arch }}]}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload kernel-${{ inputs.arch }}-${{ inputs.version }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kernel-WSA-${{ inputs.arch }}-${{ inputs.version }}
|
||||
path: "${{ env.file_path }}"
|
||||
31
build.sh
Executable file
31
build.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script builds the KernelSU Next manager APK.
|
||||
|
||||
# Ensure you have the setup Android SDK & NDK installed and necessary environment variables set and sourced.
|
||||
|
||||
# For LKM make sure you have imported the androidX-X.X_kernelsu.ko drivers to userspace/ksud_*/bin/aarch64 directory.
|
||||
|
||||
cross build --target aarch64-linux-android --release --manifest-path ./userspace/ksud_magic/Cargo.toml
|
||||
|
||||
cp userspace/ksud_magic/target/aarch64-linux-android/release/ksud manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
||||
|
||||
cross build --target aarch64-linux-android --release --manifest-path ./userspace/ksud_overlayfs/Cargo.toml
|
||||
|
||||
cp userspace/ksud_overlayfs/target/aarch64-linux-android/release/ksud manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
||||
|
||||
cd userspace/susfsd/jni
|
||||
|
||||
ndk-build
|
||||
|
||||
cp ../libs/arm64-v8a/susfsd ../../../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
||||
|
||||
cd ../../..
|
||||
|
||||
cd manager
|
||||
|
||||
./setup.sh
|
||||
|
||||
cd ..
|
||||
|
||||
adb install manager/app/build/outputs/apk/release/KernelSU_Next_v*.apk
|
||||
2
crowdin.yml
Normal file
2
crowdin.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
bundles:
|
||||
- 8
|
||||
106
docs/README.md
106
docs/README.md
@@ -1,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)
|
||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/kernelsu-next"><img src="https://badges.crowdin.net/kernelsu-next/localized.svg"></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Features
|
||||
---
|
||||
|
||||
1. Kernel-based `su` and root access management.
|
||||
2. Module system based on dynamic mount system [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage.
|
||||
## 🚀 Features
|
||||
|
||||
## Compatibility State
|
||||
- Kernel-based `su` and root access management.
|
||||
- Module system based on [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) and [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
- [App Profile](https://kernelsu.org/guide/app-profile.html): Limit root privileges per app.
|
||||
|
||||
KernelSU Next officially supports most Android kernels starting from 4.4 up to 6.6.
|
||||
- GKI 2.0 (5.10+) kernels can run pre-built images and LKM/KMI.
|
||||
- GKI 1.0 (4.19 - 5.4) kernels need to rebuilt with KernelSU driver.
|
||||
- EOL (<4.14) kernels also need to be rebuilt with KernelSU driver (3.18+ is experimental and may need some function backports).
|
||||
---
|
||||
|
||||
Currently, only `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).
|
||||
- All other parts except the `kernel` directory are [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
Please refer to the [Installation](https://kernelsu-next.github.io/webpage/pages/installation.html) guide for setup instructions.
|
||||
|
||||
## Credits
|
||||
---
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): the KernelSU idea.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): the powerful root tool.
|
||||
- [genuine](https://github.com/brevent/genuine/): apk v2 signature validation.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): some rootkit skills.
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): thanks to tiann, or else KernelSU Next wouldn't even exist.
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff for saving KernelSU!
|
||||
## 🔐 Security
|
||||
|
||||
To report security issues, please see [SECURITY.md](/SECURITY.md).
|
||||
|
||||
---
|
||||
|
||||
## 📜 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`
|
||||
- **USDT (SOL)**: `A4wqBXYd6Ey4nK4SJ2bmjeMgGyaLKT9TwDLh8BEo8Zu6`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Credits
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Concept inspiration
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Core root implementation
|
||||
- [Genuine](https://github.com/brevent/genuine/) – APK v2 signature validation
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Rootkit techniques
|
||||
- [KernelSU](https://github.com/tiann/KernelSU) – The original base that made KernelSU Next possible
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – For magic mount support
|
||||
|
||||
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` 和权限管理
|
||||
2. 基于动态挂载系统 [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模块系统。
|
||||
3. [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 权限关进笼子里
|
||||
- 基于内核的 `su` 和超级用户权限管理
|
||||
- 动态挂载系统基于 **[Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount)** 以及 **[OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)**
|
||||
- [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 权限关进笼子里
|
||||
|
||||
## 兼容状态
|
||||
---
|
||||
|
||||
KernelSU Next 支持从 4.4 到 6.6 的大多数安卓内核
|
||||
- GKI 2.0(5.10+)内核可运行预置镜像和 LKM/KMI
|
||||
- GKI 1.0(4.19 - 5.4)内核需要使用 KernelSU 内核驱动重新编译
|
||||
- EOL (<4.14) 内核也需要使用 KernelSU 内核驱动重新编译 (3.18+ 的版本处于试验阶段,可能需要移植一些功能)
|
||||
## ✅ 兼容性
|
||||
|
||||
目前只支持 `arm64-v8a` 架构
|
||||
KernelSU Next 支持从 **4.4 到 6.6** 的大多数安卓内核
|
||||
|
||||
## 用法
|
||||
| 内核版本 | 支持情况 |
|
||||
|----------------|---------------|
|
||||
| 5.10+ (GKI 2.0) | 可运行预置镜像和 LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | 需要使用 KernelSU 内核驱动重新编译 |
|
||||
| <4.14 (EOL) | 需要使用 KernelSU 内核驱动重新编译(3.18+ 的版本处于试验阶段,可能需要进行回溯移植) |
|
||||
|
||||
- [安装说明](https://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).
|
||||
|
||||
## 许可证
|
||||
---
|
||||
|
||||
- 目录 `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
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
Une solution root basée sur le noyau pour les appareils Android.
|
||||
|
||||
[](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)
|
||||
[](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)
|
||||
|
||||
## 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 EOL (<4.14) doivent également être reconstruits avec le pilote KernelSU (3.18+ est expérimental et peut nécessiter des rétroportages fonctionnels).
|
||||
|
||||
Actuellement, seul `arm64-v8a` est pris en charge.
|
||||
Actuellement, seul `arm64-v8a`, `armeabi-v7a` & `x86_64` est pris en charge.
|
||||
|
||||
## Utilisation
|
||||
|
||||
- [Instructions d'installation](https://rifsxd.github.io/KernelSU-Next/)
|
||||
- [Instructions d'installation](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## Sécurité
|
||||
|
||||
@@ -41,9 +41,9 @@ Pour signaler des vulnérabilités de sécurité dans KernelSU, consultez [SECUR
|
||||
|
||||
## Crédits
|
||||
|
||||
- [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.
|
||||
- [genuine](https://github.com/brevent/genuine/) : validation de signature apk v2.
|
||||
- [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.
|
||||
- [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.
|
||||
- [genuine](https://github.com/brevent/genuine/) : Validation de signature APK v2.
|
||||
- [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.
|
||||
- [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
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
Sebuah solusi root berbasis Kernel untuk perangkat Android.
|
||||
|
||||
[](https://github.com/rifsxd/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
||||
[](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)
|
||||
[](/LICENSE)
|
||||
|
||||
## 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 EOL (<4.14) juga perlu dibangun ulang dengan driver KernelSU (3.18+ bersifat eksperimental dan mungkin memerlukan beberapa backport fungsi).
|
||||
|
||||
Saat ini, hanya `arm64-v8a` yang didukung.
|
||||
Saat ini, hanya `arm64-v8a`, `armeabi-v7a` & `x86_64` yang didukung.
|
||||
|
||||
## Penggunaan
|
||||
|
||||
- [Petunjuk instalasi](https://rifsxd.github.io/KernelSU-Next/)
|
||||
- [Petunjuk instalasi](https://ksunext.org/pages/installation.html)
|
||||
|
||||
## Keamanan
|
||||
|
||||
@@ -41,9 +41,9 @@ Untuk informasi tentang melaporkan kerentanannya di KernelSU, lihat [SECURITY.md
|
||||
|
||||
## Kredit
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): ide KernelSU.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): alat root yang kuat.
|
||||
- [genuine](https://github.com/brevent/genuine/): validasi tanda tangan apk v2.
|
||||
- [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.
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): Ide KernelSU.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): Alat root yang kuat.
|
||||
- [genuine](https://github.com/brevent/genuine/): Validasi tanda tangan APK v2.
|
||||
- [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.
|
||||
- [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
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
안드로이드 기기들을 위한 커널 기반 루팅 솔루션입니다.
|
||||
|
||||
[](https://github.com/rifsxd/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
||||
[](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)
|
||||
[](/LICENSE)
|
||||
|
||||
## 기능
|
||||
|
||||
@@ -24,11 +24,11 @@ KernelSU Next는 공식적으로 대부분의 4.4부터 6.6의 안드로이드
|
||||
- GKI 1.0 (4.19 - 5.4) 커널은 KernelSU 드라이버로 다시 빌드해야 합니다.
|
||||
- EOL (<4.14) 커널도 역시 KernelSU 드라이버로 다시 빌드해야 합니다.(3.18+는 실험적이며 일부 함수의 이식이 필요할 수 있습니다.).
|
||||
|
||||
현재는, `arm64-v8a`만 지원됩니다.
|
||||
현재는, `arm64-v8a`, `armeabi-v7a` & `x86_64` 만 지원됩니다.
|
||||
|
||||
## 사용 방법
|
||||
|
||||
- [설치 방법](https://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): 강력한 루팅 도구
|
||||
- [genuine](https://github.com/brevent/genuine/): apk v2 서명 검사
|
||||
- [genuine](https://github.com/brevent/genuine/): APK v2 서명 검사
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): 일부 rootkit 기술
|
||||
- [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에게 감사드립니다!
|
||||
|
||||
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)
|
||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Características
|
||||
---
|
||||
|
||||
1. `su` e gerenciamento de acesso root baseado em kernel.
|
||||
2. Sistema de módulos baseado em sistema de montagem dinâmica [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [Perfil do Aplicativo](https://kernelsu.org/pt_BR/guide/app-profile.html): Tranque o poder root em uma gaiola.
|
||||
## 🚀 Características
|
||||
|
||||
## Estado de compatibilidade
|
||||
- `su` e gerenciamento de acesso root baseado em kernel.
|
||||
- Sistema de módulos baseado em [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) e [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
- [Perfil do app](https://kernelsu.org/pt_BR/guide/app-profile.html): Limitar privilégios root por app.
|
||||
|
||||
KernelSU Next suporta oficialmente a maioria dos kernels Android a partir de 4.4 até 6.6.
|
||||
- Os kernels GKI 2.0 (5.10+) podem executar imagens pré-construídas e LKM/KMI.
|
||||
- Os kernels GKI 1.0 (4.19 - 5.4) precisam ser reconstruídos com o driver KernelSU.
|
||||
- Os kernels EOL (<4.14) também precisam ser reconstruídos com o driver KernelSU (3.18+ é experimental e pode precisar portar algumas funções).
|
||||
---
|
||||
|
||||
Atualmente, apenas `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).
|
||||
- Todas as outras partes, exceto o diretório `kernel` são [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
Consulte o guia de [Instalação](https://kernelsu-next.github.io/webpage/pt_BR/pages/installation.html) para obter instruções de configuração.
|
||||
|
||||
## Créditos
|
||||
---
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): a ideia do KernelSU.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): a poderosa ferramenta root.
|
||||
- [genuine](https://github.com/brevent/genuine/): validação de assinatura apk v2.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): algumas habilidades de rootkit.
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): obrigado 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!
|
||||
## 🔐 Segurança
|
||||
|
||||
Para relatar problemas de segurança, consulte [SECURITY.md](/SECURITY.md).
|
||||
|
||||
---
|
||||
|
||||
## 📜 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
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
Root-решение для Android на базе ядра.
|
||||
|
||||
[](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)
|
||||
[](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)
|
||||
|
||||
## Функции
|
||||
|
||||
@@ -24,11 +24,11 @@ KernelSU Next работает с большинством ядер Android (4.4
|
||||
- GKI 1.0 (4.19 - 5.4) требуют пересборки с драйвером KernelSU.
|
||||
- EOL (<4.14) также требуют пересборки с драйвером KernelSU (версии 3.18+ экспериментальные и могут потребовать некоторые функции бэкпортов).
|
||||
|
||||
Сейчас поддерживается только `arm64-v8a`.
|
||||
Сейчас поддерживается только `arm64-v8a`, `armeabi-v7a` & `x86_64`.
|
||||
|
||||
## Использование
|
||||
|
||||
- [Инструкция по установке](https://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-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 Next не релизнулся бы.
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff за сохранение KernelSU!
|
||||
- [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,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
|
||||
|
||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
||||
|
||||
โซลูชันรูทบนพื้นฐานเคอร์เนลสำหรับอุปกรณ์ Android
|
||||
โซลูชั่นรูทบนพื้นฐานเคอร์เนลสำหรับอุปกรณ์ Android
|
||||
|
||||
[](https://github.com/rifsxd/KernelSU-Next/releases/latest)
|
||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
||||
[](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)
|
||||
[](/LICENSE)
|
||||
|
||||
## คุณสมบัติ
|
||||
|
||||
1. การจัดการการเข้าถึงรูทและ `su` บนเคอร์เนล
|
||||
2. ระบบโมดูลที่ใช้ระบบการติดตั้งแบบไดนามิก [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): จำกัดพลังรูทไว้สำหรับแอปต่างๆ
|
||||
1. จัดการการเข้าถึงรูท และ `su` บนพื้นฐานเคอร์เนล
|
||||
2. ระบบโมดูลแบบไดนามิกเมานต์ [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): จำกัดสิทธิ์เข้าถึงรูทสำหรับแอปต่างๆ
|
||||
|
||||
## การเข้ากันในอุปกรณ์ต่างๆ
|
||||
|
||||
KernelSU Next รองรับแบบเป็นทางการตั้งแต่เคอร์เนลแอนดรอยด์ 4.4 ถึง 6.6
|
||||
- GKI 2.0 (5.10+) เคอร์เนลสามารถรันแบบไฟล์สำเร็จรูปและ LKM/KMI ได้
|
||||
- GKI 1.0 (4.19 - 5.4) เคอร์เนลต้องการ build ร่วมกับไดร์เวอร์ของทาง KernelSU
|
||||
- EOL (<4.14) เคอร์เนลก็ต้องการ build ร่วมกับไดร์เวอร์ของทาง KernelSU เช่นกัน (3.18+ ยังเป็นการทดลองอยู่และยังต้องการเขียนในหลังบ้านเพิ่มเติม)
|
||||
- GKI 2.0 (5.10+) เคอร์เนลสามารถรันไฟล์อิมเมจสำเร็จรูป และ LKM/KMI ได้
|
||||
- GKI 1.0 (4.19 - 5.4) เคอร์เนลจะต้องรีบิ้วร่วมกับไดร์เวอร์ของ KernelSU
|
||||
- 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-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).
|
||||
|
||||
## การบริจาก
|
||||
|
||||
- 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): อุปกรณ์มือเกี่ยวกับรูทที่ทรงพลัง
|
||||
- [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 ไว้
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): ที่เป็นคนริเริ่มไอเดีย KernelSU
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): เครื่องมือรูทที่ทรงพลัง
|
||||
- [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,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)
|
||||
[](https://nightly.link/rifsxd/KernelSU-Next/workflows/build-manager/next/manager)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Özellikler
|
||||
---
|
||||
|
||||
1. Çekirdek tabanlı `su` ve kök erişim yönetimi.
|
||||
2. Dinamik montaj sistemine dayalı modül sistemi [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Kök gücünü bir kafese kilitleyin.
|
||||
## 🚀 Özellikler
|
||||
|
||||
## Uyumluluk Durumu
|
||||
- Çekirdek tabanlı `su` ve root erişim yönetimi.
|
||||
- **[Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount)** ve **[OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)** tabanlı modül sistemi.
|
||||
- [Uygulama Profili](https://kernelsu.org/guide/app-profile.html): Uygulama başına root yetkisini sınırlandırma.
|
||||
|
||||
KernelSU Next, 4.4'dan başlayarak 6.6'ya kadar çoğu Android çekirdeğini resmi olarak desteklemektedir.
|
||||
- GKI 2.0 (5.10+) çekirdekleri önceden oluşturulmuş görüntüleri ve LKM/KMI'yi çalıştırabilir.
|
||||
- GKI 1.0 (4.19 - 5.4) çekirdeklerinin KernelSU sürücüsü ile yeniden oluşturulması gerekir.
|
||||
- EOL (<4.14) çekirdeklerinin de KernelSU sürücüsü ile yeniden oluşturulması gerekir. (3.18+ deneyseldir ve bazı fonksiyon geri yüklemelerine ihtiyaç duyulabilir.)
|
||||
---
|
||||
|
||||
Şu anda sadece `arm64-v8a` desteklenmektedir.
|
||||
## ✅ Uyumluluk
|
||||
|
||||
## Kullanım
|
||||
KernelSU Next, **4.4 ile 6.6** arasındaki Android çekirdeklerini destekler:
|
||||
|
||||
- [Kurulum Talimatı](https://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.
|
||||
- `kernel` dizini dışındaki diğer tüm kısımlar [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.html) ya da daha sonraki bir sürüm lisansa tabiidir.
|
||||
Kurulum talimatları için [Kurulum Kılavuzu](https://kernelsu-next.github.io/webpage/pages/installation.html) sayfasına bakınız.
|
||||
|
||||
## Krediler
|
||||
---
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU fikri.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): güçlü kök aracı.
|
||||
- [genuine](https://github.com/brevent/genuine/): apk v2 imza doğrulama.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): bazı rootkit becerileri.
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): tiann'a teşekkürler, yoksa KernelSU Next var olamazdı bile.
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff KernelSU'yu kurtardığınız için!
|
||||
## 🔐 Güvenlik
|
||||
|
||||
Güvenlik açıklarını bildirmek için lütfen [SECURITY.md](/SECURITY.md) dosyasına bakınız.
|
||||
|
||||
---
|
||||
|
||||
## 📜 Lisans
|
||||
|
||||
- **`/kernel` dizini:** [Yalnızca GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- **Diğer tüm dosyalar:** [GPL-3.0-veya-sonrası](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
---
|
||||
|
||||
## 💸 Bağışlar
|
||||
|
||||
Projeye destek olmak isterseniz:
|
||||
|
||||
- **USDT (BEP20, ERC20):** `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20):** `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20):** `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC:** `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC:** `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Katkıda Bulunanlar
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – KernelSU Fikrinin temeli
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Temel root altyapısı
|
||||
- [Genuine](https://github.com/brevent/genuine/) – APK v2 imza doğrulaması
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Rootkit teknikleri
|
||||
- [KernelSU](https://github.com/tiann/KernelSU) – KernelSU Next'in temelini oluşturan orijinal proje
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – KernelSU’yu kurtardığı için 💜 5ec1cff’e teşekkürler
|
||||
|
||||
@@ -1,50 +1,88 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | **繁體中文** | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [ภาษาไทย](README_TH.md)
|
||||
[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 權限管理
|
||||
2. 基於動態掛載系統 [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模塊系統。
|
||||
3. [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 權限關進籠子裡
|
||||
- 基於內核的 `su` 和 Root 權限管理
|
||||
- 模塊系統基於 **[Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount)** 以及 **[OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)**
|
||||
- [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 權限關進籠子裡
|
||||
|
||||
## 兼容狀態
|
||||
---
|
||||
|
||||
KernelSU Next 正式支持大多數從 4.4 到 6.6 的 Android 內核
|
||||
- GKI 2.0 (5.10+) 內核可以運行預構建的映像和 LKM/KMI
|
||||
- GKI 1.0 (4.19 - 5.4) 內核需要重新編譯 KernelSU 驅動程序
|
||||
- EOL (<4.14) 內核也需要重新編譯 KernelSU 驅動程序(3.18+ 是實驗性的,可能需要移植一些功能)
|
||||
## ✅ 兼容狀態
|
||||
|
||||
目前僅支持 `arm64-v8a`
|
||||
KernelSU Next 正式支持大多數從 **4.4 到 6.6** 的 Android 內核
|
||||
|
||||
## 用法
|
||||
| 内核版本 | 支援狀況 |
|
||||
|----------------|---------------|
|
||||
| 5.10+ (GKI 2.0) | 可以運行預構建的映像和 LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | 需要重新編譯 KernelSU 驅動程序 |
|
||||
| <4.14 (EOL) | 需要重新編譯 KernelSU 驅動程序(3.18+ 是實驗性的,可能需要回溯移植一些功能) |
|
||||
|
||||
- [安裝說明](https://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 的靈感.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): 強大的 Root 工具.
|
||||
- [genuine](https://github.com/brevent/genuine/): apk v2 簽名驗證。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): 一些 Rootkit 技巧。
|
||||
- [KernelSU](https://github.com/tiann/KernelSU): 感謝 tiann,否則 KernelSU Next 根本不會存在。
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff 為了拯救 KernelSU!
|
||||
有關報告 KernelSU Next 漏洞的信息,請參閱 [SECURITY.md](/SECURITY.md)
|
||||
|
||||
---
|
||||
|
||||
## 📜 許可證
|
||||
|
||||
- **目錄 `/kernel` 下所有文件**為 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- **`/kernel` 目錄以外的其他部分**均為 [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
---
|
||||
|
||||
## 💸 抖内
|
||||
|
||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 鳴謝
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的靈感.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):強大的 Root 工具.
|
||||
- [genuine](https://github.com/brevent/genuine/):APK v2 簽名驗證。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 Rootkit 技巧。
|
||||
- [KernelSU](https://github.com/tiann/KernelSU):感謝 tiann,否則 KernelSU Next 根本不會存在。
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs):💜 5ec1cff 為了拯救 KernelSU!
|
||||
|
||||
90
docs/README_UA.md
Normal file
90
docs/README_UA.md
Normal file
@@ -0,0 +1,90 @@
|
||||
**Languages**:
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | **Українська** | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
||||
|
||||
<h2>KernelSU Next</h2>
|
||||
<p><strong>Рішення для root-прав на основі ядра для пристроїв Android.</strong></p>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
||||
</a>
|
||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
||||
</a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Особливості
|
||||
|
||||
- Керування `su` та root-доступом на основі ядра.
|
||||
- Модульна система на основі [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) та [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
- [Профілі програм](https://kernelsu.org/guide/app-profile.html): Обмеження root-прав для кожної програми.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Сумісність
|
||||
|
||||
KernelSU Next підтримує ядра Android від **4.4 до 6.6**:
|
||||
|
||||
| Версія ядра | Примітки підтримки |
|
||||
|----------------------|-------------------------------------------------------------------------------------------|
|
||||
| 5.10+ (GKI 2.0) | Підтримує попередньо створені образи та LKM/KMI |
|
||||
| 4.19 – 5.4 (GKI 1.0) | Потрібен вбудований драйвер KernelSU |
|
||||
| <4.14 (EOL) | Потрібен драйвер KernelSU (версія 3.18+ є експериментальною, може знадобитися портування) |
|
||||
|
||||
**Підтримувані архітектури:** `arm64-v8a`, `armeabi-v7a`, `x86_64`
|
||||
|
||||
---
|
||||
|
||||
## 📦 Встановлення
|
||||
|
||||
Будь ласка, зверніться до [Посібника з встановлення](https://kernelsu-next.github.io/webpage/pages/installation.html) для отримання інструкцій з налаштування.
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Безпека
|
||||
|
||||
Щоб повідомити про проблеми безпеки, див [SECURITY.md](/SECURITY.md).
|
||||
|
||||
---
|
||||
|
||||
## 📜 Ліцензія
|
||||
|
||||
- **Каталог `/kernel`:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- **Усі інші файли:** [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
---
|
||||
|
||||
## 💸 Пожертви
|
||||
|
||||
Якщо ви хочете підтримати проєкт:
|
||||
|
||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Подяки
|
||||
|
||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Натхнення для концепції
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Топовий інструмент для root
|
||||
- [Genuine](https://github.com/brevent/genuine/) – Перевірка підпису APK версії 2
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Деякі навики RootKit
|
||||
- [KernelSU](https://github.com/tiann/KernelSU) – Основа для KernelSU Next
|
||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 до 5ec1cff за збереження KernelSU
|
||||
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
|
||||
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
|
||||
bool "KernelSU debug mode"
|
||||
depends on KSU
|
||||
@@ -24,4 +32,19 @@ config KSU_ALLOWLIST_WORKAROUND
|
||||
Enable session keyring init workaround for problematic devices.
|
||||
Useful for situations where the SU allowlist is not kept after a reboot.
|
||||
|
||||
config KSU_LSM_SECURITY_HOOKS
|
||||
bool "use lsm security hooks"
|
||||
depends on KSU
|
||||
default y
|
||||
help
|
||||
Disabling this is mostly only useful for kernel 4.1 and older.
|
||||
Make sure to implement manual hooks on security/security.c.
|
||||
|
||||
config KSU_SWITCH_MANAGER
|
||||
bool "KernelSU switch manager support"
|
||||
depends on KSU
|
||||
default n
|
||||
help
|
||||
Enable KernelSU switch manager support.
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -26,7 +26,7 @@ $(info -- KernelSU-Next version: $(KSU_VERSION))
|
||||
ccflags-y += -DKSU_VERSION=$(KSU_VERSION)
|
||||
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!")
|
||||
ccflags-y += -DKSU_VERSION=16
|
||||
ccflags-y += -DKSU_VERSION=11998
|
||||
endif
|
||||
|
||||
ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0)
|
||||
@@ -37,12 +37,24 @@ ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/includ
|
||||
ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE
|
||||
endif
|
||||
|
||||
ifndef KSU_NEXT_EXPECTED_SIZE
|
||||
KSU_NEXT_EXPECTED_SIZE := 0x3e6
|
||||
ifeq ($(shell grep -q "strncpy_from_user_nofault" $(srctree)/include/linux/uaccess.h; echo $$?),0)
|
||||
ccflags-y += -DKSU_STRNCPY_FROM_USER_NOFAULT
|
||||
endif
|
||||
|
||||
ifndef KSU_NEXT_EXPECTED_HASH
|
||||
KSU_NEXT_EXPECTED_HASH := 79e590113c4c4c0c222978e413a5faa801666957b1212a328e46c00c69821bf7
|
||||
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_MANAGER_SIZE
|
||||
KSU_NEXT_MANAGER_SIZE := 0x3e6
|
||||
endif
|
||||
|
||||
ifndef KSU_NEXT_MANAGER_HASH
|
||||
KSU_NEXT_MANAGER_HASH := 79e590113c4c4c0c222978e413a5faa801666957b1212a328e46c00c69821bf7
|
||||
endif
|
||||
|
||||
ifdef KSU_MANAGER_PACKAGE
|
||||
@@ -50,48 +62,14 @@ ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
|
||||
$(info -- KernelSU-Next Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
endif
|
||||
|
||||
$(info -- KernelSU-Next Manager signature size: $(KSU_NEXT_EXPECTED_SIZE))
|
||||
$(info -- KernelSU-Next Manager signature hash: $(KSU_NEXT_EXPECTED_HASH))
|
||||
$(info -- KernelSU-Next Manager signature size: $(KSU_NEXT_MANAGER_SIZE))
|
||||
$(info -- KernelSU-Next Manager signature hash: $(KSU_NEXT_MANAGER_HASH))
|
||||
|
||||
ccflags-y += -DEXPECTED_NEXT_SIZE=$(KSU_NEXT_EXPECTED_SIZE)
|
||||
ccflags-y += -DEXPECTED_NEXT_HASH=\"$(KSU_NEXT_EXPECTED_HASH)\"
|
||||
|
||||
ccflags-y += -DKSU_COMPAT_GET_CRED_RCU
|
||||
ccflags-y += -DEXPECTED_MANAGER_SIZE=$(KSU_NEXT_MANAGER_SIZE)
|
||||
ccflags-y += -DEXPECTED_MANAGER_HASH=\"$(KSU_NEXT_MANAGER_HASH)\"
|
||||
|
||||
ccflags-y += -DKSU_UMOUNT
|
||||
|
||||
# Determine the appropriate atomic function and apply patch accordingly
|
||||
ifeq ($(shell grep -q "atomic_inc_not_zero" $(srctree)/kernel/cred.c; echo $$?),0)
|
||||
ATOMIC_INC_FUNC = atomic_inc_not_zero
|
||||
else ifeq ($(shell grep -q "atomic_long_inc_not_zero" $(srctree)/kernel/cred.c; echo $$?),0)
|
||||
ATOMIC_INC_FUNC = atomic_long_inc_not_zero
|
||||
else
|
||||
$(info -- KSU_NEXT: Neither atomic_inc_not_zero nor atomic_long_inc_not_zero found in kernel/cred.c)
|
||||
endif
|
||||
|
||||
# Inform which function is being patched
|
||||
$(info -- KSU_NEXT: Using $(ATOMIC_INC_FUNC) in get_cred_rcu patch.)
|
||||
|
||||
# Add the get_cred_rcu function to cred.h if not already present
|
||||
ifneq ($(shell grep -Eq "^static inline const struct cred \*get_cred_rcu" $(srctree)/include/linux/cred.h; echo $$?),0)
|
||||
$(info -- KSU_NEXT: adding function 'static inline const struct cred *get_cred_rcu(const struct cred *cred);' to $(srctree)/include/linux/cred.h)
|
||||
GET_CRED_RCU = static inline const struct cred *get_cred_rcu(const struct cred *cred)\n\
|
||||
{\n\t\
|
||||
struct cred *nonconst_cred = (struct cred *) cred;\n\t\
|
||||
if (!cred)\n\t\t\
|
||||
return NULL;\n\t\
|
||||
if (!$(ATOMIC_INC_FUNC)(&nonconst_cred->usage))\n\t\t\
|
||||
return NULL;\n\t\
|
||||
validate_creds(cred);\n\t\
|
||||
return cred;\n\
|
||||
}\n
|
||||
$(shell grep -qF "$(GET_CRED_RCU)" $(srctree)/include/linux/cred.h || sed -i '/^static inline void put_cred/i $(GET_CRED_RCU)' $(srctree)/include/linux/cred.h)
|
||||
|
||||
# Modify get_task_cred in cred.c
|
||||
$(info -- KSU_NEXT: modifying 'get_task_cred' function in $(srctree)/kernel/cred.c)
|
||||
$(shell sed -i "s/!$(ATOMIC_INC_FUNC)(&((struct cred \*)cred)->usage)/!get_cred_rcu(cred)/g" $(srctree)/kernel/cred.c)
|
||||
endif
|
||||
|
||||
ifneq ($(shell grep -Eq "^static int can_umount" $(srctree)/fs/namespace.c; echo $$?),0)
|
||||
$(info -- KSU_NEXT: adding function 'static int can_umount(const struct path *path, int flags);' to $(srctree)/fs/namespace.c)
|
||||
CAN_UMOUNT = static int can_umount(const struct path *path, int flags)\n\
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <linux/capability.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/gfp.h>
|
||||
@@ -64,12 +65,14 @@ static void remove_uid_from_arr(uid_t uid)
|
||||
|
||||
static void init_default_profiles()
|
||||
{
|
||||
kernel_cap_t full_cap = CAP_FULL_SET;
|
||||
|
||||
default_root_profile.uid = 0;
|
||||
default_root_profile.gid = 0;
|
||||
default_root_profile.groups_count = 1;
|
||||
default_root_profile.groups[0] = 0;
|
||||
memset(&default_root_profile.capabilities, 0xff,
|
||||
sizeof(default_root_profile.capabilities));
|
||||
memcpy(&default_root_profile.capabilities.effective, &full_cap,
|
||||
sizeof(default_root_profile.capabilities.effective));
|
||||
default_root_profile.namespaces = 0;
|
||||
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||
|
||||
@@ -110,6 +113,7 @@ void ksu_show_allow_list(void)
|
||||
static void ksu_grant_root_to_shell()
|
||||
{
|
||||
struct app_profile profile = {
|
||||
.version = KSU_APP_PROFILE_VER,
|
||||
.allow_su = true,
|
||||
.current_uid = 2000,
|
||||
};
|
||||
@@ -152,11 +156,6 @@ static bool profile_valid(struct app_profile *profile)
|
||||
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) {
|
||||
pr_info("Unsupported profile version: %d\n", profile->version);
|
||||
return false;
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/version.h>
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
#include <linux/moduleparam.h>
|
||||
#endif
|
||||
#include <crypto/hash.h>
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
#include <crypto/sha2.h>
|
||||
@@ -17,7 +16,10 @@
|
||||
#include "apk_sign.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
#include "throne_tracker.h"
|
||||
|
||||
static unsigned int expected_manager_size = EXPECTED_MANAGER_SIZE;
|
||||
static char expected_manager_hash[SHA256_DIGEST_SIZE * 2 + 1] = EXPECTED_MANAGER_HASH;
|
||||
|
||||
struct sdesc {
|
||||
struct shash_desc shash;
|
||||
@@ -314,7 +316,76 @@ module_param_cb(ksu_debug_manager_uid, &expected_size_ops,
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_SWITCH_MANAGER
|
||||
|
||||
static int set_expected_size(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
int rv = param_set_uint(val, kp);
|
||||
pr_info("expected_manager_size set to %u\n", expected_manager_size);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int get_expected_size(char *buf, const struct kernel_param *kp)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", expected_manager_size);
|
||||
}
|
||||
|
||||
static int set_expected_hash(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
if (strlen(val) != SHA256_DIGEST_SIZE * 2) {
|
||||
pr_err("Invalid hash length: %s\n", val);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
strncpy(expected_manager_hash, val, SHA256_DIGEST_SIZE * 2);
|
||||
expected_manager_hash[SHA256_DIGEST_SIZE * 2] = '\0';
|
||||
|
||||
pr_info("expected_manager_hash set to %s\n", expected_manager_hash);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_expected_hash(char *buf, const struct kernel_param *kp)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%s\n", expected_manager_hash);
|
||||
}
|
||||
|
||||
static struct kernel_param_ops expected_size_ops = {
|
||||
.set = set_expected_size,
|
||||
.get = get_expected_size,
|
||||
};
|
||||
|
||||
static struct kernel_param_ops expected_hash_ops = {
|
||||
.set = set_expected_hash,
|
||||
.get = get_expected_hash,
|
||||
};
|
||||
|
||||
module_param_cb(expected_manager_size, &expected_size_ops, &expected_manager_size, 0644);
|
||||
|
||||
module_param_cb(expected_manager_hash, &expected_hash_ops, &expected_manager_hash, 0644);
|
||||
|
||||
#endif
|
||||
|
||||
bool is_manager_apk(char *path)
|
||||
{
|
||||
return check_v2_signature(path, EXPECTED_NEXT_SIZE, EXPECTED_NEXT_HASH);
|
||||
}
|
||||
int tries = 0;
|
||||
|
||||
while (tries++ < 10) {
|
||||
if (!is_lock_held(path))
|
||||
break;
|
||||
|
||||
pr_info("%s: waiting for %s\n", __func__, path);
|
||||
msleep(100);
|
||||
}
|
||||
|
||||
// let it go, if retry fails, check_v2_signature will fail to open it anyway
|
||||
if (tries == 10) {
|
||||
pr_info("%s: timeout for %s\n", __func__, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// set debug info to print size and hash to kernel log
|
||||
pr_info("%s: expected size: %u, expected hash: %s\n",
|
||||
path, expected_manager_size, expected_manager_hash);
|
||||
|
||||
return check_v2_signature(path, expected_manager_size, expected_manager_hash);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kprobes.h>
|
||||
#ifdef CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||
#include <linux/lsm_hooks.h>
|
||||
#endif
|
||||
#include <linux/mm.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/path.h>
|
||||
@@ -45,14 +47,14 @@
|
||||
#include "throne_tracker.h"
|
||||
#include "kernel_compat.h"
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) || defined(KSU_COMPAT_GET_CRED_RCU)
|
||||
#define KSU_GET_CRED_RCU
|
||||
#endif
|
||||
|
||||
static bool ksu_module_mounted = false;
|
||||
|
||||
extern int handle_sepolicy(unsigned long arg3, void __user *arg4);
|
||||
|
||||
static bool ksu_su_compat_enabled = true;
|
||||
extern void ksu_sucompat_init();
|
||||
extern void ksu_sucompat_exit();
|
||||
|
||||
static inline bool is_allow_su()
|
||||
{
|
||||
if (is_manager()) {
|
||||
@@ -112,6 +114,7 @@ static void setup_groups(struct root_profile *profile, struct cred *cred)
|
||||
|
||||
groups_sort(group_info);
|
||||
set_groups(cred, group_info);
|
||||
put_group_info(group_info);
|
||||
}
|
||||
|
||||
static void disable_seccomp(void)
|
||||
@@ -136,27 +139,17 @@ void escape_to_root(void)
|
||||
{
|
||||
struct cred *cred;
|
||||
|
||||
#ifdef KSU_GET_CRED_RCU
|
||||
rcu_read_lock();
|
||||
|
||||
do {
|
||||
cred = (struct cred *)__task_cred((current));
|
||||
BUG_ON(!cred);
|
||||
} while (!get_cred_rcu(cred));
|
||||
cred = prepare_creds();
|
||||
if (!cred) {
|
||||
pr_warn("prepare_creds failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cred->euid.val == 0) {
|
||||
pr_warn("Already root, don't escape!\n");
|
||||
rcu_read_unlock();
|
||||
abort_creds(cred);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
cred = (struct cred *)__task_cred(current);
|
||||
|
||||
if (cred->euid.val == 0) {
|
||||
pr_warn("Already root, don't escape!\n");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct root_profile *profile = ksu_get_root_profile(cred->uid.val);
|
||||
|
||||
@@ -169,6 +162,7 @@ void escape_to_root(void)
|
||||
cred->fsgid.val = profile->gid;
|
||||
cred->sgid.val = profile->gid;
|
||||
cred->egid.val = profile->gid;
|
||||
cred->securebits = 0;
|
||||
|
||||
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
|
||||
sizeof(kernel_cap_t));
|
||||
@@ -180,24 +174,18 @@ void escape_to_root(void)
|
||||
profile->capabilities.effective | CAP_DAC_READ_SEARCH;
|
||||
memcpy(&cred->cap_effective, &cap_for_ksud,
|
||||
sizeof(cred->cap_effective));
|
||||
memcpy(&cred->cap_inheritable, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_inheritable));
|
||||
memcpy(&cred->cap_permitted, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_permitted));
|
||||
memcpy(&cred->cap_bset, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_bset));
|
||||
memcpy(&cred->cap_ambient, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_ambient));
|
||||
// set ambient caps to all-zero
|
||||
// fixes "operation not permitted" on dbus cap dropping
|
||||
memset(&cred->cap_ambient, 0,
|
||||
sizeof(cred->cap_ambient));
|
||||
|
||||
setup_groups(profile, cred);
|
||||
|
||||
#ifdef KSU_GET_CRED_RCU
|
||||
rcu_read_unlock();
|
||||
#endif
|
||||
|
||||
commit_creds(cred);
|
||||
|
||||
// Refer to kernel/seccomp.c: seccomp_set_mode_strict
|
||||
// When disabling Seccomp, ensure that current->sighand->siglock is held during the operation.
|
||||
@@ -247,6 +235,54 @@ int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry)
|
||||
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);
|
||||
}
|
||||
|
||||
static bool is_system_bin_su(void)
|
||||
{
|
||||
static const char *su_paths[] = {
|
||||
"/system/bin/su",
|
||||
"/vendor/bin/su",
|
||||
"/product/bin/su",
|
||||
"/system_ext/bin/su",
|
||||
"/odm/bin/su",
|
||||
"/system/xbin/su",
|
||||
"/system_ext/xbin/su"
|
||||
};
|
||||
char path_buf[256];
|
||||
char *pathname;
|
||||
int i;
|
||||
|
||||
struct mm_struct *mm = current->mm;
|
||||
if (mm && mm->exe_file) {
|
||||
pathname = d_path(&mm->exe_file->f_path, path_buf, sizeof(path_buf));
|
||||
if (!IS_ERR(pathname)) {
|
||||
for (i = 0; i < ARRAY_SIZE(su_paths); i++) {
|
||||
if (strcmp(pathname, su_paths[i]) == 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
unsigned long arg4, unsigned long arg5)
|
||||
{
|
||||
@@ -269,10 +305,18 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
bool from_root = 0 == current_uid().val;
|
||||
bool from_manager = is_manager();
|
||||
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!from_root && !from_manager
|
||||
&& !(is_allow_su() && is_system_bin_su())) {
|
||||
// only root or manager can access this interface
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
if (!from_root && !from_manager) {
|
||||
// only root or manager can access this interface
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("option: 0x%x, cmd: %ld\n", option, arg2);
|
||||
@@ -305,17 +349,40 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
if (copy_to_user(arg3, &version, sizeof(version))) {
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
u32 version_flags = 0;
|
||||
#ifdef MODULE
|
||||
u32 is_lkm = 0x1;
|
||||
#else
|
||||
u32 is_lkm = 0x0;
|
||||
version_flags |= 0x1;
|
||||
#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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_GET_MANAGER_UID) {
|
||||
uid_t manager_uid = ksu_get_manager_uid();
|
||||
if (copy_to_user(arg3, &manager_uid, sizeof(manager_uid))) {
|
||||
pr_err("get manager uid failed\n");
|
||||
}
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_HOOK_MODE) {
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
const char *mode = "Kprobes";
|
||||
#else
|
||||
const char *mode = "Manual";
|
||||
#endif
|
||||
if (copy_to_user((void __user *)arg3, mode, strlen(mode) + 1)) {
|
||||
pr_info("hook: copy_to_user() failed\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_REPORT_EVENT) {
|
||||
if (!from_root) {
|
||||
return 0;
|
||||
@@ -341,6 +408,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
case EVENT_MODULE_MOUNTED: {
|
||||
ksu_module_mounted = true;
|
||||
pr_info("module mounted!\n");
|
||||
nuke_ext4_sysfs();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -414,6 +482,32 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
if (arg2 == CMD_ENABLE_SU) {
|
||||
bool enabled = (arg3 != 0);
|
||||
if (enabled == ksu_su_compat_enabled) {
|
||||
pr_info("cmd enable su but no need to change.\n");
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {// return the reply_ok directly
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
ksu_sucompat_init();
|
||||
} else {
|
||||
ksu_sucompat_exit();
|
||||
}
|
||||
ksu_su_compat_enabled = enabled;
|
||||
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// all other cmds are for 'root manager'
|
||||
if (!from_manager) {
|
||||
return 0;
|
||||
@@ -456,6 +550,44 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
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;
|
||||
}
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (arg2 == CMD_ENABLE_SU) {
|
||||
bool enabled = (arg3 != 0);
|
||||
if (enabled == ksu_su_compat_enabled) {
|
||||
pr_info("cmd enable su but no need to change.\n");
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {// return the reply_ok directly
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
ksu_sucompat_init();
|
||||
} else {
|
||||
ksu_sucompat_exit();
|
||||
}
|
||||
ksu_su_compat_enabled = enabled;
|
||||
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -508,11 +640,13 @@ static void try_umount(const char *mnt, bool check_mnt, int flags)
|
||||
|
||||
if (path.dentry != path.mnt->mnt_root) {
|
||||
// it is not root mountpoint, maybe umounted by others already.
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interest in some specific mounts
|
||||
if (check_mnt && !should_umount(&path)) {
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -576,6 +710,7 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
||||
|
||||
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
|
||||
// filter the mountpoint whose target is `/data/adb`
|
||||
try_umount("/odm", true, 0);
|
||||
try_umount("/system", true, 0);
|
||||
try_umount("/system_ext", true, 0);
|
||||
try_umount("/vendor", true, 0);
|
||||
@@ -585,10 +720,14 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
||||
// try umount ksu temp path
|
||||
try_umount("/debug_ramdisk", false, MNT_DETACH);
|
||||
try_umount("/sbin", false, MNT_DETACH);
|
||||
|
||||
|
||||
// try umount hosts file
|
||||
try_umount("/system/etc/hosts", false, MNT_DETACH);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -661,15 +800,22 @@ __maybe_unused int ksu_kprobe_exit(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
unsigned long arg4, unsigned long arg5)
|
||||
extern int ksu_handle_devpts(struct inode *inode); // sucompat.c
|
||||
|
||||
static int ksu_inode_permission(struct inode *inode, int mask)
|
||||
{
|
||||
ksu_handle_prctl(option, arg2, arg3, arg4, arg5);
|
||||
return -ENOSYS;
|
||||
if (unlikely(inode->i_sb && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC)) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("%s: devpts inode accessed with mask: %x\n", __func__, mask);
|
||||
#endif
|
||||
ksu_handle_devpts(inode);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// kernel 4.4 and 4.9
|
||||
|
||||
// kernel 4.9 and older
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND)
|
||||
static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred,
|
||||
int ksu_key_permission(key_ref_t key_ref, const struct cred *cred,
|
||||
unsigned perm)
|
||||
{
|
||||
if (init_session_keyring != NULL) {
|
||||
@@ -684,6 +830,15 @@ static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred,
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||
static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
unsigned long arg4, unsigned long arg5)
|
||||
{
|
||||
ksu_handle_prctl(option, arg2, arg3, arg4, arg5);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
|
||||
struct inode *new_inode, struct dentry *new_dentry)
|
||||
{
|
||||
@@ -701,6 +856,7 @@ static struct security_hook_list ksu_hooks[] = {
|
||||
LSM_HOOK_INIT(task_prctl, ksu_task_prctl),
|
||||
LSM_HOOK_INIT(inode_rename, ksu_inode_rename),
|
||||
LSM_HOOK_INIT(task_fix_setuid, ksu_task_fix_setuid),
|
||||
LSM_HOOK_INIT(inode_permission, ksu_inode_permission),
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND)
|
||||
LSM_HOOK_INIT(key_permission, ksu_key_permission)
|
||||
#endif
|
||||
@@ -882,16 +1038,21 @@ void __init ksu_lsm_hook_init(void)
|
||||
}
|
||||
smp_mb();
|
||||
}
|
||||
#endif
|
||||
#endif // MODULE
|
||||
#endif // CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||
|
||||
void __init ksu_core_init(void)
|
||||
{
|
||||
#ifdef CONFIG_KSU_LSM_SECURITY_HOOKS
|
||||
ksu_lsm_hook_init();
|
||||
#else
|
||||
pr_info("ksu_core_init: LSM hooks not in use.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_core_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
pr_info("ksu_core_kprobe_exit\n");
|
||||
// we dont use this now
|
||||
// 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,
|
||||
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);
|
||||
#else
|
||||
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,
|
||||
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);
|
||||
#else
|
||||
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
|
||||
}
|
||||
|
||||
#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 count)
|
||||
{
|
||||
@@ -173,3 +173,26 @@ long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline int ksu_access_ok(const void *addr, unsigned long size)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)
|
||||
return access_ok(addr, size);
|
||||
#else
|
||||
return access_ok(VERIFY_READ, addr, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
long ksu_strncpy_from_user_retry(char *dst, const void __user *unsafe_addr,
|
||||
long count)
|
||||
{
|
||||
long ret = ksu_strncpy_from_user_nofault(dst, unsafe_addr, count);
|
||||
if (likely(ret >= 0))
|
||||
return ret;
|
||||
|
||||
// we faulted! fallback to slow path
|
||||
if (unlikely(!ksu_access_ok(unsafe_addr, count)))
|
||||
return -EFAULT;
|
||||
|
||||
return strncpy_from_user(dst, unsafe_addr, count);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
extern long ksu_strncpy_from_user_nofault(char *dst,
|
||||
const void __user *unsafe_addr,
|
||||
long count);
|
||||
extern long ksu_strncpy_from_user_retry(char *dst,
|
||||
const void __user *unsafe_addr,
|
||||
long count);
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND)
|
||||
extern struct key *init_session_keyring;
|
||||
|
||||
@@ -57,7 +57,7 @@ int __init kernelsu_init(void)
|
||||
|
||||
ksu_throne_tracker_init();
|
||||
|
||||
#ifdef CONFIG_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
ksu_sucompat_init();
|
||||
ksu_ksud_init();
|
||||
#else
|
||||
@@ -80,7 +80,7 @@ void kernelsu_exit(void)
|
||||
|
||||
destroy_workqueue(ksu_workqueue);
|
||||
|
||||
#ifdef CONFIG_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
ksu_ksud_exit();
|
||||
ksu_sucompat_exit();
|
||||
#endif
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
#define CMD_SET_APP_PROFILE 11
|
||||
#define CMD_UID_GRANTED_ROOT 12
|
||||
#define CMD_UID_SHOULD_UMOUNT 13
|
||||
#define CMD_IS_SU_ENABLED 14
|
||||
#define CMD_ENABLE_SU 15
|
||||
#define CMD_GET_MANAGER_UID 16
|
||||
|
||||
#define CMD_HOOK_MODE 0xC0DEAD1A
|
||||
|
||||
#define EVENT_POST_FS_DATA 1
|
||||
#define EVENT_BOOT_COMPLETED 2
|
||||
|
||||
@@ -54,7 +54,7 @@ static void stop_vfs_read_hook();
|
||||
static void stop_execve_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_execve_hook_work;
|
||||
static struct work_struct stop_input_hook_work;
|
||||
@@ -66,6 +66,10 @@ bool ksu_input_hook __read_mostly = true;
|
||||
|
||||
u32 ksu_devpts_sid;
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
bool ksu_is_compat __read_mostly = false;
|
||||
#endif
|
||||
|
||||
void on_post_fs_data(void)
|
||||
{
|
||||
static bool done = false;
|
||||
@@ -107,6 +111,7 @@ static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
|
||||
if (get_user(compat, argv.ptr.compat + nr))
|
||||
return ERR_PTR(-EFAULT);
|
||||
|
||||
ksu_is_compat = true;
|
||||
return compat_ptr(compat);
|
||||
}
|
||||
#endif
|
||||
@@ -157,7 +162,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
struct user_arg_ptr *argv,
|
||||
struct user_arg_ptr *envp, int *flags)
|
||||
{
|
||||
#ifndef CONFIG_KPROBES
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_execveat_hook) {
|
||||
return 0;
|
||||
}
|
||||
@@ -191,7 +196,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
const char __user *p = get_user_arg_ptr(*argv, 1);
|
||||
if (p && !IS_ERR(p)) {
|
||||
char first_arg[16];
|
||||
ksu_strncpy_from_user_nofault(
|
||||
ksu_strncpy_from_user_retry(
|
||||
first_arg, p, sizeof(first_arg));
|
||||
pr_info("/system/bin/init first arg: %s\n",
|
||||
first_arg);
|
||||
@@ -216,7 +221,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
const char __user *p = get_user_arg_ptr(*argv, 1);
|
||||
if (p && !IS_ERR(p)) {
|
||||
char first_arg[16];
|
||||
ksu_strncpy_from_user_nofault(
|
||||
ksu_strncpy_from_user_retry(
|
||||
first_arg, p, sizeof(first_arg));
|
||||
pr_info("/init first arg: %s\n", first_arg);
|
||||
if (!strcmp(first_arg, "--second-stage")) {
|
||||
@@ -241,7 +246,7 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
}
|
||||
char env[256];
|
||||
// Reading environment variable strings from user space
|
||||
if (ksu_strncpy_from_user_nofault(
|
||||
if (ksu_strncpy_from_user_retry(
|
||||
env, p, sizeof(env)) < 0)
|
||||
continue;
|
||||
// Parsing environment variable names and values
|
||||
@@ -313,7 +318,7 @@ static ssize_t read_iter_proxy(struct kiocb *iocb, struct iov_iter *to)
|
||||
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
size_t *count_ptr, loff_t **pos)
|
||||
{
|
||||
#ifndef CONFIG_KPROBES
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_vfs_read_hook) {
|
||||
return 0;
|
||||
}
|
||||
@@ -332,7 +337,7 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!d_is_reg(file->f_path.dentry)) {
|
||||
if (!S_ISREG(file->f_path.dentry->d_inode->i_mode)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -390,10 +395,12 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
if (orig_read) {
|
||||
fops_proxy.read = read_proxy;
|
||||
}
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
|
||||
orig_read_iter = file->f_op->read_iter;
|
||||
if (orig_read_iter) {
|
||||
fops_proxy.read_iter = read_iter_proxy;
|
||||
}
|
||||
#endif
|
||||
// replace the file_operations
|
||||
file->f_op = &fops_proxy;
|
||||
read_count_append = rc_count;
|
||||
@@ -426,7 +433,7 @@ static bool is_volumedown_enough(unsigned int count)
|
||||
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||
int *value)
|
||||
{
|
||||
#ifndef CONFIG_KPROBES
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_input_hook) {
|
||||
return 0;
|
||||
}
|
||||
@@ -468,7 +475,39 @@ bool ksu_is_safe_mode()
|
||||
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];
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
// return early if disabled.
|
||||
if (!ksu_execveat_hook) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)
|
||||
#include "objsec.h" // task_security_struct
|
||||
bool is_ksu_transition(const struct task_security_struct *old_tsec,
|
||||
const struct task_security_struct *new_tsec)
|
||||
{
|
||||
static u32 ksu_sid;
|
||||
char *secdata;
|
||||
u32 seclen;
|
||||
bool allowed = false;
|
||||
|
||||
if (!ksu_sid)
|
||||
security_secctx_to_secid("u:r:su:s0", strlen("u:r:su:s0"), &ksu_sid);
|
||||
|
||||
if (security_secid_to_secctx(old_tsec->sid, &secdata, &seclen))
|
||||
return false;
|
||||
|
||||
allowed = (!strcmp("u:r:init:s0", secdata) && new_tsec->sid == ksu_sid);
|
||||
security_release_secctx(secdata, seclen);
|
||||
|
||||
return allowed;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void stop_vfs_read_hook()
|
||||
{
|
||||
#ifdef CONFIG_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
bool ret = schedule_work(&stop_vfs_read_work);
|
||||
pr_info("unregister vfs_read kprobe: %d!\n", ret);
|
||||
#else
|
||||
@@ -609,7 +671,7 @@ static void stop_vfs_read_hook()
|
||||
|
||||
static void stop_execve_hook()
|
||||
{
|
||||
#ifdef CONFIG_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
bool ret = schedule_work(&stop_execve_hook_work);
|
||||
pr_info("unregister execve kprobe: %d!\n", ret);
|
||||
#else
|
||||
@@ -620,15 +682,16 @@ static void stop_execve_hook()
|
||||
|
||||
static void stop_input_hook()
|
||||
{
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
static bool input_hook_stopped = false;
|
||||
if (input_hook_stopped) {
|
||||
return;
|
||||
}
|
||||
input_hook_stopped = true;
|
||||
#ifdef CONFIG_KPROBES
|
||||
bool ret = schedule_work(&stop_input_hook_work);
|
||||
pr_info("unregister input kprobe: %d!\n", ret);
|
||||
#else
|
||||
if (!ksu_input_hook) { return; }
|
||||
ksu_input_hook = false;
|
||||
pr_info("stop input_hook\n");
|
||||
#endif
|
||||
@@ -637,7 +700,7 @@ static void stop_input_hook()
|
||||
// ksud: module support
|
||||
void ksu_ksud_init()
|
||||
{
|
||||
#ifdef CONFIG_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
int ret;
|
||||
|
||||
ret = register_kprobe(&execve_kp);
|
||||
@@ -657,10 +720,10 @@ void ksu_ksud_init()
|
||||
|
||||
void ksu_ksud_exit()
|
||||
{
|
||||
#ifdef CONFIG_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
unregister_kprobe(&execve_kp);
|
||||
// this should be done before unregister vfs_read_kp
|
||||
// unregister_kprobe(&vfs_read_kp);
|
||||
unregister_kprobe(&input_event_kp);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +36,19 @@ static struct policydb *get_policydb(void)
|
||||
return db;
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(ksu_rules);
|
||||
|
||||
void apply_kernelsu_rules()
|
||||
{
|
||||
struct policydb *db;
|
||||
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, apply rules!\n");
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
struct policydb *db = get_policydb();
|
||||
mutex_lock(&ksu_rules);
|
||||
|
||||
db = get_policydb();
|
||||
|
||||
ksu_permissive(db, KERNEL_SU_DOMAIN);
|
||||
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject");
|
||||
@@ -130,11 +135,11 @@ void apply_kernelsu_rules()
|
||||
// Allow all binder transactions
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL);
|
||||
|
||||
// Allow system server kill su process
|
||||
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "getpgid");
|
||||
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill");
|
||||
// Allow system server kill su process
|
||||
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "getpgid");
|
||||
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill");
|
||||
|
||||
rcu_read_unlock();
|
||||
mutex_unlock(&ksu_rules);
|
||||
}
|
||||
|
||||
#define MAX_SEPOL_LEN 128
|
||||
@@ -149,17 +154,45 @@ void apply_kernelsu_rules()
|
||||
#define CMD_TYPE_CHANGE 8
|
||||
#define CMD_GENFSCON 9
|
||||
|
||||
#ifdef CONFIG_64BIT
|
||||
struct sepol_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
char __user *sepol1;
|
||||
char __user *sepol2;
|
||||
char __user *sepol3;
|
||||
char __user *sepol4;
|
||||
char __user *sepol5;
|
||||
char __user *sepol6;
|
||||
char __user *sepol7;
|
||||
u64 field_sepol1;
|
||||
u64 field_sepol2;
|
||||
u64 field_sepol3;
|
||||
u64 field_sepol4;
|
||||
u64 field_sepol5;
|
||||
u64 field_sepol6;
|
||||
u64 field_sepol7;
|
||||
};
|
||||
#ifdef CONFIG_COMPAT
|
||||
extern bool ksu_is_compat __read_mostly;
|
||||
struct sepol_compat_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
};
|
||||
#endif // CONFIG_COMPAT
|
||||
#else
|
||||
struct sepol_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
};
|
||||
#endif // CONFIG_64BIT
|
||||
|
||||
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||
char **object)
|
||||
@@ -204,15 +237,59 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
||||
}
|
||||
|
||||
u32 cmd, subcmd;
|
||||
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7;
|
||||
|
||||
#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
|
||||
if (unlikely(ksu_is_compat)) {
|
||||
struct sepol_compat_data compat_data;
|
||||
if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = compat_ptr(compat_data.field_sepol1);
|
||||
sepol2 = compat_ptr(compat_data.field_sepol2);
|
||||
sepol3 = compat_ptr(compat_data.field_sepol3);
|
||||
sepol4 = compat_ptr(compat_data.field_sepol4);
|
||||
sepol5 = compat_ptr(compat_data.field_sepol5);
|
||||
sepol6 = compat_ptr(compat_data.field_sepol6);
|
||||
sepol7 = compat_ptr(compat_data.field_sepol7);
|
||||
cmd = compat_data.cmd;
|
||||
subcmd = compat_data.subcmd;
|
||||
} else {
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
}
|
||||
#else
|
||||
// basically for full native, say (64BIT=y COMPAT=n) || (64BIT=n)
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 cmd = data.cmd;
|
||||
u32 subcmd = data.subcmd;
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
#endif
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
@@ -226,22 +303,22 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char perm_buf[MAX_SEPOL_LEN];
|
||||
|
||||
char *s, *t, *c, *p;
|
||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) <
|
||||
if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm failed.\n");
|
||||
goto exit;
|
||||
@@ -271,24 +348,24 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char perm_set[MAX_SEPOL_LEN];
|
||||
|
||||
char *s, *t, *c;
|
||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(operation, data.sepol4,
|
||||
if (strncpy_from_user(operation, sepol4,
|
||||
sizeof(operation)) < 0) {
|
||||
pr_err("sepol: copy operation failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(perm_set, data.sepol5, sizeof(perm_set)) <
|
||||
if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm_set failed.\n");
|
||||
goto exit;
|
||||
@@ -308,7 +385,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
} else if (cmd == CMD_TYPE_STATE) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
@@ -328,11 +405,11 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char type[MAX_SEPOL_LEN];
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(type, data.sepol1, sizeof(type)) < 0) {
|
||||
if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) {
|
||||
pr_err("sepol: copy type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(attr, data.sepol2, sizeof(attr)) < 0) {
|
||||
if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
@@ -352,7 +429,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
} else if (cmd == CMD_ATTR) {
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(attr, data.sepol1, sizeof(attr)) < 0) {
|
||||
if (strncpy_from_user(attr, sepol1, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
@@ -369,28 +446,28 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
char object[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, data.sepol4,
|
||||
if (strncpy_from_user(default_type, sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
char *real_object;
|
||||
if (data.sepol5 == NULL) {
|
||||
if (sepol5 == NULL) {
|
||||
real_object = NULL;
|
||||
} else {
|
||||
if (strncpy_from_user(object, data.sepol5,
|
||||
if (strncpy_from_user(object, sepol5,
|
||||
sizeof(object)) < 0) {
|
||||
pr_err("sepol: copy object failed.\n");
|
||||
goto exit;
|
||||
@@ -409,19 +486,19 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char cls[MAX_SEPOL_LEN];
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, data.sepol4,
|
||||
if (strncpy_from_user(default_type, sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
@@ -442,15 +519,15 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
char name[MAX_SEPOL_LEN];
|
||||
char path[MAX_SEPOL_LEN];
|
||||
char context[MAX_SEPOL_LEN];
|
||||
if (strncpy_from_user(name, data.sepol1, sizeof(name)) < 0) {
|
||||
if (strncpy_from_user(name, sepol1, sizeof(name)) < 0) {
|
||||
pr_err("sepol: copy name failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(path, data.sepol2, sizeof(path)) < 0) {
|
||||
if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) {
|
||||
pr_err("sepol: copy path failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(context, data.sepol3, sizeof(context)) <
|
||||
if (strncpy_from_user(context, sepol3, sizeof(context)) <
|
||||
0) {
|
||||
pr_err("sepol: copy context failed.\n");
|
||||
goto exit;
|
||||
|
||||
@@ -39,7 +39,7 @@ perform_cleanup() {
|
||||
# Sets up or update KernelSU-Next environment
|
||||
setup_kernelsu() {
|
||||
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"
|
||||
git stash && echo "[-] Stashed current changes."
|
||||
if [ "$(git status | grep -Po 'v\d+(\.\d+)*' | head -n1)" ]; then
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
#define SU_PATH "/system/bin/su"
|
||||
#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();
|
||||
|
||||
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;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
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 su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp){
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -119,6 +135,12 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||
const char sh[] = KSUD_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (unlikely(!filename_ptr))
|
||||
return 0;
|
||||
|
||||
@@ -148,11 +170,17 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
||||
const char su[] = SU_PATH;
|
||||
char path[sizeof(su) + 1];
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (unlikely(!filename_user))
|
||||
return 0;
|
||||
|
||||
memset(path, 0, sizeof(path));
|
||||
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
ksu_strncpy_from_user_retry(path, *filename_user, sizeof(path));
|
||||
|
||||
if (likely(memcmp(path, su, sizeof(su))))
|
||||
return 0;
|
||||
@@ -170,6 +198,12 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
||||
|
||||
int ksu_handle_devpts(struct inode *inode)
|
||||
{
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_non_kp) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!current->mm) {
|
||||
return 0;
|
||||
}
|
||||
@@ -198,21 +232,9 @@ int ksu_handle_devpts(struct inode *inode)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KPROBES
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
|
||||
__maybe_unused static int faccessat_handler_pre(struct kprobe *p,
|
||||
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)
|
||||
static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||
@@ -223,23 +245,7 @@ static int sys_faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
return ksu_handle_faccessat(dfd, filename_user, mode, NULL);
|
||||
}
|
||||
|
||||
__maybe_unused static int newfstatat_handler_pre(struct kprobe *p,
|
||||
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)
|
||||
static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||
@@ -250,17 +256,7 @@ static int sys_newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
const char __user **filename_user =
|
||||
@@ -270,56 +266,6 @@ static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
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)
|
||||
{
|
||||
struct inode *inode;
|
||||
@@ -333,35 +279,61 @@ static int pts_unix98_lookup_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
return ksu_handle_devpts(inode);
|
||||
}
|
||||
|
||||
static struct kprobe pts_unix98_lookup_kp = { .symbol_name =
|
||||
"pts_unix98_lookup",
|
||||
.pre_handler =
|
||||
pts_unix98_lookup_pre };
|
||||
static struct kprobe *init_kprobe(const char *name,
|
||||
kprobe_pre_handler_t handler)
|
||||
{
|
||||
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
|
||||
|
||||
// sucompat: permited process can execute 'su' to gain root access.
|
||||
void ksu_sucompat_init()
|
||||
{
|
||||
#ifdef CONFIG_KPROBES
|
||||
int ret;
|
||||
ret = register_kprobe(&execve_kp);
|
||||
pr_info("sucompat: execve_kp: %d\n", ret);
|
||||
ret = register_kprobe(&newfstatat_kp);
|
||||
pr_info("sucompat: newfstatat_kp: %d\n", ret);
|
||||
ret = register_kprobe(&faccessat_kp);
|
||||
pr_info("sucompat: faccessat_kp: %d\n", ret);
|
||||
ret = register_kprobe(&pts_unix98_lookup_kp);
|
||||
pr_info("sucompat: devpts_kp: %d\n", ret);
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
|
||||
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
|
||||
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
|
||||
su_kps[3] = init_kprobe("pts_unix98_lookup", pts_unix98_lookup_pre);
|
||||
#else
|
||||
ksu_sucompat_non_kp = true;
|
||||
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat, devpts\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_sucompat_exit()
|
||||
{
|
||||
#ifdef CONFIG_KPROBES
|
||||
unregister_kprobe(&execve_kp);
|
||||
unregister_kprobe(&newfstatat_kp);
|
||||
unregister_kprobe(&faccessat_kp);
|
||||
unregister_kprobe(&pts_unix98_lookup_kp);
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
for (int i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
||||
destroy_kprobe(&su_kps[i]);
|
||||
}
|
||||
#else
|
||||
ksu_sucompat_non_kp = false;
|
||||
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat, devpts\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -148,6 +148,12 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||
if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen))
|
||||
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,
|
||||
namelen, name) >= DATA_PATH_LEN) {
|
||||
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;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0)
|
||||
strlcpy(data->dirpath, dirpath, DATA_PATH_LEN);
|
||||
#else
|
||||
strscpy(data->dirpath, dirpath, DATA_PATH_LEN);
|
||||
#endif
|
||||
data->depth = my_ctx->depth - 1;
|
||||
list_add_tail(&data->list, my_ctx->data_path_list);
|
||||
} else {
|
||||
@@ -206,12 +216,53 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||
return FILLDIR_ACTOR_CONTINUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* small helper to check if lock is held
|
||||
* false - file is stable
|
||||
* true - file is being deleted/renamed
|
||||
* possibly optional
|
||||
*
|
||||
*/
|
||||
bool is_lock_held(const char *path)
|
||||
{
|
||||
struct path kpath;
|
||||
|
||||
// kern_path returns 0 on success
|
||||
if (kern_path(path, 0, &kpath))
|
||||
return true;
|
||||
|
||||
// just being defensive
|
||||
if (!kpath.dentry) {
|
||||
path_put(&kpath);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!spin_trylock(&kpath.dentry->d_lock)) {
|
||||
pr_info("%s: lock held, bail out!\n", __func__);
|
||||
path_put(&kpath);
|
||||
return true;
|
||||
}
|
||||
// we hold it ourselves here!
|
||||
|
||||
spin_unlock(&kpath.dentry->d_lock);
|
||||
path_put(&kpath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// compat: https://elixir.bootlin.com/linux/v3.9/source/include/linux/fs.h#L771
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)
|
||||
#define S_MAGIC_COMPAT(x) ((x)->f_inode->i_sb->s_magic)
|
||||
#else
|
||||
#define S_MAGIC_COMPAT(x) ((x)->f_path.dentry->d_inode->i_sb->s_magic)
|
||||
#endif
|
||||
|
||||
void search_manager(const char *path, int depth, struct list_head *uid_data)
|
||||
{
|
||||
int i, stop = 0;
|
||||
struct list_head data_path_list;
|
||||
INIT_LIST_HEAD(&data_path_list);
|
||||
|
||||
unsigned long data_app_magic = 0;
|
||||
|
||||
// Initialize APK cache list
|
||||
struct apk_path_hash *pos, *n;
|
||||
list_for_each_entry(pos, &apk_path_hash_list, list) {
|
||||
@@ -220,11 +271,15 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
|
||||
|
||||
// First depth
|
||||
struct data_path data;
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0)
|
||||
strlcpy(data.dirpath, path, DATA_PATH_LEN);
|
||||
#else
|
||||
strscpy(data.dirpath, path, DATA_PATH_LEN);
|
||||
#endif
|
||||
data.depth = depth;
|
||||
list_add_tail(&data.list, &data_path_list);
|
||||
|
||||
for (i = depth; i > 0; i--) {
|
||||
for (i = depth; i >= 0; i--) {
|
||||
struct data_path *pos, *n;
|
||||
|
||||
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));
|
||||
goto skip_iterate;
|
||||
}
|
||||
|
||||
// grab magic on first folder, which is /data/app
|
||||
if (!data_app_magic) {
|
||||
if (S_MAGIC_COMPAT(file)) {
|
||||
data_app_magic = S_MAGIC_COMPAT(file);
|
||||
pr_info("%s: dir: %s got magic! 0x%lx\n", __func__, pos->dirpath, data_app_magic);
|
||||
} else {
|
||||
filp_close(file, NULL);
|
||||
goto skip_iterate;
|
||||
}
|
||||
}
|
||||
|
||||
if (S_MAGIC_COMPAT(file) != data_app_magic) {
|
||||
pr_info("%s: skip: %s magic: 0x%lx expected: 0x%lx\n", __func__, pos->dirpath,
|
||||
S_MAGIC_COMPAT(file), data_app_magic);
|
||||
filp_close(file, NULL);
|
||||
goto skip_iterate;
|
||||
}
|
||||
|
||||
iterate_dir(file, &ctx.ctx);
|
||||
filp_close(file, NULL);
|
||||
@@ -280,13 +353,25 @@ static bool is_uid_exist(uid_t uid, char *package, void *data)
|
||||
|
||||
void track_throne()
|
||||
{
|
||||
struct file *fp =
|
||||
ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
|
||||
struct file *fp;
|
||||
int tries = 0;
|
||||
|
||||
while (tries++ < 10) {
|
||||
if (!is_lock_held(SYSTEM_PACKAGES_LIST_PATH)) {
|
||||
fp = ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
|
||||
if (!IS_ERR(fp))
|
||||
break;
|
||||
}
|
||||
|
||||
pr_info("%s: waiting for %s\n", __func__, SYSTEM_PACKAGES_LIST_PATH);
|
||||
msleep(100); // migth as well add a delay
|
||||
};
|
||||
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n",
|
||||
__func__, PTR_ERR(fp));
|
||||
pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n", __func__, PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
} else
|
||||
pr_info("%s: %s found!\n", __func__, SYSTEM_PACKAGES_LIST_PATH);
|
||||
|
||||
struct list_head uid_list;
|
||||
INIT_LIST_HEAD(&uid_list);
|
||||
@@ -355,12 +440,14 @@ void track_throne()
|
||||
if (ksu_is_manager_uid_valid()) {
|
||||
pr_info("manager is uninstalled, invalidate it!\n");
|
||||
ksu_invalidate_manager_uid();
|
||||
goto prune;
|
||||
}
|
||||
pr_info("Searching manager...\n");
|
||||
search_manager("/data/app", 2, &uid_list);
|
||||
pr_info("Search manager finished\n");
|
||||
}
|
||||
|
||||
prune:
|
||||
// then prune the allowlist
|
||||
ksu_prune_allowlist(is_uid_exist, &uid_list);
|
||||
out:
|
||||
|
||||
@@ -7,4 +7,6 @@ void ksu_throne_tracker_exit();
|
||||
|
||||
void track_throne();
|
||||
|
||||
bool is_lock_held(const char *path);
|
||||
|
||||
#endif
|
||||
|
||||
3
manager/.gitignore
vendored
3
manager/.gitignore
vendored
@@ -7,5 +7,4 @@ build
|
||||
captures
|
||||
.cxx
|
||||
local.properties
|
||||
key.jks
|
||||
setup.sh
|
||||
key.jks
|
||||
@@ -133,4 +133,6 @@ dependencies {
|
||||
implementation(libs.androidx.webkit)
|
||||
|
||||
implementation(libs.lsposed.cxx)
|
||||
|
||||
implementation(libs.mmrl.ui)
|
||||
}
|
||||
39
manager/app/proguard-rules.pro
vendored
39
manager/app/proguard-rules.pro
vendored
@@ -0,0 +1,39 @@
|
||||
-verbose
|
||||
-optimizationpasses 5
|
||||
|
||||
-dontwarn org.conscrypt.**
|
||||
-dontwarn kotlinx.serialization.**
|
||||
|
||||
# Please add these rules to your existing keep rules in order to suppress warnings.
|
||||
# This is generated automatically by the Android Gradle plugin.
|
||||
-dontwarn com.google.auto.service.AutoService
|
||||
-dontwarn com.google.j2objc.annotations.RetainedWith
|
||||
-dontwarn javax.lang.model.SourceVersion
|
||||
-dontwarn javax.lang.model.element.AnnotationMirror
|
||||
-dontwarn javax.lang.model.element.AnnotationValue
|
||||
-dontwarn javax.lang.model.element.Element
|
||||
-dontwarn javax.lang.model.element.ElementKind
|
||||
-dontwarn javax.lang.model.element.ElementVisitor
|
||||
-dontwarn javax.lang.model.element.ExecutableElement
|
||||
-dontwarn javax.lang.model.element.Modifier
|
||||
-dontwarn javax.lang.model.element.Name
|
||||
-dontwarn javax.lang.model.element.PackageElement
|
||||
-dontwarn javax.lang.model.element.TypeElement
|
||||
-dontwarn javax.lang.model.element.TypeParameterElement
|
||||
-dontwarn javax.lang.model.element.VariableElement
|
||||
-dontwarn javax.lang.model.type.ArrayType
|
||||
-dontwarn javax.lang.model.type.DeclaredType
|
||||
-dontwarn javax.lang.model.type.ExecutableType
|
||||
-dontwarn javax.lang.model.type.TypeKind
|
||||
-dontwarn javax.lang.model.type.TypeMirror
|
||||
-dontwarn javax.lang.model.type.TypeVariable
|
||||
-dontwarn javax.lang.model.type.TypeVisitor
|
||||
-dontwarn javax.lang.model.util.AbstractAnnotationValueVisitor8
|
||||
-dontwarn javax.lang.model.util.AbstractTypeVisitor8
|
||||
-dontwarn javax.lang.model.util.ElementFilter
|
||||
-dontwarn javax.lang.model.util.Elements
|
||||
-dontwarn javax.lang.model.util.SimpleElementVisitor8
|
||||
-dontwarn javax.lang.model.util.SimpleTypeVisitor7
|
||||
-dontwarn javax.lang.model.util.SimpleTypeVisitor8
|
||||
-dontwarn javax.lang.model.util.Types
|
||||
-dontwarn javax.tools.Diagnostic$Kind
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
|
||||
|
||||
<application
|
||||
android:name=".KernelSUApplication"
|
||||
@@ -24,6 +25,15 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:pathPattern=".*\\.zip" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@@ -33,6 +43,13 @@
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.webui.WebUIXActivity"
|
||||
android:autoRemoveFromRecents="true"
|
||||
android:documentLaunchMode="intoExisting"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
||||
9
manager/app/src/main/assets/eruda.min.js
vendored
Normal file
9
manager/app/src/main/assets/eruda.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -25,6 +25,20 @@ Java_com_rifsxd_ksunext_Natives_getVersion(JNIEnv *env, jobject) {
|
||||
return get_version();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_rifsxd_ksunext_Natives_getManagerUid(JNIEnv *env, jobject) {
|
||||
uid_t manager_uid = get_manager_uid();
|
||||
return (jint)manager_uid;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_rifsxd_ksunext_Natives_getHookMode(JNIEnv *env, jobject) {
|
||||
const char* mode = get_hook_mode();
|
||||
return env->NewStringUTF(mode);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_com_rifsxd_ksunext_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||
@@ -296,3 +310,19 @@ JNIEXPORT jboolean JNICALL
|
||||
Java_com_rifsxd_ksunext_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint 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 <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ksu.h"
|
||||
@@ -27,6 +28,11 @@
|
||||
|
||||
#define CMD_IS_UID_GRANTED_ROOT 12
|
||||
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
||||
#define CMD_IS_SU_ENABLED 14
|
||||
#define CMD_ENABLE_SU 15
|
||||
#define CMD_GET_MANAGER_UID 16
|
||||
|
||||
#define CMD_HOOK_MODE 0xC0DEAD1A
|
||||
|
||||
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||
int32_t result = 0;
|
||||
@@ -48,17 +54,31 @@ bool become_manager(const char* pkg) {
|
||||
}
|
||||
|
||||
// cache the result to avoid unnecessary syscall
|
||||
static bool is_lkm;
|
||||
int get_version() {
|
||||
static bool is_lkm = false;
|
||||
|
||||
int get_version(void) {
|
||||
int32_t version = -1;
|
||||
int32_t lkm = 0;
|
||||
ksuctl(CMD_GET_VERSION, &version, &lkm);
|
||||
if (!is_lkm && lkm != 0) {
|
||||
int32_t flags = 0;
|
||||
ksuctl(CMD_GET_VERSION, &version, &flags);
|
||||
if (!is_lkm && (flags & 0x1)) {
|
||||
is_lkm = true;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
uid_t get_manager_uid() {
|
||||
uid_t manager_uid = 0;
|
||||
ksuctl(CMD_GET_MANAGER_UID, &manager_uid, nullptr);
|
||||
return manager_uid;
|
||||
}
|
||||
|
||||
const char* get_hook_mode() {
|
||||
static char mode[16];
|
||||
ksuctl(CMD_HOOK_MODE, mode, nullptr);
|
||||
return mode;
|
||||
}
|
||||
|
||||
|
||||
bool get_allow_list(int *uids, int *size) {
|
||||
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
||||
}
|
||||
@@ -84,3 +104,18 @@ bool set_app_profile(const app_profile *profile) {
|
||||
bool get_app_profile(p_key_t key, app_profile *profile) {
|
||||
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();
|
||||
|
||||
uid_t get_manager_uid();
|
||||
|
||||
const char* get_hook_mode();
|
||||
|
||||
bool get_allow_list(int *uids, int *size);
|
||||
|
||||
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 set_su_enabled(bool enabled);
|
||||
|
||||
bool is_su_enabled();
|
||||
|
||||
bool is_zygisk_enabled();
|
||||
|
||||
#endif //KERNELSU_KSU_H
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package com.rifsxd.ksunext
|
||||
|
||||
import android.app.Application
|
||||
import android.system.Os
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
lateinit var ksuApp: KernelSUApplication
|
||||
|
||||
class KernelSUApplication : Application() {
|
||||
|
||||
lateinit var okhttpClient: OkHttpClient
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
ksuApp = this
|
||||
@@ -30,7 +36,20 @@ class KernelSUApplication : Application() {
|
||||
if (!webroot.exists()) {
|
||||
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
|
||||
}
|
||||
|
||||
fun isULegacy(): Boolean {
|
||||
return major == 3
|
||||
}
|
||||
|
||||
fun isLegacy(): Boolean {
|
||||
return major == 4 && patchLevel in 1..18
|
||||
}
|
||||
|
||||
fun isGKI1(): Boolean {
|
||||
return (major == 4 && patchLevel >= 19) || (major == 5 && patchLevel < 10)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun parseKernelVersion(version: String): KernelVersion {
|
||||
|
||||
@@ -74,4 +74,4 @@ public class KsuService extends RootService {
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,22 @@ object Natives {
|
||||
// 10946: add capabilities
|
||||
// 10977: change groups_count and groups to avoid overflow write
|
||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 11071
|
||||
// 12797: zygisk query and get manager uid.
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 12797
|
||||
|
||||
// 11640: Support query working mode, LKM or GKI
|
||||
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 12797
|
||||
|
||||
// 12404: Support disable sucompat mode
|
||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12404
|
||||
|
||||
// 12569: support get hook mode
|
||||
const val MINIMAL_SUPPORTED_HOOK_MODE = 12569
|
||||
|
||||
// 12750: support get manager UID
|
||||
const val MINIMAL_SUPPORTED_MANAGER_UID = 12751
|
||||
|
||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||
|
||||
const val ROOT_UID = 0
|
||||
@@ -47,6 +58,27 @@ object Natives {
|
||||
|
||||
external fun uidShouldUmount(uid: Int): Boolean
|
||||
|
||||
/**
|
||||
* Get the UID of the current root manager.
|
||||
* @return manager UID, or 0 if unavailable.
|
||||
*/
|
||||
external fun getManagerUid(): Int
|
||||
|
||||
/**
|
||||
* Get a string indicating the SU hook mode enabled in kernel.
|
||||
* The return values are:
|
||||
* - "Manual": Manual hooks was enabled.
|
||||
* - "Kprobes": Kprobes hooks was enabled (CONFIG_KSU_KPROBES_HOOK).
|
||||
*
|
||||
* @return return hook mode, or null if unavailable.
|
||||
*/
|
||||
external fun getHookMode(): String?
|
||||
|
||||
/**
|
||||
* Check if Zygisk injection is enabled in the environment.
|
||||
*/
|
||||
external fun isZygiskEnabled(): Boolean
|
||||
|
||||
/**
|
||||
* Get the profile of the given package.
|
||||
* @param key usually the package name
|
||||
@@ -55,6 +87,15 @@ object Natives {
|
||||
external fun getAppProfile(key: String?, uid: Int): Profile
|
||||
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 NOBODY_UID = 9999
|
||||
|
||||
@@ -79,6 +120,9 @@ object Natives {
|
||||
return version < MINIMAL_SUPPORTED_KERNEL
|
||||
}
|
||||
|
||||
val KSU_WORK_DIR = "/data/adb/ksu/"
|
||||
val GLOBAL_NAMESPACE_FILE = KSU_WORK_DIR + ".global_mnt"
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
@Keep
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.rifsxd.ksunext.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -21,6 +24,8 @@ import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.BadgedBox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
@@ -29,11 +34,13 @@ import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
@@ -49,13 +56,22 @@ import com.rifsxd.ksunext.Natives
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.ui.screen.BottomBarDestination
|
||||
import com.rifsxd.ksunext.ui.theme.KernelSUTheme
|
||||
import com.rifsxd.ksunext.ui.util.*
|
||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||
import com.rifsxd.ksunext.ui.util.LocaleHelper
|
||||
import com.rifsxd.ksunext.ui.util.rootAvailable
|
||||
import com.rifsxd.ksunext.ui.util.install
|
||||
import com.rifsxd.ksunext.ui.util.*
|
||||
import com.rifsxd.ksunext.ui.util.isSuCompatDisabled
|
||||
import com.rifsxd.ksunext.ui.screen.FlashIt
|
||||
import com.rifsxd.ksunext.ui.viewmodel.ModuleViewModel
|
||||
import com.rifsxd.ksunext.ui.viewmodel.SuperUserViewModel
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
super.attachBaseContext(newBase?.let { LocaleHelper.applyLanguage(it) })
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
// Enable edge to edge
|
||||
@@ -67,14 +83,73 @@ class MainActivity : ComponentActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
if (isManager) install()
|
||||
if (isManager) install()
|
||||
|
||||
val zipUri: Uri? = when (intent?.action) {
|
||||
Intent.ACTION_VIEW, Intent.ACTION_SEND -> {
|
||||
val uri = intent.data ?: intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
uri?.let {
|
||||
val name = when (it.scheme) {
|
||||
"file" -> it.lastPathSegment ?: ""
|
||||
"content" -> {
|
||||
contentResolver.query(it, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
|
||||
if (cursor.moveToFirst() && nameIndex != -1) {
|
||||
cursor.getString(nameIndex)
|
||||
} else {
|
||||
it.lastPathSegment ?: ""
|
||||
}
|
||||
} ?: (it.lastPathSegment ?: "")
|
||||
}
|
||||
else -> it.lastPathSegment ?: ""
|
||||
}
|
||||
if (name.lowercase().endsWith(".zip")) it else null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
setContent {
|
||||
KernelSUTheme {
|
||||
// Read AMOLED mode preference
|
||||
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val amoledMode = prefs.getBoolean("enable_amoled", false)
|
||||
|
||||
val moduleViewModel: ModuleViewModel = viewModel()
|
||||
val superUserViewModel: SuperUserViewModel = viewModel()
|
||||
val moduleUpdateCount = moduleViewModel.moduleList.count {
|
||||
moduleViewModel.checkUpdate(it).first.isNotEmpty()
|
||||
}
|
||||
|
||||
KernelSUTheme (
|
||||
amoledMode = amoledMode
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val snackBarHostState = remember { SnackbarHostState() }
|
||||
val currentDestination = navController.currentBackStackEntryAsState()?.value?.destination
|
||||
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
|
||||
LaunchedEffect(zipUri) {
|
||||
if (zipUri != null) {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashModules(listOf(zipUri)),
|
||||
finishIntent = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (superUserViewModel.appList.isEmpty()) {
|
||||
superUserViewModel.fetchAppList()
|
||||
}
|
||||
|
||||
if (moduleViewModel.moduleList.isEmpty()) {
|
||||
moduleViewModel.fetchModuleList()
|
||||
}
|
||||
}
|
||||
|
||||
val showBottomBar = when (currentDestination?.route) {
|
||||
FlashScreenDestination.route -> false // Hide for FlashScreenDestination
|
||||
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen
|
||||
@@ -88,7 +163,7 @@ class MainActivity : ComponentActivity() {
|
||||
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
||||
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
||||
) {
|
||||
BottomBar(navController)
|
||||
BottomBar(navController, moduleUpdateCount)
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||
@@ -115,43 +190,59 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomBar(navController: NavHostController) {
|
||||
private fun BottomBar(navController: NavHostController, moduleUpdateCount: Int) {
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
||||
val suCompatDisabled = isSuCompatDisabled()
|
||||
val suSFS = getSuSFS()
|
||||
val susSUMode = susfsSUS_SU_Mode()
|
||||
|
||||
NavigationBar(
|
||||
tonalElevation = 8.dp,
|
||||
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
||||
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
|
||||
)
|
||||
) {
|
||||
BottomBarDestination.entries.forEach { destination ->
|
||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||
NavigationBarItem(
|
||||
selected = isCurrentDestOnBackStack,
|
||||
onClick = {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
navigator.popBackStack(destination.direction, false)
|
||||
}
|
||||
navigator.navigate(destination.direction) {
|
||||
popUpTo(NavGraphs.root) {
|
||||
saveState = true
|
||||
BottomBarDestination.entries
|
||||
.forEach { destination ->
|
||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||
NavigationBarItem(
|
||||
selected = isCurrentDestOnBackStack,
|
||||
onClick = {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
navigator.popBackStack(destination.direction, false)
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
alwaysShowLabel = true
|
||||
)
|
||||
}
|
||||
navigator.navigate(destination.direction) {
|
||||
popUpTo(NavGraphs.root) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
// Show badge for Module icon if moduleUpdateCount > 0
|
||||
if (destination == BottomBarDestination.Module && moduleUpdateCount > 0) {
|
||||
BadgedBox(badge = { Badge { Text(moduleUpdateCount.toString()) } }) {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isCurrentDestOnBackStack) {
|
||||
Icon(destination.iconSelected, stringResource(destination.label))
|
||||
} else {
|
||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
}
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
alwaysShowLabel = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import com.rifsxd.ksunext.BuildConfig
|
||||
import com.rifsxd.ksunext.R
|
||||
|
||||
@@ -83,8 +84,9 @@ private fun AboutCardContent() {
|
||||
Column {
|
||||
|
||||
Text(
|
||||
stringResource(id = R.string.app_name),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
text = stringResource(id = R.string.app_name),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
Text(
|
||||
@@ -98,7 +100,7 @@ private fun AboutCardContent() {
|
||||
val annotatedString = AnnotatedString.Companion.fromHtml(
|
||||
htmlString = stringResource(
|
||||
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(
|
||||
style = SpanStyle(
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
@@ -402,7 +403,11 @@ private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, di
|
||||
dismiss()
|
||||
},
|
||||
title = {
|
||||
Text(text = visuals.title)
|
||||
Text(
|
||||
text = visuals.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
},
|
||||
text = {
|
||||
if (visuals.isMarkdown) {
|
||||
|
||||
@@ -5,14 +5,20 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||
import com.dergoogler.mmrl.ui.component.text.TextRow
|
||||
|
||||
@Composable
|
||||
fun SwitchItem(
|
||||
@@ -21,9 +27,11 @@ fun SwitchItem(
|
||||
summary: String? = null,
|
||||
checked: Boolean,
|
||||
enabled: Boolean = true,
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
beta: Boolean = false,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val stateAlpha = remember(checked, enabled) { Modifier.alpha(if (enabled) 1f else 0.5f) }
|
||||
|
||||
ListItem(
|
||||
modifier = Modifier
|
||||
@@ -36,10 +44,32 @@ fun SwitchItem(
|
||||
onValueChange = onCheckedChange
|
||||
),
|
||||
headlineContent = {
|
||||
Text(title)
|
||||
TextRow(
|
||||
leadingContent = if (beta) {
|
||||
{
|
||||
LabelItem(
|
||||
modifier = Modifier.then(stateAlpha),
|
||||
text = "Beta"
|
||||
)
|
||||
}
|
||||
} else null
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.then(stateAlpha),
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
},
|
||||
leadingContent = icon?.let {
|
||||
{ Icon(icon, title) }
|
||||
{
|
||||
Icon(
|
||||
modifier = Modifier.then(stateAlpha),
|
||||
imageVector = icon,
|
||||
contentDescription = title
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(
|
||||
@@ -51,7 +81,10 @@ fun SwitchItem(
|
||||
},
|
||||
supportingContent = {
|
||||
if (summary != null) {
|
||||
Text(summary)
|
||||
Text(
|
||||
modifier = Modifier.then(stateAlpha),
|
||||
text = summary
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -73,6 +73,7 @@ fun RootProfileConfig(
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val currentNamespace = when (profile.namespace) {
|
||||
Natives.Profile.Namespace.INHERITED.ordinal -> stringResource(R.string.profile_namespace_inherited)
|
||||
@@ -126,6 +127,7 @@ fun RootProfileConfig(
|
||||
}
|
||||
}
|
||||
})
|
||||
*/
|
||||
|
||||
UidPanel(uid = profile.uid, label = "uid", onUidChange = {
|
||||
onProfileChange(
|
||||
|
||||
@@ -37,6 +37,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -44,6 +45,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -55,6 +57,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -93,8 +97,10 @@ fun AppProfileScreen(
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewModel: SuperUserViewModel = viewModel()
|
||||
val failToUpdateAppProfile = stringResource(R.string.failed_to_update_app_profile).format(appInfo.label)
|
||||
val failToUpdateSepolicy = stringResource(R.string.failed_to_update_sepolicy).format(appInfo.label)
|
||||
val suNotAllowed = stringResource(R.string.su_not_allowed).format(appInfo.label)
|
||||
|
||||
val packageName = appInfo.packageName
|
||||
val initialProfile = Natives.getAppProfile(packageName, appInfo.uid)
|
||||
@@ -108,7 +114,7 @@ fun AppProfileScreen(
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
onBack = { navigator.popBackStack() },
|
||||
onBack = dropUnlessResumed { navigator.popBackStack() },
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
@@ -143,8 +149,13 @@ fun AppProfileScreen(
|
||||
},
|
||||
onProfileChange = {
|
||||
scope.launch {
|
||||
if (it.allowSu && !it.rootUseDefault && it.rules.isNotEmpty()) {
|
||||
if (!setSepolicy(profile.name, it.rules)) {
|
||||
if (it.allowSu) {
|
||||
// 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)
|
||||
return@launch
|
||||
}
|
||||
@@ -153,6 +164,7 @@ fun AppProfileScreen(
|
||||
snackBarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
|
||||
} else {
|
||||
profile = it
|
||||
viewModel.updateAppProfile(packageName, it)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -176,7 +188,11 @@ private fun AppProfileInner(
|
||||
Column(modifier = modifier) {
|
||||
AppMenuBox(packageName) {
|
||||
ListItem(
|
||||
headlineContent = { Text(appLabel) },
|
||||
headlineContent = { Text(
|
||||
text = appLabel,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
supportingContent = { Text(packageName) },
|
||||
leadingContent = appIcon,
|
||||
)
|
||||
@@ -262,7 +278,11 @@ private fun TopBar(
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.profile))
|
||||
Text(
|
||||
text = stringResource(R.string.profile),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
@@ -281,7 +301,11 @@ private fun ProfileBox(
|
||||
onModeChange: (Mode) -> Unit,
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.profile)) },
|
||||
headlineContent = { Text(
|
||||
text = stringResource(R.string.profile),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
supportingContent = { Text(mode.text) },
|
||||
leadingContent = { Icon(Icons.Filled.AccountCircle, null) },
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -27,18 +28,23 @@ import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
@@ -66,6 +72,19 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
||||
var actionResult: Boolean
|
||||
var isActionRunning by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
val context = LocalContext.current
|
||||
// Read developer options from SharedPreferences
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||
|
||||
val view = LocalView.current
|
||||
DisposableEffect(isActionRunning) {
|
||||
view.keepScreenOn = isActionRunning
|
||||
onDispose {
|
||||
view.keepScreenOn = false
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = isActionRunning) {
|
||||
// Disable back button if action is running
|
||||
}
|
||||
@@ -100,7 +119,7 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
||||
topBar = {
|
||||
TopBar(
|
||||
isActionRunning = isActionRunning,
|
||||
onBack = {
|
||||
onBack = dropUnlessResumed {
|
||||
navigator.popBackStack()
|
||||
},
|
||||
onSave = {
|
||||
@@ -110,7 +129,7 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
||||
val date = format.format(Date())
|
||||
val file = File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
"KernelSU_module_action_log_${date}.log"
|
||||
"KernelSU_Next_module_action_log_${date}.log"
|
||||
)
|
||||
file.writeText(logContent.toString())
|
||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
||||
@@ -147,7 +166,7 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = text,
|
||||
text = if (developerOptionsEnabled) logContent.toString() else text,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||
@@ -160,7 +179,11 @@ fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String
|
||||
@Composable
|
||||
private fun TopBar(isActionRunning: Boolean, onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.action)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.action),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.rifsxd.ksunext.ui.screen
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.os.Parcelable
|
||||
@@ -33,21 +35,26 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
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
|
||||
@@ -58,7 +65,13 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import com.rifsxd.ksunext.R
|
||||
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
||||
import com.rifsxd.ksunext.ui.component.ConfirmResult
|
||||
import com.rifsxd.ksunext.ui.component.KeyEventBlocker
|
||||
import com.rifsxd.ksunext.ui.theme.ORANGE
|
||||
import com.rifsxd.ksunext.ui.theme.GREEN
|
||||
import com.rifsxd.ksunext.ui.theme.RED
|
||||
import com.rifsxd.ksunext.ui.util.FlashResult
|
||||
import com.rifsxd.ksunext.ui.util.LkmSelection
|
||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||
import com.rifsxd.ksunext.ui.util.flashModule
|
||||
@@ -77,41 +90,26 @@ enum class FlashingStatus {
|
||||
FAILED
|
||||
}
|
||||
|
||||
fun Context.findActivity(): Activity? = when (this) {
|
||||
is Activity -> this
|
||||
is android.content.ContextWrapper -> baseContext.findActivity()
|
||||
else -> null
|
||||
}
|
||||
|
||||
// Lets you flash modules sequentially when mutiple zipUris are selected
|
||||
fun flashModulesSequentially(
|
||||
uris: List<Uri>,
|
||||
onFinish: (Boolean, Int) -> Unit,
|
||||
onStdout: (String) -> Unit,
|
||||
onStderr: (String) -> Unit
|
||||
) {
|
||||
val iterator = uris.iterator()
|
||||
|
||||
// Start processing from the first module inside a coroutine
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
// 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)
|
||||
): FlashResult {
|
||||
for (uri in uris) {
|
||||
flashModule(uri, onStdout, onStderr).apply {
|
||||
if (code != 0) {
|
||||
return FlashResult(code, err, showReboot)
|
||||
}
|
||||
}
|
||||
|
||||
// Start the process
|
||||
processNext()
|
||||
}
|
||||
return FlashResult(0, "", true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,10 +119,14 @@ fun flashModulesSequentially(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Destination<RootGraph>
|
||||
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
fun FlashScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
flashIt: FlashIt,
|
||||
finishIntent: Boolean = false
|
||||
) {
|
||||
|
||||
var text by rememberSaveable { mutableStateOf("") }
|
||||
var tempText : String
|
||||
var tempText: String
|
||||
val logContent = rememberSaveable { StringBuilder() }
|
||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
@@ -136,25 +138,68 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
mutableStateOf(FlashingStatus.FLASHING)
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||
|
||||
val activity = context.findActivity()
|
||||
|
||||
val view = LocalView.current
|
||||
DisposableEffect(flashing) {
|
||||
view.keepScreenOn = flashing == FlashingStatus.FLASHING
|
||||
onDispose {
|
||||
view.keepScreenOn = false
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = flashing == FlashingStatus.FLASHING) {
|
||||
// Disable back button if flashing is running
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (text.isNotEmpty()) {
|
||||
return@LaunchedEffect
|
||||
BackHandler(enabled = flashing != FlashingStatus.FLASHING) {
|
||||
navigator.popBackStack()
|
||||
if (finishIntent) activity?.finish()
|
||||
}
|
||||
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
var confirmed by rememberSaveable { mutableStateOf(flashIt !is FlashIt.FlashModules) }
|
||||
var pendingFlashIt by rememberSaveable { mutableStateOf<FlashIt?>(null) }
|
||||
var hasFlashed by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(flashIt) {
|
||||
if (flashIt is FlashIt.FlashModules && !confirmed) {
|
||||
val uris = flashIt.uris
|
||||
val moduleNames =
|
||||
uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }
|
||||
.joinToString("")
|
||||
val confirmContent =
|
||||
context.getString(R.string.module_install_prompt_with_name, moduleNames)
|
||||
val confirmTitle = context.getString(R.string.module)
|
||||
val result = confirmDialog.awaitConfirm(
|
||||
title = confirmTitle,
|
||||
content = confirmContent,
|
||||
markdown = true
|
||||
)
|
||||
if (result == ConfirmResult.Confirmed) {
|
||||
confirmed = true
|
||||
pendingFlashIt = flashIt
|
||||
} else {
|
||||
// User cancelled, go back
|
||||
navigator.popBackStack()
|
||||
if (finishIntent) activity?.finish()
|
||||
}
|
||||
} else {
|
||||
confirmed = true
|
||||
pendingFlashIt = flashIt
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(confirmed, pendingFlashIt) {
|
||||
if (!confirmed || pendingFlashIt == null || text.isNotEmpty() || hasFlashed) return@LaunchedEffect
|
||||
hasFlashed = true
|
||||
withContext(Dispatchers.IO) {
|
||||
flashIt(flashIt, onFinish = { showReboot, code ->
|
||||
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 = {
|
||||
flashIt(pendingFlashIt!!, onStdout = {
|
||||
tempText = "$it\n"
|
||||
if (tempText.startsWith("[H[J")) { // clear command
|
||||
text = tempText.substring(6)
|
||||
@@ -164,7 +209,16 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
logContent.append(it).append("\n")
|
||||
}, onStderr = {
|
||||
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(
|
||||
flashing,
|
||||
onBack = {
|
||||
onBack = dropUnlessResumed {
|
||||
navigator.popBackStack()
|
||||
if (finishIntent) activity?.finish()
|
||||
},
|
||||
onSave = {
|
||||
scope.launch {
|
||||
@@ -181,7 +236,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
val date = format.format(Date())
|
||||
val file = File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
"KernelSU_install_log_${date}.log"
|
||||
"KernelSU_Next_install_log_${date}.log"
|
||||
)
|
||||
file.writeText(logContent.toString())
|
||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
||||
@@ -191,8 +246,8 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (showFloatAction) {
|
||||
// Reboot button (bottom left)
|
||||
if (flashIt is FlashIt.FlashModules && (flashing == FlashingStatus.SUCCESS)) {
|
||||
// Reboot button for modules flashing
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
@@ -205,6 +260,58 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
text = { Text(text = stringResource(R.string.reboot)) }
|
||||
)
|
||||
}
|
||||
|
||||
if (flashIt is FlashIt.FlashModules && (flashing == FlashingStatus.FAILED)) {
|
||||
// Close button for modules flashing
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(text = stringResource(R.string.close)) },
|
||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
||||
onClick = {
|
||||
navigator.popBackStack()
|
||||
if (finishIntent) activity?.finish()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (flashIt is FlashIt.FlashBoot && (flashing == FlashingStatus.SUCCESS || flashing == FlashingStatus.FAILED)) {
|
||||
val isLocalPatch = flashIt.boot != null && !flashIt.ota
|
||||
val isDirectOrOta = flashIt.boot == null || flashIt.ota
|
||||
|
||||
if (flashing == FlashingStatus.FAILED) {
|
||||
// Always show close on failure
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(text = stringResource(R.string.close)) },
|
||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
||||
onClick = {
|
||||
navigator.popBackStack()
|
||||
}
|
||||
)
|
||||
} else if (flashing == FlashingStatus.SUCCESS) {
|
||||
if (isLocalPatch) {
|
||||
// Local patching: show only Close
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(text = stringResource(R.string.close)) },
|
||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
||||
onClick = {
|
||||
navigator.popBackStack()
|
||||
}
|
||||
)
|
||||
} else if (isDirectOrOta) {
|
||||
// Direct install or OTA inactive slot: show only Reboot
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
reboot()
|
||||
}
|
||||
}
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.reboot)) },
|
||||
text = { Text(text = stringResource(R.string.reboot)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets.safeDrawing,
|
||||
snackbarHost = { SnackbarHost(hostState = snackBarHost) }
|
||||
@@ -224,7 +331,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = text,
|
||||
text = if (developerOptionsEnabled) logContent.toString() else text,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||
@@ -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
|
||||
sealed class FlashIt : Parcelable {
|
||||
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :
|
||||
FlashIt()
|
||||
|
||||
data class FlashModule(val uri: Uri) : FlashIt()
|
||||
|
||||
data class FlashModules(val uris: List<Uri>) : FlashIt()
|
||||
|
||||
data object FlashRestore : FlashIt()
|
||||
@@ -248,29 +366,26 @@ sealed class FlashIt : Parcelable {
|
||||
}
|
||||
|
||||
fun flashIt(
|
||||
flashIt: FlashIt, onFinish: (Boolean, Int) -> Unit,
|
||||
flashIt: FlashIt,
|
||||
onStdout: (String) -> Unit,
|
||||
onStderr: (String) -> Unit
|
||||
) {
|
||||
when (flashIt) {
|
||||
): FlashResult {
|
||||
return when (flashIt) {
|
||||
is FlashIt.FlashBoot -> installBoot(
|
||||
flashIt.boot,
|
||||
flashIt.lkm,
|
||||
flashIt.ota,
|
||||
onFinish,
|
||||
onStdout,
|
||||
onStderr
|
||||
)
|
||||
|
||||
is FlashIt.FlashModule -> flashModule(flashIt.uri, onFinish, onStdout, onStderr)
|
||||
|
||||
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.FAILED -> R.string.flash_failed
|
||||
}
|
||||
)
|
||||
),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
color = when (status) {
|
||||
FlashingStatus.FLASHING -> ORANGE
|
||||
FlashingStatus.SUCCESS -> GREEN
|
||||
FlashingStatus.FAILED -> RED
|
||||
}
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
|
||||
@@ -6,9 +6,15 @@ import android.os.PowerManager
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.system.Os
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -28,10 +34,17 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.toUpperCase
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||
import com.dergoogler.mmrl.ui.component.LabelItemDefaults
|
||||
import com.dergoogler.mmrl.ui.component.text.TextRow
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
||||
@@ -55,6 +68,10 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
val context = LocalContext.current
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
@@ -83,6 +100,19 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
StatusCard(kernelVersion, ksuVersion, lkmMode) {
|
||||
navigator.navigate(InstallScreenDestination)
|
||||
}
|
||||
|
||||
if (ksuVersion != null && rootAvailable()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min),
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp)
|
||||
) {
|
||||
Box(modifier = Modifier.weight(1f)) { SuperuserCard() }
|
||||
Box(modifier = Modifier.weight(1f)) { ModuleCard() }
|
||||
}
|
||||
}
|
||||
|
||||
if (isManager && Natives.requireNewKernel()) {
|
||||
WarningCard(
|
||||
stringResource(id = R.string.require_kernel_version).format(
|
||||
@@ -97,12 +127,12 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
val checkUpdate =
|
||||
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("check_update", true)
|
||||
.getBoolean("check_update", false)
|
||||
if (checkUpdate) {
|
||||
UpdateCard()
|
||||
}
|
||||
//NextCard()
|
||||
InfoCard()
|
||||
InfoCard(autoExpand = developerOptionsEnabled)
|
||||
IssueReportCard()
|
||||
//EXperimentalCard()
|
||||
Spacer(Modifier)
|
||||
@@ -110,6 +140,78 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SuperuserCard() {
|
||||
val count = getSuperuserCount()
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (count <= 1) {
|
||||
stringResource(R.string.home_superuser_count_singular)
|
||||
} else {
|
||||
stringResource(R.string.home_superuser_count_plural)
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
Text(
|
||||
text = count.toString(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ModuleCard() {
|
||||
val count = getModuleCount()
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (count <= 1) {
|
||||
stringResource(R.string.home_module_count_singular)
|
||||
} else {
|
||||
stringResource(R.string.home_module_count_plural)
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
Text(
|
||||
text = count.toString(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpdateCard() {
|
||||
val context = LocalContext.current
|
||||
@@ -162,6 +264,19 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getSeasonalIcon(): ImageVector {
|
||||
val month = Calendar.getInstance().get(Calendar.MONTH) // 0-11 for January-December
|
||||
return when (month) {
|
||||
Calendar.DECEMBER, Calendar.JANUARY, Calendar.FEBRUARY -> Icons.Filled.AcUnit // Winter
|
||||
Calendar.MARCH, Calendar.APRIL, Calendar.MAY -> Icons.Filled.Spa // Spring
|
||||
Calendar.JUNE, Calendar.JULY, Calendar.AUGUST -> Icons.Filled.WbSunny // Summer
|
||||
Calendar.SEPTEMBER, Calendar.OCTOBER, Calendar.NOVEMBER -> Icons.Filled.Forest // Fall
|
||||
else -> Icons.Filled.Whatshot // Fallback icon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
@@ -170,15 +285,55 @@ private fun TopBar(
|
||||
onInstallClick: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
var isSpinning by remember { mutableStateOf(false) }
|
||||
val rotation by animateFloatAsState(
|
||||
targetValue = if (isSpinning) 360f else 0f,
|
||||
animationSpec = tween(durationMillis = 800),
|
||||
finishedListener = {
|
||||
isSpinning = false
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
isSpinning = true
|
||||
}
|
||||
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.app_name)) },
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
if (!isSpinning) isSpinning = true
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = getSeasonalIcon(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.graphicsLayer {
|
||||
rotationZ = rotation
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (kernelVersion.isGKI()) {
|
||||
IconButton(onClick = onInstallClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Archive,
|
||||
contentDescription = stringResource(id = R.string.install)
|
||||
)
|
||||
if (ksuVersion != null) {
|
||||
if (kernelVersion.isGKI()) {
|
||||
IconButton(onClick = onInstallClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Archive,
|
||||
contentDescription = stringResource(id = R.string.install)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +343,7 @@ private fun TopBar(
|
||||
showDropdown = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Refresh,
|
||||
imageVector = Icons.Filled.PowerSettingsNew,
|
||||
contentDescription = stringResource(id = R.string.reboot)
|
||||
)
|
||||
|
||||
@@ -197,7 +352,8 @@ private fun TopBar(
|
||||
}) {
|
||||
RebootDropdownItem(id = R.string.reboot)
|
||||
|
||||
val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
val pm =
|
||||
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||
@@ -215,93 +371,131 @@ private fun TopBar(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getSeasonalIcon(): ImageVector {
|
||||
val month = Calendar.getInstance().get(Calendar.MONTH) // 0-11 for January-December
|
||||
return when (month) {
|
||||
Calendar.DECEMBER, Calendar.JANUARY, Calendar.FEBRUARY -> Icons.Filled.AcUnit // Winter
|
||||
Calendar.MARCH, Calendar.APRIL, Calendar.MAY -> Icons.Filled.Spa // Spring
|
||||
Calendar.JUNE, Calendar.JULY, Calendar.AUGUST -> Icons.Filled.WbSunny // Summer
|
||||
Calendar.SEPTEMBER, Calendar.OCTOBER, Calendar.NOVEMBER -> Icons.Filled.Forest // Fall
|
||||
else -> Icons.Filled.Whatshot // Fallback icon
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusCard(
|
||||
kernelVersion: KernelVersion,
|
||||
ksuVersion: Int?,
|
||||
lkmMode: Boolean?,
|
||||
moduleUpdateCount: Int = 0,
|
||||
onClickInstall: () -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var tapCount by remember { mutableStateOf(0) }
|
||||
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
||||
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
|
||||
if (ksuVersion != null) MaterialTheme.colorScheme.primaryContainer
|
||||
else MaterialTheme.colorScheme.errorContainer
|
||||
})
|
||||
) {
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (kernelVersion.isGKI()) {
|
||||
onClickInstall()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
tapCount++
|
||||
if (tapCount == 5) {
|
||||
Toast.makeText(context, "What are you doing? 🤔", Toast.LENGTH_SHORT).show()
|
||||
} else if (tapCount == 10) {
|
||||
Toast.makeText(context, "Never gonna give you up! 💜", Toast.LENGTH_SHORT).show()
|
||||
val url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
val intent = android.content.Intent(android.content.Intent.ACTION_VIEW, android.net.Uri.parse(url))
|
||||
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (ksuVersion != null) {
|
||||
context.startActivity(intent)
|
||||
} else if (kernelVersion.isGKI()) {
|
||||
onClickInstall()
|
||||
} else {
|
||||
Toast.makeText(context, "Something weird happened... 🤔", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else if (ksuVersion == null && kernelVersion.isGKI()) {
|
||||
onClickInstall()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
when {
|
||||
ksuVersion != null -> {
|
||||
val safeMode = when {
|
||||
Natives.isSafeMode -> " [${stringResource(id = R.string.safe_mode)}]"
|
||||
else -> ""
|
||||
val workingMode = when {
|
||||
lkmMode == true -> "LKM"
|
||||
lkmMode == false || kernelVersion.isGKI() -> "GKI2"
|
||||
lkmMode == null && kernelVersion.isULegacy() -> "U-LEGACY"
|
||||
lkmMode == null && kernelVersion.isLegacy() -> "LEGACY"
|
||||
lkmMode == null && kernelVersion.isGKI1() -> "GKI1"
|
||||
else -> "NON-STANDARD"
|
||||
}
|
||||
|
||||
val workingMode = when (lkmMode) {
|
||||
null -> " <LTS>"
|
||||
true -> " <LKM>"
|
||||
else -> " <GKI>"
|
||||
}
|
||||
|
||||
val workingText =
|
||||
"${stringResource(id = R.string.home_working)}$workingMode$safeMode"
|
||||
|
||||
Icon(
|
||||
getSeasonalIcon(), // Use dynamic seasonal icon
|
||||
imageVector = Icons.Filled.CheckCircle,
|
||||
contentDescription = stringResource(R.string.home_working)
|
||||
)
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = workingText,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.home_superuser_count, getSuperuserCount()
|
||||
), style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_module_count, getModuleCount()),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
val suSFS = getSuSFS()
|
||||
if (suSFS == "Supported") {
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 20.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
val labelStyle = LabelItemDefaults.style
|
||||
TextRow(
|
||||
trailingContent = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
LabelItem(
|
||||
icon = if (Natives.isSafeMode) {
|
||||
{
|
||||
Icon(
|
||||
tint = labelStyle.contentColor,
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = workingMode,
|
||||
style = labelStyle.textStyle.copy(color = labelStyle.contentColor),
|
||||
)
|
||||
}
|
||||
)
|
||||
if (isSuCompatDisabled()) {
|
||||
LabelItem(
|
||||
icon = {
|
||||
Icon(
|
||||
tint = labelStyle.contentColor,
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.sucompat_disabled),
|
||||
style = labelStyle.textStyle.copy(
|
||||
color = labelStyle.contentColor,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.home_susfs, getSuSFS()),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
text = stringResource(id = R.string.home_working),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
kernelVersion.isGKI() -> {
|
||||
Icon(Icons.Filled.AutoFixHigh, stringResource(R.string.home_not_installed))
|
||||
Icon(Icons.Filled.NewReleases, stringResource(R.string.home_not_installed))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.home_not_installed),
|
||||
@@ -316,7 +510,7 @@ private fun StatusCard(
|
||||
}
|
||||
|
||||
else -> {
|
||||
Icon(Icons.Filled.Dangerous, stringResource(R.string.home_failure))
|
||||
Icon(Icons.Filled.Cancel, stringResource(R.string.home_failure))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.home_failure),
|
||||
@@ -357,31 +551,30 @@ fun WarningCard(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoCard() {
|
||||
private fun InfoCard(autoExpand: Boolean = false) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
var useOverlayFs by rememberSaveable {
|
||||
mutableStateOf(prefs.getBoolean("use_overlay_fs", false))
|
||||
}
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
useOverlayFs = prefs.getBoolean("use_overlay_fs", false)
|
||||
}
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
||||
|
||||
LaunchedEffect(autoExpand) {
|
||||
if (autoExpand) {
|
||||
expanded = true
|
||||
}
|
||||
}
|
||||
|
||||
ElevatedCard {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)
|
||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 24.dp)
|
||||
) {
|
||||
val contents = StringBuilder()
|
||||
val uname = Os.uname()
|
||||
|
||||
@Composable
|
||||
fun InfoCardItem(label: String, content: String, icon: Any? = null) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
@@ -402,68 +595,137 @@ private fun InfoCard() {
|
||||
Column {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Text(
|
||||
text = content,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_kernel),
|
||||
content = uname.release,
|
||||
icon = painterResource(R.drawable.ic_linux),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_android),
|
||||
content = "${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})",
|
||||
icon = Icons.Filled.Android,
|
||||
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val managerVersion = getManagerVersion(context)
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_manager_version),
|
||||
content = "${managerVersion.first} (${managerVersion.second})",
|
||||
icon = painterResource(R.drawable.ic_ksu_next),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_selinux_status),
|
||||
content = getSELinuxStatus(),
|
||||
icon = Icons.Filled.Security,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_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 ""
|
||||
Column {
|
||||
val managerVersion = getManagerVersion(context)
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_susfs_version),
|
||||
content = "${getSuSFSVersion()} (${getSuSFSVariant()}) $susSUMode",
|
||||
icon = painterResource(R.drawable.ic_sus),
|
||||
label = stringResource(R.string.home_manager_version),
|
||||
content = if (
|
||||
developerOptionsEnabled &&
|
||||
Natives.version >= Natives.MINIMAL_SUPPORTED_MANAGER_UID
|
||||
) {
|
||||
"${managerVersion.first} (${managerVersion.second}) | UID: ${Natives.getManagerUid()}"
|
||||
} else {
|
||||
"${managerVersion.first} (${managerVersion.second})"
|
||||
},
|
||||
icon = painterResource(R.drawable.ic_ksu_next),
|
||||
)
|
||||
|
||||
if (ksuVersion != null &&
|
||||
Natives.version >= Natives.MINIMAL_SUPPORTED_HOOK_MODE) {
|
||||
|
||||
val hookMode =
|
||||
Natives.getHookMode()
|
||||
.takeUnless { it.isNullOrBlank() }
|
||||
?: stringResource(R.string.unavailable)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.hook_mode),
|
||||
content = hookMode,
|
||||
icon = Icons.Filled.Phishing,
|
||||
)
|
||||
}
|
||||
|
||||
if (ksuVersion != null) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_mount_system),
|
||||
content = currentMountSystem().ifEmpty { stringResource(R.string.unavailable) },
|
||||
icon = Icons.Filled.SettingsSuggest,
|
||||
)
|
||||
|
||||
|
||||
val suSFS = getSuSFS()
|
||||
if (suSFS == "Supported") {
|
||||
val isSUS_SU = hasSuSFs_SUS_SU() == "Supported"
|
||||
val susSUMode = if (isSUS_SU) {
|
||||
val mode = susfsSUS_SU_Mode()
|
||||
val modeString =
|
||||
if (mode == "2") stringResource(R.string.enabled) else stringResource(R.string.disabled)
|
||||
"| SuS SU: $modeString"
|
||||
} else ""
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_susfs_version),
|
||||
content = "${stringResource(R.string.susfs_supported)} | ${getSuSFSVersion()} (${getSuSFSVariant()}) $susSUMode",
|
||||
icon = painterResource(R.drawable.ic_sus),
|
||||
)
|
||||
}
|
||||
|
||||
if (Natives.isZygiskEnabled()) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.zygisk_status),
|
||||
content = stringResource(R.string.enabled),
|
||||
icon = Icons.Filled.Vaccines
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!expanded) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { expanded = true },
|
||||
modifier = Modifier.size(36.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.KeyboardArrowDown,
|
||||
contentDescription = "Show more"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
val uname = Os.uname()
|
||||
Column {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_kernel),
|
||||
content = "${uname.release} (${uname.machine})",
|
||||
icon = painterResource(R.drawable.ic_linux),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_android),
|
||||
content = "${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})",
|
||||
icon = Icons.Filled.Android,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_abi),
|
||||
content = Build.SUPPORTED_ABIS.joinToString(", "),
|
||||
icon = Icons.Filled.Memory,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_selinux_status),
|
||||
content = getSELinuxStatus(),
|
||||
icon = Icons.Filled.Security,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -476,12 +738,13 @@ fun NextCard() {
|
||||
|
||||
ElevatedCard {
|
||||
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
uriHandler.openUri(url)
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
uriHandler.openUri(url)
|
||||
}
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.home_next_kernelsu),
|
||||
@@ -505,13 +768,15 @@ fun EXperimentalCard() {
|
||||
|
||||
ElevatedCard {
|
||||
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
/*.clickable {
|
||||
uriHandler.openUri(url)
|
||||
}
|
||||
*/
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
/*.clickable {
|
||||
uriHandler.openUri(url)
|
||||
}
|
||||
*/
|
||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.home_experimental_kernelsu),
|
||||
@@ -558,17 +823,18 @@ fun IssueReportCard() {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = stringResource(R.string.issue_report_title),
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.issue_report_body),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.issue_report_body_2),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.FileUpload
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -30,6 +31,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
@@ -39,7 +41,9 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -47,6 +51,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.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
|
||||
@@ -76,6 +81,36 @@ import com.rifsxd.ksunext.ui.util.rootAvailable
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
var showLkmWarning by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
if (showLkmWarning) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
showLkmWarning = false
|
||||
navigator.popBackStack()
|
||||
},
|
||||
title = { Text(
|
||||
text = stringResource(R.string.warning),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
text = { Text(stringResource(R.string.lkm_warning_message)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showLkmWarning = false }) {
|
||||
Text(stringResource(R.string.proceed))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = {
|
||||
showLkmWarning = false
|
||||
navigator.popBackStack()
|
||||
}) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var installMethod by remember {
|
||||
mutableStateOf<InstallMethod?>(null)
|
||||
}
|
||||
@@ -133,7 +168,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
onBack = { navigator.popBackStack() },
|
||||
onBack = dropUnlessResumed { navigator.popBackStack() },
|
||||
onLkmUpload = onLkmUpload,
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
@@ -204,7 +239,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||
val rootAvailable = rootAvailable()
|
||||
val isAbDevice = isAbDevice()
|
||||
val selectFileTip = stringResource(
|
||||
id = R.string.select_file_tip, if (isInitBoot()) "init_boot" else "boot"
|
||||
id = R.string.select_file_tip, if (isInitBoot()) "init_boot/vendor_boot" else "boot"
|
||||
)
|
||||
val radioOptions =
|
||||
mutableListOf<InstallMethod>(InstallMethod.SelectFile(summary = selectFileTip))
|
||||
@@ -340,7 +375,11 @@ private fun TopBar(
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.install)) }, navigationIcon = {
|
||||
title = { Text(
|
||||
text = stringResource(R.string.install),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) }, navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -34,6 +35,7 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -42,6 +44,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
@@ -63,6 +66,9 @@ import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.BackupRestoreScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.CustomizationScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.DeveloperScreenDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -82,6 +88,8 @@ import com.rifsxd.ksunext.ui.component.rememberLoadingDialog
|
||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
||||
import com.rifsxd.ksunext.ui.util.getBugreportFile
|
||||
import com.rifsxd.ksunext.ui.util.*
|
||||
import com.rifsxd.ksunext.ui.util.isGlobalNamespaceEnabled
|
||||
import com.rifsxd.ksunext.ui.util.setGlobalNamespaceEnabled
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@@ -95,6 +103,8 @@ import java.time.format.DateTimeFormatter
|
||||
fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
var isGlobalNamespaceEnabled by rememberSaveable { mutableStateOf(false) }
|
||||
isGlobalNamespaceEnabled = isGlobalNamespaceEnabled()
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
@@ -113,8 +123,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
val loadingDialog = rememberLoadingDialog()
|
||||
val shrinkDialog = rememberConfirmDialog()
|
||||
val restoreDialog = rememberConfirmDialog()
|
||||
val backupDialog = rememberConfirmDialog()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -146,7 +154,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
if (ksuVersion != null) {
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
||||
headlineContent = { Text(profileTemplate) },
|
||||
headlineContent = { Text(
|
||||
text = profileTemplate,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||
@@ -159,10 +171,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
if (ksuVersion != null) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.RemoveModerator,
|
||||
icon = Icons.Filled.FolderDelete,
|
||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||
checked = umountChecked
|
||||
|
||||
) {
|
||||
if (Natives.setDefaultUmountModules(it)) {
|
||||
umountChecked = it
|
||||
@@ -170,12 +183,48 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Engineering,
|
||||
title = stringResource(id = R.string.settings_global_namespace_mode),
|
||||
summary = stringResource(id = R.string.settings_global_namespace_mode_summary),
|
||||
checked = isGlobalNamespaceEnabled,
|
||||
onCheckedChange = {
|
||||
setGlobalNamespaceEnabled(
|
||||
if (isGlobalNamespaceEnabled) {
|
||||
"0"
|
||||
} else {
|
||||
"1"
|
||||
}
|
||||
)
|
||||
isGlobalNamespaceEnabled = it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
val suSFS = getSuSFS()
|
||||
val isSUS_SU = getSuSFSFeatures()
|
||||
val isSUS_SU = hasSuSFs_SUS_SU() == "Supported"
|
||||
if (suSFS == "Supported") {
|
||||
if (isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") {
|
||||
if (isSUS_SU) {
|
||||
var isEnabled by rememberSaveable {
|
||||
mutableStateOf(susfsSUS_SU_Mode() == "2")
|
||||
}
|
||||
@@ -201,66 +250,47 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
val hasShownWarning = rememberSaveable { mutableStateOf(prefs.getBoolean("has_shown_warning", false)) }
|
||||
|
||||
var useOverlayFs by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("use_overlay_fs", false)
|
||||
)
|
||||
mutableStateOf(readMountSystemFile())
|
||||
}
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
LaunchedEffect(Unit) {
|
||||
useOverlayFs = readMountSystemFile()
|
||||
}
|
||||
|
||||
var showRebootDialog by remember { mutableStateOf(false) }
|
||||
|
||||
var showWarningDialog by remember { mutableStateOf(false) }
|
||||
val isOverlayAvailable = overlayFsAvailable()
|
||||
|
||||
if (ksuVersion != null) {
|
||||
if (ksuVersion != null && isOverlayAvailable) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Build,
|
||||
title = stringResource(id = R.string.use_overlay_fs),
|
||||
summary = stringResource(id = R.string.use_overlay_fs_summary),
|
||||
checked = useOverlayFs
|
||||
) {
|
||||
if (!hasShownWarning.value) {
|
||||
showWarningDialog = true
|
||||
prefs.edit().putBoolean("use_overlay_fs", it).apply()
|
||||
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) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRebootDialog = false },
|
||||
title = { Text(stringResource(R.string.reboot_required)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.reboot_required),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
text = { Text(stringResource(R.string.reboot_message)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
@@ -281,7 +311,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
var checkUpdate by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("check_update", true)
|
||||
prefs.getBoolean("check_update", false)
|
||||
)
|
||||
}
|
||||
SwitchItem(
|
||||
@@ -294,19 +324,97 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
checkUpdate = it
|
||||
}
|
||||
|
||||
var enableWebDebugging by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("enable_web_debugging", false)
|
||||
if (isOverlayAvailable && 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(
|
||||
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,
|
||||
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
|
||||
|
||||
val customization = stringResource(id = R.string.customization)
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Palette,
|
||||
customization
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(
|
||||
text = customization,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(CustomizationScreenDestination)
|
||||
}
|
||||
)
|
||||
|
||||
if (ksuVersion != null) {
|
||||
val backupRestore = stringResource(id = R.string.backup_restore)
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Backup,
|
||||
backupRestore
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(
|
||||
text = backupRestore,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(BackupRestoreScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val developer = stringResource(id = R.string.developer)
|
||||
if (ksuVersion != null) {
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.DeveloperBoard,
|
||||
developer
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(
|
||||
text = developer,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(DeveloperScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
||||
if (lkmMode) {
|
||||
UninstallItem(navigator) {
|
||||
loadingDialog.withLoading(it)
|
||||
}
|
||||
}
|
||||
|
||||
var showBottomsheet by remember { mutableStateOf(false) }
|
||||
@@ -318,7 +426,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
stringResource(id = R.string.export_log)
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(stringResource(id = R.string.export_log)) },
|
||||
headlineContent = { Text(
|
||||
text = stringResource(id = R.string.export_log),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
showBottomsheet = true
|
||||
}
|
||||
@@ -340,7 +452,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
.clickable {
|
||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
||||
val current = LocalDateTime.now().format(formatter)
|
||||
exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz")
|
||||
exportBugreportLauncher.launch("KernelSU_Next_bugreport_${current}.tar.gz")
|
||||
showBottomsheet = false
|
||||
}
|
||||
) {
|
||||
@@ -418,87 +530,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)
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
@@ -507,7 +538,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
about
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(about) },
|
||||
headlineContent = { Text(
|
||||
text = about,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
aboutDialog.show()
|
||||
}
|
||||
@@ -519,7 +554,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
@Composable
|
||||
fun UninstallItem(
|
||||
navigator: DestinationsNavigator,
|
||||
withLoading: suspend (suspend () -> Unit) -> Unit
|
||||
withLoading: suspend (suspend () -> Unit) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -557,7 +592,11 @@ fun UninstallItem(
|
||||
uninstall
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(uninstall) },
|
||||
headlineContent = { Text(
|
||||
text = uninstall,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
) },
|
||||
modifier = Modifier.clickable {
|
||||
uninstallDialog.show()
|
||||
}
|
||||
@@ -621,10 +660,14 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.settings)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.settings),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) },
|
||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
@@ -634,4 +677,4 @@ private fun TopBar(
|
||||
@Composable
|
||||
private fun SettingsPreview() {
|
||||
SettingScreen(EmptyDestinationsNavigator)
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -21,9 +22,12 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||
import com.dergoogler.mmrl.ui.component.LabelItemDefaults
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
|
||||
@@ -43,30 +47,21 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LaunchedEffect(key1 = navigator) {
|
||||
LaunchedEffect(navigator) {
|
||||
viewModel.search = ""
|
||||
if (viewModel.appList.isEmpty()) {
|
||||
viewModel.fetchAppList()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(viewModel.search) {
|
||||
if (viewModel.search.isEmpty()) {
|
||||
listState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.refreshOnReturn) {
|
||||
viewModel.fetchAppList()
|
||||
viewModel.refreshOnReturn = false
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SearchAppBar(
|
||||
title = { Text(stringResource(R.string.superuser)) },
|
||||
title = { Text(
|
||||
text = stringResource(R.string.superuser),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
) },
|
||||
searchText = viewModel.search,
|
||||
onSearchTextChange = { viewModel.search = it },
|
||||
onClearClick = { viewModel.search = "" },
|
||||
@@ -101,7 +96,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
}, onClick = {
|
||||
viewModel.showSystemApps = !viewModel.showSystemApps
|
||||
viewModel.updateShowSystemApps(!viewModel.showSystemApps)
|
||||
showDropdown = false
|
||||
})
|
||||
}
|
||||
@@ -127,7 +122,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
) {
|
||||
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
||||
AppItem(app) {
|
||||
viewModel.refreshOnReturn = true
|
||||
navigator.navigate(AppProfileScreenDestination(app))
|
||||
}
|
||||
}
|
||||
@@ -144,20 +138,54 @@ private fun AppItem(
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable(onClick = onClickListener),
|
||||
headlineContent = { Text(app.label) },
|
||||
headlineContent = { Text(
|
||||
text = app.label,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
) },
|
||||
supportingContent = {
|
||||
Column {
|
||||
Text(app.packageName)
|
||||
FlowRow {
|
||||
Text(
|
||||
text = app.packageName,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
if (app.allowSu) {
|
||||
LabelText(label = "ROOT")
|
||||
LabelItem(
|
||||
text = "ROOT",
|
||||
)
|
||||
} else {
|
||||
if (Natives.uidShouldUmount(app.uid)) {
|
||||
LabelText(label = "UMOUNT")
|
||||
LabelItem(
|
||||
text = "UMOUNT",
|
||||
style = LabelItemDefaults.style.copy(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (app.hasCustomProfile) {
|
||||
LabelText(label = "CUSTOM")
|
||||
LabelItem(
|
||||
text = "CUSTOM",
|
||||
style = LabelItemDefaults.style.copy(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
)
|
||||
)
|
||||
} else if (!app.allowSu && !Natives.uidShouldUmount(app.uid)) {
|
||||
LabelItem(
|
||||
text = "DEFAULT",
|
||||
style = LabelItemDefaults.style.copy(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
@@ -42,6 +45,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
@@ -49,7 +53,9 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
|
||||
@@ -61,6 +67,15 @@ import kotlinx.coroutines.launch
|
||||
import com.rifsxd.ksunext.R
|
||||
import com.rifsxd.ksunext.ui.viewmodel.TemplateViewModel
|
||||
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.core.tween
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/10/20.
|
||||
@@ -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(
|
||||
topBar = {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
@@ -100,7 +139,7 @@ fun AppProfileTemplateScreen(
|
||||
}
|
||||
}
|
||||
TopBar(
|
||||
onBack = { navigator.popBackStack() },
|
||||
onBack = dropUnlessResumed { navigator.popBackStack() },
|
||||
onSync = {
|
||||
scope.launch { viewModel.fetchTemplates(true) }
|
||||
},
|
||||
@@ -136,18 +175,30 @@ fun AppProfileTemplateScreen(
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
navigator.navigate(
|
||||
TemplateEditorScreenDestination(
|
||||
TemplateViewModel.TemplateInfo(),
|
||||
false
|
||||
AnimatedVisibility(
|
||||
visible = showFab,
|
||||
enter = scaleIn(
|
||||
animationSpec = tween(200),
|
||||
initialScale = 0.8f
|
||||
) + fadeIn(animationSpec = tween(400)),
|
||||
exit = scaleOut(
|
||||
animationSpec = tween(200),
|
||||
targetScale = 0.8f
|
||||
) + fadeOut(animationSpec = tween(400))
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
navigator.navigate(
|
||||
TemplateEditorScreenDestination(
|
||||
TemplateViewModel.TemplateInfo(),
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Add, null) },
|
||||
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
||||
)
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Add, null) },
|
||||
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
||||
)
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
||||
) { innerPadding ->
|
||||
@@ -159,11 +210,12 @@ fun AppProfileTemplateScreen(
|
||||
}
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
contentPadding = remember {
|
||||
PaddingValues(bottom = 16.dp + 56.dp + 16.dp /* Scaffold Fab Spacing + Fab container height */)
|
||||
PaddingValues(bottom = 16.dp /* Scaffold Fab Spacing + Fab container height */)
|
||||
}
|
||||
) {
|
||||
items(viewModel.templateList, key = { it.id }) { app ->
|
||||
@@ -185,7 +237,11 @@ private fun TemplateItem(
|
||||
.clickable {
|
||||
navigator.navigate(TemplateEditorScreenDestination(template, !template.local))
|
||||
},
|
||||
headlineContent = { Text(template.name) },
|
||||
headlineContent = { Text(
|
||||
text = template.name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
) },
|
||||
supportingContent = {
|
||||
Column {
|
||||
Text(
|
||||
@@ -194,14 +250,19 @@ private fun TemplateItem(
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
)
|
||||
Text(template.description)
|
||||
FlowRow {
|
||||
LabelText(label = "UID: ${template.uid}")
|
||||
LabelText(label = "GID: ${template.gid}")
|
||||
LabelText(label = template.context)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
LabelItem(text = "UID: ${template.uid}")
|
||||
LabelItem(text = "GID: ${template.gid}")
|
||||
LabelItem(text = template.context)
|
||||
if (template.local) {
|
||||
LabelText(label = "local")
|
||||
LabelItem(text = "local")
|
||||
} else {
|
||||
LabelText(label = "remote")
|
||||
LabelItem(text = "remote")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +281,11 @@ private fun TopBar(
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.settings_profile_template))
|
||||
Text(
|
||||
text = stringResource(R.string.settings_profile_template),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
|
||||
@@ -35,6 +35,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -44,6 +45,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
||||
@@ -105,7 +107,7 @@ fun TemplateEditorScreen(
|
||||
},
|
||||
readOnly = readOnly,
|
||||
summary = titleSummary,
|
||||
onBack = { navigator.navigateBack(result = !readOnly) },
|
||||
onBack = dropUnlessResumed { navigator.navigateBack(result = !readOnly) },
|
||||
onDelete = {
|
||||
if (deleteAppProfileTemplate(template.id)) {
|
||||
navigator.navigateBack(result = true)
|
||||
@@ -261,7 +263,11 @@ private fun TopBar(
|
||||
TopAppBar(
|
||||
title = {
|
||||
Column {
|
||||
Text(title)
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Black,
|
||||
)
|
||||
if (summary.isNotBlank()) {
|
||||
Text(
|
||||
text = summary,
|
||||
|
||||
@@ -2,9 +2,19 @@ package com.rifsxd.ksunext.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val YELLOW = Color(0xFFeed502)
|
||||
val YELLOW_LIGHT = Color(0xFFffff52)
|
||||
val SECONDARY_LIGHT = Color(0xffa9817f)
|
||||
val PRIMARY = Color(0xFF8AADF4) // Catppuccin Blue
|
||||
val PRIMARY_LIGHT = Color(0xFFB7BDF8) // Catppuccin Lavender
|
||||
val SECONDARY_LIGHT = Color(0xFFA6DA95) // Catppuccin Green
|
||||
|
||||
val YELLOW_DARK = Color(0xFFb7a400)
|
||||
val SECONDARY_DARK = Color(0xFF4c2b2b)
|
||||
val PRIMARY_DARK = Color(0xFF7DC4E4) // Catppuccin Sky
|
||||
val SECONDARY_DARK = Color(0xFFF5BDE6) // Catppuccin Pink
|
||||
|
||||
val AMOLED_BLACK = Color(0xFF000000) // Pure black for AMOLED
|
||||
|
||||
val DARK_PURPLE = Color(0xFF6E6CB6) // Catppuccin Mauve (dark purple)
|
||||
val DARK_GREY = Color(0xFF363A4F) // Catppuccin Surface (dark grey)
|
||||
|
||||
val GREEN = Color(0xFF4CAF50) // Green
|
||||
val RED = Color(0xFFF44336) // Red
|
||||
val YELLOW = Color(0xFFFFEB3B) // Yellow
|
||||
val ORANGE = Color(0xFFFF9800) // Orange
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.rifsxd.ksunext.ui.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
@@ -8,39 +11,115 @@ import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = YELLOW,
|
||||
secondary = YELLOW_DARK,
|
||||
primary = PRIMARY,
|
||||
secondary = PRIMARY_DARK,
|
||||
tertiary = SECONDARY_DARK
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = YELLOW,
|
||||
secondary = YELLOW_LIGHT,
|
||||
primary = PRIMARY,
|
||||
secondary = PRIMARY_LIGHT,
|
||||
tertiary = SECONDARY_LIGHT
|
||||
)
|
||||
|
||||
fun Color.blend(other: Color, ratio: Float): Color {
|
||||
val inverse = 1f - ratio
|
||||
return Color(
|
||||
red = red * inverse + other.red * ratio,
|
||||
green = green * inverse + other.green * ratio,
|
||||
blue = blue * inverse + other.blue * ratio,
|
||||
alpha = alpha
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun KernelSUTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
amoledMode: Boolean = false,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
amoledMode && darkTheme && dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
val dynamicScheme = dynamicDarkColorScheme(context)
|
||||
dynamicScheme.copy(
|
||||
background = AMOLED_BLACK,
|
||||
surface = AMOLED_BLACK,
|
||||
surfaceVariant = dynamicScheme.surfaceVariant.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainer = dynamicScheme.surfaceContainer.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerLow = dynamicScheme.surfaceContainerLow.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerLowest = dynamicScheme.surfaceContainerLowest.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerHigh = dynamicScheme.surfaceContainerHigh.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerHighest = dynamicScheme.surfaceContainerHighest.blend(AMOLED_BLACK, 0.6f),
|
||||
primaryContainer = dynamicScheme.primaryContainer.blend(AMOLED_BLACK, 0.6f),
|
||||
secondaryContainer = dynamicScheme.secondaryContainer.blend(AMOLED_BLACK, 0.6f),
|
||||
tertiaryContainer = dynamicScheme.tertiaryContainer.blend(AMOLED_BLACK, 0.6f)
|
||||
)
|
||||
}
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
amoledMode && darkTheme -> {
|
||||
DarkColorScheme.copy(
|
||||
background = AMOLED_BLACK,
|
||||
surface = AMOLED_BLACK,
|
||||
surfaceVariant = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainer = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainerLow = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainerLowest = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainerHigh = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
surfaceContainerHighest = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
||||
)
|
||||
}
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
SystemBarStyle(
|
||||
darkMode = darkTheme
|
||||
)
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SystemBarStyle(
|
||||
darkMode: Boolean,
|
||||
statusBarScrim: Color = Color.Transparent,
|
||||
navigationBarScrim: Color = Color.Transparent,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val activity = context as ComponentActivity
|
||||
|
||||
SideEffect {
|
||||
activity.enableEdgeToEdge(
|
||||
statusBarStyle = SystemBarStyle.auto(
|
||||
statusBarScrim.toArgb(),
|
||||
statusBarScrim.toArgb(),
|
||||
) { darkMode },
|
||||
navigationBarStyle = when {
|
||||
darkMode -> SystemBarStyle.dark(
|
||||
navigationBarScrim.toArgb()
|
||||
)
|
||||
|
||||
else -> SystemBarStyle.light(
|
||||
navigationBarScrim.toArgb(),
|
||||
navigationBarScrim.toArgb(),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import android.os.Environment
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.ui.util.module.LatestVersionInfo
|
||||
|
||||
/**
|
||||
@@ -63,11 +64,11 @@ fun download(
|
||||
|
||||
fun checkNewVersion(): LatestVersionInfo {
|
||||
// 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
|
||||
val defaultValue = LatestVersionInfo()
|
||||
runCatching {
|
||||
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
||||
ksuApp.okhttpClient.newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
||||
.use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
return defaultValue
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.io.File
|
||||
* @date 2023/1/1.
|
||||
*/
|
||||
private const val TAG = "KsuCli"
|
||||
private const val BUSYBOX = "/data/adb/ksu/bin/busybox"
|
||||
|
||||
private fun ksuDaemonMagicPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud_magic.so"
|
||||
@@ -37,10 +38,16 @@ private fun ksuDaemonOverlayfsPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud_overlayfs.so"
|
||||
}
|
||||
|
||||
fun readMountSystemFile(): Boolean {
|
||||
val shell = getRootShell()
|
||||
val filePath = "/data/adb/ksu/mount_system"
|
||||
val result = ShellUtils.fastCmd(shell, "cat $filePath").trim()
|
||||
return result == "OVERLAYFS"
|
||||
}
|
||||
|
||||
// Get the path based on the user's choice
|
||||
fun getKsuDaemonPath(): String {
|
||||
val prefs = ksuApp.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val useOverlayFs = prefs.getBoolean("use_overlay_fs", false)
|
||||
val useOverlayFs = readMountSystemFile()
|
||||
|
||||
return if (useOverlayFs) {
|
||||
ksuDaemonOverlayfsPath()
|
||||
@@ -49,6 +56,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 {
|
||||
val SHELL: Shell = createRootShell()
|
||||
val GLOBAL_MNT_SHELL: Shell = createRootShell(true)
|
||||
@@ -81,20 +103,23 @@ fun Uri.getFileName(context: Context): String? {
|
||||
|
||||
fun createRootShell(globalMnt: Boolean = false): Shell {
|
||||
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
||||
val builder = Shell.Builder.create()
|
||||
val builder = Shell.Builder.create().apply {
|
||||
setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||
}
|
||||
|
||||
return try {
|
||||
if (globalMnt) {
|
||||
builder.build(getKsuDaemonPath(), "debug", "su", "-g")
|
||||
builder.build(ksuDaemonMagicPath(), "debug", "su", "-g")
|
||||
} else {
|
||||
builder.build(getKsuDaemonPath(), "debug", "su")
|
||||
builder.build(ksuDaemonMagicPath(), "debug", "su")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.w(TAG, "ksu failed: ", e)
|
||||
try {
|
||||
if (globalMnt) {
|
||||
builder.build("su")
|
||||
} else {
|
||||
builder.build("su", "-mm")
|
||||
} else {
|
||||
builder.build("su")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "su failed: ", e)
|
||||
@@ -190,10 +215,9 @@ private fun flashWithIO(
|
||||
|
||||
fun flashModule(
|
||||
uri: Uri,
|
||||
onFinish: (Boolean, Int) -> Unit,
|
||||
onStdout: (String) -> Unit,
|
||||
onStderr: (String) -> Unit
|
||||
): Boolean {
|
||||
): FlashResult {
|
||||
val resolver = ksuApp.contentResolver
|
||||
with(resolver.openInputStream(uri)) {
|
||||
val file = File(ksuApp.cacheDir, "module.zip")
|
||||
@@ -206,15 +230,14 @@ fun flashModule(
|
||||
|
||||
file.delete()
|
||||
|
||||
onFinish(result.isSuccess, result.code)
|
||||
return result.isSuccess
|
||||
return FlashResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
fun runModuleAction(
|
||||
moduleId: String, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||
): Boolean {
|
||||
val shell = getRootShell()
|
||||
val shell = createRootShell(true)
|
||||
|
||||
val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||
override fun onAddElement(s: String?) {
|
||||
@@ -236,21 +259,19 @@ fun runModuleAction(
|
||||
}
|
||||
|
||||
fun restoreBoot(
|
||||
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||
): Boolean {
|
||||
onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||
): FlashResult {
|
||||
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
||||
val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr)
|
||||
onFinish(result.isSuccess, result.code)
|
||||
return result.isSuccess
|
||||
return FlashResult(result)
|
||||
}
|
||||
|
||||
fun uninstallPermanently(
|
||||
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||
): Boolean {
|
||||
onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||
): FlashResult {
|
||||
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
||||
val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr)
|
||||
onFinish(result.isSuccess, result.code)
|
||||
return result.isSuccess
|
||||
return FlashResult(result)
|
||||
}
|
||||
|
||||
suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) {
|
||||
@@ -268,10 +289,9 @@ fun installBoot(
|
||||
bootUri: Uri?,
|
||||
lkm: LkmSelection,
|
||||
ota: Boolean,
|
||||
onFinish: (Boolean, Int) -> Unit,
|
||||
onStdout: (String) -> Unit,
|
||||
onStderr: (String) -> Unit,
|
||||
): Boolean {
|
||||
): FlashResult {
|
||||
val resolver = ksuApp.contentResolver
|
||||
|
||||
val bootFile = bootUri?.let { uri ->
|
||||
@@ -334,15 +354,14 @@ fun installBoot(
|
||||
lkmFile?.delete()
|
||||
|
||||
// if boot uri is empty, it is direct install, when success, we should show reboot button
|
||||
onFinish(bootUri == null && result.isSuccess, result.code)
|
||||
return result.isSuccess
|
||||
return FlashResult(result, bootUri == null && result.isSuccess)
|
||||
}
|
||||
|
||||
fun reboot(reason: String = "") {
|
||||
val shell = getRootShell()
|
||||
if (reason == "recovery") {
|
||||
// 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")
|
||||
}
|
||||
@@ -387,6 +406,22 @@ fun hasMagisk(): Boolean {
|
||||
return result.isSuccess
|
||||
}
|
||||
|
||||
fun isGlobalNamespaceEnabled(): Boolean {
|
||||
val shell = getRootShell()
|
||||
val result =
|
||||
ShellUtils.fastCmd(shell, "nsenter --mount=/proc/1/ns/mnt cat ${Natives.GLOBAL_NAMESPACE_FILE}")
|
||||
Log.i(TAG, "is global namespace enabled: $result")
|
||||
return result == "1"
|
||||
}
|
||||
|
||||
fun setGlobalNamespaceEnabled(value: String) {
|
||||
getRootShell().newJob()
|
||||
.add("nsenter --mount=/proc/1/ns/mnt echo $value > ${Natives.GLOBAL_NAMESPACE_FILE}")
|
||||
.submit { result ->
|
||||
Log.i(TAG, "setGlobalNamespaceEnabled result: ${result.isSuccess} [${result.out}]")
|
||||
}
|
||||
}
|
||||
|
||||
fun isSepolicyValid(rules: String?): Boolean {
|
||||
if (rules == null) {
|
||||
return true
|
||||
@@ -444,7 +479,7 @@ fun getFileName(context: Context, uri: Uri): String {
|
||||
|
||||
fun moduleBackupDir(): String? {
|
||||
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()
|
||||
if (resultBase.isNotEmpty()) return null
|
||||
|
||||
@@ -463,16 +498,87 @@ fun moduleBackup(): Boolean {
|
||||
|
||||
val checkEmptyCommand = "if [ -z \"$(ls -A /data/adb/modules)\" ]; then echo 'empty'; fi"
|
||||
val resultCheckEmpty = ShellUtils.fastCmd(shell, checkEmptyCommand).trim()
|
||||
|
||||
if (resultCheckEmpty == "empty") {
|
||||
return false
|
||||
}
|
||||
|
||||
val backupDir = moduleBackupDir() ?: return false
|
||||
val command = "cp -rp /data/adb/modules/* $backupDir"
|
||||
val result = ShellUtils.fastCmd(shell, command).trim()
|
||||
val timestamp = ShellUtils.fastCmd(shell, "date +%Y%m%d_%H%M%S").trim()
|
||||
if (timestamp.isEmpty()) return false
|
||||
|
||||
return result.isEmpty()
|
||||
val tarName = "modules_backup_$timestamp.tar"
|
||||
val tarPath = "/data/local/tmp/$tarName"
|
||||
val internalBackupDir = "/data/adb/ksu/backup/modules"
|
||||
val internalBackupPath = "$internalBackupDir/$tarName"
|
||||
|
||||
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 {
|
||||
@@ -483,26 +589,6 @@ fun moduleMigration(): Boolean {
|
||||
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 {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libsusfsd.so"
|
||||
}
|
||||
@@ -524,12 +610,19 @@ fun getSuSFSVariant(): String {
|
||||
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} variant")
|
||||
return result
|
||||
}
|
||||
|
||||
fun getSuSFSFeatures(): String {
|
||||
val shell = getRootShell()
|
||||
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} features")
|
||||
return result
|
||||
}
|
||||
|
||||
fun hasSuSFs_SUS_SU(): String {
|
||||
val shell = getRootShell()
|
||||
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su support")
|
||||
return result
|
||||
}
|
||||
|
||||
fun susfsSUS_SU_0(): String {
|
||||
val shell = getRootShell()
|
||||
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 0")
|
||||
@@ -548,6 +641,32 @@ fun susfsSUS_SU_Mode(): String {
|
||||
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 {
|
||||
val shell = getRootShell()
|
||||
val escapedTemplate = template.replace("\"", "\\\"")
|
||||
@@ -581,4 +700,4 @@ fun launchApp(packageName: String) {
|
||||
fun restartApp(packageName: String) {
|
||||
forceStopApp(packageName)
|
||||
launchApp(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.rifsxd.ksunext.ui.util
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import java.util.Locale
|
||||
|
||||
object LocaleHelper {
|
||||
|
||||
/**
|
||||
* Check if should use system language settings (Android 13+)
|
||||
*/
|
||||
val useSystemLanguageSettings: Boolean
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
|
||||
/**
|
||||
* Launch system app locale settings (Android 13+)
|
||||
*/
|
||||
fun launchSystemLanguageSettings(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
try {
|
||||
val intent = Intent(Settings.ACTION_APP_LOCALE_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", context.packageName, null)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
// Fallback to app language settings if system settings not available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply saved language setting to context (for Android < 13)
|
||||
*/
|
||||
fun applyLanguage(context: Context): Context {
|
||||
// On Android 13+, language is handled by system
|
||||
if (useSystemLanguageSettings) {
|
||||
return context
|
||||
}
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val localeTag = prefs.getString("app_locale", "system") ?: "system"
|
||||
|
||||
return if (localeTag == "system") {
|
||||
context
|
||||
} else {
|
||||
val locale = parseLocaleTag(localeTag)
|
||||
setLocale(context, locale)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set locale for context (Android < 13)
|
||||
*/
|
||||
private fun setLocale(context: Context, locale: Locale): Context {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
updateResources(context, locale)
|
||||
} else {
|
||||
updateResourcesLegacy(context, locale)
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
private fun updateResources(context: Context, locale: Locale): Context {
|
||||
val configuration = Configuration()
|
||||
configuration.setLocale(locale)
|
||||
configuration.setLayoutDirection(locale)
|
||||
return context.createConfigurationContext(configuration)
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private fun updateResourcesLegacy(context: Context, locale: Locale): Context {
|
||||
Locale.setDefault(locale)
|
||||
val resources = context.resources
|
||||
val configuration = resources.configuration
|
||||
configuration.locale = locale
|
||||
configuration.setLayoutDirection(locale)
|
||||
resources.updateConfiguration(configuration, resources.displayMetrics)
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse locale tag to Locale object
|
||||
*/
|
||||
private fun parseLocaleTag(tag: String): Locale {
|
||||
return try {
|
||||
if (tag.contains("_")) {
|
||||
val parts = tag.split("_")
|
||||
Locale.Builder()
|
||||
.setLanguage(parts[0])
|
||||
.setRegion(parts.getOrNull(1) ?: "")
|
||||
.build()
|
||||
} else {
|
||||
Locale.Builder()
|
||||
.setLanguage(tag)
|
||||
.build()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Locale.getDefault()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart activity to apply language change (Android < 13)
|
||||
*/
|
||||
fun restartActivity(context: Context) {
|
||||
if (context is Activity && !useSystemLanguageSettings) {
|
||||
context.recreate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current app locale
|
||||
*/
|
||||
fun getCurrentAppLocale(context: Context): Locale? {
|
||||
return if (useSystemLanguageSettings) {
|
||||
// Android 13+ - get from system app locale settings
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
try {
|
||||
val localeManager = context.getSystemService(Context.LOCALE_SERVICE) as? android.app.LocaleManager
|
||||
val locales = localeManager?.applicationLocales
|
||||
if (locales != null && !locales.isEmpty) {
|
||||
locales.get(0)
|
||||
} else {
|
||||
null // System default
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null // System default
|
||||
}
|
||||
} else {
|
||||
null // System default
|
||||
}
|
||||
} else {
|
||||
// Android < 13 - get from SharedPreferences
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val localeTag = prefs.getString("app_locale", "system") ?: "system"
|
||||
if (localeTag == "system") {
|
||||
null // System default
|
||||
} else {
|
||||
parseLocaleTag(localeTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ fun getBugreportFile(context: Context): File {
|
||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
||||
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("rm -rf ${bugreportDir.absolutePath}").exec()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.rifsxd.ksunext.ui.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@@ -8,13 +9,19 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.io.File
|
||||
import java.text.Collator
|
||||
import java.util.Locale
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
||||
import com.rifsxd.ksunext.ui.util.listModules
|
||||
import com.rifsxd.ksunext.ui.util.overlayFsAvailable
|
||||
import com.rifsxd.ksunext.ui.util.getModuleSize
|
||||
import com.rifsxd.ksunext.ui.util.zygiskRequired
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
@@ -38,7 +45,10 @@ class ModuleViewModel : ViewModel() {
|
||||
val updateJson: String,
|
||||
val hasWebUi: Boolean,
|
||||
val hasActionScript: Boolean,
|
||||
val dirId: String
|
||||
val dirId: String,
|
||||
val size: Long,
|
||||
val banner: String,
|
||||
val zygiskRequired: Boolean
|
||||
)
|
||||
|
||||
data class ModuleUpdateInfo(
|
||||
@@ -48,9 +58,6 @@ class ModuleViewModel : ViewModel() {
|
||||
val changelog: String,
|
||||
)
|
||||
|
||||
var isOverlayAvailable by mutableStateOf(overlayFsAvailable())
|
||||
private set
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
@@ -58,11 +65,21 @@ class ModuleViewModel : ViewModel() {
|
||||
|
||||
var sortAToZ by mutableStateOf(false)
|
||||
var sortZToA by mutableStateOf(false)
|
||||
var sortSizeLowToHigh by mutableStateOf(false)
|
||||
var sortSizeHighToLow by mutableStateOf(false)
|
||||
var sortEnabledFirst by mutableStateOf(false)
|
||||
var sortActionFirst by mutableStateOf(false)
|
||||
var sortWebUiFirst by mutableStateOf(false)
|
||||
|
||||
val moduleList by derivedStateOf {
|
||||
val comparator = when {
|
||||
sortWebUiFirst -> compareByDescending<ModuleInfo> { it.hasWebUi }
|
||||
sortEnabledFirst -> compareByDescending<ModuleInfo> { it.enabled }
|
||||
sortActionFirst -> compareByDescending<ModuleInfo> { it.hasActionScript }
|
||||
sortAToZ -> compareBy<ModuleInfo> { it.name.lowercase() }
|
||||
sortZToA -> compareByDescending<ModuleInfo> { it.name.lowercase() }
|
||||
sortSizeLowToHigh -> compareBy<ModuleInfo> { it.size }
|
||||
sortSizeHighToLow -> compareByDescending<ModuleInfo> { it.size }
|
||||
else -> compareBy<ModuleInfo> { it.dirId }
|
||||
}.thenBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
|
||||
|
||||
@@ -83,58 +100,81 @@ class ModuleViewModel : ViewModel() {
|
||||
isNeedRefresh = true
|
||||
}
|
||||
|
||||
var zipUris by mutableStateOf<List<Uri>>(emptyList())
|
||||
|
||||
fun updateZipUris(uris: List<Uri>) {
|
||||
zipUris = uris
|
||||
}
|
||||
|
||||
fun clearZipUris() {
|
||||
zipUris = emptyList()
|
||||
}
|
||||
|
||||
fun fetchModuleList() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
viewModelScope.launch {
|
||||
|
||||
isRefreshing = true
|
||||
|
||||
val oldModuleList = modules
|
||||
withContext(Dispatchers.IO) {
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
val oldModuleList = modules
|
||||
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
kotlin.runCatching {
|
||||
val result = listModules()
|
||||
Log.i(TAG, "result: $result")
|
||||
|
||||
kotlin.runCatching {
|
||||
isOverlayAvailable = overlayFsAvailable()
|
||||
val array = JSONArray(result)
|
||||
modules = (0 until array.length())
|
||||
.asSequence()
|
||||
.map { array.getJSONObject(it) }
|
||||
.map { obj ->
|
||||
val id = obj.getString("id")
|
||||
val dirId = obj.getString("dir_id")
|
||||
val moduleDir = File("/data/adb/modules/$dirId")
|
||||
val size = getModuleSize(moduleDir)
|
||||
val zygiskRequired = zygiskRequired(moduleDir)
|
||||
|
||||
val result = listModules()
|
||||
ModuleInfo(
|
||||
id,
|
||||
obj.optString("name"),
|
||||
obj.optString("author", "Unknown"),
|
||||
obj.optString("version", "Unknown"),
|
||||
obj.optInt("versionCode", 0),
|
||||
obj.optString("description"),
|
||||
obj.getBoolean("enabled"),
|
||||
obj.getBoolean("update"),
|
||||
obj.getBoolean("remove"),
|
||||
obj.optString("updateJson"),
|
||||
obj.optBoolean("web"),
|
||||
obj.optBoolean("action"),
|
||||
dirId,
|
||||
size,
|
||||
obj.optString("banner"),
|
||||
zygiskRequired
|
||||
)
|
||||
}.toList()
|
||||
isNeedRefresh = false
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "fetchModuleList: ", e)
|
||||
isRefreshing = false
|
||||
}
|
||||
|
||||
Log.i(TAG, "result: $result")
|
||||
// when both old and new is kotlin.collections.EmptyList
|
||||
// moduleList update will don't trigger
|
||||
if (oldModuleList === modules) {
|
||||
isRefreshing = false
|
||||
}
|
||||
|
||||
val array = JSONArray(result)
|
||||
modules = (0 until array.length())
|
||||
.asSequence()
|
||||
.map { array.getJSONObject(it) }
|
||||
.map { obj ->
|
||||
ModuleInfo(
|
||||
obj.getString("id"),
|
||||
obj.optString("name"),
|
||||
obj.optString("author", "Unknown"),
|
||||
obj.optString("version", "Unknown"),
|
||||
obj.optInt("versionCode", 0),
|
||||
obj.optString("description"),
|
||||
obj.getBoolean("enabled"),
|
||||
obj.getBoolean("update"),
|
||||
obj.getBoolean("remove"),
|
||||
obj.optString("updateJson"),
|
||||
obj.optBoolean("web"),
|
||||
obj.optBoolean("action"),
|
||||
obj.getString("dir_id")
|
||||
)
|
||||
}.toList()
|
||||
isNeedRefresh = false
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "fetchModuleList: ", e)
|
||||
isRefreshing = false
|
||||
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
||||
}
|
||||
|
||||
// when both old and new is kotlin.collections.EmptyList
|
||||
// moduleList update will don't trigger
|
||||
if (oldModuleList === modules) {
|
||||
isRefreshing = false
|
||||
}
|
||||
|
||||
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
||||
}
|
||||
}
|
||||
|
||||
private fun sanitizeVersionString(version: String): String {
|
||||
return version.replace(Regex("[^a-zA-Z0-9.\\-_]"), "_")
|
||||
}
|
||||
|
||||
fun checkUpdate(m: ModuleInfo): Triple<String, String, String> {
|
||||
val empty = Triple("", "", "")
|
||||
if (m.updateJson.isEmpty() || m.remove || m.update || !m.enabled) {
|
||||
@@ -144,11 +184,8 @@ class ModuleViewModel : ViewModel() {
|
||||
val result = kotlin.runCatching {
|
||||
val url = m.updateJson
|
||||
Log.i(TAG, "checkUpdate url: $url")
|
||||
val response = okhttp3.OkHttpClient()
|
||||
.newCall(
|
||||
okhttp3.Request.Builder()
|
||||
.url(url)
|
||||
.build()
|
||||
val response = ksuApp.okhttpClient.newCall(
|
||||
okhttp3.Request.Builder().url(url).build()
|
||||
).execute()
|
||||
Log.d(TAG, "checkUpdate code: ${response.code}")
|
||||
if (response.isSuccessful) {
|
||||
@@ -167,7 +204,8 @@ class ModuleViewModel : ViewModel() {
|
||||
JSONObject(result)
|
||||
}.getOrNull() ?: return empty
|
||||
|
||||
val version = updateJson.optString("version", "")
|
||||
var version = updateJson.optString("version", "")
|
||||
version = sanitizeVersionString(version)
|
||||
val versionCode = updateJson.optInt("versionCode", 0)
|
||||
val zipUrl = updateJson.optString("zipUrl", "")
|
||||
val changelog = updateJson.optString("changelog", "")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.rifsxd.ksunext.ui.viewmodel
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.ApplicationInfo
|
||||
@@ -9,6 +10,7 @@ import android.os.IBinder
|
||||
import android.os.Parcelable
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -24,19 +26,20 @@ import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.ui.KsuService
|
||||
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
||||
import com.rifsxd.ksunext.ui.util.KsuCli
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import androidx.core.content.edit
|
||||
|
||||
class SuperUserViewModel : ViewModel() {
|
||||
|
||||
var refreshOnReturn by mutableStateOf(false)
|
||||
public set
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SuperUserViewModel"
|
||||
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
||||
private var profileOverrides by mutableStateOf<Map<String, Natives.Profile>>(emptyMap())
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
@@ -66,16 +69,26 @@ class SuperUserViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private val prefs = ksuApp.getSharedPreferences("settings", Context.MODE_PRIVATE)!!
|
||||
|
||||
var search by mutableStateOf("")
|
||||
var showSystemApps by mutableStateOf(false)
|
||||
var showSystemApps by mutableStateOf(prefs.getBoolean("show_system_apps", false))
|
||||
private set
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
fun updateShowSystemApps(newValue: Boolean) {
|
||||
showSystemApps = newValue
|
||||
prefs.edit { putBoolean("show_system_apps", newValue) }
|
||||
}
|
||||
|
||||
private val sortedList by derivedStateOf {
|
||||
val comparator = compareBy<AppInfo> {
|
||||
when {
|
||||
it.allowSu -> 0
|
||||
it.hasCustomProfile -> 1
|
||||
it.profile != null && it.profile.allowSu -> 0
|
||||
it.profile != null && (
|
||||
if (it.profile.allowSu) !it.profile.rootUseDefault else !it.profile.nonRootUseDefault
|
||||
) -> 1
|
||||
else -> 2
|
||||
}
|
||||
}.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))
|
||||
@@ -85,7 +98,9 @@ class SuperUserViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
val appList by derivedStateOf {
|
||||
sortedList.filter {
|
||||
sortedList.map { app ->
|
||||
profileOverrides[app.packageName]?.let { app.copy(profile = it) } ?: app
|
||||
}.filter {
|
||||
it.label.contains(search, true) || it.packageName.contains(
|
||||
search,
|
||||
true
|
||||
@@ -97,6 +112,12 @@ class SuperUserViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAppProfile(packageName: String, newProfile: Natives.Profile) {
|
||||
profileOverrides = profileOverrides.toMutableMap().apply {
|
||||
put(packageName, newProfile)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun connectKsuService(
|
||||
crossinline onDisconnect: () -> Unit = {}
|
||||
): Pair<IBinder, ServiceConnection> = suspendCoroutine {
|
||||
|
||||
@@ -11,18 +11,17 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import com.rifsxd.ksunext.Natives
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.profile.Capabilities
|
||||
import com.rifsxd.ksunext.profile.Groups
|
||||
import com.rifsxd.ksunext.ui.util.getAppProfileTemplate
|
||||
import com.rifsxd.ksunext.ui.util.listAppProfileTemplates
|
||||
import com.rifsxd.ksunext.ui.util.setAppProfileTemplate
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.text.Collator
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
/**
|
||||
@@ -138,13 +137,7 @@ class TemplateViewModel : ViewModel() {
|
||||
|
||||
private fun fetchRemoteTemplates() {
|
||||
runCatching {
|
||||
val client: OkHttpClient = OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.writeTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(10, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
client.newCall(
|
||||
ksuApp.okhttpClient.newCall(
|
||||
Request.Builder().url(TEMPLATE_INDEX_URL).build()
|
||||
).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
@@ -155,7 +148,7 @@ private fun fetchRemoteTemplates() {
|
||||
0.until(remoteTemplateIds.length()).forEach { i ->
|
||||
val id = remoteTemplateIds.getString(i)
|
||||
Log.i(TAG, "fetch template: $id")
|
||||
val templateJson = client.newCall(
|
||||
val templateJson = ksuApp.okhttpClient.newCall(
|
||||
Request.Builder().url(TEMPLATE_URL.format(id)).build()
|
||||
).runCatching {
|
||||
execute().use { response ->
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user