234 Commits

Author SHA1 Message Date
ShirkNeko
56b4664ec7 Optimized the UI of the slot selection dialog box, added separator lines and button styles, and improved the display logic of the current slot. 2025-04-27 22:37:56 +08:00
ShirkNeko
70f7c75a92 Add custom color and transparency settings to the top app bar 2025-04-27 20:32:17 +08:00
ShirkNeko
e414b4de92 Adding a localized message for a failed swipe 2025-04-27 20:27:29 +08:00
ShirkNeko
79e68f473f Update Chinese and English strings, change Anykernel3 related descriptions to generic Kernel descriptions. 2025-04-27 20:21:17 +08:00
ShirkNeko
6656604809 Add the function of obtaining and restoring the original slot, and display the current slot information in the slot selection dialog box
-It should be possible to fix the issue of selecting slot positions

Signed-off-by: ShirkNeko 109797057+ShirkNeko@users.noreply.github.com
2025-04-27 19:35:55 +08:00
ShirkNeko
85b4d11912 Improve the ui and function of the anykernel3 flashing interface.
- Add self-selected brushwrite A/B slot (not perfect)

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-04-27 18:01:45 +08:00
ShirkNeko
7769a23f59 Opt the image editing dialog box, add full-screen zoom function, improve the panning limit to ensure the security of image transformation. 2025-04-27 14:02:16 +08:00
ShirkNeko
c442f43090 Add free adjustment of image position when selecting background
- Updated AGP version to 8.9.2.
- Added support for Android 16 (36).
- Replaced the new API and fixed some minor bugs.

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-04-27 04:05:49 +08:00
WenHao2130
d73670bf43 [skip ci]: manager: update simplified chinese translation (#59)
Signed-off-by: WenHao2130 <WenHao2130@outlook.com>
2025-04-26 15:42:08 +08:00
ShirkNeko
dd1967f0d0 Update the README file to include a thank you message to DARKWWEE. 2025-04-26 15:41:23 +08:00
ShirkNeko
e3d2fc64ac Update the minimum supported kernel version and standardize kernel-related constants to 12800. 2025-04-25 14:03:16 +08:00
ShirkNeko
e07f20bf29 [skip ci]: Update kpm module display logic, fix installed and uninstalled name display 2025-04-25 14:00:18 +08:00
ShirkNeko
34f216181f Expose the getSuSFSDaemonPath method to support the installation of the SuSFSD daemon. 2025-04-23 23:06:19 +08:00
ShirkNeko
8aef775474 Opt InfoCard component, add icon support, improve information presentation 2025-04-23 22:23:02 +08:00
ShirkNeko
f669ad92b6 Refactor Kpm.kt, optimize file type checking logic, add ELF file detection, simplify string command execution 2025-04-23 21:06:21 +08:00
ShirkNeko
cc0b272770 Modified Kpm file type validation and check module ID extraction logic
Fix the problem that the specified kpm module could not be deleted after uninstallation due to a mismatch between the file type and the actual module name.

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-04-23 03:33:46 +08:00
ShirkNeko
9ea6de340d Refactor the namespace to com.sukisu.ultra, add IKsuInterface and LatestVersionInfo data classes, and remove obsolete classes and methods. 2025-04-23 02:24:55 +08:00
Qumolama.d
be37f8a2a3 Update English translation and remove unused keys (#56)
* Remove untranslatable keys

* Fix English translations

* Remove unused string entry

---------

Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-04-22 13:39:28 +08:00
Re*Index. (ot_inc)
8a12fac39f [skip ci]: fix typo & update README (#55)
* Update strings.xml

* fix typo

* Update README-en.md

* Update README-ja.md
2025-04-22 13:37:17 +08:00
Re*Index. (ot_inc)
0242fe12e3 Update Japanese & fix text(#54) 2025-04-21 20:15:08 +08:00
ShirkNeko
acf2e1a5ec Update KSU_GIT_VERSION to use the master branch count and change the KernelSU manager name to SukiSU 2025-04-21 17:33:29 +08:00
ShirkNeko
626db4be56 [skip ci]: Adding a check for LKM mode to the KPM info card 2025-04-21 09:20:38 +08:00
ShirkNeko
5941fa1ec7 manager: hide root-related features if kernelsu version null (#71)
Related PR:
tiann#2483

Also attempting to address this:
tiann#2483 (comment)

Co-authored-by: rsuntk <rsuntk@yukiprjkt.my.id>
Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-04-20 22:30:23 +08:00
ShirkNeko
1dd8651a1a Remove unused functions and data classes and optimize code structure; update string resources to support new features 2025-04-20 22:01:35 +08:00
ShirkNeko
33dd0ca16b Add check for GKI version and KERNEL_TYPE setting 2025-04-19 21:44:41 +08:00
Re*Index. (ot_inc)
dfe7852c25 fix typo (#50)
* Update Japanese!

* Fix typo

* Fix typo 2

* Update strings.xml

* Update strings.xml

* Update Japanese.

* Update strings.xml

* Add Japanese README

* Update README-ja.md

* Update README-ja.md

* Update README-ja.md

* Update README-ja.md

* Update README-ja.md

* Update README-en.md

* Update README-ja.md

* Update README-en.md

* Update README.md

* Update README-ja.md

* fix typo

* Update strings.xml
2025-04-19 16:03:25 +08:00
Re*Index. (ot_inc)
abe6184f63 Update READMEs. (#49)
* Update Japanese!

* Fix typo

* Fix typo 2

* Update strings.xml

* Update strings.xml

* Update Japanese.

* Update strings.xml

* Add Japanese README

* Update README-ja.md

* Update README-ja.md

* Update README-ja.md

* Update README-ja.md

* Update README-ja.md

* Update README-en.md

* Update README-ja.md

* Update README-en.md

* Update README.md

* Update README-ja.md
2025-04-18 20:16:31 +08:00
Re*Index. (ot_inc)
e8afb2143b Japanese Translate (#48)
* Update Japanese!

* Fix typo

* Fix typo 2

* Update strings.xml

* Update strings.xml

* Update Japanese.

* Update strings.xml

* Add Japanese README

* Update README-ja.md

* Update README-ja.md
2025-04-17 17:23:26 +08:00
ShirkNeko
7e1c363bad Add module uninstallation confirmation dialog and file type validation message 2025-04-16 14:36:27 +08:00
Kousei
bcf6809deb Add files via upload (#47) 2025-04-16 13:21:43 +08:00
ShirkNeko
bce5f6cf61 Fix GitHub link in About card 2025-04-15 15:09:36 +08:00
ShirkNeko
5495eebb38 Update version number 2025-04-14 20:18:52 +08:00
ShirkNeko
d255801666 Update version number 2025-04-14 20:18:24 +08:00
ShirkNeko
44f2601126 Update version number 2025-04-14 20:18:10 +08:00
ShirkNeko
3a7366c4bc Update version number 2025-04-14 20:18:04 +08:00
ShirkNeko
5da289714d Update version number 2025-04-14 20:17:51 +08:00
ShirkNeko
a18d718744 Update version number 2025-04-14 20:17:44 +08:00
ShirkNeko
c90fc461d9 Update version number 2025-04-14 20:17:37 +08:00
ShirkNeko
e80fbe8934 Update version number 2025-04-14 20:17:32 +08:00
ShirkNeko
e0650ade4f Update version number 2025-04-14 20:17:16 +08:00
ShirkNeko
378b8458f2 Opt device disabling logic in HomeScreen 2025-04-14 19:30:30 +08:00
ShirkNeko
182028d9ea Updating the downloader to use the new GitHub publishing interface 2025-04-14 19:06:54 +08:00
ShirkNeko
0c3b8e7610 1 2025-04-14 18:46:51 +08:00
ShirkNeko
332fdcd2a7 Fix KPM module loading calls to explicitly use namespaces 2025-04-14 15:55:35 +08:00
ShirkNeko
aa20d04d3a Refactoring KPM module loading logic, removing existing KPM loading functions, simplifying code and enhancing error handling 2025-04-14 15:51:47 +08:00
ShirkNeko
949106bc09 Refactoring KPM module loading logic, adding modification event handling, improving error handling 2025-04-14 15:39:43 +08:00
ShirkNeko
67babc2858 Enhanced KPM module loading logic, added filename validation and error handling 2025-04-14 15:35:11 +08:00
ShirkNeko
d087ec510e Ksud: refactor KPM event handling logic, add error handling and ensure KPM catalog exists 2025-04-14 15:26:30 +08:00
gggggdw
c2ed3da87c Update Engish translation (#46) 2025-04-14 14:39:04 +08:00
ShirkNeko
f2d159f732 Updated README file to add KPM support information and remove duplicate feature descriptions 2025-04-14 14:38:27 +08:00
ShirkNeko
468fc2207d Fixed integration note on susfs in README.md 2025-04-14 02:05:34 +08:00
Qumolama.d
eced8bae82 Fix typos and translations in README.md and README-en.md (#23)
* Fix typos in README.md

* Fix various translation error in README-en.md

* Update links in README-en.md
2025-04-14 02:04:35 +08:00
Wang Han
e61ecb3963 Fix opuls -> oplus typo (#2536) 2025-04-13 18:14:27 +08:00
Kousei
304f4f8b2c Update Vietnamese language translation (#44)
* Add files via upload

* Add files via upload
2025-04-13 16:00:30 +08:00
ShirkNeko
48888087e1 Fix branch checking in build-manager.yml by changing 'susfs' to 'main' 2025-04-13 15:41:33 +08:00
crazymrli
75a70e70be Merge pull request #43 from ShirkNeko/dev
Dev
2025-04-13 15:25:59 +08:00
liankong
cf825f912c 添加KPM模板说明 2025-04-13 15:22:22 +08:00
liankong
1944a49fd8 添加super_access对task_struct的支持 2025-04-13 14:41:06 +08:00
liankong
657f343f5c 添加super_access对task_struct的支持 2025-04-13 14:18:33 +08:00
ShirkNeko
74bb90b3d8 Update build-lkm.yml to force uploading inputs to be required, default value changed to true 2025-04-13 02:26:26 +08:00
Kousei
8bf9828c80 Add files via upload (#42) 2025-04-13 01:43:55 +08:00
ShirkNeko
06324def38 更新super_access.c to remove redundant macro definitions and optimize conditional compilation; use resource strings instead of hard-coded text in MoreSettings.kt; add theme color string resource in strings.xml 2025-04-13 01:33:40 +08:00
ShirkNeko
70259a5ec5 清理super_access.c中的多余空行 2025-04-12 17:00:14 +08:00
ShirkNeko
a63057c594 Merge branch 'dev' into main 2025-04-12 16:55:20 +08:00
ShirkNeko
48d5270611 Add super_access function to support dynamic access to structures and members 2025-04-12 16:52:13 +08:00
ShirkNeko
712d0f3342 更新super_access.c以支持不同内核版本的结构体成员定义 2025-04-12 16:29:13 +08:00
ShirkNeko
d6084aeca1 在super_access.c中添加对linux/version.h的引用,并定义KERNEL_VERSION_6_6宏,以支持不同内核版本的条件编译 2025-04-12 16:09:13 +08:00
ShirkNeko
562b9624d7 移除对linux/nsproxy.h的引用,并添加对../fs/mount.h的引用 2025-04-12 15:49:00 +08:00
ShirkNeko
a68d5e8bbe 在super_access.c中添加对linux/nsproxy.h的引用 2025-04-12 15:02:22 +08:00
ShirkNeko
d45aa8197e Remove unnecessary header file fs/mount.h and use linux/mount.h instead. 2025-04-12 14:54:10 +08:00
ShirkNeko
314d3ef97a 更新Makefile以使用super_access.o替代super_access.c 2025-04-12 14:44:50 +08:00
liankong
e3750ccd51 完善super_access 2025-04-12 10:35:08 +08:00
liankong
a712efe9d8 添加super_access机制 2025-04-12 00:34:37 +08:00
ShirkNeko
2266362e24 Changing the package name 2025-04-12 00:33:46 +08:00
ShirkNeko
b7056b5baa Changing the package name 2025-04-12 00:33:29 +08:00
liankong
8bd07bf56c Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-04-11 15:19:48 +08:00
liankong
569183efe9 添加super_access机制 2025-04-11 15:19:18 +08:00
crazymrli
937cf25e9b Merge pull request #38 from ShirkNeko/main
Merge to dev
2025-04-11 15:17:01 +08:00
Kousei
e2b6617577 Add String-vi (#36)
Add Vietnamese language translation
2025-04-10 23:42:28 +08:00
Re*Index. (ot_inc)
8323d6394b Fix Japanese Translate! (#35)
* Update Japanese!

* Fix typo

* Fix typo 2

* Update strings.xml

* Update strings.xml
2025-04-10 23:39:02 +08:00
Re*Index. (ot_inc)
e31b892a20 Update Japanese Translate! (#34)
* Update Japanese!

* Fix typo
2025-04-10 15:48:24 +08:00
ShirkNeko
65b1518e26 Opt KPM module installation to support URL-encoded filenames and extract module IDs for dynamically constructed names.
This will fix the flashback issue due to path issue

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-04-10 01:25:59 +08:00
ShirkNeko
865dbd3799 feat: Load Existing KPM Modules on KPM Monitor Startup 2025-04-08 19:03:29 +08:00
ShirkNeko
7de9d7967a Add embedded and load-optimized KPM module installation and uninstallation prompt messages 2025-04-08 18:56:14 +08:00
ShirkNeko
aa2d2454e1 feat: 引入anyhow库以增强错误处理能力 2025-04-07 22:33:45 +08:00
ShirkNeko
b850336872 feat: Optimize KPM file processing logic, use is_some_and method to simplify extension checking, and enhance readability and stability of event processing. 2025-04-07 22:28:22 +08:00
ShirkNeko
138dec35c7 feat: Add KPM module management function, support loading, unloading and deleting all KPM modules. 2025-04-07 22:13:21 +08:00
ShirkNeko
569fffa962 feat: 引入notify库以支持KPM目录的文件监控 2025-04-07 21:40:25 +08:00
ShirkNeko
2a283e6793 feat: 修复unload_kpm函数的定义,添加缺失的fn关键字 2025-04-07 21:35:44 +08:00
ShirkNeko
73a7ba3ac9 feat: 将KPM相关函数公开以支持外部调用 2025-04-07 21:32:41 +08:00
ShirkNeko
c0c4ea9f86 feat: 引入KPM模块以支持文件系统数据处理 2025-04-07 21:26:08 +08:00
ShirkNeko
a13179cd09 feat: 添加KPM模块以支持KPM相关功能 2025-04-07 21:21:36 +08:00
ShirkNeko
de089b7b73 feat: Adding a KPM monitor to handle KPM file creation and deletion events 2025-04-07 21:18:14 +08:00
ShirkNeko
1b700fb8e0 Merge pull request #33 from ShirkNeko/dev
增加了两个必要的兼容函数,以确保能够运行Peekaboo.KPM
2025-04-07 19:48:03 +08:00
liankong
f0febf13f2 增加了两个必要的兼容函数,以确保能够运行Peekaboo.KPM 2025-04-07 19:43:06 +08:00
ShirkNeko
a002967a92 Refactor dialog implementation, replace ListDialog with AlertDialog to optimize user interaction experience.
This will improve fixing KMI and restoring init_boot image style issues
2025-04-07 16:56:40 +08:00
ShirkNeko
1167b20d89 Merge pull request #32 from ShirkNeko/dev 2025-04-07 15:40:48 +08:00
ShirkNeko
297ff3ae90 Dev (#31)
* 添加安装工具类,并增加把kpmmgr安装到/data/adb/ksu/bin的功能,方便模块调用

* Update README

* Update README

---------

Co-authored-by: liankong <xhsw.new@qq.com>
2025-04-07 15:38:04 +08:00
liankong
a39e2ce15a Update README 2025-04-07 15:36:46 +08:00
liankong
78eda275d6 Update README 2025-04-07 15:35:21 +08:00
ShirkNeko
1fce0fd77d 添加安装工具类,并增加把kpmmgr安装到/data/adb/ksu/bin的功能,方便模块调用 (#30)
Co-authored-by: liankong <xhsw.new@qq.com>
2025-04-07 15:17:46 +08:00
liankong
c942393f21 添加安装工具类,并增加把kpmmgr安装到/data/adb/ksu/bin的功能,方便模块调用 2025-04-07 15:09:05 +08:00
ShirkNeko
040cc30e73 manager: Updates the sub-levels and patch levels of the Android version 2025-04-06 13:54:57 +08:00
ShirkNeko
6b75ffc928 Delete .github/dependabot.yml 2025-04-06 00:24:46 +08:00
WenHao2130
a7f1b21f91 manager: move more_settings_simplicity_mode to custom_settings (#28)
Signed-off-by: WenHao2130 <WenHao2130@outlook.com>
2025-04-05 15:28:09 +08:00
WenHao2130
c8f7d9d5bc manager: beta -> Beta (#27)
Signed-off-by: WenHao2130 <WenHao2130@outlook.com>
2025-04-05 11:46:41 +08:00
WenHao2130
2520d45dc4 manager: add some animation (#26)
Signed-off-by: WenHao2130 <98936399+WenHao2130@users.noreply.github.com>
2025-04-05 02:59:25 +08:00
WenHao2130
8bdf8d98c3 manager: allow hide susfs status information
Signed-off-by: WenHao2130 <WenHao2130@outlook.com>
2025-04-04 21:32:06 +08:00
WenHao2130
04025f3d32 manager: allow hide some other information
Signed-off-by: WenHao2130 <WenHao2130@outlook.com>
2025-04-04 21:32:06 +08:00
ShirkNeko
4c7ed9c8ee 修正 SukiSU Ultra 展望字符串中的空格格式 2025-04-04 18:01:57 +08:00
ShirkNeko
c36f8b0df3 Opt the transparency slider display logic with AnimatedVisibility and update the formatting in the Chinese string resource.
Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-04-04 17:51:27 +08:00
WenHao2130
5ccec93940 manager: Use AnimatedVisibility for Exclude Modifications SwitchItem
Co-authored-by: Light summer <93428659+lightsummer233@users.noreply.github.com>
Co-authored-by: WenHao2130 <WenHao2130@outlook.com>
Signed-off-by: WenHao2130 <WenHao2130@outlook.com>
2025-04-04 17:24:36 +08:00
ShirkNeko
14b15e18f3 Update README.md 2025-04-04 15:49:21 +08:00
ShirkNeko
3e6f3b4d80 Merge pull request #22 from WenHao2130/main
ksud: update banner
2025-04-04 15:04:48 +08:00
WenHao2130
470b3106cb ksud: update banner
Signed-off-by: WenHao2130 <WenHao2130@outlook.com>
2025-04-04 15:00:55 +08:00
ShirkNeko
e0074bc3ab Docs: simplify susfs branch usage instructions, remove redundant information 2025-04-04 14:27:07 +08:00
ShirkNeko
8d04ecdc52 Adjust card transparency and shadow settings to improve visualization 2025-04-04 13:44:21 +08:00
ShirkNeko
c83b1e88b9 Merge pull request #19 from ShirkNeko/dev
Dev
2025-04-03 23:34:01 +08:00
ShirkNeko
0446cc499e Merge branch 'main' into dev 2025-04-03 23:33:04 +08:00
ShirkNeko
8a6507e834 Manager: Add clean mode switch and option to hide kernel version number 2025-04-03 23:23:05 +08:00
ShirkNeko
09893b9472 Merge pull request #18 from WenHao2130/main
manager: optimize layout
2025-04-03 16:07:38 +08:00
WenHao2130
13d2290205 manager: optimize layout
Signed-off-by: WenHao2130 <WenHao2130@outlook.com>
2025-04-03 15:59:00 +08:00
ShirkNeko
46e4c85563 Add missing package names 2025-04-02 22:58:13 +08:00
ShirkNeko
d925036bd6 Optimize the error handling logic for loading and unloading modules, this will fix the file type error, and more detail handling-2 2025-04-02 17:59:59 +08:00
ShirkNeko
fbf2799674 Opt the error handling logic for loading and unloading modules, this will fix the file type error, and more detail handling 2025-04-02 17:59:29 +08:00
liankong
12c7558b91 给WebUI添加对KPM的操控支持 2025-04-02 14:03:50 +08:00
ShirkNeko
d55af76260 Optimize the error handling logic for loading and unloading modules, this will fix the file type error, and more detail handling-2 2025-04-02 00:36:08 +08:00
ShirkNeko
e4c70e2efb Opt the error handling logic for loading and unloading modules, this will fix the file type error, and more detail handling 2025-04-02 00:35:55 +08:00
ShirkNeko
a971fee132 Revert "Add KPM module count display to the main page and update related string resources"
This reverts commit a45e0f78ef.
2025-04-01 17:27:51 +08:00
ShirkNeko
a45e0f78ef Add KPM module count display to the main page and update related string resources 2025-04-01 17:24:13 +08:00
ShirkNeko
bf07dcb9ea Merge pull request #14 from ShirkNeko/dev
Add input dialogs to the KPM module to optimize control logic and dis…
2025-04-01 17:05:09 +08:00
ShirkNeko
e5f5b8f831 Add input dialogs to the KPM module to optimize control logic and display Snackbar prompts for operation results. 2025-04-01 17:04:00 +08:00
ShirkNeko
a80513fc50 Merge pull request #13 from ShirkNeko/dev
Dev
2025-04-01 16:18:17 +08:00
ShirkNeko
f71de1742a 优化KPM模块列表的获取逻辑,添加定时刷新功能,并调整空状态显示 2025-04-01 16:16:56 +08:00
ShirkNeko
57c65fdcda 移除调试签名配置,优化KPM版本显示逻辑,添加KPM控制成功和失败的提示字符串 2025-04-01 16:09:18 +08:00
ShirkNeko
0a9cf5f9aa Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-04-01 15:56:40 +08:00
ShirkNeko
08fdf2bdad 为KPM模块添加控制功能,显示操作成功或失败的Snackbar提示,并优化模块信息获取逻辑 2025-04-01 15:56:30 +08:00
liankong
690c6bac38 Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-04-01 15:54:03 +08:00
liankong
af81308097 修复获取不到完整信息的bug 2025-04-01 15:53:58 +08:00
ShirkNeko
90b79f5c04 将KPM模块的Snackbar持续时间从长改为短,以改善用户体验 2025-04-01 15:04:09 +08:00
liankong
0c5dcec7bc 尝试修复KPM信息解析 2025-04-01 14:57:22 +08:00
ShirkNeko
a30dfbc15d 优化KPM模块加载和卸载逻辑,改进错误处理,增强模块信息获取功能 2025-04-01 14:34:24 +08:00
ShirkNeko
52f3335977 Merge pull request #12 from ShirkNeko/dev
Dev
2025-04-01 13:56:08 +08:00
liankong
c8b3e953ad Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-04-01 12:29:06 +08:00
liankong
e9d0526e1b 1 2025-04-01 12:28:59 +08:00
ShirkNeko
408c3be675 优化Kpm模块相关函数的返回类型,改为Boolean或Int以提高可读性和准确性 2025-04-01 02:40:58 +08:00
ShirkNeko
f67f16733f 优化KPM模块加载逻辑,更新相关字符串资源,调整KPM版本显示条件 2025-03-31 23:22:03 +08:00
ShirkNeko
25a173ad7b Merge pull request #8 from ShirkNeko/dev
Dev
2025-03-31 22:34:09 +08:00
ShirkNeko
75ec88f7a7 Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-31 22:33:23 +08:00
ShirkNeko
e9c5ffb430 优化HomeScreen,移除KPM状态检查,调整ContributionCard显示逻辑 2025-03-31 22:33:20 +08:00
ShirkNeko
cb2cdaed12 删除KernelConfigUtils.kt文件 2025-03-31 22:32:57 +08:00
liankong
029c7f1e2a Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-31 22:32:48 +08:00
liankong
5a8d6895fa 添加加载成功提示 2025-03-31 22:32:42 +08:00
ShirkNeko
bb11e23006 Merge pull request #7 from ShirkNeko/dev
Dev
2025-03-31 22:12:28 +08:00
ShirkNeko
37b00d49c8 添加KPM状态检查功能,并更新相关UI文本 2025-03-31 22:11:52 +08:00
liankong
8055aed507 Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-31 20:28:31 +08:00
liankong
e7cef05c6a 修复 2025-03-31 20:28:26 +08:00
ShirkNeko
313746b578 Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-31 20:27:40 +08:00
ShirkNeko
a7c557222c 修复Makefile中KPM状态信息的重复输出 2025-03-31 20:27:28 +08:00
liankong
9c902fb264 2 2025-03-31 20:24:56 +08:00
liankong
079f74d960 更改返回值处理方式 2025-03-31 19:47:22 +08:00
liankong
00d7de5276 1 2025-03-31 19:07:07 +08:00
liankong
3e928365de 1 2025-03-31 18:51:20 +08:00
liankong
47ba174fb1 尝试支持clang关闭优化 2025-03-31 18:31:21 +08:00
liankong
9446296daa 尝试禁用对壳函数的优化 2025-03-31 18:19:55 +08:00
liankong
7175a6fa7d Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-31 18:08:20 +08:00
liankong
1b06f7d317 关掉所有对壳函数的优化 2025-03-31 18:08:09 +08:00
ShirkNeko
c739bf6bfb 更新应用名称为 SukiSU Ultra,并修改相关提示信息为 SukiSU beta版管理器 2025-03-31 17:30:51 +08:00
liankong
dec9a72b41 增加壳函数信息打印 2025-03-31 17:17:21 +08:00
liankong
593cbaa067 添加壳函数防止被inline优化掉 2025-03-31 16:55:51 +08:00
liankong
fb8906e371 4 2025-03-31 16:19:52 +08:00
liankong
df943250ac 3 2025-03-31 16:07:29 +08:00
liankong
5a522a1489 kpmmgr 2025-03-31 16:01:22 +08:00
liankong
0b0d64b9d0 Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-31 16:00:21 +08:00
liankong
18876e8a69 修复kpmmgr 2025-03-31 16:00:09 +08:00
ShirkNeko
b668378e23 重命名函数并更新命令以获取KPM版本信息 2025-03-31 15:52:57 +08:00
liankong
9de2c09a27 更新kpmmgr 2025-03-31 15:40:28 +08:00
liankong
6b3d2bef12 1 2025-03-31 15:22:30 +08:00
liankong
344ed41bc7 Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-31 15:13:24 +08:00
liankong
2e711c3ac9 3 2025-03-31 15:13:13 +08:00
ShirkNeko
1bf4486cf1 Merge pull request #6 from ShirkNeko/dev
Dev
2025-03-31 14:53:34 +08:00
ShirkNeko
cb116286ed Manager: Fix the problem that the contrast of key color is too light 2025-03-31 14:50:27 +08:00
ShirkNeko
7f0ae95dfb Improve font color blending and contrast in dark mode.
Fix login issue (#19).

Modify styles for more settings.

Do not show the status of susfs when using lkm mode.

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-03-31 14:50:20 +08:00
ShirkNeko
fe7ec9dcf5 docs: update README to clarify branch usage and support for non-GKI devices 2025-03-31 14:50:07 +08:00
ShirkNeko
d88eccdda3 docs: add acknowledgment for yspbwx2010's support 2025-03-31 14:49:53 +08:00
liankong
78fe01d9a4 2 2025-03-31 14:30:43 +08:00
liankong
60cb41c76b 1 2025-03-31 14:04:04 +08:00
liankong
af78f3bac4 转变为仅在内核留下Stub在外部加载KPM 2025-03-31 13:55:48 +08:00
liankong
fff86dcc8d 4 2025-03-30 21:06:56 +08:00
liankong
5ec053ca34 3 2025-03-30 20:39:23 +08:00
liankong
e9f1631b06 根据KP修复 2025-03-30 20:29:43 +08:00
ShirkNeko
3705993330 Merge pull request #5 from ShirkNeko/dev
Dev
2025-03-30 20:21:37 +08:00
liankong
3db338da3e 2 2025-03-30 20:10:50 +08:00
liankong
d126d0f5b8 1 2025-03-30 20:03:40 +08:00
liankong
549adebb30 Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-30 19:56:18 +08:00
liankong
40bada35c6 尝试修复 2025-03-30 19:56:04 +08:00
ShirkNeko
074903a299 更新支持的非官方管理器信息,添加 udochina 2025-03-30 19:50:14 +08:00
ShirkNeko
328bee94e5 Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-30 19:45:12 +08:00
ShirkNeko
0db25f14f1 添加 KPM 状态信息输出,并更新支持的非官方管理器信息 2025-03-30 19:45:10 +08:00
liankong
877e4f9416 修复调用错误 2025-03-30 19:42:09 +08:00
liankong
8b3e864ffa Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-30 19:38:50 +08:00
liankong
5f5f677b7b 修复 2025-03-30 19:38:19 +08:00
ShirkNeko
3933d83d3e 更新 KPM 配置选项名称为 SukiSU,并修改帮助信息以反映新功能和潜在影响 2025-03-30 19:37:14 +08:00
liankong
4abd35fb44 10 2025-03-30 19:27:27 +08:00
liankong
e68afb04eb 9 2025-03-30 19:18:38 +08:00
liankong
bf2be96b29 8 2025-03-30 19:17:42 +08:00
liankong
b0b5048b01 7 2025-03-30 18:57:51 +08:00
liankong
aff69af690 6 2025-03-30 18:56:28 +08:00
liankong
c1d156cd6b 5 2025-03-30 18:50:33 +08:00
liankong
e58e00be9d Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-30 18:36:01 +08:00
liankong
440fe972f4 4 2025-03-30 18:35:47 +08:00
ShirkNeko
e24588b961 Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-30 18:31:09 +08:00
ShirkNeko
57c8d69e83 Manager: simplify and fix SUSFS version display errors
Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-03-30 18:30:53 +08:00
liankong
79c0bebcf5 3 2025-03-30 18:30:44 +08:00
ShirkNeko
321c9c20d5 Manager: simplify and fix SUSFS version display errors
Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-03-30 18:30:04 +08:00
liankong
f6134b47da 2 2025-03-30 18:25:40 +08:00
liankong
8c282b28a0 1 2025-03-30 18:24:56 +08:00
liankong
656cd11876 根据KP修复 2025-03-30 18:10:14 +08:00
liankong
5e77c08872 尝试修复: 内存段缓存/权限问题? 2025-03-30 17:56:53 +08:00
liankong
1090f64117 修复符号引用问题 2025-03-30 17:32:35 +08:00
liankong
470aaa29dc 兼容修复 2025-03-30 17:21:33 +08:00
liankong
c6664af45b 修复 2025-03-30 17:15:25 +08:00
liankong
d6b0ce2565 修复堆栈支持 2025-03-30 17:09:51 +08:00
liankong
770c9632ae 添加panic时打印出对应KPM信息的情况 2025-03-30 16:52:44 +08:00
liankong
cd60773d73 修复编译规则 2025-03-30 16:20:35 +08:00
liankong
06cdd92129 BypassCFI 2025-03-30 16:19:36 +08:00
liankong
315df33bd6 添加跳过CFI检查的机制 2025-03-30 16:14:23 +08:00
liankong
f990bda4e5 绕过CFI限制 2025-03-30 15:37:09 +08:00
liankong
b755ad3602 2 2025-03-30 15:22:40 +08:00
liankong
b060b2827e 1 2025-03-30 15:13:16 +08:00
liankong
73493b288f 按照KernelPatch原代码修复 2025-03-30 14:58:08 +08:00
liankong
87640fb824 3 2025-03-30 02:56:54 +08:00
liankong
fb0a48f9db Merge branch 'dev' of https://github.com/ShirkNeko/SukiSU-Ultra into dev 2025-03-30 02:51:30 +08:00
liankong
acc8670aa9 2 2025-03-30 02:51:20 +08:00
106 changed files with 4626 additions and 2870 deletions

View File

@@ -1,28 +0,0 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
groups:
actions:
patterns:
- "*"
- package-ecosystem: cargo
directory: userspace/ksud
schedule:
interval: daily
allow:
- dependency-type: "all"
groups:
crates:
patterns:
- "*"
- package-ecosystem: gradle
directory: manager
schedule:
interval: daily
groups:
maven:
patterns:
- "*"

View File

@@ -3,9 +3,9 @@ on:
workflow_call:
inputs:
upload:
required: false
required: true
type: boolean
default: false
default: true
description: "Whether to upload to branch"
secrets:
# username:github_pat
@@ -14,9 +14,9 @@ on:
workflow_dispatch:
inputs:
upload:
required: false
required: true
type: boolean
default: false
default: true
description: "Whether to upload to branch"
jobs:
build-lkm:
@@ -27,20 +27,20 @@ jobs:
sub_level: 233
os_patch_level: 2025-02
- version: "android13-5.10"
sub_level: 228
os_patch_level: 2025-01
sub_level: 234
os_patch_level: 2025-03
- version: "android13-5.15"
sub_level: 170
os_patch_level: 2025-01
sub_level: 178
os_patch_level: 2025-03
- version: "android14-5.15"
sub_level: 170
os_patch_level: 2025-01
sub_level: 178
os_patch_level: 2025-03
- version: "android14-6.1"
sub_level: 128
os_patch_level: 2025-03
sub_level: 129
os_patch_level: 2025-04
- version: "android15-6.6"
sub_level: 77
os_patch_level: 2025-03
sub_level: 82
os_patch_level: 2025-04
# uses: ./.github/workflows/gki-kernel-mock.yml when debugging
uses: ./.github/workflows/gki-kernel.yml
with:

View File

@@ -8,7 +8,7 @@ on:
- 'manager/**'
- 'kernel/**'
- 'userspace/ksud/**'
- 'userspace/zakomksd/**'
- 'userspace/susfs/**'
- 'userspace/kpmmgr/**'
pull_request:
branches: [ "main" ]
@@ -29,7 +29,7 @@ on:
upload_lkm:
required: true
type: boolean
default: false
default: true
description: "Whether to upload lkm"
jobs:
check-build-lkm:
@@ -64,7 +64,7 @@ jobs:
cd ..
rm -rf tmp
fi
if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/susfs' ]; then
if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/main' ]; then
need_upload=true
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
need_upload="${{ inputs.upload_lkm }}"
@@ -83,7 +83,7 @@ jobs:
with:
upload: ${{ needs.check-build-lkm.outputs.upload_lkm == 'true' }}
secrets: inherit
build-zakomksd:
build-susfs:
if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ]
strategy:
@@ -91,7 +91,7 @@ jobs:
include:
- target: aarch64-linux-android
os: ubuntu-latest
uses: ./.github/workflows/zakomksd.yml
uses: ./.github/workflows/susfs.yml
with:
target: ${{ matrix.target }}
os: ${{ matrix.os }}
@@ -174,10 +174,10 @@ jobs:
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Download arm64 zakomksd
- name: Download arm64 susfs
uses: actions/download-artifact@v4
with:
name: zakomksd-aarch64-linux-android
name: susfs-aarch64-linux-android
path: .
- name: Download arm64 kpmmgr
@@ -202,18 +202,18 @@ jobs:
run: |
mkdir -p app/src/main/jniLibs/arm64-v8a
mkdir -p app/src/main/jniLibs/x86_64
cp -f ../aarch64-linux-android/release/zakomk ../manager/app/src/main/jniLibs/arm64-v8a/libzakomk.so
cp -f ../x86_64-linux-android/release/zakomk ../manager/app/src/main/jniLibs/x86_64/libzakomk.so
cp -f ../aarch64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozako.so
cp -f ../x86_64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/x86_64/libzakozako.so
- name: Copy kpmmgr to app jniLibs
run: |
mkdir -p app/src/main/jniLibs/arm64-v8a
cp -f ../arm64-v8a/kpmmgr ../manager/app/src/main/jniLibs/arm64-v8a/libkpmmgr.so
- name: Copy zakomksd to app jniLibs
- name: Copy susfs to app jniLibs
run: |
mkdir -p app/src/main/jniLibs/arm64-v8a
cp -f ../arm64-v8a/zakomksd ../manager/app/src/main/jniLibs/arm64-v8a/libzakomksd.so
cp -f ../arm64-v8a/zakozakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozakozako.so
- name: Build with Gradle
run: |

View File

@@ -71,4 +71,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ksud-${{ inputs.target }}
path: userspace/ksud/target/**/release/zakomk*
path: userspace/ksud/target/**/release/zakozako*

View File

@@ -1,11 +1,11 @@
name: Build zakomksd
name: Build susfs
on:
push:
branches: [ "mian" ]
paths:
- '.github/workflows/zakomksd.yml'
- 'userspace/zakomksd/**'
- '.github/workflows/susfs.yml'
- 'userspace/susfs/**'
workflow_dispatch:
workflow_call:
inputs:
@@ -19,7 +19,7 @@ on:
jobs:
build-susfs:
name: Build userspace zakomksd
name: Build userspace susfs
runs-on: ubuntu-latest
steps:
@@ -28,13 +28,13 @@ jobs:
with:
fetch-depth: 0
- name: Build zakomksd
working-directory: ./userspace/zakomksd
- name: Build susfs
working-directory: ./userspace/susfs
run: |
$ANDROID_NDK_HOME/ndk-build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
with:
name: zakomksd-aarch64-linux-android
path: ./userspace/zakomksd/libs
name: susfs-aarch64-linux-android
path: ./userspace/susfs/libs

View File

@@ -1,49 +1,63 @@
# SukiSU
# SukiSU Ultra
**Enlish** | [简体中文](README.md)
**English** | [简体中文](README.md) | [日本語](README-ja.md)
Android device root solution based on [KernelSU](https://github.com/KernelSU/KernelSU)
Android device root solution based on [KernelSU](https://github.com/tiann/KernelSU)
**Experimental! Use at your own risk! **This solution is based on [KernelSU]() and is experimental!
**Experimental! Use at your own risk!** This solution is based on [KernelSU](https://github.com/tiann/KernelSU) and is experimental!
>
> This is an unofficial fork, all rights reserved [@tiann](https://github.com/tiann)
> This is an unofficial fork. All rights are reserved to [@tiann](https://github.com/tiann)
> However, we will be a separately maintained branch of KSU in the future
>
- Fully adapted for non-GKI devices (susfs-dev and unsusfs-patched dev branches only)
## How to add
Using the susfs-dev branch (integrated susfs with support for non-GKI devices)
Use the susfs-stable or susfs-dev branch (integrated susfs with support for non-GKI devices)
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
Use main branching (no longer with support for non-GKI devices)
Use the main branch
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
```
## How to use integrated susfs
Use the susfs-dev branch directly without any patching
1. Use the susfs-dev branch directly without any patching
## KPM support
- We have removed duplicate KSU functions based on KernelPatch and retained KPM support.
- We will introduce more APatch-compatible functions to ensure the integrity of KPM functionality.
Open source address: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
KPM template address: https://github.com/udochina/KPM-Build-Anywhere
## More links
Projects compiled based on Sukisu and susfs
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
## Hook method
- This method references the hook manual to (https://github.com/rsuntk/KernelSU)
- This method references the hook method from (https://github.com/rsuntk/KernelSU)
1. **KPROBES hook:**
- This fork only supports GKI (5.10 - 6.x) kernels, all non-GKI kernels must use manual hooks.
- This method only supports GKI (5.10 - 6.x) kernels, and all non-GKI kernels must use manual hooks.
- For Loadable Kernel Modules (LKM)
- Default hooking method for GKI kernels
- Requires `CONFIG_KPROBES=y`. 2.
2. **Hooks manual:**
- Requires `CONFIG_KPROBES=y`.
2. **Manual hooks:**
- For GKI (5.10 - 6.x) kernels, add `CONFIG_KSU_MANUAL_HOOK=y` to the kernel defconfig and make sure to protect KernelSU hooks by using `#ifdef CONFIG_KSU_MANUAL_HOOK` instead of `#ifdef CONFIG_KSU`.
- Standard KernelSU hooks: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
- backslashxx syscall hooks: https://github.com/backslashxx/KernelSU/issues/5
@@ -51,51 +65,56 @@ Projects compiled based on Sukisu and susfs
## Usage
[GKI]
1. such as millet redmi samsung and other devices (does not include the magic kernel manufacturers such as: meizu, a plus real me oppo)
2. find more links in the GKI build project to find the device kernel version directly download with TWRP or kernel flashing tool to brush into the zip with AnyKernel3 suffix can be
3. General without the suffix of the .zip compressed package is universal, gz suffix for the special TianGui models, lz4 suffix for Google models, general brush without the suffix can be!
### GKI
1. such as Xiaomi, Redmi, Samsung, and other devices (does not include manufacturers that modified the kernel like Meizu, OnePlus, RealMe, and OPPO)
2. Use the prebuilt GKI kernel, the ones with their name ending with AnyKernel3, mentioned in the 'More Links' section, and then flash it with recoveries like TWRP
3. Generally, packages with a plain .zip suffix are universal. However, if your device has a MediaTek processor, you should use the ones with .gz suffix, and packages with .lz4 suffix are dedicated to Google devices.
[OnePlus]
1. Find the Yiga project in the More link and fill in your own, then build it with cloud compilation, and finally brush in the zip with AnyKernel3 suffix.
Note: You only need to fill in the first two kernel versions, such as 5.10, 5.15, 6.1, 6.6.
- Please search for the processor codename by yourself, usually it is all English without numbers.
- Branching and configuration files, please fill in the kernel open source address.
### OnePlus
1. Use the link mentioned in the 'More Links' section to create a customized build with your device information, and then flash the zip file with the AnyKernel3 suffix.
> [!Note]
> - You only need to fill in the first two parts of kernel versions, such as 5.10, 5.15, 6.1, or 6.6.
> - Please search for the processor codename by yourself, usually it is all English without numbers.
> - You can find the branch and configuration files from the OnePlus open-source kernel repository.
## Features
1. Kernel-based `su` and root access management.
2. Not based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) module system. 3.
3. [Application Profiles](https://kernelsu.org/guide/app-profile.html): Lock root privileges in a cage. 4.
2. Not based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) module system, but based on [Magic Mount](https://github.com/5ec1cff/KernelSU) from 5ec1cff
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock root privileges in a cage.
4. Bringing back non-GKI/GKI 1.0 support
5. More customization
6. Support for KPM kernel modules
## License
- The file in the “kernel” directory is [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
- All other parts except the “kernel” directory are [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html).
- The file in the “kernel” directory is under [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) license.
- All other parts except the “kernel” directory are under [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html) license.
## Sponsorship list
- [Ktouls](https://github.com/Ktouls) Thanks so much for bringing me support
- [zaoqi123](https://github.com/zaoqi123) It's not a bad idea to buy me a milk tea
- [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project
- [yspbwx2010](https://github.com/yspbwx2010) Many thanks
- [DARKWWEE](https://github.com/DARKWWEE) Thanks for the 100 USDT Lao
How the above list does not have your name, I will keep you updated, thanks again for your support!
If the above list does not have your name, I will update it as soon as possible, and thanks again for your support!
## Contributions
- [KernelSU](https://github.com/tiann/KernelSU): original project
- [MKSU](https://github.com/5ec1cff/KernelSU): Used project
- [RKSU](https://github.com/rsuntk/KernelsU)Re-support of non-GKI devices using the kernel of this project
- [RKSU](https://github.com/rsuntk/KernelsU): Reintroduced the support of non-GKI devices using the kernel of this project
- [susfs](https://gitlab.com/simonpunk/susfs4ksu)Used susfs file system
- [KernelSU](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU conceptualization
- [Magisk](https://github.com/topjohnwu/Magisk): Powerful root utility
- [genuine](https://github.com/brevent/genuine/): APK v2 Signature Verification
- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit skills.
- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit utilities.
- [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch is a key part of the APatch implementation of the kernel module

113
docs/README-ja.md Normal file
View File

@@ -0,0 +1,113 @@
# SukiSU Ultra
**日本語** | [简体中文](README.md) | [English](README-en.md)
[KernelSU](https://github.com/tiann/KernelSU) をベースとした Android デバイスの root ソリューション
**試験中なビルドです!自己責任で使用してください!**<br>
このソリューションは [KernelSU](https://github.com/tiann/KernelSU) に基づいていますが、試験中なビルドです。
>
> これは非公式なフォークです。すべての権利は [@tiann](https://github.com/tiann) に帰属します。
>
>ただし、将来的には KSU とは別に管理されるブランチとなる予定です。
- GKI 非対応なデバイスに完全に適応 (susfs-dev と unsusfs-patched dev ブランチのみ)
## 追加方法
susfs-stable または susfs-dev ブランチ (GKI 非対応デバイスに対応する統合された susfs) 使用してください。
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
メインブランチを使用する場合
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
```
## 統合された susfs の使い方
1. パッチを当てずに susfs-dev ブランチを直接使用してください。
## KPM に対応
- KernelPatch に基づいて重複した KSU の機能を削除、KPM の対応を維持させています。
- KPM 機能の整合性を確保するために、APatch の互換機能を更に向上させる予定です。
オープンソースアドレス: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
KPM テンプレートのアドレス: https://github.com/udochina/KPM-Build-Anywhere
## その他のリンク
SukiSU と susfs をベースにコンパイルされたプロジェクトです。
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
## フックの方式
- この方式は (https://github.com/rsuntk/KernelSU) のフック方式を参照してください。
1. **KPROBES フック:**
- この方式は GKI (5.10 - 6.x) のカーネルのみに対応しています。GKI 以外のカーネルは手動でフックを使用する必要があります。
- 読み込み可能なカーネルモジュールの場合 (LKM)
- GKI カーネルのデフォルトとなるフック方式
- `CONFIG_KPROBES=y` が必要です。
2. **手動でフック:**
- GKI (5.10 - 6.x) のカーネルの場合、カーネルの defconfig に `CONFIG_KSU_MANUAL_HOOK=y` を追加して `#ifdef CONFIG_KSU` ではなく `#ifdef CONFIG_KSU_MANUAL_HOOK` を使用して KernelSU フックを保護するようにしてください。
- 標準の KernelSU フック: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
- backslashxx syscall フック: https://github.com/backslashxx/KernelSU/issues/5
- KPROBES を手動で統合する一部の非 GKI デバイスでは手動の VFS フック `new_hook.patch` パッチは不要です。
## 使い方
### GKI
1. Xiaomi、Redmi、Samsung などのデバイス (Meizu、OnePlus、Realme、OPPO などのカーネルを変更したメーカー以外)
2. `その他のリンク`の項目で言及されているカーネル名が、AnyKernel3 で終わるビルド済みの GKI カーネルを TWRP などのリカバリーでフラッシュします。
3. 一般的な .zip の接頭辞を持つパッケージは汎用的になります。ただし、デバイスに MediaTek 製の SoC が搭載されている場合は、.gz の接頭辞を持つパッケージを使用する必要があります。その他に .lz4 の接頭辞を持つパッケージは Google 製デバイス専用です。
### OnePlus
1. `その他のリンク`の項目に記載されているリンクを開き、デバイス情報を使用してカスタマイズされたカーネルをビルドし、AnyKernel3 の接頭辞を持つ .zip ファイルをフラッシュします。
> [!Note]
> - 5.10、5.15、6.1、6.6 などのカーネルバージョンの最初の 2 文字のみを入力する必要があります。
> - SoC のコードネームは自分で検索してください。通常は、数字がなく英語表記のみです。
> - ブランチと構成ファイルは、OnePlus オープンソースカーネルリポジトリから見つけることができます。
## 機能
1. カーネルベースな `su` および root アクセスの管理。
2. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) モジュールシステムではなく、 5ec1cff 氏の [Magic Mount](https://github.com/5ec1cff/KernelSU) に基づいています。
3. [アプリプロファイル](https://kernelsu.org/guide/app-profile.html): root 権限をケージ内にロックします。
4. 非 GKI / GKI 1.0 の対応を復活
5. その他のカスタマイズ
6. KPM カーネルモジュールに対応
## ライセンス
- “kernel” ディレクトリ内のファイルは [GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.ja.html) のみライセンス下にあります。
- “kernel” ディレクトリを除くその他すべての部分は [GPL-3.0 またはそれ以降](https://www.gnu.org/licenses/gpl-3.0.html) のライセンス下にあります。
## スポンサーシップの一覧
- [Ktouls](https://github.com/Ktouls) 応援をしてくれたことに感謝。
- [zaoqi123](https://github.com/zaoqi123) ミルクティーを買ってあげるのも良い考えですね。
- [wswzgdg](https://github.com/wswzgdg) このプロジェクトを支援していただき、ありがとうございます。
- [yspbwx2010](https://github.com/yspbwx2010) どうもありがとう。
- [DARKWWEE](https://github.com/DARKWWEE) ラオウ100USDTありがとう
上記の一覧にあなたの名前がない場合は、できるだけ早急に更新しますので再度ご支援をお願いします。
## 貢献者
- [KernelSU](https://github.com/tiann/KernelSU): オリジナルのプロジェクトです。
- [MKSU](https://github.com/5ec1cff/KernelSU): 使用しているプロジェクトです。
- [RKSU](https://github.com/rsuntk/KernelsU): このプロジェクトのカーネルを使用して非 GKI デバイスのサポートを追加しています。
- [susfs](https://gitlab.com/simonpunk/susfs4ksu):使用している susfs ファイルシステムです。
- [KernelSU](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU について。
- [Magisk](https://github.com/topjohnwu/Magisk): パワフルな root ユーティリティです。
- [genuine](https://github.com/brevent/genuine/): APK v2 署名認証で使用しています。
- [Diamorphine](https://github.com/m0nad/Diamorphine): いくつかの rootkit ユーティリティを使用しています。
- [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch はカーネルモジュールの APatch 実装での重要な部分となります。

View File

@@ -1,14 +1,15 @@
# SukiSU
# SukiSU Ultra
**简体中文** | [English](README-en.md)
**简体中文** | [English](README-en.md) | [日本語](README-ja.md)
基于 [KernelSU](https://github.com/tiann/KernelSU) 的安卓设备 root 解决方案
**实验性!使用风险自负!**
**实验性! 使用风险自负!**
>
> 这是非官方分支,保留所有权利 [@tiann](https://github.com/tiann)
> 但是我们将会在未来成为一个单独维护的KSU分支
>
@@ -16,33 +17,46 @@
在内核源码的根目录下执行以下命令:
使用 susfs-dev 分支已集成susfs带非GKI设备的支持
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
使用 main 分支不再带非GKI设备的支持
使用 main 分支
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
```
## 如何集成 susfs
1. 直接使用 susfs-dev 分支,不需要再集成 susfs
1. 直接使用 susfs-stable 或者 susfs-dev 分支,不需要再集成 susfs
## 钩子方法
- 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU)
1. **KPROBES 钩子:**
- 此方法仅支持 GKI 2.05.10 - 6.x内核,所有非 GKI 2.0 内核都必须使用手动钩子
- 此方法仅支持 GKI 2.0 (5.10 - 6.x) 内核, 所有非 GKI 2.0 内核都必须使用手动钩子
- 用于可加载内核模块 (LKM)
- GKI 2.0 内核的默认钩子方法
- 需要 `CONFIG_KPROBES=y`
2. **手动钩子:**
- 对于 GKI 2.05.10 - 6.x内核,需要在对应设备的 defconfig 文件中添加 `CONFIG_KSU_MANUAL_HOOK=y` 并确保使用 `#ifdef CONFIG_KSU_MANUAL_HOOK` 而不是 `#ifdef CONFIG_KSU` 来保护 KernelSU 钩子
- 对于 GKI 2.0 (5.10 - 6.x) 内核,需要在对应设备的 defconfig 文件中添加 `CONFIG_KSU_MANUAL_HOOK=y` 并确保使用 `#ifdef CONFIG_KSU_MANUAL_HOOK` 而不是 `#ifdef CONFIG_KSU` 来保护 KernelSU 钩子
- 标准的 KernelSU 钩子https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
- backslashxx 的 syscall 手动钩子https://github.com/backslashxx/KernelSU/issues/5
- 部分手动集成KPROBES的非 GKI 2.0 设备不需要手动 VFS 钩子 `new_hook.patch` 补丁
- 部分手动集成 KPROBES 的非 GKI 2.0 设备不需要手动 VFS 钩子 `new_hook.patch` 补丁
## KPM支持
- 我们基于KernelPatch去掉了和KSU重复的功能保留了KPM支持
- 我们将会引入更多的兼容APatch的函数来确保KPM功能的完整性
开源地址: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
KPM模板地址: https://github.com/udochina/KPM-Build-Anywhere
## 更多链接
@@ -54,17 +68,17 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
## 使用方法
### GKI
1. 适用于如小米红米三星等的 GKI 2.0 的设备不包含魔改内核的厂商如魅族、一加、真我和 oppo
1. 适用于如小米红米三星等的 GKI 2.0 的设备 (不包含魔改内核的厂商如魅族、一加、真我和 oppo)
2. 找到更多链接里的 GKI 构建的项目找到设备内核版本直接下载用TWRP或者内核刷写工具刷入带 AnyKernel3 后缀的压缩包即可
3. 一般不带后缀的 .zip 压缩包是通用gz 后缀的为天玑机型专用lz4 后缀的为谷歌系机型专用,一般刷不带后缀的即可
### 一加
1.找到更多链接里的一加项目进行自行填写,然后云编译构建,最后刷入带 AnyKernel3 后缀的压缩包即可
注意事项:
- 内核版本只需要填写前两位即可,如 5.105.156.16.6
- 处理器代号请自行搜索,一般为全英文不带数字的代号
- 分支和配置文件请自行到一加内核开源地址进行填写
> [!Note]
> - 内核版本只需要填写前两位即可,如 5.105.156.16.6
> - 处理器代号请自行搜索,一般为全英文不带数字的代号
> - 分支和配置文件请自行到一加内核开源地址进行填写
## 特点
@@ -72,8 +86,9 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
1. 基于内核的 `su` 和 root 访问管理
2. 基于 5ec1cff 的 [Magic Mount](https://github.com/5ec1cff/KernelSU) 的模块系统
3. [App Profile](https://kernelsu.org/guide/app-profile.html):将 root 权限锁在笼子里
4. 恢复对非 GKI 2.0 内核的支持仅限susfs-dev和未进行susfs补丁的dev分支
4. 恢复对非 GKI 2.0 内核的支持
5. 更多自定义功能
6. 对KPM内核模块的支持
## 许可证
@@ -85,11 +100,13 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
- [Ktouls](https://github.com/Ktouls) 非常感谢你给我带来的支持
- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持
- [yspbwx2010](https://github.com/yspbwx2010) 非常感谢
- [DARKWWEE](https://github.com/DARKWWEE) 感谢老哥的 100 USDT
以上名单没有你的名称,我会及时更新,再次感谢大家的支持
以上名单没有你的名称,我会及时更新,再次感谢大家的支持
## 贡献
@@ -101,3 +118,4 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
- [Magisk](https://github.com/topjohnwu/Magisk):强大的 root 工具
- [genuine](https://github.com/brevent/genuine/)APK v2 签名验证
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技能
- [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch是APatch实现内核模块的关键部分

View File

@@ -24,11 +24,12 @@ config KSU_HOOK
override the kernel version check and enable the hook functionality.
config KPM
bool "Enable KernelSU KPM"
bool "Enable SukiSU KPM"
default n
help
This option enables the KernelSU KPM feature. If enabled, it will
override the kernel version check and enable the hook functionality.
Enabling this option will activate the KPM feature of SukiSU.
This option is suitable for scenarios where you need to force KPM to be enabled.
but it may affect system stability.
endmenu

View File

@@ -22,7 +22,7 @@ obj-$(CONFIG_KPM) += kpm/
# .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_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count main)
# ksu_version: major * 10000 + git version + 606 for historical reasons
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 606))
$(info -- KernelSU version: $(KSU_VERSION))
@@ -42,14 +42,32 @@ endif
ifdef KSU_MANAGER_PACKAGE
ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
$(info -- KernelSU Manager package name: $(KSU_MANAGER_PACKAGE))
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE))
endif
$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
$(info -- Supported Unofficial Manager: ShirkNeko (GKI) (Non-GKI))
$(info -- SukiSU Manager signature size: $(KSU_EXPECTED_SIZE))
$(info -- SukiSU Manager signature hash: $(KSU_EXPECTED_HASH))
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
KERNEL_TYPE := Non-GKI
# Check for GKI 2.0 (5.10+ or 6.x+)
ifneq ($(shell test \( $(VERSION) -ge 5 -a $(PATCHLEVEL) -ge 10 \) -o $(VERSION) -ge 6; echo $$?),0)
# Check for GKI 1.0 (5.4)
ifeq ($(shell test $(VERSION)-$(PATCHLEVEL) = 5-4; echo $$?),0)
KERNEL_TYPE := GKI 1.0
endif
else
KERNEL_TYPE := GKI 2.0
endif
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
$(info -- KERNEL_TYPE: $(KERNEL_TYPE))
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
ifeq ($(CONFIG_KPM),y)
$(info -- KPM is enabled)
else
$(info -- KPM is disabled)
endif
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)

View File

@@ -419,10 +419,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
pr_info("KPM: calling before arg2=%d\n", (int) arg2);
res = sukisu_handle_kpm(arg2, arg3, arg4);
copy_to_user(result, &res, sizeof(res));
pr_info("KPM: calling before arg2=%d res=%d\n", (int) arg2, (int) res);
res = sukisu_handle_kpm(arg2, arg3, arg4, arg5);
return 0;
}

View File

@@ -1,2 +1,6 @@
obj-y += kpm.o
obj-y += compact.o
obj-y += compact.o
obj-y += super_access.o
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function

View File

@@ -26,37 +26,56 @@
#include <linux/slab.h>
#include "kpm.h"
#include "compact.h"
#include "../allowlist.h"
#include "../manager.h"
unsigned long sukisu_compact_find_symbol(const char* name);
// ======================================================================
// 兼容函数 for KPM
const char* kpver = "0.10";
static
int sukisu_is_su_allow_uid(uid_t uid) {
return ksu_is_allow_uid(uid) ? 1 : 0;
}
static
int sukisu_get_ap_mod_exclude(uid_t uid) {
// Not supported
return 0;
}
static
int sukisu_is_uid_should_umount(uid_t uid) {
return ksu_uid_should_umount(uid) ? 1 : 0;
}
static
int sukisu_is_current_uid_manager() {
return is_manager();
}
static
uid_t sukisu_get_manager_uid() {
return ksu_manager_uid;
}
// ======================================================================
struct CompactAddressSymbol {
const char* symbol_name;
void* addr;
};
struct CompactAliasSymbol {
const char* symbol_name;
const char* compact_symbol_name;
};
struct CompactAddressSymbol address_symbol [] = {
static struct CompactAddressSymbol address_symbol [] = {
{ "kallsyms_lookup_name", &kallsyms_lookup_name },
{ "compact_find_symbol", &sukisu_compact_find_symbol },
{ "compat_copy_to_user", &copy_to_user },
{ "compat_strncpy_from_user", &strncpy_from_user },
{ "kpver", &kpver },
{ "is_run_in_sukisu_ultra", (void*)1 }
};
struct CompactAliasSymbol alias_symbol[] = {
{"kf_strncat", "strncat"},
{"kf_strlen", "strlen" },
{"kf_strcpy", "strcpy"},
{"compat_copy_to_user", "__arch_copy_to_user"}
{ "is_run_in_sukisu_ultra", (void*)1 },
{ "is_su_allow_uid", &sukisu_is_su_allow_uid },
{ "get_ap_mod_exclude", &sukisu_get_ap_mod_exclude },
{ "is_uid_should_umount", &sukisu_is_uid_should_umount },
{ "is_current_uid_manager", &sukisu_is_current_uid_manager },
{ "get_manager_uid", &sukisu_get_manager_uid }
};
unsigned long sukisu_compact_find_symbol(const char* name) {
@@ -71,30 +90,13 @@ unsigned long sukisu_compact_find_symbol(const char* name) {
}
}
/* 如果符号名以 "kf__" 开头,尝试解析去掉前缀的部分 */
if (strncmp(name, "kf__", 4) == 0) {
const char *real_name = name + 4; // 去掉 "kf__"
addr = (unsigned long)kallsyms_lookup_name(real_name);
if (addr) {
return addr;
}
}
// 通过内核来查
addr = kallsyms_lookup_name(name);
if(addr) {
return addr;
}
// 查不到就查查兼容的符号
for(i = 0; i < (sizeof(alias_symbol) / sizeof(struct CompactAliasSymbol)); i++) {
struct CompactAliasSymbol* symbol = &alias_symbol[i];
if(strcmp(name, symbol->symbol_name) == 0) {
addr = kallsyms_lookup_name(symbol->compact_symbol_name);
if(addr)
return addr;
}
}
return 0;
}
}
EXPORT_SYMBOL(sukisu_compact_find_symbol);

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +1,44 @@
#ifndef ___SUKISU_KPM_H
#define ___SUKISU_KPM_H
int sukisu_handle_kpm(unsigned long arg3, unsigned long arg4, unsigned long arg5);
int sukisu_handle_kpm(unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
int sukisu_is_kpm_control_code(unsigned long arg2);
// KPM控制代码
#define CMD_KPM_CONTROL 28
#define CMD_KPM_CONTROL_MAX 34
#define CMD_KPM_CONTROL_MAX 35
// 控制代码
// prctl(xxx, xxx, 1, "PATH", "ARGS")
// prctl(xxx, 28, "PATH", "ARGS")
// success return 0, error return -N
#define SUKISU_KPM_LOAD 28
// prctl(xxx, xxx, 2, "NAME")
// prctl(xxx, 29, "NAME")
// success return 0, error return -N
#define SUKISU_KPM_UNLOAD 29
// num = prctl(xxx, xxx, 3)
// num = prctl(xxx, 30)
// error return -N
// success return +num or 0
#define SUKISU_KPM_NUM 30
// prctl(xxx, xxx, 4, Buffer, BufferSize)
// prctl(xxx, 31, Buffer, BufferSize)
// success return +out, error return -N
#define SUKISU_KPM_LIST 31
// prctl(xxx, xxx, 5, "NAME", Buffer[256])
// prctl(xxx, 32, "NAME", Buffer[256])
// success return +out, error return -N
#define SUKISU_KPM_INFO 32
// prctl(xxx, xxx, 6, "NAME", "ARGS")
// prctl(xxx, 33, "NAME", "ARGS")
// success return KPM's result value
// error return -N
#define SUKISU_KPM_CONTROL 33
// prctl(xxx, xxx, 7)
// success will printf to stdout and return 0
// error will return -1
#define SUKISU_KPM_PRINT 34
// prctl(xxx, 34, buffer, bufferSize)
// success return KPM's result value
// error return -N
#define SUKISU_KPM_VERSION 34
#endif

View File

@@ -0,0 +1,285 @@
#include <linux/export.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/kernfs.h>
#include <linux/file.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/uaccess.h>
#include <linux/elf.h>
#include <linux/kallsyms.h>
#include <linux/version.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/rcupdate.h>
#include <asm/elf.h> /* 包含 ARM64 重定位类型定义 */
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <asm/cacheflush.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/set_memory.h>
#include <linux/version.h>
#include <linux/export.h>
#include <linux/slab.h>
#include "kpm.h"
#include "compact.h"
#include <linux/types.h>
#include <linux/stddef.h>
// 结构体成员元数据
struct DynamicStructMember {
const char* name;
size_t size;
size_t offset;
};
// 结构体元数据(包含总大小)
struct DynamicStructInfo {
const char* name;
size_t count;
size_t total_size;
struct DynamicStructMember* members;
};
// 定义结构体元数据的宏(直接使用 struct 名称)
#define DYNAMIC_STRUCT_BEGIN(struct_name) \
static struct DynamicStructMember struct_name##_members[] = {
#define DEFINE_MEMBER(struct_name, member) \
{ \
.name = #member, \
.size = sizeof(((struct struct_name*)0)->member), \
.offset = offsetof(struct struct_name, member) \
},
#define DYNAMIC_STRUCT_END(struct_name) \
}; \
static struct DynamicStructInfo struct_name##_info = { \
.name = #struct_name, \
.count = sizeof(struct_name##_members) / sizeof(struct DynamicStructMember), \
.total_size = sizeof(struct struct_name), \
.members = struct_name##_members \
};
// ==================================================================================
#include <linux/version.h>
#define KERNEL_VERSION_6_1 KERNEL_VERSION(6, 1, 0)
#define KERNEL_VERSION_5_15 KERNEL_VERSION(5, 15, 0)
#include <../fs/mount.h>
#include <linux/mount.h>
// 定义元数据
DYNAMIC_STRUCT_BEGIN(mount)
DEFINE_MEMBER(mount, mnt_parent)
DEFINE_MEMBER(mount, mnt)
DEFINE_MEMBER(mount, mnt_id)
DEFINE_MEMBER(mount, mnt_group_id)
DEFINE_MEMBER(mount, mnt_expiry_mark)
DEFINE_MEMBER(mount, mnt_master)
DEFINE_MEMBER(mount, mnt_devname)
DYNAMIC_STRUCT_END(mount)
DYNAMIC_STRUCT_BEGIN(vfsmount)
DEFINE_MEMBER(vfsmount, mnt_root)
DEFINE_MEMBER(vfsmount, mnt_sb)
DEFINE_MEMBER(vfsmount, mnt_flags)
DYNAMIC_STRUCT_END(vfsmount)
DYNAMIC_STRUCT_BEGIN(mnt_namespace)
DEFINE_MEMBER(mnt_namespace, ns)
DEFINE_MEMBER(mnt_namespace, root)
DEFINE_MEMBER(mnt_namespace, seq)
DEFINE_MEMBER(mnt_namespace, mounts)
#if LINUX_VERSION_CODE < KERNEL_VERSION_5_15
DEFINE_MEMBER(mnt_namespace, count)
#endif
DYNAMIC_STRUCT_END(mnt_namespace)
#include <linux/kprobes.h>
#ifdef CONFIG_KPROBES
DYNAMIC_STRUCT_BEGIN(kprobe)
DEFINE_MEMBER(kprobe, addr)
DEFINE_MEMBER(kprobe, symbol_name)
DEFINE_MEMBER(kprobe, offset)
DEFINE_MEMBER(kprobe, pre_handler)
DEFINE_MEMBER(kprobe, post_handler)
#if LINUX_VERSION_CODE < KERNEL_VERSION_5_15
DEFINE_MEMBER(kprobe, fault_handler)
#endif
DEFINE_MEMBER(kprobe, flags)
DYNAMIC_STRUCT_END(kprobe)
#endif
#include <linux/mm.h>
#include <linux/mm_types.h>
DYNAMIC_STRUCT_BEGIN(vm_area_struct)
DEFINE_MEMBER(vm_area_struct,vm_start)
DEFINE_MEMBER(vm_area_struct,vm_end)
DEFINE_MEMBER(vm_area_struct,vm_flags)
DEFINE_MEMBER(vm_area_struct,anon_vma)
DEFINE_MEMBER(vm_area_struct,vm_pgoff)
DEFINE_MEMBER(vm_area_struct,vm_file)
DEFINE_MEMBER(vm_area_struct,vm_private_data)
#ifdef CONFIG_ANON_VMA_NAME
DEFINE_MEMBER(vm_area_struct, anon_name)
#endif
DEFINE_MEMBER(vm_area_struct, vm_ops)
DYNAMIC_STRUCT_END(vm_area_struct)
DYNAMIC_STRUCT_BEGIN(vm_operations_struct)
DEFINE_MEMBER(vm_operations_struct, open)
DEFINE_MEMBER(vm_operations_struct, close)
DEFINE_MEMBER(vm_operations_struct, name)
DEFINE_MEMBER(vm_operations_struct, access)
DYNAMIC_STRUCT_END(vm_operations_struct)
#include <linux/netlink.h>
DYNAMIC_STRUCT_BEGIN(netlink_kernel_cfg)
DEFINE_MEMBER(netlink_kernel_cfg, groups)
DEFINE_MEMBER(netlink_kernel_cfg, flags)
DEFINE_MEMBER(netlink_kernel_cfg, input)
DEFINE_MEMBER(netlink_kernel_cfg, cb_mutex)
DEFINE_MEMBER(netlink_kernel_cfg, bind)
DEFINE_MEMBER(netlink_kernel_cfg, unbind)
#if LINUX_VERSION_CODE < KERNEL_VERSION_6_1
DEFINE_MEMBER(netlink_kernel_cfg, compare)
#endif
DYNAMIC_STRUCT_END(netlink_kernel_cfg)
#include <linux/sched.h>
DYNAMIC_STRUCT_BEGIN(task_struct)
DEFINE_MEMBER(task_struct, pid)
DEFINE_MEMBER(task_struct, tgid)
DEFINE_MEMBER(task_struct, cred)
DEFINE_MEMBER(task_struct, real_cred)
DEFINE_MEMBER(task_struct, comm)
DEFINE_MEMBER(task_struct, parent)
DEFINE_MEMBER(task_struct, group_leader)
DEFINE_MEMBER(task_struct, mm)
DEFINE_MEMBER(task_struct, active_mm)
DEFINE_MEMBER(task_struct, thread_pid)
DEFINE_MEMBER(task_struct, files)
DEFINE_MEMBER(task_struct, seccomp)
#ifdef CONFIG_THREAD_INFO_IN_TASK
DEFINE_MEMBER(task_struct, thread_info)
#endif
#ifdef CONFIG_CGROUPS
DEFINE_MEMBER(task_struct, cgroups)
#endif
#ifdef CONFIG_SECURITY
DEFINE_MEMBER(task_struct, security)
#endif
DEFINE_MEMBER(task_struct, thread)
DYNAMIC_STRUCT_END(task_struct)
// =====================================================================================================================
#define STRUCT_INFO(name) &(name##_info)
static
struct DynamicStructInfo* dynamic_struct_infos[] = {
STRUCT_INFO(mount),
STRUCT_INFO(vfsmount),
STRUCT_INFO(mnt_namespace),
#ifdef CONFIG_KPROBES
STRUCT_INFO(kprobe),
#endif
STRUCT_INFO(vm_area_struct),
STRUCT_INFO(vm_operations_struct),
STRUCT_INFO(netlink_kernel_cfg),
STRUCT_INFO(task_struct)
};
// return 0 if successful
// return -1 if struct not defined
int sukisu_super_find_struct(
const char* struct_name,
size_t* out_size,
int* out_members
) {
for(size_t i = 0; i < (sizeof(dynamic_struct_infos) / sizeof(dynamic_struct_infos[0])); i++) {
struct DynamicStructInfo* info = dynamic_struct_infos[i];
if(strcmp(struct_name, info->name) == 0) {
if(out_size)
*out_size = info->total_size;
if(out_members)
*out_members = info->count;
return 0;
}
}
return -1;
}
EXPORT_SYMBOL(sukisu_super_find_struct);
// Dynamic access struct
// return 0 if successful
// return -1 if struct not defined
// return -2 if member not defined
int sukisu_super_access (
const char* struct_name,
const char* member_name,
size_t* out_offset,
size_t* out_size
) {
for(size_t i = 0; i < (sizeof(dynamic_struct_infos) / sizeof(dynamic_struct_infos[0])); i++) {
struct DynamicStructInfo* info = dynamic_struct_infos[i];
if(strcmp(struct_name, info->name) == 0) {
for (size_t i1 = 0; i1 < info->count; i1++) {
if (strcmp(info->members[i1].name, member_name) == 0) {
if(out_offset)
*out_offset = info->members[i].offset;
if(out_size)
*out_size = info->members[i].size;
return 0;
}
}
return -2;
}
}
return -1;
}
EXPORT_SYMBOL(sukisu_super_access);
// 动态 container_of 宏
#define DYNAMIC_CONTAINER_OF(offset, member_ptr) ({ \
(offset != (size_t)-1) ? (void*)((char*)(member_ptr) - offset) : NULL; \
})
// Dynamic container_of
// return 0 if success
// return -1 if current struct not defined
// return -2 if target member not defined
int sukisu_super_container_of(
const char* struct_name,
const char* member_name,
void* ptr,
void** out_ptr
) {
if(ptr == NULL) {
return -3;
}
for(size_t i = 0; i < (sizeof(dynamic_struct_infos) / sizeof(dynamic_struct_infos[0])); i++) {
struct DynamicStructInfo* info = dynamic_struct_infos[i];
if(strcmp(struct_name, info->name) == 0) {
for (size_t i1 = 0; i1 < info->count; i1++) {
if (strcmp(info->members[i1].name, member_name) == 0) {
*out_ptr = (void*) DYNAMIC_CONTAINER_OF(info->members[i1].offset, ptr);
return 0;
}
}
return -2;
}
}
return -1;
}
EXPORT_SYMBOL(sukisu_super_container_of);

View File

@@ -0,0 +1,39 @@
#ifndef __SUKISU_SUPER_ACCESS_H
#define __SUKISU_SUPER_ACCESS_H
#include <linux/types.h>
#include <linux/stddef.h>
#include "kpm.h"
#include "compact.h"
// return 0 if successful
// return -1 if struct not defined
int sukisu_super_find_struct(
const char* struct_name,
size_t* out_size,
int* out_members
);
// Dynamic access struct
// return 0 if successful
// return -1 if struct not defined
// return -2 if member not defined
int sukisu_super_access (
const char* struct_name,
const char* member_name,
size_t* out_offset,
size_t* out_size
);
// Dynamic container_of
// return 0 if success
// return -1 if current struct not defined
// return -2 if target member not defined
int sukisu_super_container_of(
const char* struct_name,
const char* member_name,
void* ptr,
void** out_ptr
);
#endif

View File

@@ -25,7 +25,7 @@ apksign {
}
android {
namespace = "shirkneko.zako.sukisu"
namespace = "com.sukisu.ultra"
buildTypes {
release {

View File

@@ -1,5 +1,4 @@
// IKsuInterface.aidl
package shirkneko.zako.sukisu;
package com.sukisu.zako;
import android.content.pm.PackageInfo;
import rikka.parcelablelist.ParcelableListSlice;

Binary file not shown.

View File

@@ -12,7 +12,7 @@
extern "C"
JNIEXPORT jboolean JNICALL
Java_shirkneko_zako_sukisu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
Java_com_sukisu_ultra_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
auto cpkg = env->GetStringUTFChars(pkg, nullptr);
auto result = become_manager(cpkg);
env->ReleaseStringUTFChars(pkg, cpkg);
@@ -21,13 +21,13 @@ Java_shirkneko_zako_sukisu_Natives_becomeManager(JNIEnv *env, jobject, jstring p
extern "C"
JNIEXPORT jint JNICALL
Java_shirkneko_zako_sukisu_Natives_getVersion(JNIEnv *env, jobject) {
Java_com_sukisu_ultra_Natives_getVersion(JNIEnv *env, jobject) {
return get_version();
}
extern "C"
JNIEXPORT jintArray JNICALL
Java_shirkneko_zako_sukisu_Natives_getAllowList(JNIEnv *env, jobject) {
Java_com_sukisu_ultra_Natives_getAllowList(JNIEnv *env, jobject) {
int uids[1024];
int size = 0;
bool result = get_allow_list(uids, &size);
@@ -42,13 +42,13 @@ Java_shirkneko_zako_sukisu_Natives_getAllowList(JNIEnv *env, jobject) {
extern "C"
JNIEXPORT jboolean JNICALL
Java_shirkneko_zako_sukisu_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
Java_com_sukisu_ultra_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
return is_safe_mode();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_shirkneko_zako_sukisu_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
Java_com_sukisu_ultra_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
return is_lkm_mode();
}
@@ -111,7 +111,7 @@ static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
extern "C"
JNIEXPORT jobject JNICALL
Java_shirkneko_zako_sukisu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
Java_com_sukisu_ultra_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
return nullptr;
}
@@ -129,7 +129,7 @@ Java_shirkneko_zako_sukisu_Natives_getAppProfile(JNIEnv *env, jobject, jstring p
bool useDefaultProfile = !get_app_profile(key, &profile);
auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile");
auto cls = env->FindClass("com/sukisu/ultra/Natives$Profile");
auto constructor = env->GetMethodID(cls, "<init>", "()V");
auto obj = env->NewObject(cls, constructor);
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
@@ -207,8 +207,8 @@ Java_shirkneko_zako_sukisu_Natives_getAppProfile(JNIEnv *env, jobject, jstring p
extern "C"
JNIEXPORT jboolean JNICALL
Java_shirkneko_zako_sukisu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile");
Java_com_sukisu_ultra_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
auto cls = env->FindClass("com/sukisu/ultra/Natives$Profile");
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
@@ -293,16 +293,16 @@ Java_shirkneko_zako_sukisu_Natives_setAppProfile(JNIEnv *env, jobject clazz, job
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_shirkneko_zako_sukisu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
Java_com_sukisu_ultra_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
return uid_should_umount(uid);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_shirkneko_zako_sukisu_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
Java_com_sukisu_ultra_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
return is_su_enabled();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_shirkneko_zako_sukisu_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
Java_com_sukisu_ultra_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
return set_su_enabled(enabled);
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu
package com.sukisu.ultra
import android.app.Application
import coil.Coil

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu
package com.sukisu.ultra
import android.system.Os

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu
package com.sukisu.ultra
import android.os.Parcelable
import androidx.annotation.Keep
@@ -16,14 +16,14 @@ object Natives {
// 10946: add capabilities
// 10977: change groups_count and groups to avoid overflow write
// 11071: Fix the issue of failing to set a custom SELinux type.
const val MINIMAL_SUPPORTED_KERNEL = 11071
const val MINIMAL_SUPPORTED_KERNEL = 12800
// 11640: Support query working mode, LKM or GKI
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
const val MINIMAL_SUPPORTED_KERNEL_LKM = 12800
// 12040: Support disable sucompat mode
const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
const val MINIMAL_SUPPORTED_SU_COMPAT = 12800
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
const val ROOT_UID = 0
@@ -90,6 +90,14 @@ object Natives {
fun requireNewKernel(): Boolean {
return version < MINIMAL_SUPPORTED_KERNEL
}
fun isKsuValid(pkgName: String?): Boolean {
if (becomeManager(pkgName)) {
return true
} else {
return false
}
}
@Immutable
@Parcelize

View File

@@ -0,0 +1,423 @@
package com.sukisu.ultra.flash
import android.app.Activity
import android.content.Context
import android.net.Uri
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.CheckCircle
import androidx.compose.material.icons.filled.Error
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.documentfile.provider.DocumentFile
import com.sukisu.ultra.R
import com.sukisu.ultra.utils.AssetsUtil
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
data class FlashState(
val isFlashing: Boolean = false,
val isCompleted: Boolean = false,
val progress: Float = 0f,
val currentStep: String = "",
val logs: List<String> = emptyList(),
val error: String = ""
)
class HorizonKernelState {
private val _state = MutableStateFlow(FlashState())
val state: StateFlow<FlashState> = _state.asStateFlow()
fun updateProgress(progress: Float) {
_state.update { it.copy(progress = progress) }
}
fun updateStep(step: String) {
_state.update { it.copy(currentStep = step) }
}
fun addLog(log: String) {
_state.update {
it.copy(logs = it.logs + log)
}
}
fun setError(error: String) {
_state.update { it.copy(error = error) }
}
fun startFlashing() {
_state.update {
it.copy(
isFlashing = true,
isCompleted = false,
progress = 0f,
currentStep = "under preparation...",
logs = emptyList(),
error = ""
)
}
}
fun completeFlashing() {
_state.update { it.copy(isCompleted = true, progress = 1f) }
}
fun reset() {
_state.value = FlashState()
}
}
class HorizonKernelWorker(
private val context: Context,
private val state: HorizonKernelState,
private val slot: String? = null
) : Thread() {
var uri: Uri? = null
private lateinit var filePath: String
private lateinit var binaryPath: String
private var onFlashComplete: (() -> Unit)? = null
private var originalSlot: String? = null
fun setOnFlashCompleteListener(listener: () -> Unit) {
onFlashComplete = listener
}
override fun run() {
state.startFlashing()
state.updateStep(context.getString(R.string.horizon_preparing))
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
try {
state.updateStep(context.getString(R.string.horizon_cleaning_files))
state.updateProgress(0.1f)
cleanup()
if (!rootAvailable()) {
state.setError(context.getString(R.string.root_required))
return
}
state.updateStep(context.getString(R.string.horizon_copying_files))
state.updateProgress(0.2f)
copy()
if (!File(filePath).exists()) {
state.setError(context.getString(R.string.horizon_copy_failed))
return
}
state.updateStep(context.getString(R.string.horizon_extracting_tool))
state.updateProgress(0.4f)
getBinary()
state.updateStep(context.getString(R.string.horizon_patching_script))
state.updateProgress(0.6f)
patch()
state.updateStep(context.getString(R.string.horizon_flashing))
state.updateProgress(0.7f)
// 获取原始槽位信息
if (slot != null) {
state.updateStep(context.getString(R.string.horizon_getting_original_slot))
state.updateProgress(0.72f)
originalSlot = runCommandGetOutput(true, "getprop ro.boot.slot_suffix")
}
// 设置目标槽位
if (!slot.isNullOrEmpty()) {
state.updateStep(context.getString(R.string.horizon_setting_target_slot))
state.updateProgress(0.74f)
runCommand(true, "resetprop -n ro.boot.slot_suffix _$slot")
}
flash()
// 恢复原始槽位
if (!originalSlot.isNullOrEmpty()) {
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
state.updateProgress(0.8f)
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
}
state.updateStep(context.getString(R.string.horizon_flash_complete_status))
state.completeFlashing()
(context as? Activity)?.runOnUiThread {
onFlashComplete?.invoke()
}
} catch (e: Exception) {
state.setError(e.message ?: context.getString(R.string.horizon_unknown_error))
// 恢复原始槽位
if (!originalSlot.isNullOrEmpty()) {
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
state.updateProgress(0.8f)
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
}
}
}
private fun cleanup() {
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
}
private fun copy() {
uri?.let { safeUri ->
context.contentResolver.openInputStream(safeUri)?.use { input ->
FileOutputStream(File(filePath)).use { output ->
input.copyTo(output)
}
}
}
}
private fun getBinary() {
runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}")
if (!File(binaryPath).exists()) {
throw IOException("Failed to extract update-binary")
}
}
private fun patch() {
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
}
private fun flash() {
val process = ProcessBuilder("su")
.redirectErrorStream(true)
.start()
try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n")
// 写入槽位信息到临时文件
slot?.let { selectedSlot ->
writer.write("echo \"$selectedSlot\" > ${context.filesDir.absolutePath}/bootslot\n")
}
// 构建刷写命令
val flashCommand = buildString {
append("sh $binaryPath 3 1 \"$filePath\"")
if (slot != null) {
append(" \"$(cat ${context.filesDir.absolutePath}/bootslot)\"")
}
append(" && touch ${context.filesDir.absolutePath}/done\n")
}
writer.write(flashCommand)
writer.write("exit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.lineSequence().forEach { line ->
if (line.startsWith("ui_print")) {
val logMessage = line.removePrefix("ui_print").trim()
state.addLog(logMessage)
when {
logMessage.contains("extracting", ignoreCase = true) -> {
state.updateProgress(0.75f)
}
logMessage.contains("installing", ignoreCase = true) -> {
state.updateProgress(0.85f)
}
logMessage.contains("complete", ignoreCase = true) -> {
state.updateProgress(0.95f)
}
}
}
}
}
} finally {
process.destroy()
}
if (!File("${context.filesDir.absolutePath}/done").exists()) {
throw IOException(context.getString(R.string.flash_failed_message))
}
}
private fun runCommand(su: Boolean, cmd: String): Int {
val process = ProcessBuilder(if (su) "su" else "sh")
.redirectErrorStream(true)
.start()
return try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.waitFor()
} finally {
process.destroy()
}
}
private fun runCommandGetOutput(su: Boolean, cmd: String): String {
val process = ProcessBuilder(if (su) "su" else "sh")
.redirectErrorStream(true)
.start()
return try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.readText().trim()
}
} catch (_: Exception) {
""
} finally {
process.destroy()
}
}
private fun rootAvailable(): Boolean {
return try {
val process = Runtime.getRuntime().exec("su -c id")
val exitValue = process.waitFor()
exitValue == 0
} catch (_: Exception) {
false
}
}
}
@Composable
fun HorizonKernelFlashProgress(state: FlashState) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.horizon_flash_title),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
progress = { state.progress },
)
Text(
text = state.currentStep,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 4.dp)
)
if (state.logs.isNotEmpty()) {
Text(
text = stringResource(id = R.string.horizon_logs_label),
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.align(Alignment.Start)
.padding(top = 8.dp, bottom = 4.dp)
)
Surface(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 150.dp)
.padding(vertical = 4.dp),
color = MaterialTheme.colorScheme.surface,
tonalElevation = 1.dp,
shape = MaterialTheme.shapes.small
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.verticalScroll(rememberScrollState())
) {
state.logs.forEach { log ->
Text(
text = log,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(vertical = 2.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}
}
}
}
if (state.error.isNotEmpty()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
) {
Icon(
imageVector = Icons.Default.Error,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = state.error,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error
)
}
} else if (state.isCompleted) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
) {
Icon(
imageVector = Icons.Default.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = stringResource(id = R.string.horizon_flash_complete),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary
)
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.profile
package com.sukisu.ultra.profile
/**
* @author weishu

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.profile
package com.sukisu.ultra.profile
/**
* https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui;
package com.sukisu.ultra.ui;
import android.content.Context;
import android.content.Intent;
@@ -17,7 +17,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import shirkneko.zako.sukisu.IKsuInterface;
import com.sukisu.zako.IKsuInterface;
import rikka.parcelablelist.ParcelableListSlice;
/**

View File

@@ -0,0 +1,168 @@
package com.sukisu.ultra.ui
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
import com.ramcosta.composedestinations.generated.NavGraphs
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
import com.ramcosta.composedestinations.spec.RouteOrDirection
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import io.sukisu.ultra.UltraToolInstall
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.ui.screen.BottomBarDestination
import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.util.*
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Enable edge to edge
enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
super.onCreate(savedInstanceState)
// 加载保存的背景设置
loadCustomBackground()
loadThemeMode()
CardConfig.load(applicationContext)
val isManager = Natives.becomeManager(ksuApp.packageName)
if (isManager) {
install()
UltraToolInstall.tryToInstall()
}
setContent {
KernelSUTheme {
val navController = rememberNavController()
val snackBarHostState = remember { SnackbarHostState() }
Scaffold(
bottomBar = { BottomBar(navController) },
contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { innerPadding ->
CompositionLocalProvider(
LocalSnackbarHost provides snackBarHostState,
) {
DestinationsNavHost(
modifier = Modifier.padding(innerPadding),
navGraph = NavGraphs.root as NavHostGraphSpec,
navController = navController,
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
get() = { fadeIn(animationSpec = tween(340)) }
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
get() = { fadeOut(animationSpec = tween(340)) }
}
)
}
}
}
}
}
}
@Composable
private fun BottomBar(navController: NavHostController) {
val navigator = navController.rememberDestinationsNavigator()
val isManager = Natives.becomeManager(ksuApp.packageName)
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
val kpmVersion = getKpmVersion()
// 获取卡片颜色和透明度
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
val cardElevation = CardConfig.cardElevation
NavigationBar(
tonalElevation = cardElevation, // 动态设置阴影
containerColor = cardColor.copy(alpha = cardAlpha),
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
)
) {
BottomBarDestination.entries.forEach { destination ->
if (destination == BottomBarDestination.Kpm) {
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
if (!fullFeatured && destination.rootRequired) return@forEach
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
NavigationBarItem(
selected = isCurrentDestOnBackStack,
onClick = {
if (isCurrentDestOnBackStack) {
navigator.popBackStack(destination.direction, false)
}
navigator.navigate(destination.direction) {
popUpTo(NavGraphs.root as RouteOrDirection) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
}
},
label = { Text(stringResource(destination.label)) },
alwaysShowLabel = false,
colors = NavigationBarItemDefaults.colors(
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
)
)
}
} else {
if (!fullFeatured && destination.rootRequired) return@forEach
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
NavigationBarItem(
selected = isCurrentDestOnBackStack,
onClick = {
if (isCurrentDestOnBackStack) {
navigator.popBackStack(destination.direction, false)
}
navigator.navigate(destination.direction) {
popUpTo(NavGraphs.root as RouteOrDirection) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
}
},
label = { Text(stringResource(destination.label)) },
alwaysShowLabel = false,
)
}
}
}
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.component
package com.sukisu.ultra.ui.component
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
@@ -31,8 +31,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import shirkneko.zako.sukisu.BuildConfig
import shirkneko.zako.sukisu.R
import com.sukisu.ultra.BuildConfig
import com.sukisu.ultra.R
@Preview
@Composable
@@ -98,7 +98,7 @@ private fun AboutCardContent() {
val annotatedString = AnnotatedString.Companion.fromHtml(
htmlString = stringResource(
id = R.string.about_source_code,
"<b><a href=\"https://github.com/ShirkNeko/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://github.com/ShirkNeko/SukiSU-Ultra\">GitHub</a></b>",
"<b><a href=\"https://t.me/SukiKSU\">Telegram</a></b>"
),
linkStyles = TextLinkStyles(

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.component
package com.sukisu.ultra.ui.component
import android.graphics.text.LineBreaker
import android.os.Build

View File

@@ -0,0 +1,224 @@
package com.sukisu.ultra.ui.component
import android.net.Uri
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Fullscreen
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.util.BackgroundTransformation
import com.sukisu.ultra.ui.util.saveTransformedBackground
import kotlinx.coroutines.launch
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.layout.onSizeChanged
import kotlin.math.max
@Composable
fun ImageEditorDialog(
imageUri: Uri,
onDismiss: () -> Unit,
onConfirm: (Uri) -> Unit
) {
var scale by remember { mutableFloatStateOf(1f) }
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
val context = LocalContext.current
val scope = rememberCoroutineScope()
val density = LocalDensity.current
var lastScale by remember { mutableFloatStateOf(1f) }
var lastOffsetX by remember { mutableFloatStateOf(0f) }
var lastOffsetY by remember { mutableFloatStateOf(0f) }
var imageSize by remember { mutableStateOf(Size.Zero) }
var screenSize by remember { mutableStateOf(Size.Zero) }
val animatedScale by animateFloatAsState(
targetValue = scale,
label = "ScaleAnimation"
)
val animatedOffsetX by animateFloatAsState(
targetValue = offsetX,
label = "OffsetXAnimation"
)
val animatedOffsetY by animateFloatAsState(
targetValue = offsetY,
label = "OffsetYAnimation"
)
val updateTransformation = remember {
{ newScale: Float, newOffsetX: Float, newOffsetY: Float ->
val scaleDiff = kotlin.math.abs(newScale - lastScale)
val offsetXDiff = kotlin.math.abs(newOffsetX - lastOffsetX)
val offsetYDiff = kotlin.math.abs(newOffsetY - lastOffsetY)
if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) {
scale = newScale
offsetX = newOffsetX
offsetY = newOffsetY
lastScale = newScale
lastOffsetX = newOffsetX
lastOffsetY = newOffsetY
}
}
}
val scaleToFullScreen = remember {
{
if (imageSize.height > 0 && screenSize.height > 0) {
val newScale = screenSize.height / imageSize.height
updateTransformation(newScale, 0f, 0f)
}
}
}
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = false,
usePlatformDefaultWidth = false
)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.9f))
.onSizeChanged { size ->
screenSize = Size(size.width.toFloat(), size.height.toFloat())
}
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = stringResource(R.string.settings_custom_background),
contentScale = ContentScale.Fit,
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
scaleX = animatedScale,
scaleY = animatedScale,
translationX = animatedOffsetX,
translationY = animatedOffsetY
)
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
scope.launch {
try {
val newScale = (scale * zoom).coerceIn(0.5f, 3f)
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
val newOffsetX = if (maxOffsetX > 0) {
(offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
} else {
0f
}
val newOffsetY = if (maxOffsetY > 0) {
(offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
} else {
0f
}
updateTransformation(newScale, newOffsetX, newOffsetY)
} catch (e: Exception) {
updateTransformation(lastScale, lastOffsetX, lastOffsetY)
}
}
}
}
.onSizeChanged { size ->
imageSize = Size(size.width.toFloat(), size.height.toFloat())
}
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.align(Alignment.TopCenter),
horizontalArrangement = Arrangement.SpaceBetween
) {
IconButton(
onClick = onDismiss,
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.cancel),
tint = Color.White
)
}
IconButton(
onClick = { scaleToFullScreen() },
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
) {
Icon(
imageVector = Icons.Default.Fullscreen,
contentDescription = stringResource(R.string.reprovision),
tint = Color.White
)
}
IconButton(
onClick = {
scope.launch {
try {
val transformation = BackgroundTransformation(scale, offsetX, offsetY)
val savedUri = context.saveTransformedBackground(imageUri, transformation)
savedUri?.let { onConfirm(it) }
} catch (e: Exception) {
""
}
}
},
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = stringResource(R.string.confirm),
tint = Color.White
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
.padding(16.dp)
.align(Alignment.BottomCenter)
) {
Text(
text = stringResource(id = R.string.image_editor_hint),
color = Color.White,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.component
package com.sukisu.ultra.ui.component
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.component
package com.sukisu.ultra.ui.component
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
@@ -42,7 +42,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import shirkneko.zako.sukisu.ui.theme.CardConfig
import com.sukisu.ultra.ui.theme.CardConfig
private const val TAG = "SearchBar"

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.component
package com.sukisu.ultra.ui.component
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource

View File

@@ -0,0 +1,210 @@
package com.sukisu.ultra.ui.component
import android.content.Context
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme.getCardElevation
import androidx.compose.foundation.shape.CornerSize
/**
* 槽位选择对话框组件
* 用于HorizonKernel刷写时选择目标槽位
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SlotSelectionDialog(
show: Boolean,
onDismiss: () -> Unit,
onSlotSelected: (String) -> Unit
) {
val context = LocalContext.current
var currentSlot by remember { mutableStateOf<String?>(null) }
var errorMessage by remember { mutableStateOf<String?>(null) }
LaunchedEffect(Unit) {
try {
currentSlot = getCurrentSlot(context)
errorMessage = null
} catch (e: Exception) {
errorMessage = e.message
currentSlot = null
}
}
if (show) {
val backgroundColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast.copy(alpha = 1.0f)
} else {
MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 1.0f)
}
Dialog(onDismissRequest = onDismiss) {
Card(
shape = MaterialTheme.shapes.medium.copy(
topStart = CornerSize(16.dp),
topEnd = CornerSize(16.dp),
bottomEnd = CornerSize(16.dp),
bottomStart = CornerSize(16.dp)
),
colors = CardDefaults.cardColors(
containerColor = backgroundColor
),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Column(
modifier = Modifier
.padding(24.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.select_slot_title),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 16.dp)
)
if (errorMessage != null) {
Text(
text = "Error: $errorMessage",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
textAlign = TextAlign.Center
)
} else {
Text(
text = stringResource(
id = R.string.current_slot,
currentSlot ?: "Unknown"
),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(id = R.string.select_slot_description),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
val isDefaultSlotA = currentSlot == "_a" || currentSlot == "a"
Button(
onClick = { onSlotSelected("a") },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = if (isDefaultSlotA)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.primaryContainer,
contentColor = if (isDefaultSlotA)
MaterialTheme.colorScheme.onPrimary
else
MaterialTheme.colorScheme.onPrimaryContainer
)
) {
Text(
text = stringResource(id = R.string.slot_a),
style = MaterialTheme.typography.labelLarge
)
}
val isDefaultSlotB = currentSlot == "_b" || currentSlot == "b"
Button(
onClick = { onSlotSelected("b") },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = if (isDefaultSlotB)
MaterialTheme.colorScheme.secondary
else
MaterialTheme.colorScheme.secondaryContainer,
contentColor = if (isDefaultSlotB)
MaterialTheme.colorScheme.onSecondary
else
MaterialTheme.colorScheme.onSecondaryContainer
)
) {
Text(
text = stringResource(id = R.string.slot_b),
style = MaterialTheme.typography.labelLarge
)
}
}
Spacer(modifier = Modifier.height(24.dp))
HorizontalDivider(
modifier = Modifier.fillMaxWidth(),
thickness = 1.dp,
color = MaterialTheme.colorScheme.outline
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
TextButton(
onClick = onDismiss,
modifier = Modifier.weight(1f)
) {
Text(text = stringResource(id = android.R.string.cancel))
}
TextButton(
onClick = {
currentSlot?.let { onSlotSelected(it) }
onDismiss()
},
modifier = Modifier.weight(1f)
) {
Text(text = stringResource(id = android.R.string.ok))
}
}
}
}
}
}
}
// 获取当前槽位信息
private fun getCurrentSlot(context: Context): String? {
return runCommandGetOutput(true, "getprop ro.boot.slot_suffix")?.let {
if (it.startsWith("_")) it.substring(1) else it
}
}
private fun runCommandGetOutput(su: Boolean, cmd: String): String? {
return try {
val process = ProcessBuilder(if (su) "su" else "sh").start()
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.readText().trim()
}
} catch (_: Exception) {
null
}
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.component
package com.sukisu.ultra.ui.component
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.component.profile
package com.sukisu.ultra.ui.component.profile
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.OutlinedTextField
@@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SwitchItem
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.SwitchItem
@Composable
fun AppProfileConfig(

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.component.profile
package com.sukisu.ultra.ui.component.profile
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
@@ -42,12 +42,12 @@ import com.maxkeppeler.sheets.input.models.ValidationResult
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.profile.Capabilities
import shirkneko.zako.sukisu.profile.Groups
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.util.isSepolicyValid
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.profile.Capabilities
import com.sukisu.ultra.profile.Groups
import com.sukisu.ultra.ui.component.rememberCustomDialog
import com.sukisu.ultra.ui.util.isSepolicyValid
@OptIn(ExperimentalMaterial3Api::class)
@Composable

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.component.profile
package com.sukisu.ultra.ui.component.profile
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
@@ -23,11 +23,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
import shirkneko.zako.sukisu.ui.util.setSepolicy
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.util.listAppProfileTemplates
import com.sukisu.ultra.ui.util.setSepolicy
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
/**
* @author weishu

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import androidx.annotation.StringRes
import androidx.compose.animation.Crossfade
@@ -64,20 +64,20 @@ import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplat
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.component.profile.AppProfileConfig
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
import shirkneko.zako.sukisu.ui.component.profile.TemplateConfig
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.forceStopApp
import shirkneko.zako.sukisu.ui.util.getSepolicy
import shirkneko.zako.sukisu.ui.util.launchApp
import shirkneko.zako.sukisu.ui.util.restartApp
import shirkneko.zako.sukisu.ui.util.setSepolicy
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.SwitchItem
import com.sukisu.ultra.ui.component.profile.AppProfileConfig
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
import com.sukisu.ultra.ui.component.profile.TemplateConfig
import com.sukisu.ultra.ui.util.LocalSnackbarHost
import com.sukisu.ultra.ui.util.forceStopApp
import com.sukisu.ultra.ui.util.getSepolicy
import com.sukisu.ultra.ui.util.launchApp
import com.sukisu.ultra.ui.util.restartApp
import com.sukisu.ultra.ui.util.setSepolicy
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
/**
* @author weishu

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
@@ -11,7 +11,7 @@ import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDe
import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination
import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
import shirkneko.zako.sukisu.R
import com.sukisu.ultra.R
enum class BottomBarDestination(
val direction: DirectionDestinationSpec,
@@ -21,8 +21,8 @@ enum class BottomBarDestination(
val rootRequired: Boolean,
) {
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false),
Kpm(KpmScreenDestination, R.string.kpm_title, Icons.Filled.Build, Icons.Outlined.Build, true),
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true),
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true),
Kpm(KpmScreenDestination, R.string.kpm_title, Icons.Filled.Build, Icons.Outlined.Build, true),
Settings(SettingScreenDestination, R.string.settings, Icons.Filled.Settings, Icons.Outlined.Settings, false),
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import android.os.Environment
import androidx.compose.foundation.layout.Column
@@ -37,10 +37,10 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.runModuleAction
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.KeyEventBlocker
import com.sukisu.ultra.ui.util.LocalSnackbarHost
import com.sukisu.ultra.ui.util.runModuleAction
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import android.net.Uri
import android.os.Environment
@@ -30,9 +30,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
import shirkneko.zako.sukisu.ui.util.*
import com.sukisu.ultra.ui.component.KeyEventBlocker
import com.sukisu.ultra.ui.util.*
import com.sukisu.ultra.R
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
@@ -45,10 +45,6 @@ enum class FlashingStatus {
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
fun getFlashingStatus(): FlashingStatus {
return currentFlashingStatus.value
}
fun setFlashingStatus(status: FlashingStatus) {
currentFlashingStatus.value = status
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import android.annotation.SuppressLint
import android.content.Context
@@ -33,15 +33,15 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.*
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.util.*
import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo
import com.sukisu.ultra.*
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.util.*
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import shirkneko.zako.sukisu.ui.theme.getCardColors
import shirkneko.zako.sukisu.ui.theme.getCardElevation
import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
@@ -49,8 +49,13 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.runtime.saveable.rememberSaveable
import shirkneko.zako.sukisu.ui.theme.CardConfig
import androidx.compose.ui.graphics.vector.ImageVector
import com.sukisu.ultra.ui.theme.CardConfig
import androidx.core.content.edit
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.zip.GZIPInputStream
import kotlin.random.Random
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>(start = true)
@@ -59,6 +64,8 @@ fun HomeScreen(navigator: DestinationsNavigator) {
val context = LocalContext.current
var isSimpleMode by rememberSaveable { mutableStateOf(false) }
var isHideVersion by rememberSaveable { mutableStateOf(false) }
var isHideOtherInfo by rememberSaveable { mutableStateOf(false) }
var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) }
// 从 SharedPreferences 加载简洁模式状态
LaunchedEffect(Unit) {
@@ -70,9 +77,36 @@ fun HomeScreen(navigator: DestinationsNavigator) {
isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_version", false)
}
// 从 SharedPreferences 加载隐藏模块数量等信息开关状态
LaunchedEffect(Unit) {
isHideOtherInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_other_info", false)
}
// 从 SharedPreferences 加载隐藏 SuSFS 状态开关状态
LaunchedEffect(Unit) {
isHideSusfsStatus = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_susfs_status", false)
}
val kernelVersion = getKernelVersion()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val isManager = Natives.becomeManager(ksuApp.packageName)
val deviceModel = getDeviceModel(context)
val ksuVersion = if (isManager) Natives.version else null
val zako = "一.*加.*A.*c.*e.*5.*P.*r.*o".toRegex().matches(deviceModel)
val isVersion = ksuVersion == 12777
val shouldTriggerRestart = zako && kernelVersion.isGKI() && (isVersion)
LaunchedEffect(shouldTriggerRestart) {
if (shouldTriggerRestart) {
val random = Random.nextInt(0, 100)
if (random <= 95) {
reboot()
} else {
""
}
}
}
Scaffold(
topBar = {
@@ -96,6 +130,10 @@ fun HomeScreen(navigator: DestinationsNavigator) {
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (shouldTriggerRestart) {
WarningCard(message = "zakozako")
return@Column
}
val isManager = Natives.becomeManager(ksuApp.packageName)
val ksuVersion = if (isManager) Natives.version else null
val lkmMode = ksuVersion?.let {
@@ -155,10 +193,10 @@ fun HomeScreen(navigator: DestinationsNavigator) {
}
InfoCard()
if (!isSimpleMode) {
ContributionCard()
DonateCard()
LearnMoreCard()
}
Spacer(Modifier)
}
}
@@ -246,22 +284,27 @@ private fun TopBar(
}
var showDropdown by remember { mutableStateOf(false) }
IconButton(onClick = { showDropdown = true }) {
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }
) {
if (Natives.isKsuValid(ksuApp.packageName)) {
IconButton(onClick = { showDropdown = true }) {
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
DropdownMenu(
expanded = showDropdown,
onDismissRequest = { showDropdown = false }
) {
RebootDropdownItem(id = R.string.reboot)
RebootDropdownItem(id = R.string.reboot)
val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
val pm =
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
}
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
}
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
}
}
},
@@ -270,6 +313,7 @@ private fun TopBar(
)
}
@Composable
private fun StatusCard(
kernelVersion: KernelVersion,
@@ -308,6 +352,12 @@ private fun StatusCard(
val isHideVersion = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_version", false)
val isHideOtherInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_other_info", false)
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_susfs_status", false)
Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working))
Column(Modifier.padding(start = 20.dp)) {
Text(
@@ -321,30 +371,44 @@ private fun StatusCard(
style = MaterialTheme.typography.bodyMedium
)
}
Spacer(Modifier.height(4.dp))
Text(
text = stringResource(
R.string.home_superuser_count, getSuperuserCount()
), style = MaterialTheme.typography.bodyMedium
)
Spacer(Modifier.height(4.dp))
Text(
text = stringResource(R.string.home_module_count, getModuleCount()),
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(4.dp))
val suSFS = getSuSFS()
val translatedStatus = when (suSFS) {
"Supported" -> stringResource(R.string.status_supported)
"Not Supported" -> stringResource(R.string.status_not_supported)
else -> stringResource(R.string.status_unknown)
if (!isHideOtherInfo) {
Spacer(Modifier.height(4.dp))
Text(
text = stringResource(
R.string.home_superuser_count, getSuperuserCount()
), style = MaterialTheme.typography.bodyMedium
)
Spacer(Modifier.height(4.dp))
Text(
text = stringResource(R.string.home_module_count, getModuleCount()),
style = MaterialTheme.typography.bodyMedium
)
val kpmVersion = getKpmVersion()
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
Spacer(Modifier.height(4.dp))
Text(
text = stringResource(R.string.home_kpm_module, getKpmModuleCount()),
style = MaterialTheme.typography.bodyMedium
)
}
}
if (!isHideSusfsStatus) {
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(R.string.home_susfs, translatedStatus),
style = MaterialTheme.typography.bodyMedium
)
val suSFS = getSuSFS()
if (lkmMode != true) {
val translatedStatus = when (suSFS) {
"Supported" -> stringResource(R.string.status_supported)
"Not Supported" -> stringResource(R.string.status_not_supported)
else -> stringResource(R.string.status_unknown)
}
Text(
text = stringResource(R.string.home_susfs, translatedStatus),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
@@ -402,6 +466,38 @@ fun WarningCard(
}
}
}
@Composable
fun ContributionCard() {
val uriHandler = LocalUriHandler.current
val links = listOf("https://github.com/zako", "https://github.com/udochina")
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
val randomIndex = Random.nextInt(links.size)
uriHandler.openUri(links[randomIndex])
}
.padding(24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
text = stringResource(R.string.home_ContributionCard_kernelsu),
style = MaterialTheme.typography.titleSmall
)
Spacer(Modifier.height(4.dp))
Text(
text = stringResource(R.string.home_click_to_ContributionCard_kernelsu),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
@Composable
fun LearnMoreCard() {
@@ -466,6 +562,7 @@ fun DonateCard() {
@Composable
private fun InfoCard() {
val lkmMode = Natives.isLkmMode
val context = LocalContext.current
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_simple_mode", false)
@@ -477,8 +574,8 @@ private fun InfoCard() {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)
) {
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp),
) withContext@{
val contents = StringBuilder()
val uname = Os.uname()
@@ -486,72 +583,86 @@ private fun InfoCard() {
fun InfoCardItem(
label: String,
content: String,
icon: ImageVector = Icons.Default.Info
) {
contents.appendLine(label).appendLine(content).appendLine()
Text(text = label, style = MaterialTheme.typography.bodyLarge)
Text(text = content, style = MaterialTheme.typography.bodyMedium)
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = icon,
contentDescription = label,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(text = label, style = MaterialTheme.typography.bodyLarge)
Text(text = content, style = MaterialTheme.typography.bodyMedium)
}
}
}
InfoCardItem(stringResource(R.string.home_kernel), uname.release)
InfoCardItem(stringResource(R.string.home_kernel), uname.release, icon = Icons.Default.Memory)
if (!isSimpleMode) {
Spacer(Modifier.height(16.dp))
val androidVersion = Build.VERSION.RELEASE
InfoCardItem(stringResource(R.string.home_android_version), androidVersion)
InfoCardItem(stringResource(R.string.home_android_version), androidVersion, icon = Icons.Default.Android)
}
Spacer(Modifier.height(16.dp))
val deviceModel = getDeviceModel(context)
InfoCardItem(stringResource(R.string.home_device_model), deviceModel, icon = Icons.Default.PhoneAndroid)
Spacer(Modifier.height(16.dp))
val deviceModel = getDeviceModel(context)
InfoCardItem(stringResource(R.string.home_device_model), deviceModel)
Spacer(Modifier.height(16.dp))
val managerVersion = getManagerVersion(context)
InfoCardItem(
stringResource(R.string.home_manager_version),
"${managerVersion.first} (${managerVersion.second})"
)
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())
Spacer(Modifier.height(16.dp))
val managerVersion = getManagerVersion(context)
InfoCardItem(stringResource(R.string.home_manager_version), "${managerVersion.first} (${managerVersion.second})", icon = Icons.Default.Settings)
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus(), icon = Icons.Default.Security)
if (!isSimpleMode) {
if (lkmMode != true) {
val kpmVersion = getKpmVersion()
var displayVersion: String
val isKpmConfigured = checkKpmConfigured()
if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) {
val statusText = if (isKpmConfigured) {
stringResource(R.string.kernel_patched)
} else {
stringResource(R.string.kernel_not_enabled)
}
displayVersion = "${stringResource(R.string.not_supported)} ($statusText)"
} else {
displayVersion = "${stringResource(R.string.supported)} ($kpmVersion)"
}
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_kpm_version), displayVersion, icon = Icons.Default.Code)
}
}
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_susfs_status", false)
if ((!isSimpleMode) && (!isHideSusfsStatus)) {
Spacer(modifier = Modifier.height(16.dp))
val suSFS = getSuSFS()
if (suSFS == "Supported") {
val suSFSVersion = getSuSFSVersion()
if (suSFSVersion.isEmpty()) return@withContext
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
val infoText = buildString {
append(suSFSVersion)
append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})")
if (isSUS_SU) {
val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" }
if (susSUMode.isNotEmpty()) {
append(" ${stringResource(R.string.sus_su_mode)} $susSUMode")
}
}
}
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})"
)
} else {
val susSUMode = try {
susfsSUS_SU_Mode()
} catch (e: Exception) {
0
}
if (susSUMode == 2 || susSUMode == 0) {
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
val susSUModeLabel = stringResource(R.string.sus_su_mode)
val susSUModeValue = susSUMode.toString()
val susSUModeText = if (isSUS_SU) " $susSUModeLabel $susSUModeValue" else ""
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${getSuSFSVariant()})$susSUModeText"
)
} else {
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})"
)
}
stringResource(R.string.home_susfs_version), infoText, icon = Icons.Default.Storage)
}
}
}
@@ -605,7 +716,27 @@ private fun getDeviceModel(context: Context): String {
}
}
Build.DEVICE
} catch (e: Exception) {
} catch (_: Exception) {
Build.DEVICE
}
}
private fun checkKpmConfigured(): Boolean {
try {
val process = Runtime.getRuntime().exec("su -c cat /proc/config.gz")
val inputStream = process.inputStream
val gzipInputStream = GZIPInputStream(inputStream)
val reader = BufferedReader(InputStreamReader(gzipInputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line?.contains("CONFIG_KPM=y") == true) {
return true
}
}
reader.close()
} catch (e: Exception) {
e.printStackTrace()
}
return false
}

View File

@@ -1,14 +1,19 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
@@ -27,27 +32,25 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.documentfile.provider.DocumentFile
import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.DialogHandle
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.util.*
import shirkneko.zako.sukisu.utils.AssetsUtil
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.DialogHandle
import com.sukisu.ultra.ui.component.SlotSelectionDialog
import com.sukisu.ultra.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.component.rememberCustomDialog
import com.sukisu.ultra.flash.HorizonKernelFlashProgress
import com.sukisu.ultra.flash.HorizonKernelState
import com.sukisu.ultra.flash.HorizonKernelWorker
import com.sukisu.ultra.ui.theme.CardConfig
import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation
import com.sukisu.ultra.ui.util.*
/**
* @author weishu
@@ -60,8 +63,12 @@ fun InstallScreen(navigator: DestinationsNavigator) {
var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
val context = LocalContext.current
var showRebootDialog by remember { mutableStateOf(false) }
var showSlotSelectionDialog by remember { mutableStateOf(false) }
var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
val horizonKernelState = remember { HorizonKernelState() }
val flashState by horizonKernelState.state.collectAsState()
val summary = stringResource(R.string.horizon_kernel_summary)
val onFlashComplete = {
showRebootDialog = true
@@ -79,7 +86,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
writer.write("svc power reboot\n")
writer.write("exit\n")
}
} catch (e: Exception) {
} catch (_: Exception) {
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
}
}
@@ -91,7 +98,11 @@ fun InstallScreen(navigator: DestinationsNavigator) {
when (method) {
is InstallMethod.HorizonKernel -> {
method.uri?.let { uri ->
val worker = HorizonKernelWorker(context)
val worker = HorizonKernelWorker(
context = context,
state = horizonKernelState,
slot = method.slot
)
worker.uri = uri
worker.setOnFlashCompleteListener(onFlashComplete)
worker.start()
@@ -110,6 +121,22 @@ fun InstallScreen(navigator: DestinationsNavigator) {
Unit
}
// 槽位选择
SlotSelectionDialog(
show = showSlotSelectionDialog,
onDismiss = { showSlotSelectionDialog = false },
onSlotSelected = { slot ->
showSlotSelectionDialog = false
val horizonMethod = InstallMethod.HorizonKernel(
uri = tempKernelUri,
slot = slot,
summary = summary
)
installMethod = horizonMethod
onInstall()
}
)
val currentKmi by produceState(initialValue = "") {
value = getCurrentKmi()
}
@@ -165,8 +192,24 @@ fun InstallScreen(navigator: DestinationsNavigator) {
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
) {
SelectInstallMethod { method ->
installMethod = method
SelectInstallMethod(
onSelected = { method ->
if (method is InstallMethod.HorizonKernel && method.uri != null && method.slot == null) {
tempKernelUri = method.uri
showSlotSelectionDialog = true
} else {
installMethod = method
}
horizonKernelState.reset()
}
)
AnimatedVisibility(
visible = flashState.isFlashing && installMethod is InstallMethod.HorizonKernel,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
HorizonKernelFlashProgress(flashState)
}
Column(
@@ -182,9 +225,20 @@ fun InstallScreen(navigator: DestinationsNavigator) {
)
)
}
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
if (method.slot != null) {
Text(
stringResource(
id = R.string.selected_slot,
if (method.slot == "a") stringResource(id = R.string.slot_a)
else stringResource(id = R.string.slot_b)
)
)
}
}
Button(
modifier = Modifier.fillMaxWidth(),
enabled = installMethod != null,
enabled = installMethod != null && !flashState.isFlashing,
onClick = onClickNext
) {
Text(
@@ -197,14 +251,6 @@ fun InstallScreen(navigator: DestinationsNavigator) {
}
}
private fun launchHorizonKernelFlash(context: Context, uri: Uri) {
val worker = HorizonKernelWorker(context)
worker.uri = uri
worker.setOnFlashCompleteListener {
}
worker.start()
}
@Composable
private fun RebootDialog(
show: Boolean,
@@ -230,133 +276,6 @@ private fun RebootDialog(
}
}
private class HorizonKernelWorker(private val context: Context) : Thread() {
var uri: Uri? = null
private lateinit var filePath: String
private lateinit var binaryPath: String
private var onFlashComplete: (() -> Unit)? = null
fun setOnFlashCompleteListener(listener: () -> Unit) {
onFlashComplete = listener
}
override fun run() {
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
try {
cleanup()
if (!rootAvailable()) {
showError(context.getString(R.string.root_required))
return
}
copy()
if (!File(filePath).exists()) {
showError(context.getString(R.string.copy_failed))
return
}
getBinary()
patch()
flash()
(context as? Activity)?.runOnUiThread {
onFlashComplete?.invoke()
}
} catch (e: Exception) {
showError(e.message ?: context.getString(R.string.unknown_error))
}
}
private fun cleanup() {
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
}
private fun copy() {
uri?.let { safeUri ->
context.contentResolver.openInputStream(safeUri)?.use { input ->
FileOutputStream(File(filePath)).use { output ->
input.copyTo(output)
}
}
}
}
private fun getBinary() {
runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}")
if (!File(binaryPath).exists()) {
throw IOException("Failed to extract update-binary")
}
}
private fun patch() {
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
}
private fun flash() {
val process = ProcessBuilder("su")
.redirectErrorStream(true)
.start()
try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n")
writer.write("sh $binaryPath 3 1 \"$filePath\" && touch ${context.filesDir.absolutePath}/done\nexit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.lineSequence().forEach { line ->
if (line.startsWith("ui_print")) {
showLog(line.removePrefix("ui_print"))
}
}
}
} finally {
process.destroy()
}
if (!File("${context.filesDir.absolutePath}/done").exists()) {
throw IOException("Flash failed")
}
}
private fun runCommand(su: Boolean, cmd: String): Int {
val process = ProcessBuilder(if (su) "su" else "sh")
.redirectErrorStream(true)
.start()
return try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.waitFor()
} finally {
process.destroy()
}
}
private fun showError(message: String) {
(context as? Activity)?.runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
}
private fun showLog(message: String) {
(context as? Activity)?.runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
}
sealed class InstallMethod {
data class SelectFile(
val uri: Uri? = null,
@@ -376,6 +295,7 @@ sealed class InstallMethod {
data class HorizonKernel(
val uri: Uri? = null,
val slot: String? = null,
@StringRes override val label: Int = R.string.horizon_kernel,
override val summary: String? = null
) : InstallMethod()
@@ -388,6 +308,7 @@ sealed class InstallMethod {
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice()
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
val selectFileTip = stringResource(
id = R.string.select_file_tip,
if (isInitBoot()) "init_boot" else "boot"
@@ -402,7 +323,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
if (isAbDevice) {
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
}
radioOptions.add(InstallMethod.HorizonKernel(summary = "Flashing the Anykernel3 Kernel"))
radioOptions.add(InstallMethod.HorizonKernel(summary = horizonKernelSummary))
}
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
@@ -415,7 +336,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
it.data?.data?.let { uri ->
val option = when (currentSelectingMethod) {
is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip)
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = " Flashing the Anykernel3 Kernel")
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = horizonKernelSummary)
else -> null
}
option?.let {
@@ -506,39 +427,77 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
val supportedKmi by produceState(initialValue = emptyList<String>()) {
value = getSupportedKmis()
}
val options = supportedKmi.map { value ->
val listOptions = supportedKmi.map { value ->
ListOption(
titleText = value
titleText = value,
subtitleText = null,
icon = null
)
}
var selection by remember { mutableStateOf<String?>(null) }
Surface(
color = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
shape = MaterialTheme.shapes.medium
) {
ListDialog(
state = rememberUseCaseState(
visible = true,
onFinishedRequest = {
onSelected(selection)
},
onCloseRequest = {
var selection: String? = null
val cardColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast
} else {
MaterialTheme.colorScheme.secondaryContainer
}
AlertDialog(
onDismissRequest = {
dismiss()
},
title = {
Text(text = stringResource(R.string.select_kmi))
},
text = {
Column {
listOptions.forEachIndexed { index, option ->
Row(
modifier = Modifier
.clickable {
selection = supportedKmi[index]
}
.padding(vertical = 8.dp)
) {
Column {
Text(text = option.titleText)
option.subtitleText?.let {
Text(
text = it,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
},
confirmButton = {
TextButton(
onClick = {
if (selection != null) {
onSelected(selection)
}
dismiss()
}
),
header = Header.Default(
title = stringResource(R.string.select_kmi),
),
selection = ListSelection.Single(
showRadioButtons = true,
options = options,
) { _, option ->
selection = option.titleText
) {
Text(text = stringResource(android.R.string.ok))
}
)
}
},
dismissButton = {
TextButton(
onClick = {
dismiss()
}
) {
Text(text = stringResource(android.R.string.cancel))
}
},
containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f),
shape = MaterialTheme.shapes.medium,
tonalElevation = getCardElevation()
)
}
}
@@ -549,8 +508,15 @@ private fun TopBar(
onLkmUpload: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
TopAppBar(
title = { Text(stringResource(R.string.install)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
),
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)

View File

@@ -0,0 +1,631 @@
package com.sukisu.ultra.ui.screen
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import com.sukisu.ultra.ui.component.*
import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.viewmodel.KpmViewModel
import com.sukisu.ultra.ui.util.*
import java.io.File
import androidx.core.content.edit
import com.sukisu.ultra.R
import java.io.BufferedReader
import java.io.FileInputStream
import java.io.InputStreamReader
import java.net.*
/**
* KPM 管理界面
* 以下内核模块功能由KernelPatch开发经过修改后加入SukiSU Ultra的内核模块功能
* 开发者ShirkNeko, Liaokong
*/
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun KpmScreen(
navigator: DestinationsNavigator,
viewModel: KpmViewModel = viewModel()
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val snackBarHost = remember { SnackbarHostState() }
val confirmDialog = rememberConfirmDialog()
val cardColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast
} else {
MaterialTheme.colorScheme.secondaryContainer
}
val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
val moduleFileName = module.id
module.id to stringResource(R.string.confirm_uninstall_content, moduleFileName)
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
val kpmInstallFailed = stringResource(R.string.kpm_install_failed)
val cancel = stringResource(R.string.cancel)
val uninstall = stringResource(R.string.uninstall)
val failedToCheckModuleFile = stringResource(R.string.snackbar_failed_to_check_module_file)
val kpmUninstallSuccess = stringResource(R.string.kpm_uninstall_success)
val kpmUninstallFailed = stringResource(R.string.kpm_uninstall_failed)
val kpmInstallMode = stringResource(R.string.kpm_install_mode)
val kpmInstallModeLoad = stringResource(R.string.kpm_install_mode_load)
val kpmInstallModeEmbed = stringResource(R.string.kpm_install_mode_embed)
val invalidFileTypeMessage = stringResource(R.string.invalid_file_type)
val confirmTitle = stringResource(R.string.confirm_uninstall_title_with_filename)
var tempFileForInstall by remember { mutableStateOf<File?>(null) }
val installModeDialog = rememberCustomDialog { dismiss ->
var moduleName by remember { mutableStateOf<String?>(null) }
LaunchedEffect(tempFileForInstall) {
tempFileForInstall?.let { tempFile ->
try {
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
val process = Runtime.getRuntime().exec(command)
val inputStream = process.inputStream
val reader = BufferedReader(InputStreamReader(inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line!!.startsWith("name=")) {
moduleName = line.substringAfter("name=").trim()
break
}
}
process.waitFor()
} catch (e: Exception) {
Log.e("KsuCli", "Failed to get module name: ${e.message}", e)
}
}
}
AlertDialog(
onDismissRequest = {
dismiss()
tempFileForInstall?.delete()
tempFileForInstall = null
},
title = { Text(kpmInstallMode) },
text = { moduleName?.let { Text(stringResource(R.string.kpm_install_mode_description, it)) } },
confirmButton = {
Column {
Button(
onClick = {
scope.launch {
dismiss()
tempFileForInstall?.let { tempFile ->
handleModuleInstall(
tempFile = tempFile,
isEmbed = false,
viewModel = viewModel,
snackBarHost = snackBarHost,
kpmInstallSuccess = kpmInstallSuccess,
kpmInstallFailed = kpmInstallFailed
)
}
tempFileForInstall = null
}
}
) {
Text(kpmInstallModeLoad)
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
scope.launch {
dismiss()
tempFileForInstall?.let { tempFile ->
handleModuleInstall(
tempFile = tempFile,
isEmbed = true,
viewModel = viewModel,
snackBarHost = snackBarHost,
kpmInstallSuccess = kpmInstallSuccess,
kpmInstallFailed = kpmInstallFailed
)
}
tempFileForInstall = null
}
}
) {
Text(kpmInstallModeEmbed)
}
}
},
dismissButton = {
TextButton(
onClick = {
dismiss()
tempFileForInstall?.delete()
tempFileForInstall = null
}
) {
Text(cancel)
}
}
)
}
val selectPatchLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult
val uri = result.data?.data ?: return@rememberLauncherForActivityResult
scope.launch {
val fileName = uri.lastPathSegment ?: "unknown.kpm"
val encodedFileName = URLEncoder.encode(fileName, "UTF-8")
val tempFile = File(context.cacheDir, encodedFileName)
context.contentResolver.openInputStream(uri)?.use { input ->
tempFile.outputStream().use { output ->
input.copyTo(output)
}
}
val mimeType = context.contentResolver.getType(uri)
val isCorrectMimeType = mimeType == null || mimeType.contains("application/octet-stream")
if (!isCorrectMimeType) {
var shouldShowSnackbar = true
try {
val matchCount = checkStringsCommand(tempFile)
val isElf = isElfFile(tempFile)
if (matchCount >= 1 || isElf) {
shouldShowSnackbar = false
}
} catch (e: Exception) {
Log.e("KsuCli", "Failed to execute checks: ${e.message}", e)
}
if (shouldShowSnackbar) {
snackBarHost.showSnackbar(
message = invalidFileTypeMessage,
duration = SnackbarDuration.Short
)
}
tempFile.delete()
return@launch
}
tempFileForInstall = tempFile
installModeDialog.show()
}
}
LaunchedEffect(Unit) {
while(true) {
viewModel.fetchModuleList()
delay(5000)
}
}
val sharedPreferences = context.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
var isNoticeClosed by remember { mutableStateOf(sharedPreferences.getBoolean("is_notice_closed", false)) }
Scaffold(
topBar = {
SearchAppBar(
title = { Text(stringResource(R.string.kpm_title)) },
searchText = viewModel.search,
onSearchTextChange = { viewModel.search = it },
onClearClick = { viewModel.search = "" },
scrollBehavior = scrollBehavior,
dropdownContent = {
IconButton(onClick = { viewModel.fetchModuleList() }) {
Icon(
imageVector = Icons.Outlined.Refresh,
contentDescription = stringResource(R.string.refresh)
)
}
}
)
},
floatingActionButton = {
ExtendedFloatingActionButton(
onClick = {
selectPatchLauncher.launch(
Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream"
}
)
},
icon = {
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = stringResource(R.string.kpm_install)
)
},
text = { Text(stringResource(R.string.kpm_install)) },
containerColor = cardColor.copy(alpha = 1f),
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
},
snackbarHost = { SnackbarHost(snackBarHost) }
) { padding ->
Column(modifier = Modifier.padding(padding)) {
if (!isNoticeClosed) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(R.string.kernel_module_notice),
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center
)
IconButton(onClick = {
isNoticeClosed = true
sharedPreferences.edit { putBoolean("is_notice_closed", true) }
}) {
Icon(
imageVector = Icons.Outlined.Close,
contentDescription = stringResource(R.string.close_notice)
)
}
}
}
if (viewModel.moduleList.isEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
stringResource(R.string.kpm_empty),
textAlign = TextAlign.Center
)
}
} else {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(viewModel.moduleList) { module ->
KpmModuleItem(
module = module,
onUninstall = {
scope.launch {
val confirmContent = moduleConfirmContentMap[module.id] ?: ""
handleModuleUninstall(
module = module,
viewModel = viewModel,
snackBarHost = snackBarHost,
kpmUninstallSuccess = kpmUninstallSuccess,
kpmUninstallFailed = kpmUninstallFailed,
failedToCheckModuleFile = failedToCheckModuleFile,
uninstall = uninstall,
cancel = cancel,
confirmDialog = confirmDialog,
confirmTitle = confirmTitle,
confirmContent = confirmContent
)
}
},
onControl = {
viewModel.loadModuleDetail(module.id)
}
)
}
}
}
}
}
}
private suspend fun handleModuleInstall(
tempFile: File,
isEmbed: Boolean,
viewModel: KpmViewModel,
snackBarHost: SnackbarHostState,
kpmInstallSuccess: String,
kpmInstallFailed: String
) {
var moduleId: String? = null
try {
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
val process = Runtime.getRuntime().exec(command)
val inputStream = process.inputStream
val reader = BufferedReader(InputStreamReader(inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line!!.startsWith("name=")) {
moduleId = line.substringAfter("name=").trim()
break
}
}
process.waitFor()
} catch (e: Exception) {
Log.e("KsuCli", "Failed to get module ID from strings command: ${e.message}", e)
}
if (moduleId == null || moduleId.isEmpty()) {
Log.e("KsuCli", "Failed to extract module ID from file: ${tempFile.name}")
snackBarHost.showSnackbar(
message = kpmInstallFailed,
duration = SnackbarDuration.Short
)
tempFile.delete()
return
}
val targetPath = "/data/adb/kpm/$moduleId.kpm"
try {
if (isEmbed) {
Runtime.getRuntime().exec(arrayOf("su", "-c", "mkdir -p /data/adb/kpm")).waitFor()
Runtime.getRuntime().exec(arrayOf("su", "-c", "cp ${tempFile.absolutePath} $targetPath")).waitFor()
}
val loadResult = loadKpmModule(tempFile.absolutePath)
if (loadResult.startsWith("Error")) {
Log.e("KsuCli", "Failed to load KPM module: $loadResult")
snackBarHost.showSnackbar(
message = kpmInstallFailed,
duration = SnackbarDuration.Short
)
} else {
viewModel.fetchModuleList()
snackBarHost.showSnackbar(
message = kpmInstallSuccess,
duration = SnackbarDuration.Short
)
}
} catch (e: Exception) {
Log.e("KsuCli", "Failed to load KPM module: ${e.message}", e)
snackBarHost.showSnackbar(
message = kpmInstallFailed,
duration = SnackbarDuration.Short
)
}
tempFile.delete()
}
private suspend fun handleModuleUninstall(
module: KpmViewModel.ModuleInfo,
viewModel: KpmViewModel,
snackBarHost: SnackbarHostState,
kpmUninstallSuccess: String,
kpmUninstallFailed: String,
failedToCheckModuleFile: String,
uninstall: String,
cancel: String,
confirmTitle : String,
confirmContent : String,
confirmDialog: ConfirmDialogHandle
) {
val moduleFileName = "${module.id}.kpm"
val moduleFilePath = "/data/adb/kpm/$moduleFileName"
val fileExists = try {
val result = Runtime.getRuntime().exec(arrayOf("su", "-c", "ls /data/adb/kpm/$moduleFileName")).waitFor() == 0
result
} catch (e: Exception) {
Log.e("KsuCli", "Failed to check module file existence: ${e.message}", e)
snackBarHost.showSnackbar(
message = failedToCheckModuleFile,
duration = SnackbarDuration.Short
)
false
}
val confirmResult = confirmDialog.awaitConfirm(
title = confirmTitle,
content = confirmContent,
confirm = uninstall,
dismiss = cancel
)
if (confirmResult == ConfirmResult.Confirmed) {
try {
val unloadResult = unloadKpmModule(module.id)
if (unloadResult.startsWith("Error")) {
Log.e("KsuCli", "Failed to unload KPM module: $unloadResult")
snackBarHost.showSnackbar(
message = kpmUninstallFailed,
duration = SnackbarDuration.Short
)
return
}
if (fileExists) {
Runtime.getRuntime().exec(arrayOf("su", "-c", "rm $moduleFilePath")).waitFor()
}
viewModel.fetchModuleList()
snackBarHost.showSnackbar(
message = kpmUninstallSuccess,
duration = SnackbarDuration.Short
)
} catch (e: Exception) {
Log.e("KsuCli", "Failed to unload KPM module: ${e.message}", e)
snackBarHost.showSnackbar(
message = kpmUninstallFailed,
duration = SnackbarDuration.Short
)
}
}
}
@Composable
private fun KpmModuleItem(
module: KpmViewModel.ModuleInfo,
onUninstall: () -> Unit,
onControl: () -> Unit
) {
val viewModel: KpmViewModel = viewModel()
val scope = rememberCoroutineScope()
val snackBarHost = remember { SnackbarHostState() }
val successMessage = stringResource(R.string.kpm_control_success)
val failureMessage = stringResource(R.string.kpm_control_failed)
if (viewModel.showInputDialog && viewModel.selectedModuleId == module.id) {
AlertDialog(
onDismissRequest = { viewModel.hideInputDialog() },
title = { Text(stringResource(R.string.kpm_control)) },
text = {
OutlinedTextField(
value = viewModel.inputArgs,
onValueChange = { viewModel.updateInputArgs(it) },
label = { Text(stringResource(R.string.kpm_args)) },
placeholder = { Text(module.args) }
)
},
confirmButton = {
TextButton(
onClick = {
scope.launch {
val result = viewModel.executeControl()
val message = when (result) {
0 -> successMessage
else -> failureMessage
}
snackBarHost.showSnackbar(message)
onControl()
}
}
) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton(onClick = { viewModel.hideInputDialog() }) {
Text(stringResource(R.string.cancel))
}
}
)
}
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = module.name,
style = MaterialTheme.typography.titleMedium
)
Text(
text = "${stringResource(R.string.kpm_version)}: ${module.version}",
style = MaterialTheme.typography.bodyMedium
)
Text(
text = "${stringResource(R.string.kpm_author)}: ${module.author}",
style = MaterialTheme.typography.bodyMedium
)
Text(
text = "${stringResource(R.string.kpm_args)}: ${module.args}",
style = MaterialTheme.typography.bodyMedium
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = module.description,
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
FilledTonalButton(
onClick = { viewModel.showInputDialog(module.id) },
enabled = module.hasAction
) {
Icon(
imageVector = Icons.Outlined.Settings,
contentDescription = null
)
Text(stringResource(R.string.kpm_control))
}
FilledTonalButton(
onClick = onUninstall
) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = null
)
Text(stringResource(R.string.kpm_uninstall))
}
}
}
}
}
private fun checkStringsCommand(tempFile: File): Int {
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep -E 'name=|version=|license=|author='")
val process = Runtime.getRuntime().exec(command)
val inputStream = process.inputStream
val reader = BufferedReader(InputStreamReader(inputStream))
var line: String?
var matchCount = 0
val keywords = listOf("name=", "version=", "license=", "author=")
var nameExists = false
while (reader.readLine().also { line = it } != null) {
if (!nameExists && line!!.startsWith("name=")) {
nameExists = true
matchCount++
} else if (nameExists) {
for (keyword in keywords) {
if (line!!.startsWith(keyword)) {
matchCount++
break
}
}
}
}
process.waitFor()
return if (nameExists) matchCount else 0
}
private fun isElfFile(tempFile: File): Boolean {
val elfMagic = byteArrayOf(0x7F, 'E'.code.toByte(), 'L'.code.toByte(), 'F'.code.toByte())
val fileBytes = ByteArray(4)
FileInputStream(tempFile).use { input ->
input.read(fileBytes)
}
return fileBytes.contentEquals(elfMagic)
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import android.app.Activity.*
import android.content.Context
@@ -88,32 +88,32 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.ConfirmResult
import shirkneko.zako.sukisu.ui.component.SearchAppBar
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
import shirkneko.zako.sukisu.ui.util.DownloadListener
import shirkneko.zako.sukisu.ui.util.*
import shirkneko.zako.sukisu.ui.util.download
import shirkneko.zako.sukisu.ui.util.hasMagisk
import shirkneko.zako.sukisu.ui.util.reboot
import shirkneko.zako.sukisu.ui.util.restoreModule
import shirkneko.zako.sukisu.ui.util.toggleModule
import shirkneko.zako.sukisu.ui.util.uninstallModule
import shirkneko.zako.sukisu.ui.webui.WebUIActivity
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ui.component.ConfirmResult
import com.sukisu.ultra.ui.component.SearchAppBar
import com.sukisu.ultra.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.component.rememberLoadingDialog
import com.sukisu.ultra.ui.util.DownloadListener
import com.sukisu.ultra.ui.util.*
import com.sukisu.ultra.ui.util.download
import com.sukisu.ultra.ui.util.hasMagisk
import com.sukisu.ultra.ui.util.reboot
import com.sukisu.ultra.ui.util.restoreModule
import com.sukisu.ultra.ui.util.toggleModule
import com.sukisu.ultra.ui.util.uninstallModule
import com.sukisu.ultra.ui.webui.WebUIActivity
import okhttp3.OkHttpClient
import shirkneko.zako.sukisu.ui.util.ModuleModify
import shirkneko.zako.sukisu.ui.theme.getCardColors
import shirkneko.zako.sukisu.ui.theme.getCardElevation
import shirkneko.zako.sukisu.ui.viewmodel.ModuleViewModel
import com.sukisu.ultra.ui.util.ModuleModify
import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.zip.ZipInputStream
import androidx.core.content.edit
import androidx.core.net.toUri
import shirkneko.zako.sukisu.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.R
@OptIn(ExperimentalMaterial3Api::class)
@@ -346,7 +346,11 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
floatingActionButton = {
if (!hideInstallButton) {
val moduleInstall = stringResource(id = R.string.module_install)
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast
} else {
MaterialTheme.colorScheme.secondaryContainer
}
ExtendedFloatingActionButton(
onClick = {
selectZipLauncher.launch(

View File

@@ -1,5 +1,6 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import androidx.compose.animation.AnimatedVisibility
import android.content.Context
import android.net.Uri
import android.os.Build
@@ -23,9 +24,8 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import com.ramcosta.composedestinations.annotation.Destination
@@ -35,21 +35,13 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.theme.CardConfig
import shirkneko.zako.sukisu.ui.theme.ThemeColors
import shirkneko.zako.sukisu.ui.theme.ThemeConfig
import shirkneko.zako.sukisu.ui.theme.saveCustomBackground
import shirkneko.zako.sukisu.ui.theme.saveThemeColors
import shirkneko.zako.sukisu.ui.theme.saveThemeMode
import shirkneko.zako.sukisu.ui.theme.saveDynamicColorState
import shirkneko.zako.sukisu.ui.util.getSuSFS
import shirkneko.zako.sukisu.ui.util.getSuSFSFeatures
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_0
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_2
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_Mode
import com.sukisu.ultra.ui.component.ImageEditorDialog
import com.sukisu.ultra.ui.component.SwitchItem
import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.util.*
import androidx.core.content.edit
import com.sukisu.ultra.R
import com.sukisu.ultra.*
fun saveCardConfig(context: Context) {
@@ -115,6 +107,28 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
isHideVersion = newValue
}
// 隐藏模块数量等信息开关状态
var isHideOtherInfo by remember {
mutableStateOf(prefs.getBoolean("is_hide_other_info", false))
}
// 隐藏模块数量等信息开关状态
val onHideOtherInfoChange = { newValue: Boolean ->
prefs.edit { putBoolean("is_hide_other_info", newValue) }
isHideOtherInfo = newValue
}
// 隐藏 SuSFS 状态开关状态
var isHideSusfsStatus by remember {
mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false))
}
// 隐藏 SuSFS 状态开关状态
val onHideSusfsStatusChange = { newValue: Boolean ->
prefs.edit { putBoolean("is_hide_susfs_status", newValue) }
isHideSusfsStatus = newValue
}
// SELinux 状态
var selinuxEnabled by remember {
mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing")
@@ -127,11 +141,15 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
mutableStateOf(ThemeConfig.customBackgroundUri != null)
}
// 图片编辑状态
var showImageEditor by remember { mutableStateOf(false) }
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
// 初始化卡片配置
val systemIsDark = isSystemInDarkTheme()
LaunchedEffect(Unit) {
CardConfig.apply {
cardAlpha = prefs.getFloat("card_alpha", 0.65f)
cardAlpha = prefs.getFloat("card_alpha", 0.45f)
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
@@ -139,7 +157,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
if (!isCustomAlphaSet) {
val isDarkMode = ThemeConfig.forceDarkMode ?: systemIsDark
if (isDarkMode) {
cardAlpha = 0.5f
cardAlpha = 0.35f
}
}
}
@@ -163,19 +181,38 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
)
var showThemeColorDialog by remember { mutableStateOf(false) }
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
// 图片选择器
val pickImageLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
uri?.let {
context.saveCustomBackground(it)
isCustomBackgroundEnabled = true
CardConfig.cardElevation = 0.dp
saveCardConfig(context)
selectedImageUri = it
showImageEditor = true
}
}
// 显示图片编辑对话框
if (showImageEditor && selectedImageUri != null) {
ImageEditorDialog(
imageUri = selectedImageUri!!,
onDismiss = {
showImageEditor = false
selectedImageUri = null
},
onConfirm = { transformedUri ->
context.saveAndApplyCustomBackground(transformedUri)
isCustomBackgroundEnabled = true
CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true
saveCardConfig(context)
showImageEditor = false
selectedImageUri = null
}
)
}
Scaffold(
topBar = {
TopAppBar(
@@ -196,38 +233,86 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
.padding(top = 12.dp)
) {
// SELinux 开关
SwitchItem(
icon = Icons.Filled.Security,
title = stringResource(R.string.selinux),
summary = if (selinuxEnabled)
stringResource(R.string.selinux_enabled) else
stringResource(R.string.selinux_disabled),
checked = selinuxEnabled
) { enabled ->
val command = if (enabled) "setenforce 1" else "setenforce 0"
Shell.getShell().newJob().add(command).exec().let { result ->
if (result.isSuccess) selinuxEnabled = enabled
if (ksuIsValid) {
SwitchItem(
icon = Icons.Filled.Security,
title = stringResource(R.string.selinux),
summary = if (selinuxEnabled)
stringResource(R.string.selinux_enabled) else
stringResource(R.string.selinux_disabled),
checked = selinuxEnabled
) { enabled ->
val command = if (enabled) "setenforce 1" else "setenforce 0"
Shell.getShell().newJob().add(command).exec().let { result ->
if (result.isSuccess) selinuxEnabled = enabled
}
}
}
// 添加简洁模块开关
SwitchItem(
icon = Icons.Filled.FormatPaint,
title = stringResource(R.string.simple_mode),
summary = stringResource(R.string.simple_mode_summary),
checked = isSimpleMode
) {
onSimpleModeChange(it)
}
var isExpanded by remember { mutableStateOf(false) }
// 隐藏内核部分版本号
SwitchItem(
icon = Icons.Filled.FormatPaint,
title = stringResource(R.string.hide_kernel_kernelsu_version),
summary = stringResource(R.string.hide_kernel_kernelsu_version_summary),
checked = isHideVersion
ListItem(
leadingContent = { Icon(Icons.Filled.AutoFixHigh, null) },
headlineContent = { Text(stringResource(R.string.custom_settings)) },
modifier = Modifier.clickable {
isExpanded = !isExpanded
}
)
AnimatedVisibility(
visible = isExpanded,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
onHideVersionChange(it)
// 添加简洁模块开关
SwitchItem(
icon = Icons.Filled.Brush,
title = stringResource(R.string.simple_mode),
summary = stringResource(R.string.simple_mode_summary),
checked = isSimpleMode
) {
onSimpleModeChange(it)
}
}
AnimatedVisibility(
visible = isExpanded,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
// 隐藏内核部分版本号
SwitchItem(
icon = Icons.Filled.VisibilityOff,
title = stringResource(R.string.hide_kernel_kernelsu_version),
summary = stringResource(R.string.hide_kernel_kernelsu_version_summary),
checked = isHideVersion
) {
onHideVersionChange(it)
}
}
AnimatedVisibility(
visible = isExpanded,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
// 模块数量等信息
SwitchItem(
icon = Icons.Filled.VisibilityOff,
title = stringResource(R.string.hide_other_info),
summary = stringResource(R.string.hide_other_info_summary),
checked = isHideOtherInfo
) {
onHideOtherInfoChange(it)
}
}
AnimatedVisibility(
visible = isExpanded,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
// SuSFS 状态信息
SwitchItem(
icon = Icons.Filled.VisibilityOff,
title = stringResource(R.string.hide_susfs_status),
summary = stringResource(R.string.hide_susfs_status_summary),
checked = isHideSusfsStatus
) {
onHideSusfsStatusChange(it)
}
}
// region SUSFS 配置(仅在支持时显示)
@@ -285,10 +370,12 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
}
}
// 只在未启用动态颜色时显示主题色选择
if (!useDynamicColor) {
AnimatedVisibility(
visible = !useDynamicColor
) {
ListItem(
leadingContent = { Icon(Icons.Default.Palette, null) },
headlineContent = { Text("主题颜色") },
headlineContent = { Text(stringResource(R.string.theme_color)) },
supportingContent = {
val currentThemeName = when (ThemeConfig.currentTheme) {
is ThemeColors.Default -> stringResource(R.string.color_default)
@@ -373,10 +460,11 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
context.saveCustomBackground(null)
isCustomBackgroundEnabled = false
CardConfig.cardElevation = CardConfig.defaultElevation
CardConfig.cardAlpha = 1f
CardConfig.cardAlpha = 0.45f
CardConfig.isCustomAlphaSet = false
CardConfig.isCustomBackgroundEnabled = false
saveCardConfig(context)
cardAlpha = 0.65f
cardAlpha = 0.35f
themeMode = 0
context.saveThemeMode(null)
CardConfig.isUserDarkModeEnabled = false
@@ -387,9 +475,11 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
)
}
)
if (ThemeConfig.customBackgroundUri != null && showCardSettings) {
// 透明度 Slider
// 透明度 Slider
AnimatedVisibility(
visible = ThemeConfig.customBackgroundUri != null && showCardSettings,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
ListItem(
leadingContent = { Icon(Icons.Filled.Opacity, null) },
headlineContent = { Text(stringResource(R.string.settings_card_alpha)) },
@@ -419,7 +509,11 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
)
}
)
}
AnimatedVisibility(
visible = ThemeConfig.customBackgroundUri != null && showCardSettings,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
){
ListItem(
leadingContent = { Icon(Icons.Filled.DarkMode, null) },
headlineContent = { Text(stringResource(R.string.theme_mode)) },
@@ -428,70 +522,70 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
showThemeModeDialog = true
}
)
}
// 主题模式选择对话框
if (showThemeModeDialog) {
AlertDialog(
onDismissRequest = { showThemeModeDialog = false },
title = { Text(stringResource(R.string.theme_mode)) },
text = {
Column {
themeOptions.forEachIndexed { index, option ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
themeMode = index
val newThemeMode = when(index) {
0 -> null // 跟随系统
1 -> false //
2 -> true // 深色
else -> null
}
context.saveThemeMode(newThemeMode)
when (index) {
2 -> {
ThemeConfig.forceDarkMode = true
CardConfig.isUserLightModeEnabled = false
CardConfig.isUserDarkModeEnabled = true
CardConfig.save(context)
}
1 -> {
ThemeConfig.forceDarkMode = false
CardConfig.isUserLightModeEnabled = true
CardConfig.isUserDarkModeEnabled = false
CardConfig.save(context)
}
0 -> {
ThemeConfig.forceDarkMode = null
CardConfig.isUserLightModeEnabled = false
CardConfig.isUserDarkModeEnabled = false
CardConfig.save(context)
}
}
showThemeModeDialog = false
// 主题模式选择对话框
if (showThemeModeDialog) {
AlertDialog(
onDismissRequest = { showThemeModeDialog = false },
title = { Text(stringResource(R.string.theme_mode)) },
text = {
Column {
themeOptions.forEachIndexed { index, option ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
themeMode = index
val newThemeMode = when(index) {
0 -> null // 跟随系统
1 -> false // 浅色
2 -> true //
else -> null
}
context.saveThemeMode(newThemeMode)
when (index) {
2 -> {
ThemeConfig.forceDarkMode = true
CardConfig.isUserLightModeEnabled = false
CardConfig.isUserDarkModeEnabled = true
CardConfig.save(context)
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = themeMode == index,
onClick = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(option)
1 -> {
ThemeConfig.forceDarkMode = false
CardConfig.isUserLightModeEnabled = true
CardConfig.isUserDarkModeEnabled = false
CardConfig.save(context)
}
0 -> {
ThemeConfig.forceDarkMode = null
CardConfig.isUserLightModeEnabled = false
CardConfig.isUserDarkModeEnabled = false
CardConfig.save(context)
}
}
showThemeModeDialog = false
}
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = themeMode == index,
onClick = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(option)
}
},
confirmButton = {}
)
}
}
}
}
},
confirmButton = {}
)
}
}
}
}
@Composable
private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false): SliderColors {
@@ -534,4 +628,4 @@ private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false):
)
}
}
}
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import android.content.Context
import android.content.Intent
@@ -7,30 +7,14 @@ import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
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.automirrored.filled.Undo
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -49,12 +33,9 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider
import com.maxkeppeker.sheets.core.models.base.Header
import androidx.core.content.edit
import com.maxkeppeker.sheets.core.models.base.IconSource
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
@@ -65,24 +46,16 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.BuildConfig
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.AboutDialog
import shirkneko.zako.sukisu.ui.component.ConfirmResult
import shirkneko.zako.sukisu.ui.component.DialogHandle
import shirkneko.zako.sukisu.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.getBugreportFile
import com.sukisu.ultra.BuildConfig
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.*
import com.sukisu.ultra.ui.component.*
import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.util.LocalSnackbarHost
import com.sukisu.ultra.ui.util.getBugreportFile
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.MaterialTheme
import shirkneko.zako.sukisu.ui.theme.CardConfig
import androidx.core.content.edit
/**
@@ -96,6 +69,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
// region 界面基础设置
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val snackBarHost = LocalSnackbarHost.current
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
// endregion
Scaffold(
@@ -111,7 +85,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
AboutDialog(it)
}
val loadingDialog = rememberLoadingDialog()
val shrinkDialog = rememberConfirmDialog()
// endregion
Column(
@@ -140,11 +113,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
loadingDialog.hide()
snackBarHost.showSnackbar(context.getString(R.string.log_saved))
}
// endregion
}
// region 配置项列表
// 配置文件模板入口
val profileTemplate = stringResource(id = R.string.settings_profile_template)
if (ksuIsValid) {
ListItem(
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
headlineContent = { Text(profileTemplate) },
@@ -153,34 +126,40 @@ fun SettingScreen(navigator: DestinationsNavigator) {
navigator.navigate(AppProfileTemplateScreenDestination)
}
)
}
// 卸载模块开关
var umountChecked by rememberSaveable {
mutableStateOf(Natives.isDefaultUmountModules())
}
SwitchItem(
icon = Icons.Filled.FolderDelete,
title = stringResource(id = R.string.settings_umount_modules_default),
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
checked = umountChecked
) {
if (Natives.setDefaultUmountModules(it)) {
umountChecked = it
}
if (ksuIsValid) {
SwitchItem(
icon = Icons.Filled.FolderDelete,
title = stringResource(id = R.string.settings_umount_modules_default),
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
checked = umountChecked
) {
if (Natives.setDefaultUmountModules(it)) {
umountChecked = it
}
}
}
// SU 禁用开关(仅在兼容版本显示)
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
var isSuDisabled by rememberSaveable {
mutableStateOf(!Natives.isSuEnabled())
}
SwitchItem(
icon = Icons.Filled.RemoveModerator,
title = stringResource(id = R.string.settings_disable_su),
summary = stringResource(id = R.string.settings_disable_su_summary),
checked = isSuDisabled,
) { checked ->
val shouldEnable = !checked
if (Natives.setSuEnabled(shouldEnable)) {
isSuDisabled = !shouldEnable
if (ksuIsValid) {
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
var isSuDisabled by rememberSaveable {
mutableStateOf(!Natives.isSuEnabled())
}
SwitchItem(
icon = Icons.Filled.RemoveModerator,
title = stringResource(id = R.string.settings_disable_su),
summary = stringResource(id = R.string.settings_disable_su_summary),
checked = isSuDisabled,
) { checked ->
val shouldEnable = !checked
if (Natives.setSuEnabled(shouldEnable)) {
isSuDisabled = !shouldEnable
}
}
}
}
@@ -209,21 +188,23 @@ fun SettingScreen(navigator: DestinationsNavigator) {
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) }
enableWebDebugging = it
if (Natives.isKsuValid(ksuApp.packageName)) {
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) }
enableWebDebugging = it
}
}
// endregion
// 更多设置
val newButtonTitle = stringResource(id = R.string.more_settings)
ListItem(
leadingContent = {
Icon(
Icons.Filled.ExpandMore,
Icons.Filled.Settings,
contentDescription = newButtonTitle
)
},
@@ -452,20 +433,73 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
}
var selection = UninstallType.NONE
ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = {
if (selection != UninstallType.NONE) {
onSelected(selection)
}
}, onCloseRequest = {
dismiss()
}), header = Header.Default(
title = stringResource(R.string.settings_uninstall),
), selection = ListSelection.Single(
showRadioButtons = false,
options = listOptions,
) { index, _ ->
selection = options[index]
})
val cardColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast
} else {
MaterialTheme.colorScheme.secondaryContainer
}
AlertDialog(
onDismissRequest = {
dismiss()
},
title = {
Text(text = stringResource(R.string.settings_uninstall))
},
text = {
Column {
listOptions.forEachIndexed { index, option ->
Row(
modifier = Modifier
.clickable {
selection = options[index]
}
.padding(vertical = 8.dp)
) {
Icon(
imageVector = options[index].icon,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp)
)
Column {
Text(text = option.titleText)
option.subtitleText?.let {
Text(
text = it,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
},
confirmButton = {
androidx.compose.material3.TextButton(
onClick = {
if (selection != UninstallType.NONE) {
onSelected(selection)
}
dismiss()
}
) {
Text(text = stringResource(android.R.string.ok))
}
},
dismissButton = {
androidx.compose.material3.TextButton(
onClick = {
dismiss()
}
) {
Text(text = stringResource(android.R.string.cancel))
}
},
containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f),
shape = MaterialTheme.shapes.medium,
tonalElevation = getCardElevation()
)
}
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
@@ -24,6 +24,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.sukisu.ultra.R
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination
@@ -31,11 +32,10 @@ import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SearchAppBar
import shirkneko.zako.sukisu.ui.util.ModuleModify
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ui.component.SearchAppBar
import com.sukisu.ultra.ui.util.ModuleModify
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph>

View File

@@ -1,5 +1,7 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import android.content.ClipData
import android.content.ClipboardManager
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
@@ -44,11 +46,10 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination
@@ -59,8 +60,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient
import com.ramcosta.composedestinations.result.getOr
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
/**
* @author weishu
@@ -77,7 +79,11 @@ fun AppProfileTemplateScreen(
val viewModel = viewModel<TemplateViewModel>()
val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast
} else {
MaterialTheme.colorScheme.secondaryContainer
}
LaunchedEffect(Unit) {
if (viewModel.templateList.isEmpty()) {
@@ -94,8 +100,8 @@ fun AppProfileTemplateScreen(
Scaffold(
topBar = {
val clipboardManager = LocalClipboardManager.current
val context = LocalContext.current
val clipboardManager = context.getSystemService<ClipboardManager>()
val showToast = fun(msg: String) {
scope.launch(Dispatchers.Main) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
@@ -107,20 +113,20 @@ fun AppProfileTemplateScreen(
scope.launch { viewModel.fetchTemplates(true) }
},
onImport = {
clipboardManager.getText()?.text?.let {
if (it.isEmpty()) {
scope.launch {
val clipboardText = clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString()
if (clipboardText.isNullOrEmpty()) {
showToast(context.getString(R.string.app_profile_template_import_empty))
return@let
}
scope.launch {
viewModel.importTemplates(
it, {
showToast(context.getString(R.string.app_profile_template_import_success))
viewModel.fetchTemplates(false)
},
showToast
)
return@launch
}
viewModel.importTemplates(
clipboardText,
{
showToast(context.getString(R.string.app_profile_template_import_success))
viewModel.fetchTemplates(false)
},
showToast
)
}
},
onExport = {
@@ -129,8 +135,8 @@ fun AppProfileTemplateScreen(
{
showToast(context.getString(R.string.app_profile_template_export_empty))
}
) {
clipboardManager.setText(AnnotatedString(it))
) { text ->
clipboardManager?.setPrimaryClip(ClipData.newPlainText("", text))
}
}
},

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.screen
package com.sukisu.ultra.ui.screen
import android.widget.Toast
import androidx.activity.compose.BackHandler
@@ -47,14 +47,14 @@ import androidx.compose.ui.text.input.KeyboardType
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.result.ResultBackNavigator
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
import shirkneko.zako.sukisu.ui.util.deleteAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
import shirkneko.zako.sukisu.ui.viewmodel.toJSON
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
import com.sukisu.ultra.ui.util.deleteAppProfileTemplate
import com.sukisu.ultra.ui.util.getAppProfileTemplate
import com.sukisu.ultra.ui.util.setAppProfileTemplate
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
import com.sukisu.ultra.ui.viewmodel.toJSON
import androidx.lifecycle.compose.dropUnlessResumed
/**

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.theme
package com.sukisu.ultra.ui.theme
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
@@ -15,7 +15,7 @@ import androidx.compose.ui.unit.dp
object CardConfig {
val defaultElevation: Dp = 0.dp
var cardAlpha by mutableStateOf(1f)
var cardAlpha by mutableStateOf(0.45f)
var cardElevation by mutableStateOf(defaultElevation)
var isShadowEnabled by mutableStateOf(true)
var isCustomAlphaSet by mutableStateOf(false)
@@ -38,7 +38,7 @@ object CardConfig {
fun load(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
cardAlpha = prefs.getFloat("card_alpha", 1f)
cardAlpha = prefs.getFloat("card_alpha", 0.45f)
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false)
@@ -53,7 +53,7 @@ object CardConfig {
fun setDarkModeDefaults() {
if (!isCustomAlphaSet) {
cardAlpha = 0.5f
cardAlpha = 0.35f
cardElevation = 0.dp
}
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.theme
package com.sukisu.ultra.ui.theme
import androidx.compose.ui.graphics.Color

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.theme
package com.sukisu.ultra.ui.theme
import android.content.ContentResolver
import android.content.Context
@@ -27,11 +27,14 @@ import androidx.compose.ui.zIndex
import coil.compose.rememberAsyncImagePainter
import androidx.compose.foundation.background
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.dp
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import androidx.core.content.edit
import androidx.core.net.toUri
import com.sukisu.ultra.ui.util.BackgroundTransformation
import com.sukisu.ultra.ui.util.saveTransformedBackground
object ThemeConfig {
var customBackgroundUri by mutableStateOf<Uri?>(null)
@@ -43,23 +46,23 @@ object ThemeConfig {
@Composable
private fun getDarkColorScheme() = darkColorScheme(
primary = ThemeConfig.currentTheme.Primary.copy(alpha = 0.8f),
onPrimary = Color.White,
onPrimary = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer.copy(alpha = 0.15f),
onPrimaryContainer = Color.White,
onPrimaryContainer = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
secondary = ThemeConfig.currentTheme.Secondary.copy(alpha = 0.8f),
onSecondary = Color.White,
onSecondary = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f),
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer.copy(alpha = 0.15f),
onSecondaryContainer = Color.White,
onSecondaryContainer = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f),
tertiary = ThemeConfig.currentTheme.Tertiary.copy(alpha = 0.8f),
onTertiary = Color.White,
onTertiary = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f),
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f),
onTertiaryContainer = Color.White,
onTertiaryContainer = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f),
background = Color.Transparent,
surface = Color.Transparent,
onBackground = Color.White.copy(alpha = 0.87f),
onSurface = Color.White.copy(alpha = 0.87f),
onBackground = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
onSurface = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
surfaceVariant = Color(0xFF2F2F2F),
onSurfaceVariant = Color.White.copy(alpha = 0.78f),
onSurfaceVariant = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
outline = Color.White.copy(alpha = 0.12f),
outlineVariant = Color.White.copy(alpha = 0.12f)
)
@@ -88,29 +91,6 @@ private fun getLightColorScheme() = lightColorScheme(
outlineVariant = Color.Black.copy(alpha = 0.12f)
)
// 复制图片到应用内部存储
fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
try {
val contentResolver: ContentResolver = contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri)!!
val fileName = "custom_background.jpg"
val file = File(filesDir, fileName)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(4 * 1024)
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
}
outputStream.flush()
outputStream.close()
inputStream.close()
return Uri.fromFile(file)
} catch (e: Exception) {
Log.e("ImageCopy", "Failed to copy image: ${e.message}")
return null
}
}
@Composable
fun KernelSUTheme(
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
@@ -131,7 +111,6 @@ fun KernelSUTheme(
if (darkTheme) {
val originalScheme = dynamicDarkColorScheme(context)
originalScheme.copy(
// 调整按钮相关颜色
primary = adjustColor(originalScheme.primary),
onPrimary = adjustColor(originalScheme.onPrimary),
primaryContainer = adjustColor(originalScheme.primaryContainer),
@@ -242,6 +221,48 @@ fun KernelSUTheme(
}
}
// 复制图片到应用内部存储
private fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
try {
val contentResolver: ContentResolver = contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri)!!
val fileName = "custom_background.jpg"
val file = File(filesDir, fileName)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(4 * 1024)
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
}
outputStream.flush()
outputStream.close()
inputStream.close()
return Uri.fromFile(file)
} catch (e: Exception) {
Log.e("ImageCopy", "Failed to copy image: ${e.message}")
return null
}
}
// 保存变换后的背景图片到应用内部存储并更新配置
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
val finalUri = if (transformation != null) {
saveTransformedBackground(uri, transformation)
} else {
copyImageToInternalStorage(uri)
}
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit {
putString("custom_background", finalUri?.toString())
}
ThemeConfig.customBackgroundUri = finalUri
CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true
}
// 保存背景图片到应用内部存储并更新配置
fun Context.saveCustomBackground(uri: Uri?) {
val newUri = uri?.let { copyImageToInternalStorage(it) }
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
@@ -249,6 +270,10 @@ fun Context.saveCustomBackground(uri: Uri?) {
putString("custom_background", newUri?.toString())
}
ThemeConfig.customBackgroundUri = newUri
if (uri != null) {
CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true
}
}
fun Context.loadCustomBackground() {
@@ -339,4 +364,12 @@ private fun adjustColor(color: Color): Color {
luminance = maxLuminance
}
return color.copy(luminance)
}
private fun mixColors(color1: Color, color2: Color, ratio: Float): Color {
val r = (color1.red * ratio + color2.red * (1 - ratio))
val g = (color1.green * ratio + color2.green * (1 - ratio))
val b = (color1.blue * ratio + color2.blue * (1 - ratio))
val a = (color1.alpha * ratio + color2.alpha * (1 - ratio))
return Color(r, g, b, a)
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.theme
package com.sukisu.ultra.ui.theme
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily

View File

@@ -0,0 +1,114 @@
package com.sukisu.ultra.ui.util
import android.content.ContentResolver
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.net.Uri
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import androidx.core.graphics.createBitmap
data class BackgroundTransformation(
val scale: Float = 1f,
val offsetX: Float = 0f,
val offsetY: Float = 0f
)
fun Context.getImageBitmap(uri: Uri): Bitmap? {
return try {
val contentResolver: ContentResolver = contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream.close()
bitmap
} catch (e: Exception) {
Log.e("BackgroundUtils", "Failed to get image bitmap: ${e.message}")
null
}
}
fun Context.applyTransformationToBitmap(bitmap: Bitmap, transformation: BackgroundTransformation): Bitmap {
val width = bitmap.width
val height = bitmap.height
// 创建与屏幕比例相同的目标位图
val displayMetrics = resources.displayMetrics
val screenWidth = displayMetrics.widthPixels
val screenHeight = displayMetrics.heightPixels
val screenRatio = screenHeight.toFloat() / screenWidth.toFloat()
// 计算目标宽高
val targetWidth: Int
val targetHeight: Int
if (width.toFloat() / height.toFloat() > screenRatio) {
targetHeight = height
targetWidth = (height / screenRatio).toInt()
} else {
targetWidth = width
targetHeight = (width * screenRatio).toInt()
}
// 创建与目标相同大小的位图
val scaledBitmap = createBitmap(targetWidth, targetHeight)
val canvas = Canvas(scaledBitmap)
val matrix = Matrix()
// 确保缩放值有效
val safeScale = maxOf(0.1f, transformation.scale)
matrix.postScale(safeScale, safeScale)
// 计算中心点
val centerX = targetWidth / 2f
val centerY = targetHeight / 2f
// 计算偏移量,确保不会出现负最大值的问题
val widthDiff = (bitmap.width * safeScale - targetWidth)
val heightDiff = (bitmap.height * safeScale - targetHeight)
// 安全计算偏移量边界
val maxOffsetX = maxOf(0f, widthDiff / 2)
val maxOffsetY = maxOf(0f, heightDiff / 2)
// 限制偏移范围
val safeOffsetX = if (maxOffsetX > 0)
transformation.offsetX.coerceIn(-maxOffsetX, maxOffsetX) else 0f
val safeOffsetY = if (maxOffsetY > 0)
transformation.offsetY.coerceIn(-maxOffsetY, maxOffsetY) else 0f
// 应用偏移量到矩阵
val translationX = -widthDiff / 2 + safeOffsetX
val translationY = -heightDiff / 2 + safeOffsetY
matrix.postTranslate(translationX, translationY)
// 将原始位图绘制到新位图上
canvas.drawBitmap(bitmap, matrix, null)
return scaledBitmap
}
fun Context.saveTransformedBackground(uri: Uri, transformation: BackgroundTransformation): Uri? {
try {
val bitmap = getImageBitmap(uri) ?: return null
val transformedBitmap = applyTransformationToBitmap(bitmap, transformation)
val fileName = "custom_background_transformed.jpg"
val file = File(filesDir, fileName)
val outputStream = FileOutputStream(file)
transformedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
outputStream.flush()
outputStream.close()
return Uri.fromFile(file)
} catch (e: Exception) {
Log.e("BackgroundUtils", "Failed to save transformed image: ${e.message}", e)
return null
}
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.util
package com.sukisu.ultra.ui.util
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.compositionLocalOf

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.util
package com.sukisu.ultra.ui.util
import android.annotation.SuppressLint
import android.app.DownloadManager
@@ -12,7 +12,8 @@ import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.core.content.ContextCompat
import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
import androidx.core.net.toUri
/**
* @author weishu
@@ -42,14 +43,14 @@ fun download(
onDownloading()
return
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
onDownloaded(Uri.parse(localUri))
onDownloaded(localUri.toUri())
return
}
}
}
}
val request = DownloadManager.Request(Uri.parse(url))
val request = DownloadManager.Request(url.toUri())
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
fileName
@@ -63,7 +64,6 @@ fun download(
}
fun checkNewVersion(): LatestVersionInfo {
// 改为新的 release 接口
val url = "https://api.github.com/repos/ShirkNeko/SukiSU-Ultra/releases/latest"
val defaultValue = LatestVersionInfo()
return runCatching {
@@ -141,7 +141,7 @@ fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
val uri = cursor.getString(
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
)
onDownloaded(Uri.parse(uri))
onDownloaded(uri.toUri())
}
}
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.util;
package com.sukisu.ultra.ui.util;
/*
* Copyright (C) 2009 The Android Open Source Project
*

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.util
package com.sukisu.ultra.ui.util
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.material3.MaterialTheme

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.util
package com.sukisu.ultra.ui.util
import android.content.ContentResolver
import android.content.Context
@@ -16,9 +16,9 @@ import com.topjohnwu.superuser.ShellUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import shirkneko.zako.sukisu.BuildConfig
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ksuApp
import com.sukisu.ultra.BuildConfig
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import org.json.JSONArray
import java.io.File
@@ -30,7 +30,7 @@ import java.io.File
private const val TAG = "KsuCli"
private fun getKsuDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomk.so"
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozako.so"
}
object KsuCli {
@@ -436,8 +436,8 @@ fun restartApp(packageName: String) {
launchApp(packageName)
}
private fun getSuSFSDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomksd.so"
fun getSuSFSDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozakozako.so"
}
fun getSuSFS(): String {
@@ -481,7 +481,7 @@ fun susfsSUS_SU_Mode(): String {
return result
}
private fun getKpmmgrPath(): String {
fun getKpmmgrPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libkpmmgr.so"
}
@@ -489,48 +489,62 @@ private fun getKpmmgrPath(): String {
fun loadKpmModule(path: String, args: String? = null): String {
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} load $path ${args ?: ""}"
val result = ShellUtils.fastCmd(shell, cmd)
return result
return ShellUtils.fastCmd(shell, cmd)
}
fun unloadKpmModule(name: String): String {
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} unload $name"
val result = ShellUtils.fastCmd(shell, cmd)
return result
return ShellUtils.fastCmd(shell, cmd)
}
fun getKpmModuleCount(): String {
fun getKpmModuleCount(): Int {
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} num"
val result = ShellUtils.fastCmd(shell, cmd)
return result
return result.trim().toIntOrNull() ?: 0
}
fun runCmd(shell : Shell, cmd : String) : String {
return shell.newJob()
.add(cmd)
.to(mutableListOf<String>(), null)
.exec().out
.joinToString("\n")
}
fun listKpmModules(): String {
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} list"
val result = ShellUtils.fastCmd(shell, cmd)
return result
return try {
runCmd(shell, cmd).trim()
} catch (e: Exception) {
Log.e(TAG, "Failed to list KPM modules", e)
""
}
}
fun getKpmModuleInfo(name: String): String {
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} info $name"
val result = ShellUtils.fastCmd(shell, cmd)
return result
return try {
runCmd(shell, cmd).trim()
} catch (e: Exception) {
Log.e(TAG, "Failed to get KPM module info: $name", e)
""
}
}
fun controlKpmModule(name: String, args: String? = null): String {
fun controlKpmModule(name: String, args: String? = null): Int {
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} control $name ${args ?: ""}"
val result = ShellUtils.fastCmd(shell, cmd)
return result
val result = runCmd(shell, cmd)
return result.trim().toIntOrNull() ?: -1
}
fun printKpmModules(): String {
fun getKpmVersion(): String {
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} print"
val cmd = "${getKpmmgrPath()} version"
val result = ShellUtils.fastCmd(shell, cmd)
return result
return result.trim()
}

View File

@@ -1,11 +1,11 @@
package shirkneko.zako.sukisu.ui.util
package com.sukisu.ultra.ui.util
import android.content.Context
import android.os.Build
import android.system.Os
import com.topjohnwu.superuser.ShellUtils
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ui.screen.getManagerVersion
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ui.screen.getManagerVersion
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter
@@ -24,7 +24,7 @@ fun getBugreportFile(context: Context): File {
val pstoreFile = File(bugreportDir, "pstore.tar.gz")
// Xiaomi/Readmi devices have diag in /data/vendor/diag
val diagFile = File(bugreportDir, "diag.tar.gz")
val opulsFile = File(bugreportDir, "opuls.tar.gz")
val oplusFile = File(bugreportDir, "oplus.tar.gz")
val bootlogFile = File(bugreportDir, "bootlog.tar.gz")
val mountsFile = File(bugreportDir, "mounts.txt")
val fileSystemsFile = File(bugreportDir, "filesystems.txt")
@@ -46,7 +46,7 @@ fun getBugreportFile(context: Context): File {
shell.newJob().add("tar -czf ${dropboxFile.absolutePath} -C /data/system/dropbox .").exec()
shell.newJob().add("tar -czf ${pstoreFile.absolutePath} -C /sys/fs/pstore .").exec()
shell.newJob().add("tar -czf ${diagFile.absolutePath} -C /data/vendor/diag . --exclude=./minidump.gz").exec()
shell.newJob().add("tar -czf ${opulsFile.absolutePath} -C /mnt/oplus/op2/media/log/boot_log/ .").exec()
shell.newJob().add("tar -czf ${oplusFile.absolutePath} -C /mnt/oplus/op2/media/log/boot_log/ .").exec()
shell.newJob().add("tar -czf ${bootlogFile.absolutePath} -C /data/adb/ksu/log .").exec()
shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec()

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.util
package com.sukisu.ultra.ui.util
import android.app.AlertDialog
import android.content.Context
@@ -16,7 +16,7 @@ import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.R
import com.sukisu.ultra.R
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader

View File

@@ -1,9 +1,9 @@
package shirkneko.zako.sukisu.ui.util
package com.sukisu.ultra.ui.util
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.topjohnwu.superuser.Shell
import shirkneko.zako.sukisu.R
import com.sukisu.ultra.R
@Composable
fun getSELinuxStatus(): String {

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.util.module
package com.sukisu.ultra.ui.util.module
data class LatestVersionInfo(
val versionCode : Int = 0,

View File

@@ -0,0 +1,156 @@
package com.sukisu.ultra.ui.viewmodel
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import com.sukisu.ultra.ui.util.*
class KpmViewModel : ViewModel() {
var moduleList by mutableStateOf(emptyList<ModuleInfo>())
private set
var search by mutableStateOf("")
internal set
var isRefreshing by mutableStateOf(false)
private set
var currentModuleDetail by mutableStateOf("")
private set
fun fetchModuleList() {
viewModelScope.launch {
isRefreshing = true
try {
val moduleCount = getKpmModuleCount()
Log.d("KsuCli", "Module count: $moduleCount")
moduleList = getAllKpmModuleInfo()
// 获取 KPM 版本信息
val kpmVersion = getKpmVersion()
Log.d("KsuCli", "KPM Version: $kpmVersion")
} catch (e: Exception) {
Log.e("KsuCli", "获取模块列表失败", e)
} finally {
isRefreshing = false
}
}
}
private fun getAllKpmModuleInfo(): List<ModuleInfo> {
val result = mutableListOf<ModuleInfo>()
try {
val str = listKpmModules()
val moduleNames = str
.split("\n")
.filter { it.isNotBlank() }
for (name in moduleNames) {
try {
val moduleInfo = parseModuleInfo(name)
moduleInfo?.let { result.add(it) }
} catch (e: Exception) {
Log.e("KsuCli", "Error processing module $name", e)
}
}
} catch (e: Exception) {
Log.e("KsuCli", "Failed to get module list", e)
}
return result
}
private fun parseModuleInfo(name: String): ModuleInfo? {
val info = getKpmModuleInfo(name)
if (info.isBlank()) return null
val properties = info.lineSequence()
.filter { line ->
val trimmed = line.trim()
trimmed.isNotEmpty() && !trimmed.startsWith("#")
}
.mapNotNull { line ->
line.split("=", limit = 2).let { parts ->
when (parts.size) {
2 -> parts[0].trim() to parts[1].trim()
1 -> parts[0].trim() to ""
else -> null
}
}
}
.toMap()
return ModuleInfo(
id = name,
name = properties["name"] ?: name,
version = properties["version"] ?: "",
author = properties["author"] ?: "",
description = properties["description"] ?: "",
args = properties["args"] ?: "",
enabled = true,
hasAction = true
)
}
fun loadModuleDetail(moduleId: String) {
viewModelScope.launch {
try {
currentModuleDetail = withContext(Dispatchers.IO) {
getKpmModuleInfo(moduleId)
}
Log.d("KsuCli", "Module detail loaded: $currentModuleDetail")
} catch (e: Exception) {
Log.e("KsuCli", "Failed to load module detail", e)
currentModuleDetail = "Error: ${e.message}"
}
}
}
var showInputDialog by mutableStateOf(false)
private set
var selectedModuleId by mutableStateOf<String?>(null)
private set
var inputArgs by mutableStateOf("")
private set
fun showInputDialog(moduleId: String) {
selectedModuleId = moduleId
showInputDialog = true
}
fun hideInputDialog() {
showInputDialog = false
selectedModuleId = null
inputArgs = ""
}
fun updateInputArgs(args: String) {
inputArgs = args
}
fun executeControl(): Int {
val moduleId = selectedModuleId ?: return -1
val result = controlKpmModule(moduleId, inputArgs)
hideInputDialog()
return result
}
data class ModuleInfo(
val id: String,
val name: String,
val version: String,
val author: String,
val description: String,
val args: String,
val enabled: Boolean,
val hasAction: Boolean
)
}

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.viewmodel
package com.sukisu.ultra.ui.viewmodel
import android.os.SystemClock
import android.util.Log
@@ -10,8 +10,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.ui.util.HanziToPinyin
import shirkneko.zako.sukisu.ui.util.listModules
import com.sukisu.ultra.ui.util.HanziToPinyin
import com.sukisu.ultra.ui.util.listModules
import org.json.JSONArray
import org.json.JSONObject
import java.text.Collator
@@ -40,13 +40,6 @@ class ModuleViewModel : ViewModel() {
val dirId: String, // real module id (dir name)
)
data class ModuleUpdateInfo(
val version: String,
val versionCode: Int,
val zipUrl: String,
val changelog: String,
)
var isRefreshing by mutableStateOf(false)
private set
var search by mutableStateOf("")

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.viewmodel
package com.sukisu.ultra.ui.viewmodel
import android.content.ComponentName
import android.content.Intent
@@ -18,12 +18,12 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import shirkneko.zako.sukisu.IKsuInterface
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ksuApp
import shirkneko.zako.sukisu.ui.KsuService
import shirkneko.zako.sukisu.ui.util.HanziToPinyin
import shirkneko.zako.sukisu.ui.util.KsuCli
import com.sukisu.zako.IKsuInterface
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.ui.KsuService
import com.sukisu.ultra.ui.util.HanziToPinyin
import com.sukisu.ultra.ui.util.KsuCli
import java.text.Collator
import java.util.*
import kotlin.coroutines.resume

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.viewmodel
package com.sukisu.ultra.ui.viewmodel
import android.os.Parcelable
import android.util.Log
@@ -10,12 +10,12 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.profile.Capabilities
import shirkneko.zako.sukisu.profile.Groups
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
import com.sukisu.ultra.Natives
import com.sukisu.ultra.profile.Capabilities
import com.sukisu.ultra.profile.Groups
import com.sukisu.ultra.ui.util.getAppProfileTemplate
import com.sukisu.ultra.ui.util.listAppProfileTemplates
import com.sukisu.ultra.ui.util.setAppProfileTemplate
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONArray

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package shirkneko.zako.sukisu.ui.webui;
package com.sukisu.ultra.ui.webui;
import java.net.URLConnection;

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.webui;
package com.sukisu.ultra.ui.webui;
import android.content.Context;
import android.util.Log;

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.webui
package com.sukisu.ultra.ui.webui
import android.annotation.SuppressLint
import android.app.ActivityManager
@@ -16,7 +16,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.webkit.WebViewAssetLoader
import com.topjohnwu.superuser.Shell
import shirkneko.zako.sukisu.ui.util.createRootShell
import com.sukisu.ultra.ui.util.createRootShell
import java.io.File
@SuppressLint("SetJavaScriptEnabled")

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.ui.webui
package com.sukisu.ultra.ui.webui
import android.app.Activity
import android.content.Context
@@ -14,11 +14,13 @@ import androidx.core.view.WindowInsetsControllerCompat
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler
import shirkneko.zako.sukisu.ui.util.createRootShell
import shirkneko.zako.sukisu.ui.util.listModules
import shirkneko.zako.sukisu.ui.util.withNewRootShell
import com.sukisu.ultra.ui.util.createRootShell
import com.sukisu.ultra.ui.util.listModules
import com.sukisu.ultra.ui.util.withNewRootShell
import org.json.JSONArray
import org.json.JSONObject
import com.sukisu.ultra.ui.util.controlKpmModule
import com.sukisu.ultra.ui.util.listKpmModules
import java.io.File
import java.util.concurrent.CompletableFuture
@@ -197,6 +199,18 @@ class WebViewInterface(
}
return currentModuleInfo.toString()
}
// =================== KPM支持 =============================
@JavascriptInterface
fun listAllKpm() : String {
return listKpmModules()
}
@JavascriptInterface
fun controlKpm(name: String, args: String) : Int {
return controlKpmModule(name, args)
}
}
fun hideSystemUI(window: Window) =

View File

@@ -1,4 +1,4 @@
package shirkneko.zako.sukisu.utils
package com.sukisu.ultra.utils
import android.content.Context
import java.io.File

View File

@@ -0,0 +1,28 @@
package io.sukisu.ultra;
import java.util.ArrayList;
import com.sukisu.ultra.ui.util.KsuCli;
public class UltraShellHelper {
public static String runCmd(String cmds) {
StringBuilder sb = new StringBuilder();
for(String str : KsuCli.INSTANCE.getGLOBAL_MNT_SHELL()
.newJob()
.add(cmds)
.to(new ArrayList<>(), null)
.exec()
.getOut()) {
sb.append(str).append("\n");
}
return sb.toString();
}
public static boolean isPathExists(String path) {
return runCmd("file " + path).contains("No such file or directory");
}
public static void CopyFileTo(String path, String target) {
runCmd("cp -f " + path + " " + target);
}
}

View File

@@ -0,0 +1,21 @@
package io.sukisu.ultra;
import static com.sukisu.ultra.ui.util.KsuCliKt.getKpmmgrPath;
import static com.sukisu.ultra.ui.util.KsuCliKt.getSuSFSDaemonPath;
public class UltraToolInstall {
private static final String OUTSIDE_KPMMGR_PATH = "/data/adb/ksu/bin/kpmmgr";
private static final String OUTSIDE_SUSFSD_PATH = "/data/adb/ksu/bin/susfsd";
public static void tryToInstall() {
String kpmmgrPath = getKpmmgrPath();
if (UltraShellHelper.isPathExists(OUTSIDE_KPMMGR_PATH)) {
UltraShellHelper.CopyFileTo(kpmmgrPath, OUTSIDE_KPMMGR_PATH);
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_KPMMGR_PATH);
}
String SuSFSDaemonPath = getSuSFSDaemonPath();
if (UltraShellHelper.isPathExists(OUTSIDE_SUSFSD_PATH)) {
UltraShellHelper.CopyFileTo(SuSFSDaemonPath, OUTSIDE_SUSFSD_PATH);
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_SUSFSD_PATH);
}
}
}

View File

@@ -1,156 +0,0 @@
package shirkneko.zako.sukisu.ui
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.union
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
import com.ramcosta.composedestinations.generated.NavGraphs
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ksuApp
import shirkneko.zako.sukisu.ui.screen.BottomBarDestination
import shirkneko.zako.sukisu.ui.theme.CardConfig
import shirkneko.zako.sukisu.ui.theme.KernelSUTheme
import shirkneko.zako.sukisu.ui.theme.loadCustomBackground
import shirkneko.zako.sukisu.ui.theme.loadThemeMode
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.rootAvailable
import shirkneko.zako.sukisu.ui.util.install
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Enable edge to edge
enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
super.onCreate(savedInstanceState)
// 加载保存的背景设置
loadCustomBackground()
loadThemeMode()
CardConfig.load(applicationContext)
val isManager = Natives.becomeManager(ksuApp.packageName)
if (isManager) install()
setContent {
KernelSUTheme {
val navController = rememberNavController()
val snackBarHostState = remember { SnackbarHostState() }
Scaffold(
bottomBar = { BottomBar(navController) },
contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { innerPadding ->
CompositionLocalProvider(
LocalSnackbarHost provides snackBarHostState,
) {
DestinationsNavHost(
modifier = Modifier.padding(innerPadding),
navGraph = NavGraphs.root,
navController = navController,
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
get() = { fadeIn(animationSpec = tween(340)) }
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
get() = { fadeOut(animationSpec = tween(340)) }
}
)
}
}
}
}
}
}
@Composable
private fun BottomBar(navController: NavHostController) {
val navigator = navController.rememberDestinationsNavigator()
val isManager = Natives.becomeManager(ksuApp.packageName)
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
// 获取卡片颜色和透明度
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
val cardElevation = CardConfig.cardElevation
NavigationBar(
tonalElevation = cardElevation, // 动态设置阴影
containerColor = cardColor.copy(alpha = cardAlpha), // 动态设置颜色和透明度
contentColor = if (cardColor.luminance() > 0.5) Color.Black else Color.White, // 根据背景亮度设置文字颜色
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
)
) {
BottomBarDestination.entries.forEach { destination ->
if (!fullFeatured && destination.rootRequired) return@forEach
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
NavigationBarItem(
selected = isCurrentDestOnBackStack,
onClick = {
if (isCurrentDestOnBackStack) {
navigator.popBackStack(destination.direction, false)
}
navigator.navigate(destination.direction) {
popUpTo(NavGraphs.root) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = {
if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label))
} else {
Icon(destination.iconNotSelected, stringResource(destination.label))
}
},
label = { Text(stringResource(destination.label)) },
alwaysShowLabel = false,
colors = androidx.compose.material3.NavigationBarItemDefaults.colors(
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
)
)
}
}
}

View File

@@ -1,297 +0,0 @@
package shirkneko.zako.sukisu.ui.screen
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.ConfirmResult
import shirkneko.zako.sukisu.ui.component.SearchAppBar
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
import shirkneko.zako.sukisu.ui.theme.getCardColors
import shirkneko.zako.sukisu.ui.theme.getCardElevation
import shirkneko.zako.sukisu.ui.viewmodel.KpmViewModel
import shirkneko.zako.sukisu.ui.util.loadKpmModule
import shirkneko.zako.sukisu.ui.util.unloadKpmModule
import java.io.File
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun KpmScreen(
navigator: DestinationsNavigator,
viewModel: KpmViewModel = viewModel()
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val snackBarHost = remember { SnackbarHostState() }
val confirmDialog = rememberConfirmDialog()
val loadingDialog = rememberLoadingDialog()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val kpmInstall = stringResource(R.string.kpm_install)
val kpmInstallConfirm = stringResource(R.string.kpm_install_confirm)
val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
val kpmInstallFailed = stringResource(R.string.kpm_install_failed)
val install = stringResource(R.string.install)
val cancel = stringResource(R.string.cancel)
val kpmUninstall = stringResource(R.string.kpm_uninstall)
val kpmUninstallConfirmTemplate = stringResource(R.string.kpm_uninstall_confirm)
val uninstall = stringResource(R.string.uninstall)
val kpmUninstallSuccess = stringResource(R.string.kpm_uninstall_success)
val kpmUninstallFailed = stringResource(R.string.kpm_uninstall_failed)
val selectPatchLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult
val uri = result.data?.data ?: return@rememberLauncherForActivityResult
scope.launch {
// 复制文件到临时目录
val tempFile = File(context.cacheDir, "temp_patch.kpm")
context.contentResolver.openInputStream(uri)?.use { input ->
tempFile.outputStream().use { output ->
input.copyTo(output)
}
}
val confirmResult = confirmDialog.awaitConfirm(
title = kpmInstall,
content = kpmInstallConfirm,
confirm = install,
dismiss = cancel
)
if (confirmResult == ConfirmResult.Confirmed) {
val success = loadingDialog.withLoading {
loadKpmModule(tempFile.absolutePath)
}
Log.d("KsuCli", "loadKpmModule result: $success")
if (success == "success") {
viewModel.fetchModuleList()
snackBarHost.showSnackbar(
message = kpmInstallSuccess,
duration = SnackbarDuration.Long
)
} else {
// 修正为显示安装失败的消息
snackBarHost.showSnackbar(
message = kpmInstallFailed,
duration = SnackbarDuration.Long
)
}
}
tempFile.delete()
}
}
LaunchedEffect(Unit) {
if (viewModel.moduleList.isEmpty()) {
viewModel.fetchModuleList()
}
}
Scaffold(
topBar = {
SearchAppBar(
title = { Text(stringResource(R.string.kpm_title)) },
searchText = viewModel.search,
onSearchTextChange = { viewModel.search = it },
onClearClick = { viewModel.search = "" },
scrollBehavior = scrollBehavior,
dropdownContent = {
IconButton(onClick = { viewModel.fetchModuleList() }) {
Icon(
imageVector = Icons.Outlined.Refresh,
contentDescription = stringResource(R.string.refresh)
)
}
}
)
},
floatingActionButton = {
ExtendedFloatingActionButton(
onClick = {
selectPatchLauncher.launch(
Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/*"
}
)
},
icon = {
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = stringResource(R.string.kpm_install)
)
},
text = { Text(stringResource(R.string.kpm_install)) },
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
},
snackbarHost = { SnackbarHost(snackBarHost) }
) { padding ->
PullToRefreshBox(
onRefresh = { viewModel.fetchModuleList() },
isRefreshing = viewModel.isRefreshing,
modifier = Modifier.padding(padding)
) {
if (viewModel.moduleList.isEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
stringResource(R.string.kpm_empty),
textAlign = TextAlign.Center
)
}
} else {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(viewModel.moduleList) { module ->
val kpmUninstallConfirm = String.format(kpmUninstallConfirmTemplate, module.name)
KpmModuleItem(
module = module,
onUninstall = {
scope.launch {
val confirmResult = confirmDialog.awaitConfirm(
title = kpmUninstall,
content = kpmUninstallConfirm,
confirm = uninstall,
dismiss = cancel
)
if (confirmResult == ConfirmResult.Confirmed) {
val success = loadingDialog.withLoading {
unloadKpmModule(module.id)
}
Log.d("KsuCli", "unloadKpmModule result: $success")
if (success == "success") {
viewModel.fetchModuleList()
snackBarHost.showSnackbar(
message = kpmUninstallSuccess,
duration = SnackbarDuration.Long
)
} else {
snackBarHost.showSnackbar(
message = kpmUninstallFailed,
duration = SnackbarDuration.Long
)
}
}
}
},
onControl = {
viewModel.loadModuleDetail(module.id)
}
)
}
}
}
}
}
}
@Composable
private fun KpmModuleItem(
module: KpmViewModel.ModuleInfo,
onUninstall: () -> Unit,
onControl: () -> Unit
) {
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = module.name,
style = MaterialTheme.typography.titleMedium
)
Text(
text = "${stringResource(R.string.kpm_version)}: ${module.version}",
style = MaterialTheme.typography.bodyMedium
)
Text(
text = "${stringResource(R.string.kpm_author)}: ${module.author}",
style = MaterialTheme.typography.bodyMedium
)
Text(
text = "${stringResource(R.string.kpm_args)}: ${module.args}",
style = MaterialTheme.typography.bodyMedium
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = module.description,
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
FilledTonalButton(
onClick = onControl
) {
Icon(
imageVector = Icons.Outlined.Settings,
contentDescription = null
)
Text(stringResource(R.string.kpm_control))
}
FilledTonalButton(
onClick = onUninstall
) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = null
)
Text(stringResource(R.string.kpm_uninstall))
}
}
}
}
}

View File

@@ -1,98 +0,0 @@
package shirkneko.zako.sukisu.ui.viewmodel
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.ui.util.*
class KpmViewModel : ViewModel() {
var moduleList by mutableStateOf(emptyList<ModuleInfo>())
private set
var search by mutableStateOf("")
internal set
var isRefreshing by mutableStateOf(false)
private set
var currentModuleDetail by mutableStateOf("")
private set
fun loadModuleDetail(moduleId: String) {
viewModelScope.launch {
currentModuleDetail = withContext(Dispatchers.IO) {
try {
getKpmModuleInfo(moduleId)
} catch (e: Exception) {
"无法获取模块详细信息: ${e.message}"
}
}
Log.d("KsuCli", "Module detail: $currentModuleDetail")
}
}
fun fetchModuleList() {
viewModelScope.launch {
isRefreshing = true
try {
val moduleCount = getKpmModuleCount()
Log.d("KsuCli", "Module count: $moduleCount")
val moduleInfo = listKpmModules()
Log.d("KsuCli", "Module info: $moduleInfo")
val modules = parseModuleList(moduleInfo)
moduleList = modules
} finally {
isRefreshing = false
}
}
}
private fun getInstalledKernelPatches(): List<ModuleInfo> {
return try {
val output = printKpmModules()
parseModuleList(output)
} catch (e: Exception) {
emptyList()
}
}
private fun parseModuleList(output: String): List<ModuleInfo> {
return output.split("\n").mapNotNull { line ->
if (line.isBlank()) return@mapNotNull null
val parts = line.split("|")
if (parts.size < 7) return@mapNotNull null
ModuleInfo(
id = parts[0].trim(),
name = parts[1].trim(),
version = parts[2].trim(),
author = parts[3].trim(),
description = parts[4].trim(),
args = parts[6].trim(),
enabled = true,
hasAction = controlKpmModule(parts[0].trim()).isNotBlank()
)
}
}
data class ModuleInfo(
val id: String,
val name: String,
val version: String,
val author: String,
val description: String,
val args: String,
val enabled: Boolean,
val hasAction: Boolean
)
}

View File

@@ -1,3 +1,5 @@
libzakomk.so
libzakomksd.so
libkpmmgr.so
libzakozako.so
libzakozakozako.so
libkpmmgr.so
libzako.so
libandroidx.graphics.path.so

View File

@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید</string>
<string name="home_support_title">از ما حمایت کنید</string>
<string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string>
<string name="profile">پروفایل برنامه</string>
<string name="profile_default">پیش‌فرض</string>
<string name="profile_template">قالب</string>
<string name="profile_custom">شخصی سازی شده</string>

View File

@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">Pelajari cara instal KernelSU dan menggunakan modul</string>
<string name="home_support_title">Dukung Kami</string>
<string name="home_support_content">KernelSU akan selalu menjadi aplikasi gratis dan terbuka. Anda dapat memberikan donasi sebagai bentuk dukungan.</string>
<string name="profile">Profil Apl</string>
<string name="profile_default">Bawaan</string>
<string name="profile_template">Templat</string>
<string name="profile_custom">Khusus</string>

View File

@@ -8,50 +8,56 @@
<string name="home_superuser_count">スーパーユーザー: %d</string>
<string name="home_module_count">モジュール: %d</string>
<string name="home_unsupported">非対応</string>
<string name="home_unsupported_reason">現在、 KernelSU は GKI カーネルにのみ対応しています</string>
<string name="home_kernel">カーネル</string>
<string name="home_manager_version">アプリのバージョン</string>
<string name="home_unsupported_reason">カーネルの KernelSU ドライバが未検出です。カーネルが間違ってませんか?</string>
<string name="home_kernel">カーネルのバージョン</string>
<string name="home_susfs">SuSFS: %s</string>
<string name="home_susfs_version">SuSFS のバージョン</string>
<string name="home_susfs_sus_su">SuS SU</string>
<string name="home_manager_version">マネージャーのバージョン</string>
<string name="home_fingerprint">Fingerprint</string>
<string name="home_selinux_status">SELinux の状態</string>
<string name="selinux_status_disabled">Disabled</string>
<string name="home_selinux_status">SELinux のステータス</string>
<string name="selinux_status_disabled">無効</string>
<string name="selinux_status_enforcing">Enforcing</string>
<string name="selinux_status_permissive">Permissive</string>
<string name="selinux_status_unknown">不明</string>
<string name="superuser">スーパーユーザー</string>
<string name="module_failed_to_enable">%s モジュールをオンにできませんでした</string>
<string name="module_failed_to_disable">%s モジュールをオフにできませんでした</string>
<string name="module_failed_to_enable">%s モジュールを ON にできませんでした</string>
<string name="module_failed_to_disable">%s モジュールを OFF にできませんでした</string>
<string name="module_empty">モジュールがインストールされていません</string>
<string name="module">モジュール</string>
<string name="module_sort_action_first">並べ替え (アクション優先)</string>
<string name="module_sort_enabled_first">並べ替え (最初に有効)</string>
<string name="uninstall">アンインストール</string>
<string name="restore">復元</string>
<string name="module_install">インストール</string>
<string name="install">インストール</string>
<string name="reboot">再起動</string>
<string name="settings">設定</string>
<string name="reboot_userspace">通常の再起動</string>
<string name="reboot_recovery">リカバリー再起動</string>
<string name="reboot_bootloader">ブートローダー再起動</string>
<string name="reboot_download">ダウンロードモード再起動</string>
<string name="reboot_edl">EDL 再起動</string>
<string name="reboot_userspace">ソフトリブート</string>
<string name="reboot_recovery">リカバリー再起動</string>
<string name="reboot_bootloader">ブートローダー再起動</string>
<string name="reboot_download">ダウンロードモード再起動</string>
<string name="reboot_edl">EDL 再起動</string>
<string name="about">アプリについて</string>
<string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string>
<string name="module_uninstall_success">%s はアンインストールされました</string>
<string name="module_uninstall_failed">%s をアンインストールできませんでした</string>
<string name="module_version">バージョン</string>
<string name="module_author">作者</string>
<string name="module_author">作者</string>
<string name="refresh">更新</string>
<string name="show_system_apps">システムアプリを表示</string>
<string name="hide_system_apps">システムアプリを非表示</string>
<string name="send_log">ログを送信</string>
<string name="send_log">ログを送信する</string>
<string name="safe_mode">セーフモード</string>
<string name="reboot_to_apply">再起動すると有効化されます</string>
<string name="module_magisk_conflict">モジュールが Magisk との競合により利用できません!</string>
<string name="home_learn_kernelsu">KernelSU について</string>
<string name="module_magisk_conflict">モジュールが Magisk との競合により利用できません</string>
<string name="home_learn_kernelsu">KernelSU について学ぶ</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/ja_JP/guide/what-is-kernelsu.html</string>
<string name="home_click_to_learn_kernelsu">KernelSU のインストール方法やモジュールの使い方はこちら</string>
<string name="home_click_to_learn_kernelsu">KernelSU のインストール方法やモジュールの使い方を学習できます。</string>
<string name="home_support_title">支援する</string>
<string name="home_support_content">KernelSU はこれからもずっと無料でオープンソースです。寄付をして頂くことで、開発を支援していただけます。</string>
<string name="profile">アプリのプロファイル</string>
<string name="profile_default">既定</string>
<string name="home_support_content">KernelSU は今後も無料でオープンソースです。ですが、寄付をして頂けると開発者への貢献になります。</string>
<string name="about_source_code"><![CDATA[ソースコードは %1$s で確認できます。<br/>%2$s チャンネルにご参加ください。]]></string>
<string name="profile_default">デフォルト</string>
<string name="profile_template">テンプレート</string>
<string name="profile_custom">カスタム</string>
<string name="profile_name">プロファイル名</string>
@@ -59,76 +65,205 @@
<string name="profile_namespace_inherited">継承</string>
<string name="profile_namespace_global">共通</string>
<string name="profile_namespace_individual">分離</string>
<string name="profile_umount_modules">モジュールのアンマウント</string>
<string name="profile_groups">グループ</string>
<string name="profile_capabilities">ケーパビリティ</string>
<string name="profile_selinux_context">SELinux コンテキスト</string>
<string name="profile_umount_modules">モジュールのアンマウント</string>
<string name="failed_to_update_app_profile">%s のアプリのプロファイルの更新をできませでした</string>
<string name="require_kernel_version" formatted="false">現在の KernelSU のバージョン %d は低すぎるため、マネージャーは正常に動作しません。バージョン %d 以上に更新してください!</string>
<string name="settings_umount_modules_default">デフォルトでモジュールのマウントを解除する</string>
<string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通となるデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string>
<string name="settings_susfs_toggle">Kprobe フックを非表示にする</string>
<string name="settings_susfs_toggle_summary">KSU によって生成された Kprobe フックを無効化して、代替となる組み込みの非 Kprobe を有効化します。Kprobe をサポートしない 非 GKI カーネルに適用される同等の機能を実装します。</string>
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
<string name="profile_selinux_domain">ドメイン</string>
<string name="profile_selinux_rules">ルール</string>
<string name="new_version_available">新しいバージョン %s が利用可能です。タップしてダウンロード。</string>
<string name="module_update">アップデート</string>
<string name="module_update">更新</string>
<string name="module_downloading">モジュールをダウンロード中: %s</string>
<string name="module_start_downloading">ダウンロードを開始: %s</string>
<string name="new_version_available">新しいバージョン %s が利用可能です。タップしてダウンロード。</string>
<string name="launch_app">起動</string>
<string name="force_stop_app" formatted="false">強制停止</string>
<string name="restart_app">再起動</string>
<string name="failed_to_update_sepolicy">SELinux ルールの更新に失敗しました %s</string>
<string name="profile_capabilities">ケーパビリティ</string>
<string name="module_downloading">モジュールをダウンロード中: %s</string>
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
<string name="settings_umount_modules_default">既定でモジュールのマウントを解除</string>
<string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通のデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string>
<string name="module_changelog">変更履歴</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_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_id_invalid">無効なテンプレート ID</string>
<string name="app_profile_template_sync">オンラインテンプレートの同期</string>
<string name="settings_profile_template">アプリプロファイルのテンプレート</string>
<string name="settings_profile_template_summary">アプリプロファイルのローカルおよびオンラインテンプレートを管理します</string>
<string name="app_profile_template_create">テンプレートの作成</string>
<string name="app_profile_template_readonly">読み取り専用</string>
<string name="app_profile_import_export">インポート/エクスポート</string>
<string name="app_profile_template_save_failed">テンプレートの保存に失敗しました</string>
<string name="app_profile_template_edit">テンプレートの編集</string>
<string name="app_profile_template_id">ID</string>
<string name="settings_profile_template">アプリプロファイルのテンプレート</string>
<string name="app_profile_template_id_invalid">無効なテンプレート ID</string>
<string name="app_profile_template_name">名前</string>
<string name="app_profile_template_description">説明</string>
<string name="app_profile_template_save">保存</string>
<string name="settings_profile_template_summary">アプリプロファイルのローカルおよびオンラインテンプレートを管理する</string>
<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_summary">アプリを開いたときにアップデートを自動的に確認する</string>
<string name="app_profile_template_readonly">読み取り専用</string>
<string name="app_profile_template_id_exist">テンプレート ID はすでに存在します!</string>
<string name="app_profile_import_export">インポートとエクスポート</string>
<string name="app_profile_import_from_clipboard">クリップボードからインポート</string>
<string name="app_profile_export_to_clipboard">クリップボードからエクスポート</string>
<string name="app_profile_template_export_empty">エクスポートするローカル テンプレートが見つかりません!</string>
<string name="app_profile_template_import_success">インポートが成功しました</string>
<string name="app_profile_template_sync">オンラインテンプレートの同期</string>
<string name="app_profile_template_save_failed">テンプレートの保存に失敗しました</string>
<string name="app_profile_template_import_empty">クリップボードが空です!</string>
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
<string name="settings_check_update">更新を確認する</string>
<string name="settings_check_update_summary">アプリを開いたときに更新を自動的に確認します</string>
<string name="grant_root_failed">root の付与に失敗しました!</string>
<string name="action">アクション</string>
<string name="open">開く</string>
<string name="enable_web_debugging">WebView デバッグを有効する</string>
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合のみ有効してください</string>
<string name="select_file_tip">%1$s パーティション イメージが推奨されます</string>
<string name="select_kmi">KMI を選択してください</string>
<string name="install_next">次に</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="direct_install">直接インストール (推奨)</string>
<string name="select_file">ファイルを選択してください</string>
\n続行しますか</string>
<string name="install_next">次へ</string>
<string name="select_file_tip">%1$s のパーティションイメージを推奨します</string>
<string name="select_kmi">KMI を選択してください</string>
<string name="settings_uninstall">アンインストール</string>
<string name="settings_uninstall_temporary">一時的にアンインストールする</string>
<string name="settings_uninstall_permanent">完全にアンインストールする</string>
<string name="settings_restore_stock_image">ストックイメージを復元</string>
<string name="settings_uninstall_temporary">一時的にアンインストールする</string>
<string name="settings_uninstall">アンインストール</string>
<string name="settings_uninstall_temporary_message">KernelSU を一時的にアンインストールし、次回の再起動後に元の状態に戻します。</string>
<string name="settings_uninstall_permanent_message">KernelSU (ルートおよびすべてのモジュール) を完全かつ永久にアンインストールします。</string>
<string name="settings_uninstall_permanent_message">KernelSU (root およびすべてのモジュール) を完全かつ恒久的にアンインストールします。</string>
<string name="settings_restore_stock_image_message">バックアップが存在する場合、工場出荷時のイメージを復元できます (OTA の前に使用してください)。KernelSU をアンインストールする必要がある場合は、「完全にアンインストールする」を使用してください。</string>
<string name="flashing">フラッシュ</string>
<string name="flash_success">フラッシュ成功</string>
<string name="flash_failed">フラッシュ失敗</string>
<string name="flash_success">フラッシュ成功しました</string>
<string name="flash_failed">フラッシュ失敗しました</string>
<string name="selected_lkm">選択された LKM: %s</string>
<string name="save_log">ログを保存</string>
<string name="action">アクション</string>
<string name="log_saved">保存されたログ</string>
<string name="module_sort_enabled_first">並べ替え(最初に有効)</string>
<string name="module_sort_action_first">並べ替え(アクション優先)</string>
<string name="status_supported">対応</string>
<string name="status_not_supported">非対応</string>
<string name="status_unknown">不明</string>
<string name="sus_su_mode">SuS SU モード:</string>
<!-- Module related -->
<string name="module_install_confirm">%1$s のモジュールをインストールしますか?</string>
<string name="unknown_module">不明なモジュール</string>
<!-- Restore related -->
<string name="restore_confirm_title">モジュールの復元を確認</string>
<string name="restore_confirm_message">この操作によりモジュールが上書きされます。続行しますか?</string>
<string name="confirm">確認</string>
<string name="cancel">キャンセル</string>
<!-- Backup related -->
<string name="backup_success">バックアップが完了しました (tar.gz)</string>
<string name="backup_failed">バックアップに失敗: %1$s</string>
<string name="backup_modules">モジュールをバックアップ</string>
<string name="restore_modules">モジュールを復元</string>
<!-- Restore related messages -->
<string name="restore_success">モジュールは正常に復元されました、再起動が必要です</string>
<string name="restore_failed">復元に失敗: %1$s</string>
<string name="restart_now">今すぐ再起動</string>
<string name="unknown_error">不明なエラー</string>
<!-- Command related -->
<string name="command_execution_failed">コマンドの実行に失敗しました: %1$s</string>
<!-- Allowlist related -->
<string name="allowlist_backup_success">許可リストのバックアップが成功しました</string>
<string name="allowlist_backup_failed">許可リストのバックアップに失敗: %1$s</string>
<string name="allowlist_restore_confirm_title">許可リストの復元を確認</string>
<string name="allowlist_restore_confirm_message">この操作により許可リストが上書きされます。続行しますか?</string>
<string name="allowlist_restore_success">許可リストの復元が成功しました</string>
<string name="allowlist_restore_failed">許可リストの復元に失敗: %1$s</string>
<string name="backup_allowlist">許可リストをバックアップ</string>
<string name="restore_allowlist">許可リストを復元</string>
<string name="settings_custom_background">カスタム背景を設定</string>
<string name="settings_custom_background_summary">カスタム背景を設定します</string>
<string name="settings_card_alpha">ナビゲーションバーの透過</string>
<string name="settings_restore_default">デフォルトに復元</string>
<string name="home_android_version">Android のバージョン</string>
<string name="home_device_model">デバイスモデル</string>
<string name="su_not_allowed">%s にスーパーユーザー権限を付与することはできません</string>
<string name="settings_disable_su">su の互換性を無効化する</string>
<string name="settings_disable_su_summary">su コマンドを使用してアプリが root 権限を取得する動作を一時的に無効化します (既存の root プロセスは影響を受けません)。</string>
<string name="using_mksu_manager">SukiSU Beta Manager を使用しています。</string>
<string name="module_install_multiple_confirm">選択した %d 個のモジュールをインストールしてもよろしいですか?</string>
<string name="module_install_multiple_confirm_with_names">%1$d 個のモジュールをインストールしてもよろしいですか?\n\n%2$s</string>
<string name="more_settings">その他の設定</string>
<string name="selinux">SELinux</string>
<string name="selinux_enabled">有効</string>
<string name="selinux_disabled">無効</string>
<string name="simple_mode">シンプルモード</string>
<string name="simple_mode_summary">ON にすると不要なカードを非表示にします</string>
<string name="hide_kernel_kernelsu_version">カーネルのバージョンを非表示にする</string>
<string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします</string>
<string name="hide_other_info">その他の情報を非表示にする</string>
<string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします</string>
<string name="hide_susfs_status">SuSFS ステータスを非表示にする</string>
<string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします</string>
<string name="theme_mode">テーマモード</string>
<string name="theme_follow_system">システムに従う</string>
<string name="theme_light">ライトカラー</string>
<string name="theme_dark">ダークカラー</string>
<string name="manual_hook">手動でフック</string>
<string name="dynamic_color_title">ダイナミックカラー</string>
<string name="dynamic_color_summary">システムテーマのダイナミックカラーを使用します</string>
<string name="choose_theme_color">テーマカラーを選択</string>
<string name="color_default">ホワイト</string>
<string name="color_blue">ブルー</string>
<string name="color_green">グリーン</string>
<string name="color_purple">パープル</string>
<string name="color_orange">オレンジ</string>
<string name="color_pink">ピンク</string>
<string name="color_gray">グレー</string>
<string name="color_ivory">アイボリー</string>
<string name="flash_option">ブラシの設定</string>
<string name="flash_option_tip">フラッシュするファイルを選択</string>
<string name="horizon_kernel">AnyKernel3 をフラッシュ</string>
<string name="horizon_kernel_summary">AnyKernel3 をフラッシュします</string>
<string name="root_required">root 権限が必要です</string>
<string name="copy_failed">ファイルのコピーに失敗しました</string>
<string name="reboot_complete_title">スクラブが完了しました</string>
<string name="reboot_complete_msg">すぐに再起動しますか?</string>
<string name="yes">はい</string>
<string name="no">いいえ</string>
<string name="failed_reboot">再起動に失敗しました</string>
<string name="batch_authorization">bulk ライセンス</string>
<string name="batch_cancel_authorization">認証を一括でキャンセル</string>
<string name="backup">バックアップ</string>
<string name="color_yellow">イエロー</string>
<string name="kpm">KPM モジュール</string>
<string name="kpm_title">カーネルモジュール</string>
<string name="kpm_empty">カーネルモジュールは現在インストールされていません</string>
<string name="kpm_version">リリース</string>
<string name="kpm_author">作者</string>
<string name="kpm_uninstall">アンインストール</string>
<string name="kpm_uninstall_success">アンインストールに失敗しました</string>
<string name="kpm_uninstall_failed">アンインストールに失敗しました</string>
<string name="kpm_install">インストールを選択</string>
<string name="kpm_install_success">KPM モジュールの読み込みに成功しました</string>
<string name="kpm_install_failed">KPM モジュールの読み込みに失敗しました</string>
<string name="kpm_args">KPM パラメーター</string>
<string name="kpm_control">完全</string>
<string name="home_kpm_version">KPM のバージョン</string>
<string name="close_notice">閉じる</string>
<string name="kernel_module_notice">以下のカーネルモジュール関数は KernelPatch によって開発され、SukiSU Ultra のカーネルモジュール関数を含むように変更されました</string>
<string name="home_ContributionCard_kernelsu">SukiSU Ultra の今後にご期待ください</string>
<string name="kpm_control_success">成功</string>
<string name="kpm_control_failed">失敗</string>
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra は将来的に KSU から比較的に独立したブランチになりますが、公式の KernelSU や MKSU などの貢献に繋がるでしょう!</string>
<string name="not_supported">非対応</string>
<string name="supported">対応</string>
<string name="home_kpm_module">KPM モジュール数: %d </string>
<string name="kpm_invalid_file">無効な KPM ファイル</string>
<string name="kernel_patched">カーネルはパッチされていません</string>
<string name="kernel_not_enabled">カーネルは未設定です</string>
<string name="custom_settings">カスタム設定</string>
<string name="kpm_install_mode">インストール</string>
<string name="kpm_install_mode_load">読み込む</string>
<string name="kpm_install_mode_embed">埋め込む</string>
<string name="kpm_install_mode_description">モジュール:%1\$s のインストールモードを選択してください \n\n読み込む: モジュールを一時的に読み込みます\n埋め込む: システムに恒久的にインストールします</string>
<string name="log_failed_to_check_module_file">モジュールファイルの存在を確認できませんでした</string>
<string name="snackbar_failed_to_check_module_file">モジュールファイルが存在するか確認できません</string>
<string name="confirm_uninstall_title">アンインストールを確認</string>
<string name="confirm_uninstall_confirm">アンインストール中</string>
<string name="confirm_uninstall_dismiss">中止</string>
<string name="theme_color">テーマカラー</string>
<string name="invalid_file_type">ファイルの種類が正しくありません。.kpm ファイルを選択してください。</string>
<string name="confirm_uninstall_title_with_filename">アンインストール</string>
<string name="confirm_uninstall_content">次の KPM モジュールがアンインストールされます:\n%s</string>
</resources>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">KernelSU</string>
<string name="home">Strona główna</string>
<string name="home_not_installed">Nie zainstalowano</string>
<string name="home_click_to_install">Kliknij, aby zainstalować</string>
@@ -51,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">Dowiedz się jak zainstalować KernelSU i jak korzystać z modułów</string>
<string name="home_support_title">Wesprzyj nas</string>
<string name="home_support_content">KernelSU jest i zawsze będzie darmowy oraz otwarty. Niemniej jednak możesz nam pokazać, że Ci zależy, wysyłając darowiznę.</string>
<string name="profile" translatable="false">Profil aplikacji</string>
<string name="profile_default">Domyślny</string>
<string name="profile_template">Szablon</string>
<string name="profile_custom">Własny</string>

View File

@@ -50,7 +50,6 @@
<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 nos ajudar enviando uma pequena doação.</string>
<string name="profile" translatable="false">Perfil do Aplicativo</string>
<string name="profile_default">Padrão</string>
<string name="profile_template">Modelo</string>
<string name="profile_custom">Personalizado</string>

View File

@@ -52,7 +52,6 @@
<string name="home_click_to_learn_kernelsu">Узнайте, как установить KernelSU и использовать модули</string>
<string name="home_support_title">Поддержите нас</string>
<string name="home_support_content">KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string>
<string name="profile" translatable="false">App Profile</string>
<!--Don't translate this string!-->
<string name="profile_default">По умолчанию</string>
<string name="profile_template">Шаблон</string>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">KernelSU</string>
<string name="home">Ana Sayfa</string>
<string name="home_not_installed">Kurulmadı</string>
<string name="home_click_to_install">Kurmak için tıklayın</string>
@@ -51,7 +50,6 @@
<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="profile" translatable="false">Uygulama profili</string>
<string name="profile_default">Varsayılan</string>
<string name="profile_template">Şablon</string>
<string name="profile_custom">Özel</string>

View File

@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">Дізнайтеся, як інсталювати KernelSU і використовувати модулі</string>
<string name="home_support_title">Підтримати нас</string>
<string name="home_support_content">KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string>
<string name="profile">Профіль додатка</string>
<string name="profile_default">Типовий</string>
<string name="profile_template">Шаблон</string>
<string name="profile_custom">Власний</string>

View File

@@ -1,2 +1,263 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="home">Home</string>
<string name="home_not_installed">Chưa cài đặt</string>
<string name="home_click_to_install">Nhấn vào đây để 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_superuser_count">Superusers: %d</string>
<string name="home_module_count">Mô-đun: %d</string>
<string name="home_unsupported">Không được hỗ trợ</string>
<string name="home_unsupported_reason">Không phát hiện được trình điều khiển KernelSU trên kernel của bạn.</string>
<string name="home_kernel">Phiên bản Kernel</string>
<string name="home_susfs">SuSFS: %s</string>
<string name="home_susfs_version">Phiên bản SuSFS</string>
<string name="home_susfs_sus_su">SuS SU</string>
<string name="home_manager_version">Phiên bản quản lý</string>
<string name="home_fingerprint">Dấu vân tay</string>
<string name="home_selinux_status">Trạng thái SELinux</string>
<string name="selinux_status_disabled">Disabled</string>
<string name="selinux_status_enforcing">Enforcing</string>
<string name="selinux_status_permissive">Permissive</string>
<string name="selinux_status_unknown">Không rõ</string>
<string name="superuser">Superuser</string>
<string name="module_failed_to_enable">Không thể bậ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">Không có mô-đun nào được cài đặt</string>
<string name="module">Mô-đun</string>
<string name="module_sort_action_first">Sắp xếp (theo hành động)</string>
<string name="module_sort_enabled_first">Sắp xếp (theo trạng thái)</string>
<string name="uninstall">Gỡ cài đặt</string>
<string name="restore">Khôi phục</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="reboot_userspace">Khởi động lại không gian người dùng</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">Thông tin thêm</string>
<string name="module_uninstall_confirm">Bạn có chắc chắn muốn gỡ cài đặt mô-đun %s không?</string>
<string name="module_uninstall_success">%s đã gỡ cài đặt</string>
<string name="module_uninstall_failed">Không thể gỡ cài đặt: %s</string>
<string name="module_version">Phiên bản</string>
<string name="module_author">Tác giả</string>
<string name="refresh">Làm mới</string>
<string name="show_system_apps">Hiển thị ứng dụng hệ thống</string>
<string name="hide_system_apps">Ẩn ứng dụng hệ thống</string>
<string name="send_log">Gửi nhật ký</string>
<string name="safe_mode">Chế độ an toàn</string>
<string name="reboot_to_apply">Khởi động lại để có hiệu lực</string>
<string name="module_magisk_conflict">Các mô-đun không khả dụng do xung đột với Magisk!</string>
<string name="home_learn_kernelsu">Tìm hiểu KernelSU</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_click_to_learn_kernelsu">Tìm hiểu cách cài đặt KernelSU và sử dụng các mô-đun</string>
<string name="home_support_title">Hỗ trợ chúng tôi</string>
<string name="home_support_content">KernelSU sẽ luôn là miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách quyên góp.</string>
<string name="about_source_code"><![CDATA[View source code at %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
<string name="profile_default">Mặc định</string>
<string name="profile_template">Bản mẫu</string>
<string name="profile_custom">Tùy chỉnh</string>
<string name="profile_name">Tên hồ sơ</string>
<string name="profile_namespace">Tên không gian gắn kết</string>
<string name="profile_namespace_inherited">Được thừa hưởng</string>
<string name="profile_namespace_global">Toàn cầu</string>
<string name="profile_namespace_individual">Cá nhân</string>
<string name="profile_groups">Nhóm</string>
<string name="profile_capabilities">Khả năng</string>
<string name="profile_selinux_context">Bối cảnh SELinux</string>
<string name="profile_umount_modules">Bỏ gắn kết mô-đun</string>
<string name="failed_to_update_app_profile">Không cập nhật được Hồ sơ ứng dụng cho %s</string>
<string name="require_kernel_version" formatted="false">Phiên bản KernelSU hiện tại %d quá thấp để trình quản lý hoạt động bình thường. Vui lòng nâng cấp lên phiên bản %d hoặc cao hơn!</string>
<string name="settings_umount_modules_default">Bỏ gắn kết các mô-đun theo mặc định</string>
<string name="settings_umount_modules_default_summary">Giá trị mặc định cho \"Bỏ gắn kết các mô-đun\" trong Hồ sơ ứng dụng. Nếu được bật, nó sẽ xóa tất cả các sửa đổi của mô-đun đối với hệ thống đối và các ứng dụng không có hồ sơ được đặt.</string>
<string name="settings_susfs_toggle">Ẩn móc kprobe</string>
<string name="settings_susfs_toggle_summary">Vô hiệu hóa các móc kprobe do KSU tạo ra và kích hoạt các móc không phải kprobe tích hợp sẵn, triển khai sẽ được áp dụng cho hạt nhân không phải GKI, không hỗ trợ krp</string>
<string name="profile_umount_modules_summary">Bật tùy chọn này sẽ cho phép KernelSU khôi phục mọi tệp đã được các mô-đun sửa đổi cho ứng dụng này.</string>
<string name="profile_selinux_domain">Domain</string>
<string name="profile_selinux_rules">Quy tắc</string>
<string name="module_update">Cập nhật</string>
<string name="module_downloading">Đang tải xuống mô-đun: %s</string>
<string name="module_start_downloading">Bắt đầu tải xuống: %s</string>
<string name="new_version_available">Phiên bản mới %s đã có sẵn, hãy nhấp để cập nhật.</string>
<string name="launch_app">Khởi chạy</string>
<string name="force_stop_app" formatted="false">Buộc dừng</string>
<string name="restart_app">Khởi động lại</string>
<string name="failed_to_update_sepolicy">Không cập nhật được quy tắc SELinux cho %s</string>
<string name="module_changelog">Nhật ký thay đổi</string>
<string name="settings_profile_template">Mẫu hồ sơ ứng dụng</string>
<string name="settings_profile_template_summary">Quản lý mẫu cục bộ và trực tuyến của Hồ sơ ứng dụng</string>
<string name="app_profile_template_create">Tạo mẫu</string>
<string name="app_profile_template_edit">Chỉnh sửa mẫu</string>
<string name="app_profile_template_id">ID</string>
<string name="app_profile_template_id_invalid">ID mẫu không hợp lệ</string>
<string name="app_profile_template_name">Tên</string>
<string name="app_profile_template_description">Miêu tả</string>
<string name="app_profile_template_save">Lưu</string>
<string name="app_profile_template_delete">Xoá</string>
<string name="app_profile_template_view">Xem mẫu</string>
<string name="app_profile_template_readonly">Chỉ đọc</string>
<string name="app_profile_template_id_exist">ID mẫu đã tồn tại!</string>
<string name="app_profile_import_export">Nhập/Xuất</string>
<string name="app_profile_import_from_clipboard">Nhập từ bộ nhớ tạm clipboard</string>
<string name="app_profile_export_to_clipboard">Xuất vào bộ nhớ tạm clipboard</string>
<string name="app_profile_template_export_empty">Cannot find local template to export!</string>
<string name="app_profile_template_import_success">Đã nhập thành công</string>
<string name="app_profile_template_sync">Đồng bộ mẫu trực tuyến</string>
<string name="app_profile_template_save_failed">Không lưu được mẫu</string>
<string name="app_profile_template_import_empty">Bộ nhớ tạm đang trống!</string>
<string name="module_changelog_failed">Lấy nhật ký thay đổi không thành công: %s</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="grant_root_failed">Không cấp được quyền root!</string>
<string name="action">Khởi chạy</string>
<string name="open">Mở</string>
<string name="enable_web_debugging">Bật gỡ lỗi WebView</string>
<string name="enable_web_debugging_summary">Có thể sử dụng để gỡ lỗi WebUI. Vui lòng chỉ bật khi cần thiết.</string>
<string name="direct_install">Cài đặt trực tiếp (Khuyến nghị)</string>
<string name="select_file">Chọn một tập tin</string>
<string name="install_inactive_slot">Cài đặt vào khe không hoạt động (Sau OTA)</string>
<string name="install_inactive_slot_warning">Thiết bị của bạn sẽ **BUỘC** phải khởi động vào khe cắm hiện tại không hoạt động sau khi khởi động lại!\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất.\nTiếp tục?</string>
<string name="install_next">Kế tiếp</string>
<string name="select_file_tip">Phân vùng %1$s được khuyến nghị</string>
<string name="select_kmi">Chọn KMI</string>
<string name="settings_uninstall">Uninstall</string>
<string name="settings_uninstall_temporary">Gỡ cài đặt tạm thời</string>
<string name="settings_uninstall_permanent">Gỡ cài đặt vĩnh viễn</string>
<string name="settings_restore_stock_image">Khôi phục img ban đầu</string>
<string name="settings_uninstall_temporary_message">Gỡ cài đặt tạm thời KernelSU, khôi phục lại trạng thái ban đầu sau lần khởi động lại tiếp theo.</string>
<string name="settings_uninstall_permanent_message">Gỡ cài đặt KernelSU (Root và tất cả các mô-đun) hoàn toàn và vĩnh viễn.</string>
<string name="settings_restore_stock_image_message">Khôi phục ảnh gốc (nếu có bản sao lưu), thường được sử dụng trước OTA; nếu bạn cần gỡ cài đặt KernelSU, vui lòng sử dụng \"Gỡ cài đặt vĩnh viễn\".</string>
<string name="flashing">Đang flash...</string>
<string name="flash_success">Flash thành công</string>
<string name="flash_failed">Lỗi flash</string>
<string name="selected_lkm">LKM đã chọn: %s</string>
<string name="save_log">Lưu nhật ký</string>
<string name="log_saved">Nhật ký đã lưu</string>
<string name="status_supported">Được hỗ trợ</string>
<string name="status_not_supported">Không được hỗ trợ</string>
<string name="status_unknown">Không rõ</string>
<string name="sus_su_mode">Chế độ SuS: SU</string>
<!-- Module related -->
<string name="module_install_confirm">Xác nhận cài đặt mô-đun %1$s ?</string>
<string name="unknown_module">Mô-đun không xác định</string>
<!-- Restore related -->
<string name="restore_confirm_title">Xác nhận khôi phục mô-đun</string>
<string name="restore_confirm_message">Hành động này sẽ ghi đè lên tất cả các mô-đun hiện có. Tiếp tục?</string>
<string name="confirm">Xác nhận</string>
<string name="cancel">Hủy bỏ</string>
<!-- Backup related -->
<string name="backup_success">Sao lưu thành công (tar.gz)</string>
<string name="backup_failed">Sao lưu không thành công: %1$s</string>
<string name="backup_modules">Sao lưu mô-đun</string>
<string name="restore_modules">Khôi phục các mô-đun</string>
<!-- Restore related messages -->
<string name="restore_success">Các mô-đun đã được khôi phục thành công, cần khởi động lại</string>
<string name="restore_failed">Khôi phục không thành công: %1$s</string>
<string name="restart_now">Khởi động lại ngay</string>
<string name="unknown_error">Lỗi không xác định</string>
<!-- Command related -->
<string name="command_execution_failed">Thực hiện lệnh không thành công: %1$s</string>
<!-- Allowlist related -->
<string name="allowlist_backup_success">Sao lưu danh sách cho phép thành công</string>
<string name="allowlist_backup_failed">Sao lưu danh sách cho phép không thành công: %1$s</string>
<string name="allowlist_restore_confirm_title">Xác nhận khôi phục danh sách cho phép</string>
<string name="allowlist_restore_confirm_message">Hành động này sẽ ghi đè lên danh sách cho phép hiện tại. Tiếp tục?</string>
<string name="allowlist_restore_success">Đã khôi phục danh sách cho phép thành công</string>
<string name="allowlist_restore_failed">Khôi phục danh sách cho phép không thành công: %1$s</string>
<string name="backup_allowlist">Sao lưu danh sách cho phép</string>
<string name="restore_allowlist">Khôi phục danh sách cho phép</string>
<string name="settings_custom_background">Cài đặt nền tùy chỉnh</string>
<string name="settings_custom_background_summary">Cài đặt tùy chỉnh nền</string>
<string name="settings_card_alpha">Thẻ alpha</string>
<string name="settings_restore_default">Khôi phục mặc định</string>
<string name="home_android_version">Phiên bản Android</string>
<string name="home_device_model">Model thiết bị</string>
<string name="su_not_allowed">Không được phép cấp siêu người dùng cho %s</string>
<string name="settings_disable_su">Vô hiệu hóa khả năng tương thích su</string>
<string name="settings_disable_su_summary">Tạm thời vô hiệu hóa mọi ứng dụng khỏi quyền root thông qua lệnh su (các tiến trình root hiện có sẽ không bị ảnh hưởng).</string>
<string name="using_mksu_manager">Bạn đang sử dụng trình quản lý SukiSU thử nghiệm</string>
<string name="module_install_multiple_confirm">Bạn có chắc chắn muốn cài đặt các mô-đun %d đã chọn không?</string>
<string name="module_install_multiple_confirm_with_names">Bạn có chắc chắn muốn cài đặt các mô-đun %1$d sau không? \n\n%2$s</string>
<string name="more_settings">Nhiều thiết lập hơn</string>
<string name="selinux">SELinux</string>
<string name="selinux_enabled">Đã bật</string>
<string name="selinux_disabled">Đã tắt</string>
<string name="simple_mode">Chế độ đơn giản</string>
<string name="simple_mode_summary">Ẩn các thẻ không cần thiết khi bật</string>
<string name="hide_kernel_kernelsu_version">Ẩn phiên bản kernel</string>
<string name="hide_kernel_kernelsu_version_summary">Ẩn phiên bản kernel hiện tại</string>
<string name="hide_other_info">Ẩn thông tin thêm</string>
<string name="hide_other_info_summary">Ẩn thông tin về số lượng app đã được cấp root, mô-đun và mô-đun KPM trên trang chủ</string>
<string name="hide_susfs_status">Ẩn trạng thái SuSFS</string>
<string name="hide_susfs_status_summary">Ẩn thông tin trạng thái SuSFS trên trang chủ</string>
<string name="theme_mode">Chế độ chủ đề</string>
<string name="theme_follow_system">Theo hệ thống</string>
<string name="theme_light">Sáng</string>
<string name="theme_dark">Tối</string>
<string name="manual_hook">Móc thủ công</string>
<string name="dynamic_color_title">Màu sắc động</string>
<string name="dynamic_color_summary">Màu sắc động sử dụng chủ đề hệ thống</string>
<string name="choose_theme_color">Chọn màu chủ đề</string>
<string name="color_default">Trắng</string>
<string name="color_blue">Xanh dương</string>
<string name="color_green">Xanh lá</string>
<string name="color_purple">Tím</string>
<string name="color_orange">Cam</string>
<string name="color_pink">Hồng</string>
<string name="color_gray">Xám</string>
<string name="color_ivory">Ngà voi</string>
<string name="flash_option">Tùy chọn cọ</string>
<string name="flash_option_tip">Chọn tập tin cần flash</string>
<string name="horizon_kernel">File Anykernel3</string>
<string name="root_required">Yêu cầu quyền root</string>
<string name="copy_failed">Sao chép tập tin không thành công</string>
<string name="reboot_complete_title">Hoàn tất</string>
<string name="reboot_complete_msg">khởi động lại ngay lập tức?</string>
<string name="yes"></string>
<string name="no">Không</string>
<string name="failed_reboot">Khởi động lại không thành công</string>
<string name="batch_authorization">Giấy phép số lượng lớn</string>
<string name="batch_cancel_authorization">Hủy ủy quyền hàng loạt</string>
<string name="backup">Sao lưu</string>
<string name="color_yellow">Vàng</string>
<string name="kpm">Mô-đun kpm</string>
<string name="kpm_title">Mô-đun KPM</string>
<string name="kpm_empty">Không có mô-đun nào được cài đặt.</string>
<string name="kpm_version">Phát hành</string>
<string name="kpm_author">Tác giả</string>
<string name="kpm_uninstall">Gỡ cài đặt</string>
<string name="kpm_uninstall_success">Đã gỡ cài đặt thành công</string>
<string name="kpm_uninstall_failed">Không thể gỡ cài đặt</string>
<string name="kpm_install">Cài đặt</string>
<string name="kpm_install_success">Tải module kpm thành công</string>
<string name="kpm_install_failed">Tải module kpm không thành công</string>
<string name="kpm_args">thông số kpm</string>
<string name="kpm_control">Tùy chỉnh</string>
<string name="home_kpm_version">Phiên bản KPM</string>
<string name="close_notice">Đóng</string>
<string name="kernel_module_notice">Các chức năng mô-đun kernel sau đây được KernelPatch phát triển và sửa đổi để bao gồm các chức năng mô-đun hạt nhân của SukiSU Ultra</string>
<string name="home_ContributionCard_kernelsu">SukiSU Ultra mong đợi:</string>
<string name="kpm_control_success">Thành công</string>
<string name="kpm_control_failed">Thất bại</string>
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra sẽ là một nhánh tương đối độc lập của KSU trong tương lai, nhưng xin cảm ơn KernelSU và MKSU chính thức vì những đóng góp của họ!</string>
<string name="not_supported">Không được hỗ trợ</string>
<string name="supported">Được hỗ trợ</string>
<string name="home_kpm_module">Mô-đun KPM%d </string>
<string name="kpm_invalid_file">Tệp KPM không hợp lệ</string>
<string name="kernel_patched">chưa được vá</string>
<string name="kernel_not_enabled">chưa được vá</string>
<string name="custom_settings">Cài đặt tùy chỉnh</string>
<string name="kpm_install_mode">Cài đặt</string>
<string name="kpm_install_mode_load">Tải</string>
<string name="kpm_install_mode_embed">Nhúng</string>
<string name="kpm_install_mode_description">Vui lòng%1\$s chọn chế độ cài đặt mô-đun \n\nTải: Tải tạm thời mô-đun \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string>
<string name="log_failed_to_check_module_file">Không kiểm tra được sự tồn tại của tệp mô-đun</string>
<string name="snackbar_failed_to_check_module_file">Không thể kiểm tra xem tệp mô-đun có tồn tại không</string>
<string name="confirm_uninstall_title">Xác nhận gỡ cài đặt</string>
<string name="confirm_uninstall_confirm">loại bỏ</string>
<string name="confirm_uninstall_dismiss">bãi bỏ</string>
<string name="theme_color">Màu chủ đề</string>
</resources>

View File

@@ -172,7 +172,6 @@
<string name="restore_allowlist">还原应用列表</string>
<string name="settings_custom_background">自定义背景</string>
<string name="settings_custom_background_summary">选择一张图片作为应用背景</string>
<string name="settings_card_manage">卡片管理</string>
<string name="settings_card_alpha">卡片透明度</string>
<string name="settings_restore_default">恢复默认</string>
<string name="home_android_version">Android 版本</string>
@@ -180,7 +179,7 @@
<string name="su_not_allowed">不允许授予 %s 超级用户权限</string>
<string name="settings_disable_su">禁用 su 兼容性</string>
<string name="settings_disable_su_summary">临时禁止任何应用程序通过 su 命令获取 Root 权限(现有的 Root 进程不受影响)</string>
<string name="using_mksu_manager">你正在使用的是 MKSU 第三方管理器</string>
<string name="using_mksu_manager">你正在使用的是 SukiSU Beta 版管理器</string>
<string name="module_install_multiple_confirm">确定要安装选择的 %d 个模块吗?</string>
<string name="module_install_multiple_confirm_with_names">确定要安装以下 %1$d 个模块吗?\n\n%2$s</string>
<string name="more_settings">更多设置</string>
@@ -191,6 +190,10 @@
<string name="simple_mode_summary">开启后将隐藏不必要的卡片</string>
<string name="hide_kernel_kernelsu_version">隐藏内核版本号</string>
<string name="hide_kernel_kernelsu_version_summary">隐藏内核部分的 KernelSU 版本号</string>
<string name="hide_other_info">强迫症开关</string>
<string name="hide_other_info_summary">隐藏主页上的超级用户数、模块数和 KPM 模块数信息</string>
<string name="hide_susfs_status">隐藏 SuSFS 状态信息</string>
<string name="hide_susfs_status_summary">隐藏主页上的 SuSFS 状态信息</string>
<string name="theme_mode">主题模式</string>
<string name="theme_follow_system">跟随系统</string>
<string name="theme_light">浅色</string>
@@ -210,6 +213,7 @@
<string name="flash_option">刷入选项</string>
<string name="flash_option_tip">选择要刷入的文件</string>
<string name="horizon_kernel">刷写 AnyKernel3 压缩包</string>
<string name="horizon_kernel_summary">刷入 Anykernel3 内核</string>
<string name="root_required">需要 root 权限</string>
<string name="copy_failed">文件复制失败</string>
<string name="reboot_complete_title">刷写完成</string>
@@ -225,13 +229,67 @@
<string name="kpm_empty">暂无已安装的内核模块</string>
<string name="kpm_version">版本</string>
<string name="kpm_author">作者</string>
<string name="kpm_execute">执行</string>
<string name="kpm_uninstall">卸载</string>
<string name="kpm_uninstall_confirm">确定要卸载内核模块 %1$s 吗?</string>
<string name="kpm_uninstall_success">卸载成功</string>
<string name="kpm_uninstall_failed">卸载失败</string>
<string name="kpm_install">安装kpm模块</string>
<string name="kpm_install_confirm">确认安装吗?</string>
<string name="kpm_install_success">安装kpm模块成功</string>
<string name="kpm_install_failed">安装kpm模块失败</string>
</resources>
<string name="kpm_install">选择安装</string>
<string name="kpm_install_success">加载 kpm 模块成功</string>
<string name="kpm_install_failed">加载 kpm 模块失败</string>
<string name="home_kpm_version">KPM 版本</string>
<string name="kpm_control">调参</string>
<string name="close_notice">关闭</string>
<string name="kpm_control_success">成功</string>
<string name="kpm_control_failed">错误</string>
<string name="not_supported">不支持</string>
<string name="supported">支持</string>
<string name="home_kpm_module">KPM 模块数:%d </string>
<string name="kpm_invalid_file">KPM 文件无效</string>
<string name="kernel_patched">内核未进行补丁</string>
<string name="kernel_not_enabled">内核未配置</string>
<string name="kernel_module_notice">以下内核模块功能由 KernelPatch 开发,经过修改后加入 SukiSU Ultra 的内核模块功能</string>
<string name="home_ContributionCard_kernelsu">SukiSU Ultra 展望</string>
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra 未来将会成为一个相对独立的 KSU 分支,但是依然感谢官方 KernelSU 和 MKSU 等做出的贡献</string>
<string name="custom_settings">个性化设置</string>
<string name="kpm_install_mode">安装</string>
<string name="kpm_install_mode_load">加载</string>
<string name="kpm_install_mode_embed">嵌入</string>
<string name="kpm_install_mode_description">请选择: %1\$s 模块的安装模式 \n\n加载临时加载模块\n嵌入永久安装到系统</string>
<string name="snackbar_failed_to_check_module_file">无法检查模块文件是否存在</string>
<string name="confirm_uninstall_title">确认卸载</string>
<string name="confirm_uninstall_confirm">删除</string>
<string name="confirm_uninstall_dismiss">取消</string>
<string name="theme_color">主题颜色</string>
<string name="invalid_file_type">文件类型不正确,请选择 .kpm 文件</string>
<string name="confirm_uninstall_title_with_filename">卸载</string>
<string name="confirm_uninstall_content">将卸载以下 kpm 模块:\n%s</string>
<string name="image_editor_title">调整背景图片</string>
<string name="image_editor_hint">使用双指缩放图片,单指拖动调整位置</string>
<string name="background_image_error">无法加载图片</string>
<string name="reprovision">重置</string>
<!-- Anykernel3 Kernel刷写进度相关 -->
<string name="horizon_flash_title">刷写Kernel</string>
<string name="horizon_logs_label">日志:</string>
<string name="horizon_flash_complete">刷写完成</string>
<!-- 刷写状态相关 -->
<string name="horizon_preparing">准备中…</string>
<string name="horizon_cleaning_files">清理文件…</string>
<string name="horizon_copying_files">复制文件…</string>
<string name="horizon_extracting_tool">提取刷写工具…</string>
<string name="horizon_patching_script">修补刷写脚本…</string>
<string name="horizon_flashing">刷写内核中…</string>
<string name="horizon_flash_complete_status">刷写完成</string>
<!-- 槽位选择相关字符串 -->
<string name="select_slot_title">选择刷写槽位</string>
<string name="select_slot_description">请选择要刷写boot的目标槽位</string>
<string name="slot_a">A槽位</string>
<string name="slot_b">B槽位</string>
<string name="selected_slot">已选择槽位: %1$s</string>
<!-- 错误信息 -->
<string name="horizon_copy_failed">复制失败</string>
<string name="horizon_unknown_error">未知错误</string>
<string name="horizon_getting_original_slot">获取原有槽位</string>
<string name="horizon_setting_target_slot">设置指定槽位</string>
<string name="horizon_restoring_original_slot">恢复默认槽位</string>
<string name="current_slot">当前槽位:%1$s </string>
<string name="flash_failed_message">刷写失败</string>
</resources>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">SukiSU</string>
<string name="app_name" translatable="false">SukiSU Ultra</string>
<string name="home">Home</string>
<string name="home_not_installed">Not installed</string>
<string name="home_click_to_install">Click to install</string>
@@ -10,9 +10,9 @@
<string name="home_module_count">Modules: %d</string>
<string name="home_unsupported">Unsupported</string>
<string name="home_unsupported_reason">No KernelSU driver detected on your kernel, wrong kernel?.</string>
<string name="home_kernel">Kernel</string>
<string name="home_kernel">Kernel version</string>
<string name="home_susfs">SuSFS: %s</string>
<string name="home_susfs_version">Version de SuSFS</string>
<string name="home_susfs_version">SuSFS Version</string>
<string name="home_susfs_sus_su">SuS SU</string>
<string name="home_manager_version">Manager version</string>
<string name="home_fingerprint">Fingerprint</string>
@@ -75,8 +75,7 @@
<string name="require_kernel_version" formatted="false">The current KernelSU version %d is too low for the manager to work properly. Please upgrade to version %d or higher!</string>
<string name="settings_umount_modules_default">Umount modules by default</string>
<string name="settings_umount_modules_default_summary">The global default value for \"Umount modules\" in App Profile. If enabled, it will remove all module modifications to the system for apps that don\'t have a profile set.</string>
<string name="settings_susfs_toggle">Cacher les hooks kprobe</string>
<string name="settings_susfs_toggle_summary">Désactive les hooks kprobe créés par KSU et, à la place, active les hooks non-kprobe intégrés, implémentant les mêmes fonctionnalités qui seraient appliquées à un kernel non-GKI, qui ne supportent krpobe.</string>
<string name="settings_susfs_toggle">Disable kprobe hooks</string>
<string name="profile_umount_modules_summary">Enabling this option will allow KernelSU to restore any modified files by the modules for this app.</string>
<string name="profile_selinux_domain">Domain</string>
<string name="profile_selinux_rules">Rules</string>
@@ -171,71 +170,130 @@
<string name="allowlist_restore_failed">Allowlist restore failed: %1$s</string>
<string name="backup_allowlist">Backup Allowlist</string>
<string name="restore_allowlist">Restore Allowlist</string>
<string name="settings_custom_background">settings custom background</string>
<string name="settings_custom_background_summary">settings custom background summary</string>
<string name="settings_card_manage">card manage</string>
<string name="settings_card_alpha">card alpha</string>
<string name="settings_restore_default">restore default</string>
<string name="settings_custom_background">Custom App Background</string>
<string name="settings_custom_background_summary">Select an image as background</string>
<string name="settings_card_alpha">Navigation bar transparency</string>
<string name="settings_restore_default">Restore default</string>
<string name="home_android_version">Android version</string>
<string name="home_device_model">device model</string>
<string name="home_device_model">Device model</string>
<string name="su_not_allowed">Granting superuser to %s is not allowed</string>
<string name="settings_disable_su">Disable su compatibility</string>
<string name="settings_disable_su_summary">Temporarily disable any applications from obtaining root privileges via the su command (existing root processes will not be affected).</string>
<string name="using_mksu_manager">You are using the MKSU third-party manager</string>
<string name="using_mksu_manager">You are using the SukiSU Beta manager</string>
<string name="module_install_multiple_confirm">Are you sure you want to install the selected %d modules?</string>
<string name="module_install_multiple_confirm_with_names">Sure you want to install the following %1$d modules? \n\n%2$s</string>
<string name="more_settings">more settings</string>
<string name="more_settings">More settings</string>
<string name="selinux">SELinux</string>
<string name="selinux_enabled">Enabled</string>
<string name="selinux_disabled">Disabled</string>
<string name="simple_mode">simplicity mode</string>
<string name="simple_mode">Simplicity mode</string>
<string name="simple_mode_summary">Hides unnecessary cards when turned on</string>
<string name="hide_kernel_kernelsu_version">Hide kernel version</string>
<string name="hide_kernel_kernelsu_version_summary">Hide kernel version</string>
<string name="theme_mode">Theme Mode</string>
<string name="theme_follow_system">follow-up system</string>
<string name="theme_light">light color</string>
<string name="theme_dark">dark colored</string>
<string name="hide_other_info">Hide other info</string>
<string name="hide_other_info_summary">Hides information about the number of super users, modules and KPM modules on the home page</string>
<string name="hide_susfs_status">Hide SuSFS status</string>
<string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</string>
<string name="theme_mode">Theme</string>
<string name="theme_follow_system">Follow system</string>
<string name="theme_light">Light</string>
<string name="theme_dark">Dark</string>
<string name="manual_hook">Manual Hook</string>
<string name="dynamic_color_title">Dynamic colours</string>
<string name="dynamic_color_summary">Dynamic colours using system themes</string>
<string name="choose_theme_color">Choose a theme colour</string>
<string name="color_default">White</string>
<string name="color_blue">blue</string>
<string name="color_green">green</string>
<string name="color_purple">purple</string>
<string name="color_orange">orange</string>
<string name="color_pink">pink</string>
<string name="color_gray">gray</string>
<string name="color_ivory">ivory</string>
<string name="color_blue">Blue</string>
<string name="color_green">Green</string>
<string name="color_purple">Purple</string>
<string name="color_orange">Orange</string>
<string name="color_pink">Pink</string>
<string name="color_gray">Gray</string>
<string name="color_ivory">Ivory</string>
<string name="flash_option">Brush Options</string>
<string name="flash_option_tip">Select the file to be flashed</string>
<string name="horizon_kernel">Anykernel3 Flush</string>
<string name="horizon_kernel">Install Anykernel3</string>
<string name="horizon_kernel_summary">Flash AnyKernel3 kernel file</string>
<string name="root_required">Requires root privileges</string>
<string name="copy_failed">File Copy Failure</string>
<string name="reboot_complete_title">Scrubbing complete</string>
<string name="reboot_complete_msg">Whether to reboot immediately</string>
<string name="yes">yes</string>
<string name="no">no</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="failed_reboot">Reboot Failed</string>
<string name="batch_authorization">bulk license</string>
<string name="batch_authorization">Bulk license</string>
<string name="batch_cancel_authorization">Batch cancel authorization</string>
<string name="backup">Backup</string>
<string name="color_yellow">Yellow</string>
<string name="kpm">kpm module</string>
<string name="kpm_title">kernel module</string>
<string name="kpm">Kernel Module</string>
<string name="kpm_title">Kernel Module</string>
<string name="kpm_empty">No installed kernel modules at this time</string>
<string name="kpm_version">releases</string>
<string name="kpm_author">author</string>
<string name="kpm_execute">fulfillment</string>
<string name="kpm_uninstall">uninstallation</string>
<string name="kpm_uninstall_confirm">Determine the kernel module to uninstall: %1$s </string>
<string name="kpm_version">Version</string>
<string name="kpm_author">Author</string>
<string name="kpm_uninstall">Uninstall</string>
<string name="kpm_uninstall_success">Uninstalled successfully</string>
<string name="kpm_uninstall_failed">Failed to uninstall</string>
<string name="kpm_install">Installing the kpm module</string>
<string name="kpm_install_confirm">Confirm installation?</string>
<string name="kpm_install_success">Installation of kpm module successful</string>
<string name="kpm_install_failed">Installation of kpm module failed</string>
<string name="kpm_args">kpm 参数</string>
<string name="kpm_control">kpm 控制</string>
<string name="kpm_install">Install</string>
<string name="kpm_install_success">Load of kpm module successful</string>
<string name="kpm_install_failed">Load of kpm module failed</string>
<string name="kpm_args">Parameters</string>
<string name="kpm_control">Execute</string>
<string name="home_kpm_version">KPM Version</string>
<string name="close_notice">Close</string>
<string name="kernel_module_notice">The following kernel module functions were developed by KernelPatch and modified to include the kernel module functions of SukiSU Ultra</string>
<string name="home_ContributionCard_kernelsu">SukiSU Ultra Look forward to</string>
<string name="kpm_control_success">Success</string>
<string name="kpm_control_failed">Failed</string>
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra will be a relatively independent branch of KSU in the future, but we still appreciate the official KernelSU and MKSU etc. for their contributions!</string>
<string name="not_supported">Unsupported</string>
<string name="supported">Supported</string>
<string name="home_kpm_module">"Number of KPM modules: %d "</string>
<string name="kpm_invalid_file">Invalid KPM file</string>
<string name="kernel_patched">Kernel not patched</string>
<string name="kernel_not_enabled">Kernel not configured</string>
<string name="custom_settings">Custom settings</string>
<string name="kpm_install_mode">KPM Install</string>
<string name="kpm_install_mode_load">Load</string>
<string name="kpm_install_mode_embed">Embed</string>
<string name="kpm_install_mode_description">Please select: %1\$s Module Installation Mode \n\nLoad: Temporarily load the module \nEmbedded: Permanently install into the system</string>
<string name="log_failed_to_check_module_file">Failed to check module file existence</string>
<string name="snackbar_failed_to_check_module_file">Unable to check if module file exists</string>
<string name="confirm_uninstall_title">Confirm uninstallation</string>
<string name="confirm_uninstall_confirm">Uninstall</string>
<string name="confirm_uninstall_dismiss">Cancel</string>
<string name="theme_color">Theme Color</string>
<string name="invalid_file_type">Incorrect file type! Please select .kpm file.</string>
<string name="confirm_uninstall_title_with_filename">Uninstall</string>
<string name="confirm_uninstall_content">The following KPM will be uninstalled: %s</string>
<string name="settings_susfs_toggle_summary">Disable kprobe hooks created by KernelSU, using inline hooks instead, which is similar to non-GKI kernel hooking method.</string>
<string name="image_editor_title">Adjust background image</string>
<string name="image_editor_hint">Use two fingers to zoom the image, and one finger to drag it to adjust the position</string>
<string name="background_image_error">Could not load image</string>
<string name="reprovision">Reprovision</string>
<!-- Kernel Flash Progress Related -->
<string name="horizon_flash_title">Kernel Flashing</string>
<string name="horizon_logs_label">Logs:</string>
<string name="horizon_flash_complete">Flash Complete</string>
<!-- Flash Status Related -->
<string name="horizon_preparing">Preparing…</string>
<string name="horizon_cleaning_files">Cleaning files…</string>
<string name="horizon_copying_files">Copying files…</string>
<string name="horizon_extracting_tool">Extracting flash tool…</string>
<string name="horizon_patching_script">Patching flash script…</string>
<string name="horizon_flashing">Flashing kernel…</string>
<string name="horizon_flash_complete_status">Flash completed</string>
<!-- Slot selection related strings -->
<string name="select_slot_title">Select Flash Slot</string>
<string name="select_slot_description">Please select the target slot for flashing boot</string>
<string name="slot_a">Slot A</string>
<string name="slot_b">Slot B</string>
<string name="selected_slot">Selected slot: %1$s</string>
<!-- Error Messages -->
<string name="horizon_copy_failed">Copy failed</string>
<string name="horizon_unknown_error">Unknown error</string>
<string name="horizon_getting_original_slot">Getting the original slot</string>
<string name="horizon_setting_target_slot">Setting the specified slot</string>
<string name="horizon_restoring_original_slot">Restore Default Slot</string>
<string name="current_slot">Current Slot%1$s </string>
<string name="flash_failed_message">Flash failed</string>
</resources>

View File

@@ -28,8 +28,8 @@ cmaker {
}
val androidMinSdkVersion = 26
val androidTargetSdkVersion = 35
val androidCompileSdkVersion = 35
val androidTargetSdkVersion = 36
val androidCompileSdkVersion = 36
val androidCompileNdkVersion = "28.0.13004108"
val androidSourceCompatibility = JavaVersion.VERSION_21
val androidTargetCompatibility = JavaVersion.VERSION_21

View File

@@ -1,25 +1,25 @@
[versions]
agp = "8.9.1"
agp = "8.9.2"
kotlin = "2.1.10"
ksp = "2.1.10-1.0.30"
compose-bom = "2025.02.00"
compose-bom = "2025.04.01"
lifecycle = "2.8.7"
navigation = "2.8.7"
activity-compose = "1.10.0"
kotlinx-coroutines = "1.10.1"
navigation = "2.8.9"
activity-compose = "1.10.1"
kotlinx-coroutines = "1.10.2"
coil-compose = "2.7.0"
compose-destination = "2.1.0-beta16"
compose-destination = "2.1.0"
sheets-compose-dialogs = "1.3.0"
markdown = "4.6.2"
webkit = "1.12.1"
webkit = "1.13.0"
appiconloader-coil = "1.5.0"
parcelablelist = "2.0.1"
libsu = "6.0.0"
apksign = "1.4"
cmaker = "1.2"
compose-material = "1.7.8"
compose-material3 = "1.3.1"
compose-ui = "1.7.8"
compose-material = "1.8.0"
compose-material3 = "1.3.2"
compose-ui = "1.8.0"
compose-foundation = "1.7.8"
documentfile = "1.0.1"

View File

@@ -13,15 +13,38 @@
#define CMD_KPM_CONTROL_MAX 7
// 控制代码
#define SUKISU_KPM_LOAD 1
#define SUKISU_KPM_UNLOAD 2
#define SUKISU_KPM_NUM 3
#define SUKISU_KPM_LIST 4
#define SUKISU_KPM_INFO 5
#define SUKISU_KPM_CONTROL 6
#define SUKISU_KPM_PRINT 7
// prctl(xxx, 28, "PATH", "ARGS")
// success return 0, error return -N
#define SUKISU_KPM_LOAD 28
#define CONTROL_CODE(n) (CMD_KPM_CONTROL + n - 1)
// prctl(xxx, 29, "NAME")
// success return 0, error return -N
#define SUKISU_KPM_UNLOAD 29
// num = prctl(xxx, 30)
// error return -N
// success return +num or 0
#define SUKISU_KPM_NUM 30
// prctl(xxx, 31, Buffer, BufferSize)
// success return +out, error return -N
#define SUKISU_KPM_LIST 31
// prctl(xxx, 32, "NAME", Buffer[256])
// success return +out, error return -N
#define SUKISU_KPM_INFO 32
// prctl(xxx, 33, "NAME", "ARGS")
// success return KPM's result value
// error return -N
#define SUKISU_KPM_CONTROL 33
// prctl(xxx, 34, buffer, bufferSize)
// success return KPM's result value
// error return -N
#define SUKISU_KPM_VERSION 34
#define CONTROL_CODE(n) (n)
void print_usage(const char *prog) {
printf("Usage: %s <command> [args]\n", prog);
@@ -32,7 +55,7 @@ void print_usage(const char *prog) {
printf(" list List loaded KPM modules\n");
printf(" info <name> Get info of a KPM module\n");
printf(" control <name> <args> Send control command to a KPM module\n");
printf(" print Print KPM module list to stdout\n");
printf(" version Print KPM Loader version\n");
}
int main(int argc, char *argv[]) {
@@ -47,6 +70,9 @@ int main(int argc, char *argv[]) {
if (strcmp(argv[1], "load") == 0 && argc >= 3) {
// 加载 KPM 模块
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_LOAD), argv[2], (argc > 3 ? argv[3] : NULL), &out);
if(out > 0) {
printf("Success");
}
} else if (strcmp(argv[1], "unload") == 0 && argc >= 3) {
// 卸载 KPM 模块
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_UNLOAD), argv[2], NULL, &out);
@@ -59,22 +85,25 @@ int main(int argc, char *argv[]) {
// 获取模块列表
char buffer[1024] = {0};
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_LIST), buffer, sizeof(buffer), &out);
if (ret >= 0) {
if (out >= 0) {
printf("%s", buffer);
}
} else if (strcmp(argv[1], "info") == 0 && argc >= 3) {
// 获取指定模块信息
char buffer[256] = {0};
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_INFO), argv[2], buffer, &out);
if (ret >= 0) {
if (out >= 0) {
printf("%s\n", buffer);
}
} else if (strcmp(argv[1], "control") == 0 && argc >= 4) {
// 控制 KPM 模块
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_CONTROL), argv[2], argv[3], &out);
} else if (strcmp(argv[1], "print") == 0) {
// 在 stdout 输出 KPM 列表
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_PRINT), NULL, NULL, &out);
} else if (strcmp(argv[1], "version") == 0) {
char buffer[1024] = {0};
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_VERSION), buffer, sizeof(buffer), &out);
if (out >= 0) {
printf("%s", buffer);
}
} else {
print_usage(argv[0]);
return 1;

View File

@@ -784,7 +784,7 @@ dependencies = [
]
[[package]]
name = "zakomk"
name = "zakozako"
version = "0.1.0"
dependencies = [
"android-properties",

View File

@@ -1,11 +1,12 @@
[package]
name = "zakomk"
name = "zakozako"
version = "0.1.0"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
notify = "6.1"
anyhow = "1"
clap = { version = "4", features = ["derive"] }
const_format = "0.2"

View File

@@ -1,5 +1,10 @@
____ _ _ _ ____ _____ _____
/ ___|| |__(_) |__ | ___|| ____|_ _|
\___ \| '_ \ | '_ \|___ \| _| | |
___) | | | | | | | ___) | |___ | |
|____/|_| |_|_| |_| |____/|_____| |_|
____ _ _ ____ _ _
/ ___| _ _| | _(_) ___|| | | |
\___ \| | | | |/ / \___ \| | | |
___) | |_| | <| |___) | |_| |
|____/ \__,_|_|\_\_|____/ \___/
_ _ _ _
| | | | | |_ _ __ __ _
| | | | | __| '__/ _\ |
| |_| | | |_| | | (_| |
\___/|_|\__|_| \__,_|

View File

@@ -5,10 +5,13 @@ use anyhow::{Context, Result};
use log::{info, warn};
use rustix::fs::{MountFlags, mount};
use std::path::Path;
use crate::kpm;
pub fn on_post_data_fs() -> Result<()> {
ksucalls::report_post_fs_data();
kpm::start_kpm_watcher()?;
utils::umask(0);
#[cfg(unix)]
@@ -98,6 +101,9 @@ pub fn on_post_data_fs() -> Result<()> {
run_stage("post-mount", true);
// load kpm modules
kpm::load_kpm_modules()?;
Ok(())
}

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