You've already forked KernelSU
mirror of
https://github.com/tiann/KernelSU.git
synced 2025-08-27 23:46:34 +00:00
Compare commits
522 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f341a4e3a | ||
|
|
40d7bc6256 | ||
|
|
56dbc980f4 | ||
|
|
bf71ff133c | ||
|
|
a9b156df43 | ||
|
|
1690e5db02 | ||
|
|
300d9d4cca | ||
|
|
3f12080dfe | ||
|
|
b670db2d22 | ||
|
|
62a31b3dc2 | ||
|
|
a395b1011e | ||
|
|
406070914a | ||
|
|
8685fa1f60 | ||
|
|
c95163b144 | ||
|
|
648d56da39 | ||
|
|
33c0a9eebd | ||
|
|
3f86fb016d | ||
|
|
66316e76f5 | ||
|
|
5591a94f87 | ||
|
|
f855f8148a | ||
|
|
0c52f24612 | ||
|
|
9635a00036 | ||
|
|
cbc04ff6df | ||
|
|
8e448767a5 | ||
|
|
2820779947 | ||
|
|
a99c69f9b4 | ||
|
|
a829707b16 | ||
|
|
77d16ac896 | ||
|
|
d02855a40a | ||
|
|
b904680f13 | ||
|
|
811c68cac0 | ||
|
|
f20ccc1728 | ||
|
|
66e7db2a4e | ||
|
|
e6b05b1d3c | ||
|
|
329010a694 | ||
|
|
9c4d20c0f2 | ||
|
|
355b55a01d | ||
|
|
e85646fad4 | ||
|
|
4969c5f548 | ||
|
|
55f8f2da90 | ||
|
|
65bff7bf03 | ||
|
|
30dfbbdc0e | ||
|
|
fceffc9cfe | ||
|
|
01b685ce58 | ||
|
|
cbd184421c | ||
|
|
b0a42abf4f | ||
|
|
cfc982f2f3 | ||
|
|
e0e7058d14 | ||
|
|
e0802b0d15 | ||
|
|
81f15ef120 | ||
|
|
20c19d7126 | ||
|
|
a360cd87c0 | ||
|
|
ea9b572402 | ||
|
|
6bf9e0478e | ||
|
|
abf0dacb36 | ||
|
|
263b986bcd | ||
|
|
15bdd9f507 | ||
|
|
810a62f795 | ||
|
|
07e475c5dc | ||
|
|
eb02e42bc7 | ||
|
|
5db51b0715 | ||
|
|
60d2685f7e | ||
|
|
a4b9ea04a4 | ||
|
|
f80d0764b5 | ||
|
|
f80769a82a | ||
|
|
64269c8c4f | ||
|
|
9f04482b90 | ||
|
|
aca505c3e6 | ||
|
|
d4826bc97c | ||
|
|
4efc8164f1 | ||
|
|
0fc25cf091 | ||
|
|
ca438291cc | ||
|
|
d6cab60e6d | ||
|
|
4d4bd4793f | ||
|
|
c1a2cbf1e4 | ||
|
|
4b1fb121b4 | ||
|
|
883a3e3407 | ||
|
|
27bd18f60e | ||
|
|
7cb5fb47e1 | ||
|
|
d43b40572d | ||
|
|
c99b5b31c1 | ||
|
|
ca960a2a8f | ||
|
|
cce423a2f6 | ||
|
|
946fb6f999 | ||
|
|
b6ecce4317 | ||
|
|
be70a91f16 | ||
|
|
71c2790f08 | ||
|
|
8733b390ca | ||
|
|
c4e106d6f8 | ||
|
|
b612efcfad | ||
|
|
7f53882007 | ||
|
|
23ba3182cf | ||
|
|
8abd37a35c | ||
|
|
d7bc853bfc | ||
|
|
16c5aba4ff | ||
|
|
3914242457 | ||
|
|
eaa12161d6 | ||
|
|
0f985917f9 | ||
|
|
a569b1c76e | ||
|
|
7f1ea2e178 | ||
|
|
da89a45d56 | ||
|
|
dc2fb20d24 | ||
|
|
d7625722db | ||
|
|
4144f10d9a | ||
|
|
aaddaf1a78 | ||
|
|
2fec279de3 | ||
|
|
1e676e5dc2 | ||
|
|
7b63e099ce | ||
|
|
aef943ebe3 | ||
|
|
1637864636 | ||
|
|
e934bfb648 | ||
|
|
653225bb5b | ||
|
|
decbdeb5d7 | ||
|
|
51ca19f267 | ||
|
|
5b920f8230 | ||
|
|
601ce2120a | ||
|
|
6d79060e4c | ||
|
|
e95e87a7a4 | ||
|
|
144b0cc8e9 | ||
|
|
d9d9066316 | ||
|
|
3af293f991 | ||
|
|
60c9fabb44 | ||
|
|
23ffc2a3b2 | ||
|
|
2bab388bbf | ||
|
|
e9997a07c1 | ||
|
|
757e69b15e | ||
|
|
506385cfad | ||
|
|
4b27a9a324 | ||
|
|
1326fd32c5 | ||
|
|
90d63fe184 | ||
|
|
0f8a1346c7 | ||
|
|
1fad91a4e2 | ||
|
|
ddff9ee701 | ||
|
|
c7a9655ab9 | ||
|
|
fd7681c3ff | ||
|
|
e3e4d2eed4 | ||
|
|
30e00859b9 | ||
|
|
faf7a8e3b1 | ||
|
|
b82fc971dd | ||
|
|
52f5727875 | ||
|
|
01711b4114 | ||
|
|
153ce9a39a | ||
|
|
f37cc16117 | ||
|
|
5f31571cc7 | ||
|
|
92a1267b29 | ||
|
|
b99701d216 | ||
|
|
097e291d93 | ||
|
|
eb3f604ab8 | ||
|
|
3d9ca63bac | ||
|
|
4be7485180 | ||
|
|
afefe20c96 | ||
|
|
76e7d7c60c | ||
|
|
d08f537c89 | ||
|
|
88e20d102d | ||
|
|
32b3ec9844 | ||
|
|
fbeea49318 | ||
|
|
5deecb3b50 | ||
|
|
534ac88195 | ||
|
|
d867c3c5e2 | ||
|
|
aca9ac50f3 | ||
|
|
d6cbda49aa | ||
|
|
fe7f509f9d | ||
|
|
26da7d590e | ||
|
|
706cd1e73e | ||
|
|
6472b14a59 | ||
|
|
c305dca5ab | ||
|
|
94978b7b28 | ||
|
|
161b3280c4 | ||
|
|
e69769d25f | ||
|
|
a1153683e1 | ||
|
|
79341ab501 | ||
|
|
05b33abb79 | ||
|
|
f5095f96fa | ||
|
|
dcd9d65c92 | ||
|
|
fa3dec8852 | ||
|
|
87d10054ae | ||
|
|
1a308afe63 | ||
|
|
f46830f28f | ||
|
|
c560d603e6 | ||
|
|
064de704f2 | ||
|
|
860911c455 | ||
|
|
1d04c5086c | ||
|
|
f4eab986a9 | ||
|
|
701e85d931 | ||
|
|
bf43654725 | ||
|
|
a56a922f96 | ||
|
|
19a697a968 | ||
|
|
c9b540b12c | ||
|
|
cd48f64154 | ||
|
|
acd2c343e2 | ||
|
|
dec7f91182 | ||
|
|
d2684292e8 | ||
|
|
07b940d127 | ||
|
|
845515fa6b | ||
|
|
0db7aa573e | ||
|
|
3783f82b28 | ||
|
|
684973b4bf | ||
|
|
0617c4440b | ||
|
|
9223558197 | ||
|
|
c13040a0ea | ||
|
|
b45c4f57c5 | ||
|
|
8196243478 | ||
|
|
b7f937b7f9 | ||
|
|
8fdff569d6 | ||
|
|
b658d820a1 | ||
|
|
ce56c19ad9 | ||
|
|
d27561c505 | ||
|
|
57898f13c1 | ||
|
|
5da1767ff8 | ||
|
|
aa21385a36 | ||
|
|
697e06125a | ||
|
|
95c383ea18 | ||
|
|
048e94ba69 | ||
|
|
4a9f1de7a0 | ||
|
|
00438a6d6e | ||
|
|
88d4eca8ff | ||
|
|
8a298bb867 | ||
|
|
5526fa2468 | ||
|
|
c26a26b92d | ||
|
|
75e64eafd2 | ||
|
|
961478fa23 | ||
|
|
a903b0fa4e | ||
|
|
0f09b90f5b | ||
|
|
8b81aeaf70 | ||
|
|
b4a52f89cc | ||
|
|
e9df5105b3 | ||
|
|
cd2711395a | ||
|
|
83a8d77018 | ||
|
|
426fde58fd | ||
|
|
59040c3aea | ||
|
|
f4b53daddf | ||
|
|
4c79ad7a81 | ||
|
|
fb6565bd19 | ||
|
|
a3e939ab90 | ||
|
|
80797548b5 | ||
|
|
f86fce6e16 | ||
|
|
318be535a8 | ||
|
|
92d4de3a73 | ||
|
|
3cf5fc4c51 | ||
|
|
4e3cbf627a | ||
|
|
72febb34c0 | ||
|
|
03068f279d | ||
|
|
7371eae382 | ||
|
|
fae1fd9826 | ||
|
|
ec5d9e6368 | ||
|
|
64f849dc10 | ||
|
|
d68fa5aed5 | ||
|
|
1b67c1b153 | ||
|
|
f349507232 | ||
|
|
bf823a29e8 | ||
|
|
55f712d1b2 | ||
|
|
ca10d7bcb9 | ||
|
|
d24813b2c3 | ||
|
|
a6325193cf | ||
|
|
7a1767b4c9 | ||
|
|
2d4d26c68e | ||
|
|
3f98493bb3 | ||
|
|
927f2a26bd | ||
|
|
344c08bb79 | ||
|
|
84f16e4c82 | ||
|
|
4ff9dcaa17 | ||
|
|
34b64b8310 | ||
|
|
8f4e7f8a79 | ||
|
|
75b5fdfb9d | ||
|
|
d4e19bb8fc | ||
|
|
571f89fac3 | ||
|
|
9e058c48a6 | ||
|
|
e828053439 | ||
|
|
8a7d153b02 | ||
|
|
06fdae18d2 | ||
|
|
df8c91b306 | ||
|
|
10b31bd09a | ||
|
|
54a705d8dc | ||
|
|
294caf3deb | ||
|
|
a1f7d474f9 | ||
|
|
1df5fec49b | ||
|
|
fd03626362 | ||
|
|
9b294682b0 | ||
|
|
a4fb9e4031 | ||
|
|
3ebee74efc | ||
|
|
433627c82b | ||
|
|
6cce322107 | ||
|
|
906007a7b3 | ||
|
|
583426ecc5 | ||
|
|
ce892bc439 | ||
|
|
1f1d4d454e | ||
|
|
4695b41f53 | ||
|
|
52f17cde40 | ||
|
|
3408f944e6 | ||
|
|
b1830049f1 | ||
|
|
79951f06ed | ||
|
|
8828939994 | ||
|
|
a3b92d6fee | ||
|
|
a22959beae | ||
|
|
7753dc0987 | ||
|
|
960c40129b | ||
|
|
f371d784ea | ||
|
|
59b45ce822 | ||
|
|
340595276f | ||
|
|
72d756c9f2 | ||
|
|
3d59071571 | ||
|
|
394cfe7516 | ||
|
|
0810db101e | ||
|
|
ab0ae9d196 | ||
|
|
13748300eb | ||
|
|
c4db2bab4f | ||
|
|
e352ccc470 | ||
|
|
7747c0e211 | ||
|
|
2661a36375 | ||
|
|
1bdddb13ce | ||
|
|
7d3c50ef0a | ||
|
|
2ee7696d67 | ||
|
|
54ee400dc5 | ||
|
|
945e2c3209 | ||
|
|
298e42cb42 | ||
|
|
3a657a9dbb | ||
|
|
55aa54ca85 | ||
|
|
0b8359a2e2 | ||
|
|
afb04126f6 | ||
|
|
98fae23864 | ||
|
|
23805d4784 | ||
|
|
01bf24fa7b | ||
|
|
47f05a139d | ||
|
|
3c0c70ba7f | ||
|
|
c19ba7fab0 | ||
|
|
1f42bbac5e | ||
|
|
cbb98a1de9 | ||
|
|
08745664a6 | ||
|
|
eac6fd0484 | ||
|
|
ad1dbf77a1 | ||
|
|
81bbb31098 | ||
|
|
52234d040f | ||
|
|
1fb2aad893 | ||
|
|
64744bb31d | ||
|
|
b9747fbe69 | ||
|
|
85922946b7 | ||
|
|
40fc6d2163 | ||
|
|
da662133ae | ||
|
|
25592a0614 | ||
|
|
2d96aaa28f | ||
|
|
64cf6eb8b9 | ||
|
|
7e44765074 | ||
|
|
d84fdada31 | ||
|
|
71c14d96ab | ||
|
|
5d988002c7 | ||
|
|
15ff9fbf41 | ||
|
|
542d3e40af | ||
|
|
7c4fb51b5c | ||
|
|
5b10d10b82 | ||
|
|
677d3357b9 | ||
|
|
5e893e3d04 | ||
|
|
685cd75c99 | ||
|
|
8354204c32 | ||
|
|
d394fd2e01 | ||
|
|
71799c7aed | ||
|
|
76bdd12f73 | ||
|
|
fb103472c6 | ||
|
|
9f17bafbf0 | ||
|
|
b2f9f3ade9 | ||
|
|
12a095fd1a | ||
|
|
7153336ad1 | ||
|
|
5f2566e478 | ||
|
|
0af25af1be | ||
|
|
ea3b397f34 | ||
|
|
6aeb76a3ef | ||
|
|
ae9519de42 | ||
|
|
8bf33e9aca | ||
|
|
b91a294138 | ||
|
|
d274a315b1 | ||
|
|
b0c3c3a9a2 | ||
|
|
1147eb205d | ||
|
|
f160abf9ce | ||
|
|
61ad99dbe5 | ||
|
|
9a126645e8 | ||
|
|
21f39f6de8 | ||
|
|
0ddb8a4c89 | ||
|
|
c9997b5ca9 | ||
|
|
0b1bab5b01 | ||
|
|
676590be15 | ||
|
|
b52bf53d01 | ||
|
|
7bdb885816 | ||
|
|
cde3e95180 | ||
|
|
7ecb4b03ee | ||
|
|
e8a90aadb2 | ||
|
|
beaa048be3 | ||
|
|
936b650f12 | ||
|
|
747c91d5c8 | ||
|
|
5023d0ab1d | ||
|
|
5b638c876e | ||
|
|
d6a7231fae | ||
|
|
06681a2490 | ||
|
|
278cbef3ec | ||
|
|
a83390b0ec | ||
|
|
907bcad1a7 | ||
|
|
4c0a36785b | ||
|
|
983ad2c1fd | ||
|
|
a3590b767e | ||
|
|
12e00dc717 | ||
|
|
e00a355fa8 | ||
|
|
e95c5a9675 | ||
|
|
d0b8144b96 | ||
|
|
d16d3f87a6 | ||
|
|
76decba8d9 | ||
|
|
d75678fca0 | ||
|
|
abe1fa471d | ||
|
|
07a430aa5b | ||
|
|
df9cf61575 | ||
|
|
bfe8c2eecd | ||
|
|
b732765811 | ||
|
|
a966252fa5 | ||
|
|
3664003260 | ||
|
|
978178afc0 | ||
|
|
16f6f30eae | ||
|
|
63851f8c88 | ||
|
|
34c6765752 | ||
|
|
c23d1bcf58 | ||
|
|
6ae7e1624f | ||
|
|
ba4b014a99 | ||
|
|
f192638943 | ||
|
|
0323ee7958 | ||
|
|
021ef521cb | ||
|
|
34086cd445 | ||
|
|
c306eddee8 | ||
|
|
c72f7d750e | ||
|
|
f9a91848ad | ||
|
|
cd8013a616 | ||
|
|
dc536d652a | ||
|
|
3114f6d7f6 | ||
|
|
e2f5015107 | ||
|
|
884dd606eb | ||
|
|
8d246a6b9d | ||
|
|
2ee3d55c88 | ||
|
|
55b540bf4c | ||
|
|
c0d147dcad | ||
|
|
be413fd147 | ||
|
|
786f3d6441 | ||
|
|
8f50b5f6e5 | ||
|
|
90639fad6c | ||
|
|
2979434e2a | ||
|
|
56d145666c | ||
|
|
db5f77aa96 | ||
|
|
5fbce8ef07 | ||
|
|
4cc3644416 | ||
|
|
01a7678a26 | ||
|
|
95d22d2bb4 | ||
|
|
3efb2be456 | ||
|
|
3b7ca2d7e8 | ||
|
|
7b45bc5aad | ||
|
|
ed1e892600 | ||
|
|
f188802044 | ||
|
|
965c23867f | ||
|
|
b271b2f587 | ||
|
|
0953f50e0c | ||
|
|
77ac974ce8 | ||
|
|
d714ab0c5d | ||
|
|
38eb93d5ca | ||
|
|
080d5bd9e8 | ||
|
|
d05ec41379 | ||
|
|
d07956ead6 | ||
|
|
4e3af6dab3 | ||
|
|
45d96b98c5 | ||
|
|
b554c66b46 | ||
|
|
fb87d0f0f5 | ||
|
|
37abe48702 | ||
|
|
ec9babea76 | ||
|
|
8a464ac7b2 | ||
|
|
eb5a99e4b6 | ||
|
|
047312e0e5 | ||
|
|
45a25eda50 | ||
|
|
120c2f43de | ||
|
|
9deb820923 | ||
|
|
ae21d4c9fd | ||
|
|
5381ceabae | ||
|
|
5d07e1d392 | ||
|
|
d78f24098a | ||
|
|
cd952f5e45 | ||
|
|
156b17f69d | ||
|
|
f4d2b0feab | ||
|
|
2c0a9cd64c | ||
|
|
134507b928 | ||
|
|
2b42d14ef8 | ||
|
|
e6071b5247 | ||
|
|
f288cfccae | ||
|
|
1cc9da5efe | ||
|
|
0aee64f339 | ||
|
|
cbbdc665c8 | ||
|
|
1b2635784f | ||
|
|
ee5d2f8c84 | ||
|
|
dfc2a86e70 | ||
|
|
c0066b68f5 | ||
|
|
6ef5e4ef76 | ||
|
|
e3e77fde78 | ||
|
|
8ea55c7f2f | ||
|
|
d2a976b3cc | ||
|
|
d675662862 | ||
|
|
839b318785 | ||
|
|
971f59c11e | ||
|
|
ab58808b64 | ||
|
|
9cbb7cb10e | ||
|
|
70f2df11d1 | ||
|
|
827a2f2901 | ||
|
|
a9c33f6940 | ||
|
|
2bb73a2a92 | ||
|
|
90407986be | ||
|
|
b85ece440b | ||
|
|
a10d2651c1 | ||
|
|
b308a368d3 | ||
|
|
e6fea652de | ||
|
|
0856b718de | ||
|
|
6f1ccc5b3c | ||
|
|
da959b4e17 | ||
|
|
0bfd6d9e30 | ||
|
|
980f1d09bc | ||
|
|
b644c124e3 | ||
|
|
65005131bd | ||
|
|
18aa7f2a17 | ||
|
|
cd5bc2efa9 | ||
|
|
477361f119 | ||
|
|
d3632e4b3b | ||
|
|
0c2f90123b | ||
|
|
09d90e1a0a | ||
|
|
4fe167c361 |
5
.github/FUNDING.yml
vendored
Normal file
5
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: tiann
|
||||
patreon: weishu
|
||||
custom: https://vxposed.com/donate.html
|
||||
2
.github/ISSUE_TEMPLATE/add_device.yml
vendored
2
.github/ISSUE_TEMPLATE/add_device.yml
vendored
@@ -6,7 +6,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for supporting KernelSU !
|
||||
Thanks for supporting KernelSU!
|
||||
- type: input
|
||||
id: repo-url
|
||||
attributes:
|
||||
|
||||
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
72
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
72
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve KernelSU
|
||||
labels: [Bug]
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please check before submitting an issue
|
||||
options:
|
||||
- label: I have searched the issues and haven't found anything relevant
|
||||
required: true
|
||||
|
||||
- label: I will upload bugreport file in KernelSU Manager - Settings - Report log
|
||||
required: true
|
||||
|
||||
- label: I know how to reproduce the issue which may not be specific to my device
|
||||
required: false
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: Steps to reproduce the behaviour
|
||||
placeholder: |
|
||||
- 1. Go to '...'
|
||||
- 2. Click on '....'
|
||||
- 3. Scroll down to '....'
|
||||
- 4. See error
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Logs
|
||||
description: If applicable, add crash or any other logs to help us figure out the problem.
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Device info
|
||||
value: |
|
||||
- Device:
|
||||
- OS Version:
|
||||
- KernelSU Version:
|
||||
- Kernel Version:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
10
.github/ISSUE_TEMPLATE/custom.md
vendored
10
.github/ISSUE_TEMPLATE/custom.md
vendored
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Custom issue template
|
||||
about: Describe this issue template's purpose here.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/custom.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/custom.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: Custom issue template
|
||||
description: WARNING! If you are reporting a bug but use this template, the issue will be closed directly.
|
||||
title: '[Custom]'
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "Describe your problem."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
40
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
40
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Feature Request
|
||||
description: "Suggest an idea for this project"
|
||||
title: "[Feature]"
|
||||
labels: "feature"
|
||||
assignees: tiann
|
||||
body:
|
||||
- type: markdown
|
||||
id: feature-info
|
||||
attributes:
|
||||
value: "## Feature Infomation"
|
||||
- type: textarea
|
||||
id: feature-main
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "Is your feature request related to a problem? Please describe."
|
||||
description: "A clear and concise description of what the problem is."
|
||||
placeholder: "I'm always frustrated when [...]"
|
||||
- type: textarea
|
||||
id: feature-solution
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "Describe the solution you'd like."
|
||||
description: "A clear and concise description of what you want to happen."
|
||||
- type: textarea
|
||||
id: feature-describe
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "Describe alternatives you've considered."
|
||||
description: "A clear and concise description of any alternative solutions or features you've considered."
|
||||
- type: textarea
|
||||
id: feature-extra
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "Additional context"
|
||||
description: "Add any other context or screenshots about the feature request here."
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
From f1e398602b989ac197cdd0fda4a7c4c323b03eb9 Mon Sep 17 00:00:00 2001
|
||||
From: DozNaka <dozdguide@gmail.com>
|
||||
Date: Mon, 11 Apr 2022 20:43:45 -0400
|
||||
Subject: [PATCH] Makefile: Use CCACHE for faster compilation
|
||||
|
||||
---
|
||||
Makefile | 20 ++++++++++----------
|
||||
1 file changed, 10 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/Makefile b/Makefile
|
||||
index e8b8d5894..51e8aac6e 100644
|
||||
--- a/Makefile
|
||||
+++ b/Makefile
|
||||
@@ -442,21 +442,21 @@ KBUILD_HOSTLDLIBS := $(HOST_LFS_LIBS) $(HOSTLDLIBS)
|
||||
# Make variables (CC, etc...)
|
||||
CPP = $(CC) -E
|
||||
ifneq ($(LLVM),)
|
||||
-CC = clang
|
||||
-LD = ld.lld
|
||||
-AR = llvm-ar
|
||||
+CC = $(CCACHE) clang
|
||||
+LD = $(CCACHE) ld.lld
|
||||
+AR = $(CCACHE) llvm-ar
|
||||
NM = llvm-nm
|
||||
-OBJCOPY = llvm-objcopy
|
||||
-OBJDUMP = llvm-objdump
|
||||
+OBJCOPY = $(CCACHE) llvm-objcopy
|
||||
+OBJDUMP = $(CCACHE) llvm-objdump
|
||||
READELF = llvm-readelf
|
||||
STRIP = llvm-strip
|
||||
else
|
||||
-CC = $(CROSS_COMPILE)gcc
|
||||
-LD = $(CROSS_COMPILE)ld
|
||||
-AR = $(CROSS_COMPILE)ar
|
||||
+CC = $(CCACHE) $(CROSS_COMPILE)gcc
|
||||
+LD = $(CCACHE) $(CROSS_COMPILE)ld
|
||||
+AR = $(CCACHE) $(CROSS_COMPILE)ar
|
||||
NM = $(CROSS_COMPILE)nm
|
||||
-OBJCOPY = $(CROSS_COMPILE)objcopy
|
||||
-OBJDUMP = $(CROSS_COMPILE)objdump
|
||||
+OBJCOPY = $(CCACHE) $(CROSS_COMPILE)objcopy
|
||||
+OBJDUMP = $(CCACHE) $(CROSS_COMPILE)objdump
|
||||
READELF = $(CROSS_COMPILE)readelf
|
||||
STRIP = $(CROSS_COMPILE)strip
|
||||
endif
|
||||
--
|
||||
2.37.2
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
From f1e398602b989ac197cdd0fda4a7c4c323b03eb9 Mon Sep 17 00:00:00 2001
|
||||
From: DozNaka <dozdguide@gmail.com>
|
||||
Date: Mon, 11 Apr 2022 20:43:45 -0400
|
||||
Subject: [PATCH] Makefile: Use CCACHE for faster compilation
|
||||
|
||||
---
|
||||
Makefile | 20 ++++++++++----------
|
||||
1 file changed, 10 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/Makefile b/Makefile
|
||||
index e8b8d5894..51e8aac6e 100644
|
||||
--- a/Makefile
|
||||
+++ b/Makefile
|
||||
@@ -442,21 +442,21 @@ KBUILD_HOSTLDLIBS := $(HOST_LFS_LIBS) $(HOSTLDLIBS)
|
||||
# Make variables (CC, etc...)
|
||||
CPP = $(CC) -E
|
||||
ifneq ($(LLVM),)
|
||||
-CC = clang
|
||||
-LD = ld.lld
|
||||
-AR = llvm-ar
|
||||
+CC = $(CCACHE) clang
|
||||
+LD = $(CCACHE) ld.lld
|
||||
+AR = $(CCACHE) llvm-ar
|
||||
NM = llvm-nm
|
||||
-OBJCOPY = llvm-objcopy
|
||||
-OBJDUMP = llvm-objdump
|
||||
+OBJCOPY = $(CCACHE) llvm-objcopy
|
||||
+OBJDUMP = $(CCACHE) llvm-objdump
|
||||
READELF = llvm-readelf
|
||||
STRIP = llvm-strip
|
||||
else
|
||||
-CC = $(CROSS_COMPILE)gcc
|
||||
-LD = $(CROSS_COMPILE)ld
|
||||
-AR = $(CROSS_COMPILE)ar
|
||||
+CC = $(CCACHE) $(CROSS_COMPILE)gcc
|
||||
+LD = $(CCACHE) $(CROSS_COMPILE)ld
|
||||
+AR = $(CCACHE) $(CROSS_COMPILE)ar
|
||||
NM = $(CROSS_COMPILE)nm
|
||||
-OBJCOPY = $(CROSS_COMPILE)objcopy
|
||||
-OBJDUMP = $(CROSS_COMPILE)objdump
|
||||
+OBJCOPY = $(CCACHE) $(CROSS_COMPILE)objcopy
|
||||
+OBJDUMP = $(CCACHE) $(CROSS_COMPILE)objdump
|
||||
READELF = $(CROSS_COMPILE)readelf
|
||||
STRIP = $(CROSS_COMPILE)strip
|
||||
endif
|
||||
--
|
||||
2.37.2
|
||||
|
||||
2
.github/workflows/add-device.yml
vendored
2
.github/workflows/add-device.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
env:
|
||||
ISSUE_CONTENT: ${{ github.event.issue.body }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Parse issue body
|
||||
id: handle-add-device
|
||||
run: |
|
||||
|
||||
16
.github/workflows/build-debug-kernel.yml
vendored
16
.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.177
|
||||
tag: android12-5.10-2023-06
|
||||
os_patch_level: 2023-06
|
||||
version_name: android12-5.10.185
|
||||
tag: android12-5.10-2023-09
|
||||
os_patch_level: 2023-09
|
||||
patch_path: "5.10"
|
||||
debug: true
|
||||
build-debug-kernel-a13:
|
||||
@@ -17,15 +17,15 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 177
|
||||
os_patch_level: 2023-06
|
||||
sub_level: 187
|
||||
os_patch_level: 2023-08
|
||||
- version: "5.15"
|
||||
sub_level: 94
|
||||
os_patch_level: 2023-06
|
||||
sub_level: 119
|
||||
os_patch_level: 2023-09
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android13-${{ matrix.version }}
|
||||
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
|
||||
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
debug: true
|
||||
debug: true
|
||||
|
||||
36
.github/workflows/build-kernel-a12.yml
vendored
36
.github/workflows/build-kernel-a12.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Build Kernel - Android 12
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "ci"]
|
||||
branches: ["main", "ci", "checkci"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-a12.yml"
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
@@ -17,18 +17,20 @@ on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
build-kernel:
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- sub_level: 66
|
||||
os_patch_level: 2021-11
|
||||
os_patch_level: 2022-01
|
||||
- sub_level: 81
|
||||
os_patch_level: 2022-03
|
||||
- sub_level: 101
|
||||
os_patch_level: 2022-05
|
||||
- sub_level: 110
|
||||
os_patch_level: 2022-07
|
||||
- sub_level: 117
|
||||
os_patch_level: 2022-09
|
||||
- sub_level: 136
|
||||
os_patch_level: 2022-11
|
||||
- sub_level: 149
|
||||
@@ -38,7 +40,11 @@ jobs:
|
||||
- sub_level: 168
|
||||
os_patch_level: 2023-05
|
||||
- sub_level: 177
|
||||
os_patch_level: 2023-06
|
||||
os_patch_level: 2023-07
|
||||
- sub_level: 185
|
||||
os_patch_level: 2023-09
|
||||
- sub_level: 198
|
||||
os_patch_level: 2023-11
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -47,13 +53,13 @@ jobs:
|
||||
tag: android12-5.10-${{ matrix.os_patch_level }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
patch_path: "5.10"
|
||||
|
||||
upload-artifacts:
|
||||
needs: build-kernel
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }}
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
@@ -61,9 +67,9 @@ jobs:
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
@@ -75,11 +81,11 @@ jobs:
|
||||
- name: Download prebuilt toolchain
|
||||
run: |
|
||||
AOSP_MIRROR=https://android.googlesource.com
|
||||
BRANCH=master-kernel-build-2022
|
||||
BRANCH=main-kernel-build-2023
|
||||
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
|
||||
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
|
||||
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
|
||||
pip3 install python-telegram-bot
|
||||
pip3 install telethon==1.31.1
|
||||
|
||||
- name: Set boot sign key
|
||||
env:
|
||||
@@ -89,8 +95,12 @@ jobs:
|
||||
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
fi
|
||||
|
||||
- name: Setup mutex for uploading
|
||||
uses: ben-z/gh-action-mutex@v1.0-alpha-7
|
||||
- name: Bot session cache
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: scripts/ksubot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Build boot images
|
||||
run: |
|
||||
@@ -109,13 +119,13 @@ jobs:
|
||||
run: ls -R
|
||||
|
||||
- name: Upload images artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: boot-images-android12
|
||||
path: Image-android12*/*.img.gz
|
||||
|
||||
check-build-kernel:
|
||||
if: github.event_name == 'pull_request'
|
||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android12-5.10
|
||||
|
||||
62
.github/workflows/build-kernel-a13.yml
vendored
62
.github/workflows/build-kernel-a13.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Build Kernel - Android 13
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "ci"]
|
||||
branches: ["main", "ci", "checkci"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-a13.yml"
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
@@ -17,7 +17,7 @@ on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
build-kernel:
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -36,12 +36,24 @@ jobs:
|
||||
- version: "5.10"
|
||||
sub_level: 177
|
||||
os_patch_level: 2023-06
|
||||
- version: "5.10"
|
||||
sub_level: 186
|
||||
os_patch_level: 2023-08
|
||||
- version: "5.10"
|
||||
sub_level: 186
|
||||
os_patch_level: 2023-09
|
||||
- version: "5.10"
|
||||
sub_level: 189
|
||||
os_patch_level: 2023-11
|
||||
- version: "5.10"
|
||||
sub_level: 198
|
||||
os_patch_level: 2023-12
|
||||
- version: "5.15"
|
||||
sub_level: 41
|
||||
os_patch_level: 2022-11
|
||||
- version: "5.15"
|
||||
sub_level: 74
|
||||
os_patch_level: 2023-02
|
||||
os_patch_level: 2023-01
|
||||
- version: "5.15"
|
||||
sub_level: 78
|
||||
os_patch_level: 2023-03
|
||||
@@ -50,7 +62,16 @@ jobs:
|
||||
os_patch_level: 2023-05
|
||||
- version: "5.15"
|
||||
sub_level: 104
|
||||
os_patch_level: 2023-06
|
||||
os_patch_level: 2023-07
|
||||
- version: "5.15"
|
||||
sub_level: 119
|
||||
os_patch_level: 2023-09
|
||||
- version: "5.15"
|
||||
sub_level: 123
|
||||
os_patch_level: 2023-11
|
||||
- version: "5.15"
|
||||
sub_level: 137
|
||||
os_patch_level: 2023-12
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -59,13 +80,13 @@ jobs:
|
||||
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
|
||||
upload-artifacts:
|
||||
needs: build-kernel
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }}
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
@@ -73,9 +94,9 @@ jobs:
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
@@ -87,11 +108,11 @@ jobs:
|
||||
- name: Download prebuilt toolchain
|
||||
run: |
|
||||
AOSP_MIRROR=https://android.googlesource.com
|
||||
BRANCH=master-kernel-build-2022
|
||||
BRANCH=main-kernel-build-2023
|
||||
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
|
||||
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
|
||||
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
|
||||
pip3 install python-telegram-bot
|
||||
pip3 install telethon==1.31.1
|
||||
|
||||
- name: Set boot sign key
|
||||
env:
|
||||
@@ -101,8 +122,12 @@ jobs:
|
||||
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
fi
|
||||
|
||||
- name: Setup mutex for uploading
|
||||
uses: ben-z/gh-action-mutex@v1.0-alpha-7
|
||||
- name: Bot session cache
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: scripts/ksubot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Build boot images
|
||||
run: |
|
||||
@@ -116,30 +141,31 @@ jobs:
|
||||
echo "VERSION: $VERSION"
|
||||
cd -
|
||||
bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a13.sh
|
||||
|
||||
|
||||
- name: Display structure of boot files
|
||||
run: ls -R
|
||||
|
||||
- name: Upload images artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: boot-images-android13
|
||||
path: Image-android13*/*.img.gz
|
||||
|
||||
check-build-kernel:
|
||||
if: github.event_name == 'pull_request'
|
||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.10"
|
||||
sub_level: 177
|
||||
os_patch_level: 2023-06
|
||||
sub_level: 189
|
||||
os_patch_level: 2023-10
|
||||
- version: "5.15"
|
||||
sub_level: 104
|
||||
os_patch_level: 2023-06
|
||||
sub_level: 123
|
||||
os_patch_level: 2023-10
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android13-${{ matrix.version }}
|
||||
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
|
||||
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
|
||||
132
.github/workflows/build-kernel-a14.yml
vendored
Normal file
132
.github/workflows/build-kernel-a14.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
name: Build Kernel - Android 14
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "ci", "checkci"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-a14.yml"
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
- ".github/scripts/build_a13.sh"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-a14.yml"
|
||||
- ".github/workflows/gki-kernel.yml"
|
||||
- ".github/scripts/build-a13.sh"
|
||||
- "kernel/**"
|
||||
workflow_call:
|
||||
jobs:
|
||||
build-kernel:
|
||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.15"
|
||||
sub_level: 110
|
||||
os_patch_level: 2023-09
|
||||
- version: "5.15"
|
||||
sub_level: 131
|
||||
os_patch_level: 2023-11
|
||||
- version: "6.1"
|
||||
sub_level: 25
|
||||
os_patch_level: 2023-10
|
||||
- version: "6.1"
|
||||
sub_level: 43
|
||||
os_patch_level: 2023-11
|
||||
- version: "6.1"
|
||||
sub_level: 57
|
||||
os_patch_level: 2023-12
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
version: android14-${{ matrix.version }}
|
||||
version_name: android14-${{ matrix.version }}.${{ matrix.sub_level }}
|
||||
tag: android14-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
|
||||
upload-artifacts:
|
||||
needs: build-kernel
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }}
|
||||
env:
|
||||
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 }}
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
|
||||
- name: List artifacts
|
||||
run: |
|
||||
tree
|
||||
|
||||
- name: Download prebuilt toolchain
|
||||
run: |
|
||||
AOSP_MIRROR=https://android.googlesource.com
|
||||
BRANCH=main-kernel-build-2023
|
||||
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
|
||||
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
|
||||
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
|
||||
pip3 install telethon==1.31.1
|
||||
|
||||
- name: Set boot sign key
|
||||
env:
|
||||
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
|
||||
run: |
|
||||
if [ ! -z "$BOOT_SIGN_KEY" ]; then
|
||||
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
||||
fi
|
||||
|
||||
- name: Bot session cache
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: scripts/ksubot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Build boot images
|
||||
run: |
|
||||
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
|
||||
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
|
||||
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
|
||||
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
|
||||
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
export VERSION=$(($(git rev-list --count HEAD) + 10200))
|
||||
echo "VERSION: $VERSION"
|
||||
cd -
|
||||
bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a13.sh
|
||||
|
||||
- name: Display structure of boot files
|
||||
run: ls -R
|
||||
|
||||
- name: Upload images artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: boot-images-android14
|
||||
path: Image-android14*/*.img.gz
|
||||
|
||||
check-build-kernel:
|
||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- version: "5.15"
|
||||
sub_level: 110
|
||||
os_patch_level: 2023-09
|
||||
uses: ./.github/workflows/gki-kernel.yml
|
||||
with:
|
||||
version: android14-${{ matrix.version }}
|
||||
version_name: android14-${{ matrix.version }}.${{ matrix.sub_level }}
|
||||
tag: android14-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
||||
os_patch_level: ${{ matrix.os_patch_level }}
|
||||
patch_path: ${{ matrix.version }}
|
||||
25
.github/workflows/build-kernel-arcvm.yml
vendored
25
.github/workflows/build-kernel-arcvm.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Build Kernel - ChromeOS ARCVM
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
branches: ["main", "ci", "checkci"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-arcvm.yml"
|
||||
- "kernel/**"
|
||||
@@ -15,6 +15,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64]
|
||||
@@ -59,10 +60,9 @@ jobs:
|
||||
sudo ln -s --force /usr/bin/clang++-$LLVM_VERSION /usr/bin/clang++
|
||||
|
||||
- name: Checkout KernelSU
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup kernel source
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.10/*.patch
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.10/*.patch || echo "[-] No patch found"
|
||||
|
||||
echo "[+] Patch script/setlocalversion"
|
||||
sed -i 's/-dirty//g' $KERNEL_ROOT/scripts/setlocalversion
|
||||
@@ -98,6 +98,10 @@ jobs:
|
||||
run: |
|
||||
set -a && . build.config.gki.x86_64; set +a
|
||||
export DEFCONFIG=x86_64_arcvm_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
|
||||
@@ -107,16 +111,23 @@ jobs:
|
||||
echo "file_path=${PWD}/arch/x86/boot/bzImage" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload kernel-ARCVM-${{ matrix.arch }}-${{ matrix.version }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kernel-ARCVM-${{ matrix.arch }}-${{ matrix.version }}
|
||||
path: "${{ env.file_path }}"
|
||||
|
||||
- name: Bot session cache
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: scripts/ksubot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Post to Telegram
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
@@ -132,6 +143,6 @@ jobs:
|
||||
echo "[+] Image to upload"
|
||||
ls -l "${{ env.file_path }}.gz"
|
||||
if [ -n "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
pip3 install python-telegram-bot
|
||||
pip3 install telethon==1.31.1
|
||||
python3 "$GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py" "${{ env.file_path }}.gz"
|
||||
fi
|
||||
|
||||
149
.github/workflows/build-kernel-wsa.yml
vendored
149
.github/workflows/build-kernel-wsa.yml
vendored
@@ -1,151 +1,38 @@
|
||||
name: Build Kernel - WSA
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
branches: ["main", "ci", "checkci"]
|
||||
paths:
|
||||
- ".github/workflows/build-kernel-wsa.yml"
|
||||
- ".github/workflows/wsa-kernel.yml"
|
||||
- "kernel/**"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
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.78.1", "5.15.94.4"]
|
||||
include:
|
||||
- arch: x86_64
|
||||
file_name: "bzImage"
|
||||
- arch: arm64
|
||||
file_name: "Image"
|
||||
cross_compile: "aarch64-linux-gnu"
|
||||
- version: "5.15.78.1"
|
||||
arch: x86_64
|
||||
make_config: config-wsa-x64
|
||||
- version: "5.15.78.1"
|
||||
arch: arm64
|
||||
make_config: config-wsa-arm64
|
||||
- version: "5.15.94.4"
|
||||
arch: x86_64
|
||||
make_config: config-wsa-x64
|
||||
- version: "5.15.94.4"
|
||||
arch: arm64
|
||||
make_config: config-wsa-arm64
|
||||
- version: "5.15.78.1"
|
||||
device_code: latte-2
|
||||
kernel_version: "5.15"
|
||||
- version: "5.15.94.4"
|
||||
device_code: latte-2
|
||||
kernel_version: "5.15"
|
||||
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 }}
|
||||
|
||||
|
||||
name: Build WSA-Kernel-${{ matrix.version }}-${{ matrix.arch }}
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_HARDLINK: "true"
|
||||
|
||||
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
|
||||
export LLVM_VERSION=12
|
||||
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
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: KernelSU
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup kernel source
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: microsoft/WSA-Linux-Kernel
|
||||
ref: android-lts/${{ matrix.device_code }}/${{ matrix.version }}
|
||||
path: WSA-Linux-Kernel
|
||||
|
||||
- name: Setup Ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: WSA-Kernel-${{ matrix.version }}-${{ matrix.arch }}
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
max-size: 2G
|
||||
|
||||
- name: Setup KernelSU
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
echo "[+] KernelSU setup"
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel
|
||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
||||
echo "[+] Copy KernelSU driver to $KERNEL_ROOT/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $KERNEL_ROOT/drivers/kernelsu
|
||||
echo "[+] Add KernelSU driver to Makefile"
|
||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/${{ matrix.kernel_version }}/*.patch
|
||||
echo "[+] KernelSU setup done."
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
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: |
|
||||
cp configs/wsa/${{ matrix.make_config }} .config
|
||||
make olddefconfig
|
||||
make -j`nproc` LLVM=1 ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cross_compile }} ${{ matrix.file_name }} CCACHE="/usr/bin/ccache"
|
||||
echo "file_path=WSA-Linux-Kernel/arch/${{ matrix.arch }}/boot/${{ matrix.file_name }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload kernel-${{ matrix.arch }}-${{ matrix.version }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kernel-WSA-${{ matrix.arch }}-${{ matrix.version }}
|
||||
path: "${{ env.file_path }}"
|
||||
|
||||
- name: Post to Telegram
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_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 }}
|
||||
run: |
|
||||
TITLE=kernel-${{ matrix.arch }}-WSA-${{ matrix.version }}
|
||||
echo "[+] title: $TITLE"
|
||||
export TITLE
|
||||
export VERSION="${{ env.kernelsu_version }}"
|
||||
echo "[+] Compress images"
|
||||
gzip -n -f -9 "${{ env.file_path }}"
|
||||
echo "[+] Image to upload"
|
||||
ls -l "${{ env.file_path }}.gz"
|
||||
if [ -n "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
pip3 install python-telegram-bot
|
||||
python3 "$GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py" "${{ env.file_path }}.gz"
|
||||
fi
|
||||
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"
|
||||
4
.github/workflows/build-ksud.yml
vendored
4
.github/workflows/build-ksud.yml
vendored
@@ -2,13 +2,13 @@ name: Build KSUD
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "ci" ]
|
||||
paths:
|
||||
paths:
|
||||
- '.github/workflows/build-ksud.yml'
|
||||
- '.github/workflows/ksud.yml'
|
||||
- 'userspace/ksud/**'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
paths:
|
||||
- '.github/workflows/build-ksud.yml'
|
||||
- '.github/workflows/ksud.yml'
|
||||
- 'userspace/ksud/**'
|
||||
|
||||
49
.github/workflows/build-manager.yml
vendored
49
.github/workflows/build-manager.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -50,15 +50,17 @@ jobs:
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}' >> gradle.properties
|
||||
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}' >> gradle.properties
|
||||
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}' >> gradle.properties
|
||||
echo KEYSTORE_FILE='../key.jks' >> gradle.properties
|
||||
echo ${{ secrets.KEYSTORE }} | base64 --decode > key.jks
|
||||
{
|
||||
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@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
@@ -69,13 +71,13 @@ jobs:
|
||||
gradle-home-cache-cleanup: true
|
||||
|
||||
- name: Download arm64 ksud
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud-aarch64-linux-android
|
||||
path: .
|
||||
|
||||
- name: Download x86_64 ksud
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ksud-x86_64-linux-android
|
||||
path: .
|
||||
@@ -89,28 +91,39 @@ jobs:
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
echo 'org.gradle.parallel=true' >> gradle.properties
|
||||
echo 'org.gradle.vfs.watch=true' >> gradle.properties
|
||||
echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
|
||||
echo 'android.native.buildOutput=verbose' >> gradle.properties
|
||||
{
|
||||
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
|
||||
./gradlew clean assembleRelease
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: manager
|
||||
path: manager/app/build/outputs/apk/release/*.apk
|
||||
|
||||
- name: Setup mutex for uploading
|
||||
uses: ben-z/gh-action-mutex@v1.0-alpha-7
|
||||
- name: Upload mappings
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "mappings"
|
||||
path: "manager/app/build/outputs/mapping/release/"
|
||||
|
||||
- name: Bot session cache
|
||||
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: scripts/ksubot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Upload to telegram
|
||||
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
@@ -121,6 +134,6 @@ jobs:
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
export VERSION=$(git rev-list --count HEAD)
|
||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
||||
pip3 install python-telegram-bot
|
||||
pip3 install telethon==1.31.1
|
||||
python3 $GITHUB_WORKSPACE/scripts/ksubot.py $APK
|
||||
fi
|
||||
|
||||
24
.github/workflows/build-su.yml
vendored
24
.github/workflows/build-su.yml
vendored
@@ -1,21 +1,21 @@
|
||||
name: Build SU
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
branches: [ "main", "ci" ]
|
||||
paths:
|
||||
- '.github/workflows/build-su.yml'
|
||||
- 'userspace/su/**'
|
||||
- 'scripts/ksubot.py'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
paths:
|
||||
- 'userspace/su/**'
|
||||
jobs:
|
||||
build-su:
|
||||
name: Build userspace su
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup need_upload
|
||||
@@ -28,24 +28,26 @@ jobs:
|
||||
fi
|
||||
- uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r25b
|
||||
local-cache: true
|
||||
ndk-version: r25c
|
||||
- name: Build su
|
||||
working-directory: ./userspace/su
|
||||
run: ndk-build
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: su
|
||||
path: ./userspace/su/libs
|
||||
- name: Setup mutex for uploading
|
||||
uses: ben-z/gh-action-mutex@v1.0-alpha-7
|
||||
- name: Bot session cache
|
||||
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: scripts/ksubot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
- name: Upload to telegram
|
||||
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
|
||||
env:
|
||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
||||
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
@@ -55,7 +57,7 @@ jobs:
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
export VERSION=$(git rev-list --count HEAD)
|
||||
pip3 install python-telegram-bot
|
||||
pip3 install telethon==1.31.1
|
||||
mv ./userspace/su/libs/arm64-v8a/su su-arm64
|
||||
mv ./userspace/su/libs/x86_64/su su-x86_64
|
||||
python3 scripts/ksubot.py su-arm64 su-x86_64
|
||||
|
||||
4
.github/workflows/clippy.yml
vendored
4
.github/workflows/clippy.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
# cross build failed after Rust 1.68, see https://github.com/cross-rs/cross/issues/1222
|
||||
- run: rustup default 1.67.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
workspaces: userspace/ksud
|
||||
|
||||
- name: Install cross
|
||||
run: cargo install cross
|
||||
run: cargo install cross --locked
|
||||
|
||||
- name: Run clippy
|
||||
run: |
|
||||
|
||||
62
.github/workflows/deploy-website.yml
vendored
62
.github/workflows/deploy-website.yml
vendored
@@ -8,30 +8,60 @@ on:
|
||||
paths:
|
||||
- '.github/workflows/deploy-website.yml'
|
||||
- 'website/**'
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./website
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v3
|
||||
fetch-depth: 0 # Not needed if lastUpdated is not enabled
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
cache: yarn
|
||||
node-version: 18
|
||||
cache: yarn # or pnpm / yarn
|
||||
cache-dependency-path: website/yarn.lock
|
||||
- run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: yarn docs:build
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build with VitePress
|
||||
run: |
|
||||
yarn docs:build
|
||||
touch docs/.vitepress/dist/.nojekyll
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: website/docs/.vitepress/dist
|
||||
cname: kernelsu.org # if wanna deploy to custom domain
|
||||
path: website/docs/.vitepress/dist
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
61
.github/workflows/gki-kernel.yml
vendored
61
.github/workflows/gki-kernel.yml
vendored
@@ -54,8 +54,6 @@ on:
|
||||
required: false
|
||||
CHAT_ID:
|
||||
required: false
|
||||
CACHE_CHAT_ID:
|
||||
required: false
|
||||
BOT_TOKEN:
|
||||
required: false
|
||||
MESSAGE_THREAD_ID:
|
||||
@@ -70,7 +68,17 @@ jobs:
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_HARDLINK: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Maximize build space
|
||||
uses: easimon/maximize-build-space@master
|
||||
with:
|
||||
root-reserve-mb: 8192
|
||||
temp-reserve-mb: 2048
|
||||
remove-dotnet: 'true'
|
||||
remove-android: 'true'
|
||||
remove-haskell: 'true'
|
||||
remove-codeql: 'true'
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU
|
||||
fetch-depth: 0
|
||||
@@ -86,17 +94,22 @@ jobs:
|
||||
|
||||
- name: Setup kernel source
|
||||
run: |
|
||||
echo "Free space:"
|
||||
df -h
|
||||
cd $GITHUB_WORKSPACE
|
||||
git clone https://gerrit.googlesource.com/git-repo
|
||||
sudo apt-get install repo -y
|
||||
mkdir android-kernel && cd android-kernel
|
||||
../git-repo/repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }}
|
||||
repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }} --repo-rev=v2.16
|
||||
REMOTE_BRANCH=$(git ls-remote https://android.googlesource.com/kernel/common ${{ inputs.tag }})
|
||||
DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml
|
||||
if grep -q deprecated <<< $REMOTE_BRANCH; then
|
||||
echo "Found deprecated branch: ${{ inputs.tag }}"
|
||||
sed -i 's/"${{ inputs.tag }}"/"deprecated\/${{ inputs.tag }}"/g' .repo/manifests/default.xml
|
||||
cat .repo/manifests/default.xml
|
||||
sed -i 's/"${{ inputs.tag }}"/"deprecated\/${{ inputs.tag }}"/g' $DEFAULT_MANIFEST_PATH
|
||||
cat $DEFAULT_MANIFEST_PATH
|
||||
fi
|
||||
../git-repo/repo sync -j$(nproc --all)
|
||||
repo --version
|
||||
repo --trace sync -c -j$(nproc --all) --no-tags
|
||||
df -h
|
||||
|
||||
- name: Setup KernelSU
|
||||
env:
|
||||
@@ -113,14 +126,13 @@ jobs:
|
||||
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch
|
||||
echo "[+] Patch script/setlocalversion"
|
||||
sed -i 's/-dirty//g' $GKI_ROOT/common/scripts/setlocalversion
|
||||
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch || echo "[-] No patch found"
|
||||
|
||||
if [ "$IS_DEBUG_KERNEL" = "true" ]; then
|
||||
echo "[+] Enable debug features for kernel"
|
||||
echo "ccflags-y += -DCONFIG_KSU_DEBUG" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
|
||||
fi
|
||||
repo status
|
||||
echo "[+] KernelSU setup done."
|
||||
|
||||
- name: Symbol magic
|
||||
@@ -142,14 +154,35 @@ jobs:
|
||||
max-size: 2G
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Make working directory clean to avoid dirty
|
||||
working-directory: android-kernel
|
||||
run: |
|
||||
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
|
||||
git config --global user.email "bot@kernelsu.org"
|
||||
git config --global user.name "KernelSUBot"
|
||||
cd common/ && git add -A && git commit -a -m "Add KernelSU"
|
||||
repo status
|
||||
|
||||
- name: Build boot.img
|
||||
working-directory: android-kernel
|
||||
run: CCACHE="/usr/bin/ccache" LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
|
||||
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
|
||||
if [ -e build/build.sh ]; then
|
||||
LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh CC="/usr/bin/ccache clang"
|
||||
else
|
||||
tools/bazel run --disk_cache=/home/runner/.cache/bazel --config=fast --config=stamp --lto=thin //common:kernel_aarch64_dist -- --dist_dir=dist
|
||||
fi
|
||||
|
||||
- name: Prepare artifacts
|
||||
id: prepareArtifacts
|
||||
run: |
|
||||
OUTDIR=android-kernel/out/${{ inputs.version }}/dist
|
||||
if [ ! -e $OUTDIR ]; then
|
||||
OUTDIR=android-kernel/dist
|
||||
fi
|
||||
mkdir output
|
||||
cp $OUTDIR/Image ./output/
|
||||
cp $OUTDIR/Image.lz4 ./output/
|
||||
@@ -158,13 +191,13 @@ jobs:
|
||||
cp $OUTDIR/Image ./AnyKernel3/
|
||||
|
||||
- name: Upload Image and Image.gz
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Image-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
||||
path: ./output/*
|
||||
|
||||
- name: Upload AnyKernel3
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
|
||||
path: ./AnyKernel3/*
|
||||
|
||||
6
.github/workflows/ksud.yml
vendored
6
.github/workflows/ksud.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# cross build failed after Rust 1.68, see https://github.com/cross-rs/cross/issues/1222
|
||||
@@ -24,13 +24,13 @@ jobs:
|
||||
cache-targets: false
|
||||
|
||||
- name: Install cross
|
||||
run: cargo install cross
|
||||
run: cargo install cross --locked
|
||||
|
||||
- name: Build ksud
|
||||
run: cross build --target ${{ inputs.target }} --release --manifest-path ./userspace/ksud/Cargo.toml
|
||||
|
||||
- name: Upload ksud artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ksud-${{ inputs.target }}
|
||||
path: userspace/ksud/target/**/release/ksud
|
||||
|
||||
25
.github/workflows/release.yml
vendored
25
.github/workflows/release.yml
vendored
@@ -11,20 +11,31 @@ jobs:
|
||||
secrets: inherit
|
||||
build-a12-kernel:
|
||||
uses: ./.github/workflows/build-kernel-a12.yml
|
||||
secrets: inherit
|
||||
build-a13-kernel:
|
||||
uses: ./.github/workflows/build-kernel-a13.yml
|
||||
secrets: inherit
|
||||
build-a14-kernel:
|
||||
uses: ./.github/workflows/build-kernel-a14.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:
|
||||
needs:
|
||||
- build-manager
|
||||
- build-a12-kernel
|
||||
- build-a13-kernel
|
||||
- build-a14-kernel
|
||||
- build-wsa-kernel
|
||||
- build-arcvm-kernel
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Zip AnyKernel3
|
||||
run: |
|
||||
for dir in AnyKernel3-*; do
|
||||
@@ -43,6 +54,15 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -54,3 +74,4 @@ jobs:
|
||||
AnyKernel3-*.zip
|
||||
boot-images-*/Image-*/*.img.gz
|
||||
kernel-WSA*.zip
|
||||
kernel-ARCVM*.zip
|
||||
|
||||
6
.github/workflows/rustfmt.yml
vendored
6
.github/workflows/rustfmt.yml
vendored
@@ -21,13 +21,13 @@ jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- uses: LoliGothick/rustfmt-check@v0.3.1
|
||||
- uses: LoliGothick/rustfmt-check@master
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
options: --manifest-path userspace/ksud/Cargo.toml
|
||||
working-directory: userspace/ksud
|
||||
|
||||
2
.github/workflows/shellcheck.yml
vendored
2
.github/workflows/shellcheck.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
shellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@2.0.0
|
||||
|
||||
135
.github/workflows/wsa-kernel.yml
vendored
Normal file
135
.github/workflows/wsa-kernel.yml
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
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-20.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@v3
|
||||
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
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: KernelSU
|
||||
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.2
|
||||
with:
|
||||
key: WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }}
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
max-size: 2G
|
||||
|
||||
- name: Setup KernelSU
|
||||
working-directory: WSA-Linux-Kernel
|
||||
run: |
|
||||
echo "[+] KernelSU setup"
|
||||
KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel
|
||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
||||
echo "[+] Copy KernelSU driver to $KERNEL_ROOT/drivers"
|
||||
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $KERNEL_ROOT/drivers/kernelsu
|
||||
echo "[+] Add KernelSU driver to Makefile"
|
||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
||||
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
|
||||
echo "[+] Apply KernelSU patches"
|
||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.15/*.patch || echo "[-] No patch found"
|
||||
echo "[+] KernelSU setup done."
|
||||
cd $GITHUB_WORKSPACE/KernelSU
|
||||
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 }}"
|
||||
|
||||
- name: Bot session cache
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.ref_type == 'tag'
|
||||
id: bot_session_cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: scripts/ksubot.session
|
||||
key: ${{ runner.os }}-bot-session
|
||||
|
||||
- name: Post to Telegram
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.ref_type == 'tag'
|
||||
env:
|
||||
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 }}
|
||||
run: |
|
||||
TITLE=kernel-${{ inputs.arch }}-WSA-${{ inputs.version }}
|
||||
echo "[+] title: $TITLE"
|
||||
export TITLE
|
||||
export VERSION="${{ env.kernelsu_version }}"
|
||||
echo "[+] Compress images"
|
||||
gzip -n -f -9 "${{ env.file_path }}"
|
||||
echo "[+] Image to upload"
|
||||
ls -l "${{ env.file_path }}.gz"
|
||||
if [ -n "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
pip3 install telethon==1.31.1
|
||||
python3 "$GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py" "${{ env.file_path }}.gz"
|
||||
fi
|
||||
42
README.md
42
README.md
@@ -1,42 +0,0 @@
|
||||
**English** | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
A Kernel based root solution for Android devices.
|
||||
|
||||
## Features
|
||||
|
||||
1. Kernel-based `su` and root access management.
|
||||
2. Module system based on overlayfs.
|
||||
|
||||
## Compatibility State
|
||||
|
||||
KernelSU officially supports Android GKI 2.0 devices(with kernel 5.10+), old kernels(4.14+) is also compatible, but you need to build kernel yourself.
|
||||
|
||||
WSA and containter-based Android should also work with KernelSU integrated.
|
||||
|
||||
And the current supported ABIs are : `arm64-v8a` and `x86_64`
|
||||
|
||||
## Usage
|
||||
|
||||
[Installation](https://kernelsu.org/guide/installation.html)
|
||||
|
||||
## Build
|
||||
|
||||
[How to build?](https://kernelsu.org/guide/how-to-build.html)
|
||||
|
||||
### Discussion
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## License
|
||||
|
||||
- Files under `kernel` directory are [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- All other parts except `kernel` directory are [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
## Credits
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): the KernelSU idea.
|
||||
- [genuine](https://github.com/brevent/genuine/): apk v2 signature validation.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): some rootkit skills.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): the sepolicy implementation.
|
||||
42
README_CN.md
42
README_CN.md
@@ -1,42 +0,0 @@
|
||||
[English](README.md) | **简体中文** | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
一个 Android 上基于内核的 root 方案。
|
||||
|
||||
## 特性
|
||||
|
||||
- 基于内核的 su 和权限管理。
|
||||
- 基于 overlayfs 的模块系统。
|
||||
|
||||
## 兼容状态
|
||||
|
||||
KernelSU 官方支持 GKI 2.0 的设备(内核版本5.10以上);旧内核也是兼容的(最低4.14+),不过需要自己编译内核。
|
||||
|
||||
WSA 和运行在容器上的 Android 也可以与 KernelSU 一起工作。
|
||||
|
||||
目前支持架构 : `arm64-v8a` 和 `x86_64`
|
||||
|
||||
## 使用方法
|
||||
|
||||
[安装教程](https://kernelsu.org/zh_CN/guide/installation.html)
|
||||
|
||||
## 构建
|
||||
|
||||
[如何构建?](https://kernelsu.org/zh_CN/guide/how-to-build.html)
|
||||
|
||||
### 讨论
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## 许可证
|
||||
|
||||
- 目录 `kernel` 下所有文件为 [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- 除 `kernel` 目录的其他部分均为 [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的灵感。
|
||||
- [genuine](https://github.com/brevent/genuine/):apk v2 签名验证。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):sepolicy 的实现。
|
||||
42
README_JP.md
42
README_JP.md
@@ -1,42 +0,0 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | **日本語** | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
Android におけるカーネルベースの root ソリューションです。
|
||||
|
||||
## 特徴
|
||||
|
||||
1. カーネルベースの `su` と権限管理
|
||||
2. OverlayFS に基づくモジュールシステム
|
||||
|
||||
## 対応状況
|
||||
|
||||
KernelSU は GKI 2.0 デバイス(カーネルバージョン 5.10 以上)を公式にサポートしています。古いカーネル(4.14以上)とも互換性がありますが、自分でカーネルをビルドする必要があります。
|
||||
|
||||
WSA とコンテナ上で動作する Android でも KernelSU を統合して動かせます。
|
||||
|
||||
現在サポートしているアーキテクチャは `arm64-v8a` および `x86_64` です。
|
||||
|
||||
## 使用方法
|
||||
|
||||
[インストール方法はこちら](https://kernelsu.org/ja_JP/guide/installation.html)
|
||||
|
||||
## ビルド
|
||||
|
||||
[ビルド方法はこちら](https://kernelsu.org/guide/how-to-build.html)
|
||||
|
||||
### ディスカッション
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## ライセンス
|
||||
|
||||
- `kernel` ディレクトリの下にあるすべてのファイル: [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- `kernel` ディレクトリ以外のすべてのファイル: [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
## クレジット
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU のアイデア元
|
||||
- [genuine](https://github.com/brevent/genuine/):apk v2 の署名検証
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): rootkit のスキル
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):sepolicy の実装
|
||||
@@ -1,47 +0,0 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | **Portuguese-Brazil** | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
Uma solução raiz baseada em Kernel para dispositivos Android.
|
||||
|
||||
## Características
|
||||
|
||||
1. `su` baseado em kernel e gerenciamento de acesso root.
|
||||
|
||||
2. Sistema modular baseado em overlayfs.
|
||||
|
||||
## Estado de compatibilidade
|
||||
|
||||
O KernelSU suporta oficialmente dispositivos Android GKI 2.0 (com kernel 5.10+), kernels antigos (4.14+) também são compatíveis, mas você mesmo precisa construir o kernel.
|
||||
|
||||
O Android baseado em WSA e contêiner também deve funcionar com o KernelSU integrado.
|
||||
|
||||
E os ABIs atualmente suportados são: `arm64-v8a` e `x86_64`
|
||||
|
||||
## Uso
|
||||
|
||||
[Instalação](https://kernelsu.org/guide/installation.html)
|
||||
|
||||
## Construir
|
||||
|
||||
[Como construir?](https://kernelsu.org/guide/how-to-build.html)
|
||||
|
||||
### Discussão
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Licença
|
||||
|
||||
- Os arquivos no diretório `kernel` são [GPL-2](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](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
## Créditos
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): a ideia do KernelSU.
|
||||
|
||||
- [genuine](https://github.com/brevent/genuine/): validação de assinatura apk v2.
|
||||
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): algumas habilidades de rootkit.
|
||||
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): a implementação da sepolicy.
|
||||
42
README_TR.md
42
README_TR.md
@@ -1,42 +0,0 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Portuguese-Brazil](README_PT-BR.md) | **Türkçe**
|
||||
|
||||
# KernelSU
|
||||
|
||||
Android cihazlar için kernel tabanlı bir root çözümü.
|
||||
|
||||
## Özellikler
|
||||
|
||||
1. Kernel-tabanlı `su` ve root erişimi yönetimi.
|
||||
2. Overlayfs'ye dayalı modül sistemi.
|
||||
|
||||
## Uyumluluk Durumu
|
||||
|
||||
KernelSU resmi olarak Android GKI 2.0 cihazlarını ( 5.10+ kernelli) destekler, eski kernellerle de (4.14+) uyumludur, ancak kerneli kendinizin inşaa etmesi gerekir.
|
||||
|
||||
WSA ve konteyner tabanlı Android'in de, KernelSU ile entegre olarak da çalışması gerekmektedir.
|
||||
|
||||
Ve desteklenen mevcut ABI'ler : `arm64-v8a` ve `x86_64`
|
||||
|
||||
## Kullanım
|
||||
|
||||
[Yükleme](https://kernelsu.org/guide/installation.html)
|
||||
|
||||
## İnşaa
|
||||
|
||||
[Nasıl inşa edilir?](https://kernelsu.org/guide/how-to-build.html)
|
||||
|
||||
### Tartışma
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Lisans
|
||||
|
||||
- `kernel` klasöründeki dosyalar [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) lisansı altındadır.
|
||||
- `kernel` klasörü dışındaki bütün diğer bölümler [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html) lisansı altındadır.
|
||||
|
||||
## Krediler
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU fikri.
|
||||
- [genuine](https://github.com/brevent/genuine/): apk v2 imza doğrulama.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): bazı rootkit becerileri.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): sepolicy uygulaması.
|
||||
42
README_TW.md
42
README_TW.md
@@ -1,42 +0,0 @@
|
||||
[English](README.md) | [简体中文](README_CN.md) | **繁體中文** | [日本語](README_JP.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
一個基於核心的 Android 裝置 Root 解決方案
|
||||
|
||||
## 功能
|
||||
|
||||
- 基於核心的 Su 和 Root 存取權管理。
|
||||
- 基於 Overlayfs 的模組系統。
|
||||
|
||||
## 相容性狀態
|
||||
|
||||
KernelSU 官方支援 Android GKI 2.0 的裝置 (核心版本 5.10+);舊版核心同樣相容 (最低 4.14+),但需要自行編譯核心。
|
||||
|
||||
WSA 和執行在容器中的 Android 也可以與 KernelSU 一同運作。
|
||||
|
||||
目前支援架構:`arm64-v8a` 和 `x86_64`
|
||||
|
||||
## 使用方法
|
||||
|
||||
[安裝教學](https://kernelsu.org/zh_TW/guide/installation.html)
|
||||
|
||||
## 建置
|
||||
|
||||
[如何建置?](https://kernelsu.org/zh_TW/guide/how-to-build.html)
|
||||
|
||||
### 討論
|
||||
|
||||
- Telegram:[@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## 授權
|
||||
|
||||
- 目錄 `kernel` 下所有檔案為 [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
- 除 `kernel` 目錄的其他部分均為 [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
|
||||
## 致謝
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的靈感。
|
||||
- [genuine](https://github.com/brevent/genuine/):apk v2 簽章驗證。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk):sepolicy 實作。
|
||||
7
SECURITY.md
Normal file
7
SECURITY.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Reporting Security Issues
|
||||
|
||||
The KernelSU team and community take security bugs in KernelSU seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
||||
|
||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/tiann/KernelSU/security/advisories/new) tab, or you can mailto [weishu](mailto:twsxtd@gmail.com) directly.
|
||||
|
||||
The KernelSU team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
||||
57
docs/README.md
Normal file
57
docs/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
**English** | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
A Kernel-based root solution for Android devices.
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## Features
|
||||
|
||||
1. Kernel-based `su` and root access management.
|
||||
2. Module system based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage.
|
||||
|
||||
## Compatibility State
|
||||
|
||||
KernelSU officially supports Android GKI 2.0 devices (kernel 5.10+). Older kernels (4.14+) are also compatible, but the kernel will have to be built manually.
|
||||
|
||||
With this, WSA, ChromeOS, and container-based Android are all supported.
|
||||
|
||||
Currently, only `arm64-v8a` and `x86_64` are supported.
|
||||
|
||||
## Usage
|
||||
|
||||
- [Installation Instruction](https://kernelsu.org/guide/installation.html)
|
||||
- [How to build?](https://kernelsu.org/guide/how-to-build.html)
|
||||
- [Official Website](https://kernelsu.org/)
|
||||
|
||||
## Translation
|
||||
|
||||
To help translate KernelSU or improve existing translations, please use [Weblate](https://hosted.weblate.org/engage/kernelsu/). PR of Manager's translation is no longer accepted, because it will conflict with Weblate.
|
||||
|
||||
## Discussion
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Security
|
||||
|
||||
For information on reporting security vulnerabilities in KernelSU, see [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## License
|
||||
|
||||
- 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).
|
||||
|
||||
## 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.
|
||||
57
docs/README_CN.md
Normal file
57
docs/README_CN.md
Normal file
@@ -0,0 +1,57 @@
|
||||
[English](README.md) | [Español](README_ES.md) | **简体中文** | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
一个 Android 上基于内核的 root 方案。
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## 特性
|
||||
|
||||
- 基于内核的 `su` 和权限管理。
|
||||
- 基于 [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模块系统。
|
||||
- [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html): 把 Root 权限关进笼子里。
|
||||
|
||||
## 兼容状态
|
||||
|
||||
KernelSU 官方支持 GKI 2.0 的设备(内核版本5.10以上);旧内核也是兼容的(最低4.14+),不过需要自己编译内核。
|
||||
|
||||
WSA, ChromeOS 和运行在容器上的 Android 也可以与 KernelSU 一起工作。
|
||||
|
||||
目前支持架构 : `arm64-v8a` 和 `x86_64`。
|
||||
|
||||
## 使用方法
|
||||
|
||||
- [安装教程](https://kernelsu.org/zh_CN/guide/installation.html)
|
||||
- [如何构建?](https://kernelsu.org/zh_CN/guide/how-to-build.html)
|
||||
- [官方网站](https://kernelsu.org/zh_CN/)
|
||||
|
||||
## 参与翻译
|
||||
|
||||
要将 KernelSU 翻译成您的语言,或完善现有的翻译,请使用 [Weblate](https://hosted.weblate.org/engage/kernelsu/)。现已不再接受有关管理器翻译的PR,因为这会与Weblate冲突。
|
||||
|
||||
## 讨论
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## 安全性
|
||||
|
||||
有关报告 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-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 技巧。
|
||||
56
docs/README_ES.md
Normal file
56
docs/README_ES.md
Normal file
@@ -0,0 +1,56 @@
|
||||
[English](README.md) | **Español** | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Una solución root basada en el kernel para dispositivos Android.
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## Características
|
||||
|
||||
1. Binario `su` basado en el kernel y gestión de acceso root.
|
||||
2. Sistema de módulos basado en [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
|
||||
## Estado de compatibilidad
|
||||
|
||||
**KernelSU** soporta de forma oficial dispositivos Android con **GKI 2.0** (a partir de la versión **5.10** del kernel). Los kernels antiguos (a partir de la versión **4.14**) también son compatibles, pero necesitas compilarlos por tu cuenta.
|
||||
|
||||
Con esto, WSA, ChromeOS y Android basado en contenedores están todos compatibles.
|
||||
|
||||
Actualmente, solo se admiten las arquitecturas `arm64-v8a` y `x86_64`.
|
||||
|
||||
## Uso
|
||||
|
||||
- [¿Cómo instalarlo?](https://kernelsu.org/guide/installation.html)
|
||||
- [¿Cómo compilarlo?](https://kernelsu.org/guide/how-to-build.html)
|
||||
- [Site oficial](https://kernelsu.org/)
|
||||
|
||||
## Traducción
|
||||
|
||||
Para ayudar a traducir KernelSU o mejorar las traducciones existentes, utilice [Weblate](https://hosted.weblate.org/engage/kernelsu/). Ya no se aceptan PR de la traducción de Manager porque entrará en conflicto con Weblate.
|
||||
|
||||
## Discusión
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Seguridad
|
||||
|
||||
Para obtener información sobre cómo informar vulnerabilidades de seguridad en KernelSU, consulte [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## Licencia
|
||||
|
||||
- Los archivos bajo el directorio `kernel` están licenciados bajo [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- Todas las demás partes, a excepción del directorio `kernel`, están licenciados bajo [GPL-3-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
## Créditos
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): la idea de KernelSU.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): la poderosa herramienta root.
|
||||
- [genuine](https://github.com/brevent/genuine/): validación de firma apk v2.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): algunas habilidades de rootkit.
|
||||
53
docs/README_ID.md
Normal file
53
docs/README_ID.md
Normal file
@@ -0,0 +1,53 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | **Indonesia** | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Solusi root berbasis Kernel untuk perangkat Android.
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## Fitur
|
||||
|
||||
1. Manajemen akses root dan `su` berbasis kernel.
|
||||
2. Sistem modul berdasarkan [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [Profil Aplikasi](https://kernelsu.org/guide/app-profile.html): Kunci daya root di dalam sangkar.
|
||||
|
||||
## Status Kompatibilitas
|
||||
|
||||
KernelSU secara resmi mendukung perangkat Android GKI 2.0 (dengan kernel 5.10+), kernel lama (4.14+) juga kompatibel, tetapi Anda perlu membuat kernel sendiri.
|
||||
|
||||
WSA, ChromeOS, dan Android berbasis wadah juga dapat bekerja dengan KernelSU terintegrasi.
|
||||
|
||||
Dan ABI yang didukung saat ini adalah: `arm64-v8a` dan `x86_64`
|
||||
|
||||
## Penggunaan
|
||||
|
||||
- [Petunjuk Instalasi](https://kernelsu.org/id_ID/guide/installation.html)
|
||||
- [Bagaimana cara membuat?](https://kernelsu.org/id_ID/guide/how-to-build.html)
|
||||
- [Situs Web Resmi](https://kernelsu.org/id_ID/)
|
||||
|
||||
## Terjemahan
|
||||
|
||||
Untuk menerjemahkan KernelSU ke dalam bahasa Anda atau menyempurnakan terjemahan yang sudah ada, harap gunakan [Weblat](https://hosted.weblate.org/engage/kernelsu/).
|
||||
|
||||
## Diskusi
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Lisensi
|
||||
|
||||
- File di bawah direktori `kernel` adalah [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- Semua bagian lain kecuali direktori `kernel` adalah [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
## Kredit
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): ide KernelSU.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): alat root yang ampuh.
|
||||
- [genuine](https://github.com/brevent/genuine/): validasi tanda tangan apk v2.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): beberapa keterampilan rootkit.
|
||||
53
docs/README_IN.md
Normal file
53
docs/README_IN.md
Normal file
@@ -0,0 +1,53 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | **हिंदी**
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Android उपकरणों के लिए कर्नेल-आधारित रूट समाधान।
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## विशेषताएँ
|
||||
|
||||
1. कर्नेल-आधारित `su` और रूट एक्सेस प्रबंधन।
|
||||
2. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) पर आधारित मॉड्यूल प्रणाली।
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Root शक्ति को पिंजरे में बंद कर दो।
|
||||
|
||||
## अनुकूलता अवस्था
|
||||
|
||||
KernelSU आधिकारिक तौर पर Android GKI 2.0 डिवाइस (कर्नेल 5.10+) का समर्थन करता है। पुराने कर्नेल (4.14+) भी संगत हैं, लेकिन कर्नेल को मैन्युअल रूप से बनाना होगा।
|
||||
|
||||
इसके साथ, WSA, ChromeOS और कंटेनर-आधारित Android सभी समर्थित हैं।
|
||||
|
||||
वर्तमान में, केवल `arm64-v8a` और `x86_64` समर्थित हैं।
|
||||
|
||||
## प्रयोग
|
||||
|
||||
- [स्थापना निर्देश](https://kernelsu.org/guide/installation.html)
|
||||
- [कैसे बनाना है ?](https://kernelsu.org/guide/how-to-build.html)
|
||||
- [आधिकारिक वेबसाइट](https://kernelsu.org/)
|
||||
|
||||
## अनुवाद करना
|
||||
|
||||
KernelSU का अनुवाद करने या मौजूदा अनुवादों को बेहतर बनाने में सहायता के लिए, कृपया इसका उपयोग करें [Weblate](https://hosted.weblate.org/engage/kernelsu/).
|
||||
|
||||
## बहस
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## लाइसेंस
|
||||
|
||||
- `Kernel` निर्देशिका के अंतर्गत फ़ाइलें हैं [GPL-2-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): कुछ रूटकिट कौशल।
|
||||
53
docs/README_IW.md
Normal file
53
docs/README_IW.md
Normal file
@@ -0,0 +1,53 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | **עברית** | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
פתרון לניהול root מבוסס על Kernel עבור מכשירי Android.
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## תכונות
|
||||
|
||||
1. ניהול root ו־`su` מבוססים על Kernel.
|
||||
2. מערכת מודולים מבוססת [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [פרופיל אפליקציה](https://kernelsu.org/guide/app-profile.html): נעילת גישת root בכלוב.
|
||||
|
||||
## מצב תאימות
|
||||
|
||||
KernelSU תומך במכשירי Android GKI 2.0 (kernel 5.10+) באופן רשמי. לליבות ישנות (4.14+) יש גם תאימות, אך יידרש לבנות את הליבה באופן ידני.
|
||||
|
||||
באמצעות זה, תמיכה זמינה גם ל-WSA, ChromeOS ומכשירי Android המבוססים על מיכלים.
|
||||
|
||||
כרגע, רק `arm64-v8a` ו־`x86_64` נתמכים.
|
||||
|
||||
## שימוש
|
||||
|
||||
- [הוראות התקנה](https://kernelsu.org/guide/installation.html)
|
||||
- [איך לבנות?](https://kernelsu.org/guide/how-to-build.html)
|
||||
- [האתר רשמי](https://kernelsu.org/)
|
||||
|
||||
## תרגום
|
||||
|
||||
כדי לעזור בתרגום של KernelSU או לשפר תרגומים קיימים, יש להשתמש ב-[Weblate](https://hosted.weblate.org/engage/kernelsu/).
|
||||
|
||||
## דיון
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## רשיון
|
||||
|
||||
- קבצים תחת הספרייה `kernel` מוגנים על פי [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- כל החלקים האחרים, למעט הספרייה `kernel`, מוגנים על פי [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
## קרדיטים
|
||||
|
||||
- [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): כמה יכולות רוט.
|
||||
53
docs/README_JP.md
Normal file
53
docs/README_JP.md
Normal file
@@ -0,0 +1,53 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | **日本語** | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Android におけるカーネルベースの root ソリューションです。
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## 特徴
|
||||
|
||||
1. カーネルベースの `su` と権限管理。
|
||||
2. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) に基づくモジュールシステム。
|
||||
3. [アプリのプロファイル](https://kernelsu.org/guide/app-profile.html): root の権限をケージ内に閉じ込めます。
|
||||
|
||||
## 対応状況
|
||||
|
||||
KernelSU は GKI 2.0 デバイス(カーネルバージョン 5.10 以上)を公式にサポートしています。古いカーネル(4.14以上)とも互換性がありますが、自分でカーネルをビルドする必要があります。
|
||||
|
||||
WSA 、ChromeOS とコンテナ上で動作する Android でも KernelSU を統合して動かせます。
|
||||
|
||||
現在サポートしているアーキテクチャは `arm64-v8a` および `x86_64` です。
|
||||
|
||||
## 使用方法
|
||||
|
||||
- [インストール方法はこちら](https://kernelsu.org/ja_JP/guide/installation.html)
|
||||
- [ビルド方法はこちら](https://kernelsu.org/guide/how-to-build.html)
|
||||
- [公式サイト](https://kernelsu.org/ja_JP/)
|
||||
|
||||
## 翻訳
|
||||
|
||||
KernelSU をあなたの言語に翻訳するか、既存の翻訳を改善するには、[Weblate](https://hosted.weblate.org/engage/kernelsu/) を使用してください。Manager翻訳した PR は、Weblate と競合するため受け入れられなくなりました。
|
||||
|
||||
## ディスカッション
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## ライセンス
|
||||
|
||||
- `kernel` ディレクトリの下にあるすべてのファイル: [GPL-2-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 のスキル。
|
||||
55
docs/README_PL.md
Normal file
55
docs/README_PL.md
Normal file
@@ -0,0 +1,55 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | **Polski** | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Rozwiązanie root oparte na jądrze dla urządzeń z systemem Android.
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## Cechy
|
||||
|
||||
1. Oparte na jądrze `su` i zarządzanie dostępem roota.
|
||||
2. System modułów oparty na [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
|
||||
## Kompatybilność
|
||||
|
||||
KernelSU oficjalnie obsługuje urządzenia z Androidem GKI 2.0 (z jądrem 5.10+), starsze jądra (4.14+) są również kompatybilne, ale musisz sam skompilować jądro.
|
||||
|
||||
WSA i Android oparty na kontenerach również powinny działać ze zintegrowanym KernelSU.
|
||||
|
||||
Aktualnie obsługiwane ABI to : `arm64-v8a` i `x86_64`.
|
||||
|
||||
## Użycie
|
||||
|
||||
- [Instalacja](https://kernelsu.org/guide/installation.html)
|
||||
- [Jak skompilować?](https://kernelsu.org/guide/how-to-build.html)
|
||||
|
||||
## Tłumaczenie
|
||||
|
||||
Aby pomóc w tłumaczeniu KernelSU lub ulepszyć istniejące tłumaczenia, użyj [Weblate](https://hosted.weblate.org/engage/kernelsu/). PR tłumaczenia Managera nie jest już akceptowany, ponieważ będzie kolidował z Weblate.
|
||||
|
||||
## Dyskusja
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Bezpieczeństwo
|
||||
|
||||
Informacje na temat zgłaszania luk w zabezpieczeniach w KernelSU można znaleźć w pliku [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## Licencja
|
||||
|
||||
- Pliki w katalogu `kernel` są na licencji [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- Wszystkie inne części poza katalogiem `kernel` są na licencji [GPL-3-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
## Podziękowania
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): pomysłodawca KernelSU.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): implementacja sepolicy.
|
||||
- [genuine](https://github.com/brevent/genuine/): walidacja podpisu apk v2.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): cenna znajomość rootkitów.
|
||||
57
docs/README_PT-BR.md
Normal file
57
docs/README_PT-BR.md
Normal file
@@ -0,0 +1,57 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | **Português (Brasil)** | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Uma solução root baseada em kernel para dispositivos Android.
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## Características
|
||||
|
||||
1. `su` e gerenciamento de acesso root baseado em kernel.
|
||||
2. Sistema modular baseado em [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [Perfil do Aplicativo](https://kernelsu.org/pt_BR/guide/app-profile.html): Tranque o poder root em uma gaiola.
|
||||
|
||||
## Estado de compatibilidade
|
||||
|
||||
O KernelSU oferece suporte oficial a dispositivos Android GKI 2.0 (kernel 5.10+). Kernels mais antigos (4.14+) também são compatíveis, mas o kernel terá que ser construído manualmente.
|
||||
|
||||
Com isso, WSA, ChromeOS e Android baseado em contêiner são todos suportados.
|
||||
|
||||
Atualmente, apenas `arm64-v8a` e `x86_64` são suportados.
|
||||
|
||||
## Uso
|
||||
|
||||
- [Instalação](https://kernelsu.org/pt_BR/guide/installation.html)
|
||||
- [Como construir o KernelSU?](https://kernelsu.org/pt_BR/guide/how-to-build.html)
|
||||
- [Site oficial](https://kernelsu.org/pt_BR/)
|
||||
|
||||
## Tradução
|
||||
|
||||
Para contribuir com a tradução do KernelSU ou aprimorar traduções existentes, por favor, utilize o [Weblate](https://hosted.weblate.org/engage/kernelsu/). PR para a tradução do Gerenciador não são mais aceitas, pois podem entrar em conflito com o Weblate.
|
||||
|
||||
## Discussão
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Segurança
|
||||
|
||||
Para obter informações sobre como relatar vulnerabilidades de segurança do KernelSU, consulte [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## Licença
|
||||
|
||||
- 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).
|
||||
|
||||
## 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.
|
||||
49
docs/README_RU.md
Normal file
49
docs/README_RU.md
Normal file
@@ -0,0 +1,49 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | **Русский** | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Решение на основе ядра root для Android-устройств.
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## Особенности
|
||||
|
||||
1. Управление `su` и root-доступом на основе ядра.
|
||||
2. Система модулей на основе [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [Профиль приложений](https://kernelsu.org/ru_RU/guide/app-profile.html): Запри корневую силу в клетке.
|
||||
|
||||
## Совместимость
|
||||
|
||||
KernelSU официально поддерживает устройства на базе Android GKI 2.0 (с ядром 5.10+), старые ядра (4.14+) также совместимы, но для этого необходимо собрать ядро самостоятельно.
|
||||
|
||||
WSA и Android на основе контейнеров также должны работать с интегрированным KernelSU.
|
||||
|
||||
В настоящее время поддерживаются следующие ABI: `arm64-v8a` и `x86_64`.
|
||||
|
||||
## Использование
|
||||
|
||||
- [Установка](https://kernelsu.org/ru_RU/guide/installation.html)
|
||||
- [Как собрать?](https://kernelsu.org/ru_RU/guide/how-to-build.html)
|
||||
- [официальный сайт](https://kernelsu.org/ru_RU/)
|
||||
|
||||
## Обсуждение
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Лицензия
|
||||
|
||||
- Файлы в директории `kernel` [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- Все остальные части, кроме директории `kernel` [GPL-3-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): реализация sepolicy.
|
||||
- [genuine](https://github.com/brevent/genuine/): проверка подписи apk v2.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): некоторые навыки руткита.
|
||||
57
docs/README_TR.md
Normal file
57
docs/README_TR.md
Normal file
@@ -0,0 +1,57 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | **Türkçe** | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Android cihazlar için kernel tabanlı root çözümü.
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## Özellikler
|
||||
|
||||
1. Kernel-tabanlı `su` ve root erişimi yönetimi.
|
||||
2. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)'ye dayalı modül sistemi.
|
||||
3. [Uygulama profili](https://kernelsu.org/guide/app-profile.html): Root gücünü bir kafese kapatın.
|
||||
|
||||
## Uyumluluk Durumu
|
||||
|
||||
KernelSU resmi olarak Android GKI 2.0 cihazlarını (5.10+ kernelli) destekler, eski kernellerle de (4.14+) uyumludur, ancak kerneli kendinizin derlemeniz gerekir.
|
||||
|
||||
Bununla birlikte; WSA, ChromeOS ve konteyner tabanlı Android'in tamamı desteklenmektedir.
|
||||
|
||||
Şimdilik sadece `arm64-v8a` ve `x86_64` desteklenmektedir.
|
||||
|
||||
## Kullanım
|
||||
|
||||
- [Yükleme yönergeleri](https://kernelsu.org/guide/installation.html)
|
||||
- [Nasıl derlenir?](https://kernelsu.org/guide/how-to-build.html)
|
||||
- [Resmi WEB sitesi](https://kernelsu.org/)
|
||||
|
||||
## Çeviri
|
||||
|
||||
KernelSU'nun çevirisine veya mevcut çevirilerin iyileştirilmesine yardımcı olmak için lütfen [Weblate](https://hosted.weblate.org/engage/kernelsu/) kullanın. Yönetici uygulamasının PR ile çevirisi, Weblate ile çakışacağından artık kabul edilmeyecektir.
|
||||
|
||||
## Tartışma
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Güvenlik
|
||||
|
||||
KernelSU'daki güvenlik açıklarını bildirme hakkında bilgi için, bkz [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## Lisans
|
||||
|
||||
- `kernel` klasöründeki dosyalar [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) lisansı altındadır.
|
||||
- `kernel` klasörü dışındaki bütün diğer bölümler [GPL-3-veya-sonraki](https://www.gnu.org/licenses/gpl-3.0.html) lisansı altındadır.
|
||||
|
||||
## Krediler
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU fikri.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): güçlü root aracı.
|
||||
- [genuine](https://github.com/brevent/genuine/): apk v2 imza doğrulaması.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): bazı rootkit becerileri.
|
||||
47
docs/README_TW.md
Normal file
47
docs/README_TW.md
Normal file
@@ -0,0 +1,47 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | **繁體中文** | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
一個基於核心的 Android 裝置 Root 解決方案
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## 功能
|
||||
|
||||
- 基於核心的 `su` 和 Root 存取權管理。
|
||||
- 基於 [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模組系統。
|
||||
|
||||
## 相容性狀態
|
||||
|
||||
KernelSU 官方支援 Android GKI 2.0 的裝置 (核心版本 5.10+);舊版核心同樣相容 (最低 4.14+),但需要自行編譯核心。
|
||||
|
||||
WSA 和執行在容器中的 Android 也可以與 KernelSU 一同運作。
|
||||
|
||||
目前支援架構:`arm64-v8a` 和 `x86_64`。
|
||||
|
||||
## 使用方法
|
||||
|
||||
- [安裝教學](https://kernelsu.org/zh_TW/guide/installation.html)
|
||||
- [如何建置?](https://kernelsu.org/zh_TW/guide/how-to-build.html)
|
||||
|
||||
### 討論
|
||||
|
||||
- Telegram:[@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## 授權
|
||||
|
||||
- 目錄 `kernel` 下所有檔案為 [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)。
|
||||
- 除 `kernel` 目錄的其他部分均為 [GPL-3-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):sepolicy 實作。
|
||||
- [genuine](https://github.com/brevent/genuine/):apk v2 簽章驗證。
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。
|
||||
53
docs/README_VI.md
Normal file
53
docs/README_VI.md
Normal file
@@ -0,0 +1,53 @@
|
||||
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | **Tiếng Việt** | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md)
|
||||
|
||||
# KernelSU
|
||||
|
||||
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="logo">
|
||||
|
||||
Giải pháp root thông qua thay đổi trên Kernel hệ điều hành cho các thiết bị Android.
|
||||
|
||||
[](https://github.com/tiann/KernelSU/releases/latest)
|
||||
[](https://hosted.weblate.org/engage/kernelsu)
|
||||
[](https://t.me/KernelSU)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](/LICENSE)
|
||||
|
||||
## Tính năng
|
||||
|
||||
1. Hỗ trợ gói thực thi `su` và quản lý quyền root.
|
||||
2. Hệ thống mô-đun thông qua [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Hạn chế quyền root của ứng dụng.
|
||||
|
||||
## Tình trạng tương thích
|
||||
|
||||
KernelSU chính thức hỗ trợ các thiết bị Android với kernel GKI 2.0 (phiên bản kernel 5.10+), các phiên bản kernel cũ hơn (4.14+) cũng tương thích, nhưng bạn cần phải tự biên dịch.
|
||||
|
||||
WSA, ChromeOS và Android dựa trên container(container-based) cũng được hỗ trợ bởi KernelSU.
|
||||
|
||||
Hiên tại Giao diện nhị phân của ứng dụng (ABI) được hỗ trợ bao gồm `arm64-v8a` và `x86_64`.
|
||||
|
||||
## Sử dụng
|
||||
|
||||
- [Hướng dẫn cài đặt](https://kernelsu.org/vi_VN/guide/installation.html)
|
||||
- [Cách để build?](https://kernelsu.org/vi_VN/guide/how-to-build.html)
|
||||
- [Website Chính Thức](https://kernelsu.org/vi_VN/)
|
||||
|
||||
## Hỗ trợ dịch
|
||||
|
||||
Nếu bạn muốn hỗ trợ dịch KernelSU sang một ngôn ngữ khác hoặc cải thiện các bản dịch trước, vui lòng sử dụng [Weblate](https://hosted.weblate.org/engage/kernelsu/).
|
||||
|
||||
## Thảo luận
|
||||
|
||||
- Telegram: [@KernelSU](https://t.me/KernelSU)
|
||||
|
||||
## Giấy phép
|
||||
|
||||
- Tất cả các file trong thư mục `kernel` dùng giấy phép [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
||||
- Tất cả các thành phần khác ngoại trừ thư mục `kernel` dùng giấy phép [GPL-3-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
## Lời cảm ơn
|
||||
|
||||
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): ý tưởng cho KernelSU.
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk): công cụ root mạnh mẽ.
|
||||
- [genuine](https://github.com/brevent/genuine/): phương pháp xác thực apk v2.
|
||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): các phương pháp ẩn của rootkit.
|
||||
111
js/README.md
Normal file
111
js/README.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Library for KernelSU's module WebUI
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
yarn add kernelsu
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### exec
|
||||
|
||||
Spawns a **root** shell and runs a command within that shell, passing the `stdout` and `stderr` to a Promise when complete.
|
||||
|
||||
- `command` `<string>` The command to run, with space-separated arguments.
|
||||
- `options` `<Object>`
|
||||
- `cwd` - Current working directory of the child process
|
||||
- `env` - Environment key-value pairs
|
||||
|
||||
```javascript
|
||||
import { exec } from 'kernelsu';
|
||||
|
||||
const { errno, stdout, stderr } = await exec('ls -l', { cwd: '/tmp' });
|
||||
if (errno === 0) {
|
||||
// success
|
||||
console.log(stdout);
|
||||
}
|
||||
```
|
||||
|
||||
### spawn
|
||||
|
||||
Spawns a new process using the given `command` in **root** shell, with command-line arguments in `args`. If omitted, `args` defaults to an empty array.
|
||||
|
||||
Returns a `ChildProcess`, Instances of the ChildProcess represent spawned child processes.
|
||||
|
||||
- `command` `<string>` The command to run.
|
||||
- `args` `<string[]>` List of string arguments.
|
||||
- `options` `<Object>`:
|
||||
- `cwd` `<string>` - Current working directory of the child process
|
||||
- `env` `<Object>` - Environment key-value pairs
|
||||
|
||||
Example of running `ls -lh /data`, capturing `stdout`, `stderr`, and the exit code:
|
||||
|
||||
```javascript
|
||||
import { spawn } from 'kernelsu';
|
||||
|
||||
const ls = spawn('ls', ['-lh', '/data']);
|
||||
|
||||
ls.stdout.on('data', (data) => {
|
||||
console.log(`stdout: ${data}`);
|
||||
});
|
||||
|
||||
ls.stderr.on('data', (data) => {
|
||||
console.log(`stderr: ${data}`);
|
||||
});
|
||||
|
||||
ls.on('exit', (code) => {
|
||||
console.log(`child process exited with code ${code}`);
|
||||
});
|
||||
```
|
||||
|
||||
#### ChildProcess
|
||||
|
||||
##### Event 'exit'
|
||||
|
||||
- `code` `<number>` The exit code if the child exited on its own.
|
||||
|
||||
The `'exit'` event is emitted after the child process ends. If the process exited, `code` is the final exit code of the process, otherwise null
|
||||
|
||||
##### Event 'error'
|
||||
|
||||
- `err` `<Error>` The error.
|
||||
|
||||
The `'error'` event is emitted whenever:
|
||||
|
||||
- The process could not be spawned.
|
||||
- The process could not be killed.
|
||||
|
||||
##### `stdout`
|
||||
|
||||
A `Readable Stream` that represents the child process's `stdout`.
|
||||
|
||||
```javascript
|
||||
const subprocess = spawn('ls');
|
||||
|
||||
subprocess.stdout.on('data', (data) => {
|
||||
console.log(`Received chunk ${data}`);
|
||||
});
|
||||
```
|
||||
|
||||
#### `stderr`
|
||||
|
||||
A `Readable Stream` that represents the child process's `stderr`.
|
||||
|
||||
### fullScreen
|
||||
|
||||
Request the WebView enter/exit full screen.
|
||||
|
||||
```javascript
|
||||
import { fullScreen } from 'kernelsu';
|
||||
fullScreen(true);
|
||||
```
|
||||
|
||||
### toast
|
||||
|
||||
Show a toast message.
|
||||
|
||||
```javascript
|
||||
import { toast } from 'kernelsu';
|
||||
toast('Hello, world!');
|
||||
```
|
||||
115
js/index.js
Normal file
115
js/index.js
Normal file
@@ -0,0 +1,115 @@
|
||||
let callbackCounter = 0;
|
||||
function getUniqueCallbackName(prefix) {
|
||||
return `${prefix}_callback_${Date.now()}_${callbackCounter++}`;
|
||||
}
|
||||
|
||||
export function exec(command, options) {
|
||||
if (typeof options === "undefined") {
|
||||
options = {};
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Generate a unique callback function name
|
||||
const callbackFuncName = getUniqueCallbackName("exec");
|
||||
|
||||
// Define the success callback function
|
||||
window[callbackFuncName] = (errno, stdout, stderr) => {
|
||||
resolve({ errno, stdout, stderr });
|
||||
cleanup(callbackFuncName);
|
||||
};
|
||||
|
||||
function cleanup(successName) {
|
||||
delete window[successName];
|
||||
}
|
||||
|
||||
try {
|
||||
ksu.exec(command, JSON.stringify(options), callbackFuncName);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
cleanup(callbackFuncName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function Stdio() {
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
Stdio.prototype.on = function (event, listener) {
|
||||
if (!this.listeners[event]) {
|
||||
this.listeners[event] = [];
|
||||
}
|
||||
this.listeners[event].push(listener);
|
||||
};
|
||||
|
||||
Stdio.prototype.emit = function (event, ...args) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach((listener) => listener(...args));
|
||||
}
|
||||
};
|
||||
|
||||
function ChildProcess() {
|
||||
this.listeners = {};
|
||||
this.stdin = new Stdio();
|
||||
this.stdout = new Stdio();
|
||||
this.stderr = new Stdio();
|
||||
}
|
||||
|
||||
ChildProcess.prototype.on = function (event, listener) {
|
||||
if (!this.listeners[event]) {
|
||||
this.listeners[event] = [];
|
||||
}
|
||||
this.listeners[event].push(listener);
|
||||
};
|
||||
|
||||
ChildProcess.prototype.emit = function (event, ...args) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach((listener) => listener(...args));
|
||||
}
|
||||
};
|
||||
|
||||
export function spawn(command, args, options) {
|
||||
if (typeof args === "undefined") {
|
||||
args = [];
|
||||
} else if (typeof args === "object") {
|
||||
// allow for (command, options) signature
|
||||
options = args;
|
||||
}
|
||||
|
||||
if (typeof options === "undefined") {
|
||||
options = {};
|
||||
}
|
||||
|
||||
const child = new ChildProcess();
|
||||
const childCallbackName = getUniqueCallbackName("spawn");
|
||||
window[childCallbackName] = child;
|
||||
|
||||
function cleanup(name) {
|
||||
delete window[name];
|
||||
}
|
||||
|
||||
child.on("exit", code => {
|
||||
cleanup(childCallbackName);
|
||||
});
|
||||
|
||||
try {
|
||||
ksu.spawn(
|
||||
command,
|
||||
JSON.stringify(args),
|
||||
JSON.stringify(options),
|
||||
childCallbackName
|
||||
);
|
||||
} catch (error) {
|
||||
child.emit("error", error);
|
||||
cleanup(childCallbackName);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
export function fullScreen(isFullScreen) {
|
||||
ksu.fullScreen(isFullScreen);
|
||||
}
|
||||
|
||||
export function toast(message) {
|
||||
ksu.toast(message);
|
||||
}
|
||||
25
js/package.json
Normal file
25
js/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "kernelsu",
|
||||
"version": "1.0.6",
|
||||
"description": "Library for KernelSU's module WebUI",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tiann/KernelSU.git"
|
||||
},
|
||||
"keywords": [
|
||||
"su",
|
||||
"kernelsu",
|
||||
"module",
|
||||
"webui"
|
||||
],
|
||||
"author": "weishu",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tiann/KernelSU/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tiann/KernelSU#readme"
|
||||
}
|
||||
10
justfile
Normal file
10
justfile
Normal file
@@ -0,0 +1,10 @@
|
||||
alias bk := build_ksud
|
||||
alias bm := build_manager
|
||||
|
||||
build_ksud:
|
||||
cross build --target aarch64-linux-android --release --manifest-path ./userspace/ksud/Cargo.toml
|
||||
|
||||
build_manager: build_ksud
|
||||
cp userspace/ksud/target/aarch64-linux-android/release/ksud manager/app/src/main/jniLibs/arm64-v8a/libksud.so
|
||||
cd manager && ./gradlew aDebug
|
||||
|
||||
@@ -2,7 +2,7 @@ menu "KernelSU"
|
||||
|
||||
config KSU
|
||||
tristate "KernelSU function support"
|
||||
select OVERLAY_FS
|
||||
depends on OVERLAY_FS
|
||||
default y
|
||||
help
|
||||
Enable kernel-level root privileges on Android System.
|
||||
|
||||
@@ -15,18 +15,32 @@ obj-y += selinux/
|
||||
# .git is a text file while the module is imported by 'git submodule add'.
|
||||
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
|
||||
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD)
|
||||
ccflags-y += -DKSU_GIT_VERSION=$(KSU_GIT_VERSION)
|
||||
# ksu_version: major * 10000 + git version + 200 for historical reasons
|
||||
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 200))
|
||||
$(info -- KernelSU 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 a git submodule!")
|
||||
ccflags-y += -DKSU_VERSION=16
|
||||
endif
|
||||
|
||||
ifndef EXPECTED_SIZE
|
||||
EXPECTED_SIZE := 0x033b
|
||||
ifndef KSU_EXPECTED_SIZE
|
||||
KSU_EXPECTED_SIZE := 0x033b
|
||||
endif
|
||||
|
||||
ifndef EXPECTED_HASH
|
||||
EXPECTED_HASH := 0xb0b91415
|
||||
ifndef KSU_EXPECTED_HASH
|
||||
KSU_EXPECTED_HASH := c371061b19d8c7d7d6133c6a9bafe198fa944e50c1b31c9d8daa8d7f1fc2d2d6
|
||||
endif
|
||||
|
||||
ccflags-y += -DEXPECTED_SIZE=$(EXPECTED_SIZE)
|
||||
ccflags-y += -DEXPECTED_HASH=$(EXPECTED_HASH)
|
||||
ifdef KSU_MANAGER_PACKAGE
|
||||
ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
|
||||
$(info -- KernelSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
endif
|
||||
|
||||
$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
|
||||
$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
||||
|
||||
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
|
||||
ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\"
|
||||
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
|
||||
ccflags-y += -Wno-declaration-after-statement
|
||||
|
||||
@@ -97,7 +97,7 @@ void ksu_show_allow_list(void)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
pr_info("ksu_show_allow_list");
|
||||
pr_info("ksu_show_allow_list\n");
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
pr_info("uid :%d, allow: %d\n", p->profile.current_uid,
|
||||
@@ -349,10 +349,9 @@ void do_save_allow_list(struct work_struct *work)
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
loff_t off = 0;
|
||||
KWORKER_INSTALL_KEYRING();
|
||||
struct file *fp =
|
||||
filp_open(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT, 0644);
|
||||
|
||||
struct file *fp =
|
||||
ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
@@ -392,15 +391,14 @@ void do_load_allow_list(struct work_struct *work)
|
||||
struct file *fp = NULL;
|
||||
u32 magic;
|
||||
u32 version;
|
||||
KWORKER_INSTALL_KEYRING();
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
// always allow adb shell by default
|
||||
ksu_grant_root_to_shell();
|
||||
#endif
|
||||
// load allowlist now!
|
||||
fp = filp_open(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
|
||||
|
||||
// load allowlist now!
|
||||
fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
@@ -443,7 +441,7 @@ exit:
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data)
|
||||
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data)
|
||||
{
|
||||
struct perm_data *np = NULL;
|
||||
struct perm_data *n = NULL;
|
||||
@@ -453,13 +451,16 @@ void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data)
|
||||
mutex_lock(&allowlist_mutex);
|
||||
list_for_each_entry_safe (np, n, &allow_list, list) {
|
||||
uid_t uid = np->profile.current_uid;
|
||||
char *package = np->profile.key;
|
||||
// we use this uid for special cases, don't prune it!
|
||||
bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID;
|
||||
if (!is_preserved_uid && !is_uid_exist(uid, data)) {
|
||||
if (!is_preserved_uid && !is_uid_valid(uid, package, data)) {
|
||||
modified = true;
|
||||
pr_info("prune uid: %d\n", uid);
|
||||
pr_info("prune uid: %d, package: %s\n", uid, package);
|
||||
list_del(&np->list);
|
||||
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
|
||||
if (likely(uid <= BITMAP_UID_MAX)) {
|
||||
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
|
||||
}
|
||||
remove_uid_from_arr(uid);
|
||||
smp_mb();
|
||||
kfree(np);
|
||||
|
||||
@@ -17,7 +17,7 @@ bool __ksu_is_allow_uid(uid_t uid);
|
||||
|
||||
bool ksu_get_allow_list(int *array, int *length, bool allow);
|
||||
|
||||
void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data);
|
||||
void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, char *, void *), void *data);
|
||||
|
||||
bool ksu_get_app_profile(struct app_profile *);
|
||||
bool ksu_set_app_profile(struct app_profile *, bool persist);
|
||||
|
||||
@@ -1,12 +1,177 @@
|
||||
#include "linux/err.h"
|
||||
#include "linux/fs.h"
|
||||
#include "linux/gfp.h"
|
||||
#include "linux/kernel.h"
|
||||
#include "linux/moduleparam.h"
|
||||
|
||||
#include "apk_sign.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "linux/slab.h"
|
||||
#include "linux/version.h"
|
||||
|
||||
static __always_inline int
|
||||
check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
#include "crypto/sha2.h"
|
||||
#else
|
||||
#include "crypto/sha.h"
|
||||
#endif
|
||||
|
||||
struct sdesc {
|
||||
struct shash_desc shash;
|
||||
char ctx[];
|
||||
};
|
||||
|
||||
static struct sdesc *init_sdesc(struct crypto_shash *alg)
|
||||
{
|
||||
struct sdesc *sdesc;
|
||||
int size;
|
||||
|
||||
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
|
||||
sdesc = kmalloc(size, GFP_KERNEL);
|
||||
if (!sdesc)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
sdesc->shash.tfm = alg;
|
||||
return sdesc;
|
||||
}
|
||||
|
||||
static int calc_hash(struct crypto_shash *alg, const unsigned char *data,
|
||||
unsigned int datalen, unsigned char *digest)
|
||||
{
|
||||
struct sdesc *sdesc;
|
||||
int ret;
|
||||
|
||||
sdesc = init_sdesc(alg);
|
||||
if (IS_ERR(sdesc)) {
|
||||
pr_info("can't alloc sdesc\n");
|
||||
return PTR_ERR(sdesc);
|
||||
}
|
||||
|
||||
ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
|
||||
kfree(sdesc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ksu_sha256(const unsigned char *data, unsigned int datalen,
|
||||
unsigned char *digest)
|
||||
{
|
||||
struct crypto_shash *alg;
|
||||
char *hash_alg_name = "sha256";
|
||||
int ret;
|
||||
|
||||
alg = crypto_alloc_shash(hash_alg_name, 0, 0);
|
||||
if (IS_ERR(alg)) {
|
||||
pr_info("can't alloc alg %s\n", hash_alg_name);
|
||||
return PTR_ERR(alg);
|
||||
}
|
||||
ret = calc_hash(alg, data, datalen, digest);
|
||||
crypto_free_shash(alg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset,
|
||||
unsigned expected_size, const char *expected_sha256)
|
||||
{
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length
|
||||
|
||||
*offset += 0x4 * 3;
|
||||
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length
|
||||
|
||||
*pos += *size4;
|
||||
*offset += 0x4 + *size4;
|
||||
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length
|
||||
*offset += 0x4 * 2;
|
||||
|
||||
if (*size4 == expected_size) {
|
||||
*offset += *size4;
|
||||
|
||||
#define CERT_MAX_LENGTH 1024
|
||||
char cert[CERT_MAX_LENGTH];
|
||||
if (*size4 > CERT_MAX_LENGTH) {
|
||||
pr_info("cert length overlimit\n");
|
||||
return false;
|
||||
}
|
||||
ksu_kernel_read_compat(fp, cert, *size4, pos);
|
||||
unsigned char digest[SHA256_DIGEST_SIZE];
|
||||
if (IS_ERR(ksu_sha256(cert, *size4, digest))) {
|
||||
pr_info("sha256 error\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
|
||||
hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
|
||||
|
||||
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
|
||||
pr_info("sha256: %s, expected: %s\n", hash_str,
|
||||
expected_sha256);
|
||||
if (strcmp(expected_sha256, hash_str) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct zip_entry_header {
|
||||
uint32_t signature;
|
||||
uint16_t version;
|
||||
uint16_t flags;
|
||||
uint16_t compression;
|
||||
uint16_t mod_time;
|
||||
uint16_t mod_date;
|
||||
uint32_t crc32;
|
||||
uint32_t compressed_size;
|
||||
uint32_t uncompressed_size;
|
||||
uint16_t file_name_length;
|
||||
uint16_t extra_field_length;
|
||||
} __attribute__((packed));
|
||||
|
||||
// This is a necessary but not sufficient condition, but it is enough for us
|
||||
static bool has_v1_signature_file(struct file *fp)
|
||||
{
|
||||
struct zip_entry_header header;
|
||||
const char MANIFEST[] = "META-INF/MANIFEST.MF";
|
||||
|
||||
loff_t pos = 0;
|
||||
|
||||
while (ksu_kernel_read_compat(fp, &header,
|
||||
sizeof(struct zip_entry_header), &pos) ==
|
||||
sizeof(struct zip_entry_header)) {
|
||||
if (header.signature != 0x04034b50) {
|
||||
// ZIP magic: 'PK'
|
||||
return false;
|
||||
}
|
||||
// Read the entry file name
|
||||
if (header.file_name_length == sizeof(MANIFEST) - 1) {
|
||||
char fileName[sizeof(MANIFEST)];
|
||||
ksu_kernel_read_compat(fp, fileName,
|
||||
header.file_name_length, &pos);
|
||||
fileName[header.file_name_length] = '\0';
|
||||
|
||||
// Check if the entry matches META-INF/MANIFEST.MF
|
||||
if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) ==
|
||||
0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Skip the entry file name
|
||||
pos += header.file_name_length;
|
||||
}
|
||||
|
||||
// Skip to the next entry
|
||||
pos += header.extra_field_length + header.compressed_size;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static __always_inline bool check_v2_signature(char *path,
|
||||
unsigned expected_size,
|
||||
const char *expected_sha256)
|
||||
{
|
||||
unsigned char buffer[0x11] = { 0 };
|
||||
u32 size4;
|
||||
@@ -14,18 +179,21 @@ check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
|
||||
|
||||
loff_t pos;
|
||||
|
||||
int sign = -1;
|
||||
bool v2_signing_valid = false;
|
||||
int v2_signing_blocks = 0;
|
||||
bool v3_signing_exist = false;
|
||||
bool v3_1_signing_exist = false;
|
||||
|
||||
int i;
|
||||
struct file *fp = filp_open(path, O_RDONLY, 0);
|
||||
struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("open %s error.", path);
|
||||
pr_err("open %s error.\n", path);
|
||||
return PTR_ERR(fp);
|
||||
}
|
||||
|
||||
// disable inotify for this file
|
||||
fp->f_mode |= FMODE_NONOTIFY;
|
||||
|
||||
sign = 1;
|
||||
// https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
|
||||
for (i = 0;; ++i) {
|
||||
unsigned short n;
|
||||
@@ -64,73 +232,58 @@ check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
|
||||
for (;;) {
|
||||
uint32_t id;
|
||||
uint32_t offset;
|
||||
ksu_kernel_read_compat(fp, &size8, 0x8, &pos); // sequence length
|
||||
ksu_kernel_read_compat(fp, &size8, 0x8,
|
||||
&pos); // sequence length
|
||||
if (size8 == size_of_block) {
|
||||
break;
|
||||
}
|
||||
ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id
|
||||
offset = 4;
|
||||
pr_info("id: 0x%08x\n", id);
|
||||
if ((id ^ 0xdeadbeefu) == 0xafa439f5u ||
|
||||
(id ^ 0xdeadbeefu) == 0x2efed62f) {
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // signer-sequence length
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4, &pos); // signer length
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // signed data length
|
||||
offset += 0x4 * 3;
|
||||
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // digests-sequence length
|
||||
pos += size4;
|
||||
offset += 0x4 + size4;
|
||||
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // certificates length
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4,
|
||||
&pos); // certificate length
|
||||
offset += 0x4 * 2;
|
||||
#if 0
|
||||
int hash = 1;
|
||||
signed char c;
|
||||
for (i = 0; i < size4; ++i) {
|
||||
ksu_kernel_read_compat(fp, &c, 0x1, &pos);
|
||||
hash = 31 * hash + c;
|
||||
}
|
||||
offset += size4;
|
||||
pr_info(" size: 0x%04x, hash: 0x%08x\n", size4, ((unsigned) hash) ^ 0x14131211u);
|
||||
#else
|
||||
if (size4 == expected_size) {
|
||||
int hash = 1;
|
||||
signed char c;
|
||||
for (i = 0; i < size4; ++i) {
|
||||
ksu_kernel_read_compat(fp, &c, 0x1, &pos);
|
||||
hash = 31 * hash + c;
|
||||
}
|
||||
offset += size4;
|
||||
if ((((unsigned)hash) ^ 0x14131211u) ==
|
||||
expected_hash) {
|
||||
sign = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// don't try again.
|
||||
break;
|
||||
#endif
|
||||
if (id == 0x7109871au) {
|
||||
v2_signing_blocks++;
|
||||
v2_signing_valid =
|
||||
check_block(fp, &size4, &pos, &offset,
|
||||
expected_size, expected_sha256);
|
||||
} else if (id == 0xf05368c0u) {
|
||||
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#73
|
||||
v3_signing_exist = true;
|
||||
} else if (id == 0x1b93ad61u) {
|
||||
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#74
|
||||
v3_1_signing_exist = true;
|
||||
}
|
||||
pos += (size8 - offset);
|
||||
}
|
||||
|
||||
if (v2_signing_blocks != 1) {
|
||||
pr_err("Unexpected v2 signature count: %d\n",
|
||||
v2_signing_blocks);
|
||||
v2_signing_valid = false;
|
||||
}
|
||||
|
||||
if (v2_signing_valid) {
|
||||
int has_v1_signing = has_v1_signature_file(fp);
|
||||
if (has_v1_signing) {
|
||||
pr_err("Unexpected v1 signature scheme found!\n");
|
||||
filp_close(fp, 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
clean:
|
||||
filp_close(fp, 0);
|
||||
|
||||
return sign;
|
||||
if (v3_signing_exist || v3_1_signing_exist) {
|
||||
pr_err("Unexpected v3 signature scheme found!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return v2_signing_valid;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
|
||||
unsigned ksu_expected_size = EXPECTED_SIZE;
|
||||
unsigned ksu_expected_hash = EXPECTED_HASH;
|
||||
const char *ksu_expected_hash = EXPECTED_HASH;
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
@@ -138,15 +291,16 @@ static int set_expected_size(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
int rv = param_set_uint(val, kp);
|
||||
ksu_invalidate_manager_uid();
|
||||
pr_info("ksu_expected_size set to %x", ksu_expected_size);
|
||||
pr_info("ksu_expected_size set to %x\n", ksu_expected_size);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int set_expected_hash(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
int rv = param_set_uint(val, kp);
|
||||
pr_info("set_expected_hash: %s\n", val);
|
||||
int rv = param_set_charp(val, kp);
|
||||
ksu_invalidate_manager_uid();
|
||||
pr_info("ksu_expected_hash set to %x", ksu_expected_hash);
|
||||
pr_info("ksu_expected_hash set to %s\n", ksu_expected_hash);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -157,7 +311,8 @@ static struct kernel_param_ops expected_size_ops = {
|
||||
|
||||
static struct kernel_param_ops expected_hash_ops = {
|
||||
.set = set_expected_hash,
|
||||
.get = param_get_uint,
|
||||
.get = param_get_charp,
|
||||
.free = param_free_charp,
|
||||
};
|
||||
|
||||
module_param_cb(ksu_expected_size, &expected_size_ops, &ksu_expected_size,
|
||||
@@ -165,14 +320,14 @@ module_param_cb(ksu_expected_size, &expected_size_ops, &ksu_expected_size,
|
||||
module_param_cb(ksu_expected_hash, &expected_hash_ops, &ksu_expected_hash,
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
int is_manager_apk(char *path)
|
||||
bool is_manager_apk(char *path)
|
||||
{
|
||||
return check_v2_signature(path, ksu_expected_size, ksu_expected_hash);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int is_manager_apk(char *path)
|
||||
bool is_manager_apk(char *path)
|
||||
{
|
||||
return check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#ifndef __KSU_H_APK_V2_SIGN
|
||||
#define __KSU_H_APK_V2_SIGN
|
||||
|
||||
// return 0 if signature match
|
||||
int is_manager_apk(char *path);
|
||||
#include "linux/types.h"
|
||||
|
||||
bool is_manager_apk(char *path);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#define __PT_PARM1_REG regs[0]
|
||||
#define __PT_PARM2_REG regs[1]
|
||||
#define __PT_PARM3_REG regs[2]
|
||||
#define __PT_PARM4_REG regs[3]
|
||||
#define __PT_SYSCALL_PARM4_REG regs[3]
|
||||
#define __PT_CCALL_PARM4_REG regs[3]
|
||||
#define __PT_PARM5_REG regs[4]
|
||||
#define __PT_PARM6_REG regs[5]
|
||||
#define __PT_RET_REG regs[30]
|
||||
@@ -29,8 +30,8 @@
|
||||
#define __PT_PARM2_REG si
|
||||
#define __PT_PARM3_REG dx
|
||||
/* syscall uses r10 for PARM4 */
|
||||
#define __PT_PARM4_REG r10
|
||||
// #define __PT_PARM4_REG cx
|
||||
#define __PT_SYSCALL_PARM4_REG r10
|
||||
#define __PT_CCALL_PARM4_REG cx
|
||||
#define __PT_PARM5_REG r8
|
||||
#define __PT_PARM6_REG r9
|
||||
#define __PT_RET_REG sp
|
||||
@@ -56,7 +57,8 @@
|
||||
#define PT_REGS_PARM1(x) (__PT_REGS_CAST(x)->__PT_PARM1_REG)
|
||||
#define PT_REGS_PARM2(x) (__PT_REGS_CAST(x)->__PT_PARM2_REG)
|
||||
#define PT_REGS_PARM3(x) (__PT_REGS_CAST(x)->__PT_PARM3_REG)
|
||||
#define PT_REGS_PARM4(x) (__PT_REGS_CAST(x)->__PT_PARM4_REG)
|
||||
#define PT_REGS_SYSCALL_PARM4(x) (__PT_REGS_CAST(x)->__PT_SYSCALL_PARM4_REG)
|
||||
#define PT_REGS_CCALL_PARM4(x) (__PT_REGS_CAST(x)->__PT_CCALL_PARM4_REG)
|
||||
#define PT_REGS_PARM5(x) (__PT_REGS_CAST(x)->__PT_PARM5_REG)
|
||||
#define PT_REGS_PARM6(x) (__PT_REGS_CAST(x)->__PT_PARM6_REG)
|
||||
#define PT_REGS_RET(x) (__PT_REGS_CAST(x)->__PT_RET_REG)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "linux/dcache.h"
|
||||
#include "linux/err.h"
|
||||
#include "linux/init.h"
|
||||
#include "linux/init_task.h"
|
||||
#include "linux/kernel.h"
|
||||
#include "linux/kprobes.h"
|
||||
#include "linux/lsm_hooks.h"
|
||||
@@ -29,6 +30,8 @@
|
||||
#include "uid_observer.h"
|
||||
#include "kernel_compat.h"
|
||||
|
||||
static bool ksu_module_mounted = false;
|
||||
|
||||
extern int handle_sepolicy(unsigned long arg3, void __user *arg4);
|
||||
|
||||
static inline bool is_allow_su()
|
||||
@@ -122,8 +125,12 @@ void escape_to_root(void)
|
||||
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
|
||||
sizeof(kernel_cap_t));
|
||||
|
||||
// capabilities
|
||||
memcpy(&cred->cap_effective, &profile->capabilities.effective,
|
||||
// setup capabilities
|
||||
// we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process
|
||||
// we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec!
|
||||
u64 cap_for_ksud =
|
||||
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));
|
||||
@@ -184,7 +191,7 @@ int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry)
|
||||
if (strcmp(buf, "/system/packages.list")) {
|
||||
return 0;
|
||||
}
|
||||
pr_info("renameat: %s -> %s, new path: %s", old_dentry->d_iname,
|
||||
pr_info("renameat: %s -> %s, new path: %s\n", old_dentry->d_iname,
|
||||
new_dentry->d_iname, buf);
|
||||
|
||||
update_uid();
|
||||
@@ -232,9 +239,12 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
// someone wants to be root manager, just check it!
|
||||
// arg3 should be `/data/user/<userId>/<manager_package_name>`
|
||||
char param[128];
|
||||
if (copy_from_user(param, arg3, sizeof(param))) {
|
||||
if (ksu_strncpy_from_user_nofault(param, arg3, sizeof(param)) ==
|
||||
-EFAULT) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_err("become_manager: copy param err\n");
|
||||
return 0;
|
||||
#endif
|
||||
goto block;
|
||||
}
|
||||
|
||||
// for user 0, it is /data/data
|
||||
@@ -252,7 +262,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
|
||||
if (startswith(param, (char *)prefix) != 0) {
|
||||
pr_info("become_manager: invalid param: %s\n", param);
|
||||
return 0;
|
||||
goto block;
|
||||
}
|
||||
|
||||
// stat the param, app must have permission to do this
|
||||
@@ -260,12 +270,13 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
struct path path;
|
||||
if (kern_path(param, LOOKUP_DIRECTORY, &path)) {
|
||||
pr_err("become_manager: kern_path err\n");
|
||||
return 0;
|
||||
goto block;
|
||||
}
|
||||
if (path.dentry->d_inode->i_uid.val != current_uid().val) {
|
||||
uid_t inode_uid = path.dentry->d_inode->i_uid.val;
|
||||
path_put(&path);
|
||||
if (inode_uid != current_uid().val) {
|
||||
pr_err("become_manager: path uid != current uid\n");
|
||||
path_put(&path);
|
||||
return 0;
|
||||
goto block;
|
||||
}
|
||||
char *pkg = param + strlen(prefix);
|
||||
pr_info("become_manager: param pkg: %s\n", pkg);
|
||||
@@ -275,14 +286,16 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("become_manager: prctl reply error\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
path_put(&path);
|
||||
block:
|
||||
last_failed_uid = current_uid().val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_GRANT_ROOT) {
|
||||
if (is_allow_su()) {
|
||||
pr_info("allow root for: %d\n", current_uid());
|
||||
pr_info("allow root for: %d\n", current_uid().val);
|
||||
escape_to_root();
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("grant_root: prctl reply error\n");
|
||||
@@ -296,7 +309,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
if (is_manager() || 0 == current_uid().val) {
|
||||
u32 version = KERNEL_SU_VERSION;
|
||||
if (copy_to_user(arg3, &version, sizeof(version))) {
|
||||
pr_err("prctl reply error, cmd: %d\n", arg2);
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -311,7 +324,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
static bool post_fs_data_lock = false;
|
||||
if (!post_fs_data_lock) {
|
||||
post_fs_data_lock = true;
|
||||
pr_info("post-fs-data triggered");
|
||||
pr_info("post-fs-data triggered\n");
|
||||
on_post_fs_data();
|
||||
}
|
||||
break;
|
||||
@@ -320,10 +333,15 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
static bool boot_complete_lock = false;
|
||||
if (!boot_complete_lock) {
|
||||
boot_complete_lock = true;
|
||||
pr_info("boot_complete triggered");
|
||||
pr_info("boot_complete triggered\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EVENT_MODULE_MOUNTED: {
|
||||
ksu_module_mounted = true;
|
||||
pr_info("module mounted!\n");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -370,7 +388,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
sizeof(u32) * array_length)) {
|
||||
if (copy_to_user(result, &reply_ok,
|
||||
sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %d\n",
|
||||
pr_err("prctl reply error, cmd: %lu\n",
|
||||
arg2);
|
||||
}
|
||||
} else {
|
||||
@@ -390,16 +408,16 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
} else if (arg2 == CMD_UID_SHOULD_UMOUNT) {
|
||||
allow = ksu_uid_should_umount(target_uid);
|
||||
} else {
|
||||
pr_err("unknown cmd: %d\n", arg2);
|
||||
pr_err("unknown cmd: %lu\n", arg2);
|
||||
}
|
||||
if (!copy_to_user(arg4, &allow, sizeof(allow))) {
|
||||
if (copy_to_user(result, &reply_ok,
|
||||
sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %d\n",
|
||||
pr_err("prctl reply error, cmd: %lu\n",
|
||||
arg2);
|
||||
}
|
||||
} else {
|
||||
pr_err("prctl copy err, cmd: %d\n", arg2);
|
||||
pr_err("prctl copy err, cmd: %lu\n", arg2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -426,7 +444,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
return 0;
|
||||
}
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %d\n", arg2);
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -442,7 +460,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
// todo: validate the params
|
||||
if (ksu_set_app_profile(&profile, true)) {
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %d\n", arg2);
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -480,7 +498,19 @@ static bool should_umount(struct path *path)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void try_umount(const char *mnt)
|
||||
static void ksu_umount_mnt(struct path *path, int flags)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
|
||||
int err = path_umount(path, flags);
|
||||
if (err) {
|
||||
pr_info("umount %s failed: %d\n", path->dentry->d_iname, err);
|
||||
}
|
||||
#else
|
||||
// TODO: umount for non GKI kernel
|
||||
#endif
|
||||
}
|
||||
|
||||
static void try_umount(const char *mnt, bool check_mnt, int flags)
|
||||
{
|
||||
struct path path;
|
||||
int err = kern_path(mnt, 0, &path);
|
||||
@@ -488,21 +518,26 @@ static void try_umount(const char *mnt)
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interest in some specific mounts
|
||||
if (!should_umount(&path)) {
|
||||
if (path.dentry != path.mnt->mnt_root) {
|
||||
// it is not root mountpoint, maybe umounted by others already.
|
||||
return;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
|
||||
err = path_umount(&path, 0);
|
||||
if (err) {
|
||||
pr_info("umount %s failed: %d\n", mnt, err);
|
||||
// we are only interest in some specific mounts
|
||||
if (check_mnt && !should_umount(&path)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
ksu_umount_mnt(&path, flags);
|
||||
}
|
||||
|
||||
int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
||||
{
|
||||
// this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it!
|
||||
if (!ksu_module_mounted) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!new || !old) {
|
||||
return 0;
|
||||
}
|
||||
@@ -515,10 +550,8 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// todo: check old process's selinux context, if it is not zygote, ignore it!
|
||||
|
||||
if (!is_appuid(new_uid)) {
|
||||
// pr_info("handle setuid ignore non application uid: %d\n", new_uid.val);
|
||||
if (!is_appuid(new_uid) || is_isolated_uid(new_uid.val)) {
|
||||
// pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid.val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -535,14 +568,29 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
||||
#endif
|
||||
}
|
||||
|
||||
// check old process's selinux context, if it is not zygote, ignore it!
|
||||
// because some su apps may setuid to untrusted_app but they are in global mount namespace
|
||||
// when we umount for such process, that is a disaster!
|
||||
bool is_zygote_child = is_zygote(old->security);
|
||||
if (!is_zygote_child) {
|
||||
pr_info("handle umount ignore non zygote child: %d\n",
|
||||
current->pid);
|
||||
return 0;
|
||||
}
|
||||
// umount the target mnt
|
||||
pr_info("handle umount for uid: %d\n", new_uid.val);
|
||||
pr_info("handle umount for uid: %d, pid: %d\n", new_uid.val,
|
||||
current->pid);
|
||||
|
||||
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
|
||||
// filter the mountpoint whose target is `/data/adb`
|
||||
try_umount("/system");
|
||||
try_umount("/vendor");
|
||||
try_umount("/product");
|
||||
try_umount("/system", true, 0);
|
||||
try_umount("/vendor", true, 0);
|
||||
try_umount("/product", true, 0);
|
||||
try_umount("/data/adb/modules", false, MNT_DETACH);
|
||||
|
||||
// try umount ksu temp path
|
||||
try_umount("/debug_ramdisk", false, MNT_DETACH);
|
||||
try_umount("/sbin", false, MNT_DETACH);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -559,7 +607,14 @@ static int handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
int option = (int)PT_REGS_PARM1(real_regs);
|
||||
unsigned long arg2 = (unsigned long)PT_REGS_PARM2(real_regs);
|
||||
unsigned long arg3 = (unsigned long)PT_REGS_PARM3(real_regs);
|
||||
unsigned long arg4 = (unsigned long)PT_REGS_PARM4(real_regs);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)
|
||||
// PRCTL_SYMBOL is the arch-specificed one, which receive raw pt_regs from syscall
|
||||
unsigned long arg4 = (unsigned long)PT_REGS_SYSCALL_PARM4(real_regs);
|
||||
#else
|
||||
// PRCTL_SYMBOL is the common one, called by C convention in do_syscall_64
|
||||
// https://elixir.bootlin.com/linux/v4.15.18/source/arch/x86/entry/common.c#L287
|
||||
unsigned long arg4 = (unsigned long)PT_REGS_CCALL_PARM4(real_regs);
|
||||
#endif
|
||||
unsigned long arg5 = (unsigned long)PT_REGS_PARM5(real_regs);
|
||||
|
||||
return ksu_handle_prctl(option, arg2, arg3, arg4, arg5);
|
||||
@@ -579,7 +634,7 @@ static int renameat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
struct dentry *new_entry = rd->new_dentry;
|
||||
#else
|
||||
struct dentry *old_entry = (struct dentry *)PT_REGS_PARM2(regs);
|
||||
struct dentry *new_entry = (struct dentry *)PT_REGS_PARM4(regs);
|
||||
struct dentry *new_entry = (struct dentry *)PT_REGS_CCALL_PARM4(regs);
|
||||
#endif
|
||||
|
||||
return ksu_handle_rename(old_entry, new_entry);
|
||||
@@ -632,7 +687,7 @@ static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred,
|
||||
return 0;
|
||||
}
|
||||
init_session_keyring = cred->session_keyring;
|
||||
pr_info("kernel_compat: got init_session_keyring");
|
||||
pr_info("kernel_compat: got init_session_keyring\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,34 +1,178 @@
|
||||
#include "linux/version.h"
|
||||
#include "linux/fs.h"
|
||||
#include "linux/nsproxy.h"
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
|
||||
#include "linux/sched/task.h"
|
||||
#include "linux/uaccess.h"
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
|
||||
#include "linux/uaccess.h"
|
||||
#include "linux/sched.h"
|
||||
#else
|
||||
#include "linux/sched.h"
|
||||
#endif
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||||
#include "linux/key.h"
|
||||
#include "linux/errno.h"
|
||||
#include "linux/cred.h"
|
||||
struct key *init_session_keyring = NULL;
|
||||
|
||||
static inline int install_session_keyring(struct key *keyring)
|
||||
{
|
||||
struct cred *new;
|
||||
int ret;
|
||||
|
||||
new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = install_session_keyring_to_cred(new, keyring);
|
||||
if (ret < 0) {
|
||||
abort_creds(new);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return commit_creds(new);
|
||||
}
|
||||
#endif
|
||||
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, loff_t *pos){
|
||||
|
||||
extern struct task_struct init_task;
|
||||
|
||||
// mnt_ns context switch for environment that android_init->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns, such as WSA
|
||||
struct ksu_ns_fs_saved {
|
||||
struct nsproxy *ns;
|
||||
struct fs_struct *fs;
|
||||
};
|
||||
|
||||
static void ksu_save_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
|
||||
{
|
||||
ns_fs_saved->ns = current->nsproxy;
|
||||
ns_fs_saved->fs = current->fs;
|
||||
}
|
||||
|
||||
static void ksu_load_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
|
||||
{
|
||||
current->nsproxy = ns_fs_saved->ns;
|
||||
current->fs = ns_fs_saved->fs;
|
||||
}
|
||||
|
||||
static bool android_context_saved_checked = false;
|
||||
static bool android_context_saved_enabled = false;
|
||||
static struct ksu_ns_fs_saved android_context_saved;
|
||||
|
||||
void ksu_android_ns_fs_check()
|
||||
{
|
||||
if (android_context_saved_checked)
|
||||
return;
|
||||
android_context_saved_checked = true;
|
||||
task_lock(current);
|
||||
if (current->nsproxy && current->fs &&
|
||||
current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) {
|
||||
android_context_saved_enabled = true;
|
||||
pr_info("android context saved enabled due to init mnt_ns(%p) != android mnt_ns(%p)\n",
|
||||
current->nsproxy->mnt_ns, init_task.nsproxy->mnt_ns);
|
||||
ksu_save_ns_fs(&android_context_saved);
|
||||
} else {
|
||||
pr_info("android context saved disabled\n");
|
||||
}
|
||||
task_unlock(current);
|
||||
}
|
||||
|
||||
struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode)
|
||||
{
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||||
if (init_session_keyring != NULL && !current_cred()->session_keyring &&
|
||||
(current->flags & PF_WQ_WORKER)) {
|
||||
pr_info("installing init session keyring for older kernel\n");
|
||||
install_session_keyring(init_session_keyring);
|
||||
}
|
||||
#endif
|
||||
// switch mnt_ns even if current is not wq_worker, to ensure what we open is the correct file in android mnt_ns, rather than user created mnt_ns
|
||||
struct ksu_ns_fs_saved saved;
|
||||
if (android_context_saved_enabled) {
|
||||
pr_info("start switch current nsproxy and fs to android context\n");
|
||||
task_lock(current);
|
||||
ksu_save_ns_fs(&saved);
|
||||
ksu_load_ns_fs(&android_context_saved);
|
||||
task_unlock(current);
|
||||
}
|
||||
struct file *fp = filp_open(filename, flags, mode);
|
||||
if (android_context_saved_enabled) {
|
||||
task_lock(current);
|
||||
ksu_load_ns_fs(&saved);
|
||||
task_unlock(current);
|
||||
pr_info("switch current nsproxy and fs back to saved successfully\n");
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
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)
|
||||
return kernel_read(p, buf, count, pos);
|
||||
return kernel_read(p, buf, count, pos);
|
||||
#else
|
||||
loff_t offset = pos ? *pos : 0;
|
||||
ssize_t result = kernel_read(p, offset, (char *)buf, count);
|
||||
if (pos && result > 0)
|
||||
{
|
||||
*pos = offset + result;
|
||||
}
|
||||
return result;
|
||||
loff_t offset = pos ? *pos : 0;
|
||||
ssize_t result = kernel_read(p, offset, (char *)buf, count);
|
||||
if (pos && result > 0) {
|
||||
*pos = offset + result;
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, loff_t *pos){
|
||||
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)
|
||||
return kernel_write(p, buf, count, pos);
|
||||
return kernel_write(p, buf, count, pos);
|
||||
#else
|
||||
loff_t offset = pos ? *pos : 0;
|
||||
ssize_t result = kernel_write(p, buf, count, offset);
|
||||
if (pos && result > 0)
|
||||
{
|
||||
*pos = offset + result;
|
||||
}
|
||||
return result;
|
||||
loff_t offset = pos ? *pos : 0;
|
||||
ssize_t result = kernel_write(p, buf, count, offset);
|
||||
if (pos && result > 0) {
|
||||
*pos = offset + result;
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
||||
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
||||
long count)
|
||||
{
|
||||
return strncpy_from_user_nofault(dst, unsafe_addr, count);
|
||||
}
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
|
||||
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
||||
long count)
|
||||
{
|
||||
return strncpy_from_unsafe_user(dst, unsafe_addr, count);
|
||||
}
|
||||
#else
|
||||
// Copied from: https://elixir.bootlin.com/linux/v4.9.337/source/mm/maccess.c#L201
|
||||
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
||||
long count)
|
||||
{
|
||||
mm_segment_t old_fs = get_fs();
|
||||
long ret;
|
||||
|
||||
if (unlikely(count <= 0))
|
||||
return 0;
|
||||
|
||||
set_fs(USER_DS);
|
||||
pagefault_disable();
|
||||
ret = strncpy_from_user(dst, unsafe_addr, count);
|
||||
pagefault_enable();
|
||||
set_fs(old_fs);
|
||||
|
||||
if (ret >= count) {
|
||||
ret = count;
|
||||
dst[ret - 1] = '\0';
|
||||
} else if (ret > 0) {
|
||||
ret++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -5,38 +5,20 @@
|
||||
#include "linux/key.h"
|
||||
#include "linux/version.h"
|
||||
|
||||
extern struct key *init_session_keyring;
|
||||
|
||||
extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, loff_t *pos);
|
||||
extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, loff_t *pos);
|
||||
extern long ksu_strncpy_from_user_nofault(char *dst,
|
||||
const void __user *unsafe_addr,
|
||||
long count);
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||||
static inline int install_session_keyring(struct key *keyring)
|
||||
{
|
||||
struct cred *new;
|
||||
int ret;
|
||||
|
||||
new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = install_session_keyring_to_cred(new, keyring);
|
||||
if (ret < 0) {
|
||||
abort_creds(new);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return commit_creds(new);
|
||||
}
|
||||
#define KWORKER_INSTALL_KEYRING() \
|
||||
static bool keyring_installed = false; \
|
||||
if (init_session_keyring != NULL && !keyring_installed) \
|
||||
{ \
|
||||
install_session_keyring(init_session_keyring); \
|
||||
keyring_installed = true; \
|
||||
}
|
||||
#else
|
||||
#define KWORKER_INSTALL_KEYRING()
|
||||
extern struct key *init_session_keyring;
|
||||
#endif
|
||||
|
||||
extern void ksu_android_ns_fs_check();
|
||||
extern struct file *ksu_filp_open_compat(const char *filename, int flags,
|
||||
umode_t mode);
|
||||
extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
|
||||
loff_t *pos);
|
||||
extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf,
|
||||
size_t count, loff_t *pos);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -39,7 +39,7 @@ int __init kernelsu_init(void)
|
||||
pr_alert("*************************************************************");
|
||||
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
|
||||
pr_alert("** **");
|
||||
pr_alert("** You are running DEBUG version of KernelSU **");
|
||||
pr_alert("** You are running KernelSU in DEBUG mode **");
|
||||
pr_alert("** **");
|
||||
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
|
||||
pr_alert("*************************************************************");
|
||||
@@ -47,7 +47,7 @@ int __init kernelsu_init(void)
|
||||
|
||||
ksu_core_init();
|
||||
|
||||
ksu_workqueue = alloc_workqueue("kernelsu_work_queue", 0, 0);
|
||||
ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);
|
||||
|
||||
ksu_allowlist_init();
|
||||
|
||||
|
||||
12
kernel/ksu.h
12
kernel/ksu.h
@@ -4,16 +4,7 @@
|
||||
#include "linux/types.h"
|
||||
#include "linux/workqueue.h"
|
||||
|
||||
#ifndef KSU_GIT_VERSION
|
||||
#warning \
|
||||
"KSU_GIT_VERSION not defined! It is better to make KernelSU a git submodule!"
|
||||
#define KERNEL_SU_VERSION (16)
|
||||
#else
|
||||
#define KERNEL_SU_VERSION \
|
||||
(10000 + KSU_GIT_VERSION + \
|
||||
200) // major * 10000 + git version + 200 for historical reasons
|
||||
#endif
|
||||
|
||||
#define KERNEL_SU_VERSION KSU_VERSION
|
||||
#define KERNEL_SU_OPTION 0xDEADBEEF
|
||||
|
||||
#define CMD_GRANT_ROOT 0
|
||||
@@ -33,6 +24,7 @@
|
||||
|
||||
#define EVENT_POST_FS_DATA 1
|
||||
#define EVENT_BOOT_COMPLETED 2
|
||||
#define EVENT_MODULE_MOUNTED 3
|
||||
|
||||
#define KSU_APP_PROFILE_VER 2
|
||||
#define KSU_MAX_PACKAGE_NAME 256
|
||||
|
||||
135
kernel/ksud.c
135
kernel/ksud.c
@@ -1,7 +1,5 @@
|
||||
#include "asm/current.h"
|
||||
#include "linux/string.h"
|
||||
#include "linux/compat.h"
|
||||
#include "linux/cred.h"
|
||||
#include "linux/dcache.h"
|
||||
#include "linux/err.h"
|
||||
#include "linux/fs.h"
|
||||
@@ -12,12 +10,12 @@
|
||||
#include "linux/uaccess.h"
|
||||
#include "linux/version.h"
|
||||
#include "linux/workqueue.h"
|
||||
#include "linux/input.h"
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksud.h"
|
||||
#include "kernel_compat.h"
|
||||
#include "selinux/selinux.h"
|
||||
|
||||
static const char KERNEL_SU_RC[] =
|
||||
@@ -61,11 +59,11 @@ void on_post_fs_data(void)
|
||||
{
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
pr_info("on_post_fs_data already done");
|
||||
pr_info("on_post_fs_data already done\n");
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
pr_info("on_post_fs_data!");
|
||||
pr_info("on_post_fs_data!\n");
|
||||
ksu_load_allow_list();
|
||||
// sanity check, this may influence the performance
|
||||
stop_input_hook();
|
||||
@@ -140,8 +138,9 @@ static int __maybe_unused count(struct user_arg_ptr argv, int max)
|
||||
return i;
|
||||
}
|
||||
|
||||
// IMPORTANT NOTE: the call from execve_handler_pre WON'T provided correct value for envp and flags in GKI version
|
||||
int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
void *argv, void *envp, int *flags)
|
||||
struct user_arg_ptr *argv, struct user_arg_ptr *envp, int *flags)
|
||||
{
|
||||
#ifndef CONFIG_KPROBES
|
||||
if (!ksu_execveat_hook) {
|
||||
@@ -152,7 +151,11 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
|
||||
static const char app_process[] = "/system/bin/app_process";
|
||||
static bool first_app_process = true;
|
||||
|
||||
/* This applies to versions Android 10+ */
|
||||
static const char system_bin_init[] = "/system/bin/init";
|
||||
/* This applies to versions between Android 6 ~ 9 */
|
||||
static const char old_system_init[] = "/init";
|
||||
static bool init_second_stage_executed = false;
|
||||
|
||||
if (!filename_ptr)
|
||||
@@ -163,51 +166,84 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (unlikely(!memcmp(filename->name, system_bin_init,
|
||||
sizeof(system_bin_init) - 1))) {
|
||||
#ifdef __aarch64__
|
||||
if (unlikely(!memcmp(filename->name, system_bin_init,
|
||||
sizeof(system_bin_init) - 1) && argv)) {
|
||||
// /system/bin/init executed
|
||||
struct user_arg_ptr *ptr = (struct user_arg_ptr*) argv;
|
||||
int argc = count(*ptr, MAX_ARG_STRINGS);
|
||||
int argc = count(*argv, MAX_ARG_STRINGS);
|
||||
pr_info("/system/bin/init argc: %d\n", argc);
|
||||
if (argc > 1 && !init_second_stage_executed) {
|
||||
const char __user *p = get_user_arg_ptr(*ptr, 1);
|
||||
const char __user *p = get_user_arg_ptr(*argv, 1);
|
||||
if (p && !IS_ERR(p)) {
|
||||
char first_arg[16];
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
||||
strncpy_from_user_nofault(first_arg, p, sizeof(first_arg));
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
|
||||
strncpy_from_unsafe_user(first_arg, p, sizeof(first_arg));
|
||||
#else
|
||||
strncpy_from_user(first_arg, p, sizeof(first_arg));
|
||||
#endif
|
||||
pr_info("first arg: %s\n", first_arg);
|
||||
ksu_strncpy_from_user_nofault(first_arg, p, sizeof(first_arg));
|
||||
pr_info("/system/bin/init first arg: %s\n", first_arg);
|
||||
if (!strcmp(first_arg, "second_stage")) {
|
||||
pr_info("/system/bin/init second_stage executed\n");
|
||||
apply_kernelsu_rules();
|
||||
init_second_stage_executed = true;
|
||||
ksu_android_ns_fs_check();
|
||||
}
|
||||
} else {
|
||||
pr_err("/system/bin/init parse args err!\n");
|
||||
}
|
||||
}
|
||||
#else
|
||||
// The argument parse is incorrect becuase of the struct user_arg_ptr has 16bytes
|
||||
// and it is passed by value(not pointer), in arm64, it is correct becuase the register
|
||||
// is just arranged correct accidentally, but is not correct in x86_64
|
||||
// i have no device to test, so revert it for x86_64
|
||||
static int init_count = 0;
|
||||
if (++init_count == 2) {
|
||||
// 1: /system/bin/init selinux_setup
|
||||
// 2: /system/bin/init second_stage
|
||||
pr_info("/system/bin/init second_stage executed\n");
|
||||
apply_kernelsu_rules();
|
||||
} else if (unlikely(!memcmp(filename->name, old_system_init,
|
||||
sizeof(old_system_init) - 1) && argv)) {
|
||||
// /init executed
|
||||
int argc = count(*argv, MAX_ARG_STRINGS);
|
||||
pr_info("/init argc: %d\n", argc);
|
||||
if (argc > 1 && !init_second_stage_executed) {
|
||||
/* This applies to versions between Android 6 ~ 7 */
|
||||
const char __user *p = get_user_arg_ptr(*argv, 1);
|
||||
if (p && !IS_ERR(p)) {
|
||||
char first_arg[16];
|
||||
ksu_strncpy_from_user_nofault(first_arg, p, sizeof(first_arg));
|
||||
pr_info("/init first arg: %s\n", first_arg);
|
||||
if (!strcmp(first_arg, "--second-stage")) {
|
||||
pr_info("/init second_stage executed\n");
|
||||
apply_kernelsu_rules();
|
||||
init_second_stage_executed = true;
|
||||
ksu_android_ns_fs_check();
|
||||
}
|
||||
} else {
|
||||
pr_err("/init parse args err!\n");
|
||||
}
|
||||
} else if (argc == 1 && !init_second_stage_executed && envp) {
|
||||
/* This applies to versions between Android 8 ~ 9 */
|
||||
int envc = count(*envp, MAX_ARG_STRINGS);
|
||||
if (envc > 0) {
|
||||
int n;
|
||||
for (n = 1; n <= envc; n++) {
|
||||
const char __user *p = get_user_arg_ptr(*envp, n);
|
||||
if (!p || IS_ERR(p)) {
|
||||
continue;
|
||||
}
|
||||
char env[256];
|
||||
// Reading environment variable strings from user space
|
||||
if (ksu_strncpy_from_user_nofault(env, p, sizeof(env)) < 0)
|
||||
continue;
|
||||
// Parsing environment variable names and values
|
||||
char *env_name = env;
|
||||
char *env_value = strchr(env, '=');
|
||||
if (env_value == NULL)
|
||||
continue;
|
||||
// Replace equal sign with string terminator
|
||||
*env_value = '\0';
|
||||
env_value++;
|
||||
// Check if the environment variable name and value are matching
|
||||
if (!strcmp(env_name, "INIT_SECOND_STAGE") && (!strcmp(env_value, "1") || !strcmp(env_value, "true"))) {
|
||||
pr_info("/init second_stage executed\n");
|
||||
apply_kernelsu_rules();
|
||||
init_second_stage_executed = true;
|
||||
ksu_android_ns_fs_check();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (unlikely(first_app_process &&
|
||||
!memcmp(filename->name, app_process, sizeof(app_process) - 1))) {
|
||||
!memcmp(filename->name, app_process, sizeof(app_process) - 1))) {
|
||||
first_app_process = false;
|
||||
pr_info("exec app_process, /data prepared, second_stage: %d\n", init_second_stage_executed);
|
||||
on_post_fs_data(); // we keep this for old ksud
|
||||
@@ -228,7 +264,7 @@ static ssize_t read_proxy(struct file *file, char __user *buf, size_t count,
|
||||
bool first_read = file->f_pos == 0;
|
||||
ssize_t ret = orig_read(file, buf, count, pos);
|
||||
if (first_read) {
|
||||
pr_info("read_proxy append %ld + %ld", ret, read_count_append);
|
||||
pr_info("read_proxy append %ld + %ld\n", ret, read_count_append);
|
||||
ret += read_count_append;
|
||||
}
|
||||
return ret;
|
||||
@@ -239,7 +275,7 @@ static ssize_t read_iter_proxy(struct kiocb *iocb, struct iov_iter *to)
|
||||
bool first_read = iocb->ki_pos == 0;
|
||||
ssize_t ret = orig_read_iter(iocb, to);
|
||||
if (first_read) {
|
||||
pr_info("read_iter_proxy append %ld + %ld", ret,
|
||||
pr_info("read_iter_proxy append %ld + %ld\n", ret,
|
||||
read_count_append);
|
||||
ret += read_count_append;
|
||||
}
|
||||
@@ -304,17 +340,17 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
|
||||
size_t rc_count = strlen(KERNEL_SU_RC);
|
||||
|
||||
pr_info("vfs_read: %s, comm: %s, count: %d, rc_count: %d\n", dpath,
|
||||
pr_info("vfs_read: %s, comm: %s, count: %zu, rc_count: %zu\n", dpath,
|
||||
current->comm, count, rc_count);
|
||||
|
||||
if (count < rc_count) {
|
||||
pr_err("count: %d < rc_count: %d", count, rc_count);
|
||||
pr_err("count: %zu < rc_count: %zu\n", count, rc_count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t ret = copy_to_user(buf, KERNEL_SU_RC, rc_count);
|
||||
if (ret) {
|
||||
pr_err("copy ksud.rc failed: %d\n", ret);
|
||||
pr_err("copy ksud.rc failed: %zu\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -400,11 +436,19 @@ 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);
|
||||
void *argv = (void *)&PT_REGS_PARM3(regs);
|
||||
void *envp = (void *)&PT_REGS_PARM4(regs);
|
||||
int *flags = (int *)&PT_REGS_PARM5(regs);
|
||||
struct user_arg_ptr argv;
|
||||
#ifdef CONFIG_COMPAT
|
||||
argv.is_compat = PT_REGS_PARM3(regs);
|
||||
if (unlikely(argv.is_compat)) {
|
||||
argv.ptr.compat = PT_REGS_CCALL_PARM4(regs);
|
||||
} else {
|
||||
argv.ptr.native = PT_REGS_CCALL_PARM4(regs);
|
||||
}
|
||||
#else
|
||||
argv.ptr.native = PT_REGS_PARM3(regs);
|
||||
#endif
|
||||
|
||||
return ksu_handle_execveat_ksud(fd, filename_ptr, argv, envp, flags);
|
||||
return ksu_handle_execveat_ksud(fd, filename_ptr, &argv, NULL, NULL);
|
||||
}
|
||||
|
||||
static int read_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
@@ -412,7 +456,7 @@ static int read_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
struct file **file_ptr = (struct file **)&PT_REGS_PARM1(regs);
|
||||
char __user **buf_ptr = (char **)&PT_REGS_PARM2(regs);
|
||||
size_t *count_ptr = (size_t *)&PT_REGS_PARM3(regs);
|
||||
loff_t **pos_ptr = (loff_t **)&PT_REGS_PARM4(regs);
|
||||
loff_t **pos_ptr = (loff_t **)&PT_REGS_CCALL_PARM4(regs);
|
||||
|
||||
return ksu_handle_vfs_read(file_ptr, buf_ptr, count_ptr, pos_ptr);
|
||||
}
|
||||
@@ -422,7 +466,7 @@ static int input_handle_event_handler_pre(struct kprobe *p,
|
||||
{
|
||||
unsigned int *type = (unsigned int *)&PT_REGS_PARM2(regs);
|
||||
unsigned int *code = (unsigned int *)&PT_REGS_PARM3(regs);
|
||||
int *value = (int *)&PT_REGS_PARM4(regs);
|
||||
int *value = (int *)&PT_REGS_CCALL_PARM4(regs);
|
||||
return ksu_handle_input_handle_event(type, code, value);
|
||||
}
|
||||
|
||||
@@ -470,6 +514,7 @@ static void stop_vfs_read_hook()
|
||||
pr_info("unregister vfs_read kprobe: %d!\n", ret);
|
||||
#else
|
||||
ksu_vfs_read_hook = false;
|
||||
pr_info("stop vfs_read_hook\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -480,6 +525,7 @@ static void stop_execve_hook()
|
||||
pr_info("unregister execve kprobe: %d!\n", ret);
|
||||
#else
|
||||
ksu_execveat_hook = false;
|
||||
pr_info("stop execve_hook\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -495,6 +541,7 @@ static void stop_input_hook()
|
||||
pr_info("unregister input kprobe: %d!\n", ret);
|
||||
#else
|
||||
ksu_input_hook = false;
|
||||
pr_info("stop input_hook\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,15 @@ bool become_manager(char *pkg)
|
||||
char *buf;
|
||||
bool result = false;
|
||||
|
||||
#ifdef KSU_MANAGER_PACKAGE
|
||||
// pkg is `/<real package>`
|
||||
if (strncmp(pkg + 1, KSU_MANAGER_PACKAGE,
|
||||
sizeof(KSU_MANAGER_PACKAGE)) != 0) {
|
||||
pr_info("manager package is inconsistent with kernel build: %s\n",
|
||||
KSU_MANAGER_PACKAGE);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// must be zygote's direct child, otherwise any app can fork a new process and
|
||||
// open manager's apk
|
||||
if (task_uid(current->real_parent).val != 0) {
|
||||
@@ -48,11 +57,12 @@ bool become_manager(char *pkg)
|
||||
}
|
||||
cwd = d_path(&files_path, buf, PATH_MAX);
|
||||
if (startswith(cwd, "/data/app/") != 0 ||
|
||||
endswith(cwd, "/base.apk") != 0) {
|
||||
endswith(cwd, "==/base.apk") != 0) {
|
||||
// AOSP generate ramdom base64 with 16bit, without NO_PADDING, so it must have two "="
|
||||
continue;
|
||||
}
|
||||
// we have found the apk!
|
||||
pr_info("found apk: %s", cwd);
|
||||
pr_info("found apk: %s\n", cwd);
|
||||
char *pkg_index = strstr(cwd, pkg);
|
||||
if (!pkg_index) {
|
||||
pr_info("apk path not match package name!\n");
|
||||
@@ -70,7 +80,7 @@ bool become_manager(char *pkg)
|
||||
pr_info("invalid pkg: %s\n", pkg);
|
||||
continue;
|
||||
}
|
||||
if (is_manager_apk(cwd) == 0) {
|
||||
if (is_manager_apk(cwd)) {
|
||||
// check passed
|
||||
uid_t uid = current_uid().val;
|
||||
pr_info("manager uid: %d\n", uid);
|
||||
@@ -80,7 +90,7 @@ bool become_manager(char *pkg)
|
||||
result = true;
|
||||
goto clean;
|
||||
} else {
|
||||
pr_info("manager signature invalid!");
|
||||
pr_info("manager signature invalid!\n");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -39,7 +39,7 @@ static struct policydb *get_policydb(void)
|
||||
void apply_kernelsu_rules()
|
||||
{
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, apply rules!");
|
||||
pr_info("SELinux permissive or disabled, apply rules!\n");
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
@@ -63,6 +63,7 @@ void apply_kernelsu_rules()
|
||||
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "blk_file", ALL);
|
||||
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "fifo_file", ALL);
|
||||
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "chr_file", ALL);
|
||||
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "file", ALL);
|
||||
}
|
||||
|
||||
// we need to save allowlist in /data/adb/ksu
|
||||
@@ -83,7 +84,10 @@ void apply_kernelsu_rules()
|
||||
ksu_allow(db, "kernel", "system_data_file", "dir", ALL);
|
||||
// our ksud triggered by init
|
||||
ksu_allow(db, "init", "adb_data_file", "file", ALL);
|
||||
ksu_allow(db, "init", "adb_data_file", "dir", ALL); // #1289
|
||||
ksu_allow(db, "init", KERNEL_SU_DOMAIN, ALL, ALL);
|
||||
// we need to umount modules in zygote
|
||||
ksu_allow(db, "zygote", "adb_data_file", "dir", "search");
|
||||
|
||||
// copied from Magisk rules
|
||||
// suRights
|
||||
@@ -114,6 +118,10 @@ void apply_kernelsu_rules()
|
||||
ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process",
|
||||
"getattr");
|
||||
|
||||
// For mounting loop devices, mirrors, tmpfs
|
||||
ksu_allow(db, "kernel", ALL, "file", "read");
|
||||
ksu_allow(db, "kernel", ALL, "file", "write");
|
||||
|
||||
// Allow all binder transactions
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL);
|
||||
|
||||
@@ -123,6 +131,10 @@ void apply_kernelsu_rules()
|
||||
ksu_allow(db, "system_server", "untrusted_app_all_devpts", "chr_file",
|
||||
"write");
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -170,7 +182,8 @@ static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||
// reset avc cache table, otherwise the new rules will not take effect if already denied
|
||||
static void reset_avc_cache()
|
||||
{
|
||||
#ifndef KSU_COMPAT_USE_SELINUX_STATE
|
||||
#if ((!defined(KSU_COMPAT_USE_SELINUX_STATE)) || \
|
||||
LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0))
|
||||
avc_ss_reset(0);
|
||||
selnl_notify_policyload(0);
|
||||
selinux_status_update_policyload(0);
|
||||
@@ -245,7 +258,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
} else if (subcmd == 4) {
|
||||
success = ksu_dontaudit(db, s, t, c, p);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d", subcmd);
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
ret = success ? 0 : -1;
|
||||
|
||||
@@ -290,7 +303,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
} else if (subcmd == 3) {
|
||||
success = ksu_dontauditxperm(db, s, t, c, perm_set);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d", subcmd);
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
ret = success ? 0 : -1;
|
||||
} else if (cmd == CMD_TYPE_STATE) {
|
||||
@@ -307,7 +320,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_enforce(db, src);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d", subcmd);
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
if (success)
|
||||
ret = 0;
|
||||
@@ -422,7 +435,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
success = ksu_type_member(db, src, tgt, cls,
|
||||
default_type);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d", subcmd);
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
if (success)
|
||||
ret = 0;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
#define KERNEL_SU_DOMAIN "u:r:su:s0"
|
||||
|
||||
static u32 ksu_sid;
|
||||
|
||||
static int transive_to_domain(const char *domain)
|
||||
{
|
||||
struct cred *cred;
|
||||
@@ -26,11 +24,11 @@ static int transive_to_domain(const char *domain)
|
||||
}
|
||||
|
||||
error = security_secctx_to_secid(domain, strlen(domain), &sid);
|
||||
pr_info("error: %d, sid: %d\n", error, sid);
|
||||
if (error) {
|
||||
pr_info("security_secctx_to_secid %s -> sid: %d, error: %d\n",
|
||||
domain, sid, error);
|
||||
}
|
||||
if (!error) {
|
||||
if (!ksu_sid)
|
||||
ksu_sid = sid;
|
||||
|
||||
tsec->sid = sid;
|
||||
tsec->create_sid = 0;
|
||||
tsec->keycreate_sid = 0;
|
||||
@@ -42,7 +40,7 @@ static int transive_to_domain(const char *domain)
|
||||
void setup_selinux(const char *domain)
|
||||
{
|
||||
if (transive_to_domain(domain)) {
|
||||
pr_err("transive domain failed.");
|
||||
pr_err("transive domain failed.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,5 +101,32 @@ static inline u32 current_sid(void)
|
||||
|
||||
bool is_ksu_domain()
|
||||
{
|
||||
return ksu_sid && current_sid() == ksu_sid;
|
||||
char *domain;
|
||||
u32 seclen;
|
||||
bool result;
|
||||
int err = security_secid_to_secctx(current_sid(), &domain, &seclen);
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
result = strncmp(KERNEL_SU_DOMAIN, domain, seclen) == 0;
|
||||
security_release_secctx(domain, seclen);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_zygote(void *sec)
|
||||
{
|
||||
struct task_security_struct *tsec = (struct task_security_struct *)sec;
|
||||
if (!tsec) {
|
||||
return false;
|
||||
}
|
||||
char *domain;
|
||||
u32 seclen;
|
||||
bool result;
|
||||
int err = security_secid_to_secctx(tsec->sid, &domain, &seclen);
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
result = strncmp("u:r:zygote:s0", domain, seclen) == 0;
|
||||
security_release_secctx(domain, seclen);
|
||||
return result;
|
||||
}
|
||||
@@ -16,6 +16,8 @@ bool getenforce();
|
||||
|
||||
bool is_ksu_domain();
|
||||
|
||||
bool is_zygote(void *cred);
|
||||
|
||||
void apply_kernelsu_rules();
|
||||
|
||||
#endif
|
||||
|
||||
@@ -73,7 +73,7 @@ static bool add_typeattribute(struct policydb *db, const char *type,
|
||||
// rules
|
||||
#define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert)
|
||||
|
||||
#define ksu_hash_for_each(node_ptr, n_slot, cur) \
|
||||
#define ksu_hash_for_each(node_ptr, n_slot, cur) \
|
||||
int i; \
|
||||
for (i = 0; i < n_slot; ++i) \
|
||||
for (cur = node_ptr[i]; cur; cur = cur->next)
|
||||
@@ -81,10 +81,11 @@ static bool add_typeattribute(struct policydb *db, const char *type,
|
||||
// htable is a struct instead of pointer above 5.8.0:
|
||||
// https://elixir.bootlin.com/linux/v5.8-rc1/source/security/selinux/ss/symtab.h
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
||||
#define ksu_hashtab_for_each(htab, cur) ksu_hash_for_each (htab.htable, htab.size, cur)
|
||||
#define ksu_hashtab_for_each(htab, cur) \
|
||||
ksu_hash_for_each(htab.htable, htab.size, cur)
|
||||
#else
|
||||
#define ksu_hashtab_for_each(htab, cur) \
|
||||
ksu_hash_for_each (htab->htable, htab->size, cur)
|
||||
#define ksu_hashtab_for_each(htab, cur) \
|
||||
ksu_hash_for_each(htab->htable, htab->size, cur)
|
||||
#endif
|
||||
|
||||
// symtab_search is introduced on 5.9.0:
|
||||
@@ -95,8 +96,7 @@ static bool add_typeattribute(struct policydb *db, const char *type,
|
||||
#endif
|
||||
|
||||
#define avtab_for_each(avtab, cur) \
|
||||
ksu_hash_for_each (avtab.htable, avtab.nslot, cur) \
|
||||
;
|
||||
ksu_hash_for_each(avtab.htable, avtab.nslot, cur);
|
||||
|
||||
static struct avtab_node *get_avtab_node(struct policydb *db,
|
||||
struct avtab_key *key,
|
||||
@@ -592,14 +592,14 @@ static bool add_filename_trans(struct policydb *db, const char *s,
|
||||
trans = (struct filename_trans_datum *)kcalloc(sizeof(*trans),
|
||||
1, GFP_ATOMIC);
|
||||
if (!trans) {
|
||||
pr_err("add_filename_trans: Failed to alloc datum");
|
||||
pr_err("add_filename_trans: Failed to alloc datum\n");
|
||||
return false;
|
||||
}
|
||||
struct filename_trans *new_key =
|
||||
(struct filename_trans *)kmalloc(sizeof(*new_key),
|
||||
GFP_ATOMIC);
|
||||
if (!new_key) {
|
||||
pr_err("add_filename_trans: Failed to alloc new_key");
|
||||
pr_err("add_filename_trans: Failed to alloc new_key\n");
|
||||
return false;
|
||||
}
|
||||
*new_key = key;
|
||||
@@ -693,7 +693,7 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
int i;
|
||||
for (i = 0; i < db->p_roles.nprim; ++i) {
|
||||
ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
|
||||
0);
|
||||
1);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -743,7 +743,7 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
int i;
|
||||
for (i = 0; i < db->p_roles.nprim; ++i) {
|
||||
ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
|
||||
0);
|
||||
1);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -854,7 +854,7 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
|
||||
int i;
|
||||
for (i = 0; i < db->p_roles.nprim; ++i) {
|
||||
ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
|
||||
0);
|
||||
1);
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
|
||||
@@ -44,7 +44,7 @@ echo '[+] Add kernel su driver to Makefile'
|
||||
|
||||
DRIVER_MAKEFILE=$DRIVER_DIR/Makefile
|
||||
DRIVER_KCONFIG=$DRIVER_DIR/Kconfig
|
||||
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "obj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
|
||||
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 '[+] Done.'
|
||||
echo '[+] Done.'
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "arch.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksud.h"
|
||||
#include "kernel_compat.h"
|
||||
|
||||
#define SU_PATH "/system/bin/su"
|
||||
#define SH_PATH "/system/bin/sh"
|
||||
@@ -41,32 +42,27 @@ static char __user *sh_user_path(void)
|
||||
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||
int *flags)
|
||||
{
|
||||
struct filename *filename;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
filename = getname(*filename_user);
|
||||
char path[sizeof(su) + 1];
|
||||
memset(path, 0, sizeof(path));
|
||||
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
if (unlikely(!memcmp(filename->name, su, sizeof(su)))) {
|
||||
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
||||
pr_info("faccessat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
|
||||
putname(filename);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
{
|
||||
// const char sh[] = SH_PATH;
|
||||
struct filename *filename;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
@@ -77,23 +73,35 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
return 0;
|
||||
}
|
||||
|
||||
filename = getname(*filename_user);
|
||||
|
||||
char path[sizeof(su) + 1];
|
||||
memset(path, 0, sizeof(path));
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0)
|
||||
// it becomes a `struct filename *` after 5.18
|
||||
// https://elixir.bootlin.com/linux/v5.18/source/fs/stat.c#L216
|
||||
const char sh[] = SH_PATH;
|
||||
struct filename *filename = * ((struct filename **) filename_user);
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
if (unlikely(!memcmp(filename->name, su, sizeof(su)))) {
|
||||
if (likely(memcmp(filename->name, su, sizeof(su))))
|
||||
return 0;
|
||||
pr_info("vfs_statx su->sh!\n");
|
||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||
#else
|
||||
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
|
||||
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
||||
pr_info("newfstatat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
|
||||
putname(filename);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// the call from execve_handler_pre won't provided correct value for __never_use_argument, use them after fix execve_handler_pre, keeping them for consistence for manually patched code
|
||||
int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||
void *argv, void *envp, int *flags)
|
||||
void *__never_use_argv, void *__never_use_envp, int *__never_use_flags)
|
||||
{
|
||||
struct filename *filename;
|
||||
const char sh[] = KSUD_PATH;
|
||||
@@ -128,7 +136,8 @@ 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);
|
||||
int *flags = (int *)&PT_REGS_PARM4(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);
|
||||
}
|
||||
@@ -142,7 +151,7 @@ static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
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_PARM4(regs);
|
||||
int *flags = (int *)&PT_REGS_CCALL_PARM4(regs);
|
||||
#endif
|
||||
|
||||
return ksu_handle_stat(dfd, filename_user, flags);
|
||||
@@ -154,12 +163,8 @@ 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);
|
||||
void *argv = (void *)&PT_REGS_PARM3(regs);
|
||||
void *envp = (void *)&PT_REGS_PARM4(regs);
|
||||
int *flags = (int *)&PT_REGS_PARM5(regs);
|
||||
|
||||
return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp,
|
||||
flags);
|
||||
return ksu_handle_execveat_sucompat(fd, filename_ptr, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static struct kprobe faccessat_kp = {
|
||||
|
||||
@@ -20,16 +20,18 @@ static struct work_struct ksu_update_uid_work;
|
||||
struct uid_data {
|
||||
struct list_head list;
|
||||
u32 uid;
|
||||
char package[KSU_MAX_PACKAGE_NAME];
|
||||
};
|
||||
|
||||
static bool is_uid_exist(uid_t uid, void *data)
|
||||
static bool is_uid_exist(uid_t uid, char *package, void *data)
|
||||
{
|
||||
struct list_head *list = (struct list_head *)data;
|
||||
struct uid_data *np;
|
||||
|
||||
bool exist = false;
|
||||
list_for_each_entry (np, list, list) {
|
||||
if (np->uid == uid % 100000) {
|
||||
if (np->uid == uid % 100000 &&
|
||||
strncmp(np->package, package, KSU_MAX_PACKAGE_NAME) == 0) {
|
||||
exist = true;
|
||||
break;
|
||||
}
|
||||
@@ -39,11 +41,11 @@ static bool is_uid_exist(uid_t uid, void *data)
|
||||
|
||||
static void do_update_uid(struct work_struct *work)
|
||||
{
|
||||
KWORKER_INSTALL_KEYRING();
|
||||
struct file *fp = filp_open(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
|
||||
struct file *fp =
|
||||
ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("do_update_uid, open " SYSTEM_PACKAGES_LIST_PATH
|
||||
" failed: %d\n",
|
||||
" failed: %ld\n",
|
||||
PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
@@ -54,7 +56,7 @@ static void do_update_uid(struct work_struct *work)
|
||||
char chr = 0;
|
||||
loff_t pos = 0;
|
||||
loff_t line_start = 0;
|
||||
char buf[128];
|
||||
char buf[KSU_MAX_PACKAGE_NAME];
|
||||
for (;;) {
|
||||
ssize_t count =
|
||||
ksu_kernel_read_compat(fp, &chr, sizeof(chr), &pos);
|
||||
@@ -67,26 +69,27 @@ static void do_update_uid(struct work_struct *work)
|
||||
&line_start);
|
||||
|
||||
struct uid_data *data =
|
||||
kmalloc(sizeof(struct uid_data), GFP_ATOMIC);
|
||||
kzalloc(sizeof(struct uid_data), GFP_ATOMIC);
|
||||
if (!data) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
char *tmp = buf;
|
||||
const char *delim = " ";
|
||||
strsep(&tmp, delim); // skip package
|
||||
char *package = strsep(&tmp, delim);
|
||||
char *uid = strsep(&tmp, delim);
|
||||
if (!uid) {
|
||||
pr_err("update_uid: uid is NULL!\n");
|
||||
continue;
|
||||
if (!uid || !package) {
|
||||
pr_err("update_uid: package or uid is NULL!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
u32 res;
|
||||
if (kstrtou32(uid, 10, &res)) {
|
||||
pr_err("update_uid: uid parse err\n");
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
data->uid = res;
|
||||
strncpy(data->package, package, KSU_MAX_PACKAGE_NAME);
|
||||
list_add_tail(&data->list, &uid_list);
|
||||
// reset line start
|
||||
line_start = pos;
|
||||
|
||||
@@ -93,12 +93,14 @@ dependencies {
|
||||
implementation(libs.com.google.accompanist.drawablepainter)
|
||||
implementation(libs.com.google.accompanist.navigation.animation)
|
||||
implementation(libs.com.google.accompanist.systemuicontroller)
|
||||
implementation(libs.com.google.accompanist.webview)
|
||||
|
||||
implementation(libs.compose.destinations.animations.core)
|
||||
ksp(libs.compose.destinations.ksp)
|
||||
|
||||
implementation(libs.com.github.topjohnwu.libsu.core)
|
||||
implementation(libs.com.github.topjohnwu.libsu.service)
|
||||
implementation(libs.com.github.topjohnwu.libsu.io)
|
||||
|
||||
implementation(libs.dev.rikka.rikkax.parcelablelist)
|
||||
|
||||
@@ -111,4 +113,7 @@ dependencies {
|
||||
implementation(libs.sheet.compose.dialogs.core)
|
||||
implementation(libs.sheet.compose.dialogs.list)
|
||||
implementation(libs.sheet.compose.dialogs.input)
|
||||
|
||||
implementation(libs.markdown)
|
||||
implementation(libs.androidx.webkit)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/Theme.KernelSU"
|
||||
tools:targetApi="33">
|
||||
<activity
|
||||
|
||||
@@ -156,13 +156,6 @@ Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg,
|
||||
env->SetBooleanField(obj, allowSuField, false);
|
||||
env->SetBooleanField(obj, nonRootUseDefaultField, true);
|
||||
|
||||
jobject capList = env->GetObjectField(obj, capabilitiesField);
|
||||
int DEFAULT_CAPS[] = {CAP_DAC_READ_SEARCH};
|
||||
|
||||
for (auto i: DEFAULT_CAPS) {
|
||||
addIntToList(env, capList, i);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||
import java.io.File
|
||||
|
||||
lateinit var ksuApp: KernelSUApplication
|
||||
|
||||
@@ -24,6 +25,11 @@ class KernelSUApplication : Application() {
|
||||
}
|
||||
.build()
|
||||
)
|
||||
|
||||
val webroot = File(dataDir, "webroot")
|
||||
if (!webroot.exists()) {
|
||||
webroot.mkdir()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,13 @@ object Natives {
|
||||
// 10931: app profile struct add 'version' field
|
||||
// 10946: add capabilities
|
||||
// 10977: change groups_count and groups to avoid overflow write
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 10977
|
||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 11071
|
||||
|
||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||
|
||||
const val ROOT_UID = 0
|
||||
const val ROOT_GID = 0
|
||||
|
||||
init {
|
||||
System.loadLibrary("kernelsu")
|
||||
@@ -44,7 +50,6 @@ object Natives {
|
||||
external fun setAppProfile(profile: Profile?): Boolean
|
||||
|
||||
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
|
||||
private const val ROOT_DEFAULT_PROFILE_KEY = "#"
|
||||
private const val NOBODY_UID = 9999
|
||||
|
||||
fun setDefaultUmountModules(umountModules: Boolean): Boolean {
|
||||
@@ -84,20 +89,21 @@ object Natives {
|
||||
// these are used for root profile
|
||||
val rootUseDefault: Boolean = true,
|
||||
val rootTemplate: String? = null,
|
||||
val uid: Int = 0,
|
||||
val gid: Int = 0,
|
||||
val uid: Int = ROOT_UID,
|
||||
val gid: Int = ROOT_GID,
|
||||
val groups: List<Int> = mutableListOf(),
|
||||
val capabilities: List<Int> = mutableListOf(),
|
||||
val context: String = "u:r:su:s0",
|
||||
val namespace: Int = Namespace.Inherited.ordinal,
|
||||
val context: String = KERNEL_SU_DOMAIN,
|
||||
val namespace: Int = Namespace.INHERITED.ordinal,
|
||||
|
||||
val nonRootUseDefault: Boolean = true,
|
||||
val umountModules: Boolean = true,
|
||||
var rules: String = "", // this field is save in ksud!!
|
||||
) : Parcelable {
|
||||
enum class Namespace {
|
||||
Inherited,
|
||||
Global,
|
||||
Individual,
|
||||
INHERITED,
|
||||
GLOBAL,
|
||||
INDIVIDUAL,
|
||||
}
|
||||
|
||||
constructor() : this("")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.weishu.kernelsu.profile
|
||||
|
||||
/**
|
||||
* https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h
|
||||
* @author weishu
|
||||
* @date 2023/6/3.
|
||||
*/
|
||||
@@ -60,17 +61,55 @@ enum class Groups(val gid: Int, val display: String, val desc: String) {
|
||||
FIREWALL(1048, "firewall", "firewall process"),
|
||||
TRUNKS(1049, "trunks", "trunksd process"),
|
||||
NVRAM(1050, "nvram", "nvram daemon"),
|
||||
DNS_TETHER(1051, "dns_tether", "dns_tether device"),
|
||||
DNS_TETHER_RESERVED(1052, "dns_tether_reserved", "Reserved range for dns_tether"),
|
||||
WEBVIEW_ZYGOTE(1053, "webview_zygote", "zygote process"),
|
||||
WEBVIEW_USER(1054, "webview_user", "webview chromium user"),
|
||||
ETHERNET(1055, "ethernet", "Ethernet"),
|
||||
TOMBSTONED(1056, "tombstoned", "tombstoned process"),
|
||||
GRAPHICS_RW(1057, "graphics_rw", "graphics devices"),
|
||||
DNS(1051, "dns", "DNS resolution daemon (system: netd)"),
|
||||
DNS_TETHER(1052, "dns_tether", "DNS resolution daemon (tether: dnsmasq)"),
|
||||
WEBVIEW_ZYGOTE(1053, "webview_zygote", "WebView zygote process"),
|
||||
VEHICLE_NETWORK(1054, "vehicle_network", "Vehicle network service"),
|
||||
MEDIA_AUDIO(1055, "media_audio", "GID for audio files on internal media storage"),
|
||||
MEDIA_VIDEO(1056, "media_video", "GID for video files on internal media storage"),
|
||||
MEDIA_IMAGE(1057, "media_image", "GID for image files on internal media storage"),
|
||||
TOMBSTONED(1058, "tombstoned", "tombstoned user"),
|
||||
MEDIA_OBB(1059, "media_obb", "GID for OBB files on internal media storage"),
|
||||
ESE(1060, "ese", "embedded secure element (eSE) subsystem"),
|
||||
OTA_UPDATE(1061, "ota_update", "resource tracking UID for OTA updates"),
|
||||
AUTOMOTIVE_EVS(1062, "automotive_evs", "Automotive rear and surround view system"),
|
||||
LOWPAN(1063, "lowpan", "LoWPAN subsystem"),
|
||||
HSM(1064, "lowpan", "hardware security module subsystem"),
|
||||
RESERVED_DISK(1065, "reserved_disk", "GID that has access to reserved disk space"),
|
||||
STATSD(1066, "statsd", "statsd daemon"),
|
||||
INCIDENTD(1067, "incidentd", "incidentd daemon"),
|
||||
SECURE_ELEMENT(1068, "secure_element", "secure element subsystem"),
|
||||
LMKD(1069, "lmkd", "low memory killer daemon"),
|
||||
LLKD(1070, "llkd", "live lock daemon"),
|
||||
IORAPD(1071, "iorapd", "input/output readahead and pin daemon"),
|
||||
GPU_SERVICE(1072, "gpu_service", "GPU service daemon"),
|
||||
NETWORK_STACK(1073, "network_stack", "network stack service"),
|
||||
GSID(1074, "GSID", "GSI service daemon"),
|
||||
FSVERITY_CERT(1075, "fsverity_cert", "fs-verity key ownership in keystore"),
|
||||
CREDSTORE(1076, "credstore", "identity credential manager service"),
|
||||
EXTERNAL_STORAGE(1077, "external_storage", "Full external storage access including USB OTG volumes"),
|
||||
EXT_DATA_RW(1078, "ext_data_rw", "GID for app-private data directories on external storage"),
|
||||
EXT_OBB_RW(1079, "ext_obb_rw", "GID for OBB directories on external storage"),
|
||||
CONTEXT_HUB(1080, "context_hub", "GID for access to the Context Hub"),
|
||||
VIRTUALIZATIONSERVICE(1081, "virtualizationservice", "VirtualizationService daemon"),
|
||||
ARTD(1082, "artd", "ART Service daemon"),
|
||||
UWB(1083, "uwb", "UWB subsystem"),
|
||||
THREAD_NETWORK(1084, "thread_network", "Thread Network subsystem"),
|
||||
DICED(1085, "diced", "Android's DICE daemon"),
|
||||
DMESGD(1086, "dmesgd", "dmesg parsing daemon for kernel report collection"),
|
||||
JC_WEAVER(1087, "jc_weaver", "Javacard Weaver HAL - to manage omapi ARA rules"),
|
||||
JC_STRONGBOX(1088, "jc_strongbox", "Javacard Strongbox HAL - to manage omapi ARA rules"),
|
||||
JC_IDENTITYCRED(1089, "jc_identitycred", "Javacard Identity Cred HAL - to manage omapi ARA rules"),
|
||||
SDK_SANDBOX(1090, "sdk_sandbox", "SDK sandbox virtual UID"),
|
||||
SECURITY_LOG_WRITER(1091, "security_log_writer", "write to security log"),
|
||||
PRNG_SEEDER(1092, "prng_seeder", "PRNG seeder daemon"),
|
||||
|
||||
SHELL(2000, "shell", "adb and debug shell user"),
|
||||
CACHE(2001, "cache", "cache access"),
|
||||
DIAG(2002, "diag", "diagnostics"),
|
||||
DIAG(2002, "diag", "access to diagnostic resources"),
|
||||
|
||||
/* The 3000 series are intended for use as supplemental group id's only.
|
||||
* They indicate special Android capabilities that the kernel is aware of. */
|
||||
NET_BT_ADMIN(3001, "net_bt_admin", "bluetooth: create any socket"),
|
||||
NET_BT(3002, "net_bt", "bluetooth: create sco, rfcomm or l2cap sockets"),
|
||||
INET(3003, "inet", "can create AF_INET and AF_INET6 sockets"),
|
||||
@@ -79,7 +118,11 @@ enum class Groups(val gid: Int, val display: String, val desc: String) {
|
||||
NET_BW_STATS(3006, "net_bw_stats", "read bandwidth statistics"),
|
||||
NET_BW_ACCT(3007, "net_bw_acct", "change bandwidth statistics accounting"),
|
||||
NET_BT_STACK(3008, "net_bt_stack", "access to various bluetooth management functions"),
|
||||
QCOM_DIAG(3009, "qcom_diag", "allow msm specific diag commands"),
|
||||
READPROC(3009, "readproc", "Allow /proc read access"),
|
||||
WAKELOCK(3010, "wakelock", "Allow system wakelock read/write access"),
|
||||
UHID(3011, "uhid", "Allow read/write to /dev/uhid node"),
|
||||
READTRACEFS(3012, "readtracefs", "Allow tracefs read"),
|
||||
|
||||
EVERYBODY(9997, "everybody", "Shared external storage read/write"),
|
||||
MISC(9998, "misc", "Access to misc storage"),
|
||||
NOBODY(9999, "nobody", "Reserved"),
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.navigation.popBackStack
|
||||
@@ -32,6 +33,7 @@ import me.weishu.kernelsu.ui.screen.NavGraphs
|
||||
import me.weishu.kernelsu.ui.theme.KernelSUTheme
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.util.rootAvailable
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@@ -43,8 +45,11 @@ class MainActivity : ComponentActivity() {
|
||||
KernelSUTheme {
|
||||
val navController = rememberAnimatedNavController()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val route = navBackStackEntry?.destination?.route
|
||||
val showBottomBar = route == null || !route.startsWith("web_screen")
|
||||
Scaffold(
|
||||
bottomBar = { BottomBar(navController) },
|
||||
bottomBar = { if (showBottomBar) BottomBar(navController) },
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||
) { innerPadding ->
|
||||
CompositionLocalProvider(
|
||||
@@ -66,7 +71,7 @@ class MainActivity : ComponentActivity() {
|
||||
@Composable
|
||||
private fun BottomBar(navController: NavHostController) {
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val fullFeatured = isManager && !Natives.requireNewKernel()
|
||||
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
||||
NavigationBar(tonalElevation = 8.dp) {
|
||||
BottomBarDestination.values().forEach { destination ->
|
||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
package me.weishu.kernelsu.ui.component
|
||||
|
||||
import android.graphics.text.LineBreaker
|
||||
import android.text.Layout
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.utils.NoCopySpannableFactory
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -36,6 +49,7 @@ interface PromptDialogVisuals : DialogVisuals {
|
||||
interface ConfirmDialogVisuals : PromptDialogVisuals {
|
||||
val confirm: String?
|
||||
val dismiss: String?
|
||||
val isMarkdown: Boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -68,15 +82,15 @@ class DialogHostState {
|
||||
private object LoadingDialogVisualsImpl : LoadingDialogVisuals
|
||||
|
||||
private data class PromptDialogVisualsImpl(
|
||||
override val title: String,
|
||||
override val content: String
|
||||
override val title: String, override val content: String
|
||||
) : PromptDialogVisuals
|
||||
|
||||
private data class ConfirmDialogVisualsImpl(
|
||||
override val title: String,
|
||||
override val content: String,
|
||||
override val confirm: String?,
|
||||
override val dismiss: String?
|
||||
override val dismiss: String?,
|
||||
override val isMarkdown: Boolean,
|
||||
) : ConfirmDialogVisuals
|
||||
|
||||
private data class LoadingDialogDataImpl(
|
||||
@@ -121,8 +135,7 @@ class DialogHostState {
|
||||
mutex.withLock {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
currentDialogData = LoadingDialogDataImpl(
|
||||
visuals = LoadingDialogVisualsImpl,
|
||||
continuation = continuation
|
||||
visuals = LoadingDialogVisualsImpl, continuation = continuation
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -161,13 +174,14 @@ class DialogHostState {
|
||||
suspend fun showConfirm(
|
||||
title: String,
|
||||
content: String,
|
||||
markdown: Boolean = false,
|
||||
confirm: String? = null,
|
||||
dismiss: String? = null
|
||||
): ConfirmResult = mutex.withLock {
|
||||
try {
|
||||
return@withLock suspendCancellableCoroutine { continuation ->
|
||||
currentDialogData = ConfirmDialogDataImpl(
|
||||
visuals = ConfirmDialogVisualsImpl(title, content, confirm, dismiss),
|
||||
visuals = ConfirmDialogVisualsImpl(title, content, confirm, dismiss, markdown),
|
||||
continuation = continuation
|
||||
)
|
||||
}
|
||||
@@ -201,9 +215,7 @@ fun LoadingDialog(
|
||||
}
|
||||
Dialog(onDismissRequest = {}, properties = dialogProperties) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(100.dp),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
modifier = Modifier.size(100.dp), shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
@@ -240,11 +252,13 @@ fun PromptDialog(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
|
||||
val confirmDialogData = state.currentDialogData.tryInto<ConfirmDialogData>() ?: return
|
||||
|
||||
val visuals = confirmDialogData.visuals
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
confirmDialogData.dismiss()
|
||||
@@ -253,7 +267,11 @@ fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
|
||||
Text(text = visuals.title)
|
||||
},
|
||||
text = {
|
||||
Text(text = visuals.content)
|
||||
if (visuals.isMarkdown) {
|
||||
MarkdownContent(content = visuals.content)
|
||||
} else {
|
||||
Text(text = visuals.content)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { confirmDialogData.confirm() }) {
|
||||
@@ -266,4 +284,28 @@ fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@Composable
|
||||
private fun MarkdownContent(content: String) {
|
||||
val contentColor = LocalContentColor.current
|
||||
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
TextView(context).apply {
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
setSpannableFactory(NoCopySpannableFactory.getInstance())
|
||||
breakStrategy = LineBreaker.BREAK_STRATEGY_SIMPLE
|
||||
hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
update = {
|
||||
Markwon.create(it.context).setMarkdown(it, content)
|
||||
it.setTextColor(contentColor.toArgb())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package me.weishu.kernelsu.ui.component
|
||||
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
|
||||
@Composable
|
||||
fun KeyEventBlocker(predicate: (KeyEvent) -> Boolean) {
|
||||
val requester = remember { FocusRequester() }
|
||||
Box(
|
||||
Modifier
|
||||
.onKeyEvent {
|
||||
predicate(it)
|
||||
}
|
||||
.focusRequester(requester)
|
||||
.focusable()
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
requester.requestFocus()
|
||||
}
|
||||
}
|
||||
@@ -74,9 +74,9 @@ fun RootProfileConfig(
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val currentNamespace = when (profile.namespace) {
|
||||
Natives.Profile.Namespace.Inherited.ordinal -> stringResource(R.string.profile_namespace_inherited)
|
||||
Natives.Profile.Namespace.Global.ordinal -> stringResource(R.string.profile_namespace_global)
|
||||
Natives.Profile.Namespace.Individual.ordinal -> stringResource(R.string.profile_namespace_individual)
|
||||
Natives.Profile.Namespace.INHERITED.ordinal -> stringResource(R.string.profile_namespace_inherited)
|
||||
Natives.Profile.Namespace.GLOBAL.ordinal -> stringResource(R.string.profile_namespace_global)
|
||||
Natives.Profile.Namespace.INDIVIDUAL.ordinal -> stringResource(R.string.profile_namespace_individual)
|
||||
else -> stringResource(R.string.profile_namespace_inherited)
|
||||
}
|
||||
ListItem(headlineContent = {
|
||||
@@ -104,21 +104,21 @@ fun RootProfileConfig(
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_inherited)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Inherited.ordinal))
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.INHERITED.ordinal))
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_global)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Global.ordinal))
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.GLOBAL.ordinal))
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_individual)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Individual.ordinal))
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.INDIVIDUAL.ordinal))
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
@@ -175,6 +175,7 @@ fun RootProfileConfig(
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
context = domain,
|
||||
rules = rules,
|
||||
rootUseDefault = false
|
||||
)
|
||||
)
|
||||
@@ -190,7 +191,19 @@ fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>)
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialog) {
|
||||
val groups = Groups.values()
|
||||
val groups = Groups.values().sortedWith(
|
||||
compareBy<Groups> { if (selected.contains(it)) 0 else 1 }
|
||||
.then(compareBy {
|
||||
when (it) {
|
||||
Groups.ROOT -> 0
|
||||
Groups.SYSTEM -> 1
|
||||
Groups.SHELL -> 2
|
||||
else -> Int.MAX_VALUE
|
||||
}
|
||||
})
|
||||
.then(compareBy { it.name })
|
||||
|
||||
)
|
||||
val options = groups.map { value ->
|
||||
ListOption(
|
||||
titleText = value.display,
|
||||
@@ -256,7 +269,10 @@ fun CapsPanel(
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialog) {
|
||||
val caps = Capabilities.values()
|
||||
val caps = Capabilities.values().sortedWith(
|
||||
compareBy<Capabilities> { if (selected.contains(it)) 0 else 1 }
|
||||
.then(compareBy { it.name })
|
||||
)
|
||||
val options = caps.map { value ->
|
||||
ListOption(
|
||||
titleText = value.display,
|
||||
@@ -357,11 +373,14 @@ private fun UidPanel(uid: Int, label: String, onUidChange: (Int) -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SELinuxPanel(profile: Natives.Profile, onSELinuxChange: (domain: String, rules: String) -> Unit) {
|
||||
private fun SELinuxPanel(
|
||||
profile: Natives.Profile,
|
||||
onSELinuxChange: (domain: String, rules: String) -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
if (showDialog) {
|
||||
var domain by remember { mutableStateOf(profile.context) }
|
||||
var rules by remember { mutableStateOf("") }
|
||||
var rules by remember { mutableStateOf(profile.rules) }
|
||||
|
||||
val inputOptions = listOf(
|
||||
InputTextField(
|
||||
@@ -382,7 +401,7 @@ private fun SELinuxPanel(profile: Natives.Profile, onSELinuxChange: (domain: Str
|
||||
// value can be a-zA-Z0-9_
|
||||
val regex = Regex("^[a-z_]+:[a-z0-9_]+:[a-z0-9_]+(:[a-z0-9_]+)?$")
|
||||
if (value?.matches(regex) == true) ValidationResult.Valid
|
||||
else ValidationResult.Invalid("Domain must be valid sepolicy")
|
||||
else ValidationResult.Invalid("Domain must be in the format of \"user:role:type:level\"")
|
||||
}
|
||||
),
|
||||
InputTextField(
|
||||
@@ -393,7 +412,6 @@ private fun SELinuxPanel(profile: Natives.Profile, onSELinuxChange: (domain: Str
|
||||
type = InputTextFieldType.OUTLINED,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Ascii,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
singleLine = false,
|
||||
resultListener = {
|
||||
@@ -401,8 +419,8 @@ private fun SELinuxPanel(profile: Natives.Profile, onSELinuxChange: (domain: Str
|
||||
},
|
||||
validationListener = { value ->
|
||||
if (isSepolicyValid(value)) ValidationResult.Valid
|
||||
else ValidationResult.Invalid("Rules must be valid sepolicy")
|
||||
},
|
||||
else ValidationResult.Invalid("SELinux rules is invalid!")
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package me.weishu.kernelsu.ui.component.profile
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||
import androidx.compose.material.icons.filled.Create
|
||||
import androidx.compose.material.icons.filled.ReadMore
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
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.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.util.listAppProfileTemplates
|
||||
import me.weishu.kernelsu.ui.util.setSepolicy
|
||||
import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/10/21.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TemplateConfig(
|
||||
profile: Natives.Profile,
|
||||
onViewTemplate: (id: String) -> Unit = {},
|
||||
onManageTemplate: () -> Unit = {},
|
||||
onProfileChange: (Natives.Profile) -> Unit
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var template by rememberSaveable {
|
||||
mutableStateOf(profile.rootTemplate ?: "")
|
||||
}
|
||||
val profileTemplates = listAppProfileTemplates()
|
||||
val noTemplates = profileTemplates.isEmpty()
|
||||
|
||||
ListItem(headlineContent = {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = it },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.menuAnchor()
|
||||
.fillMaxWidth(),
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(R.string.profile_template)) },
|
||||
value = template.ifEmpty { "None" },
|
||||
onValueChange = {},
|
||||
trailingIcon = {
|
||||
if (noTemplates) {
|
||||
IconButton(
|
||||
onClick = onManageTemplate
|
||||
) {
|
||||
Icon(Icons.Filled.Create, null)
|
||||
}
|
||||
} else if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
|
||||
else Icon(Icons.Filled.ArrowDropDown, null)
|
||||
},
|
||||
)
|
||||
if (profileTemplates.isEmpty()) {
|
||||
return@ExposedDropdownMenuBox
|
||||
}
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
profileTemplates.forEach { tid ->
|
||||
val templateInfo =
|
||||
getTemplateInfoById(tid) ?: return@forEach
|
||||
DropdownMenuItem(
|
||||
text = { Text(tid) },
|
||||
onClick = {
|
||||
template = tid
|
||||
if (setSepolicy(tid, templateInfo.rules.joinToString("\n"))) {
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
rootTemplate = tid,
|
||||
rootUseDefault = false,
|
||||
uid = templateInfo.uid,
|
||||
gid = templateInfo.gid,
|
||||
groups = templateInfo.groups,
|
||||
capabilities = templateInfo.capabilities,
|
||||
context = templateInfo.context,
|
||||
namespace = templateInfo.namespace,
|
||||
)
|
||||
)
|
||||
}
|
||||
expanded = false
|
||||
},
|
||||
trailingIcon = {
|
||||
IconButton(onClick = {
|
||||
onViewTemplate(tid)
|
||||
}) {
|
||||
Icon(Icons.Filled.ReadMore, null)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -16,17 +18,15 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.material.icons.filled.Android
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@@ -38,10 +38,14 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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 coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
@@ -53,8 +57,17 @@ import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig
|
||||
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
|
||||
import me.weishu.kernelsu.ui.component.profile.TemplateConfig
|
||||
import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination
|
||||
import me.weishu.kernelsu.ui.screen.destinations.TemplateEditorScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.util.forceStopApp
|
||||
import me.weishu.kernelsu.ui.util.getSepolicy
|
||||
import me.weishu.kernelsu.ui.util.launchApp
|
||||
import me.weishu.kernelsu.ui.util.restartApp
|
||||
import me.weishu.kernelsu.ui.util.setSepolicy
|
||||
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
||||
import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -71,16 +84,20 @@ fun AppProfileScreen(
|
||||
val scope = rememberCoroutineScope()
|
||||
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 packageName = appInfo.packageName
|
||||
val initialProfile = Natives.getAppProfile(packageName, appInfo.uid)
|
||||
if (initialProfile.allowSu) {
|
||||
initialProfile.rules = getSepolicy(packageName)
|
||||
}
|
||||
var profile by rememberSaveable {
|
||||
mutableStateOf(Natives.getAppProfile(packageName, appInfo.uid))
|
||||
mutableStateOf(initialProfile)
|
||||
}
|
||||
|
||||
Log.i("mylog", "profile: $profile")
|
||||
|
||||
Scaffold(
|
||||
topBar = { TopBar { navigator.popBackStack() } }
|
||||
topBar = { TopBar { navigator.popBackStack() } },
|
||||
) { paddingValues ->
|
||||
AppProfileInner(
|
||||
modifier = Modifier
|
||||
@@ -90,9 +107,7 @@ fun AppProfileScreen(
|
||||
appLabel = appInfo.label,
|
||||
appIcon = {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(appInfo.packageInfo)
|
||||
.crossfade(true)
|
||||
model = ImageRequest.Builder(context).data(appInfo.packageInfo).crossfade(true)
|
||||
.build(),
|
||||
contentDescription = appInfo.label,
|
||||
modifier = Modifier
|
||||
@@ -102,8 +117,22 @@ fun AppProfileScreen(
|
||||
)
|
||||
},
|
||||
profile = profile,
|
||||
onViewTemplate = {
|
||||
getTemplateInfoById(it)?.let { info ->
|
||||
navigator.navigate(TemplateEditorScreenDestination(info))
|
||||
}
|
||||
},
|
||||
onManageTemplate = {
|
||||
navigator.navigate(AppProfileTemplateScreenDestination())
|
||||
},
|
||||
onProfileChange = {
|
||||
scope.launch {
|
||||
if (it.allowSu && !it.rootUseDefault && it.rules.isNotEmpty()) {
|
||||
if (!setSepolicy(profile.name, it.rules)) {
|
||||
snackbarHost.showSnackbar(failToUpdateSepolicy)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
if (!Natives.setAppProfile(it)) {
|
||||
snackbarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
|
||||
} else {
|
||||
@@ -115,7 +144,6 @@ fun AppProfileScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AppProfileInner(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -123,16 +151,20 @@ private fun AppProfileInner(
|
||||
appLabel: String,
|
||||
appIcon: @Composable () -> Unit,
|
||||
profile: Natives.Profile,
|
||||
onViewTemplate: (id: String) -> Unit = {},
|
||||
onManageTemplate: () -> Unit = {},
|
||||
onProfileChange: (Natives.Profile) -> Unit,
|
||||
) {
|
||||
val isRootGranted = profile.allowSu
|
||||
|
||||
Column(modifier = modifier) {
|
||||
ListItem(
|
||||
headlineContent = { Text(appLabel) },
|
||||
supportingContent = { Text(packageName) },
|
||||
leadingContent = appIcon,
|
||||
)
|
||||
AppMenuBox(packageName) {
|
||||
ListItem(
|
||||
headlineContent = { Text(appLabel) },
|
||||
supportingContent = { Text(packageName) },
|
||||
leadingContent = appIcon,
|
||||
)
|
||||
}
|
||||
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Security,
|
||||
@@ -154,7 +186,7 @@ private fun AppProfileInner(
|
||||
var mode by remember {
|
||||
mutableStateOf(initialMode)
|
||||
}
|
||||
ProfileBox(mode, false) {
|
||||
ProfileBox(mode, true) {
|
||||
// template mode shouldn't change profile here!
|
||||
if (it == Mode.Default || it == Mode.Custom) {
|
||||
onProfileChange(profile.copy(rootUseDefault = it == Mode.Default))
|
||||
@@ -163,43 +195,12 @@ private fun AppProfileInner(
|
||||
}
|
||||
Crossfade(targetState = mode, label = "") { currentMode ->
|
||||
if (currentMode == Mode.Template) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val templateNone = "None"
|
||||
var template by rememberSaveable {
|
||||
mutableStateOf(
|
||||
profile.rootTemplate
|
||||
?: templateNone
|
||||
)
|
||||
}
|
||||
ListItem(headlineContent = {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = it },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.menuAnchor(),
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(R.string.profile_template)) },
|
||||
value = template,
|
||||
onValueChange = {
|
||||
if (template != templateNone) {
|
||||
onProfileChange(
|
||||
profile.copy(
|
||||
rootTemplate = it,
|
||||
rootUseDefault = false
|
||||
)
|
||||
)
|
||||
template = it
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
|
||||
else Icon(Icons.Filled.ArrowDropDown, null)
|
||||
},
|
||||
)
|
||||
// TODO: Template
|
||||
}
|
||||
})
|
||||
TemplateConfig(
|
||||
profile = profile,
|
||||
onViewTemplate = onViewTemplate,
|
||||
onManageTemplate = onManageTemplate,
|
||||
onProfileChange = onProfileChange
|
||||
)
|
||||
} else if (mode == Mode.Custom) {
|
||||
RootProfileConfig(
|
||||
fixedName = true,
|
||||
@@ -229,9 +230,7 @@ private fun AppProfileInner(
|
||||
}
|
||||
|
||||
private enum class Mode(@StringRes private val res: Int) {
|
||||
Default(R.string.profile_default),
|
||||
Template(R.string.profile_template),
|
||||
Custom(R.string.profile_custom);
|
||||
Default(R.string.profile_default), Template(R.string.profile_template), Custom(R.string.profile_custom);
|
||||
|
||||
val text: String
|
||||
@Composable get() = stringResource(res)
|
||||
@@ -267,8 +266,7 @@ private fun ProfileBox(
|
||||
Divider(thickness = Dp.Hairline)
|
||||
ListItem(headlineContent = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
FilterChip(
|
||||
selected = mode == Mode.Default,
|
||||
@@ -291,6 +289,63 @@ private fun ProfileBox(
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) {
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var touchPoint: Offset by remember { mutableStateOf(Offset.Zero) }
|
||||
val density = LocalDensity.current
|
||||
|
||||
BoxWithConstraints(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures {
|
||||
touchPoint = it
|
||||
expanded = true
|
||||
}
|
||||
}) {
|
||||
|
||||
content()
|
||||
|
||||
val (offsetX, offsetY) = with(density) {
|
||||
(touchPoint.x.toDp()) to (touchPoint.y.toDp())
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
offset = DpOffset(offsetX, -offsetY),
|
||||
onDismissRequest = {
|
||||
expanded = false
|
||||
},
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(id = R.string.launch_app)) },
|
||||
onClick = {
|
||||
expanded = false
|
||||
launchApp(packageName)
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(id = R.string.force_stop_app)) },
|
||||
onClick = {
|
||||
expanded = false
|
||||
forceStopApp(packageName)
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(id = R.string.restart_app)) },
|
||||
onClick = {
|
||||
expanded = false
|
||||
restartApp(packageName)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AppProfilePreview() {
|
||||
|
||||
@@ -23,16 +23,18 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
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 com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.weishu.kernelsu.*
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
||||
import me.weishu.kernelsu.ui.component.ConfirmResult
|
||||
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.*
|
||||
|
||||
@@ -67,11 +69,22 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
)
|
||||
}
|
||||
UpdateCard()
|
||||
if (!rootAvailable()) {
|
||||
WarningCard(
|
||||
stringResource(id = R.string.grant_root_failed)
|
||||
)
|
||||
}
|
||||
val checkUpdate =
|
||||
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("check_update", true)
|
||||
if (checkUpdate) {
|
||||
UpdateCard()
|
||||
}
|
||||
InfoCard()
|
||||
DonateCard()
|
||||
LearnMoreCard()
|
||||
Spacer(Modifier)
|
||||
ConfirmDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,22 +92,37 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
@Composable
|
||||
fun UpdateCard() {
|
||||
val context = LocalContext.current
|
||||
val newVersion by produceState(initialValue = 0 to "") {
|
||||
val newVersion by produceState(initialValue = Triple(0, "", "")) {
|
||||
value = withContext(Dispatchers.IO) { checkNewVersion() }
|
||||
}
|
||||
val currentVersionCode = getManagerVersion(context).second
|
||||
val newVersionCode = newVersion.first
|
||||
val newVersionUrl = newVersion.second
|
||||
val changelog = newVersion.third
|
||||
if (newVersionCode <= currentVersionCode) {
|
||||
return
|
||||
}
|
||||
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val dialogHost = LocalDialogHost.current
|
||||
val title = stringResource(id = R.string.module_changelog)
|
||||
val updateText = stringResource(id = R.string.module_update)
|
||||
val scope = rememberCoroutineScope()
|
||||
WarningCard(
|
||||
message = stringResource(id = R.string.new_version_available).format(newVersionCode),
|
||||
MaterialTheme.colorScheme.outlineVariant
|
||||
) {
|
||||
uriHandler.openUri(newVersionUrl)
|
||||
scope.launch {
|
||||
if (changelog.isEmpty() || dialogHost.showConfirm(
|
||||
title = title,
|
||||
content = changelog,
|
||||
markdown = true,
|
||||
confirm = updateText,
|
||||
) == ConfirmResult.Confirmed
|
||||
) {
|
||||
uriHandler.openUri(newVersionUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +244,6 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.home_unsupported),
|
||||
fontFamily = FontFamily.Serif,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
@@ -233,7 +260,7 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
|
||||
@Composable
|
||||
fun WarningCard(
|
||||
message: String, color: Color = MaterialTheme.colorScheme.error, onClick: () -> Unit = {}
|
||||
message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null
|
||||
) {
|
||||
ElevatedCard(
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
@@ -243,16 +270,12 @@ fun WarningCard(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.then(onClick?.let { Modifier.clickable { it() } } ?: Modifier)
|
||||
.padding(24.dp)
|
||||
.clickable {
|
||||
onClick()
|
||||
}, verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column() {
|
||||
Text(
|
||||
text = message, style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = message, style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,3 +387,15 @@ private fun StatusCardPreview() {
|
||||
StatusCard(KernelVersion(4, 10, 101), null)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun WarningCardPreview() {
|
||||
Column {
|
||||
WarningCard(message = "Warning message")
|
||||
WarningCard(
|
||||
message = "Warning message ",
|
||||
MaterialTheme.colorScheme.outlineVariant,
|
||||
onClick = {})
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,13 @@ import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
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 com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -23,6 +28,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.KeyEventBlocker
|
||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||
import me.weishu.kernelsu.ui.util.installModule
|
||||
import me.weishu.kernelsu.ui.util.reboot
|
||||
@@ -34,15 +40,18 @@ import java.util.*
|
||||
* @author weishu
|
||||
* @date 2023/1/1.
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
@Destination
|
||||
fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
|
||||
|
||||
var text by remember { mutableStateOf("") }
|
||||
var showFloatAction by remember { mutableStateOf(false) }
|
||||
var text by rememberSaveable { mutableStateOf("") }
|
||||
val logContent = StringBuilder()
|
||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (text.isNotEmpty()) {
|
||||
@@ -53,9 +62,15 @@ fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
|
||||
if (success) {
|
||||
showFloatAction = true
|
||||
}
|
||||
}) {
|
||||
}, onStdout = {
|
||||
text += "$it\n"
|
||||
}
|
||||
scope.launch {
|
||||
scrollState.animateScrollTo(scrollState.maxValue)
|
||||
}
|
||||
logContent.append(it).append("\n")
|
||||
}, onStderr = {
|
||||
logContent.append(it).append("\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +88,7 @@ fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
"KernelSU_install_log_${date}.log"
|
||||
)
|
||||
file.writeText(text)
|
||||
file.writeText(logContent.toString())
|
||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
||||
}
|
||||
}
|
||||
@@ -97,17 +112,20 @@ fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
|
||||
|
||||
}
|
||||
) { innerPadding ->
|
||||
KeyEventBlocker {
|
||||
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(1f)
|
||||
.padding(innerPadding)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
.verticalScroll(scrollState),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = text,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@@ -19,6 +20,7 @@ import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -33,14 +35,19 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
||||
import me.weishu.kernelsu.ui.component.ConfirmResult
|
||||
import me.weishu.kernelsu.ui.component.LoadingDialog
|
||||
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
||||
import me.weishu.kernelsu.ui.screen.destinations.WebScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.*
|
||||
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
@Destination
|
||||
@Composable
|
||||
@@ -48,7 +55,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
val viewModel = viewModel<ModuleViewModel>()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.moduleList.isEmpty()) {
|
||||
if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) {
|
||||
viewModel.fetchModuleList()
|
||||
}
|
||||
}
|
||||
@@ -76,6 +83,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
navigator.navigate(InstallScreenDestination(uri))
|
||||
|
||||
viewModel.markNeedRefresh()
|
||||
|
||||
Log.i("ModuleScreen", "select zip result: ${it.data}")
|
||||
}
|
||||
|
||||
@@ -94,6 +103,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
ConfirmDialog()
|
||||
|
||||
LoadingDialog()
|
||||
|
||||
when {
|
||||
hasMagisk -> {
|
||||
Box(
|
||||
@@ -113,10 +124,15 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
ModuleList(
|
||||
viewModel = viewModel, modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
navigator.navigate(InstallScreenDestination(it))
|
||||
}
|
||||
.fillMaxSize(),
|
||||
onInstallModule =
|
||||
{
|
||||
navigator.navigate(InstallScreenDestination(it))
|
||||
}, onClickModule = { id, name, hasWebUi ->
|
||||
if (hasWebUi) {
|
||||
navigator.navigate(WebScreenDestination(id, name))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +141,10 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun ModuleList(
|
||||
viewModel: ModuleViewModel, modifier: Modifier = Modifier, onInstallModule: (Uri) -> Unit
|
||||
viewModel: ModuleViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
onInstallModule: (Uri) -> Unit,
|
||||
onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit
|
||||
) {
|
||||
val failedEnable = stringResource(R.string.module_failed_to_enable)
|
||||
val failedDisable = stringResource(R.string.module_failed_to_disable)
|
||||
@@ -137,9 +156,80 @@ private fun ModuleList(
|
||||
val uninstall = stringResource(id = R.string.uninstall)
|
||||
val cancel = stringResource(id = android.R.string.cancel)
|
||||
val moduleUninstallConfirm = stringResource(id = R.string.module_uninstall_confirm)
|
||||
val updateText = stringResource(R.string.module_update)
|
||||
val changelogText = stringResource(R.string.module_changelog)
|
||||
val downloadingText = stringResource(R.string.module_downloading)
|
||||
val startDownloadingText = stringResource(R.string.module_start_downloading)
|
||||
val fetchChangeLogFailed = stringResource(R.string.module_changelog_failed)
|
||||
|
||||
val dialogHost = LocalDialogHost.current
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val context = LocalContext.current
|
||||
|
||||
suspend fun onModuleUpdate(
|
||||
module: ModuleViewModel.ModuleInfo,
|
||||
changelogUrl: String,
|
||||
downloadUrl: String,
|
||||
fileName: String
|
||||
) {
|
||||
val changelogResult = dialogHost.withLoading {
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
OkHttpClient().newCall(
|
||||
okhttp3.Request.Builder().url(changelogUrl).build()
|
||||
).execute().body!!.string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val showToast: suspend (String) -> Unit = { msg ->
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
msg,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
val changelog = changelogResult.getOrElse {
|
||||
showToast(fetchChangeLogFailed.format(it.message))
|
||||
return
|
||||
}.ifBlank {
|
||||
showToast(fetchChangeLogFailed.format(module.name))
|
||||
return
|
||||
}
|
||||
|
||||
// changelog is not empty, show it and wait for confirm
|
||||
val confirmResult = dialogHost.showConfirm(
|
||||
changelogText,
|
||||
content = changelog,
|
||||
markdown = true,
|
||||
confirm = updateText,
|
||||
)
|
||||
|
||||
if (confirmResult != ConfirmResult.Confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
showToast(startDownloadingText.format(module.name))
|
||||
|
||||
val downloading = downloadingText.format(module.name)
|
||||
withContext(Dispatchers.IO) {
|
||||
download(
|
||||
context,
|
||||
downloadUrl,
|
||||
fileName,
|
||||
downloading,
|
||||
onDownloaded = onInstallModule,
|
||||
onDownloading = {
|
||||
launch(Dispatchers.Main) {
|
||||
Toast.makeText(context, downloading, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) {
|
||||
val confirmResult = dialogHost.showConfirm(
|
||||
@@ -152,7 +242,12 @@ private fun ModuleList(
|
||||
return
|
||||
}
|
||||
|
||||
val success = uninstallModule(module.id)
|
||||
val success = dialogHost.withLoading {
|
||||
withContext(Dispatchers.IO) {
|
||||
uninstallModule(module.id)
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
viewModel.fetchModuleList()
|
||||
}
|
||||
@@ -175,48 +270,70 @@ private fun ModuleList(
|
||||
val refreshState = rememberPullRefreshState(refreshing = viewModel.isRefreshing,
|
||||
onRefresh = { viewModel.fetchModuleList() })
|
||||
Box(modifier.pullRefresh(refreshState)) {
|
||||
if (viewModel.isOverlayAvailable) {
|
||||
val context = LocalContext.current
|
||||
val context = LocalContext.current
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = remember {
|
||||
PaddingValues(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp + 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */
|
||||
)
|
||||
},
|
||||
) {
|
||||
val isEmpty = viewModel.moduleList.isEmpty()
|
||||
if (isEmpty) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = remember {
|
||||
PaddingValues(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp + 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */
|
||||
)
|
||||
},
|
||||
) {
|
||||
when {
|
||||
!viewModel.isOverlayAvailable -> {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center
|
||||
modifier = Modifier.fillParentMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(stringResource(R.string.module_empty))
|
||||
Text(
|
||||
stringResource(R.string.module_overlay_fs_not_available),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
viewModel.moduleList.isEmpty() -> {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillParentMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.module_empty),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
items(viewModel.moduleList) { module ->
|
||||
var isChecked by remember(module) { mutableStateOf(module.enabled) }
|
||||
var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val updateUrl by produceState(initialValue = "") {
|
||||
viewModel.checkUpdate(module) { value = it.orEmpty() }
|
||||
val updatedModule by produceState(initialValue = Triple("", "", "")) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
value = viewModel.checkUpdate(module)
|
||||
}
|
||||
}
|
||||
|
||||
val downloadingText = stringResource(R.string.module_downloading)
|
||||
val startDownloadingText = stringResource(R.string.module_start_downloading)
|
||||
|
||||
ModuleItem(module, isChecked, updateUrl, onUninstall = {
|
||||
ModuleItem(module, isChecked, updatedModule.first, onUninstall = {
|
||||
scope.launch { onModuleUninstall(module) }
|
||||
}, onCheckChanged = {
|
||||
val success = toggleModule(module.id, !isChecked)
|
||||
if (success) {
|
||||
isChecked = it
|
||||
scope.launch {
|
||||
scope.launch {
|
||||
val success = dialogHost.withLoading {
|
||||
withContext(Dispatchers.IO) {
|
||||
toggleModule(module.id, !isChecked)
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
isChecked = it
|
||||
viewModel.fetchModuleList()
|
||||
|
||||
val result = snackBarHost.showSnackbar(
|
||||
@@ -225,32 +342,22 @@ private fun ModuleList(
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
reboot()
|
||||
}
|
||||
} else {
|
||||
val message = if (isChecked) failedDisable else failedEnable
|
||||
snackBarHost.showSnackbar(message.format(module.name))
|
||||
}
|
||||
} else scope.launch {
|
||||
val message = if (isChecked) failedDisable else failedEnable
|
||||
snackBarHost.showSnackbar(message.format(module.name))
|
||||
}
|
||||
}, onUpdate = {
|
||||
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
startDownloadingText.format(module.name),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
onModuleUpdate(
|
||||
module,
|
||||
updatedModule.third,
|
||||
updatedModule.first,
|
||||
"${module.name}-${updatedModule.second}.zip"
|
||||
)
|
||||
}
|
||||
|
||||
val downloading = downloadingText.format(module.name)
|
||||
download(
|
||||
context,
|
||||
updateUrl,
|
||||
"${module.name}-${module.version}.zip",
|
||||
downloading,
|
||||
onDownloaded = onInstallModule,
|
||||
onDownloading = {
|
||||
Toast.makeText(context, downloading, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
)
|
||||
}, onClick = {
|
||||
onClickModule(it.id, it.name, it.hasWebUi)
|
||||
})
|
||||
|
||||
// fix last item shadow incomplete in LazyColumn
|
||||
@@ -258,15 +365,10 @@ private fun ModuleList(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DownloadListener(context, onInstallModule)
|
||||
|
||||
} else {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(stringResource(R.string.module_overlay_fs_not_available))
|
||||
}
|
||||
}
|
||||
|
||||
DownloadListener(context, onInstallModule)
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = viewModel.isRefreshing, state = refreshState, modifier = Modifier.align(
|
||||
Alignment.TopCenter
|
||||
@@ -289,9 +391,12 @@ private fun ModuleItem(
|
||||
onUninstall: (ModuleViewModel.ModuleInfo) -> Unit,
|
||||
onCheckChanged: (Boolean) -> Unit,
|
||||
onUpdate: (ModuleViewModel.ModuleInfo) -> Unit,
|
||||
onClick: (ModuleViewModel.ModuleInfo) -> Unit
|
||||
) {
|
||||
ElevatedCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick(module) },
|
||||
colors = CardDefaults.elevatedCardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
|
||||
@@ -397,6 +502,18 @@ private fun ModuleItem(
|
||||
text = stringResource(R.string.uninstall),
|
||||
)
|
||||
}
|
||||
|
||||
if (module.hasWebUi) {
|
||||
TextButton(
|
||||
onClick = { onClick(module) },
|
||||
) {
|
||||
Text(
|
||||
fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
||||
fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
||||
text = stringResource(R.string.open),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,7 +532,8 @@ fun ModuleItemPreview() {
|
||||
enabled = true,
|
||||
update = true,
|
||||
remove = true,
|
||||
updateJson = ""
|
||||
updateJson = "",
|
||||
hasWebUi = false,
|
||||
)
|
||||
ModuleItem(module, true, "", {}, {}, {})
|
||||
ModuleItem(module, true, "", {}, {}, {}, {})
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -8,7 +9,10 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material.icons.filled.ContactPage
|
||||
import androidx.compose.material.icons.filled.Fence
|
||||
import androidx.compose.material.icons.filled.RemoveModerator
|
||||
import androidx.compose.material.icons.filled.Update
|
||||
import androidx.compose.material.icons.filled.Upgrade
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -27,6 +31,7 @@ import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.component.AboutDialog
|
||||
import me.weishu.kernelsu.ui.component.LoadingDialog
|
||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||
import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination
|
||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||
import me.weishu.kernelsu.ui.util.getBugreportFile
|
||||
|
||||
@@ -56,6 +61,16 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val dialogHost = LocalDialogHost.current
|
||||
|
||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
||||
headlineContent = { Text(profileTemplate) },
|
||||
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||
}
|
||||
)
|
||||
|
||||
var umountChecked by rememberSaveable {
|
||||
mutableStateOf(Natives.isDefaultUmountModules())
|
||||
}
|
||||
@@ -70,8 +85,30 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
var checkUpdate by rememberSaveable {
|
||||
mutableStateOf(
|
||||
prefs.getBoolean("check_update", true)
|
||||
)
|
||||
}
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Update,
|
||||
title = stringResource(id = R.string.settings_check_update),
|
||||
summary = stringResource(id = R.string.settings_check_update_summary),
|
||||
checked = checkUpdate
|
||||
) {
|
||||
prefs.edit().putBoolean("check_update", it).apply()
|
||||
checkUpdate = it
|
||||
}
|
||||
|
||||
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.BugReport, stringResource(id = R.string.send_log)) },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.BugReport,
|
||||
stringResource(id = R.string.send_log)
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(stringResource(id = R.string.send_log)) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
@@ -105,7 +142,12 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
val about = stringResource(id = R.string.about)
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.ContactPage, stringResource(id = R.string.about)) },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.ContactPage,
|
||||
stringResource(id = R.string.about)
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(about) },
|
||||
modifier = Modifier.clickable {
|
||||
showAboutDialog.value = true
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ImportExport
|
||||
import androidx.compose.material.icons.filled.Sync
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.result.ResultRecipient
|
||||
import com.ramcosta.composedestinations.result.getOr
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.ui.screen.destinations.TemplateEditorScreenDestination
|
||||
import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/10/20.
|
||||
*/
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun AppProfileTemplateScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
resultRecipient: ResultRecipient<TemplateEditorScreenDestination, Boolean>
|
||||
) {
|
||||
val viewModel = viewModel<TemplateViewModel>()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.templateList.isEmpty()) {
|
||||
viewModel.fetchTemplates()
|
||||
}
|
||||
}
|
||||
|
||||
// handle result from TemplateEditorScreen, refresh if needed
|
||||
resultRecipient.onNavResult { result ->
|
||||
if (result.getOr { false }) {
|
||||
scope.launch { viewModel.fetchTemplates() }
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val context = LocalContext.current
|
||||
val showToast = fun(msg: String) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
TopBar(onBack = { navigator.popBackStack() },
|
||||
onSync = {
|
||||
scope.launch { viewModel.fetchTemplates(true) }
|
||||
},
|
||||
onImport = {
|
||||
clipboardManager.getText()?.text?.let {
|
||||
if (it.isEmpty()) {
|
||||
showToast(context.getString(R.string.app_profile_template_import_empty))
|
||||
return@let
|
||||
}
|
||||
scope.launch {
|
||||
viewModel.importTemplates(
|
||||
it, {
|
||||
showToast(context.getString(R.string.app_profile_template_import_success))
|
||||
viewModel.fetchTemplates(false)
|
||||
},
|
||||
showToast
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onExport = {
|
||||
scope.launch {
|
||||
viewModel.exportTemplates(
|
||||
{
|
||||
showToast(context.getString(R.string.app_profile_template_export_empty))
|
||||
}
|
||||
) {
|
||||
clipboardManager.setText(AnnotatedString(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
navigator.navigate(
|
||||
TemplateEditorScreenDestination(
|
||||
TemplateViewModel.TemplateInfo(),
|
||||
false
|
||||
)
|
||||
)
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Add, null) },
|
||||
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
val refreshState = rememberPullRefreshState(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
onRefresh = { scope.launch { viewModel.fetchTemplates() } },
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.pullRefresh(refreshState)
|
||||
) {
|
||||
LazyColumn(Modifier.fillMaxSize()) {
|
||||
items(viewModel.templateList, key = { it.id }) { app ->
|
||||
TemplateItem(navigator, app)
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = viewModel.isRefreshing,
|
||||
state = refreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun TemplateItem(
|
||||
navigator: DestinationsNavigator,
|
||||
template: TemplateViewModel.TemplateInfo
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
navigator.navigate(TemplateEditorScreenDestination(template, !template.local))
|
||||
},
|
||||
headlineContent = { Text(template.name) },
|
||||
supportingContent = {
|
||||
Column {
|
||||
Text(
|
||||
text = "${template.id}${if (template.author.isEmpty()) "" else "@${template.author}"}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
)
|
||||
Text(template.description)
|
||||
FlowRow {
|
||||
LabelText(label = "UID: ${template.uid}")
|
||||
LabelText(label = "GID: ${template.gid}")
|
||||
LabelText(label = template.context)
|
||||
if (template.local) {
|
||||
LabelText(label = "local")
|
||||
} else {
|
||||
LabelText(label = "remote")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
onBack: () -> Unit,
|
||||
onSync: () -> Unit = {},
|
||||
onImport: () -> Unit = {},
|
||||
onExport: () -> Unit = {}
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.settings_profile_template))
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onSync) {
|
||||
Icon(
|
||||
Icons.Filled.Sync,
|
||||
contentDescription = stringResource(id = R.string.app_profile_template_sync)
|
||||
)
|
||||
}
|
||||
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = {
|
||||
showDropdown = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ImportExport,
|
||||
contentDescription = stringResource(id = R.string.app_profile_import_export)
|
||||
)
|
||||
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||
showDropdown = false
|
||||
}) {
|
||||
DropdownMenuItem(text = {
|
||||
Text(stringResource(id = R.string.app_profile_import_from_clipboard))
|
||||
}, onClick = {
|
||||
onImport()
|
||||
showDropdown = false
|
||||
})
|
||||
DropdownMenuItem(text = {
|
||||
Text(stringResource(id = R.string.app_profile_export_to_clipboard))
|
||||
}, onClick = {
|
||||
onExport()
|
||||
showDropdown = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.DeleteForever
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
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.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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 com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.profile.Capabilities
|
||||
import me.weishu.kernelsu.profile.Groups
|
||||
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
|
||||
import me.weishu.kernelsu.ui.util.deleteAppProfileTemplate
|
||||
import me.weishu.kernelsu.ui.util.getAppProfileTemplate
|
||||
import me.weishu.kernelsu.ui.util.setAppProfileTemplate
|
||||
import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel
|
||||
import me.weishu.kernelsu.ui.viewmodel.toJSON
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/10/20.
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun TemplateEditorScreen(
|
||||
navigator: ResultBackNavigator<Boolean>,
|
||||
initialTemplate: TemplateViewModel.TemplateInfo,
|
||||
readOnly: Boolean = true,
|
||||
) {
|
||||
|
||||
val isCreation = initialTemplate.id.isBlank()
|
||||
val autoSave = !isCreation
|
||||
|
||||
var template by rememberSaveable {
|
||||
mutableStateOf(initialTemplate)
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
navigator.navigateBack(result = !readOnly)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
val author =
|
||||
if (initialTemplate.author.isNotEmpty()) "@${initialTemplate.author}" else ""
|
||||
val readOnlyHint = if (readOnly) {
|
||||
" - ${stringResource(id = R.string.app_profile_template_readonly)}"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val titleSummary = "${initialTemplate.id}$author$readOnlyHint"
|
||||
val saveTemplateFailed = stringResource(id = R.string.app_profile_template_save_failed)
|
||||
val context = LocalContext.current
|
||||
|
||||
TopBar(
|
||||
title = if (isCreation) {
|
||||
stringResource(R.string.app_profile_template_create)
|
||||
} else if (readOnly) {
|
||||
stringResource(R.string.app_profile_template_view)
|
||||
} else {
|
||||
stringResource(R.string.app_profile_template_edit)
|
||||
},
|
||||
readOnly = readOnly,
|
||||
summary = titleSummary,
|
||||
onBack = { navigator.navigateBack(result = !readOnly) },
|
||||
onDelete = {
|
||||
if (deleteAppProfileTemplate(template.id)) {
|
||||
navigator.navigateBack(result = true)
|
||||
}
|
||||
},
|
||||
onSave = {
|
||||
if (saveTemplate(template, isCreation)) {
|
||||
navigator.navigateBack(result = true)
|
||||
} else {
|
||||
Toast.makeText(context, saveTemplateFailed, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
},
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.pointerInteropFilter {
|
||||
// disable click and ripple if readOnly
|
||||
readOnly
|
||||
}
|
||||
) {
|
||||
if (isCreation) {
|
||||
var errorHint by remember {
|
||||
mutableStateOf("")
|
||||
}
|
||||
val idConflictError = stringResource(id = R.string.app_profile_template_id_exist)
|
||||
val idInvalidError = stringResource(id = R.string.app_profile_template_id_invalid)
|
||||
TextEdit(
|
||||
label = stringResource(id = R.string.app_profile_template_id),
|
||||
text = template.id,
|
||||
errorHint = errorHint,
|
||||
isError = errorHint.isNotEmpty()
|
||||
) { value ->
|
||||
errorHint = if (isTemplateExist(value)) {
|
||||
idConflictError
|
||||
} else if (!isValidTemplateId(value)) {
|
||||
idInvalidError
|
||||
} else {
|
||||
""
|
||||
}
|
||||
template = template.copy(id = value)
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit(
|
||||
label = stringResource(id = R.string.app_profile_template_name),
|
||||
text = template.name
|
||||
) { value ->
|
||||
template.copy(name = value).run {
|
||||
if (autoSave) {
|
||||
if (!saveTemplate(this)) {
|
||||
// failed
|
||||
return@run
|
||||
}
|
||||
}
|
||||
template = this
|
||||
}
|
||||
}
|
||||
TextEdit(
|
||||
label = stringResource(id = R.string.app_profile_template_description),
|
||||
text = template.description
|
||||
) { value ->
|
||||
template.copy(description = value).run {
|
||||
if (autoSave) {
|
||||
if (!saveTemplate(this)) {
|
||||
// failed
|
||||
return@run
|
||||
}
|
||||
}
|
||||
template = this
|
||||
}
|
||||
}
|
||||
|
||||
RootProfileConfig(fixedName = true,
|
||||
profile = toNativeProfile(template),
|
||||
onProfileChange = {
|
||||
template.copy(
|
||||
uid = it.uid,
|
||||
gid = it.gid,
|
||||
groups = it.groups,
|
||||
capabilities = it.capabilities,
|
||||
context = it.context,
|
||||
namespace = it.namespace,
|
||||
rules = it.rules.split("\n")
|
||||
).run {
|
||||
if (autoSave) {
|
||||
if (!saveTemplate(this)) {
|
||||
// failed
|
||||
return@run
|
||||
}
|
||||
}
|
||||
template = this
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toNativeProfile(templateInfo: TemplateViewModel.TemplateInfo): Natives.Profile {
|
||||
return Natives.Profile().copy(rootTemplate = templateInfo.id,
|
||||
uid = templateInfo.uid,
|
||||
gid = templateInfo.gid,
|
||||
groups = templateInfo.groups,
|
||||
capabilities = templateInfo.capabilities,
|
||||
context = templateInfo.context,
|
||||
namespace = templateInfo.namespace,
|
||||
rules = templateInfo.rules.joinToString("\n").ifBlank { "" })
|
||||
}
|
||||
|
||||
fun isTemplateValid(template: TemplateViewModel.TemplateInfo): Boolean {
|
||||
if (template.id.isBlank()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isValidTemplateId(template.id)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun saveTemplate(template: TemplateViewModel.TemplateInfo, isCreation: Boolean = false): Boolean {
|
||||
if (!isTemplateValid(template)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isCreation && isTemplateExist(template.id)) {
|
||||
return false
|
||||
}
|
||||
|
||||
val json = template.toJSON()
|
||||
json.put("local", true)
|
||||
return setAppProfileTemplate(template.id, json.toString())
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
title: String,
|
||||
readOnly: Boolean,
|
||||
summary: String = "",
|
||||
onBack: () -> Unit,
|
||||
onDelete: () -> Unit = {},
|
||||
onSave: () -> Unit = {}
|
||||
) {
|
||||
TopAppBar(title = {
|
||||
Column {
|
||||
Text(title)
|
||||
if (summary.isNotBlank()) {
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}, navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
|
||||
}, actions = {
|
||||
if (readOnly) {
|
||||
return@TopAppBar
|
||||
}
|
||||
IconButton(onClick = onDelete) {
|
||||
Icon(
|
||||
Icons.Filled.DeleteForever,
|
||||
contentDescription = stringResource(id = R.string.app_profile_template_delete)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onSave) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Save,
|
||||
contentDescription = stringResource(id = R.string.app_profile_template_save)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun TextEdit(
|
||||
label: String,
|
||||
text: String,
|
||||
errorHint: String = "",
|
||||
isError: Boolean = false,
|
||||
onValueChange: (String) -> Unit = {}
|
||||
) {
|
||||
ListItem(headlineContent = {
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = { Text(label) },
|
||||
suffix =
|
||||
if (errorHint.isNotBlank()) {
|
||||
{
|
||||
Text(
|
||||
text = if (isError) errorHint else "",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
isError = isError,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Next
|
||||
),
|
||||
keyboardActions = KeyboardActions(onDone = {
|
||||
keyboardController?.hide()
|
||||
}),
|
||||
onValueChange = onValueChange
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private fun isValidTemplateId(id: String): Boolean {
|
||||
return Regex("""^([A-Za-z]{1}[A-Za-z\d_]*\.)*[A-Za-z][A-Za-z\d_]*$""").matches(id)
|
||||
}
|
||||
|
||||
private fun isTemplateExist(id: String): Boolean {
|
||||
return getAppProfileTemplate(id).isNotBlank()
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.webkit.WebViewAssetLoader
|
||||
import com.google.accompanist.web.AccompanistWebViewClient
|
||||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.rememberWebViewState
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import me.weishu.kernelsu.ui.webui.SuFilePathHandler
|
||||
import me.weishu.kernelsu.ui.webui.WebViewInterface
|
||||
import me.weishu.kernelsu.ui.webui.showSystemUI
|
||||
import java.io.File
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Destination
|
||||
@Composable
|
||||
fun WebScreen(navigator: DestinationsNavigator, moduleId: String, moduleName: String) {
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
if (WebViewInterface.isHideSystemUI && context is Activity) {
|
||||
showSystemUI(context.window)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold { innerPadding ->
|
||||
val webRoot = File("/data/adb/modules/${moduleId}/webroot")
|
||||
val webViewAssetLoader = WebViewAssetLoader.Builder()
|
||||
.setDomain("mui.kernelsu.org")
|
||||
.addPathHandler("/",
|
||||
SuFilePathHandler(context, webRoot)
|
||||
)
|
||||
.build()
|
||||
|
||||
val webViewClient = object : AccompanistWebViewClient() {
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
request: WebResourceRequest
|
||||
): WebResourceResponse? {
|
||||
return webViewAssetLoader.shouldInterceptRequest(request.url)
|
||||
}
|
||||
}
|
||||
WebView(
|
||||
state = rememberWebViewState(url = "https://mui.kernelsu.org/index.html"),
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
client = webViewClient,
|
||||
factory = { context ->
|
||||
WebView(context).apply {
|
||||
settings.javaScriptEnabled = true
|
||||
settings.domStorageEnabled = true
|
||||
settings.allowFileAccess = false
|
||||
addJavascriptInterface(WebViewInterface(context, this), "ksu")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -60,9 +60,9 @@ fun download(
|
||||
downloadManager.enqueue(request)
|
||||
}
|
||||
|
||||
fun checkNewVersion(): Pair<Int, String> {
|
||||
fun checkNewVersion(): Triple<Int, String, String> {
|
||||
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest"
|
||||
val defaultValue = 0 to ""
|
||||
val defaultValue = Triple(0, "", "")
|
||||
runCatching {
|
||||
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
||||
.use { response ->
|
||||
@@ -71,6 +71,7 @@ fun checkNewVersion(): Pair<Int, String> {
|
||||
}
|
||||
val body = response.body?.string() ?: return defaultValue
|
||||
val json = org.json.JSONObject(body)
|
||||
val changelog = json.optString("body")
|
||||
|
||||
val assets = json.getJSONArray("assets")
|
||||
for (i in 0 until assets.length()) {
|
||||
@@ -86,7 +87,7 @@ fun checkNewVersion(): Pair<Int, String> {
|
||||
val versionCode = matchResult.groupValues[2].toInt()
|
||||
val downloadUrl = asset.getString("browser_download_url")
|
||||
|
||||
return versionCode to downloadUrl
|
||||
return Triple(versionCode, downloadUrl, changelog)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.topjohnwu.superuser.ShellUtils
|
||||
import me.weishu.kernelsu.BuildConfig
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.ksuApp
|
||||
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
|
||||
@@ -26,17 +25,24 @@ private fun getKsuDaemonPath(): String {
|
||||
|
||||
object KsuCli {
|
||||
val SHELL: Shell = createRootShell()
|
||||
val GLOBAL_MNT_SHELL: Shell = createRootShell(true)
|
||||
}
|
||||
|
||||
fun getRootShell(): Shell {
|
||||
return KsuCli.SHELL
|
||||
fun getRootShell(globalMnt: Boolean = false): Shell {
|
||||
return if (globalMnt) KsuCli.GLOBAL_MNT_SHELL else {
|
||||
KsuCli.SHELL
|
||||
}
|
||||
}
|
||||
|
||||
fun createRootShell(): Shell {
|
||||
fun createRootShell(globalMnt: Boolean = false): Shell {
|
||||
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
||||
val builder = Shell.Builder.create()
|
||||
return try {
|
||||
builder.build(getKsuDaemonPath(), "debug", "su")
|
||||
if (globalMnt) {
|
||||
builder.build(getKsuDaemonPath(), "debug", "su", "-g")
|
||||
} else {
|
||||
builder.build(getKsuDaemonPath(), "debug", "su")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "su failed: ", e)
|
||||
builder.build("sh")
|
||||
@@ -92,7 +98,12 @@ fun uninstallModule(id: String): Boolean {
|
||||
return result
|
||||
}
|
||||
|
||||
fun installModule(uri: Uri, onFinish: (Boolean) -> Unit, onOutput: (String) -> Unit): Boolean {
|
||||
fun installModule(
|
||||
uri: Uri,
|
||||
onFinish: (Boolean) -> Unit,
|
||||
onStdout: (String) -> Unit,
|
||||
onStderr: (String) -> Unit
|
||||
): Boolean {
|
||||
val resolver = ksuApp.contentResolver
|
||||
with(resolver.openInputStream(uri)) {
|
||||
val file = File(ksuApp.cacheDir, "module.zip")
|
||||
@@ -101,16 +112,23 @@ fun installModule(uri: Uri, onFinish: (Boolean) -> Unit, onOutput: (String) -> U
|
||||
}
|
||||
val cmd = "module install ${file.absolutePath}"
|
||||
|
||||
val shell = getRootShell()
|
||||
val shell = createRootShell()
|
||||
|
||||
val callbackList: CallbackList<String?> = object : CallbackList<String?>() {
|
||||
val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||
override fun onAddElement(s: String?) {
|
||||
onOutput(s ?: "")
|
||||
onStdout(s ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
val stderrCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||
override fun onAddElement(s: String?) {
|
||||
onStderr(s ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
val result =
|
||||
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(callbackList, callbackList).exec()
|
||||
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(stdoutCallback, stderrCallback)
|
||||
.exec()
|
||||
Log.i("KernelSU", "install module $uri result: $result")
|
||||
|
||||
file.delete()
|
||||
@@ -129,6 +147,11 @@ fun reboot(reason: String = "") {
|
||||
ShellUtils.fastCmd(shell, "/system/bin/svc power reboot $reason || /system/bin/reboot $reason")
|
||||
}
|
||||
|
||||
fun rootAvailable(): Boolean {
|
||||
val shell = getRootShell()
|
||||
return shell.isRoot
|
||||
}
|
||||
|
||||
fun overlayFsAvailable(): Boolean {
|
||||
val shell = getRootShell()
|
||||
// check /proc/filesystems
|
||||
@@ -136,8 +159,8 @@ fun overlayFsAvailable(): Boolean {
|
||||
}
|
||||
|
||||
fun hasMagisk(): Boolean {
|
||||
val shell = getRootShell()
|
||||
val result = shell.newJob().add("nsenter --mount=/proc/1/ns/mnt which magisk").exec()
|
||||
val shell = getRootShell(true)
|
||||
val result = shell.newJob().add("which magisk").exec()
|
||||
Log.i(TAG, "has magisk: ${result.isSuccess}")
|
||||
return result.isSuccess
|
||||
}
|
||||
@@ -148,6 +171,71 @@ fun isSepolicyValid(rules: String?): Boolean {
|
||||
}
|
||||
val shell = getRootShell()
|
||||
val result =
|
||||
shell.newJob().add("ksud sepolicy check '$rules'").to(ArrayList(), null).exec()
|
||||
shell.newJob().add("${getKsuDaemonPath()} sepolicy check '$rules'").to(ArrayList(), null)
|
||||
.exec()
|
||||
return result.isSuccess
|
||||
}
|
||||
|
||||
fun getSepolicy(pkg: String): String {
|
||||
val shell = getRootShell()
|
||||
val result =
|
||||
shell.newJob().add("${getKsuDaemonPath()} profile get-sepolicy $pkg").to(ArrayList(), null)
|
||||
.exec()
|
||||
Log.i(TAG, "code: ${result.code}, out: ${result.out}, err: ${result.err}")
|
||||
return result.out.joinToString("\n")
|
||||
}
|
||||
|
||||
fun setSepolicy(pkg: String, rules: String): Boolean {
|
||||
val shell = getRootShell()
|
||||
val result =
|
||||
shell.newJob().add("${getKsuDaemonPath()} profile set-sepolicy $pkg '$rules'")
|
||||
.to(ArrayList(), null).exec()
|
||||
Log.i(TAG, "set sepolicy result: ${result.code}")
|
||||
return result.isSuccess
|
||||
}
|
||||
|
||||
fun listAppProfileTemplates(): List<String> {
|
||||
val shell = getRootShell()
|
||||
return shell.newJob().add("${getKsuDaemonPath()} profile list-templates").to(ArrayList(), null)
|
||||
.exec().out
|
||||
}
|
||||
|
||||
fun getAppProfileTemplate(id: String): String {
|
||||
val shell = getRootShell()
|
||||
return shell.newJob().add("${getKsuDaemonPath()} profile get-template '${id}'")
|
||||
.to(ArrayList(), null)
|
||||
.exec().out.joinToString("\n")
|
||||
}
|
||||
|
||||
fun setAppProfileTemplate(id: String, template: String): Boolean {
|
||||
val shell = getRootShell()
|
||||
return shell.newJob().add("${getKsuDaemonPath()} profile set-template '${id}' '${template}'")
|
||||
.to(ArrayList(), null)
|
||||
.exec().isSuccess
|
||||
}
|
||||
|
||||
fun deleteAppProfileTemplate(id: String): Boolean {
|
||||
val shell = getRootShell()
|
||||
return shell.newJob().add("${getKsuDaemonPath()} profile delete-template '${id}'")
|
||||
.to(ArrayList(), null)
|
||||
.exec().isSuccess
|
||||
}
|
||||
|
||||
fun forceStopApp(packageName: String) {
|
||||
val shell = getRootShell()
|
||||
val result = shell.newJob().add("am force-stop $packageName").exec()
|
||||
Log.i(TAG, "force stop $packageName result: $result")
|
||||
}
|
||||
|
||||
fun launchApp(packageName: String) {
|
||||
|
||||
val shell = getRootShell()
|
||||
val result =
|
||||
shell.newJob().add("monkey -p $packageName -c android.intent.category.LAUNCHER 1").exec()
|
||||
Log.i(TAG, "launch $packageName result: $result")
|
||||
}
|
||||
|
||||
fun restartApp(packageName: String) {
|
||||
forceStopApp(packageName)
|
||||
launchApp(packageName)
|
||||
}
|
||||
@@ -26,12 +26,17 @@ fun getBugreportFile(context: Context): File {
|
||||
val bootlogFile = File(bugreportDir, "bootlog.tar.gz")
|
||||
val mountsFile = File(bugreportDir, "mounts.txt")
|
||||
val fileSystemsFile = File(bugreportDir, "filesystems.txt")
|
||||
val ksuFileTree = File(bugreportDir, "ksu_tree.txt")
|
||||
val adbFileTree = File(bugreportDir, "adb_tree.txt")
|
||||
val adbFileDetails = File(bugreportDir, "adb_details.txt")
|
||||
val ksuFileSize = File(bugreportDir, "ksu_size.txt")
|
||||
val appListFile = File(bugreportDir, "packages.txt")
|
||||
val propFile = File(bugreportDir, "props.txt")
|
||||
val allowListFile = File(bugreportDir, "allowlist.bin")
|
||||
val procModules = File(bugreportDir, "proc_modules.txt")
|
||||
val bootConfig = File(bugreportDir, "boot_config.txt")
|
||||
val kernelConfig = File(bugreportDir, "defconfig.gz")
|
||||
|
||||
val shell = getRootShell()
|
||||
val shell = getRootShell(true)
|
||||
|
||||
shell.newJob().add("dmesg > ${dmesgFile.absolutePath}").exec()
|
||||
shell.newJob().add("logcat -d > ${logcatFile.absolutePath}").exec()
|
||||
@@ -43,12 +48,17 @@ fun getBugreportFile(context: Context): File {
|
||||
|
||||
shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec()
|
||||
shell.newJob().add("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec()
|
||||
shell.newJob().add("ls -alRZ /data/adb > ${ksuFileTree.absolutePath}").exec()
|
||||
shell.newJob().add("busybox tree /data/adb > ${adbFileTree.absolutePath}").exec()
|
||||
shell.newJob().add("ls -alRZ /data/adb > ${adbFileDetails.absolutePath}").exec()
|
||||
shell.newJob().add("du -sh /data/adb/ksu/* > ${ksuFileSize.absolutePath}").exec()
|
||||
shell.newJob().add("cp /data/system/packages.list ${appListFile.absolutePath}").exec()
|
||||
shell.newJob().add("getprop > ${propFile.absolutePath}").exec()
|
||||
shell.newJob().add("cp /data/adb/ksu/.allowlist ${allowListFile.absolutePath}").exec()
|
||||
shell.newJob().add("cp /proc/modules ${procModules.absolutePath}").exec()
|
||||
shell.newJob().add("cp /proc/bootconfig ${bootConfig.absolutePath}").exec()
|
||||
shell.newJob().add("cp /proc/config.gz ${kernelConfig.absolutePath}").exec()
|
||||
|
||||
val selinux = ShellUtils.fastCmd(shell, "getenforce");
|
||||
val selinux = ShellUtils.fastCmd(shell, "getenforce")
|
||||
|
||||
// basic information
|
||||
val buildInfo = File(bugreportDir, "basic.txt")
|
||||
@@ -68,7 +78,7 @@ fun getBugreportFile(context: Context): File {
|
||||
val uname = Os.uname()
|
||||
pw.println("KernelRelease: ${uname.release}")
|
||||
pw.println("KernelVersion: ${uname.version}")
|
||||
pw.println("Mahcine: ${uname.machine}")
|
||||
pw.println("Machine: ${uname.machine}")
|
||||
pw.println("Nodename: ${uname.nodename}")
|
||||
pw.println("Sysname: ${uname.sysname}")
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ class ModuleViewModel : ViewModel() {
|
||||
val update: Boolean,
|
||||
val remove: Boolean,
|
||||
val updateJson: String,
|
||||
val hasWebUi: Boolean,
|
||||
)
|
||||
|
||||
data class ModuleUpdateInfo(
|
||||
@@ -58,6 +59,13 @@ class ModuleViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
var isNeedRefresh by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
fun markNeedRefresh() {
|
||||
isNeedRefresh = true
|
||||
}
|
||||
|
||||
fun fetchModuleList() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
isRefreshing = true
|
||||
@@ -80,17 +88,20 @@ class ModuleViewModel : ViewModel() {
|
||||
.map { obj ->
|
||||
ModuleInfo(
|
||||
obj.getString("id"),
|
||||
obj.getString("name"),
|
||||
|
||||
obj.optString("name"),
|
||||
obj.optString("author", "Unknown"),
|
||||
obj.optString("version", "Unknown"),
|
||||
obj.optInt("versionCode", 0),
|
||||
obj.getString("description"),
|
||||
obj.optString("description"),
|
||||
obj.getBoolean("enabled"),
|
||||
obj.getBoolean("update"),
|
||||
obj.getBoolean("remove"),
|
||||
obj.optString("updateJson", "")
|
||||
obj.optString("updateJson"),
|
||||
obj.optBoolean("web")
|
||||
)
|
||||
}.toList()
|
||||
isNeedRefresh = false
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "fetchModuleList: ", e)
|
||||
isRefreshing = false
|
||||
@@ -106,56 +117,46 @@ class ModuleViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun checkUpdate(m: ModuleInfo, callback: (String?) -> Unit) {
|
||||
if (m.updateJson.isEmpty()) {
|
||||
callback(null)
|
||||
return
|
||||
fun checkUpdate(m: ModuleInfo): Triple<String, String, String> {
|
||||
val empty = Triple("", "", "")
|
||||
if (m.updateJson.isEmpty() || m.remove || m.update || !m.enabled) {
|
||||
return empty
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// download updateJson
|
||||
val result = kotlin.runCatching {
|
||||
val url = m.updateJson
|
||||
Log.i(TAG, "checkUpdate url: $url")
|
||||
val response = okhttp3.OkHttpClient()
|
||||
.newCall(
|
||||
// download updateJson
|
||||
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()
|
||||
).execute()
|
||||
Log.d(TAG, "checkUpdate code: ${response.code}")
|
||||
if (response.isSuccessful) {
|
||||
response.body?.string() ?: ""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}.getOrDefault("")
|
||||
Log.i(TAG, "checkUpdate result: $result")
|
||||
|
||||
if (result.isEmpty()) {
|
||||
callback(null)
|
||||
return@launch
|
||||
Log.d(TAG, "checkUpdate code: ${response.code}")
|
||||
if (response.isSuccessful) {
|
||||
response.body?.string() ?: ""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}.getOrDefault("")
|
||||
Log.i(TAG, "checkUpdate result: $result")
|
||||
|
||||
val updateJson = kotlin.runCatching {
|
||||
JSONObject(result)
|
||||
}.getOrNull()
|
||||
|
||||
if (updateJson == null) {
|
||||
callback(null)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val version = updateJson.optString("version", "")
|
||||
val versionCode = updateJson.optInt("versionCode", 0)
|
||||
val zipUrl = updateJson.optString("zipUrl", "")
|
||||
val changelog = updateJson.optString("changelog", "")
|
||||
if (versionCode <= m.versionCode || zipUrl.isEmpty()) {
|
||||
callback(null)
|
||||
return@launch
|
||||
}
|
||||
|
||||
callback(zipUrl)
|
||||
if (result.isEmpty()) {
|
||||
return empty
|
||||
}
|
||||
}
|
||||
|
||||
val updateJson = kotlin.runCatching {
|
||||
JSONObject(result)
|
||||
}.getOrNull() ?: return empty
|
||||
|
||||
val version = updateJson.optString("version", "")
|
||||
val versionCode = updateJson.optInt("versionCode", 0)
|
||||
val zipUrl = updateJson.optString("zipUrl", "")
|
||||
val changelog = updateJson.optString("changelog", "")
|
||||
if (versionCode <= m.versionCode || zipUrl.isEmpty()) {
|
||||
return empty
|
||||
}
|
||||
|
||||
return Triple(zipUrl, version, changelog)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
package me.weishu.kernelsu.ui.viewmodel
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.profile.Capabilities
|
||||
import me.weishu.kernelsu.profile.Groups
|
||||
import me.weishu.kernelsu.ui.util.getAppProfileTemplate
|
||||
import me.weishu.kernelsu.ui.util.listAppProfileTemplates
|
||||
import me.weishu.kernelsu.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
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2023/10/20.
|
||||
*/
|
||||
const val TEMPLATE_INDEX_URL = "https://kernelsu.org/templates/index.json"
|
||||
const val TEMPLATE_URL = "https://kernelsu.org/templates/%s"
|
||||
|
||||
const val TAG = "TemplateViewModel"
|
||||
|
||||
class TemplateViewModel : ViewModel() {
|
||||
companion object {
|
||||
|
||||
private var templates by mutableStateOf<List<TemplateInfo>>(emptyList())
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class TemplateInfo(
|
||||
val id: String = "",
|
||||
val name: String = "",
|
||||
val description: String = "",
|
||||
val author: String = "",
|
||||
val local: Boolean = true,
|
||||
|
||||
val namespace: Int = Natives.Profile.Namespace.INHERITED.ordinal,
|
||||
val uid: Int = Natives.ROOT_UID,
|
||||
val gid: Int = Natives.ROOT_GID,
|
||||
val groups: List<Int> = mutableListOf(),
|
||||
val capabilities: List<Int> = mutableListOf(),
|
||||
val context: String = Natives.KERNEL_SU_DOMAIN,
|
||||
val rules: List<String> = mutableListOf(),
|
||||
) : Parcelable
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
val templateList by derivedStateOf {
|
||||
val comparator = compareBy(TemplateInfo::local).reversed().then(
|
||||
compareBy(
|
||||
Collator.getInstance(Locale.getDefault()), TemplateInfo::id
|
||||
)
|
||||
)
|
||||
templates.sortedWith(comparator).apply {
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchTemplates(sync: Boolean = false) {
|
||||
isRefreshing = true
|
||||
withContext(Dispatchers.IO) {
|
||||
val localTemplateIds = listAppProfileTemplates()
|
||||
Log.i(TAG, "localTemplateIds: $localTemplateIds")
|
||||
if (localTemplateIds.isEmpty() || sync) {
|
||||
// if no templates, fetch remote templates
|
||||
fetchRemoteTemplates()
|
||||
}
|
||||
|
||||
// fetch templates again
|
||||
templates = listAppProfileTemplates().mapNotNull(::getTemplateInfoById)
|
||||
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun importTemplates(
|
||||
templates: String,
|
||||
onSuccess: suspend () -> Unit,
|
||||
onFailure: suspend (String) -> Unit
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
JSONArray(templates)
|
||||
}.getOrElse {
|
||||
runCatching {
|
||||
val json = JSONObject(templates)
|
||||
JSONArray().apply { put(json) }
|
||||
}.getOrElse {
|
||||
onFailure("invalid templates: $templates")
|
||||
return@withContext
|
||||
}
|
||||
}.let {
|
||||
0.until(it.length()).forEach { i ->
|
||||
runCatching {
|
||||
val template = it.getJSONObject(i)
|
||||
val id = template.getString("id")
|
||||
template.put("local", true)
|
||||
setAppProfileTemplate(id, template.toString())
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "ignore invalid template: $it", e)
|
||||
}
|
||||
}
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun exportTemplates(onTemplateEmpty: () -> Unit, callback: (String) -> Unit) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val templates = listAppProfileTemplates().mapNotNull(::getTemplateInfoById).filter {
|
||||
it.local
|
||||
}
|
||||
templates.ifEmpty {
|
||||
onTemplateEmpty()
|
||||
return@withContext
|
||||
}
|
||||
JSONArray(templates.map {
|
||||
it.toJSON()
|
||||
}).toString().let(callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchRemoteTemplates() {
|
||||
runCatching {
|
||||
OkHttpClient().newCall(
|
||||
Request.Builder().url(TEMPLATE_INDEX_URL).build()
|
||||
).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
return
|
||||
}
|
||||
val remoteTemplateIds = JSONArray(response.body!!.string())
|
||||
Log.i(TAG, "fetchRemoteTemplates: $remoteTemplateIds")
|
||||
0.until(remoteTemplateIds.length()).forEach { i ->
|
||||
val id = remoteTemplateIds.getString(i)
|
||||
val templateJson = OkHttpClient().newCall(
|
||||
Request.Builder().url(TEMPLATE_URL.format(id)).build()
|
||||
).runCatching {
|
||||
execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
return@forEach
|
||||
}
|
||||
response.body!!.string()
|
||||
}
|
||||
}.getOrNull() ?: return@forEach
|
||||
Log.i(TAG, "template: $templateJson")
|
||||
|
||||
// validate remote template
|
||||
runCatching {
|
||||
val json = JSONObject(templateJson)
|
||||
fromJSON(json)?.let {
|
||||
// force local template
|
||||
json.put("local", false)
|
||||
setAppProfileTemplate(id, json.toString())
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e(TAG, "ignore invalid template: $it", it)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onFailure { Log.e(TAG, "fetchRemoteTemplates: $it", it) }
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T, R> JSONArray.mapCatching(
|
||||
transform: (T) -> R, onFail: (Throwable) -> Unit
|
||||
): List<R> {
|
||||
return List(length()) { i -> get(i) as T }.mapNotNull { element ->
|
||||
runCatching {
|
||||
transform(element)
|
||||
}.onFailure(onFail).getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T : Enum<T>> getEnumOrdinals(
|
||||
jsonArray: JSONArray?, enumClass: Class<T>
|
||||
): List<T> {
|
||||
return jsonArray?.mapCatching<String, T>({ name ->
|
||||
enumValueOf(name.uppercase())
|
||||
}, {
|
||||
Log.e(TAG, "ignore invalid enum ${enumClass.simpleName}: $it", it)
|
||||
}).orEmpty()
|
||||
}
|
||||
|
||||
fun getTemplateInfoById(id: String): TemplateViewModel.TemplateInfo? {
|
||||
return runCatching {
|
||||
fromJSON(JSONObject(getAppProfileTemplate(id)))
|
||||
}.onFailure {
|
||||
Log.e(TAG, "ignore invalid template: $it", it)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
private fun getLocaleString(json: JSONObject, key: String): String {
|
||||
val fallback = json.getString(key)
|
||||
val locale = Locale.getDefault()
|
||||
val localeKey = "${locale.language}_${locale.country}"
|
||||
json.optJSONObject("locales")?.let {
|
||||
it.optJSONObject(localeKey)?.let { json->
|
||||
return json.optString(key, fallback)
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
private fun fromJSON(templateJson: JSONObject): TemplateViewModel.TemplateInfo? {
|
||||
return runCatching {
|
||||
val groupsJsonArray = templateJson.optJSONArray("groups")
|
||||
val capabilitiesJsonArray = templateJson.optJSONArray("capabilities")
|
||||
val context = templateJson.optString("context").takeIf { it.isNotEmpty() }
|
||||
?: Natives.KERNEL_SU_DOMAIN;
|
||||
val namespace = templateJson.optString("namespace").takeIf { it.isNotEmpty() }
|
||||
?: Natives.Profile.Namespace.INHERITED.name
|
||||
|
||||
val rulesJsonArray = templateJson.optJSONArray("rules")
|
||||
val templateInfo = TemplateViewModel.TemplateInfo(
|
||||
id = templateJson.getString("id"),
|
||||
name = getLocaleString(templateJson, "name"),
|
||||
description = getLocaleString(templateJson, "description"),
|
||||
author = templateJson.optString("author"),
|
||||
local = templateJson.optBoolean("local"),
|
||||
namespace = Natives.Profile.Namespace.valueOf(
|
||||
namespace.uppercase()
|
||||
).ordinal,
|
||||
uid = templateJson.optInt("uid", Natives.ROOT_UID),
|
||||
gid = templateJson.optInt("gid", Natives.ROOT_GID),
|
||||
groups = getEnumOrdinals(groupsJsonArray, Groups::class.java).map { it.gid },
|
||||
capabilities = getEnumOrdinals(
|
||||
capabilitiesJsonArray, Capabilities::class.java
|
||||
).map { it.cap },
|
||||
context = context,
|
||||
rules = rulesJsonArray?.mapCatching<String, String>({ it }, {
|
||||
Log.e(TAG, "ignore invalid rule: $it", it)
|
||||
}).orEmpty()
|
||||
)
|
||||
templateInfo
|
||||
}.onFailure {
|
||||
Log.e(TAG, "ignore invalid template: $it", it)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
fun TemplateViewModel.TemplateInfo.toJSON(): JSONObject {
|
||||
val template = this
|
||||
return JSONObject().apply {
|
||||
|
||||
put("id", template.id)
|
||||
put("name", template.name.ifBlank { template.id })
|
||||
put("description", template.description.ifBlank { template.id })
|
||||
if (template.author.isNotEmpty()) {
|
||||
put("author", template.author)
|
||||
}
|
||||
put("namespace", Natives.Profile.Namespace.values()[template.namespace].name)
|
||||
put("uid", template.uid)
|
||||
put("gid", template.gid)
|
||||
|
||||
if (template.groups.isNotEmpty()) {
|
||||
put("groups", JSONArray(
|
||||
Groups.values().filter {
|
||||
template.groups.contains(it.gid)
|
||||
}.map {
|
||||
it.name
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
if (template.capabilities.isNotEmpty()) {
|
||||
put("capabilities", JSONArray(
|
||||
Capabilities.values().filter {
|
||||
template.capabilities.contains(it.cap)
|
||||
}.map {
|
||||
it.name
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
if (template.context.isNotEmpty()) {
|
||||
put("context", template.context)
|
||||
}
|
||||
|
||||
if (template.rules.isNotEmpty()) {
|
||||
put("rules", JSONArray(template.rules))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun generateTemplates() {
|
||||
val templateJson = JSONObject()
|
||||
templateJson.put("id", "com.example")
|
||||
templateJson.put("name", "Example")
|
||||
templateJson.put("description", "This is an example template")
|
||||
templateJson.put("local", true)
|
||||
templateJson.put("namespace", Natives.Profile.Namespace.INHERITED.name)
|
||||
templateJson.put("uid", 0)
|
||||
templateJson.put("gid", 0)
|
||||
|
||||
templateJson.put("groups", JSONArray().apply { put(Groups.INET.name) })
|
||||
templateJson.put("capabilities", JSONArray().apply { put(Capabilities.CAP_NET_RAW.name) })
|
||||
templateJson.put("context", "u:r:su:s0")
|
||||
Log.i(TAG, "$templateJson")
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package me.weishu.kernelsu.ui.webui;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
class MimeUtil {
|
||||
|
||||
public static String getMimeFromFileName(String fileName) {
|
||||
if (fileName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Copying the logic and mapping that Chromium follows.
|
||||
// First we check against the OS (this is a limited list by default)
|
||||
// but app developers can extend this.
|
||||
// We then check against a list of hardcoded mime types above if the
|
||||
// OS didn't provide a result.
|
||||
String mimeType = URLConnection.guessContentTypeFromName(fileName);
|
||||
|
||||
if (mimeType != null) {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
return guessHardcodedMime(fileName);
|
||||
}
|
||||
|
||||
// We should keep this map in sync with the lists under
|
||||
// //net/base/mime_util.cc in Chromium.
|
||||
// A bunch of the mime types don't really apply to Android land
|
||||
// like word docs so feel free to filter out where necessary.
|
||||
private static String guessHardcodedMime(String fileName) {
|
||||
int finalFullStop = fileName.lastIndexOf('.');
|
||||
if (finalFullStop == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String extension = fileName.substring(finalFullStop + 1).toLowerCase();
|
||||
|
||||
switch (extension) {
|
||||
case "webm":
|
||||
return "video/webm";
|
||||
case "mpeg":
|
||||
case "mpg":
|
||||
return "video/mpeg";
|
||||
case "mp3":
|
||||
return "audio/mpeg";
|
||||
case "wasm":
|
||||
return "application/wasm";
|
||||
case "xhtml":
|
||||
case "xht":
|
||||
case "xhtm":
|
||||
return "application/xhtml+xml";
|
||||
case "flac":
|
||||
return "audio/flac";
|
||||
case "ogg":
|
||||
case "oga":
|
||||
case "opus":
|
||||
return "audio/ogg";
|
||||
case "wav":
|
||||
return "audio/wav";
|
||||
case "m4a":
|
||||
return "audio/x-m4a";
|
||||
case "gif":
|
||||
return "image/gif";
|
||||
case "jpeg":
|
||||
case "jpg":
|
||||
case "jfif":
|
||||
case "pjpeg":
|
||||
case "pjp":
|
||||
return "image/jpeg";
|
||||
case "png":
|
||||
return "image/png";
|
||||
case "apng":
|
||||
return "image/apng";
|
||||
case "svg":
|
||||
case "svgz":
|
||||
return "image/svg+xml";
|
||||
case "webp":
|
||||
return "image/webp";
|
||||
case "mht":
|
||||
case "mhtml":
|
||||
return "multipart/related";
|
||||
case "css":
|
||||
return "text/css";
|
||||
case "html":
|
||||
case "htm":
|
||||
case "shtml":
|
||||
case "shtm":
|
||||
case "ehtml":
|
||||
return "text/html";
|
||||
case "js":
|
||||
case "mjs":
|
||||
return "application/javascript";
|
||||
case "xml":
|
||||
return "text/xml";
|
||||
case "mp4":
|
||||
case "m4v":
|
||||
return "video/mp4";
|
||||
case "ogv":
|
||||
case "ogm":
|
||||
return "video/ogg";
|
||||
case "ico":
|
||||
return "image/x-icon";
|
||||
case "woff":
|
||||
return "application/font-woff";
|
||||
case "gz":
|
||||
case "tgz":
|
||||
return "application/gzip";
|
||||
case "json":
|
||||
return "application/json";
|
||||
case "pdf":
|
||||
return "application/pdf";
|
||||
case "zip":
|
||||
return "application/zip";
|
||||
case "bmp":
|
||||
return "image/bmp";
|
||||
case "tiff":
|
||||
case "tif":
|
||||
return "image/tiff";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package me.weishu.kernelsu.ui.webui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebResourceResponse;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.webkit.WebViewAssetLoader;
|
||||
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import me.weishu.kernelsu.ui.util.KsuCliKt;
|
||||
|
||||
/**
|
||||
* Handler class to open files from file system by root access
|
||||
* For more information about android storage please refer to
|
||||
* <a href="https://developer.android.com/guide/topics/data/data-storage">Android Developers
|
||||
* Docs: Data and file storage overview</a>.
|
||||
* <p class="note">
|
||||
* To avoid leaking user or app data to the web, make sure to choose {@code directory}
|
||||
* carefully, and assume any file under this directory could be accessed by any web page subject
|
||||
* to same-origin rules.
|
||||
* <p>
|
||||
* A typical usage would be like:
|
||||
* <pre class="prettyprint">
|
||||
* File publicDir = new File(context.getFilesDir(), "public");
|
||||
* // Host "files/public/" in app's data directory under:
|
||||
* // http://appassets.androidplatform.net/public/...
|
||||
* WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
|
||||
* .addPathHandler("/public/", new InternalStoragePathHandler(context, publicDir))
|
||||
* .build();
|
||||
* </pre>
|
||||
*/
|
||||
public final class SuFilePathHandler implements WebViewAssetLoader.PathHandler {
|
||||
private static final String TAG = "SuFilePathHandler";
|
||||
|
||||
/**
|
||||
* Default value to be used as MIME type if guessing MIME type failed.
|
||||
*/
|
||||
public static final String DEFAULT_MIME_TYPE = "text/plain";
|
||||
|
||||
/**
|
||||
* Forbidden subdirectories of {@link Context#getDataDir} that cannot be exposed by this
|
||||
* handler. They are forbidden as they often contain sensitive information.
|
||||
* <p class="note">
|
||||
* Note: Any future addition to this list will be considered breaking changes to the API.
|
||||
*/
|
||||
private static final String[] FORBIDDEN_DATA_DIRS =
|
||||
new String[] {"/data/data", "/data/system"};
|
||||
|
||||
@NonNull
|
||||
private final File mDirectory;
|
||||
|
||||
private final Shell mShell;
|
||||
|
||||
/**
|
||||
* Creates PathHandler for app's internal storage.
|
||||
* The directory to be exposed must be inside either the application's internal data
|
||||
* directory {@link Context#getDataDir} or cache directory {@link Context#getCacheDir}.
|
||||
* External storage is not supported for security reasons, as other apps with
|
||||
* {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} may be able to modify the
|
||||
* files.
|
||||
* <p>
|
||||
* Exposing the entire data or cache directory is not permitted, to avoid accidentally
|
||||
* exposing sensitive application files to the web. Certain existing subdirectories of
|
||||
* {@link Context#getDataDir} are also not permitted as they are often sensitive.
|
||||
* These files are ({@code "app_webview/"}, {@code "databases/"}, {@code "lib/"},
|
||||
* {@code "shared_prefs/"} and {@code "code_cache/"}).
|
||||
* <p>
|
||||
* The application should typically use a dedicated subdirectory for the files it intends to
|
||||
* expose and keep them separate from other files.
|
||||
*
|
||||
* @param context {@link Context} that is used to access app's internal storage.
|
||||
* @param directory the absolute path of the exposed app internal storage directory from
|
||||
* which files can be loaded.
|
||||
* @throws IllegalArgumentException if the directory is not allowed.
|
||||
*/
|
||||
public SuFilePathHandler(@NonNull Context context, @NonNull File directory) {
|
||||
try {
|
||||
mDirectory = new File(getCanonicalDirPath(directory));
|
||||
if (!isAllowedInternalStorageDir(context)) {
|
||||
throw new IllegalArgumentException("The given directory \"" + directory
|
||||
+ "\" doesn't exist under an allowed app internal storage directory");
|
||||
}
|
||||
mShell = KsuCliKt.createRootShell(true);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to resolve the canonical path for the given directory: "
|
||||
+ directory.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAllowedInternalStorageDir(@NonNull Context context) throws IOException {
|
||||
String dir = getCanonicalDirPath(mDirectory);
|
||||
|
||||
for (String forbiddenPath : FORBIDDEN_DATA_DIRS) {
|
||||
if (dir.startsWith(forbiddenPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the requested file from the exposed data directory.
|
||||
* <p>
|
||||
* The matched prefix path used shouldn't be a prefix of a real web path. Thus, if the
|
||||
* requested file cannot be found or is outside the mounted directory a
|
||||
* {@link WebResourceResponse} object with a {@code null} {@link InputStream} will be
|
||||
* returned instead of {@code null}. This saves the time of falling back to network and
|
||||
* trying to resolve a path that doesn't exist. A {@link WebResourceResponse} with
|
||||
* {@code null} {@link InputStream} will be received as an HTTP response with status code
|
||||
* {@code 404} and no body.
|
||||
* <p class="note">
|
||||
* The MIME type for the file will be determined from the file's extension using
|
||||
* {@link java.net.URLConnection#guessContentTypeFromName}. Developers should ensure that
|
||||
* files are named using standard file extensions. If the file does not have a
|
||||
* recognised extension, {@code "text/plain"} will be used by default.
|
||||
*
|
||||
* @param path the suffix path to be handled.
|
||||
* @return {@link WebResourceResponse} for the requested file.
|
||||
*/
|
||||
@Override
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
public WebResourceResponse handle(@NonNull String path) {
|
||||
try {
|
||||
File file = getCanonicalFileIfChild(mDirectory, path);
|
||||
if (file != null) {
|
||||
InputStream is = openFile(file, mShell);
|
||||
String mimeType = guessMimeType(path);
|
||||
return new WebResourceResponse(mimeType, null, is);
|
||||
} else {
|
||||
Log.e(TAG, String.format(
|
||||
"The requested file: %s is outside the mounted directory: %s", path,
|
||||
mDirectory));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error opening the requested path: " + path, e);
|
||||
}
|
||||
return new WebResourceResponse(null, null, null);
|
||||
}
|
||||
|
||||
public static String getCanonicalDirPath(@NonNull File file) throws IOException {
|
||||
String canonicalPath = file.getCanonicalPath();
|
||||
if (!canonicalPath.endsWith("/")) canonicalPath += "/";
|
||||
return canonicalPath;
|
||||
}
|
||||
|
||||
public static File getCanonicalFileIfChild(@NonNull File parent, @NonNull String child)
|
||||
throws IOException {
|
||||
String parentCanonicalPath = getCanonicalDirPath(parent);
|
||||
String childCanonicalPath = new File(parent, child).getCanonicalPath();
|
||||
if (childCanonicalPath.startsWith(parentCanonicalPath)) {
|
||||
return new File(childCanonicalPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static InputStream handleSvgzStream(@NonNull String path,
|
||||
@NonNull InputStream stream) throws IOException {
|
||||
return path.endsWith(".svgz") ? new GZIPInputStream(stream) : stream;
|
||||
}
|
||||
|
||||
public static InputStream openFile(@NonNull File file, @NonNull Shell shell) throws FileNotFoundException,
|
||||
IOException {
|
||||
SuFile suFile = new SuFile(file.getAbsolutePath());
|
||||
suFile.setShell(shell);
|
||||
InputStream fis = SuFileInputStream.open(suFile);
|
||||
return handleSvgzStream(file.getPath(), fis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link MimeUtil#getMimeFromFileName} to guess MIME type or return the
|
||||
* {@link #DEFAULT_MIME_TYPE} if it can't guess.
|
||||
*
|
||||
* @param filePath path of the file to guess its MIME type.
|
||||
* @return MIME type guessed from file extension or {@link #DEFAULT_MIME_TYPE}.
|
||||
*/
|
||||
@NonNull
|
||||
public static String guessMimeType(@NonNull String filePath) {
|
||||
String mimeType = MimeUtil.getMimeFromFileName(filePath);
|
||||
return mimeType == null ? DEFAULT_MIME_TYPE : mimeType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package me.weishu.kernelsu.ui.webui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.TextUtils
|
||||
import android.view.Window
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import me.weishu.kernelsu.ui.util.createRootShell
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class WebViewInterface(val context: Context, private val webView: WebView) {
|
||||
|
||||
companion object {
|
||||
var isHideSystemUI: Boolean = false
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun exec(cmd: String): String {
|
||||
val shell = createRootShell()
|
||||
return ShellUtils.fastCmd(shell, cmd)
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun exec(cmd: String, callbackFunc: String) {
|
||||
exec(cmd, null, callbackFunc)
|
||||
}
|
||||
|
||||
private fun processOptions(sb: StringBuilder, options: String?) {
|
||||
val opts = if (options == null) JSONObject() else {
|
||||
JSONObject(options)
|
||||
}
|
||||
|
||||
val cwd = opts.optString("cwd")
|
||||
if (!TextUtils.isEmpty(cwd)) {
|
||||
sb.append("cd ${cwd};")
|
||||
}
|
||||
|
||||
opts.optJSONObject("env")?.let { env ->
|
||||
env.keys().forEach { key ->
|
||||
sb.append("export ${key}=${env.getString(key)};")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun exec(
|
||||
cmd: String,
|
||||
options: String?,
|
||||
callbackFunc: String
|
||||
) {
|
||||
val finalCommand = StringBuilder()
|
||||
processOptions(finalCommand, options)
|
||||
finalCommand.append(cmd)
|
||||
|
||||
val shell = createRootShell()
|
||||
val result = shell.newJob().add(finalCommand.toString()).to(ArrayList(), ArrayList()).exec()
|
||||
val stdout = result.out.joinToString(separator = "\n")
|
||||
val stderr = result.err.joinToString(separator = "\n")
|
||||
|
||||
val jsCode =
|
||||
"javascript: (function() { try { ${callbackFunc}(${result.code}, ${
|
||||
JSONObject.quote(
|
||||
stdout
|
||||
)
|
||||
}, ${JSONObject.quote(stderr)}); } catch(e) { console.error(e); } })();"
|
||||
webView.post {
|
||||
webView.loadUrl(jsCode)
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun spawn(command: String, args: String, options: String?, callbackFunc: String) {
|
||||
val finalCommand = StringBuilder()
|
||||
|
||||
processOptions(finalCommand, options)
|
||||
|
||||
if (!TextUtils.isEmpty(args)) {
|
||||
finalCommand.append(command).append(" ")
|
||||
JSONArray(args).let { argsArray ->
|
||||
for (i in 0 until argsArray.length()) {
|
||||
finalCommand.append(argsArray.getString(i))
|
||||
finalCommand.append(" ")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalCommand.append(command)
|
||||
}
|
||||
|
||||
val shell = createRootShell()
|
||||
|
||||
val emitData = fun(name: String, data: String) {
|
||||
val jsCode =
|
||||
"javascript: (function() { try { ${callbackFunc}.${name}.emit('data', ${
|
||||
JSONObject.quote(
|
||||
data
|
||||
)
|
||||
}); } catch(e) { console.error('emitData', e); } })();"
|
||||
webView.post {
|
||||
webView.loadUrl(jsCode)
|
||||
}
|
||||
}
|
||||
|
||||
val stdout = object : CallbackList<String>() {
|
||||
override fun onAddElement(s: String) {
|
||||
emitData("stdout", s)
|
||||
}
|
||||
}
|
||||
|
||||
val stderr = object : CallbackList<String>() {
|
||||
override fun onAddElement(s: String) {
|
||||
emitData("stderr", s)
|
||||
}
|
||||
}
|
||||
|
||||
val future = shell.newJob().add(finalCommand.toString()).to(stdout, stderr).enqueue()
|
||||
val completableFuture = CompletableFuture.supplyAsync {
|
||||
future.get()
|
||||
}
|
||||
|
||||
completableFuture.thenAccept { result ->
|
||||
val emitExitCode =
|
||||
"javascript: (function() { try { ${callbackFunc}.emit('exit', ${result.code}); } catch(e) { console.error(`emitExit error: \${e}`); } })();"
|
||||
webView.post {
|
||||
webView.loadUrl(emitExitCode)
|
||||
}
|
||||
|
||||
if (result.code != 0) {
|
||||
val emitErrCode =
|
||||
"javascript: (function() { try { var err = new Error(); err.exitCode = ${result.code}; err.message = ${
|
||||
JSONObject.quote(
|
||||
result.err.joinToString(
|
||||
"\n"
|
||||
)
|
||||
)
|
||||
};${callbackFunc}.emit('error', err); } catch(e) { console.error('emitErr', e); } })();"
|
||||
webView.post {
|
||||
webView.loadUrl(emitErrCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun toast(msg: String) {
|
||||
webView.post {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun fullScreen(enable: Boolean) {
|
||||
if (context is Activity) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (enable) {
|
||||
hideSystemUI(context.window)
|
||||
} else {
|
||||
showSystemUI(context.window)
|
||||
}
|
||||
isHideSystemUI = enable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun hideSystemUI(window: Window) {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||
controller.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
}
|
||||
|
||||
fun showSystemUI(window: Window) {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, true)
|
||||
WindowInsetsControllerCompat(
|
||||
window,
|
||||
window.decorView
|
||||
).show(WindowInsetsCompat.Type.systemBars())
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user