Compare commits

...

65 Commits

Author SHA1 Message Date
Weblate (bot)
9a4ea27e9d Translations update from Hosted Weblate (#1454)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for
[KernelSU/Manager](https://hosted.weblate.org/projects/kernelsu/manager/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/kernelsu/manager/horizontal-auto.svg)

---------

Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Misaka <79515833+misakazip@users.noreply.github.com>
Co-authored-by: I g o r <igormczampola1@gmail.com>
Co-authored-by: yuztass <inkognito0901@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Madis Otenurm <robotkoer@gmail.com>
Co-authored-by: dabao1955 <dabao1955@163.com>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: Integral <integral@member.fsf.org>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: weishu tian <twsxtd@gmail.com>
Co-authored-by: Skallr2 <pm563838@gmail.com>
Co-authored-by: charlotte <charlotterose@duck.com>
Co-authored-by: sus <jeffpeng2012@gmail.com>
Co-authored-by: Caner Karaca <canerkaraca_23@hotmail.com>
Co-authored-by: Ali Beyaz <alipolatbeyaz@gmail.com>
2024-03-17 10:19:05 +08:00
igor
339e75be24 website: fix typo (#1456) 2024-03-17 09:08:46 +08:00
weishu
cf210d629f ci: Fix avd build (#1457) 2024-03-17 09:07:52 +08:00
weishu
ca480a5ec3 ci: Add lkm to release 2024-03-16 22:59:22 +08:00
weishu
23263a55de try fix 2024-03-16 12:21:03 +08:00
weishu
f65ea5a340 manager: Add install menu 2024-03-16 11:46:13 +08:00
weishu
ad6e2390f5 manager: Add CN translation 2024-03-16 11:37:31 +08:00
weishu
383f164453 remove ccache, bazel don't use 2024-03-16 11:37:30 +08:00
sewn
f675ce9aba [CI][A13] a13-5.10 patch level 2024-02 (#1449)
My Pixel 7a has a kernel version
`5.10.177-android13-4-00003-ga7208022a7ea-ab1081582`, with a security
patch of 2024-02, but the only available kernel build for that is
security patch 2023-07 which causes a bootloop.
2024-03-16 11:32:32 +08:00
weishu
7fd760f4f4 manager: Fix loading dialog 2024-03-16 11:13:50 +08:00
weishu
972d347a14 manager: Add string resource 2024-03-16 11:13:50 +08:00
weishu
ca8a88f0cc manager: Fix reboot button is missing when flash 2024-03-16 11:13:50 +08:00
weishu
e39be55db8 ksud: Fix magisk detect 2024-03-16 11:13:50 +08:00
Weblate (bot)
d00d3cbf82 Translations update from Hosted Weblate (#1437)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for
[KernelSU/Manager](https://hosted.weblate.org/projects/kernelsu/manager/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/kernelsu/manager/horizontal-auto.svg)

---------

Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Misaka <79515833+misakazip@users.noreply.github.com>
Co-authored-by: I g o r <igormczampola1@gmail.com>
Co-authored-by: yuztass <inkognito0901@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Madis Otenurm <robotkoer@gmail.com>
Co-authored-by: dabao1955 <dabao1955@163.com>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: Integral <integral@member.fsf.org>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: weishu tian <twsxtd@gmail.com>
Co-authored-by: Skallr2 <pm563838@gmail.com>
Co-authored-by: charlotte <charlotterose@duck.com>
Co-authored-by: sus <jeffpeng2012@gmail.com>
Co-authored-by: Caner Karaca <canerkaraca_23@hotmail.com>
2024-03-16 11:08:56 +08:00
cachiusa
7168a974be Add nethunter.root template (#1445)
This app requires DAC_OVERRIDE, DAC_READ_SEARCH, SYS_PTRACE, SYS_ADMIN
(for /data/local r/w) and SYS_CHROOT, SETGID (to run chroot and run it's
processes)

Devices with NetHunter installed is already considered compromised due
to lack of security features(like SELinux), therefore users are advised
not to store private data

It's not really worth restricting more capabilities of the app.
2024-03-16 10:54:00 +08:00
TheNeutrinoRaged
ddc086c4ef Update installation.md (#1451)
typo flush -> flash
2024-03-16 10:53:10 +08:00
sus
076511e275 Updated the Traditional Chinese translations for the website and templates. (#1453)
Signed-off-by: sus <54392299+jeffpeng3@users.noreply.github.com>
2024-03-16 10:52:53 +08:00
weishu
9d5529fb09 ksud: Fix magisk detect 2024-03-16 09:22:37 +08:00
weishu
ceb00dfdfd ci: remove avd build disk cache 2024-03-15 22:28:00 +08:00
weishu
39504ec2f3 ci: Fix release 2024-03-15 22:14:42 +08:00
weishu
1642e92c6f ci: Fix symbol strip 2024-03-15 21:00:44 +08:00
weishu
0f220f4044 ci: Fix release and LKM upload 2024-03-15 20:54:32 +08:00
weishu
2c34ec1742 ci: fix avd build 2024-03-15 20:29:53 +08:00
Ylarod
7568d55be1 Build KernelSU as LKM (#1254)
Co-authored-by: weishu <twsxtd@gmail.com>
2024-03-15 18:53:24 +08:00
Coconut
e3998c0744 kernel:Compatible with devices based on Huawei EMUI10 (#1447)
EMUI 10 kernel version is 4.14.xxx.  
The SELinux of Huawei's modified EMUI10 kernel is still similar to the
EMUI 9 version. This commit not support HarmonyOS 2 based EMUI 10.
2024-03-14 15:18:59 +08:00
Ylarod
50914ce39b Fix typo (#1444) 2024-03-13 18:04:58 +08:00
zalnars
6af2480008 Fix typos (#1443) 2024-03-12 13:15:03 +08:00
余空
625e1aafd1 ci: Add more kernel branches (#1442)
* Android12 5.10.205
* Android13 5.10.205
* Android13 5.15.144
* Update os_patch_level
2024-03-11 21:37:16 +08:00
weishu
d77988c1ac ksud: catch dmesg in bootlog 2024-03-11 14:20:27 +08:00
weishu
fe7ec370d4 ksud: fmt & tidy 2024-03-11 14:01:21 +08:00
weishu
ce5aa990ed ksud: don't patch if it's already patched 2024-03-11 12:48:25 +08:00
TinyHai
22a1276a22 manager: a small fix in InstallScreen (#1434)
今天刷一个模块,刷入时要配置一些内容,然后发现每次输出我都得手动滑动一下才能看全内容,就很难受。气得我直接翻源码,就有了这个PR   :P

- 防止logContent在重组时丢失。
- 修复输出内容总是自动滚动到上次内容的最底部,而不是最新内容的最底部的f问题。
2024-03-08 19:35:52 +08:00
loogeo
f9a4186dc7 Update build-kernel-a14.yml (#1432) 2024-03-08 12:39:24 +08:00
lyf
8b29137e83 [CI][A14] a14-5.15 patch level 2024-01 and 2024-02 (#1433)
Since android14 5.15.137 (2024-01) was released with QPR3 Beta2 on Pixel
8(Pro)
2024-03-08 12:39:17 +08:00
igor
ae17001033 website: update translation (#1423) 2024-03-08 10:32:08 +08:00
Syuugo
0eea198c2f Upgrade Gradle and AGP (#1431)
and Fix batch line config
2024-03-08 10:31:41 +08:00
TinyHai
425713fad3 manager: refine dialog component & add an animation to UpdateCard (#1429) 2024-03-08 10:31:14 +08:00
WindyDay
7611accc33 Add docs about Android 14 Kernel (#1421)
First time Pr to this project, not sure if I done it in the correct
format
2024-03-05 23:39:55 +08:00
riChar
323eaa0242 Add types in package.json (#1415)
The package can not be imported into TS project without this option.
[docs](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#including-declarations-in-your-npm-package)
2024-03-04 23:05:38 +08:00
igor
217755bb5a website: fixed typos and update translation (#1407) 2024-03-04 19:16:39 +08:00
weishu
170cd3f912 ci: Add android 15 avd kernel (#1414) 2024-03-04 19:12:48 +08:00
weishu
1d7d406745 manager: change webview debug icon 2024-03-04 15:10:16 +08:00
weishu
c8fc6a0656 ksud: resize image if it is shrinked 2024-03-04 13:50:00 +08:00
weishu
3829894d4d Use fallback method to mount overlayfs when fsopen one failed
Co-authored-by: natsumerinchan
2024-03-04 12:26:19 +08:00
5ec1cff
cd772fa250 manager: allow enable webview debugging (#1412) 2024-03-03 22:24:02 +08:00
weishu
dbe43b1540 ksud: remove permission mode for symlink 2024-03-01 23:10:03 +08:00
weishu
8a59fe1969 ksud: Copy directory permission mode 2024-03-01 22:46:53 +08:00
weishu
2a4fa94af0 ksud: Add more logs 2024-02-29 22:43:48 +08:00
weishu
6de330b00a ksud: Add some logs 2024-02-29 22:40:42 +08:00
weishu
8c0d06bc68 misc: Add just command 2024-02-29 20:35:03 +08:00
weishu
ed254b7ab4 ksud: fmt 2024-02-29 20:34:50 +08:00
weishu
5355625ed6 ksud: correctly copy chr device and keep xattr. close #1397 2024-02-29 19:23:11 +08:00
5ec1cff
7b89ec89c0 ci: support android 14 AVD (#1405) 2024-02-29 17:36:38 +08:00
weishu
5aa025c3f0 ksud: Add ksud to manager's su path 2024-02-29 14:04:10 +08:00
weishu
e39a80f91e webui: use global mount namespace by default. 2024-02-29 13:59:13 +08:00
weishu
622a7d73dc Revert "webui: support mount master mode"
This reverts commit 922703d2ff.
2024-02-29 13:58:44 +08:00
weishu
922703d2ff webui: support mount master mode 2024-02-29 13:55:07 +08:00
Fede2782
f459dfad54 English Faq: add wrong storage size on some Samsung (#1400)
#1389
2024-02-29 13:47:45 +08:00
Wang Han
3e2de84a81 Guard a few logprint in prctl path with KSU_DEBUG (#1402) 2024-02-29 13:47:22 +08:00
weishu
796f8a448a kernel: fix bazel build 2024-02-26 16:54:14 +08:00
Lycs-D
7775ce3938 ci: update android12-5.10 os_patch_level to 2024-01 (#1378) 2024-02-26 15:59:56 +08:00
Mufanc
2fb5334ac6 fix kernelsu.spawn && add type definitions (#1395) 2024-02-26 15:35:23 +08:00
weishu
afe0e691aa kernel: Unshallow the repo in Makefile. close #1365 2024-02-26 12:23:51 +08:00
Celica Sylphil
532796de48 website: Fix zh_CN and ja_JP paths (#1394) 2024-02-26 10:16:20 +08:00
dabao1955
e691c62811 website: fix ja guide broken links (#1392)
Signed-off-by: dabao1955 <dabao1955@163.com>
2024-02-26 09:49:10 +08:00
104 changed files with 3096 additions and 850 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.bat eol=crlf

View File

@@ -0,0 +1,71 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--https://ci.android.com/builds/submitted/9964412/kernel_virt_x86_64/latest/manifest_9964412.xml-->
<manifest>
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/" />
<default revision="master" remote="aosp" sync-j="4" />
<superproject name="kernel/superproject" remote="aosp" revision="common-android14-6.1" />
<project path="build/kernel" name="kernel/build" revision="b0377a072bb3f78cdacfd6d809914a9d1b0c0148">
<linkfile dest="tools/bazel" src="kleaf/bazel.sh" />
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE" />
<linkfile dest="build/build.sh" src="build.sh" />
<linkfile dest="build/build_abi.sh" src="build_abi.sh" />
<linkfile dest="build/build_test.sh" src="build_test.sh" />
<linkfile dest="build/build_utils.sh" src="build_utils.sh" />
<linkfile dest="build/config.sh" src="config.sh" />
<linkfile dest="build/envsetup.sh" src="envsetup.sh" />
<linkfile dest="build/_setup_env.sh" src="_setup_env.sh" />
<linkfile dest="build/multi-switcher.sh" src="multi-switcher.sh" />
<linkfile dest="build/abi" src="abi" />
<linkfile dest="build/static_analysis" src="static_analysis" />
</project>
<project path="common" name="kernel/common" revision="7e35917775b8b3e3346a87f294e334e258bf15e6">
<linkfile dest=".source_date_epoch_dir" src="." />
</project>
<project path="kernel/tests" name="kernel/tests" revision="c90a1c1b226b975cc31e709fa96fc1c6ecdbe272" />
<project path="kernel/configs" name="kernel/configs" revision="52a7267d6a9f9efabf3cb43839bb5e7f7ff05be3" />
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="0d03de3246301028775f05ea388c2c444344a268" />
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" clone-depth="1" revision="4f7e5adc160ab726ac5bafb260de98e612904c50" />
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" revision="f7b0d5b0ee369864d5ac3e96ae24ec9e2b6a52da" />
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" revision="dc92e06585a7647bf739a2309a721b82fcfa01d4" />
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="5611871963f54c688d3ac49e527aecdef21e8567" />
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" revision="2597cb1b5525e419b7fa806373be673054a68d29" />
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="2680066d0844544b3e78d6022cd21321d31837c3" />
<project path="prebuilts/bazel/linux-x86_64" name="platform/prebuilts/bazel/linux-x86_64" clone-depth="1" revision="4fdb9395071ff22118311d434d697c2b6fd887b4" />
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" clone-depth="1" revision="491e6aa056676f29c4541f71bd738e4e876e4ba2" />
<project path="prebuilts/ndk-r23" name="toolchain/prebuilts/ndk/r23" clone-depth="1" revision="19ac7e4eded12adb99d4f613490dde6dd0e72664" />
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" revision="f998e5dc13c03f0eae9e373263d3afff0932c738" />
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" revision="707b2c5fe3d0d7d934a93e00a8a4062e83557831" />
<project path="external/stardoc" name="platform/external/stardoc" revision="e83f522ee95419e55d2c5654aa6e0143beeef595" />
<project path="external/python/absl-py" name="platform/external/python/absl-py" revision="393d0b1e3f0fea3e95944a2fd3282cc9f76d4f14" />
</manifest>

View File

@@ -0,0 +1,37 @@
<manifest>
<!-- https://ci.android.com/builds/submitted/11275718/kernel_virt_aarch64/latest/manifest_11275718.xml -->
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/"/>
<default revision="main" remote="aosp" sync-j="4"/>
<superproject name="kernel/superproject" remote="aosp" revision="common-android15-6.6"/>
<project path="build/kernel" name="kernel/build" groups="ddk" revision="43337ece156eabd735426a0b007637bb52fa7339">
<linkfile dest="tools/bazel" src="kleaf/bazel.sh"/>
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE"/>
</project>
<project path="common" name="kernel/common" revision="515a956763d8c874d9a7e23528332bf907b31748"/>
<project path="kernel/common-patches" name="kernel/common-patches" revision="495419530db8761a40f8db9fb734a9be78fa25fd">
<linkfile dest="common/patches" src="android-mainline"/>
</project>
<project path="kernel/tests" name="kernel/tests" revision="99abfd276063ab3a7748939aa55e107aaca903f6"/>
<project path="kernel/configs" name="kernel/configs" revision="786dab5f2f49b983b7c448d45a50c62d36e9f7f9"/>
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="5c7466d6fc47f7ee2245f9ee6471cd78e6ff82f0"/>
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" revision="1400cf7d2f0d28c425737d7b58c1f67c52db087f" clone-depth="1" groups="ddk"/>
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" groups="ddk" revision="99e41849eb3895574b1dc7e854ca15cb3fb11a71"/>
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="93e69718fd53f0c3dd7f0087657dfe2e78926830"/>
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="bf33473342630944198190ff74e6ca09a9bcfdeb"/>
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="4b68c02455e6ce5c87ccf8e09e629f0177771a8c"/>
<project path="prebuilts/rust" name="platform/prebuilts/rust" revision="7e636e2a1b7cf415c3c01981e439d98843dc4973" clone-depth="1"/>
<project path="prebuilts/tradefed" name="platform/tools/tradefederation/prebuilts" clone-depth="1" revision="79d9ec16c1e4e747b0bfad9a80a1665080c42d7b"/>
<project path="prebuilts/asuite" name="platform/prebuilts/asuite" clone-depth="1" revision="9e2738f6242785a3b2096010ce63363a160c122f"/>
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="722e6fa37d508b190fafa9a8ce9f6d571fad3a8c"/>
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" revision="06351a976e772d8a9cdb133bb982032c64ee9e53" clone-depth="1" groups="ddk"/>
<project path="prebuilts/ndk-r26" name="toolchain/prebuilts/ndk/r26" clone-depth="1" groups="ddk" revision="e87abe7cbe9143d239ba54f32c64ca697adcba75"/>
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" groups="ddk" revision="930baaa09975eb3809629a72806acbe9494dd224"/>
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" groups="ddk" revision="5fb8d26dfb2565e0e2d8b6630d241b5f5675e0b1"/>
<project path="external/libcap-ng" name="platform/external/libcap-ng" revision="2bcc92ae19481dd2b8d3ce3abdfbbee49261abe6"/>
<project path="external/libcap" name="platform/external/libcap" revision="9577b17009379649c9220edca7d0077311445b95"/>
<project path="external/stardoc" name="platform/external/stardoc" groups="ddk" revision="85b0f239303220d902ad919ff27d2da475fc12e2"/>
<project path="external/python/absl-py" name="platform/external/python/absl-py" groups="ddk" revision="8cc5fc4798ef442b0c5b70c1ecc7e45a8f6eb2f8"/>
<project path="external/bazelbuild-rules_cc" name="platform/external/bazelbuild-rules_cc" groups="ddk" revision="9a4853f0327e0266818c8d6b4967e2e8f36b1a88"/>
<project path="external/bazelbuild-rules_python" name="platform/external/bazelbuild-rules_python" groups="ddk" revision="3e21f23d9400ba51f10e9b76016ff6d472829b4e"/>
<project path="external/bazelbuild-rules_rust" name="platform/external/bazelbuild-rules_rust" revision="bdd24099a80555ff8a4441e8bb47f4c7cfe413f8"/>
</manifest>

164
.github/workflows/avd-kernel.yml vendored Normal file
View File

@@ -0,0 +1,164 @@
name: GKI Kernel Build
on:
workflow_call:
inputs:
version_name:
required: true
type: string
description: >
With SUBLEVEL of kernel,
for example: android12-5.10.66
arch:
required: true
type: string
description: >
Build arch: aarch64/x86_64
debug:
required: false
type: boolean
default: true
manifest_name:
required: false
type: string
description: >
Local repo manifest xml path,
typically for AVD kernel build.
secrets:
BOOT_SIGN_KEY:
required: false
CHAT_ID:
required: false
BOT_TOKEN:
required: false
MESSAGE_THREAD_ID:
required: false
jobs:
build:
name: Build ${{ inputs.version_name }}
runs-on: ubuntu-latest
steps:
- 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
- name: Setup need_upload
id: need_upload
run: |
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
echo "UPLOAD=true" >> $GITHUB_OUTPUT
else
echo "UPLOAD=false" >> $GITHUB_OUTPUT
fi
- name: Setup kernel source
run: |
echo "Free space:"
df -h
cd $GITHUB_WORKSPACE
sudo apt-get install repo -y
mkdir android-kernel && cd android-kernel
repo init --depth=1 -u https://android.googlesource.com/kernel/manifest -m "$GITHUB_WORKSPACE/KernelSU/.github/manifests/${{ inputs.manifest_name }}" --repo-rev=v2.16
repo --version
repo --trace sync -c -j$(nproc --all) --no-tags
df -h
- name: Setup KernelSU
env:
PATCH_PATH: ${{ inputs.patch_path }}
IS_DEBUG_KERNEL: ${{ inputs.debug }}
run: |
cd $GITHUB_WORKSPACE/android-kernel
echo "[+] KernelSU setup"
GKI_ROOT=$(pwd)
echo "[+] GKI_ROOT: $GKI_ROOT"
echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers"
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-y += kernelsu/\n" >> "$DRIVER_MAKEFILE"
echo "[+] Apply KernelSU patches"
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"
printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
fi
repo status
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: 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 kernel
working-directory: android-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
tools/bazel run --config=android_ci --config=stamp --lto=thin //common-modules/virtual-device:virtual_device_${{ inputs.arch }}_dist -- --dist_dir=dist
NAME=kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }}
TARGET_IMAGE=dist/bzImage
if [ ! -e $TARGET_IMAGE ]; then
TARGET_IMAGE=dist/Image
fi
mv $TARGET_IMAGE $NAME
echo "file_path=android-kernel/$NAME" >> $GITHUB_ENV
- name: Upload Kernel
uses: actions/upload-artifact@v4
with:
name: kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_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 }}-avd-${{ inputs.version_name }}
echo "[+] title: $TITLE"
export TITLE
export VERSION="${{ env.kernelsu_version }}"
echo "[+] Image to upload"
ls -l "${{ env.file_path }}"
if [ -n "${{ secrets.BOT_TOKEN }}" ]; then
pip3 install telethon==1.31.1
python3 "$GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py" "${{ env.file_path }}"
fi

View File

@@ -44,7 +44,9 @@ jobs:
- sub_level: 185
os_patch_level: 2023-09
- sub_level: 198
os_patch_level: 2023-11
os_patch_level: 2024-01
- sub_level: 205
os_patch_level: 2024-03
uses: ./.github/workflows/gki-kernel.yml
secrets: inherit
with:
@@ -132,4 +134,4 @@ jobs:
version_name: android12-5.10.177
tag: android12-5.10-2023-06
os_patch_level: 2023-06
patch_path: "5.10"
patch_path: "5.10"

View File

@@ -35,7 +35,10 @@ jobs:
os_patch_level: 2023-05
- version: "5.10"
sub_level: 177
os_patch_level: 2023-06
os_patch_level: 2023-07
- version: "5.10"
sub_level: 177
os_patch_level: 2024-02
- version: "5.10"
sub_level: 186
os_patch_level: 2023-08
@@ -48,6 +51,9 @@ jobs:
- version: "5.10"
sub_level: 198
os_patch_level: 2023-12
- version: "5.10"
sub_level: 205
os_patch_level: 2024-02
- version: "5.15"
sub_level: 41
os_patch_level: 2022-11
@@ -72,6 +78,9 @@ jobs:
- version: "5.15"
sub_level: 137
os_patch_level: 2023-12
- version: "5.15"
sub_level: 144
os_patch_level: 2024-02
uses: ./.github/workflows/gki-kernel.yml
secrets: inherit
with:
@@ -168,4 +177,4 @@ jobs:
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 }}
patch_path: ${{ matrix.version }}

View File

@@ -27,6 +27,12 @@ jobs:
- version: "5.15"
sub_level: 131
os_patch_level: 2023-11
- version: "5.15"
sub_level: 137
os_patch_level: 2024-01
- version: "5.15"
sub_level: 144
os_patch_level: 2024-03
- version: "6.1"
sub_level: 25
os_patch_level: 2023-10
@@ -35,7 +41,10 @@ jobs:
os_patch_level: 2023-11
- version: "6.1"
sub_level: 57
os_patch_level: 2023-12
os_patch_level: 2024-01
- version: "6.1"
sub_level: 68
os_patch_level: 2024-02
uses: ./.github/workflows/gki-kernel.yml
secrets: inherit
with:
@@ -123,10 +132,13 @@ jobs:
- version: "5.15"
sub_level: 110
os_patch_level: 2023-09
- version: "6.1"
sub_level: 68
os_patch_level: 2024-02
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 }}
patch_path: ${{ matrix.version }}

37
.github/workflows/build-kernel-avd.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build Kernel - AVD
on:
push:
branches: ["main", "ci", "checkci"]
paths:
- ".github/workflows/build-kernel-avd.yml"
- ".github/workflows/avd-kernel.yml"
- ".github/workflows/manifests/*xml"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-avd.yml"
- ".github/workflows/avd-kernel.yml"
- ".github/workflows/manifests/*.xml"
- "kernel/**"
workflow_call:
workflow_dispatch:
jobs:
build-kernel:
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
uses: ./.github/workflows/avd-kernel.yml
secrets: inherit
strategy:
matrix:
include:
- version: "android-14-avd_x86_64"
manifest: "android-14-avd_x86_64.xml"
arch: "x86_64"
- version: "android-15-avd_aarch64"
manifest: "android-15-avd_aarch64.xml"
arch: "aarch64"
with:
version_name: ${{ matrix.version }}
manifest_name: ${{ matrix.manifest }}
arch: ${{ matrix.arch }}
debug: true

View File

@@ -18,8 +18,20 @@ jobs:
matrix:
include:
- target: aarch64-linux-android
os: ubuntu-latest
- target: x86_64-linux-android
- target: x86_64-pc-windows-gnu # only for build
os: ubuntu-latest
- target: x86_64-pc-windows-gnu # windows pc
os: ubuntu-latest
- target: x86_64-apple-darwin # Intel mac
os: macos-latest
- target: aarch64-apple-darwin # M chip mac
os: macos-latest
- target: aarch64-unknown-linux-musl # arm64 Linux
os: ubuntu-latest
- target: x86_64-unknown-linux-musl # x86 Linux
os: ubuntu-latest
uses: ./.github/workflows/ksud.yml
with:
target: ${{ matrix.target }}
os: ${{ matrix.os }}

42
.github/workflows/build-lkm.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Build LKM for KernelSU
on:
push:
branches: ["main", "ci", "checkci"]
paths:
- ".github/workflows/gki-kernel.yml"
- ".github/workflows/build-lkm.yml"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/gki-kernel.yml"
- ".github/workflows/build-lkm.yml"
- "kernel/**"
workflow_call:
jobs:
build-lkm:
strategy:
matrix:
include:
- version: "android12-5.10"
sub_level: 198
os_patch_level: "2024-01"
- version: "android13-5.10"
sub_level: 198
os_patch_level: 2023-12
- version: "android13-5.15"
sub_level: 137
os_patch_level: 2023-12
- version: "android14-5.15"
sub_level: 110
os_patch_level: 2023-09
- version: "android14-6.1"
sub_level: 43
os_patch_level: 2023-11
uses: ./.github/workflows/gki-kernel.yml
with:
version: ${{ matrix.version }}
version_name: ${{ matrix.version }}.${{ matrix.sub_level }}
tag: ${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
build_lkm: true

View File

@@ -29,7 +29,7 @@ on:
for example: 2021-11
default: 2022-05
patch_path:
required: true
required: false
type: string
description: >
Directory name of .github/patches/<patch_path>
@@ -49,6 +49,10 @@ on:
required: false
type: boolean
default: false
build_lkm:
required: false
type: boolean
default: false
secrets:
BOOT_SIGN_KEY:
required: false
@@ -124,13 +128,13 @@ jobs:
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
grep -q "kernelsu" $DRIVER_MAKEFILE || printf "\nobj-y += kernelsu/\n" >> $DRIVER_MAKEFILE
echo "[+] Apply KernelSU patches"
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
printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
fi
repo status
echo "[+] KernelSU setup done."
@@ -154,6 +158,34 @@ jobs:
max-size: 2G
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
- name: Setup for LKM
if: ${{ inputs.build_lkm == true }}
working-directory: android-kernel
run: |
pip install ast-grep-cli
sudo apt-get install llvm-15 -y
ast-grep -U -p '$$$ check_exports($$$) {$$$}' -r '' common/scripts/mod/modpost.c
ast-grep -U -p 'check_exports($$$);' -r '' common/scripts/mod/modpost.c
sed -i '1i KSU_MODULE := 1' $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
echo "drivers/kernelsu/kernelsu.ko" >> common/android/gki_aarch64_modules
# bazel build, android14-5.15, android14-6.1 use bazel
if [ ! -e build/build.sh ]; then
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
if [ -e common/modules.bzl ]; then
sed -i 's/_COMMON_GKI_MODULES_LIST = \[/_COMMON_GKI_MODULES_LIST = \[ "drivers\/kernelsu\/kernelsu.ko",/g' common/modules.bzl
fi
else
TARGET_FILE="build/kernel/build.sh"
if [ ! -e "$TARGET_FILE" ]; then
TARGET_FILE="build/build.sh"
fi
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' $TARGET_FILE || echo "No unknown symbol in $TARGET_FILE"
sed -i 's/if ! diff -u "\${KERNEL_DIR}\/\${MODULES_ORDER}" "\${OUT_DIR}\/modules\.order"; then/if false; then/g' $TARGET_FILE
sed -i 's@${ROOT_DIR}/build/abi/compare_to_symbol_list@echo@g' $TARGET_FILE
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
fi
- name: Make working directory clean to avoid dirty
working-directory: android-kernel
run: |
@@ -163,7 +195,7 @@ jobs:
cd common/ && git add -A && git commit -a -m "Add KernelSU"
repo status
- name: Build boot.img
- name: Build Kernel/LKM
working-directory: android-kernel
run: |
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
@@ -184,20 +216,34 @@ jobs:
OUTDIR=android-kernel/dist
fi
mkdir output
cp $OUTDIR/Image ./output/
cp $OUTDIR/Image.lz4 ./output/
git clone https://github.com/Kernel-SU/AnyKernel3
rm -rf ./AnyKernel3/.git
cp $OUTDIR/Image ./AnyKernel3/
if [ "${{ inputs.build_lkm}}" = "true" ]; then
llvm-strip-15 -d $OUTDIR/kernelsu.ko
mv $OUTDIR/kernelsu.ko ./output/${{ inputs.version }}_kernelsu.ko
else
cp $OUTDIR/Image ./output/
cp $OUTDIR/Image.lz4 ./output/
git clone https://github.com/Kernel-SU/AnyKernel3
rm -rf ./AnyKernel3/.git
cp $OUTDIR/Image ./AnyKernel3/
fi
- name: Upload Image and Image.gz
uses: actions/upload-artifact@v4
if: ${{ inputs.build_lkm == false }}
with:
name: Image-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
path: ./output/*
- name: Upload AnyKernel3
if: ${{ inputs.build_lkm == false }}
uses: actions/upload-artifact@v4
with:
name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
path: ./AnyKernel3/*
- name: Upload LKM
uses: actions/upload-artifact@v4
if: ${{ inputs.build_lkm == true }}
with:
name: ${{ inputs.version }}_lkm
path: ./output/*_kernelsu.ko

View File

@@ -5,19 +5,27 @@ on:
target:
required: true
type: string
os:
required: false
type: string
default: ubuntu-latest
use_cache:
required: false
type: boolean
default: true
jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ inputs.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# cross build failed after Rust 1.68, see https://github.com/cross-rs/cross/issues/1222
- run: rustup default 1.67.0
- name: Setup rustup
run: |
rustup default 1.67.0
rustup target add x86_64-apple-darwin
rustup target add aarch64-apple-darwin
- uses: Swatinem/rust-cache@v2
with:
workspaces: userspace/ksud
@@ -33,4 +41,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ksud-${{ inputs.target }}
path: userspace/ksud/target/**/release/ksud
path: userspace/ksud/target/**/release/ksud*

View File

@@ -9,6 +9,9 @@ jobs:
build-manager:
uses: ./.github/workflows/build-manager.yml
secrets: inherit
build-lkm:
uses: ./.github/workflows/build-lkm.yml
secrets: inherit
build-a12-kernel:
uses: ./.github/workflows/build-kernel-a12.yml
secrets: inherit
@@ -27,6 +30,7 @@ jobs:
release:
needs:
- build-manager
- build-lkm
- build-a12-kernel
- build-a13-kernel
- build-a14-kernel
@@ -71,6 +75,7 @@ jobs:
with:
files: |
manager/*.apk
*-lkm/*_kernelsu.ko
AnyKernel3-*.zip
boot-images-*/Image-*/*.img.gz
kernel-WSA*.zip

45
js/index.d.ts vendored Normal file
View File

@@ -0,0 +1,45 @@
interface ExecOptions {
cwd?: string,
env?: { [key: string]: string }
}
interface ExecResults {
errno: number,
stdout: string,
stderr: string
}
declare function exec(command: string): Promise<ExecResults>;
declare function exec(command: string, options: ExecOptions): Promise<ExecResults>;
interface SpawnOptions {
cwd?: string,
env?: { [key: string]: string }
}
interface Stdio {
on(event: 'data', callback: (data: string) => void)
}
interface ChildProcess {
stdout: Stdio,
stderr: Stdio,
on(event: 'exit', callback: (code: number) => void)
on(event: 'error', callback: (err: any) => void)
}
declare function spawn(command: string): ChildProcess;
declare function spawn(command: string, args: string[]): ChildProcess;
declare function spawn(command: string, options: SpawnOptions): ChildProcess;
declare function spawn(command: string, args: string[], options: SpawnOptions): ChildProcess;
declare function fullScreen(isFullScreen: boolean);
declare function toast(message: string);
export {
exec,
spawn,
fullScreen,
toast
}

View File

@@ -71,7 +71,7 @@ function Stdio() {
export function spawn(command, args, options) {
if (typeof args === "undefined") {
args = [];
} else if (typeof args === "object") {
} else if (!(args instanceof Array)) {
// allow for (command, options) signature
options = args;
}

View File

@@ -3,6 +3,7 @@
"version": "1.0.6",
"description": "Library for KernelSU's module WebUI",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"test": "npm run test"
},

View File

@@ -8,3 +8,7 @@ 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
clippy:
cargo fmt --manifest-path ./userspace/ksud/Cargo.toml
cross clippy --target x86_64-pc-windows-gnu --release --manifest-path ./userspace/ksud/Cargo.toml
cross clippy --target aarch64-linux-android --release --manifest-path ./userspace/ksud/Cargo.toml

View File

@@ -14,4 +14,11 @@ config KSU_DEBUG
help
Enable KernelSU debug mode
config KSU_MODULE
bool "Build KernelSU as a module"
depends on KSU
default n
help
Build KernelSU as a loadable kernel module
endmenu

View File

@@ -1,19 +1,30 @@
obj-y += ksu.o
obj-y += allowlist.o
kernelsu-objs := apk_sign.o
obj-y += kernelsu.o
obj-y += module_api.o
obj-y += sucompat.o
obj-y += uid_observer.o
obj-y += manager.o
obj-y += core_hook.o
obj-y += ksud.o
obj-y += embed_ksud.o
obj-y += kernel_compat.o
kernelsu-objs := ksu.o
kernelsu-objs += allowlist.o
kernelsu-objs += apk_sign.o
kernelsu-objs += module_api.o
kernelsu-objs += sucompat.o
kernelsu-objs += uid_observer.o
kernelsu-objs += manager.o
kernelsu-objs += core_hook.o
kernelsu-objs += ksud.o
kernelsu-objs += embed_ksud.o
kernelsu-objs += kernel_compat.o
kernelsu-objs += selinux/selinux.o
kernelsu-objs += selinux/sepolicy.o
kernelsu-objs += selinux/rules.o
ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
ifndef KSU_MODULE
obj-y += kernelsu.o
else
obj-m += kernelsu.o
endif
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)
$(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow)
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD)
# ksu_version: major * 10000 + git version + 200 for historical reasons
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 200))
@@ -24,6 +35,14 @@ $(warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git subm
ccflags-y += -DKSU_VERSION=16
endif
ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0)
ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID
endif
ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0)
ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE
endif
ifndef KSU_EXPECTED_SIZE
KSU_EXPECTED_SIZE := 0x033b
endif
@@ -42,5 +61,8 @@ $(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
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
# Keep a new line here!! Because someone may append config

View File

@@ -4,12 +4,20 @@
#include "linux/err.h"
#include "linux/init.h"
#include "linux/init_task.h"
#include "linux/kallsyms.h"
#include "linux/kernel.h"
#include "linux/kprobes.h"
#include "linux/list.h"
#include "linux/lsm_hooks.h"
#include "linux/mm.h"
#include "linux/mm_types.h"
#include "linux/nsproxy.h"
#include "linux/path.h"
#include "linux/printk.h"
#include "linux/sched.h"
#include "linux/security.h"
#include "linux/stddef.h"
#include "linux/types.h"
#include "linux/uaccess.h"
#include "linux/uidgid.h"
#include "linux/version.h"
@@ -25,6 +33,7 @@
#include "klog.h" // IWYU pragma: keep
#include "ksu.h"
#include "ksud.h"
#include "linux/vmalloc.h"
#include "manager.h"
#include "selinux/selinux.h"
#include "uid_observer.h"
@@ -220,7 +229,9 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
return 0;
}
// pr_info("option: 0x%x, cmd: %ld\n", option, arg2);
#ifdef CONFIG_KSU_DEBUG
pr_info("option: 0x%x, cmd: %ld\n", option, arg2);
#endif
if (arg2 == CMD_BECOME_MANAGER) {
// quick check
@@ -231,8 +242,10 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
return 0;
}
if (ksu_is_manager_uid_valid()) {
#ifdef CONFIG_KSU_DEBUG
pr_info("manager already exist: %d\n",
ksu_get_manager_uid());
#endif
return 0;
}
@@ -722,14 +735,181 @@ void __init ksu_lsm_hook_init(void)
#endif
}
#ifdef MODULE
static int override_security_head(void *head, const void *new_head, size_t len)
{
unsigned long base = (unsigned long)head & PAGE_MASK;
unsigned long offset = offset_in_page(head);
// this is impossible for our case because the page alignment
// but be careful for other cases!
BUG_ON(offset + len > PAGE_SIZE);
struct page *page = phys_to_page(__pa(base));
if (!page) {
return -EFAULT;
}
void *addr = vmap(&page, 1, VM_MAP, PAGE_KERNEL);
if (!addr) {
return -ENOMEM;
}
memcpy(addr + offset, new_head, len);
vunmap(addr);
return 0;
}
static void free_security_hook_list(struct hlist_head *head)
{
struct hlist_node *temp;
struct security_hook_list *entry;
if (!head)
return;
hlist_for_each_entry_safe (entry, temp, head, list) {
hlist_del(&entry->list);
kfree(entry);
}
kfree(head);
}
struct hlist_head *copy_security_hlist(struct hlist_head *orig)
{
struct hlist_head *new_head = kmalloc(sizeof(*new_head), GFP_KERNEL);
if (!new_head)
return NULL;
INIT_HLIST_HEAD(new_head);
struct security_hook_list *entry;
struct security_hook_list *new_entry;
hlist_for_each_entry (entry, orig, list) {
new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL);
if (!new_entry) {
free_security_hook_list(new_head);
return NULL;
}
*new_entry = *entry;
hlist_add_tail_rcu(&new_entry->list, new_head);
}
return new_head;
}
#define LSM_SEARCH_MAX 180 // This should be enough to iterate
static void *find_head_addr(void *security_ptr, int *index)
{
if (!security_ptr) {
return NULL;
}
struct hlist_head *head_start =
(struct hlist_head *)&security_hook_heads;
for (int i = 0; i < LSM_SEARCH_MAX; i++) {
struct hlist_head *head = head_start + i;
struct security_hook_list *pos;
hlist_for_each_entry (pos, head, list) {
if (pos->hook.capget == security_ptr) {
if (index) {
*index = i;
}
return head;
}
}
}
return NULL;
}
#define GET_SYMBOL_ADDR(sym) \
({ \
void *addr = kallsyms_lookup_name(#sym ".cfi_jt"); \
if (!addr) { \
addr = kallsyms_lookup_name(#sym); \
} \
addr; \
})
#define KSU_LSM_HOOK_HACK_INIT(head_ptr, name, func) \
do { \
static struct security_hook_list hook = { \
.hook = { .name = func } \
}; \
hook.head = head_ptr; \
hook.lsm = "ksu"; \
struct hlist_head *new_head = copy_security_hlist(hook.head); \
if (!new_head) { \
pr_err("Failed to copy security list: %s\n", #name); \
break; \
} \
hlist_add_tail_rcu(&hook.list, new_head); \
if (override_security_head(hook.head, new_head, \
sizeof(*new_head))) { \
free_security_hook_list(new_head); \
pr_err("Failed to hack lsm for: %s\n", #name); \
} \
} while (0)
void __init ksu_lsm_hook_init_hack(void)
{
void *cap_prctl = GET_SYMBOL_ADDR(cap_task_prctl);
void *prctl_head = find_head_addr(cap_prctl, NULL);
if (prctl_head) {
if (prctl_head != &security_hook_heads.task_prctl) {
pr_warn("prctl's address has shifted!\n");
}
KSU_LSM_HOOK_HACK_INIT(prctl_head, task_prctl, ksu_task_prctl);
} else {
pr_warn("Failed to find task_prctl!\n");
}
int inode_killpriv_index = -1;
void *cap_killpriv = GET_SYMBOL_ADDR(cap_inode_killpriv);
find_head_addr(cap_killpriv, &inode_killpriv_index);
if (inode_killpriv_index < 0) {
pr_warn("Failed to find inode_rename, use kprobe instead!\n");
register_kprobe(&renameat_kp);
} else {
int inode_rename_index = inode_killpriv_index +
&security_hook_heads.inode_rename -
&security_hook_heads.inode_killpriv;
struct hlist_head *head_start =
(struct hlist_head *)&security_hook_heads;
void *inode_rename_head = head_start + inode_rename_index;
if (inode_rename_head != &security_hook_heads.inode_rename) {
pr_warn("inode_rename's address has shifted!\n");
}
KSU_LSM_HOOK_HACK_INIT(inode_rename_head, inode_rename,
ksu_inode_rename);
}
void *cap_setuid = GET_SYMBOL_ADDR(cap_task_fix_setuid);
void *setuid_head = find_head_addr(cap_setuid, NULL);
if (setuid_head) {
if (setuid_head != &security_hook_heads.task_fix_setuid) {
pr_warn("setuid's address has shifted!\n");
}
KSU_LSM_HOOK_HACK_INIT(setuid_head, task_fix_setuid,
ksu_task_fix_setuid);
} else {
pr_warn("Failed to find task_fix_setuid!\n");
}
smp_mb();
}
#endif
void __init ksu_core_init(void)
{
#ifndef MODULE
pr_info("ksu_lsm_hook_init\n");
ksu_lsm_hook_init();
#else
pr_info("ksu_kprobe_init\n");
ksu_kprobe_init();
pr_info("ksu_lsm_hook_init hack!!!!\n");
ksu_lsm_hook_init_hack();
#endif
}

View File

@@ -14,8 +14,10 @@
* Huawei Hisi Kernel EBITMAP Enable or Disable Flag ,
* From ss/ebitmap.h
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) && \
LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) && \
LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || \
LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) && \
LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
#ifdef HISI_SELINUX_EBITMAP_RO
#define CONFIG_IS_HW_HISI
#endif

View File

@@ -37,6 +37,23 @@ fun parseKernelVersion(version: String): KernelVersion {
}
}
fun parseKMI(input: String): String? {
val regex = Regex("(.* )?(\\d+\\.\\d+)(\\S+)?(android\\d+)(.*)")
val result = regex.find(input)
return result?.let {
val androidVersion = it.groups[4]?.value ?: ""
val kernelVersion = it.groups[2]?.value ?: ""
"$androidVersion-$kernelVersion"
}
}
fun getKMI(): String? {
Os.uname().release.let {
return parseKMI(it)
}
}
fun getKernelVersion(): KernelVersion {
Os.uname().release.let {
return parseKernelVersion(it)

View File

@@ -5,13 +5,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
@@ -27,11 +21,9 @@ import com.ramcosta.composedestinations.navigation.popBackStack
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.component.rememberDialogHostState
import me.weishu.kernelsu.ui.screen.BottomBarDestination
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
@@ -54,7 +46,6 @@ class MainActivity : ComponentActivity() {
) { innerPadding ->
CompositionLocalProvider(
LocalSnackbarHost provides snackbarHostState,
LocalDialogHost provides rememberDialogHostState(),
) {
DestinationsNavHost(
modifier = Modifier.padding(innerPadding),

View File

@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@@ -52,11 +53,9 @@ fun AboutCard() {
}
@Composable
fun AboutDialog(showAboutDialog: MutableState<Boolean>) {
if (showAboutDialog.value) {
Dialog(onDismissRequest = { showAboutDialog.value = false }) {
AboutCard()
}
fun AboutDialog(dismiss: () -> Unit) {
Dialog(onDismissRequest = { dismiss() }) {
AboutCard()
}
}

View File

@@ -1,8 +1,10 @@
package me.weishu.kernelsu.ui.component
import android.graphics.text.LineBreaker
import android.os.Parcelable
import android.text.Layout
import android.text.method.LinkMovementMethod
import android.util.Log
import android.view.ViewGroup
import android.widget.TextView
import androidx.compose.foundation.layout.Box
@@ -10,14 +12,10 @@ 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.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
@@ -28,48 +26,48 @@ 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
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import me.weishu.kernelsu.ui.util.LocalDialogHost
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
import kotlin.coroutines.resume
interface DialogVisuals
private const val TAG = "DialogComponent"
interface LoadingDialogVisuals : DialogVisuals
interface PromptDialogVisuals : DialogVisuals {
interface ConfirmDialogVisuals : Parcelable {
val title: String
val content: String
}
interface ConfirmDialogVisuals : PromptDialogVisuals {
val isMarkdown: Boolean
val confirm: String?
val dismiss: String?
val isMarkdown: Boolean
}
sealed interface DialogData {
val visuals: DialogVisuals
@Parcelize
private data class ConfirmDialogVisualsImpl(
override val title: String,
override val content: String,
override val isMarkdown: Boolean,
override val confirm: String?,
override val dismiss: String?,
) : ConfirmDialogVisuals {
companion object {
val Empty: ConfirmDialogVisuals = ConfirmDialogVisualsImpl("", "", false, null, null)
}
}
interface LoadingDialogData : DialogData {
override val visuals: LoadingDialogVisuals
fun dismiss()
interface DialogHandle {
val isShown: Boolean
val dialogType: String
fun show()
fun hide()
}
interface PromptDialogData : DialogData {
override val visuals: PromptDialogVisuals
fun dismiss()
}
interface ConfirmDialogData : PromptDialogData {
override val visuals: ConfirmDialogVisuals
fun confirm()
interface LoadingDialogHandle : DialogHandle {
suspend fun <R> withLoading(block: suspend () -> R): R
fun showLoading()
}
sealed interface ConfirmResult {
@@ -77,143 +75,313 @@ sealed interface ConfirmResult {
object Canceled : ConfirmResult
}
class DialogHostState {
interface ConfirmDialogHandle : DialogHandle {
val visuals: ConfirmDialogVisuals
private object LoadingDialogVisualsImpl : LoadingDialogVisuals
private data class PromptDialogVisualsImpl(
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 isMarkdown: Boolean,
) : ConfirmDialogVisuals
private data class LoadingDialogDataImpl(
override val visuals: LoadingDialogVisuals,
private val continuation: CancellableContinuation<Unit>,
) : LoadingDialogData {
override fun dismiss() {
if (continuation.isActive) continuation.resume(Unit)
}
}
private data class PromptDialogDataImpl(
override val visuals: PromptDialogVisuals,
private val continuation: CancellableContinuation<Unit>,
) : PromptDialogData {
override fun dismiss() {
if (continuation.isActive) continuation.resume(Unit)
}
}
private data class ConfirmDialogDataImpl(
override val visuals: ConfirmDialogVisuals,
private val continuation: CancellableContinuation<ConfirmResult>
) : ConfirmDialogData {
override fun confirm() {
if (continuation.isActive) continuation.resume(ConfirmResult.Confirmed)
}
override fun dismiss() {
if (continuation.isActive) continuation.resume(ConfirmResult.Canceled)
}
}
private val mutex = Mutex()
var currentDialogData by mutableStateOf<DialogData?>(null)
private set
suspend fun showLoading() {
try {
mutex.withLock {
suspendCancellableCoroutine { continuation ->
currentDialogData = LoadingDialogDataImpl(
visuals = LoadingDialogVisualsImpl, continuation = continuation
)
}
}
} finally {
currentDialogData = null
}
}
suspend fun <R> withLoading(block: suspend () -> R) = coroutineScope {
val showLoading = launch {
showLoading()
}
val result = block()
showLoading.cancel()
result
}
suspend fun showPrompt(title: String, content: String) {
try {
mutex.withLock {
suspendCancellableCoroutine { continuation ->
currentDialogData = PromptDialogDataImpl(
visuals = PromptDialogVisualsImpl(title, content),
continuation = continuation
)
}
}
} finally {
currentDialogData = null
}
}
suspend fun showConfirm(
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, markdown),
continuation = continuation
)
)
suspend fun awaitConfirm(
title: String,
content: String,
markdown: Boolean = false,
confirm: String? = null,
dismiss: String? = null
): ConfirmResult
}
private abstract class DialogHandleBase(
protected val visible: MutableState<Boolean>,
protected val coroutineScope: CoroutineScope
) : DialogHandle {
override val isShown: Boolean
get() = visible.value
override fun show() {
coroutineScope.launch {
visible.value = true
}
}
final override fun hide() {
coroutineScope.launch {
visible.value = false
}
}
override fun toString(): String {
return dialogType
}
}
private class LoadingDialogHandleImpl(
visible: MutableState<Boolean>,
coroutineScope: CoroutineScope
) : LoadingDialogHandle, DialogHandleBase(visible, coroutineScope) {
override suspend fun <R> withLoading(block: suspend () -> R): R {
return coroutineScope.async {
try {
visible.value = true
block()
} finally {
visible.value = false
}
}.await()
}
override fun showLoading() {
show()
}
override val dialogType: String get() = "LoadingDialog"
}
typealias NullableCallback = (() -> Unit)?
interface ConfirmCallback {
val onConfirm: NullableCallback
val onDismiss: NullableCallback
val isEmpty: Boolean get() = onConfirm == null && onDismiss == null
companion object {
operator fun invoke(onConfirmProvider: () -> NullableCallback, onDismissProvider: () -> NullableCallback): ConfirmCallback {
return object : ConfirmCallback {
override val onConfirm: NullableCallback
get() = onConfirmProvider()
override val onDismiss: NullableCallback
get() = onDismissProvider()
}
} finally {
currentDialogData = null
}
}
}
private class ConfirmDialogHandleImpl(
visible: MutableState<Boolean>,
coroutineScope: CoroutineScope,
callback: ConfirmCallback,
override var visuals: ConfirmDialogVisuals = ConfirmDialogVisualsImpl.Empty,
private val resultFlow: ReceiveChannel<ConfirmResult>
) : ConfirmDialogHandle, DialogHandleBase(visible, coroutineScope) {
private class ResultCollector(
private val callback: ConfirmCallback
) : FlowCollector<ConfirmResult> {
fun handleResult(result: ConfirmResult) {
Log.d(TAG, "handleResult: ${result.javaClass.simpleName}")
when (result) {
ConfirmResult.Confirmed -> onConfirm()
ConfirmResult.Canceled -> onDismiss()
}
}
fun onConfirm() {
callback.onConfirm?.invoke()
}
fun onDismiss() {
callback.onDismiss?.invoke()
}
override suspend fun emit(value: ConfirmResult) {
handleResult(value)
}
}
private val resultCollector = ResultCollector(callback)
private var awaitContinuation: CancellableContinuation<ConfirmResult>? = null
private val isCallbackEmpty = callback.isEmpty
init {
coroutineScope.launch {
resultFlow
.consumeAsFlow()
.onEach { result ->
awaitContinuation?.let {
awaitContinuation = null
if (it.isActive) {
it.resume(result)
}
}
}
.onEach { hide() }
.collect(resultCollector)
}
}
private suspend fun awaitResult(): ConfirmResult {
return suspendCancellableCoroutine {
awaitContinuation = it.apply {
if (isCallbackEmpty) {
invokeOnCancellation {
visible.value = false
}
}
}
}
}
fun updateVisuals(visuals: ConfirmDialogVisuals) {
this.visuals = visuals
}
override fun show() {
if (visuals !== ConfirmDialogVisualsImpl.Empty) {
super.show()
} else {
throw UnsupportedOperationException("can't show confirm dialog with the Empty visuals")
}
}
override fun showConfirm(
title: String,
content: String,
markdown: Boolean,
confirm: String?,
dismiss: String?
) {
coroutineScope.launch {
updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))
show()
}
}
override suspend fun awaitConfirm(
title: String,
content: String,
markdown: Boolean,
confirm: String?,
dismiss: String?
): ConfirmResult {
coroutineScope.launch {
updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))
show()
}
return awaitResult()
}
override val dialogType: String get() = "ConfirmDialog"
override fun toString(): String {
return "${super.toString()}(visuals: $visuals)"
}
companion object {
fun Saver(
visible: MutableState<Boolean>,
coroutineScope: CoroutineScope,
callback: ConfirmCallback,
resultChannel: ReceiveChannel<ConfirmResult>
) = Saver<ConfirmDialogHandle, ConfirmDialogVisuals>(
save = {
it.visuals
},
restore = {
Log.d(TAG, "ConfirmDialog restore, visuals: $it")
ConfirmDialogHandleImpl(visible, coroutineScope, callback, it, resultChannel)
}
)
}
}
private class CustomDialogHandleImpl(
visible: MutableState<Boolean>,
coroutineScope: CoroutineScope
) : DialogHandleBase(visible, coroutineScope) {
override val dialogType: String get() = "CustomDialog"
}
@Composable
fun rememberDialogHostState(): DialogHostState {
fun rememberLoadingDialog(): LoadingDialogHandle {
val visible = remember {
mutableStateOf(false)
}
val coroutineScope = rememberCoroutineScope()
if (visible.value) {
LoadingDialog()
}
return remember {
DialogHostState()
}
}
private inline fun <reified T : DialogData> DialogData?.tryInto(): T? {
return when (this) {
is T -> this
else -> null
LoadingDialogHandleImpl(visible, coroutineScope)
}
}
@Composable
fun LoadingDialog(
state: DialogHostState = LocalDialogHost.current,
) {
state.currentDialogData.tryInto<LoadingDialogData>() ?: return
val dialogProperties = remember {
DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)
private fun rememberConfirmDialog(visuals: ConfirmDialogVisuals, callback: ConfirmCallback): ConfirmDialogHandle {
val visible = rememberSaveable {
mutableStateOf(false)
}
Dialog(onDismissRequest = {}, properties = dialogProperties) {
val coroutineScope = rememberCoroutineScope()
val resultChannel = remember {
Channel<ConfirmResult>()
}
val handle = rememberSaveable(
saver = ConfirmDialogHandleImpl.Saver(visible, coroutineScope, callback, resultChannel),
init = {
ConfirmDialogHandleImpl(visible, coroutineScope, callback, visuals, resultChannel)
}
)
if (visible.value) {
ConfirmDialog(
handle.visuals,
confirm = { coroutineScope.launch { resultChannel.send(ConfirmResult.Confirmed) } },
dismiss = { coroutineScope.launch { resultChannel.send(ConfirmResult.Canceled) } }
)
}
return handle
}
@Composable
fun rememberConfirmCallback(onConfirm: NullableCallback, onDismiss: NullableCallback): ConfirmCallback {
val currentOnConfirm by rememberUpdatedState(newValue = onConfirm)
val currentOnDismiss by rememberUpdatedState(newValue = onDismiss)
return remember {
ConfirmCallback({ currentOnConfirm }, { currentOnDismiss })
}
}
@Composable
fun rememberConfirmDialog(onConfirm: NullableCallback = null, onDismiss: NullableCallback = null): ConfirmDialogHandle {
return rememberConfirmDialog(rememberConfirmCallback(onConfirm, onDismiss))
}
@Composable
fun rememberConfirmDialog(callback: ConfirmCallback): ConfirmDialogHandle {
return rememberConfirmDialog(ConfirmDialogVisualsImpl.Empty, callback)
}
@Composable
fun rememberCustomDialog(composable: @Composable (dismiss: () -> Unit) -> Unit): DialogHandle {
val visible = rememberSaveable {
mutableStateOf(false)
}
val coroutineScope = rememberCoroutineScope()
if (visible.value) {
composable { visible.value = false }
}
return remember {
CustomDialogHandleImpl(visible, coroutineScope)
}
}
@Composable
private fun LoadingDialog() {
Dialog(
onDismissRequest = {},
properties = DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)
) {
Surface(
modifier = Modifier.size(100.dp), shape = RoundedCornerShape(8.dp)
) {
@@ -227,41 +395,10 @@ fun LoadingDialog(
}
@Composable
fun PromptDialog(
state: DialogHostState = LocalDialogHost.current,
) {
val promptDialogData = state.currentDialogData.tryInto<PromptDialogData>() ?: return
val visuals = promptDialogData.visuals
private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, dismiss: () -> Unit) {
AlertDialog(
onDismissRequest = {
promptDialogData.dismiss()
},
title = {
Text(text = visuals.title)
},
text = {
Text(text = visuals.content)
},
confirmButton = {
TextButton(onClick = { promptDialogData.dismiss() }) {
Text(text = stringResource(id = android.R.string.ok))
}
},
dismissButton = null,
)
}
@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()
dismiss()
},
title = {
Text(text = visuals.title)
@@ -274,17 +411,18 @@ fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
}
},
confirmButton = {
TextButton(onClick = { confirmDialogData.confirm() }) {
TextButton(onClick = confirm) {
Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = { confirmDialogData.dismiss() }) {
TextButton(onClick = dismiss) {
Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel))
}
},
)
}
@Composable
private fun MarkdownContent(content: String) {
val contentColor = LocalContentColor.current
@@ -307,5 +445,6 @@ private fun MarkdownContent(content: String) {
update = {
Markwon.create(it.context).setMarkdown(it, content)
it.setTextColor(contentColor.toArgb())
})
}
)
}

View File

@@ -28,6 +28,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -53,6 +54,7 @@ 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.rememberCustomDialog
import me.weishu.kernelsu.ui.util.isSepolicyValid
@OptIn(ExperimentalMaterial3Api::class)
@@ -187,10 +189,7 @@ fun RootProfileConfig(
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>) -> Unit) {
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
val selectGroupsDialog = rememberCustomDialog { dismiss: () -> Unit ->
val groups = Groups.values().sortedWith(
compareBy<Groups> { if (selected.contains(it)) 0 else 1 }
.then(compareBy {
@@ -217,7 +216,7 @@ fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>)
state = rememberUseCaseState(visible = true, onFinishedRequest = {
closeSelection(selection)
}, onCloseRequest = {
showDialog = false
dismiss()
}),
header = Header.Default(
title = stringResource(R.string.profile_groups),
@@ -241,7 +240,7 @@ fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>)
.fillMaxWidth()
.padding(16.dp)
.clickable {
showDialog = true
selectGroupsDialog.show()
}) {
Column(modifier = Modifier.padding(16.dp)) {
@@ -265,10 +264,7 @@ fun CapsPanel(
selected: Collection<Capabilities>,
closeSelection: (selection: Set<Capabilities>) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
val selectCapabilitiesDialog = rememberCustomDialog { dismiss ->
val caps = Capabilities.values().sortedWith(
compareBy<Capabilities> { if (selected.contains(it)) 0 else 1 }
.then(compareBy { it.name })
@@ -286,7 +282,7 @@ fun CapsPanel(
state = rememberUseCaseState(visible = true, onFinishedRequest = {
closeSelection(selection)
}, onCloseRequest = {
showDialog = false
dismiss()
}),
header = Header.Default(
title = stringResource(R.string.profile_capabilities),
@@ -309,7 +305,7 @@ fun CapsPanel(
.fillMaxWidth()
.padding(16.dp)
.clickable {
showDialog = true
selectCapabilitiesDialog.show()
}) {
Column(modifier = Modifier.padding(16.dp)) {
@@ -377,8 +373,7 @@ private fun SELinuxPanel(
profile: Natives.Profile,
onSELinuxChange: (domain: String, rules: String) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
val editSELinuxDialog = rememberCustomDialog { dismiss ->
var domain by remember { mutableStateOf(profile.context) }
var rules by remember { mutableStateOf(profile.rules) }
@@ -430,7 +425,7 @@ private fun SELinuxPanel(
onSELinuxChange(domain, rules)
},
onCloseRequest = {
showDialog = false
dismiss()
}),
header = Header.Default(
title = stringResource(R.string.profile_selinux_context),
@@ -449,7 +444,7 @@ private fun SELinuxPanel(
modifier = Modifier
.fillMaxWidth()
.clickable {
showDialog = true
editSELinuxDialog.show()
},
enabled = false,
colors = TextFieldDefaults.outlinedTextFieldColors(

View File

@@ -183,7 +183,7 @@ private fun AppProfileInner(
} else {
Mode.Custom
}
var mode by remember {
var mode by rememberSaveable {
mutableStateOf(initialMode)
}
ProfileBox(mode, true) {

View File

@@ -0,0 +1,189 @@
package me.weishu.kernelsu.ui.screen
import android.net.Uri
import android.os.Environment
import android.os.Parcelable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
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
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
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.installBoot
import me.weishu.kernelsu.ui.util.installModule
import me.weishu.kernelsu.ui.util.reboot
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
/**
* @author weishu
* @date 2023/1/1.
*/
@OptIn(ExperimentalComposeUiApi::class)
@Composable
@Destination
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
var text by rememberSaveable { mutableStateOf("") }
val logContent = rememberSaveable { StringBuilder() }
var showFloatAction by rememberSaveable { mutableStateOf(false) }
val snackBarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
LaunchedEffect(Unit) {
if (text.isNotEmpty()) {
return@LaunchedEffect
}
withContext(Dispatchers.IO) {
flashIt(flashIt, onFinish = { showReboot ->
if (showReboot) {
showFloatAction = true
}
}, onStdout = {
text += "$it\n"
logContent.append(it).append("\n")
}, onStderr = {
logContent.append(it).append("\n")
});
}
}
Scaffold(
topBar = {
TopBar(
onBack = {
navigator.popBackStack()
},
onSave = {
scope.launch {
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
val date = format.format(Date())
val file = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"KernelSU_install_log_${date}.log"
)
file.writeText(logContent.toString())
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
}
}
)
},
floatingActionButton = {
if (showFloatAction) {
val reboot = stringResource(id = R.string.reboot)
ExtendedFloatingActionButton(
onClick = {
scope.launch {
withContext(Dispatchers.IO) {
reboot()
}
}
},
icon = { Icon(Icons.Filled.Refresh, reboot) },
text = { Text(text = reboot) },
)
}
}
) { innerPadding ->
KeyEventBlocker {
it.key == Key.VolumeDown || it.key == Key.VolumeUp
}
Column(
modifier = Modifier
.fillMaxSize(1f)
.padding(innerPadding)
.verticalScroll(scrollState),
) {
LaunchedEffect(text) {
scrollState.animateScrollTo(scrollState.maxValue)
}
Text(
modifier = Modifier.padding(8.dp),
text = text,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = FontFamily.Monospace,
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
)
}
}
}
@Parcelize
sealed class FlashIt : Parcelable {
data class FlashBoot(val bootUri: Uri? = null, val koUri: Uri, val ota: Boolean) : FlashIt()
data class FlashModule(val uri: Uri) : FlashIt()
}
fun flashIt(
flashIt: FlashIt, onFinish: (Boolean) -> Unit,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit
) {
when (flashIt) {
is FlashIt.FlashBoot -> installBoot(
flashIt.bootUri,
flashIt.koUri,
flashIt.ota,
onFinish,
onStdout,
onStderr
)
is FlashIt.FlashModule -> installModule(flashIt.uri, onFinish, onStdout, onStderr)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
TopAppBar(
title = { Text(stringResource(R.string.install)) },
navigationIcon = {
IconButton(
onClick = onBack
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
},
actions = {
IconButton(onClick = onSave) {
Icon(
imageVector = Icons.Filled.Save,
contentDescription = "Localized description"
)
}
}
)
}
@Preview
@Composable
fun InstallPreview() {
// InstallScreen(DestinationsNavigator(), uri = Uri.EMPTY)
}

View File

@@ -5,11 +5,13 @@ import android.os.Build
import android.os.PowerManager
import android.system.Os
import androidx.annotation.StringRes
import androidx.compose.animation.*
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.outlined.Block
@@ -29,12 +31,11 @@ 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.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
import me.weishu.kernelsu.ui.util.*
@@ -42,9 +43,13 @@ import me.weishu.kernelsu.ui.util.*
@Destination
@Composable
fun HomeScreen(navigator: DestinationsNavigator) {
val kernelVersion = getKernelVersion()
Scaffold(topBar = {
TopBar(onSettingsClick = {
TopBar(kernelVersion, onSettingsClick = {
navigator.navigate(SettingScreenDestination)
}, onInstallClick = {
navigator.navigate(InstallScreenDestination)
})
}) { innerPadding ->
Column(
@@ -54,14 +59,15 @@ fun HomeScreen(navigator: DestinationsNavigator) {
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
val kernelVersion = getKernelVersion()
val isManager = Natives.becomeManager(ksuApp.packageName)
SideEffect {
if (isManager) install()
}
val ksuVersion = if (isManager) Natives.version else null
StatusCard(kernelVersion, ksuVersion)
StatusCard(kernelVersion, ksuVersion) {
navigator.navigate(InstallScreenDestination)
}
if (isManager && Natives.requireNewKernel()) {
WarningCard(
stringResource(id = R.string.require_kernel_version).format(
@@ -69,7 +75,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
)
)
}
if (!rootAvailable()) {
if (ksuVersion != null && !rootAvailable()) {
WarningCard(
stringResource(id = R.string.grant_root_failed)
)
@@ -84,7 +90,6 @@ fun HomeScreen(navigator: DestinationsNavigator) {
DonateCard()
LearnMoreCard()
Spacer(Modifier)
ConfirmDialog()
}
}
}
@@ -99,28 +104,28 @@ fun UpdateCard() {
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
AnimatedVisibility(
visible = newVersionCode >= currentVersionCode,
enter = fadeIn() + expandVertically(),
exit = shrinkVertically() + fadeOut()
) {
scope.launch {
if (changelog.isEmpty() || dialogHost.showConfirm(
val updateDialog = rememberConfirmDialog(onConfirm = { uriHandler.openUri(newVersionUrl) })
WarningCard(
message = stringResource(id = R.string.new_version_available).format(newVersionCode),
MaterialTheme.colorScheme.outlineVariant
) {
if (changelog.isNotEmpty()) {
updateDialog.showConfirm(
title = title,
content = changelog,
markdown = true,
confirm = updateText,
) == ConfirmResult.Confirmed
) {
uriHandler.openUri(newVersionUrl)
confirm = updateText
)
}
}
}
@@ -137,8 +142,17 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(onSettingsClick: () -> Unit) {
private fun TopBar(kernelVersion: KernelVersion, onInstallClick: () -> Unit, onSettingsClick: () -> Unit) {
TopAppBar(title = { Text(stringResource(R.string.app_name)) }, actions = {
if (kernelVersion.isGKI()) {
IconButton(onClick = onInstallClick) {
Icon(
imageVector = Icons.Filled.Archive,
contentDescription = stringResource(id = R.string.install)
)
}
}
var showDropdown by remember { mutableStateOf(false) }
IconButton(onClick = {
showDropdown = true
@@ -176,7 +190,7 @@ private fun TopBar(onSettingsClick: () -> Unit) {
}
@Composable
private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?, onClickInstall: () -> Unit = {}) {
ElevatedCard(
colors = CardDefaults.elevatedCardColors(containerColor = run {
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
@@ -187,8 +201,8 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
Row(modifier = Modifier
.fillMaxWidth()
.clickable {
if (kernelVersion.isGKI() && ksuVersion == null) {
uriHandler.openUri("https://kernelsu.org/guide/installation.html")
if (kernelVersion.isGKI()) {
onClickInstall()
}
}
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {

View File

@@ -1,140 +1,269 @@
package me.weishu.kernelsu.ui.screen
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Environment
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.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
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.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.platform.LocalContext
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.navigation.DestinationsNavigator
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
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination
import me.weishu.kernelsu.ui.util.DownloadListener
import me.weishu.kernelsu.ui.util.download
import me.weishu.kernelsu.ui.util.getLKMUrl
import me.weishu.kernelsu.ui.util.isAbDevice
import me.weishu.kernelsu.ui.util.rootAvailable
/**
* @author weishu
* @date 2023/1/1.
* @date 2024/3/12.
*/
@OptIn(ExperimentalComposeUiApi::class)
@Composable
@Destination
fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
var text by rememberSaveable { mutableStateOf("") }
val logContent = StringBuilder()
var showFloatAction by rememberSaveable { mutableStateOf(false) }
val snackBarHost = LocalSnackbarHost.current
@Composable
fun InstallScreen(navigator: DestinationsNavigator) {
val scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val loadingDialog = rememberLoadingDialog()
val context = LocalContext.current
var installMethod by remember {
mutableStateOf<InstallMethod?>(null)
}
LaunchedEffect(Unit) {
if (text.isNotEmpty()) {
return@LaunchedEffect
}
withContext(Dispatchers.IO) {
installModule(uri, onFinish = { success ->
if (success) {
showFloatAction = true
val onFileDownloaded = { uri: Uri ->
installMethod?.let {
scope.launch(Dispatchers.Main) {
when (it) {
InstallMethod.DirectInstall -> {
navigator.navigate(
FlashScreenDestination(
FlashIt.FlashBoot(
null,
uri,
false
)
)
)
}
InstallMethod.DirectInstallToInactiveSlot -> {
navigator.navigate(
FlashScreenDestination(
FlashIt.FlashBoot(
null,
uri,
true
)
)
)
}
is InstallMethod.SelectFile -> {
navigator.navigate(
FlashScreenDestination(
FlashIt.FlashBoot(
it.uri,
uri,
false
)
)
)
}
}
}, onStdout = {
text += "$it\n"
scope.launch {
scrollState.animateScrollTo(scrollState.maxValue)
}
logContent.append(it).append("\n")
}, onStderr = {
logContent.append(it).append("\n")
});
}
}
}
Scaffold(
topBar = {
TopBar(
onBack = {
navigator.popBackStack()
},
onSave = {
scope.launch {
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
val date = format.format(Date())
val file = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"KernelSU_install_log_${date}.log"
)
file.writeText(logContent.toString())
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
}
Scaffold(topBar = {
TopBar {
navigator.popBackStack()
}
}) {
Column(modifier = Modifier.padding(it)) {
SelectInstallMethod { method ->
installMethod = method
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
DownloadListener(context = context) { uri ->
onFileDownloaded(uri)
loadingDialog.hide()
}
)
},
floatingActionButton = {
if (showFloatAction) {
val reboot = stringResource(id = R.string.reboot)
ExtendedFloatingActionButton(
val failedMessage = stringResource(id = R.string.failed_to_fetch_lkm_url)
val downloadingMessage = stringResource(id = R.string.downloading)
Button(
modifier = Modifier.fillMaxWidth(),
enabled = installMethod != null,
onClick = {
scope.launch {
withContext(Dispatchers.IO) {
reboot()
loadingDialog.showLoading()
scope.launch(Dispatchers.IO) {
getLKMUrl().onFailure { throwable ->
loadingDialog.hide()
scope.launch(Dispatchers.Main) {
Toast.makeText(
context,
failedMessage.format(throwable.message),
Toast.LENGTH_SHORT
).show()
}
}.onSuccess { result ->
download(
context = context,
url = result.second,
fileName = result.first,
description = downloadingMessage.format(
result.first
),
onDownloaded = { uri ->
onFileDownloaded(uri)
loadingDialog.hide()
},
onDownloading = {}
)
}
}
},
icon = { Icon(Icons.Filled.Refresh, reboot) },
text = { Text(text = reboot) },
}) {
Text(
stringResource(id = R.string.install_next),
fontSize = MaterialTheme.typography.bodyMedium.fontSize
)
}
}
}
}
}
sealed class InstallMethod {
data class SelectFile(val uri: Uri? = null, override val label: Int = R.string.select_file) :
InstallMethod()
object DirectInstall : InstallMethod() {
override val label: Int
get() = R.string.direct_install
}
object DirectInstallToInactiveSlot : InstallMethod() {
override val label: Int
get() = R.string.install_inactive_slot
}
abstract val label: Int
}
@Composable
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice()
val radioOptions = mutableListOf<InstallMethod>(InstallMethod.SelectFile())
if (rootAvailable) {
radioOptions.add(InstallMethod.DirectInstall)
if (isAbDevice) {
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
}
}
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
val selectImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
val option = InstallMethod.SelectFile(uri)
selectedOption = option
onSelected(option)
}
}
}
val confirmDialog = rememberConfirmDialog(onConfirm = {
selectedOption = InstallMethod.DirectInstallToInactiveSlot
onSelected(InstallMethod.DirectInstallToInactiveSlot)
}, onDismiss = null)
val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)
val dialogContent = stringResource(id = R.string.install_inactive_slot_warning)
val onClick = { option: InstallMethod ->
when (option) {
is InstallMethod.SelectFile -> {
selectImageLauncher.launch(
Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream"
}
)
}
is InstallMethod.DirectInstall -> {
selectedOption = option
onSelected(option)
}
is InstallMethod.DirectInstallToInactiveSlot -> {
confirmDialog.showConfirm(dialogTitle, dialogContent)
}
}
) { innerPadding ->
KeyEventBlocker {
it.key == Key.VolumeDown || it.key == Key.VolumeUp
}
Column(
modifier = Modifier
.fillMaxSize(1f)
.padding(innerPadding)
.verticalScroll(scrollState),
) {
Text(
modifier = Modifier.padding(8.dp),
text = text,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = FontFamily.Monospace,
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
)
}
Column {
radioOptions.forEach { option ->
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable {
onClick(option)
}) {
RadioButton(selected = option.javaClass == selectedOption?.javaClass, onClick = {
onClick(option)
})
Text(text = stringResource(id = option.label))
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
private fun TopBar(onBack: () -> Unit = {}) {
TopAppBar(
title = { Text(stringResource(R.string.install)) },
navigationIcon = {
@@ -142,19 +271,11 @@ private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
onClick = onBack
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
},
actions = {
IconButton(onClick = onSave) {
Icon(
imageVector = Icons.Filled.Save,
contentDescription = "Localized description"
)
}
}
)
}
@Preview
@Composable
fun InstallPreview() {
// InstallScreen(DestinationsNavigator(), uri = Uri.EMPTY)
@Preview
fun SelectInstall_Preview() {
// InstallScreen(DestinationsNavigator())
}

View File

@@ -40,10 +40,10 @@ 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.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination
import me.weishu.kernelsu.ui.screen.destinations.WebScreenDestination
import me.weishu.kernelsu.ui.util.*
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
@@ -81,7 +81,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val data = it.data ?: return@rememberLauncherForActivityResult
val uri = data.data ?: return@rememberLauncherForActivityResult
navigator.navigate(InstallScreenDestination(uri))
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
viewModel.markNeedRefresh()
@@ -101,10 +101,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
}
}) { innerPadding ->
ConfirmDialog()
LoadingDialog()
when {
hasMagisk -> {
Box(
@@ -127,7 +123,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
.fillMaxSize(),
onInstallModule =
{
navigator.navigate(InstallScreenDestination(it))
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it)))
}, onClickModule = { id, name, hasWebUi ->
if (hasWebUi) {
navigator.navigate(WebScreenDestination(id, name))
@@ -162,17 +158,19 @@ private fun ModuleList(
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
val loadingDialog = rememberLoadingDialog()
val confirmDialog = rememberConfirmDialog()
suspend fun onModuleUpdate(
module: ModuleViewModel.ModuleInfo,
changelogUrl: String,
downloadUrl: String,
fileName: String
) {
val changelogResult = dialogHost.withLoading {
val changelogResult = loadingDialog.withLoading {
withContext(Dispatchers.IO) {
runCatching {
OkHttpClient().newCall(
@@ -201,7 +199,7 @@ private fun ModuleList(
}
// changelog is not empty, show it and wait for confirm
val confirmResult = dialogHost.showConfirm(
val confirmResult = confirmDialog.awaitConfirm(
changelogText,
content = changelog,
markdown = true,
@@ -232,7 +230,7 @@ private fun ModuleList(
}
suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) {
val confirmResult = dialogHost.showConfirm(
val confirmResult = confirmDialog.awaitConfirm(
moduleStr,
content = moduleUninstallConfirm.format(module.name),
confirm = uninstall,
@@ -242,7 +240,7 @@ private fun ModuleList(
return
}
val success = dialogHost.withLoading {
val success = loadingDialog.withLoading {
withContext(Dispatchers.IO) {
uninstallModule(module.id)
}
@@ -327,7 +325,7 @@ private fun ModuleList(
scope.launch { onModuleUninstall(module) }
}, onCheckChanged = {
scope.launch {
val success = dialogHost.withLoading {
val success = loadingDialog.withLoading {
withContext(Dispatchers.IO) {
toggleModule(module.id, !isChecked)
}

View File

@@ -4,15 +4,10 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
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.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
@@ -29,10 +24,10 @@ import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.Natives
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.component.rememberCustomDialog
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination
import me.weishu.kernelsu.ui.util.LocalDialogHost
import me.weishu.kernelsu.ui.util.getBugreportFile
/**
@@ -42,7 +37,6 @@ import me.weishu.kernelsu.ui.util.getBugreportFile
@Destination
@Composable
fun SettingScreen(navigator: DestinationsNavigator) {
Scaffold(
topBar = {
TopBar(onBack = {
@@ -50,16 +44,15 @@ fun SettingScreen(navigator: DestinationsNavigator) {
})
}
) { paddingValues ->
LoadingDialog()
val showAboutDialog = remember { mutableStateOf(false) }
AboutDialog(showAboutDialog)
val aboutDialog = rememberCustomDialog {
AboutDialog(it)
}
val loadingDialog = rememberLoadingDialog()
Column(modifier = Modifier.padding(paddingValues)) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val dialogHost = LocalDialogHost.current
val profileTemplate = stringResource(id = R.string.settings_profile_template)
ListItem(
@@ -101,6 +94,21 @@ fun SettingScreen(navigator: DestinationsNavigator) {
checkUpdate = it
}
var enableWebDebugging by rememberSaveable {
mutableStateOf(
prefs.getBoolean("enable_web_debugging", false)
)
}
SwitchItem(
icon = Icons.Filled.DeveloperMode,
title = stringResource(id = R.string.enable_web_debugging),
summary = stringResource(id = R.string.enable_web_debugging_summary),
checked = enableWebDebugging
) {
prefs.edit().putBoolean("enable_web_debugging", it).apply()
enableWebDebugging = it
}
ListItem(
leadingContent = {
@@ -112,7 +120,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
headlineContent = { Text(stringResource(id = R.string.send_log)) },
modifier = Modifier.clickable {
scope.launch {
val bugreport = dialogHost.withLoading {
val bugreport = loadingDialog.withLoading {
withContext(Dispatchers.IO) {
getBugreportFile(context)
}
@@ -150,7 +158,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
},
headlineContent = { Text(about) },
modifier = Modifier.clickable {
showAboutDialog.value = true
aboutDialog.show()
}
)
}

View File

@@ -30,7 +30,6 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.ConfirmDialog
import me.weishu.kernelsu.ui.component.SearchAppBar
import me.weishu.kernelsu.ui.screen.destinations.AppProfileScreenDestination
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
@@ -95,9 +94,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
)
}
) { innerPadding ->
ConfirmDialog()
val refreshState = rememberPullRefreshState(
refreshing = viewModel.isRefreshing,
onRefresh = { scope.launch { viewModel.fetchAppList() } },

View File

@@ -2,6 +2,8 @@ package me.weishu.kernelsu.ui.screen
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.util.Log
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
@@ -31,6 +33,8 @@ fun WebScreen(navigator: DestinationsNavigator, moduleId: String, moduleName: St
val context = LocalContext.current
DisposableEffect(Unit) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
WebView.setWebContentsDebuggingEnabled(prefs.getBoolean("enable_web_debugging", false))
onDispose {
if (WebViewInterface.isHideSystemUI && context is Activity) {
showSystemUI(context.window)

View File

@@ -2,12 +2,7 @@ package me.weishu.kernelsu.ui.util
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.compositionLocalOf
import me.weishu.kernelsu.ui.component.DialogHostState
val LocalSnackbarHost = compositionLocalOf<SnackbarHostState> {
error("CompositionLocal LocalSnackbarController not present")
}
val LocalDialogHost = compositionLocalOf<DialogHostState> {
error("CompositionLocal LocalDialogController not present")
}

View File

@@ -10,6 +10,7 @@ import android.net.Uri
import android.os.Environment
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import me.weishu.kernelsu.getKMI
/**
* @author weishu
@@ -94,6 +95,38 @@ fun checkNewVersion(): Triple<Int, String, String> {
}
return defaultValue
}
fun getLKMUrl(): Result<Pair<String, String>> {
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest"
val kmi = getKMI() ?: return Result.failure(RuntimeException("Get KMI failed"))
runCatching {
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
.use { response ->
val body = response.body?.string() ?: return Result.failure(RuntimeException("request body failed"))
if (!response.isSuccessful) {
return Result.failure(RuntimeException("Request failed, code: ${response.code}, message: $body"))
}
val json = org.json.JSONObject(body)
val assets = json.getJSONArray("assets")
for (i in 0 until assets.length()) {
val asset = assets.getJSONObject(i)
val name = asset.getString("name")
if (!name.endsWith(".ko")) {
continue
}
if (name.contains(kmi)) {
return Result.success(Pair(name, asset.getString("browser_download_url")))
}
}
}
}.onFailure {
return Result.failure(it)
}
return Result.failure(RuntimeException("Cannot find LKM for $kmi"))
}
@Composable
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {

View File

@@ -1,6 +1,7 @@
package me.weishu.kernelsu.ui.util
import android.net.Uri
import android.os.Environment
import android.os.SystemClock
import android.util.Log
import com.topjohnwu.superuser.CallbackList
@@ -138,6 +139,85 @@ fun installModule(
}
}
fun installBoot(
bootUri: Uri?,
lkmUri: Uri,
ota: Boolean,
onFinish: (Boolean) -> Unit,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit
): Boolean {
val resolver = ksuApp.contentResolver
with(resolver.openInputStream(lkmUri)) {
val lkmFile = File(ksuApp.cacheDir, "kernelsu.ko")
lkmFile.outputStream().use { output ->
this?.copyTo(output)
}
if (!lkmFile.exists()) {
onStdout("- kernelsu.ko not found")
onFinish(false)
return false
}
val bootFile = bootUri?.let { uri ->
with(resolver.openInputStream(uri)) {
val bootFile = File(ksuApp.cacheDir, "boot.img")
bootFile.outputStream().use { output ->
this?.copyTo(output)
}
bootFile
}
}
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
var cmd = "boot-patch -m ${lkmFile.absolutePath} --magiskboot ${magiskboot.absolutePath}"
cmd += if (bootFile == null) {
// no boot.img, use -f to force install
" -f"
} else {
" -b ${bootFile.absolutePath}"
}
if (ota) {
cmd += " -u"
}
// output dir
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
cmd += " -o $downloadsDir"
val shell = createRootShell()
val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {
override fun onAddElement(s: String?) {
onStdout(s ?: "")
}
}
val stderrCallback: CallbackList<String?> = object : CallbackList<String?>() {
override fun onAddElement(s: String?) {
onStderr(s ?: "")
}
}
val result =
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(stdoutCallback, stderrCallback)
.exec()
Log.i("KernelSU", "install boot $lkmUri result: ${result.isSuccess}")
lkmFile.delete()
bootFile?.delete()
// if boot uri is empty, it is direct install, when success, we should show reboot button
onFinish(bootUri == null && result.isSuccess)
return result.isSuccess
}
}
fun reboot(reason: String = "") {
val shell = getRootShell()
if (reason == "recovery") {
@@ -152,6 +232,11 @@ fun rootAvailable(): Boolean {
return shell.isRoot
}
fun isAbDevice(): Boolean {
val shell = getRootShell()
return ShellUtils.fastCmd(shell, "getprop ro.build.ab_update").trim().toBoolean()
}
fun overlayFsAvailable(): Boolean {
val shell = getRootShell()
// check /proc/filesystems

View File

@@ -27,7 +27,7 @@ class WebViewInterface(val context: Context, private val webView: WebView) {
@JavascriptInterface
fun exec(cmd: String): String {
val shell = createRootShell()
val shell = createRootShell(true)
return ShellUtils.fastCmd(shell, cmd)
}
@@ -63,7 +63,7 @@ class WebViewInterface(val context: Context, private val webView: WebView) {
processOptions(finalCommand, options)
finalCommand.append(cmd)
val shell = createRootShell()
val shell = createRootShell(true)
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")
@@ -97,7 +97,7 @@ class WebViewInterface(val context: Context, private val webView: WebView) {
finalCommand.append(command)
}
val shell = createRootShell()
val shell = createRootShell(true)
val emitData = fun(name: String, data: String) {
val jsCode =

View File

@@ -106,4 +106,15 @@
<string name="open">فتح</string>
<string name="settings_check_update_summary">التحقق تلقائيًا من وجود تحديثات عند فتح التطبيق</string>
<string name="settings_check_update">التحقق من التحديث</string>
<string name="enable_web_debugging">تمكين تصحيح أخطاء WebView</string>
<string name="enable_web_debugging_summary">يمكن استخدامه لتصحيح أخطاء WebUI، يرجى تمكينه فقط عند الحاجة.</string>
<string name="install_next">التالي</string>
<string name="failed_to_fetch_lkm_url">فشل جلب رابط LKM: %1$s</string>
<string name="downloading">تحميل %1$s</string>
<string name="select_file">اختيار ملف</string>
<string name="direct_install">تثبيت مباشر (موصى به)</string>
<string name="install_inactive_slot">التثبيت على فتحة غير نشطة (بعد OTA)</string>
<string name="install_inactive_slot_warning">سيتم **إجبار** جهازك على التمهيد إلى الفتحة غير النشطة الحالية بعد إعادة التشغيل!
\nاستخدم هذا الخيار فقط بعد انتهاء التحديث.
\nأستمرار؟</string>
</resources>

View File

@@ -102,4 +102,10 @@
<string name="profile_umount_modules_summary">Selle valiku lubamine lubab KernelSU-l taastada selle rakenduse moodulite poolt mistahes muudetud faile.</string>
<string name="app_profile_template_export_empty">Eksportimiseks kohalikku malli ei leitud!</string>
<string name="settings_umount_modules_default_summary">Globaalne vaikeväärtus \"Lahtihaagitud moodulitele\" rakenduseprofiilides. Lubamisel eemaldab see kõik moodulite süsteemimuudatused rakendustele, millel ei ole profiili määratud.</string>
<string name="enable_web_debugging_summary">Saab kasutada WebUI silumiseks, palun luba ainult vajadusel.</string>
<string name="grant_root_failed">Juurkasutaja andmine ebaõnnestus!</string>
<string name="settings_check_update">Kontrolli uuendusi</string>
<string name="settings_check_update_summary">Rakenduse avamisel kontrolli automaatselt uuendusi</string>
<string name="open">Ava</string>
<string name="enable_web_debugging">Luba WebView silumine</string>
</resources>

View File

@@ -51,7 +51,8 @@
<string name="home_support_title">Soutenez-nous</string>
<string name="home_click_to_learn_kernelsu">Découvrez comment installer KernelSU et utiliser les modules</string>
<string name="home_support_content">KernelSU est, et restera toujours, gratuit et open source. Vous pouvez cependant nous témoigner de votre soutien en nous faisant un don.</string>
<string name="about_source_code">Voir le code source sur %1$s<br/>Rejoindre notre canal %2$s</string>
<string name="about_source_code">Voir le code source sur %1$s<br/>
\nRejoindre notre canal %2$s</string>
<string name="profile_template">Modèle</string>
<string name="profile_default">Par défaut</string>
<string name="profile_custom">Personnalisé</string>
@@ -78,7 +79,7 @@
<string name="force_stop_app">Forcer l\'arrêt</string>
<string name="restart_app">Relancer l\'application</string>
<string name="failed_to_update_sepolicy">Échec de la mise à jour des règles SELinux pour: %s</string>
<string name="require_kernel_version">La version actuelle de KernelSU (%d) est trop ancienne pour que le gestionnaire fonctionne correctement. Veuillez passer à la version %d ou à une version supérieure!</string>
<string name="require_kernel_version">La version actuelle de KernelSU (%d) est trop ancienne pour que le gestionnaire fonctionne correctement. Veuillez passer à la version %d ou à une version supérieure!</string>
<string name="app_profile_template_import_success">Importation réussie</string>
<string name="app_profile_export_to_clipboard">Exporter vers le presse-papiers</string>
<string name="app_profile_template_export_empty">Impossible de trouver un modèle local à exporter !</string>
@@ -90,7 +91,7 @@
<string name="app_profile_template_id_invalid">id de modèle invalide</string>
<string name="app_profile_template_sync">Synchroniser les modèles en ligne</string>
<string name="app_profile_template_create">Créer un modèle</string>
<string name="app_profile_template_readonly">en lecture seule</string>
<string name="app_profile_template_readonly">lecture seule</string>
<string name="app_profile_import_export">Importer/exporter</string>
<string name="app_profile_template_save_failed">Échec de l\'enregistrement du modèle</string>
<string name="app_profile_template_edit">Modifier le modèle</string>
@@ -104,4 +105,17 @@
<string name="app_profile_template_view">Voir le modèle</string>
<string name="settings_check_update_summary">Vérifier automatiquement les mises à jour à l\'ouverture de l\'application</string>
<string name="settings_check_update">Vérifier les mises à jour</string>
<string name="enable_web_debugging">Activer le débogage de WebView</string>
<string name="enable_web_debugging_summary">Peut être utilisé pour déboguer WebUI, n\'activez cette option que si nécessaire.</string>
<string name="grant_root_failed">Échec de l\'octroi des privilèges root!</string>
<string name="open">Ouvert</string>
<string name="direct_install">Installation directe (recommandé)</string>
<string name="select_file">Sélectionner un fichier</string>
<string name="install_inactive_slot">Installer dans l\'emplacement inactif (après OTA)</string>
<string name="install_inactive_slot_warning">Votre appareil sera **FORCÉ** à démarrer sur l\'emplacement inactif actuel après un redémarrage!
\nN\'utilisez cette option qu\'une fois la mise à jour OTA terminée.
\nContinuer?</string>
<string name="install_next">Suivant</string>
<string name="downloading">Téléchargement de %1$s</string>
<string name="failed_to_fetch_lkm_url">Échec de la récupération de l\'URL du LKM: %1$s</string>
</resources>

View File

@@ -105,6 +105,8 @@
<string name="app_profile_template_view">テンプレートを表示</string>
<string name="settings_check_update">アップデートを確認</string>
<string name="settings_check_update_summary">アプリを開いたときにアップデートを自動的に確認する</string>
<string name="grant_root_failed">root の付与に失敗しました!</string>
<string name="open">ける</string>
<string name="grant_root_failed">root の付与に失敗しました</string>
<string name="open"></string>
<string name="enable_web_debugging">WebView デバッグを有効にする</string>
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合にのみ有効にしてください。</string>
</resources>

View File

@@ -102,4 +102,10 @@
<string name="module_changelog_failed">Ophalen van wijzigingslogboek mislukt: %s</string>
<string name="app_profile_export_to_clipboard">Exporteren naar klembord</string>
<string name="settings_check_update">Controleer update</string>
<string name="enable_web_debugging">Schakel WebView-foutopsporing</string>
<string name="enable_web_debugging_summary">Kan worden gebruikt om WebUI te debuggen. Schakel dit alleen in als dat nodig is.</string>
<string name="app_profile_template_export_empty">Kan geen lokale sjabloon vinden om te exporteren!</string>
<string name="app_profile_template_import_success">Succesvol geïmporteerd</string>
<string name="open">Open</string>
<string name="settings_check_update_summary">Controleer automatisch op updates bij het openen van de app</string>
</resources>

View File

@@ -18,8 +18,8 @@
<string name="selinux_status_permissive">Permissivo</string>
<string name="selinux_status_unknown">Desconhecido</string>
<string name="superuser">SuperUsuário</string>
<string name="module_failed_to_enable">Falha ao ativar o módulo: %s</string>
<string name="module_failed_to_disable">Falha ao desativar o módulo: %s</string>
<string name="module_failed_to_enable">Falha ao ativar o módulo %s</string>
<string name="module_failed_to_disable">Falha ao desativar o módulo %s</string>
<string name="module_empty">Nenhum módulo instalado</string>
<string name="module">Módulo</string>
<string name="uninstall">Desinstalar</string>
@@ -33,24 +33,24 @@
<string name="reboot_download">Reiniciar em modo Download</string>
<string name="reboot_edl">Reiniciar em modo EDL</string>
<string name="about">Sobre</string>
<string name="module_uninstall_confirm">Tem certeza de que deseja desinstalar o módulo %s?</string>
<string name="module_uninstall_success">%s foi desinstalado</string>
<string name="module_uninstall_failed">Falha ao desinstalar: %s</string>
<string name="module_uninstall_confirm">Tem certeza que deseja desinstalar o módulo %s?</string>
<string name="module_uninstall_success">%s desinstalado</string>
<string name="module_uninstall_failed">Falha ao desinstalar %s</string>
<string name="module_version">Versão</string>
<string name="module_author">Autor</string>
<string name="module_overlay_fs_not_available">OverlayFS não está disponível. O módulo não pode funcionar!</string>
<string name="module_overlay_fs_not_available">Os módulos estão indisponíveis porque OverlayFS está desabilitado pelo kernel!</string>
<string name="refresh">Atualizar</string>
<string name="show_system_apps">Mostrar apps do sistema</string>
<string name="hide_system_apps">Ocultar apps do sistema</string>
<string name="send_log">Reportar registro</string>
<string name="safe_mode">Modo de segurança</string>
<string name="reboot_to_apply">Reinicie para entrar em vigor</string>
<string name="module_magisk_conflict">Os módulos estão desativados porque estão em conflito com os do Magisk!</string>
<string name="home_learn_kernelsu">Leia mais sobre o KernelSU</string>
<string name="module_magisk_conflict">Os módulos estão indisponíveis devido a um conflito com Magisk!</string>
<string name="home_learn_kernelsu">Saiba mais sobre o KernelSU</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/pt_BR/guide/what-is-kernelsu.html</string>
<string name="home_click_to_learn_kernelsu">Aprenda como instalar o KernelSU e usar módulos</string>
<string name="home_click_to_learn_kernelsu">Saiba como instalar o KernelSU e usar os módulos</string>
<string name="home_support_title">Apoie-nos</string>
<string name="home_support_content">KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode agradecer enviando uma pequena doação.</string>
<string name="home_support_content">KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação.</string>
<string name="about_source_code"><![CDATA[Veja o código-fonte no %1$s<br/>Junte-se ao nosso canal do %2$s]]></string>
<string name="profile" translatable="false">Perfil do Aplicativo</string>
<string name="profile_default">Padrão</string>
@@ -72,9 +72,9 @@
<string name="profile_selinux_domain">Domínio</string>
<string name="profile_selinux_rules">Regras</string>
<string name="module_update">Atualizar</string>
<string name="module_downloading">Baixando módulo: %s</string>
<string name="module_start_downloading">Iniciar download: %s</string>
<string name="new_version_available">Nova versão: %s está disponível, clique para atualizar.</string>
<string name="module_downloading">Baixando módulo %s</string>
<string name="module_start_downloading">Iniciando download: %s</string>
<string name="new_version_available">Nova versão %s está disponível, clique para atualizar.</string>
<string name="launch_app">Iniciar</string>
<string name="force_stop_app">Forçar parada</string>
<string name="restart_app">Reiniciar</string>
@@ -103,8 +103,19 @@
<string name="app_profile_template_delete">Excluir</string>
<string name="app_profile_template_import_empty">A área de transferência está vazia!</string>
<string name="app_profile_template_view">Ver modelo</string>
<string name="settings_check_update">Verificar atualização</string>
<string name="settings_check_update">Verificar por atualização</string>
<string name="settings_check_update_summary">Verifique automaticamente se há atualizações ao abrir o app</string>
<string name="grant_root_failed">Falha ao conceder acesso root!</string>
<string name="open">Abrir</string>
<string name="enable_web_debugging">Ativar depuração do WebView</string>
<string name="enable_web_debugging_summary">Pode ser usado para depurar o WebUI. Por favor, ative somente quando necessário.</string>
<string name="select_file">Selecione um arquivo</string>
<string name="direct_install">Instalação direta (recomendada)</string>
<string name="install_inactive_slot">Instalar no slot inativo (após o OTA)</string>
<string name="install_inactive_slot_warning">Seu dispositivo será FORÇADO a inicializar no slot inativo atual após uma reinicialização!
\nSó use esta opção após a conclusão do OTA.
\nDeseja continuar?</string>
<string name="downloading">Baixando %1$s</string>
<string name="install_next">Próximo</string>
<string name="failed_to_fetch_lkm_url">Falha ao buscar a URL do LKM: %1$s</string>
</resources>

View File

@@ -104,4 +104,8 @@
<string name="app_profile_template_view">Vizualizare șablon</string>
<string name="settings_check_update">Verifică actualizarea</string>
<string name="settings_check_update_summary">Se verifică automat actualizările când deschizi aplicația</string>
<string name="enable_web_debugging">Activează depanarea WebView</string>
<string name="enable_web_debugging_summary">Poate fi folosit pentru a depana WebUI, activează numai când este necesar.</string>
<string name="grant_root_failed">Nu s-a acordat acces root!</string>
<string name="open">Deschide</string>
</resources>

View File

@@ -18,7 +18,7 @@
<string name="selinux_status_enforcing">Принудительный</string>
<string name="selinux_status_permissive">Разрешающий</string>
<string name="selinux_status_unknown">Неизвестно</string>
<string name="superuser">Superuser</string>
<string name="superuser">SU пользователь</string>
<!--Don't translate this string!-->
<string name="module_failed_to_enable">Не удалось включить модуль: %s</string>
<string name="module_failed_to_disable">Не удалось отключить модуль: %s</string>
@@ -106,8 +106,19 @@
<string name="app_profile_template_delete">Удалить</string>
<string name="app_profile_template_import_empty">Буфер обмена пуст!</string>
<string name="app_profile_template_view">Просмотр шаблона</string>
<string name="settings_check_update">Проверка обновления</string>
<string name="settings_check_update">Проверка обновлений</string>
<string name="settings_check_update_summary">Автоматическая проверка обновлений при открытии приложения</string>
<string name="grant_root_failed">Не удалось выдать root!</string>
<string name="open">Открыть</string>
<string name="enable_web_debugging">Включить отладку WebView</string>
<string name="enable_web_debugging_summary">Используется для отладки веб-интерфейса. Пожалуйста, включайте только при необходимости.</string>
<string name="downloading">Загрузка %1$s</string>
<string name="failed_to_fetch_lkm_url">Не удалось получить LKM по адресу: %1$s</string>
<string name="direct_install">Прямая установка (Рекомендуется)</string>
<string name="install_inactive_slot">Установка в неактивный слот (После OTA)</string>
<string name="install_next">Далее</string>
<string name="select_file">Выбрать файл</string>
<string name="install_inactive_slot_warning">Ваше устройство будет **ПРИНУДИТЕЛЬНО** загружено в текущий неактивный слот после перезагрузки!
\n Используйте эту опцию только после завершения OTA.
\n Продолжать?</string>
</resources>

View File

@@ -2,8 +2,8 @@
<resources>
<string name="app_name" translatable="false">KernelSU</string>
<string name="home">Ana Sayfa</string>
<string name="home_not_installed">Yüklenmedi</string>
<string name="home_click_to_install">Yüklemek için tıklayın</string>
<string name="home_not_installed">Kurulmadı</string>
<string name="home_click_to_install">Kurmak için tıklayın</string>
<string name="home_working">Çalışıyor</string>
<string name="home_working_version">Sürüm: %d</string>
<string name="home_superuser_count">Süper kullanıcılar: %d</string>
@@ -21,11 +21,11 @@
<string name="superuser">Süper kullanıcı</string>
<string name="module_failed_to_enable">Modül etkinleştirilemedi: %s</string>
<string name="module_failed_to_disable">Modül devre dışı bırakılamadı: %s</string>
<string name="module_empty">Yüklü modül yok</string>
<string name="module_empty">Kurulu modül yok</string>
<string name="module">Modül</string>
<string name="uninstall">Kaldır</string>
<string name="module_install">Yükle</string>
<string name="install">Yükle</string>
<string name="module_install">Kur</string>
<string name="install">Kur</string>
<string name="reboot">Cihazı yeniden başlat</string>
<string name="settings">Ayarlar</string>
<string name="reboot_userspace">Hızlı yeniden başlat</string>
@@ -52,7 +52,7 @@
<string name="home_click_to_learn_kernelsu">KernelSU\'nun nasıl kurulacağını ve modüllerin nasıl kullanılacağını öğrenin</string>
<string name="home_support_title">Bizi destekleyin</string>
<string name="home_support_content">KernelSU ücretsiz ve açık kaynaklı bir yazılımdır ve her zaman öyle kalacaktır. Ancak bağış yaparak bize destek olduğunuzu gösterebilirsiniz.</string>
<string name="about_source_code">Kaynak kodunu %1$s\'tan görüntüleyin<br/>%2$s kanalımıza katılın</string>
<string name="about_source_code">%1$s adresinde kaynak kodunu görüntüleyin<br/>%2$s kanalımıza katılın</string>
<string name="profile" translatable="false">Uygulama profili</string>
<string name="profile_default">Varsayılan</string>
<string name="profile_template">Şablon</string>
@@ -108,4 +108,15 @@
<string name="settings_check_update_summary">Uygulamayı açarken güncellemeleri otomatik denetle</string>
<string name="grant_root_failed">Root izni verilemedi!</string>
<string name="open"></string>
<string name="enable_web_debugging">Web Görünümü Hata Ayıklamasını Etkinleştir</string>
<string name="enable_web_debugging_summary">Web kullanıcı arayüzünde hata ayıklamak için kullanılabilir, lütfen yalnızca gerektiğinde etkinleştirin.</string>
<string name="direct_install">Doğrudan Kur (Tavsiye Edilen)</string>
<string name="select_file">Bir Dosya Seçin</string>
<string name="install_inactive_slot">Etkin Olmayan Yuvaya Kur (OTA\'dan Sonra)</string>
<string name="install_inactive_slot_warning">Aygıtınız yeniden başlatıldıktan sonra geçerli etkin olmayan yuvaya **ZORLA** önyükleme yapılacaktır!
\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın.
\nDevam edilsin mi?</string>
<string name="install_next">Sonraki</string>
<string name="failed_to_fetch_lkm_url">LKM URL\'si getirilemedi: %1$s</string>
<string name="downloading">%1$s indiriliyor</string>
</resources>

View File

@@ -21,10 +21,10 @@
<string name="about_source_code"><![CDATA[Xem mã nguồn tại %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
<string name="module_magisk_conflict">Các mô-đun bị vô hiệu hóa vì chúng xung đột với Magisk!</string>
<string name="module_uninstall_confirm">Bạn có muốn gỡ cài đặt mô-đun %s không\?</string>
<string name="send_log">báo cáo nhật ký</string>
<string name="send_log">Nhật ký báo cáo</string>
<string name="home">Trang chủ</string>
<string name="home_not_installed">Chưa cài đặt</string>
<string name="home_click_to_install">Nhấn để cài dặt</string>
<string name="home_click_to_install">Nhấn để cài đặt</string>
<string name="home_working">Đang hoạt động</string>
<string name="home_working_version">Phiên bản: %d</string>
<string name="home_unsupported">Không được hỗ trợ</string>
@@ -40,19 +40,19 @@
<string name="superuser">SuperUser</string>
<string name="module_failed_to_enable">Không thể kích hoạt mô-đun: %s</string>
<string name="module_failed_to_disable">Không thể vô hiệu hóa mô-đun: %s</string>
<string name="module_empty">Chưa có mô-đun nào được cài đặt</string>
<string name="module_empty">Chưa cài đặt mô-đun nào</string>
<string name="module">Mô-đun</string>
<string name="uninstall">Gỡ cài đặt</string>
<string name="module_install">Cài đặt</string>
<string name="install">Cài đặt</string>
<string name="reboot">Khởi động lại</string>
<string name="settings">Cài đặt</string>
<string name="settings">Thiết đặt</string>
<string name="reboot_userspace">Khởi động mềm</string>
<string name="reboot_recovery">Khởi động lại vào Recovery</string>
<string name="reboot_bootloader">Khởi động lại vào Bootloader</string>
<string name="reboot_download">Khởi động lại vào Download Mode</string>
<string name="reboot_edl">Khởi động lại vào EDL</string>
<string name="about">Về ứng dụng</string>
<string name="about">Giới thiệu</string>
<string name="module_uninstall_success">%s được gỡ cài đặt</string>
<string name="module_uninstall_failed">Lỗi khi gỡ cài đặt: %s</string>
<string name="module_version">Phiên bản</string>
@@ -68,16 +68,16 @@
<string name="home_module_count">Số mô-đun: %d</string>
<string name="profile_selinux_domain">Phạm vi</string>
<string name="profile_selinux_rules">Quy định</string>
<string name="launch_app">Mở</string>
<string name="launch_app">Khởi chạy</string>
<string name="restart_app">Khởi động lại</string>
<string name="profile_namespace">Danh sách mount</string>
<string name="profile_namespace">Gắn namespace</string>
<string name="profile_capabilities">Quyền</string>
<string name="failed_to_update_sepolicy">Không thể cập nhật quy định SELinux cho: %s</string>
<string name="force_stop_app">Buộc dừng</string>
<string name="profile_namespace_inherited">Thừa hưởng</string>
<string name="profile_namespace_global">Chung</string>
<string name="profile_namespace_individual">Riêng</string>
<string name="profile_selinux_context">SELinux context</string>
<string name="profile_selinux_context">Bối cảnh SELinux</string>
<string name="profile_umount_modules">Ngắt mô-đun</string>
<string name="require_kernel_version">KernelSU phiên bản %d quá thấp để trình quản lý hoạt động, hãy cập nhật lên %d hoặc mới hơn!</string>
<string name="app_profile_template_import_success">Đã nhập thành công</string>
@@ -86,7 +86,7 @@
<string name="app_profile_template_id_exist">id bản mẫu đã tồn tại!</string>
<string name="module_changelog">Nhật ký thay đổi</string>
<string name="app_profile_import_from_clipboard">Nhập từ khay nhớ tạm</string>
<string name="module_changelog_failed">Tìm nạp nhật ký thay đổi không thành công: %s</string>
<string name="module_changelog_failed">Không nạp được nhật ký thay đổi: %s</string>
<string name="app_profile_template_name">Tên</string>
<string name="app_profile_template_id_invalid">Id mẫu không hợp lệ</string>
<string name="app_profile_template_sync">Đồng bộ hóa các mẫu trực tuyến</string>
@@ -99,8 +99,14 @@
<string name="app_profile_template_save">Lưu</string>
<string name="settings_profile_template_summary">Quản lý mẫu Hồ sơ Ứng dụng cục bộ và trực tuyến</string>
<string name="app_profile_template_delete">Xóa</string>
<string name="app_profile_template_import_empty">Bảng tạm trống!</string>
<string name="app_profile_template_import_empty">Clipboard trống!</string>
<string name="app_profile_template_view">Xem Bản Mẫu</string>
<string name="app_profile_template_readonly">chỉ đọc</string>
<string name="app_profile_template_id">id</string>
<string name="enable_web_debugging">Bật gỡ lỗi WebView</string>
<string name="enable_web_debugging_summary">Có thể được sử dụng để gỡ lỗi WebUI, vui lòng chỉ bật khi cần.</string>
<string name="grant_root_failed">Không cấp được quyền root!</string>
<string name="settings_check_update">Kiểm tra cập nhật</string>
<string name="settings_check_update_summary">Tự động kiểm tra cập nhật khi mở ứng dụng</string>
<string name="open">Mở</string>
</resources>

View File

@@ -106,4 +106,13 @@
<string name="settings_check_update_summary">在应用启动后自动检查是否有最新版</string>
<string name="grant_root_failed">获取 root 失败!</string>
<string name="open">打开</string>
<string name="enable_web_debugging">启用 WebView 调试</string>
<string name="enable_web_debugging_summary">可用于调试 WebUI ,请仅在需要时启用。</string>
<string name="direct_install">直接安装(推荐)</string>
<string name="select_file">选择一个文件</string>
<string name="install_inactive_slot">安装到未使用的槽位OTA 后)</string>
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认</string>
<string name="install_next">下一步</string>
<string name="failed_to_fetch_lkm_url">获取 LKM 链接失败:%1$s</string>
<string name="downloading">正在下载:%1$s</string>
</resources>

View File

@@ -52,27 +52,27 @@
<string name="home_support_title">支援開發</string>
<string name="home_support_content">KernelSU 將保持免費和開源,您可以考慮向開發人員贊助以表示支持。</string>
<string name="about_source_code"><![CDATA[在 %1$s 中檢視原始碼<br/>加入我們的 %2$s 頻道]]></string>
<string name="profile_umount_modules">解除安裝模組</string>
<string name="profile_umount_modules">卸載模組</string>
<string name="failed_to_update_app_profile">無法更新 %s 應用程式設定檔</string>
<string name="require_kernel_version">目前安裝的 KernelSU 版本 %d 過低,管理器無法正常工作,請升級核心 KernelSU 版本至 %d 或以上!</string>
<string name="settings_umount_modules_default">預設解除安裝模組</string>
<string name="settings_umount_modules_default_summary">應用程式設定檔中「解除安裝模組」的全域預設值,如果啟用,將會為沒有設定檔的應用程式移除所有模組針對系統的修改。</string>
<string name="settings_umount_modules_default">預設卸載模組</string>
<string name="settings_umount_modules_default_summary">應用程式設定檔中「卸載模組」的全域預設值,如果啟用,將會為沒有設定檔的應用程式移除所有模組針對系統的修改。</string>
<string name="profile_umount_modules_summary">啟用後將允許 KernelSU 為本應用程式還原被模組修改過的檔案。</string>
<string name="profile_default">預設</string>
<string name="profile_custom">自訂</string>
<string name="profile_capabilities">功能</string>
<string name="profile_capabilities">權限</string>
<string name="profile_selinux_rules">規則</string>
<string name="module_downloading">正在下載模組:%s</string>
<string name="restart_app">重新啟動</string>
<string name="profile_template">範本</string>
<string name="profile_template">模板</string>
<string name="profile_name">設定檔名稱</string>
<string name="profile_namespace">掛載命名空間</string>
<string name="profile_namespace_inherited">繼承</string>
<string name="profile_namespace_global">全域</string>
<string name="profile_namespace_individual"></string>
<string name="profile_namespace_individual"></string>
<string name="profile_groups">群組</string>
<string name="profile_selinux_context">SELinux 環境</string>
<string name="profile_selinux_domain"></string>
<string name="profile_selinux_context">SELinux context</string>
<string name="profile_selinux_domain"></string>
<string name="module_update">更新</string>
<string name="module_start_downloading">開始下載:%s</string>
<string name="new_version_available">發現新版本:%s 已可供使用,按一下即可升級</string>
@@ -86,15 +86,15 @@
<string name="app_profile_template_id">模板 ID</string>
<string name="settings_profile_template">App Profile 模板</string>
<string name="settings_profile_template_summary">管理本地和線上的 App Profile 模板</string>
<string name="app_profile_template_import_success">成功匯</string>
<string name="app_profile_template_import_success">成功匯</string>
<string name="app_profile_export_to_clipboard">匯出至剪貼簿</string>
<string name="app_profile_template_export_empty">本地沒有模板可匯出!</string>
<string name="app_profile_template_export_empty">沒有本地模板可匯出!</string>
<string name="app_profile_template_id_exist">模板 ID 已存在!</string>
<string name="app_profile_import_from_clipboard">從剪貼簿匯入</string>
<string name="module_changelog_failed">獲取更新日誌失敗:%s</string>
<string name="app_profile_template_name">名稱</string>
<string name="app_profile_template_sync">與線上規則同步</string>
<string name="app_profile_template_readonly"></string>
<string name="app_profile_template_readonly"></string>
<string name="app_profile_import_export">匯出 / 匯入</string>
<string name="app_profile_template_save_failed">模板儲存失敗</string>
<string name="app_profile_template_description">描述</string>
@@ -102,4 +102,19 @@
<string name="app_profile_template_delete">刪除</string>
<string name="app_profile_template_import_empty">剪貼簿沒有內容!</string>
<string name="app_profile_template_view">檢查模板</string>
<string name="enable_web_debugging_summary">可用於偵錯 WebUI請僅在需要時啟用。</string>
<string name="enable_web_debugging">啟用 WebView 偵錯</string>
<string name="grant_root_failed">取得root失敗</string>
<string name="open">開啟</string>
<string name="settings_check_update">檢查更新</string>
<string name="settings_check_update_summary">在打開App時自動檢查更新</string>
<string name="select_file">選擇一個檔案</string>
<string name="install_inactive_slot">安裝到非使用中的槽位(在 OTA 更新後)</string>
<string name="install_inactive_slot_warning">您的裝置將在下次重新啟動後強制切換到非使用中的槽位!
\n這個選項僅在 OTA 更新完畢後使用。
\n請問是否繼續</string>
<string name="direct_install">直接安裝(建議)</string>
<string name="install_next">下一步</string>
<string name="failed_to_fetch_lkm_url">取得 LKM 下載鏈接失敗:%1$s</string>
<string name="downloading">正在下載:%1$s</string>
</resources>

View File

@@ -108,4 +108,13 @@
<string name="settings_check_update_summary">Automatically check for updates when opening the app</string>
<string name="grant_root_failed">Failed to grant root!</string>
<string name="open">Open</string>
<string name="enable_web_debugging">Enable WebView Debugging</string>
<string name="enable_web_debugging_summary">Can be used to debug WebUI, please enable only when needed.</string>
<string name="direct_install">Direct Install (Recommended)</string>
<string name="select_file">Select a File</string>
<string name="install_inactive_slot">Install to Inactive Slot (After OTA)</string>
<string name="install_inactive_slot_warning">Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue?</string>
<string name="install_next">Next</string>
<string name="failed_to_fetch_lkm_url">Failed to fetch LKM url: %1$s</string>
<string name="downloading">Downloading %1$s</string>
</resources>

View File

@@ -1,5 +1,5 @@
[versions]
agp = "8.2.2"
agp = "8.3.0"
kotlin = "1.8.10"
ksp = "1.8.10-1.0.9"
compose-bom = "2023.06.01"

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

184
manager/gradlew.bat vendored
View File

@@ -1,92 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -537,12 +537,6 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@@ -806,7 +800,6 @@ dependencies = [
"encoding_rs",
"env_logger",
"extattr",
"fs_extra",
"getopts",
"hole-punch",
"humansize",

View File

@@ -37,7 +37,6 @@ sha256 = "1"
tempdir = "0.3"
chrono = "0.4"
hole-punch = { git = "https://github.com/tiann/hole-punch" }
fs_extra = "1.3"
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
rustix = { git = "https://github.com/Kernel-SU/rustix.git", branch = "main", features = ["all-apis"] }

Binary file not shown.

Binary file not shown.

BIN
userspace/ksud/bin/x86_64/ksuinit Executable file

Binary file not shown.

View File

@@ -1,29 +1,38 @@
use anyhow::Result;
use const_format::concatcp;
use rust_embed::RustEmbed;
use std::path::Path;
use crate::{defs::BINARY_DIR, utils};
pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop");
pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox");
pub const BOOTCTL_PATH: &str = concatcp!(BINARY_DIR, "bootctl");
#[cfg(target_arch = "aarch64")]
#[derive(RustEmbed)]
#[folder = "bin/aarch64"]
struct Asset;
#[cfg(target_arch = "x86_64")]
#[cfg(all(target_arch = "x86_64", target_os = "android"))]
#[derive(RustEmbed)]
#[folder = "bin/x86_64"]
struct Asset;
// IF NOT x86_64 ANDROID, ie. macos, linux, windows, always use aarch64
#[cfg(not(all(target_arch = "x86_64", target_os = "android")))]
#[derive(RustEmbed)]
#[folder = "bin/aarch64"]
struct Asset;
pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> {
for file in Asset::iter() {
utils::ensure_binary(
format!("{BINARY_DIR}{file}"),
&Asset::get(&file).unwrap().data,
ignore_if_exist,
)?
if file == "ksuinit" {
continue;
}
let asset = Asset::get(&file).ok_or(anyhow::anyhow!("asset not found: {}", file))?;
utils::ensure_binary(format!("{BINARY_DIR}{file}"), &asset.data, ignore_if_exist)?
}
Ok(())
}
pub fn copy_assets_to_file(name: &str, dst: impl AsRef<Path>) -> Result<()> {
let asset = Asset::get(name).ok_or(anyhow::anyhow!("asset not found: {}", name))?;
std::fs::write(dst, asset.data)?;
Ok(())
}

View File

@@ -1,27 +1,52 @@
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::ensure;
use anyhow::Context;
use anyhow::Result;
use is_executable::IsExecutable;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::Stdio;
use which::which;
use crate::utils;
use crate::{assets, utils};
#[cfg(unix)]
#[cfg(target_os = "android")]
fn ensure_gki_kernel() -> Result<()> {
let version =
procfs::sys::kernel::Version::current().with_context(|| "get kernel version failed")?;
let is_gki = version.major == 5 && version.minor >= 10 || version.major > 5;
let version = get_kernel_version()?;
let is_gki = version.0 == 5 && version.1 >= 10 || version.2 > 5;
ensure!(is_gki, "only support GKI kernel");
Ok(())
}
#[cfg(target_os = "android")]
pub fn get_kernel_version() -> Result<(i32, i32, i32)> {
use regex::Regex;
let uname = rustix::system::uname();
let version = uname.release().to_string_lossy();
let re = Regex::new(r"(\d+)\.(\d+)\.(\d+)")?;
if let Some(captures) = re.captures(&version) {
let major = captures
.get(1)
.and_then(|m| m.as_str().parse::<i32>().ok())
.ok_or_else(|| anyhow!("Major version parse error"))?;
let minor = captures
.get(2)
.and_then(|m| m.as_str().parse::<i32>().ok())
.ok_or_else(|| anyhow!("Minor version parse error"))?;
let patch = captures
.get(3)
.and_then(|m| m.as_str().parse::<i32>().ok())
.ok_or_else(|| anyhow!("Patch version parse error"))?;
Ok((major, minor, patch))
} else {
Err(anyhow!("Invalid kernel version string"))
}
}
fn do_cpio_cmd(magiskboot: &Path, workding_dir: &Path, cmd: &str) -> Result<()> {
let status = Command::new(magiskboot)
.current_dir(workding_dir)
@@ -36,6 +61,18 @@ fn do_cpio_cmd(magiskboot: &Path, workding_dir: &Path, cmd: &str) -> Result<()>
Ok(())
}
fn is_magisk_patched(magiskboot: &Path, workding_dir: &Path) -> Result<bool> {
let status = Command::new(magiskboot)
.current_dir(workding_dir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.args(["cpio", "ramdisk.cpio", "test"])
.status()?;
// 0: stock, 1: magisk
Ok(status.code() == Some(1))
}
fn dd<P: AsRef<Path>, Q: AsRef<Path>>(ifile: P, ofile: Q) -> Result<()> {
let status = Command::new("dd")
.stdout(Stdio::null())
@@ -63,8 +100,28 @@ pub fn patch(
out: Option<PathBuf>,
magiskboot_path: Option<PathBuf>,
) -> Result<()> {
let result = do_patch(image, kernel, kmod, init, ota, flash, out, magiskboot_path);
if let Err(ref e) = result {
println!("- Install Error: {e}");
}
result
}
#[allow(clippy::too_many_arguments)]
fn do_patch(
image: Option<PathBuf>,
kernel: Option<PathBuf>,
kmod: Option<PathBuf>,
init: Option<PathBuf>,
ota: bool,
flash: bool,
out: Option<PathBuf>,
magiskboot_path: Option<PathBuf>,
) -> Result<()> {
println!(include_str!("banner"));
if image.is_none() {
#[cfg(unix)]
#[cfg(target_os = "android")]
ensure_gki_kernel()?;
}
@@ -76,19 +133,17 @@ pub fn patch(
"init and module must not be specified."
);
} else {
ensure!(
init.is_some() && kmod.is_some(),
"init and module must be specified"
);
ensure!(kmod.is_some(), "module must be specified");
}
let workding_dir = tempdir::TempDir::new("KernelSU")?;
let workding_dir =
tempdir::TempDir::new("KernelSU").with_context(|| "create temp dir failed")?;
let bootimage;
let mut bootdevice = None;
if let Some(image) = image {
if let Some(ref image) = image {
ensure!(image.exists(), "boot image not found");
bootimage = std::fs::canonicalize(image)?;
} else {
@@ -111,7 +166,7 @@ pub fn patch(
format!("/dev/block/by-name/boot{slot_suffix}")
};
println!("bootdevice: {boot_partition}");
println!("- Bootdevice: {boot_partition}");
let tmp_boot_path = workding_dir.path().join("boot.img");
dd(&boot_partition, &tmp_boot_path)?;
@@ -122,37 +177,56 @@ pub fn patch(
bootdevice = Some(boot_partition);
};
println!("boot image: {bootimage:?}");
// try extract magiskboot/bootctl
let _ = assets::ensure_binaries(false);
let magiskboot = magiskboot_path
.map(std::fs::canonicalize)
.transpose()?
.unwrap_or_else(|| "magiskboot".into());
if !magiskboot.is_executable() {
#[cfg(unix)]
std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755))
.with_context(|| "set magiskboot executable failed".to_string())?;
}
ensure!(magiskboot.exists(), "magiskboot not found");
// extract magiskboot
let magiskboot = {
if which("magiskboot").is_ok() {
let _ = assets::ensure_binaries(true);
"magiskboot".into()
} else {
// magiskboot is not in $PATH, use builtin or specified one
let magiskboot = if let Some(magiskboot_path) = magiskboot_path {
std::fs::canonicalize(magiskboot_path)?
} else {
let magiskboot_path = workding_dir.path().join("magiskboot");
assets::copy_assets_to_file("magiskboot", &magiskboot_path)
.with_context(|| "copy magiskboot failed")?;
magiskboot_path
};
ensure!(magiskboot.exists(), "{magiskboot:?} is not exist");
#[cfg(unix)]
let _ = std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755));
magiskboot
}
};
if let Some(kernel) = kernel {
std::fs::copy(kernel, workding_dir.path().join("kernel"))
.with_context(|| "copy kernel from failed".to_string())?;
}
if let (Some(kmod), Some(init)) = (kmod, init) {
if let Some(kmod) = kmod {
println!("- Preparing assets");
std::fs::copy(kmod, workding_dir.path().join("kernelsu.ko"))
.with_context(|| "copy kernel module failed".to_string())?;
std::fs::copy(init, workding_dir.path().join("init"))
.with_context(|| "copy init failed".to_string())?;
let init_file = workding_dir.path().join("init");
if let Some(init) = init {
std::fs::copy(init, workding_dir.path().join("init"))
.with_context(|| "copy init failed".to_string())?;
} else {
crate::assets::copy_assets_to_file("ksuinit", init_file)
.with_context(|| "copy ksuinit failed")?;
}
// magiskboot unpack boot.img
// magiskboot cpio ramdisk.cpio 'cp init init.real'
// magiskboot cpio ramdisk.cpio 'add 0755 ksuinit init'
// magiskboot cpio ramdisk.cpio 'add 0755 <kmod> kernelsu.ko'
println!("- Unpacking boot image");
let status = Command::new(&magiskboot)
.current_dir(workding_dir.path())
.stdout(Stdio::null())
@@ -162,10 +236,22 @@ pub fn patch(
.status()?;
ensure!(status.success(), "magiskboot unpack failed");
let status = do_cpio_cmd(&magiskboot, workding_dir.path(), "exists init");
if status.is_ok() {
// init exist, backup it.
do_cpio_cmd(&magiskboot, workding_dir.path(), "mv init init.real")?;
let no_ramdisk = !workding_dir.path().join("ramdisk.cpio").exists();
let is_magisk_patched = is_magisk_patched(&magiskboot, workding_dir.path())?;
ensure!(
no_ramdisk || !is_magisk_patched,
"Cannot work with Magisk patched image"
);
println!("- Adding KernelSU LKM");
let is_kernelsu_patched =
do_cpio_cmd(&magiskboot, workding_dir.path(), "exists kernelsu.ko").is_ok();
if !is_kernelsu_patched {
// kernelsu.ko is not exist, backup init if necessary
let status = do_cpio_cmd(&magiskboot, workding_dir.path(), "exists init");
if status.is_ok() {
do_cpio_cmd(&magiskboot, workding_dir.path(), "mv init init.real")?;
}
}
do_cpio_cmd(&magiskboot, workding_dir.path(), "add 0755 init init")?;
@@ -176,6 +262,7 @@ pub fn patch(
)?;
}
println!("- Repacking boot image");
// magiskboot repack boot.img
let status = Command::new(&magiskboot)
.current_dir(workding_dir.path())
@@ -185,18 +272,25 @@ pub fn patch(
.arg(bootimage.display().to_string())
.status()?;
ensure!(status.success(), "magiskboot repack failed");
let new_boot = workding_dir.path().join("new-boot.img");
let out = out.unwrap_or(std::env::current_dir()?);
if image.is_some() {
// if image is specified, write to output file
let output_dir = out.unwrap_or(std::env::current_dir()?);
let now = chrono::Utc::now();
let output_image =
output_dir.join(format!("kernelsu_boot_{}.img", now.format("%Y%m%d_%H%M%S")));
let now = chrono::Utc::now();
let output_image = out.join(format!(
"kernelsu_patched_boot_{}.img",
now.format("%Y%m%d_%H%M%S")
));
std::fs::copy(workding_dir.path().join("new-boot.img"), &output_image)
.with_context(|| "copy out new boot failed".to_string())?;
if std::fs::rename(&new_boot, &output_image).is_err() {
std::fs::copy(&new_boot, &output_image)
.with_context(|| "copy out new boot failed".to_string())?;
}
println!("- Output file is written to");
println!("- {}", output_image.display().to_string().trim_matches('"'));
}
if flash {
println!("- Flashing new boot image");
let Some(bootdevice) = bootdevice else {
bail!("boot device not found")
};
@@ -206,7 +300,52 @@ pub fn patch(
.status()?;
ensure!(status.success(), "set boot device rw failed");
dd(&output_image, &bootdevice).with_context(|| "flash boot failed")?;
dd(&new_boot, &bootdevice).with_context(|| "flash boot failed")?;
if ota {
post_ota()?;
}
}
println!("- Done!");
Ok(())
}
fn post_ota() -> Result<()> {
use crate::defs::ADB_DIR;
use assets::BOOTCTL_PATH;
let status = Command::new(BOOTCTL_PATH).arg("hal-info").status()?;
if !status.success() {
return Ok(());
}
let current_slot = Command::new(BOOTCTL_PATH)
.arg("get-current-slot")
.output()?
.stdout;
let current_slot = String::from_utf8(current_slot)?;
let current_slot = current_slot.trim();
let target_slot = if current_slot == "0" { 1 } else { 0 };
Command::new(BOOTCTL_PATH)
.arg(format!("set-active-boot-slot {target_slot}"))
.status()?;
let post_ota_sh = std::path::Path::new(ADB_DIR)
.join("post-fs-data.d")
.join("post_ota.sh");
let sh_content = format!(
r###"
{BOOTCTL_PATH} mark-boot-successful
rm -f {BOOTCTL_PATH}
rm -f /data/adb/post-fs-data.d/post_ota.sh
"###
);
std::fs::write(&post_ota_sh, sh_content)?;
#[cfg(unix)]
std::fs::set_permissions(post_ota_sh, std::fs::Permissions::from_mode(0o755))?;
Ok(())
}

View File

@@ -7,7 +7,7 @@ use android_logger::Config;
#[cfg(target_os = "android")]
use log::LevelFilter;
use crate::{apk_sign, debug, defs, event, module, utils};
use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
/// KernelSU userspace cli
#[derive(Parser, Debug)]
@@ -60,10 +60,10 @@ enum Commands {
kernel: Option<PathBuf>,
/// LKM module path to replace
#[arg(short, long, requires("init"))]
#[arg(short, long)]
module: Option<PathBuf>,
/// init to be replaced, if use LKM, this must be specified
/// init to be replaced
#[arg(short, long, requires("module"))]
init: Option<PathBuf>,
@@ -239,7 +239,7 @@ pub fn run() -> Result<()> {
// the kernel executes su with argv[0] = "su" and replace it with us
let arg0 = std::env::args().next().unwrap_or_default();
if arg0 == "su" || arg0 == "/system/bin/su" {
return crate::ksu::root_shell();
return crate::su::root_shell();
}
let cli = Args::parse();
@@ -247,8 +247,8 @@ pub fn run() -> Result<()> {
log::info!("command: {:?}", cli.command);
let result = match cli.command {
Commands::PostFsData => event::on_post_data_fs(),
Commands::BootCompleted => event::on_boot_completed(),
Commands::PostFsData => init_event::on_post_data_fs(),
Commands::BootCompleted => init_event::on_boot_completed(),
Commands::Module { command } => {
#[cfg(any(target_os = "linux", target_os = "android"))]
@@ -265,13 +265,13 @@ pub fn run() -> Result<()> {
Module::Shrink => module::shrink_ksu_images(),
}
}
Commands::Install => event::install(),
Commands::Install => utils::install(),
Commands::Sepolicy { command } => match command {
Sepolicy::Patch { sepolicy } => crate::sepolicy::live_patch(&sepolicy),
Sepolicy::Apply { file } => crate::sepolicy::apply_file(file),
Sepolicy::Check { sepolicy } => crate::sepolicy::check_rule(&sepolicy),
},
Commands::Services => event::on_services(),
Commands::Services => init_event::on_services(),
Commands::Profile { command } => match command {
Profile::GetSepolicy { package } => crate::profile::get_sepolicy(package),
Profile::SetSepolicy { package, policy } => {
@@ -291,11 +291,11 @@ pub fn run() -> Result<()> {
Ok(())
}
Debug::Version => {
println!("Kernel Version: {}", crate::ksu::get_version());
println!("Kernel Version: {}", ksucalls::get_version());
Ok(())
}
Debug::Su { global_mnt } => crate::ksu::grant_root(global_mnt),
Debug::Mount => event::mount_systemlessly(defs::MODULE_DIR),
Debug::Su { global_mnt } => crate::su::grant_root(global_mnt),
Debug::Mount => init_event::mount_modules_systemlessly(defs::MODULE_DIR),
Debug::Xcp {
src,
dst,
@@ -304,7 +304,7 @@ pub fn run() -> Result<()> {
utils::copy_sparse_file(src, dst, punch_hole)?;
Ok(())
}
Debug::Test => todo!(),
Debug::Test => assets::ensure_binaries(false),
},
Commands::BootPatch {

View File

@@ -1,13 +1,11 @@
use anyhow::{bail, Context, Result};
use log::{info, warn};
#[cfg(target_os = "android")]
use std::path::PathBuf;
use std::{collections::HashMap, path::Path};
use crate::module::prune_modules;
use crate::{
assets, defs, mount, restorecon,
utils::{self, ensure_clean_dir, ensure_dir_exists},
assets, defs, ksucalls, mount, restorecon,
utils::{self, ensure_clean_dir},
};
fn mount_partition(partition_name: &str, lowerdir: &Vec<String>) -> Result<()> {
@@ -35,7 +33,7 @@ fn mount_partition(partition_name: &str, lowerdir: &Vec<String>) -> Result<()> {
mount::mount_overlay(&partition, lowerdir, workdir, upperdir)
}
pub fn mount_systemlessly(module_dir: &str) -> Result<()> {
pub fn mount_modules_systemlessly(module_dir: &str) -> Result<()> {
// construct overlay mount params
let dir = std::fs::read_dir(module_dir);
let Ok(dir) = dir else {
@@ -99,12 +97,14 @@ pub fn mount_systemlessly(module_dir: &str) -> Result<()> {
}
pub fn on_post_data_fs() -> Result<()> {
crate::ksu::report_post_fs_data();
ksucalls::report_post_fs_data();
utils::umask(0);
#[cfg(unix)]
let _ = catch_bootlog();
let _ = catch_bootlog("logcat", vec!["logcat"]);
#[cfg(unix)]
let _ = catch_bootlog("dmesg", vec!["dmesg", "-w"]);
if utils::has_magisk() {
warn!("Magisk detected, skip post-fs-data!");
@@ -162,7 +162,7 @@ pub fn on_post_data_fs() -> Result<()> {
.with_context(|| "mount module image failed".to_string())?;
// tell kernel that we've mount the module, so that it can do some optimization
crate::ksu::report_module_mounted();
ksucalls::report_module_mounted();
// if we are in safe mode, we should disable all modules
if safe_mode {
@@ -207,7 +207,7 @@ pub fn on_post_data_fs() -> Result<()> {
}
// mount module systemlessly by overlay
if let Err(e) = mount_systemlessly(module_dir) {
if let Err(e) = mount_modules_systemlessly(module_dir) {
warn!("do systemless mount failed: {}", e);
}
@@ -247,7 +247,7 @@ pub fn on_services() -> Result<()> {
}
pub fn on_boot_completed() -> Result<()> {
crate::ksu::report_boot_complete();
ksucalls::report_boot_complete();
info!("on_boot_completed triggered!");
let module_update_img = Path::new(defs::MODULE_UPDATE_IMG);
let module_img = Path::new(defs::MODULE_IMG);
@@ -266,38 +266,15 @@ pub fn on_boot_completed() -> Result<()> {
Ok(())
}
pub fn install() -> Result<()> {
ensure_dir_exists(defs::ADB_DIR)?;
std::fs::copy("/proc/self/exe", defs::DAEMON_PATH)?;
restorecon::lsetfilecon(defs::DAEMON_PATH, restorecon::ADB_CON)?;
// install binary assets
assets::ensure_binaries(false).with_context(|| "Failed to extract assets")?;
#[cfg(target_os = "android")]
link_ksud_to_bin()?;
Ok(())
}
#[cfg(target_os = "android")]
fn link_ksud_to_bin() -> Result<()> {
let ksu_bin = PathBuf::from(defs::DAEMON_PATH);
let ksu_bin_link = PathBuf::from(defs::DAEMON_LINK_PATH);
if ksu_bin.exists() && !ksu_bin_link.exists() {
std::os::unix::fs::symlink(&ksu_bin, &ksu_bin_link)?;
}
Ok(())
}
#[cfg(unix)]
fn catch_bootlog() -> Result<()> {
fn catch_bootlog(logname: &str, command: Vec<&str>) -> Result<()> {
use std::os::unix::process::CommandExt;
use std::process::Stdio;
let logdir = Path::new(defs::LOG_DIR);
utils::ensure_dir_exists(logdir)?;
let bootlog = logdir.join("boot.log");
let oldbootlog = logdir.join("boot.old.log");
let bootlog = logdir.join(format!("{logname}.log"));
let oldbootlog = logdir.join(format!("{logname}.old.log"));
if bootlog.exists() {
std::fs::rename(&bootlog, oldbootlog)?;
@@ -305,6 +282,8 @@ fn catch_bootlog() -> Result<()> {
let bootlog = std::fs::File::create(bootlog)?;
let mut args = vec!["-s", "9", "30s"];
args.extend_from_slice(&command);
// timeout -s 9 30s logcat > boot.log
let result = unsafe {
std::process::Command::new("timeout")
@@ -313,10 +292,7 @@ fn catch_bootlog() -> Result<()> {
utils::switch_cgroups();
Ok(())
})
.arg("-s")
.arg("9")
.arg("30s")
.arg("logcat")
.args(args)
.stdout(Stdio::from(bootlog))
.spawn()
};

View File

@@ -0,0 +1,43 @@
const EVENT_POST_FS_DATA: u64 = 1;
const EVENT_BOOT_COMPLETED: u64 = 2;
const EVENT_MODULE_MOUNTED: u64 = 3;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_version() -> i32 {
rustix::process::ksu_get_version()
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn get_version() -> i32 {
0
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn report_event(event: u64) {
rustix::process::ksu_report_event(event)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn report_event(_event: u64) {}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn check_kernel_safemode() -> bool {
rustix::process::ksu_check_kernel_safemode()
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn check_kernel_safemode() -> bool {
false
}
pub fn report_post_fs_data() {
report_event(EVENT_POST_FS_DATA);
}
pub fn report_boot_complete() {
report_event(EVENT_BOOT_COMPLETED);
}
pub fn report_module_mounted() {
report_event(EVENT_MODULE_MOUNTED);
}

View File

@@ -4,13 +4,14 @@ mod boot_patch;
mod cli;
mod debug;
mod defs;
mod event;
mod ksu;
mod init_event;
mod ksucalls;
mod module;
mod mount;
mod profile;
mod restorecon;
mod sepolicy;
mod su;
mod utils;
fn main() -> anyhow::Result<()> {

View File

@@ -1,7 +1,7 @@
#[allow(clippy::wildcard_imports)]
use crate::utils::*;
use crate::{
assets, defs, mount,
assets, defs, ksucalls, mount,
restorecon::{restore_syscon, setsyscon},
sepolicy, utils,
};
@@ -12,6 +12,7 @@ use is_executable::is_executable;
use java_properties::PropertiesIter;
use log::{info, warn};
use std::fs::OpenOptions;
use std::{
collections::HashMap,
env::var as env_var,
@@ -52,7 +53,7 @@ fn exec_install_script(module_file: &str) -> Result<()> {
),
)
.env("KSU", "true")
.env("KSU_KERNEL_VER_CODE", crate::ksu::get_version().to_string())
.env("KSU_KERNEL_VER_CODE", ksucalls::get_version().to_string())
.env("KSU_VER", defs::VERSION_NAME)
.env("KSU_VER_CODE", defs::VERSION_CODE)
.env("OUTFD", "1")
@@ -177,7 +178,7 @@ fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
.arg(path.as_ref())
.env("ASH_STANDALONE", "1")
.env("KSU", "true")
.env("KSU_KERNEL_VER_CODE", crate::ksu::get_version().to_string())
.env("KSU_KERNEL_VER_CODE", ksucalls::get_version().to_string())
.env("KSU_VER_CODE", defs::VERSION_CODE)
.env("KSU_VER", defs::VERSION_NAME)
.env(
@@ -391,22 +392,30 @@ fn _install_module(zip: &str) -> Result<()> {
println!("- Legacy image, migrating to new format, please be patient...");
create_module_image(tmp_module_img, sparse_image_size, journal_size)?;
let _dontdrop =
mount::AutoMountExt4::try_new(tmp_module_img, module_update_tmp_dir, true)?;
fs_extra::dir::copy(
defs::MODULE_DIR,
module_update_tmp_dir,
&fs_extra::dir::CopyOptions::new()
.overwrite(true)
.content_only(true),
)?;
mount::AutoMountExt4::try_new(tmp_module_img, module_update_tmp_dir, true)
.with_context(|| format!("Failed to mount {tmp_module_img}"))?;
utils::copy_module_files(defs::MODULE_DIR, module_update_tmp_dir)
.with_context(|| "Failed to migrate module files".to_string())?;
} else {
utils::copy_sparse_file(modules_img, tmp_module_img, true).with_context(|| {
format!(
"Failed to copy {} to {}",
modules_img.display(),
tmp_module_img
)
})?;
utils::copy_sparse_file(modules_img, tmp_module_img, true)
.with_context(|| "Failed to copy module image".to_string())?;
if std::fs::metadata(tmp_module_img)?.len() < sparse_image_size {
// truncate the file to new size
OpenOptions::new()
.write(true)
.open(tmp_module_img)
.context("Failed to open ext4 image")?
.set_len(sparse_image_size)
.context("Failed to truncate ext4 image")?;
// resize the image to new size
check_image(tmp_module_img)?;
Command::new("resize2fs")
.arg(tmp_module_img)
.stdout(Stdio::piped())
.status()?;
}
}
}

View File

@@ -55,8 +55,13 @@ impl Drop for AutoMountExt4 {
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn mount_ext4(source: impl AsRef<Path>, target: impl AsRef<Path>) -> Result<()> {
let new_loopback = loopdev::LoopControl::open()?.next_free()?;
new_loopback.with().attach(source)?;
let new_loopback = loopdev::LoopControl::open()?
.next_free()
.with_context(|| "Failed to alloc loop")?;
new_loopback
.with()
.attach(source)
.with_context(|| "Failed to attach loop")?;
let lo = new_loopback.path().ok_or(anyhow!("no loop"))?;
if let Result::Ok(fs) = fsopen("ext4", FsOpenFlags::FSOPEN_CLOEXEC) {
let fs = fs.as_fd();
@@ -104,14 +109,21 @@ pub fn mount_overlayfs(
upperdir,
workdir
);
if let Result::Ok(fs) = fsopen("overlay", FsOpenFlags::FSOPEN_CLOEXEC) {
let upperdir = upperdir
.filter(|up| up.exists())
.map(|e| e.display().to_string());
let workdir = workdir
.filter(|wd| wd.exists())
.map(|e| e.display().to_string());
let result = (|| {
let fs = fsopen("overlay", FsOpenFlags::FSOPEN_CLOEXEC)?;
let fs = fs.as_fd();
fsconfig_set_string(fs, "lowerdir", lowerdir_config)?;
if let (Some(upperdir), Some(workdir)) = (upperdir, workdir) {
if upperdir.exists() && workdir.exists() {
fsconfig_set_string(fs, "upperdir", upperdir.display().to_string())?;
fsconfig_set_string(fs, "workdir", workdir.display().to_string())?;
}
fsconfig_set_string(fs, "lowerdir", &lowerdir_config)?;
if let (Some(upperdir), Some(workdir)) = (&upperdir, &workdir) {
fsconfig_set_string(fs, "upperdir", upperdir)?;
fsconfig_set_string(fs, "workdir", workdir)?;
}
fsconfig_set_string(fs, "source", KSU_OVERLAY_SOURCE)?;
fsconfig_create(fs)?;
@@ -122,17 +134,14 @@ pub fn mount_overlayfs(
CWD,
dest.as_ref(),
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
)?;
} else {
)
})();
if let Err(e) = result {
warn!("fsopen mount failed: {:#}, fallback to mount", e);
let mut data = format!("lowerdir={lowerdir_config}");
if let (Some(upperdir), Some(workdir)) = (upperdir, workdir) {
if upperdir.exists() && workdir.exists() {
data = format!(
"{data},upperdir={},workdir={}",
upperdir.display(),
workdir.display()
);
}
data = format!("{data},upperdir={upperdir},workdir={workdir}");
}
mount(
KSU_OVERLAY_SOURCE,

View File

@@ -17,10 +17,6 @@ use rustix::{
thread::{set_thread_res_gid, set_thread_res_uid, Gid, Uid},
};
const EVENT_POST_FS_DATA: u64 = 1;
const EVENT_BOOT_COMPLETED: u64 = 2;
const EVENT_MODULE_MOUNTED: u64 = 3;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn grant_root(global_mnt: bool) -> Result<()> {
const KERNEL_SU_OPTION: u32 = 0xDEAD_BEEF;
@@ -49,6 +45,9 @@ pub fn grant_root(global_mnt: bool) -> Result<()> {
std::result::Result::Ok(())
})
};
// add /data/adb/ksu/bin to PATH
#[cfg(any(target_os = "linux", target_os = "android"))]
add_path_to_env(defs::BINARY_DIR)?;
Err(command.exec().into())
}
@@ -80,12 +79,12 @@ fn set_identity(uid: u32, gid: u32, groups: &[u32]) {
}
}
#[cfg(not(unix))]
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn root_shell() -> Result<()> {
unimplemented!()
}
#[cfg(unix)]
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn root_shell() -> Result<()> {
// we are root now, this was set in kernel!
@@ -300,43 +299,3 @@ fn add_path_to_env(path: &str) -> Result<()> {
env::set_var("PATH", new_path_env);
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_version() -> i32 {
rustix::process::ksu_get_version()
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn get_version() -> i32 {
0
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn report_event(event: u64) {
rustix::process::ksu_report_event(event)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn report_event(_event: u64) {}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn check_kernel_safemode() -> bool {
rustix::process::ksu_check_kernel_safemode()
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn check_kernel_safemode() -> bool {
false
}
pub fn report_post_fs_data() {
report_event(EVENT_POST_FS_DATA);
}
pub fn report_boot_complete() {
report_event(EVENT_BOOT_COMPLETED);
}
pub fn report_module_mounted() {
report_event(EVENT_MODULE_MOUNTED);
}

View File

@@ -5,7 +5,7 @@ use std::{
path::Path,
};
use crate::defs;
use crate::{assets, defs, ksucalls, restorecon};
use std::fs::metadata;
#[allow(unused_imports)]
use std::fs::{set_permissions, Permissions};
@@ -15,14 +15,17 @@ use std::os::unix::prelude::PermissionsExt;
use hole_punch::*;
use std::io::{Read, Seek, SeekFrom};
use jwalk::WalkDir;
use std::path::PathBuf;
#[cfg(any(target_os = "linux", target_os = "android"))]
use rustix::{
process,
thread::{move_into_link_name_space, unshare, LinkNameSpaceType, UnshareFlags},
};
pub fn ensure_clean_dir(dir: &str) -> Result<()> {
let path = Path::new(dir);
pub fn ensure_clean_dir(dir: impl AsRef<Path>) -> Result<()> {
let path = dir.as_ref();
log::debug!("ensure_clean_dir: {}", path.display());
if path.exists() {
log::debug!("ensure_clean_dir: {} exists, remove it", path.display());
@@ -106,7 +109,7 @@ pub fn is_safe_mode() -> bool {
if safemode {
return true;
}
let safemode = crate::ksu::check_kernel_safemode();
let safemode = ksucalls::check_kernel_safemode();
log::info!("kernel_safemode: {}", safemode);
safemode
}
@@ -191,6 +194,29 @@ pub fn get_tmp_path() -> &'static str {
""
}
#[cfg(target_os = "android")]
fn link_ksud_to_bin() -> Result<()> {
let ksu_bin = PathBuf::from(defs::DAEMON_PATH);
let ksu_bin_link = PathBuf::from(defs::DAEMON_LINK_PATH);
if ksu_bin.exists() && !ksu_bin_link.exists() {
std::os::unix::fs::symlink(&ksu_bin, &ksu_bin_link)?;
}
Ok(())
}
pub fn install() -> Result<()> {
ensure_dir_exists(defs::ADB_DIR)?;
std::fs::copy("/proc/self/exe", defs::DAEMON_PATH)?;
restorecon::lsetfilecon(defs::DAEMON_PATH, restorecon::ADB_CON)?;
// install binary assets
assets::ensure_binaries(false).with_context(|| "Failed to extract assets")?;
#[cfg(target_os = "android")]
link_ksud_to_bin()?;
Ok(())
}
// TODO: use libxcp to improve the speed if cross's MSRV is 1.70
pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(
src: P,
@@ -241,3 +267,102 @@ pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn copy_xattrs(src_path: impl AsRef<Path>, dest_path: impl AsRef<Path>) -> Result<()> {
use rustix::path::Arg;
let std::result::Result::Ok(xattrs) = extattr::llistxattr(src_path.as_ref()) else {
return Ok(());
};
for xattr in xattrs {
let std::result::Result::Ok(value) = extattr::lgetxattr(src_path.as_ref(), &xattr) else {
continue;
};
log::info!(
"Set {:?} xattr {} = {}",
dest_path.as_ref(),
xattr.to_string_lossy(),
value.to_string_lossy(),
);
if let Err(e) =
extattr::lsetxattr(dest_path.as_ref(), &xattr, &value, extattr::Flags::empty())
{
log::warn!("Failed to set xattr: {}", e);
}
}
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn copy_module_files(source: impl AsRef<Path>, destination: impl AsRef<Path>) -> Result<()> {
use rustix::fs::FileTypeExt;
use rustix::fs::MetadataExt;
for entry in WalkDir::new(source.as_ref()).into_iter() {
let entry = entry.context("Failed to access entry")?;
let source_path = entry.path();
let relative_path = source_path
.strip_prefix(source.as_ref())
.context("Failed to generate relative path")?;
let dest_path = destination.as_ref().join(relative_path);
if let Some(parent) = dest_path.parent() {
std::fs::create_dir_all(parent).context("Failed to create directory")?;
}
if entry.file_type().is_file() {
std::fs::copy(&source_path, &dest_path).with_context(|| {
format!("Failed to copy file from {source_path:?} to {dest_path:?}",)
})?;
copy_xattrs(&source_path, &dest_path)?;
} else if entry.file_type().is_symlink() {
if dest_path.exists() {
std::fs::remove_file(&dest_path).context("Failed to remove file")?;
}
let target = std::fs::read_link(entry.path()).context("Failed to read symlink")?;
log::info!("Symlink: {:?} -> {:?}", dest_path, target);
std::os::unix::fs::symlink(target, &dest_path).context("Failed to create symlink")?;
copy_xattrs(&source_path, &dest_path)?;
} else if entry.file_type().is_dir() {
create_dir_all(&dest_path)?;
let metadata = std::fs::metadata(&source_path).context("Failed to read metadata")?;
std::fs::set_permissions(&dest_path, metadata.permissions())
.with_context(|| format!("Failed to set permissions for {dest_path:?}"))?;
copy_xattrs(&source_path, &dest_path)?;
} else if entry.file_type().is_char_device() {
if dest_path.exists() {
std::fs::remove_file(&dest_path).context("Failed to remove file")?;
}
let metadata = std::fs::metadata(&source_path).context("Failed to read metadata")?;
let mode = metadata.permissions().mode();
let dev = metadata.rdev();
if dev == 0 {
log::info!(
"Found a char device with major 0: {}",
entry.path().display()
);
rustix::fs::mknodat(
rustix::fs::CWD,
&dest_path,
rustix::fs::FileType::CharacterDevice,
mode.into(),
dev,
)
.with_context(|| format!("Failed to create device file at {dest_path:?}"))?;
copy_xattrs(&source_path, &dest_path)?;
}
} else {
log::info!(
"Unknown file type: {:?}, {:?},",
entry.file_type(),
entry.path(),
);
}
}
Ok(())
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn copy_module_files(_source: impl AsRef<Path>, _destination: impl AsRef<Path>) -> Result<()> {
unimplemented!()
}

View File

@@ -46,8 +46,8 @@ function sidebarGuide() {
items: [
{ text: 'KernelSU とは?', link: '/ja_JP/guide/what-is-kernelsu' },
{ text: 'インストール', link: '/ja_JP/guide/installation' },
{ text: 'ビルドするには?', link: '/guide/how-to-build' },
{ text: '非 GKI デバイスでの実装', link: '/guide/how-to-integrate-for-non-gki' },
{ text: 'ビルドするには?', link: '/ja_JP/guide/how-to-build' },
{ text: '非 GKI デバイスでの実装', link: '/ja_JP/guide/how-to-integrate-for-non-gki' },
{ text: '非公式の対応デバイス', link: '/ja_JP/guide/unofficially-support-devices.md' },
{ text: 'モジュールのガイド', link: '/ja_JP/guide/module.md' },
{ text: 'ブートループからの復旧', link: '/ja_JP/guide/rescue-from-bootloop.md' },

View File

@@ -51,7 +51,7 @@ function sidebarGuide() {
{ text: '如何为非GKI设备集成 KernelSU', link: '/zh_CN/guide/how-to-integrate-for-non-gki'},
{ text: '非官方支持设备', link: '/zh_CN/guide/unofficially-support-devices.md' },
{ text: '模块开发指南', link: '/zh_CN/guide/module.md' },
{ text: '模块 Web 界面', link: '/guide/module-webui.md' },
{ text: '模块 Web 界面', link: '/zh_CN/guide/module-webui.md' },
{ text: 'App Profile', link: '/zh_CN/guide/app-profile.md' },
{ text: '救砖', link: '/zh_CN/guide/rescue-from-bootloop.md' },
{ text: '常见问题', link: '/zh_CN/guide/faq' },

View File

@@ -45,11 +45,14 @@ function sidebarGuide() {
text: 'Guide',
items: [
{ text: '什麼是 KernelSU', link: '/zh_TW/guide/what-is-kernelsu' },
{ text: 'KernelSU 與 Magisk 的差異', link: '/zh_TW/guide/difference-with-magisk' },
{ text: '安裝', link: '/zh_TW/guide/installation' },
{ text: '如何建置?', link: '/zh_TW/guide/how-to-build' },
{ text: '如何為非 GKI 核心整合 KernelSU', link: '/zh_TW/guide/how-to-integrate-for-non-gki'},
{ text: '非官方支援裝置', link: '/zh_TW/guide/unofficially-support-devices.md' },
{ text: '模組指南', link: '/zh_TW/guide/module.md' },
{ text: '模組 WebUI', link: '/zh_TW/guide/module-webui.md' },
{ text: 'App Profile', link: '/zh_TW/guide/app-profile.md' },
{ text: '搶救開機迴圈', link: '/zh_TW/guide/rescue-from-bootloop.md' },
{ text: '常見問題', link: '/zh_TW/guide/faq' },
{ text: '隱藏功能', link: '/zh_TW/guide/hidden-features' },

View File

@@ -41,7 +41,7 @@ It is possible. But you should download the kernel source and intergrate KernelS
It is device's kernel that affect KernelSU's compatability and it has nothing to do with Android version.The only restriction is that devices launched with Android 12 must be kernel 5.10+(GKI devices). So:
1. Devices launched with Android 12 must be supported.
2. Devices with has an old kernel (Some Android 12 devices is also old kernel) is compatable (You should build kernel yourself)
2. Devices which have an old kernel (Some Android 12 devices is also old kernel) are compatible (You should build kernel yourself)
## Can KernelSU support old kernel?
@@ -67,8 +67,12 @@ We do not recommend you to modify the system partition directly. You should use
Of course. But KernelSU doesn't have builtin hosts support, you can install [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) to do it.
## Why is there a huge 1T file?
## Why is there a huge 1 TB file?
The 1T size `modules.img` is a disk image file, **don't worry about its size**, it's a special type of file known as a [sparse file](https://en.wikipedia.org/wiki/Sparse_file), its actual size is only the size of the module you use, and it will dynamically shrink after you delete the module; it does not actually occupy 1T of disk space (actually your mobile phone may not have that much space).
The 1 TB size `modules.img` is a disk image file, **don't worry about its size**, it's a special type of file known as a [sparse file](https://en.wikipedia.org/wiki/Sparse_file), its actual size is only the size of the module you use, and it will dynamically shrink after you delete the module; it does not actually occupy 1 TB of disk space (actually your mobile phone may not have that much space).
If you're really unhappy with the size of this file, you can use the `resize2fs -M` command to make it the actual size; but the module may not work properly at this time, and we won't provide any support for this.
## Why does my device show wrong storage size?
Certain devices use non-standard methods for calculating device's storage size, potentially leading to inaccurate storage calculations in system apps and menus, especially when dealing with 1 TB sparse files. While this problem seems to be specific to Samsung devices, affecting only Samsung apps and services, it's essential to note that the discrepancy is primarily in the total storage size, and the free space calculation remains accurate.

View File

@@ -40,6 +40,14 @@ Starting from Android 13, the kernel is built by `bazel`:
tools/bazel build --config=fast //common:kernel_aarch64_dist
```
:::info
For some Android 14 Kernel, to make Wi-Fi/Bluetooth work. It may be necessary to remove all the GKI protected exports:
```sh
rm common/android/abi_gki_protected_exports_*
```
:::
## Build Kernel with KernelSU
If you can build the kernel successfully, then build KernelSU is so easy, Select any one run in Kernel source root dir:

View File

@@ -70,7 +70,7 @@ If your device's `boot.img` uses a commonly used compression format, you can use
### Find proper boot.img
KernelSU provides a generic boot.img for GKI devices and you should flush the boot.img to the boot partition of the device.
KernelSU provides a generic boot.img for GKI devices and you should flash the boot.img to the boot partition of the device.
You can download boot.img from [GitHub Release](https://github.com/tiann/KernelSU/releases), please note that you should use the correct version of boot.img. If you don't know which file to download, please carefully read the description of [KMI](#kmi) and [Security Patch Level](#security-patch-level) in this document.

View File

@@ -30,7 +30,7 @@ KernelSU provides a JavaScript library and [publishes it on npm](https://www.npm
For example, you can execute a shell command to obtain a specific configuration or modify a property:
```javascript
```JavaScript
import { exec } from 'kernelsu';
const { errno, stdout } = exec("getprop ro.product.model");

View File

@@ -0,0 +1,303 @@
# 非 GKI カーネルで KernelSU を統合する方法は?
KernelSU は非 GKI カーネルに統合することが可能であり、4.14 以下のバージョンにバックポートされました。
非 GKI カーネルの断片化のため、統一されたビルド方法がありませんので、非 GKI ブートイメージを提供することができません。しかし、KernelSU を統合して自分自身でカーネルをビルドすることができます。
まず、カーネルソースコードからブート可能なカーネルをビルドできる能力が必要です。もしカーネルがオープンソースでない場合、あなたのデバイスで KernelSU を実行することは困難です。
ブート可能なカーネルをビルドできるなら、カーネルソースコードに KernelSU を統合する方法は二つあります:
1. `kprobe` で自動的に
2. 手動で
## kprobe で統合する
KernelSU は kprobe を使ってカーネルフックを行います。もし *kprobe* があなたのカーネルでうまく動作する場合、この方法を使うことを推奨します。
まず、KernelSU をカーネルソースツリーに追加してください:
```sh
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -
```
次に、*kprobe* がカーネル設定で有効になっているか確認してください。もし有効でなければ、これらの設定を追加してください:
```
CONFIG_KPROBES=y
CONFIG_HAVE_KPROBES=y
CONFIG_KPROBE_EVENTS=y
```
そしてカーネルを再度ビルドしてください。KernelSU はうまく動作するはずです。
KPROBES がまだ有効化されていない場合は、CONFIG_MODULES を有効化して試みることができます。それでも効果がない場合は、make menuconfig を使って KPROBES の他の依存関係を検索してください)
しかし、KernelSU を統合した際にブートループに遭遇した場合、それは *kprobe* があなたのカーネルで破損している可能性があります。kprobe のバグを修正するか、二番目の方法を使用するべきです。
:::tip kprobe が破損しているかどうかを確認する方法は?
`KernelSU/kernel/ksu.c` にある `ksu_enable_sucompat()``ksu_enable_ksud()` をコメントアウトし、デバイスが正常にブートするか試してください。もし正常にブートするならば、kprobe が破損している可能性があります。
## カーネルソースを手動で変更する
もし kprobe があなたのカーネルで機能しない場合(上流のバグや 4.8 以下のカーネルバグが原因かもしれません)、以下の方法を試すことができます。
まず、KernelSU をカーネルソースツリーに追加してください:
::: code-group
## カーネルソースを手動で変更する
もし kprobe があなたのカーネルで機能しない場合(上流のバグや 4.8 以下のカーネルバグが原因かもしれません)、以下の方法を試すことができます。
まず、KernelSU をカーネルソースツリーに追加してください:
::: code-group
```sh
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -
```
[ main branch(dev)]
```sh
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s main
```
[Select tag(Such as v0.5.2)]
```sh
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.5.2
```
:::
いくつかのデバイスでは、あなたの defconfig が `arch/arm64/configs` にあったり、または他のケースでは `arch/arm64/configs/vendor/your_defconfig` にあることを念頭に置いてください。例えばあなたの defconfig で、`CONFIG_KSU` を y で有効に、または n で無効に設定します。あなたのパスは次のようになるでしょう:
`arch/arm64/configs/...`
```
# KernelSU
CONFIG_KSU=y
```
次に、KernelSU の呼び出しをカーネルソースに追加します。こちらは参照のためのパッチです:
::: code-group
```diff[exec.c]
diff --git a/fs/exec.c b/fs/exec.c
index ac59664eaecf..bdd585e1d2cc 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1890,11 +1890,14 @@ static int __do_execve_file(int fd, struct filename *filename,
return retval;
}
+#ifdef CONFIG_KSU
+extern bool ksu_execveat_hook __read_mostly;
+extern int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
+ void *envp, int *flags);
+extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
+ void *argv, void *envp, int *flags);
+#endif
static int do_execveat_common(int fd, struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp,
int flags)
{
+ #ifdef CONFIG_KSU
+ if (unlikely(ksu_execveat_hook))
+ ksu_handle_execveat(&fd, &filename, &argv, &envp, &flags);
+ else
+ ksu_handle_execveat_sucompat(&fd, &filename, &argv, &envp, &flags);
+ #endif
return __do_execve_file(fd, filename, argv, envp, flags, NULL);
}
```
```diff[open.c]
diff --git a/fs/open.c b/fs/open.c
index 05036d819197..965b84d486b8 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -348,6 +348,8 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len)
return ksys_fallocate(fd, mode, offset, len);
}
+#ifdef CONFIG_KSU
+extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
+ int *flags);
+#endif
/*
* access() needs to use the real uid/gid, not the effective uid/gid.
* We do this by temporarily clearing all FS-related capabilities and
@@ -355,6 +357,7 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len)
*/
long do_faccessat(int dfd, const char __user *filename, int mode)
{
const struct cred *old_cred;
struct cred *override_cred;
struct path path;
struct inode *inode;
struct vfsmount *mnt;
int res;
unsigned int lookup_flags = LOOKUP_FOLLOW;
+ #ifdef CONFIG_KSU
+ ksu_handle_faccessat(&dfd, &filename, &mode, NULL);
+ #endif
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
return -EINVAL;
```
```diff[read_write.c]
diff --git a/fs/read_write.c b/fs/read_write.c
index 650fc7e0f3a6..55be193913b6 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -434,10 +434,14 @@ ssize_t kernel_read(struct file *file, void *buf, size_t count, loff_t *pos)
}
EXPORT_SYMBOL(kernel_read);
+#ifdef CONFIG_KSU
+extern bool ksu_vfs_read_hook __read_mostly;
+extern int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
+ size_t *count_ptr, loff_t **pos);
+#endif
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
+ #ifdef CONFIG_KSU
+ if (unlikely(ksu_vfs_read_hook))
+ ksu_handle_vfs_read(&file, &buf, &count, &pos);
+ #endif
+
if (!(file->f_mode & FMODE_READ))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_READ))
```
```diff[stat.c]
diff --git a/fs/stat.c b/fs/stat.c
index 376543199b5a..82adcef03ecc 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -148,6 +148,8 @@ int vfs_statx_fd(unsigned int fd, struct kstat *stat,
}
EXPORT_SYMBOL(vfs_statx_fd);
+#ifdef CONFIG_KSU
+extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
+#endif
+
/**
* vfs_statx - Get basic and extra attributes by filename
* @dfd: A file descriptor representing the base dir for a relative filename
@@ -170,6 +172,7 @@ int vfs_statx(int dfd, const char __user *filename, int flags,
int error = -EINVAL;
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
+ #ifdef CONFIG_KSU
+ ksu_handle_stat(&dfd, &filename, &flags);
+ #endif
if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
AT_EMPTY_PATH | KSTAT_QUERY_FLAGS)) != 0)
return -EINVAL;
```
カーネル ソースには 4 つの関数があるはずです。
1. do_faccessat、通常は `fs/open.c` にあります
2. do_execveat_common (通常は `fs/exec.c` にあります)
3. vfs_read (通常は `fs/read_write.c` にあります)
4. vfs_statx (通常は「fs/stat.c」にあります)
カーネルに `vfs_statx` がない場合は、代わりに `vfs_fstatat` を使用してください:
```diff
diff --git a/fs/stat.c b/fs/stat.c
index 068fdbcc9e26..5348b7bb9db2 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -87,6 +87,8 @@ int vfs_fstat(unsigned int fd, struct kstat *stat)
}
EXPORT_SYMBOL(vfs_fstat);
+#ifdef CONFIG_KSU
+extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
+#endif
int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,
int flag)
{
@@ -94,6 +96,8 @@ int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,
int error = -EINVAL;
unsigned int lookup_flags = 0;
+ #ifdef CONFIG_KSU
+ ksu_handle_stat(&dfd, &filename, &flag);
+ #endif
+
if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
AT_EMPTY_PATH)) != 0)
goto out;
```
4.17 より前のカーネルの場合、`do faccessat` が見つからない場合は、`faccessat` システムコールの定義に移動して、そこで呼び出しを実行します。
```diff
diff --git a/fs/open.c b/fs/open.c
index 2ff887661237..e758d7db7663 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -355,6 +355,9 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len)
return error;
}
+#ifdef CONFIG_KSU
+extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
+ int *flags);
+#endif
+
/*
* access() needs to use the real uid/gid, not the effective uid/gid.
* We do this by temporarily clearing all FS-related capabilities and
@@ -370,6 +373,8 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
int res;
unsigned int lookup_flags = LOOKUP_FOLLOW;
+ #ifdef CONFIG_KSU
+ ksu_handle_faccessat(&dfd, &filename, &mode, NULL);
+ #endif
+
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
return -EINVAL;
```
KernelSU の組み込み SafeMode を有効にするには、`drivers/input/input.c` の `input_handle_event` も変更する必要があります。
:::ヒント
この機能を有効にすることを強くお勧めします。ブートループを防ぐのに非常に役立ちます!
:::
```diff
diff --git a/drivers/input/input.c b/drivers/input/input.c
index 45306f9ef247..815091ebfca4 100755
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -367,10 +367,13 @@ static int input_get_disposition(struct input_dev *dev,
return disposition;
}
+#ifdef CONFIG_KSU
+extern bool ksu_input_hook __read_mostly;
+extern int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int *value);
+#endif
+
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = input_get_disposition(dev, type, code, &value);
+ #ifdef CONFIG_KSU
+ if (unlikely(ksu_input_hook))
+ ksu_handle_input_handle_event(&type, &code, &value);
+ #endif
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
add_input_randomness(type, code, value);
```
最後に、カーネルを再度ビルドすると、KernelSU が正常に動作するはずです。
:::info 誤ってセーフ モードに入ってしまった場合は、
手動統合を使用し、`CONFIG_KPROBES` を無効にしない場合、ユーザーは起動後に音量を下げるボタンを押してセーフ モードをトリガーする可能性があります。 したがって、手動統合を使用する場合は、`CONFIG_KPROBES` を無効にする必要があります。
:::

View File

@@ -6,7 +6,7 @@ KernelSU のモジュールは、ブートスクリプトの実行やシステ
## webroot ディレクトリ
Web リソースファイルは、モジュールのルートディレクトリの webroot サブディレクトリに置かれるべきであり、index.html という名前のファイルが必ず存在しなければなりません。これがモジュールページのエントリです。Web インターフェイスを含む最もシンプルなモジュール構造は以下の通りです:
Web リソースファイルは、モジュールのルートディレクトリの `webroot` サブディレクトリに置かれるべきであり、`index.html` という名前のファイルが必ず存在しなければなりません。これがモジュールページのエントリです。Web インターフェイスを含む最もシンプルなモジュール構造は以下の通りです:
```txt
tree .
@@ -22,15 +22,15 @@ Web リソースファイルは、モジュールのルートディレクトリ
ページに css や JavaScript が含まれている場合は、このディレクトリに配置する必要があります。
## Javascript API
## JavaScript API
単なる表示ページであれば、通常の Web ページとの違いはありません。より重要なのは、KernelSU がモジュールの固有機能を実装させるための一連のシステム API を提供することです。
KernelSU は Javascript ライブラリを提供し、[npm で公開しています](https://www.npmjs.com/package/kernelsu)。これを Web ページの JavaScript コードで使用することができます。
KernelSU は JavaScript ライブラリを提供し、[npm で公開しています](https://www.npmjs.com/package/kernelsu)。これを Web ページの JavaScript コードで使用することができます。
たとえば、特定の設定を取得したり、プロパティを変更するために、シェルコマンドを実行することができます:
```javascript
```JavaScript
import { exec } from 'kernelsu';
const { errno, stdout } = exec("getprop ro.product.model");
@@ -44,5 +44,5 @@ const { errno, stdout } = exec("getprop ro.product.model");
## いくつかのヒント
1. localStorage を通常通りに使用してデータを保存することができますが、Manager アプリをアンインストールした後には失われます。永続的に保存する必要がある場合は、自分でいくつかのディレクトリにデータを書き込むことができます。
1. `localStorage` を通常通りに使用してデータを保存することができますが、Manager アプリをアンインストールした後には失われます。永続的に保存する必要がある場合は、自分でいくつかのディレクトリにデータを書き込むことができます。
2. シンプルなページには、[parceljs](https://parceljs.org/)を使用することをお勧めします。設定が不要で非常に便利です。しかし、フロントエンドの達人である場合や、自分の好みがある場合は、気に入ったものを選んでください!

View File

@@ -67,8 +67,12 @@ Não recomendamos que você modifique a partição do sistema diretamente. Você
Claro. Mas o KernelSU não tem suporte a hosts integrados, você pode instalar [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) para fazer isso.
## Por que existe um enorme arquivo de 1T?
## Por que existe um enorme arquivo de 1 TB?
O arquivo `modules.img` de 1T é um arquivo de imagem de disco, **não se preocupe com seu tamanho**, é um tipo especial de arquivo conhecido como [arquivo esparso](https://en.wikipedia.org/wiki/Sparse_file), seu tamanho real é apenas o tamanho do módulo que você usa e diminuirá dinamicamente após você excluir o módulo. Na verdade, ele não ocupa 1T de espaço em disco (na verdade, seu celular pode não ter tanto espaço).
O arquivo `modules.img` de 1 TB é um arquivo de imagem de disco, **não se preocupe com seu tamanho**, é um tipo especial de arquivo conhecido como [arquivo esparso](https://en.wikipedia.org/wiki/Sparse_file), seu tamanho real é apenas o tamanho do módulo que você usa e diminuirá dinamicamente após você excluir o módulo. Na verdade, ele não ocupa 1 TB de espaço em disco (na verdade, seu celular pode não ter tanto espaço).
Se você estiver realmente insatisfeito com o tamanho deste arquivo, você pode usar o comando `resize2fs -M` para torná-lo seu tamanho real, mas o módulo pode não funcionar corretamente neste momento e não forneceremos nenhum suporte para isso.
## Por que meu dispositivo mostra o tamanho do armazenamento errado?
Certos dispositivos usam métodos não padrão para calcular o tamanho do armazenamento do dispositivo, potencialmente levando a cálculos de armazenamento imprecisos em apps e menus do sistema, especialmente ao lidar com arquivos esparsos de 1 TB. Embora esse problema pareça ser específico para os dispositivos Samsung, afetando apenas apps e serviços da Samsung, é essencial observar que a discrepância está principalmente no tamanho total do armazenamento, e o cálculo do espaço livre permanece preciso.

View File

@@ -40,6 +40,14 @@ A partir do Android 13, o kernel é construído pelo `bazel`:
tools/bazel build --config=fast //common:kernel_aarch64_dist
```
:::info INFORMAÇÕES
Para alguns kernel do Android 14, para fazer o Wi-Fi/Bluetooth funcionar. Pode ser necessário remover todas as exportações protegidas por GKI:
```sh
rm common/android/abi_gki_protected_exports_*
```
:::
## Construir o kernel com KernelSU
Se você conseguir construir o kernel com sucesso, então construir o KernelSU é muito fácil. Selecione qualquer um executado no diretório raiz de origem do kernel:

View File

@@ -70,7 +70,7 @@ Se o `boot.img` do seu dispositivo usa um formato de compactação comumente usa
### Encontre o boot.img adequado
O KernelSU fornece um boot.img genérico para dispositivos GKI e você deve liberar o boot.img para a partição boot do dispositivo.
O KernelSU fornece um boot.img genérico para dispositivos GKI e você deve flashar o boot.img para a partição boot do dispositivo.
Você pode baixar o boot.img em [GitHub Release](https://github.com/tiann/KernelSU/releases), por favor, observe que você deve usar a versão correta do boot.img. Se você não sabe qual arquivo baixar, leia atentamente a descrição de [KMI](#kmi) e [Nível do patch de segurança](#security-patch-level) neste documento.

View File

@@ -30,7 +30,7 @@ KernelSU fornece uma biblioteca JavaScript e [publica-a no npm](https://www.npmj
Por exemplo, você pode executar um comando shell para obter uma configuração específica ou modificar uma propriedade:
```javascript
```JavaScript
import { exec } from 'kernelsu';
const { errno, stdout } = exec("getprop ro.product.model");

View File

@@ -23,6 +23,6 @@ features:
- title: Controle de acesso root
details: Somente apps permitidos podem acessar ou ver su, todos os outros apps não estão cientes disso.
- title: Privilégios de root personalizáveis
details: KernelSU permite a personalização de uid, gid, grupos, capacidades e regras SELinux do su, bloqueando privilégios de root.
details: KernelSU permite a personalização de su, uid, gid, grupos, capacidades e regras SELinux, bloqueando privilégios de root.
- title: Módulos
details: Os módulos podem modificar /system sem sistema usando OverlayFS permitindo uma grande potência.

View File

@@ -16,6 +16,10 @@
"context":"u:r:su:s0",
"namespace":"INHERITED",
"locales": {
"zh_TW": {
"name": "Adaway Root",
"description": "僅允許 Adaway 修改 hosts 和執行 Web 伺服器的必要權限"
},
"bn": {
"name": "অ্যাডঅ্যাওয়ে রুট",
"description": "অ্যাডঅ্যাওয়ে সিস্টেমের হোস্ট ফাইল পরিবর্তন এবং ওয়েবসার্ভার চালু করতে কমপক্ষে যা অনুমতি লাগে।"

View File

@@ -13,6 +13,10 @@
"name": "Adb 模版",
"description": "大多数使用 ADB 权限应用所需要的最小权限"
},
"zh_TW": {
"name": "Adb",
"description": "大多數使用 ADB 權限應用程式所需要的最低權限"
},
"tr": {
"name": "Adb",
"description": "ADB ayrıcalığını kullanan çoğu uygulama için gereken minimum kurallar."

View File

@@ -19,6 +19,10 @@
"name": "西米露Cemiuiler",
"description": "授予Cemiuiler能正常工作——重启作用域应用的最小限度权限。"
},
"zh_TW": {
"name": "Cemiuiler",
"description": "授予Cemiuiler能正常運作重啟作用域應用程式的最低權限"
},
"tr": {
"name": "Cemiuiler",
"description": "Cemiuiler uygulamasına düzgün çalışması için minimum izinleri verin."

View File

@@ -13,6 +13,10 @@
"name": "无能的 Root",
"description": "无任何权能的 root 用户。"
},
"zh_TW": {
"name": "無能的 Root",
"description": "無任何權限的 root"
},
"tr": {
"name": "Yetersiz root",
"description": "Herhangi bir özelliği olmayan yetersiz bir root kullanıcısı."

View File

@@ -21,6 +21,10 @@
"context":"u:r:su:s0",
"namespace":"INHERITED",
"locales": {
"zh_TW": {
"name": "核心管理器",
"description": "常用於 FKM 和 SmartPack 等核心管理器"
},
"in": {
"name": "Kernel Manager",
"description": "Umumnya digunakan pada Kernel manager seperti FKM dan SmartPack."

View File

@@ -0,0 +1,22 @@
{
"id":"nethunter.root",
"name":"Kali NetHunter",
"author":"cachiusa",
"description":"Required permissions for Kali NetHunter app to chroot",
"namespace":"INHERITED",
"uid":0,
"gid":0,
"groups":[
"ROOT"
],
"capabilities":[
"CAP_DAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_ADMIN"
"CAP_SETGID",
],
"context":"u:r:su:s0",
"rules":[""]
}

View File

@@ -16,6 +16,10 @@
"context":"u:r:su:s0",
"namespace":"INHERITED",
"locales": {
"zh_TW": {
"name": "Root Explorer",
"description": "具有根目錄讀寫權限的檔案管理器"
},
"in": {
"name": "Root Explorer",
"description": "File manager dengan kemampuan Root."

View File

@@ -14,6 +14,10 @@
"context":"u:r:su:s0",
"namespace":"INHERITED",
"locales": {
"zh_TW": {
"name": "Shizuku",
"description": "只有啟動 Shizuku 服務所需的權限"
},
"bn": {
"name": "শিজুকু সার্ভিস",
"description": "শিজুকু চালানোর জন্য শুধু যে অনুমতি গুলি লাগে।"

View File

@@ -13,6 +13,10 @@
"name": "Android 系统",
"description": "Android 系统运行的权限级别,但没有任何权能。"
},
"zh_TW": {
"name": "Android 系統",
"description": "Android 系統運作的權限級別,但沒有任何權能"
},
"tr": {
"name": "Sistem",
"description": "Android sisteminin herhangi bir yetenek olmadan çalıştığı izin düzeyi."

View File

@@ -67,8 +67,8 @@ GKI1 跟 GKI2 完全是两个东西,所以你需要自行编译内核。
当然可以。但这个功能 KernelSU 没有内置,你可以安装这个 [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module)
## 为什么有个 1T 的超大文件?
## 为什么有个 1 TB 的超大文件?
1T 大小的 `modules.img` 是一个磁盘镜像文件,**不要担心它的大小**,它是一种被称之为[稀疏文件](https://en.wikipedia.org/wiki/Sparse_file)的文件格式,它的实际大小只有你使用的模块的大小,并且你在删除模块后它会动态缩小;它并不实际占用 1T 大小的磁盘空间(实际上你手机可能并没有这么多空间)。
1 TB 大小的 `modules.img` 是一个磁盘镜像文件,**不要担心它的大小**,它是一种被称之为[稀疏文件](https://en.wikipedia.org/wiki/Sparse_file)的文件格式,它的实际大小只有你使用的模块的大小,并且你在删除模块后它会动态缩小;它并不实际占用 1 TB 大小的磁盘空间(实际上你手机可能并没有这么多空间)。
如果你真的对这个文件的大小感到不爽,你可以使用 `resize2fs -M` 命令让它变成实际大小;但此时模块可能无法正常工作,我们也不会为此提供任何支持。

View File

@@ -2,7 +2,7 @@
KernelSU 的模块除了执行启动脚本和修改系统文件之外,还支持显示 UI 界面和与用户交互。
你可以通过任何 Web 技术编写 HTML + CSS + Javascript 页面KernelSU 的管理器将通过WebView 显示这些页面。此外KernelSU 还提供了一些用于与系统交互的 Javascript API例如执行shell命令。
你可以通过任何 Web 技术编写 HTML + CSS + JavaScript 页面KernelSU 的管理器将通过WebView 显示这些页面。此外KernelSU 还提供了一些用于与系统交互的 JavaScript API例如执行shell命令。
## WebUI 根目录
@@ -20,17 +20,17 @@ Web 资源文件应放置在模块根目录的 `webroot` 子目录中,并且
安装模块时KernelSU 会自动设置 `webroot` 目录的权限和 SELinux context如果您不知道自己在做什么请不要自行设置该目录的权限
:::
如果您的页面包含 cssjavascript您也需要将其放入此目录中。
如果您的页面包含 CSSJavaScript您也需要将其放入此目录中。
## JavaScript API
如果只是一个显示页面那它和普通网页没有什么区别。更重要的是KernelSU 提供了一系列的系统API可以让您实现模块特有的功能。
KernelSU 提供了一个 Javascript 库并[在 npm 上发布](https://www.npmjs.com/package/kernelsu),您可以在网页的 javascript 代码中使用它。
KernelSU 提供了一个 JavaScript 库并[在 npm 上发布](https://www.npmjs.com/package/kernelsu),您可以在网页的 JavaScript 代码中使用它。
例如,您可以执行 shell 命令来获取特定配置或修改属性:
```javascript
```JavaScript
import { exec } from 'kernelsu';
const { errno, stdout } = await exec("getprop ro.product.model");

View File

@@ -0,0 +1,118 @@
# App Profile
App Profile 是 KernelSU 提供的一種針對各種應用程式自訂其使用配置的機制。
對於授予了root 權限(也即可以使用 `su`的應用程式來說App Profile 也可以稱為Root Profile它可以自訂 `su``uid`, `gid` , `groups` , ` capabilities` 以及 `SELinux context` 規則,從而限制 root 使用者的權限;例如可以針對防火牆應用程式僅授予網路權限,而不授予檔案存取權限,針對凍結類別應用程式僅授予 shell 權限而不是直接給 root ;透過最小化權限原則**把權力關進籠子裡**。
對於沒有被授予 root 權限的普通應用App Profile 可以控制核心以及模組系統對此應用的行為;例如是否需要針對此應用程式卸載模組造成的修改等。核心和模組系統可以透過此配置決定是否要做一些類似「隱藏痕跡」類別的操作。
## Root Profile
### UID、GID 和 groups
Linux 系統中有使用者和群組兩個概念。每個使用者都有一個使用者 ID(UID),一個使用者可以屬於多個群組,每個群組也有群組 ID(GID)。此 ID 用於識別系統的使用者並確定使用者可以存取哪些系統資源。
UID 為 0 的使用者稱為 root 使用者GID 為 0 的群組稱為 root 群組root 使用者群組通常擁有系統的最高權限。
對於 Android 系統來說,每個應用程式都是一個單獨的使用者(不考慮 share uid 的情況),擁有一個唯一的 UID。例如 `0` 是 root 使用者,`1000``system``2000` 是 ADB shell10000-19999 的是一般使用者。
:::info
此處的 UID 跟 Android 系統的多使用者或者說工作資料Work Profile不是概念。工作資料實際上是對 UID 進行分片實現的,例如 10000-19999 是主使用者110000-119999 是工作資料;他們中的任何一個普通應用都擁有自己獨有的 UID。
:::
每一個應用程式可以有若干個群組GID 使其主要的群組,通常與 UID 一致;其他的群組稱為補充群組(groups)。某些權限是透過群組控制的,例如網路訪問,藍牙等。
例如,如果我們在 ADB shell 中執行 `id` 指令,會得到以下輸出:
```sh
oriole:/ $ id
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),1078(ext_data_ww) (ext_obb_rw),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid),3012(readreadtracefs:s05:
```
其中UID 為`2000`GID 也即主要組ID 也為`2000`;除此之外它還在許多補充組裡面,例如`inet` 組代表可以創建`AF_INET``AF_INET6` 的socket存取網路`sdcard_rw` 代表可以讀寫sdcard 等。
KernelSU 的 Root Profile 可以自訂執行 `su` 後 root 程式的 UID, GID 和 groups。例如你可以設定某個 root 應用程式的Root Profile 其UID 為`2000`,這表示此應用程式在使用`su` 的時候它的實際權限是ADB Shell 等級你可以去掉groups 中的`inet` ,這樣這個`su` 就無法存取網路。
:::tip 注意
App Profile 只是控制 root 應用程式使用 `su` 後的權限,它並非控制應用程式本身的權限!如果應用程式本身申請了網路存取權限,那麼它即使不使用 `su` 也可以存取網路;為 `su` 去掉 `inet` 群組只是讓 `su` 無法存取網路。
:::
與應用程式透過 `su` 主動切換使用者或群組不同Root Profile 是在核心中強制實施的,不依賴 root 應用程式的自覺行為,`su` 權限的授予完全取決於使用者而非開發者。
### Capabilities
Capabilities 是 Linux 的一種分權機制。
傳統的 UNIX 系統為了執行權限檢查,將流程分為兩類:特權程式(其有效使用者 ID 為 0稱為超級使用者或 root和非特權程式其有效 UID 為非零。特權程式會繞過所有核心權限檢查而非特權程式則根據其憑證通常是有效UID、有效GID和補充群組清單進行完整的權限檢查。
從 Linux 2.2開始Linux 將傳統上與超級使用者關聯的特權分解為獨立的單元,稱為 Capabilities有的也翻譯為「權能」它們可以獨立啟用和停用。
每一個 Capability 代表一個或一類權限。例如 `CAP_DAC_READ_SEARCH` 就代表是否有能力繞過檔案讀取權限檢查和目錄讀取和執行權限檢查。如果一個有效 UID 為 `0` 的使用者root 使用者)沒有 `CAP_DAC_READ_SEARCH` 或更高 Capalities這表示即使它是 root 也不能​​隨意讀取檔案。
KernelSU 的 Root Profile 可以自訂執行 `su` 後 root 程式的 Capabilities從而實現只授予「部分 root 權限」。與上面介紹的UID, GID 不同,某些 root 應用就是需要 `su` 後 UID 是 `0`,此時我們可以透過限制這個 UID 為 `0` 的 root 使用者的 Capabilities就可以限制它能夠執行的操作。
:::tip 強烈建議
Linux 系統關於 Capability 的[官方文件](https://man7.org/linux/man-pages/man7/capabilities.7.html)解釋了每一項Capability 所代表的能力寫的非常詳細如果你想要自訂Capabilities請務必先閱讀此文件。
:::
### SELinux
SELinux 是一種強大的強制權限存取控制MAC機制。它按照**預設拒絕**的原則運作:任何未經明確允許的行為都會被拒絕。
SELinux 可依兩種全域模式運作:
1. 寬容模式:權限拒絕事件會被記錄下來,但不會被強制執行。
2. 強制模式:權限拒絕事件會被記錄下來**並且**強制執行。
:::warning 警告
現代的 Android 系統極度依賴 SELinux 來保障整個系統的安全性,我們強烈建議您不要使用任何以「寬容模式」運作的自訂系統,因為那樣與裸奔沒什麼區別。
:::
SELinux 的完整概念比較複雜,我們這裡不打算講解它的具體運作方式,建議你先透過以下資料來了解其運作原理:
1. [wikipedia](https://en.wikipedia.org/wiki/Security-Enhanced_Linux)
2. [Redhat: what-is-selinux](https://www.redhat.com/en/topics/linux/what-is-selinux)
3. [ArchLinux: SELinux](https://wiki.archlinux.org/title/SELinux)
KernelSU 的 Root Profile 可以自訂執行 `su` 後 root 程式的 SELinux context並且可以針對這個 context 設定特定的存取控制規則,從而更精細地控制 root 權限。
通常情況下,應用程式執行 `su` 後,會將進程切換到一個**不受任何限制** 的SELinux 域,例如`u:r:su:s0`,透過 Root Profile我們可以將它切換到一個自訂的網域例如 `u:r:app1:s0`,然後為這個網域制定一系列規則:
```sh
type app1
enforce app1
typeattribute app1 mlstrustedsubject
allow app1 * * *
```
注意:此處的 `allow app1 * * *` 僅僅作為演示方便而使用,實際過程中不應使用這個規則,因為它跟 permissive 區別不大。
### 逃逸
如果 Root Profile 的配置不合理那麼可能會發生逃逸的情況Root Profile 的限制會意外失效。
例如如果你為ADB shell 使用者設定允許root 權限這是相當常見的情況然後你給某個普通應用程式允許root 權限但是配置它的root profile 中的UID 為2000ADB shell 使用者的UID那麼此時這個App 可以透過執行兩次 `su` 來獲得完整的root 權限:
1. 第一次執行 `su`,由於 App Profile 強制生效,會正常切換到 UID 為 `2000(adb shell)` 而非 `0(root)`
2. 第二次執行 `su`,由於此時它 UID 是 `2000`,而你給 `2000(adb shell)` 配置了允許 root它會獲得完整的 root 權限!
:::warning 注意
這是完全符合預期的行為,並非 BUG因此我們建議
如果你的確需要給 adb 授予 root 權限(例如你是開發者),那麼不建議你在配置 Root Profile 的時候將 UID 改成 `2000`,用 `1000(system)` 會更好。
:::
## Non Root Profile
### 卸載模組
KernelSU 提供了一種 systemless 的方式來修改系統分區,這是透過掛載 overlayfs 來實現的。但有些情況下App 可能會對這種行為比較敏感;因此,我們可以透過設定「卸載模組」來卸載掛載在這些應用程式上的模組。
另外KernelSU 管理器的設定介面還提供了一個「預設卸載模組」的開關,這個開關預設是**開啟**的,這表示**如果不對應用程式做額外的設定**,預設情況下 KernelSU 或某些模組會對此應用程式執行卸載操作。當然,如果你不喜歡這個設定或這個設定會影響某些 App你可以有以下選擇
1. 保持「預設卸載模組」的開關,然後針對不需要「卸載模組」的應用程式進行單獨的設置,在 App Profile 中關閉「卸載模組」;(相當於「白名單」)。
2. 關閉「預設卸載模組」的開關,然後針對需要「卸載模組」的應用程式進行單獨的設置,在 App Profile 中開啟「卸載模組」;(相當於「黑名單」)。
:::info
KernelSU 在 5.10 及以上內核上,內核會執行“卸載模組”的操作;但在 5.10 以下的設備上這個開關僅僅是一個“設定”KernelSU 本身不會做任何動作,一些模組(如 Zygisksu 會透過這個模組決定是否需要卸載)
:::

View File

@@ -1,4 +1,4 @@
# KernelSU 模組與 Magisk 的差異 {#title}
# KernelSU 與 Magisk 的差異 {#title}
儘管 KernelSU 模組和 Magisk 模組之間有許多相似之處,但由於它們完全不同的實作機制,不可避免地存在一些差異;如果您想讓您的模組同時在 Magisk 和 KernelSU 上運作,那麼您必須瞭解這些差異。
@@ -6,9 +6,9 @@
- 模組檔案格式:都以 Zip 的格式組織模組,並且模組的格式幾乎相同
- 模組安裝目錄:都位於 `/data/adb/modules`
- Systemless都支援過模組的形式以 systemless 修改 /system
- `post-fs-data.sh`:執行時間和語義完全相同
- `service.sh`:執行時間和語義完全相同
- Systemless都支援過模組以無系統修改的方式來更改 `/system`
- `post-fs-data.sh`:執行階段和語義完全相同
- `service.sh`:執行階段和語義完全相同
- `system.prop`:完全相同
- `sepolicy.rule`:完全相同
- BusyBox指令碼在 BusyBox 中以「獨立模式」執行
@@ -24,3 +24,5 @@
3. KernelSU 模組取代或刪除檔案與 Magisk 完全不同。KernelSU 不支援 `.replace` 方法,相反,您需要透過 `mknod filename c 0 0` 建立相同名稱的資料夾以刪除對應檔案。
4. BusyBox 的目錄不同KernelSU 內建的 BusyBox 在 `/data/adb/ksu/bin/busybox` 而 Magisk 在 `/data/adb/magisk/busybox`**注意此為 KernelSU 內部行為,未來可能會變更!**
5. KernelSU 不支援 `.replace` 檔案;但 KernelSU 支援 `REPLACE``REMOVE` 變數以移除或取代檔案 (資料夾)。
6. KernelSU 新增了 `boot-completed` 階段以在啟動完成時執行一些腳本。
7. KernelSU 新增了 `post-mount` 階段,以便在掛載 overlayfs 後執行一些腳本

View File

@@ -55,13 +55,26 @@ KernelSU 的模組系統與 Magisk 的 magic mount 存在衝突,如果在 Kern
核心版本與 Android 版本無關,如果您要刷新 KernelSU請一律使用**核心版本**而非 Android 版本,如果你為 "android12-5.10" 的裝置刷新 Android 13 的核心,等候您的將會是開機迴圈。
## 我是 GKI1.0,能用 KernelSU 嗎?
GKI1 與 GKI2 完全不同,所以您需要自行編譯核心。
## KernelSU 支援 --mount-master/全域掛接命名空間嗎?
目前沒有 (未來可能會支援),但實際上有很多種方法手動進入全域命名空間,無需 Su 內建支援,比如:
目前沒有 (未來可能會支援),但實際上有很多種方法手動進入全域命名空間,無需 `su` 內建支援,比如:
1. `nsenter -t 1 -m sh` 可以取得一個全域 mount namespace 的 shell.
2. 在您要執行的命令前新增 `nsenter --mount=/proc/1/ns/mnt` 即可使此命令在全域 mount namespace 下執行。KernelSU 本身也使用了 [這種方法](https://github.com/tiann/KernelSU/blob/77056a710073d7a5f7ee38f9e77c9fd0b3256576/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt#L115)
## 我是 GKI1.0,能用 KernelSU 嗎
## KernelSU 可以修改 Hosts 嗎? 我要怎麼使用 AdAway
當然。但是 KernelSU 沒有內建的 Hosts 支持,您可以安裝 [systemless-hosts](https://github.com/symbuzzer/systemless-hosts-KernelSU-module) 來做到這一點。
GKI1 與 GKI2 完全不同,所以您需要自行編譯核心。
## 為什麼會有 1TB 的龐大檔案?
1 TB 大小的 `modules.img` 是一個磁碟映像文件不用擔心它的大小它是一種特殊類型的文件稱為稀疏檔案它的實際大小只有你使用的模組的大小並且在你刪除模組後會動態縮小實際上並沒有佔用1TB的磁碟空間實際上你的手機可能並沒有那麼多空間
如果你確實對檔案的大小不滿意,可以使用 `resize2fs -M` 指令將其調整為實際大小;但此時模組可能無法正常運作,我們不會這種情況提供任何支援。
## 為什麼我的設備顯示錯誤的儲存空間大小?
某些裝置使用非正規方法來計算裝置的儲存大小,可能會導致系統應用程式的儲存空間計算不準確,特別是在處理 1 TB 的稀疏檔案時。雖然這個問題似乎是三星設備特有的,僅影響三星應用程式和服務,但必須注意的是,差異主要在於總儲存大小,可用空間運算仍然準確。

View File

@@ -1,7 +1,7 @@
# 隱藏功能
## ksurc
## .ksurc
預設狀況下,`/system/bin/sh` 會載入 `/system/etc/mkshrc`
可以透過建立 `/data/adb/ksu/.ksurc` 檔案來讓 Su 載入此檔案而非 `/system/etc/mkshrc`
可以透過建立 `/data/adb/ksu/.ksurc` 檔案來讓 `su` 載入此檔案而非 `/system/etc/mkshrc`

View File

@@ -40,6 +40,14 @@ LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
tools/bazel build --config=fast //common:kernel_aarch64_dist
```
:::info
對於某些 Android 14 內核,要使 Wi-Fi/藍牙正常工作,可能需要刪除所有受 GKI 保護的匯出:
```sh
rm common/android/abi_gki_protected_exports_*
```
:::
## 使用 KernelSU 建置核心
如果您可以成功建置核心,那麼建置 KernelSU 就會非常輕鬆,依自己的需求在核心原始碼根目錄中執行以下任一命令:

Some files were not shown because too many files have changed in this diff Show More