1 Commits

Author SHA1 Message Date
Der_Googler
c5ed6e1e8c manager: Update WebUI X to the latest version (#345)
* manager: bump WebUI X

* manager: forgot to add interface name back

* manager: use compose set content for webuix

* manager: fix :jna library and reflections in prod
2025-08-21 07:21:43 +08:00
42 changed files with 831 additions and 1297 deletions

View File

@@ -24,12 +24,6 @@ Prerequisites: open source bootable kernel.
- Requires [`guide/how-to-integrate.md`](guide/how-to-integrate.md)
- Requires [https://github.com/~](https://github.com/tiann/KernelSU/blob/main/website/docs/guide/how-to-integrate-for-non-gki.md#manually-modify-the-kernel-source)
3. **Tracepoint Hook:**
- Hook method introduced since SukiSU commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124)
- Requires `CONFIG_KSU_TRACEPOINT_HOOK=y`
- Requires [`guide/tracepoint-hook.md`](tracepoint-hook.md)
<!-- This part refer to [rsuntk/KernelSU](https://github.com/rsuntk/KernelSU). -->
If you're able to build a bootable kernel, there are two ways to integrate KernelSU into the kernel source code:

View File

@@ -1,270 +0,0 @@
# Tracepoint Hook Integration
## Introduction
Since commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124), SukiSU has introduced Tracepoint Hook
This Hook theoretically has lower performance overhead compared to Kprobes Hook, but is inferior to Manual Hook / Syscall Hook
> [!NOTE]
> This tutorial references the syscall hook v1.4 version from [backslashxx/KernelSU#5](https://github.com/backslashxx/KernelSU/issues/5), as well as the original KernelSU's [Manual Hook](https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source)
## Guide
### execve Hook (`exec.c`)
Generally need to modify the `do_execve` and `compat_do_execve` methods in `fs/exec.c`
```patch
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -78,6 +78,10 @@
#include <trace/hooks/sched.h>
#endif
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
EXPORT_TRACEPOINT_SYMBOL_GPL(task_rename);
static int bprm_creds_from_file(struct linux_binprm *bprm);
@@ -2037,6 +2041,9 @@ static int do_execve(struct filename *filename,
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
@@ -2064,6 +2071,9 @@ static int compat_do_execve(struct filename *filename,
.is_compat = true,
.ptr.compat = __envp,
};
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_sucompat_hook((int *)AT_FDCWD, &filename, NULL, NULL, NULL); /* 32-bit su */
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
```
### faccessat Hook (`open.c`)
Generally need to modify the `do_faccessat` method in `/fs/open.c`
```patch
--- a/fs/open.c
+++ b/fs/open.c
@@ -37,6 +37,10 @@
#include "internal.h"
#include <trace/hooks/syscall_check.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry,
loff_t length, unsigned int time_attrs, struct file *filp)
{
@@ -468,6 +472,9 @@ static long do_faccessat(int dfd, const char __user *filename, int mode, int fla
SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
return do_faccessat(dfd, filename, mode, 0);
}
```
If there's no `do_faccessat` method, you can find the `faccessat` SYSCALL definition (for kernels earlier than 4.17)
```patch
--- a/fs/open.c
+++ b/fs/open.c
@@ -31,6 +31,9 @@
#include <linux/ima.h>
#include <linux/dnotify.h>
#include <linux/compat.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
#include "internal.h"
@@ -369,6 +372,9 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
int res;
unsigned int lookup_flags = LOOKUP_FOLLOW;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
return -EINVAL;
```
### sys_read Hook (`read_write.c`)
Need to modify the `sys_read` method in `fs/read_write.c` (4.19 and above)
```patch
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,10 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -630,6 +634,9 @@ ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
return ksys_read(fd, buf, count);
}
```
Or the `read` SYSCALL definition (4.14 and below)
```patch
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,11 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -575,6 +580,9 @@ SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
if (f.file) {
loff_t pos = file_pos_read(f.file);
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
ret = vfs_read(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
```
### fstatat Hook (`stat.c`)
Need to modify the `newfstatat` SYSCALL definition in `stat.c`
If 32-bit support is needed, also need to modify the `statat64` SYSCALL definition
```patch
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -24,6 +24,10 @@
#include "internal.h"
#include "mount.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
/**
* generic_fillattr - Fill in the basic attributes from the inode struct
* @mnt_userns: user namespace of the mount the inode was found from
@@ -408,6 +412,10 @@ SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag);
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
@@ -559,6 +567,10 @@ SYSCALL_DEFINE4(fstatat64, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag); /* 32-bit su support */
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
```
### input Hook (`input.c`, for entering KSU built-in security mode)
Need to modify the `input_event` method in `drivers/input/input.c`, not `input_handle_event`
```patch
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -26,6 +26,10 @@
#include "input-compat.h"
#include "input-poller.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../../drivers/kernelsu/ksu_trace.h>
+#endif
+
MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
MODULE_DESCRIPTION("Input core");
MODULE_LICENSE("GPL");
@@ -451,6 +455,10 @@ void input_event(struct input_dev *dev,
{
unsigned long flags;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_input_hook(&type, &code, &value);
+#endif
+
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
```
### devpts Hook (`pty.c`)
Need to modify the `pts_unix98_lookup` method in `drivers/tty/pty.c`
```patch
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -31,6 +31,10 @@
#include <linux/compat.h>
#include "tty.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../../drivers/kernelsu/ksu_trace.h>
+#endif
+
#undef TTY_DEBUG_HANGUP
#ifdef TTY_DEBUG_HANGUP
# define tty_debug_hangup(tty, f, args...) tty_debug(tty, f, ##args)
@@ -707,6 +711,10 @@ static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
{
struct tty_struct *tty;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_devpts_hook((struct inode *)file->f_path.dentry->d_inode);
+#endif
+
mutex_lock(&devpts_mutex);
tty = devpts_get_priv(file->f_path.dentry);
mutex_unlock(&devpts_mutex);
```

View File

@@ -21,15 +21,9 @@ SukiSU 可以集成到 GKI 和 non-GKI 内核中,并且已反向移植到 4.14
<!-- - backslashxx's syscall manual hook: https://github.com/backslashxx/KernelSU/issues/5 (v1.5 version is not available at the moment, if you want to use it, please use v1.4 version, or standard KernelSU hooks)-->
- 需要 `CONFIG_KSU_MANUAL_HOOK=y`
- 需要 [`guide/how-to-integrate.md`](how-to-integrate.md)
- 需要 [`guide/how-to-integrate.md`](guide/how-to-integrate.md)
- 需要 [https://github.com/~](https://github.com/tiann/KernelSU/blob/main/website/docs/guide/how-to-integrate-for-non-gki.md#manually-modify-the-kernel-source)
3. **Tracepoint Hook:**
- 自 SukiSU commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124) 引入的 hook 方法
- 需要 `CONFIG_KSU_TRACEPOINT_HOOK=y`
- 需要 [`guide/tracepoint-hook.md`](tracepoint-hook.md)
<!-- This part refer to [rsuntk/KernelSU](https://github.com/rsuntk/KernelSU). -->
如果您能够构建可启动内核,有两种方法可以将 KernelSU 集成到内核源代码中:

View File

@@ -1,270 +0,0 @@
# Tracepoint Hook 集成
## 介绍
自 commit [49b01aad](https://github.com/SukiSU-Ultra/SukiSU-Ultra/commit/49b01aad74bcca6dba5a8a2e053bb54b648eb124) 起SukiSU 引入了 Tracepoint Hook
该 Hook 理论上相比于 Kprobes Hook性能开销更小但次于 Manual Hook / Syscall Hook
> [!NOTE]
> 本教程参考了 [backslashxx/KernelSU#5](https://github.com/backslashxx/KernelSU/issues/5) 的 syscall hook v1.4 版本钩子,以及原版 KernelSU 的 [Manual Hook](https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source)
## Guide
### execve 钩子(`exec.c`
一般需要修改 `fs/exec.c``do_execve``compat_do_execve` 方法
```patch
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -78,6 +78,10 @@
#include <trace/hooks/sched.h>
#endif
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
EXPORT_TRACEPOINT_SYMBOL_GPL(task_rename);
static int bprm_creds_from_file(struct linux_binprm *bprm);
@@ -2037,6 +2041,9 @@ static int do_execve(struct filename *filename,
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
@@ -2064,6 +2071,9 @@ static int compat_do_execve(struct filename *filename,
.is_compat = true,
.ptr.compat = __envp,
};
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_execveat_sucompat_hook((int *)AT_FDCWD, &filename, NULL, NULL, NULL); /* 32-bit su */
+#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
```
### faccessat 钩子 (`open.c`)
一般需要修改 `/fs/open.c``do_faccessat` 方法
```patch
--- a/fs/open.c
+++ b/fs/open.c
@@ -37,6 +37,10 @@
#include "internal.h"
#include <trace/hooks/syscall_check.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry,
loff_t length, unsigned int time_attrs, struct file *filp)
{
@@ -468,6 +472,9 @@ static long do_faccessat(int dfd, const char __user *filename, int mode, int fla
SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
return do_faccessat(dfd, filename, mode, 0);
}
```
如果没有 `do_faccessat` 方法,可以找 `faccessat` 的 SYSCALL 定义(对于早于 4.17 的内核)
```patch
--- a/fs/open.c
+++ b/fs/open.c
@@ -31,6 +31,9 @@
#include <linux/ima.h>
#include <linux/dnotify.h>
#include <linux/compat.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
#include "internal.h"
@@ -369,6 +372,9 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
int res;
unsigned int lookup_flags = LOOKUP_FOLLOW;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_faccessat_hook(&dfd, &filename, &mode, NULL);
+#endif
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
return -EINVAL;
```
### sys_read 钩子 ( `read_write.c` )
需要修改 `fs/read_write.c``sys_read` 方法4.19 及以上)
```patch
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,10 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -630,6 +634,9 @@ ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
return ksys_read(fd, buf, count);
}
```
或者是 `read` 的 SYSCALL 定义4.14 及以下)
```patch
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -25,6 +25,11 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
+
const struct file_operations generic_ro_fops = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -575,6 +580,9 @@ SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
if (f.file) {
loff_t pos = file_pos_read(f.file);
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_sys_read_hook(fd, &buf, &count);
+#endif
ret = vfs_read(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
```
### fstatat 钩子 ( `stat.c` )
需要修改 `stat.c``newfstatat` SYSCALL 定义
如果需要 32 位支持,还需要修改 `statat64` SYSCALL 定义
```patch
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -24,6 +24,10 @@
#include "internal.h"
#include "mount.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../drivers/kernelsu/ksu_trace.h>
+#endif
+
/**
* generic_fillattr - Fill in the basic attributes from the inode struct
* @mnt_userns: user namespace of the mount the inode was found from
@@ -408,6 +412,10 @@ SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag);
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
@@ -559,6 +567,10 @@ SYSCALL_DEFINE4(fstatat64, int, dfd, const char __user *, filename,
struct kstat stat;
int error;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_stat_hook(&dfd, &filename, &flag); /* 32-bit su support */
+#endif
+
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
```
### input 钩子 (`input.c` ,用于进入KSU系的内置安全模式)
需要修改 `drivers/input/input.c``input_event` 方法,而不是 `input_handle_event`
```patch
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -26,6 +26,10 @@
#include "input-compat.h"
#include "input-poller.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../../drivers/kernelsu/ksu_trace.h>
+#endif
+
MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
MODULE_DESCRIPTION("Input core");
MODULE_LICENSE("GPL");
@@ -451,6 +455,10 @@ void input_event(struct input_dev *dev,
{
unsigned long flags;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_input_hook(&type, &code, &value);
+#endif
+
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
```
### devpts 钩子 (`pty.c`)
需要修改 `drivers/tty/pty.c``pts_unix98_lookup` 方法
```patch
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -31,6 +31,10 @@
#include <linux/compat.h>
#include "tty.h"
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+#include <../../drivers/kernelsu/ksu_trace.h>
+#endif
+
#undef TTY_DEBUG_HANGUP
#ifdef TTY_DEBUG_HANGUP
# define tty_debug_hangup(tty, f, args...) tty_debug(tty, f, ##args)
@@ -707,6 +711,10 @@ static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
{
struct tty_struct *tty;
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
+ trace_ksu_trace_devpts_hook((struct inode *)file->f_path.dentry->d_inode);
+#endif
+
mutex_lock(&devpts_mutex);
tty = devpts_get_priv(file->f_path.dentry);
mutex_unlock(&devpts_mutex);
```

View File

@@ -1,6 +1,6 @@
kernelsu-objs := ksu.o
kernelsu-objs += allowlist.o
kernelsu-objs += dynamic_manager.o
kernelsu-objs += dynamic_sign.o
kernelsu-objs += apk_sign.o
kernelsu-objs += sucompat.o
kernelsu-objs += throne_tracker.o
@@ -9,7 +9,7 @@ kernelsu-objs += ksud.o
kernelsu-objs += embed_ksud.o
kernelsu-objs += kernel_compat.o
ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y)
ifeq ($(strip $(CONFIG_KSU_TRACEPOINT_HOOK)),y)
kernelsu-objs += ksu_trace.o
endif
@@ -28,7 +28,7 @@ obj-$(CONFIG_KPM) += kpm/
REPO_OWNER := SukiSU-Ultra
REPO_NAME := SukiSU-Ultra
REPO_BRANCH := main
KSU_VERSION_API := 3.1.9
KSU_VERSION_API := 3.1.8
GIT_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git
CURL_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin curl

View File

@@ -15,7 +15,7 @@
#endif
#include "apk_sign.h"
#include "dynamic_manager.h"
#include "dynamic_sign.h"
#include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h"
#include "manager_sign.h"
@@ -109,7 +109,7 @@ static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, i
if (i == 1) { // Dynamic Sign indexing
unsigned int size;
const char *hash;
if (ksu_get_dynamic_manager_config(&size, &hash)) {
if (ksu_get_dynamic_sign_config(&size, &hash)) {
sign_key.size = size;
sign_key.sha256 = hash;
}
@@ -219,8 +219,8 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana
return false;
}
// If you want to check for multi-manager APK signing, but dynamic managering is not enabled, skip
if (check_multi_manager && !ksu_is_dynamic_manager_enabled()) {
// If you want to check for multi-manager APK signing, but dynamic signing is not enabled, skip
if (check_multi_manager && !ksu_is_dynamic_sign_enabled()) {
filp_close(fp, 0);
return 0;
}
@@ -328,7 +328,7 @@ clean:
if (check_multi_manager) {
// 0: ShirkNeko/SukiSU, 1: Dynamic Sign
if (matched_index == 0 || matched_index == 1) {
pr_info("Multi-manager APK detected (dynamic_manager enabled): signature_index=%d\n", matched_index);
pr_info("Multi-manager APK detected (dynamic_sign enabled): signature_index=%d\n", matched_index);
return true;
}
return false;
@@ -369,7 +369,7 @@ bool is_manager_apk(char *path)
return check_v2_signature(path, false, NULL);
}
bool ksu_is_dynamic_manager_apk(char *path, int *signature_index)
bool ksu_is_multi_manager_apk(char *path, int *signature_index)
{
return check_v2_signature(path, true, signature_index);
}

View File

@@ -45,7 +45,7 @@
#include "kernel_compat.h"
#include "kpm/kpm.h"
#include "dynamic_manager.h"
#include "dynamic_sign.h"
static bool ksu_module_mounted = false;
@@ -345,31 +345,31 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
return 0;
}
// Allow the root manager to configure dynamic manageratures
if (arg2 == CMD_DYNAMIC_MANAGER) {
// Allow the root manager to configure dynamic signatures
if (arg2 == CMD_DYNAMIC_SIGN) {
if (!from_root && !from_manager) {
return 0;
}
struct dynamic_manager_user_config config;
struct dynamic_sign_user_config config;
if (copy_from_user(&config, (void __user *)arg3, sizeof(config))) {
pr_err("copy dynamic manager config failed\n");
pr_err("copy dynamic sign config failed\n");
return 0;
}
int ret = ksu_handle_dynamic_manager(&config);
int ret = ksu_handle_dynamic_sign(&config);
if (ret == 0 && config.operation == DYNAMIC_MANAGER_OP_GET) {
if (ret == 0 && config.operation == DYNAMIC_SIGN_OP_GET) {
if (copy_to_user((void __user *)arg3, &config, sizeof(config))) {
pr_err("copy dynamic manager config back failed\n");
pr_err("copy dynamic sign config back failed\n");
return 0;
}
}
if (ret == 0) {
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
pr_err("dynamic_manager: prctl reply error\n");
pr_err("dynamic_sign: prctl reply error\n");
}
}
return 0;
@@ -408,7 +408,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
pr_info("post-fs-data triggered\n");
on_post_fs_data();
// Initializing Dynamic Signatures
ksu_dynamic_manager_init();
ksu_dynamic_sign_init();
pr_info("Dynamic sign config loaded during post-fs-data\n");
}
break;

View File

@@ -1,43 +0,0 @@
#ifndef __KSU_H_DYNAMIC_MANAGER
#define __KSU_H_DYNAMIC_MANAGER
#include <linux/types.h>
#include "ksu.h"
#define DYNAMIC_MANAGER_FILE_MAGIC 0x7f445347 // 'DSG', u32
#define DYNAMIC_MANAGER_FILE_VERSION 1 // u32
#define KERNEL_SU_DYNAMIC_MANAGER "/data/adb/ksu/.dynamic_manager"
struct dynamic_manager_config {
unsigned int size;
char hash[65];
int is_set;
};
struct manager_info {
uid_t uid;
int signature_index;
bool is_active;
};
// Dynamic sign operations
void ksu_dynamic_manager_init(void);
void ksu_dynamic_manager_exit(void);
int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config);
bool ksu_load_dynamic_manager(void);
bool ksu_is_dynamic_manager_enabled(void);
// Multi-manager operations
void ksu_add_manager(uid_t uid, int signature_index);
void ksu_remove_manager(uid_t uid);
bool ksu_is_any_manager(uid_t uid);
int ksu_get_manager_signature_index(uid_t uid);
int ksu_get_active_managers(struct manager_list_info *info);
// Multi-manager APK verification
bool ksu_is_dynamic_manager_apk(char *path, int *signature_index);
// Configuration access for signature verification
bool ksu_get_dynamic_manager_config(unsigned int *size, const char **hash);
#endif

View File

@@ -15,7 +15,7 @@
#include <crypto/sha.h>
#endif
#include "dynamic_manager.h"
#include "dynamic_sign.h"
#include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h"
#include "manager.h"
@@ -23,7 +23,7 @@
#define MAX_MANAGERS 2
// Dynamic sign configuration
static struct dynamic_manager_config dynamic_manager = {
static struct dynamic_sign_config dynamic_sign = {
.size = 0x300,
.hash = "0000000000000000000000000000000000000000000000000000000000000000",
.is_set = 0
@@ -32,21 +32,21 @@ static struct dynamic_manager_config dynamic_manager = {
// Multi-manager state
static struct manager_info active_managers[MAX_MANAGERS];
static DEFINE_SPINLOCK(managers_lock);
static DEFINE_SPINLOCK(dynamic_manager_lock);
static DEFINE_SPINLOCK(dynamic_sign_lock);
// Work queues for persistent storage
static struct work_struct ksu_save_dynamic_manager_work;
static struct work_struct ksu_load_dynamic_manager_work;
static struct work_struct ksu_clear_dynamic_manager_work;
static struct work_struct ksu_save_dynamic_sign_work;
static struct work_struct ksu_load_dynamic_sign_work;
static struct work_struct ksu_clear_dynamic_sign_work;
bool ksu_is_dynamic_manager_enabled(void)
bool ksu_is_dynamic_sign_enabled(void)
{
unsigned long flags;
bool enabled;
spin_lock_irqsave(&dynamic_manager_lock, flags);
enabled = dynamic_manager.is_set;
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
spin_lock_irqsave(&dynamic_sign_lock, flags);
enabled = dynamic_sign.is_set;
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
return enabled;
}
@@ -56,7 +56,7 @@ void ksu_add_manager(uid_t uid, int signature_index)
unsigned long flags;
int i;
if (!ksu_is_dynamic_manager_enabled()) {
if (!ksu_is_dynamic_sign_enabled()) {
pr_info("Dynamic sign not enabled, skipping multi-manager add\n");
return;
}
@@ -94,7 +94,7 @@ void ksu_remove_manager(uid_t uid)
unsigned long flags;
int i;
if (!ksu_is_dynamic_manager_enabled()) {
if (!ksu_is_dynamic_sign_enabled()) {
return;
}
@@ -117,7 +117,7 @@ bool ksu_is_any_manager(uid_t uid)
bool is_manager = false;
int i;
if (!ksu_is_dynamic_manager_enabled()) {
if (!ksu_is_dynamic_sign_enabled()) {
return false;
}
@@ -145,7 +145,7 @@ int ksu_get_manager_signature_index(uid_t uid)
return 1;
}
if (!ksu_is_dynamic_manager_enabled()) {
if (!ksu_is_dynamic_sign_enabled()) {
return -1;
}
@@ -197,7 +197,7 @@ int ksu_get_active_managers(struct manager_list_info *info)
}
// Add dynamic managers
if (ksu_is_dynamic_manager_enabled()) {
if (ksu_is_dynamic_sign_enabled()) {
spin_lock_irqsave(&managers_lock, flags);
for (i = 0; i < MAX_MANAGERS && count < 2; i++) {
@@ -215,42 +215,42 @@ int ksu_get_active_managers(struct manager_list_info *info)
return 0;
}
static void do_save_dynamic_manager(struct work_struct *work)
static void do_save_dynamic_sign(struct work_struct *work)
{
u32 magic = DYNAMIC_MANAGER_FILE_MAGIC;
u32 version = DYNAMIC_MANAGER_FILE_VERSION;
struct dynamic_manager_config config_to_save;
u32 magic = DYNAMIC_SIGN_FILE_MAGIC;
u32 version = DYNAMIC_SIGN_FILE_VERSION;
struct dynamic_sign_config config_to_save;
loff_t off = 0;
unsigned long flags;
struct file *fp;
spin_lock_irqsave(&dynamic_manager_lock, flags);
config_to_save = dynamic_manager;
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
spin_lock_irqsave(&dynamic_sign_lock, flags);
config_to_save = dynamic_sign;
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
if (!config_to_save.is_set) {
pr_info("Dynamic sign config not set, skipping save\n");
return;
}
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) {
pr_err("save_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
pr_err("save_dynamic_sign create file failed: %ld\n", PTR_ERR(fp));
return;
}
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
pr_err("save_dynamic_manager write magic failed.\n");
pr_err("save_dynamic_sign write magic failed.\n");
goto exit;
}
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
pr_err("save_dynamic_manager write version failed.\n");
pr_err("save_dynamic_sign write version failed.\n");
goto exit;
}
if (ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
pr_err("save_dynamic_manager write config failed.\n");
pr_err("save_dynamic_sign write config failed.\n");
goto exit;
}
@@ -260,48 +260,48 @@ exit:
filp_close(fp, 0);
}
static void do_load_dynamic_manager(struct work_struct *work)
static void do_load_dynamic_sign(struct work_struct *work)
{
loff_t off = 0;
ssize_t ret = 0;
struct file *fp = NULL;
u32 magic;
u32 version;
struct dynamic_manager_config loaded_config;
struct dynamic_sign_config loaded_config;
unsigned long flags;
int i;
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_RDONLY, 0);
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_RDONLY, 0);
if (IS_ERR(fp)) {
if (PTR_ERR(fp) == -ENOENT) {
pr_info("No saved dynamic manager config found\n");
pr_info("No saved dynamic sign config found\n");
} else {
pr_err("load_dynamic_manager open file failed: %ld\n", PTR_ERR(fp));
pr_err("load_dynamic_sign open file failed: %ld\n", PTR_ERR(fp));
}
return;
}
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
magic != DYNAMIC_MANAGER_FILE_MAGIC) {
pr_err("dynamic manager file invalid magic: %x!\n", magic);
magic != DYNAMIC_SIGN_FILE_MAGIC) {
pr_err("dynamic sign file invalid magic: %x!\n", magic);
goto exit;
}
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
pr_err("dynamic manager read version failed\n");
pr_err("dynamic sign read version failed\n");
goto exit;
}
pr_info("dynamic manager file version: %d\n", version);
pr_info("dynamic sign file version: %d\n", version);
ret = ksu_kernel_read_compat(fp, &loaded_config, sizeof(loaded_config), &off);
if (ret <= 0) {
pr_info("load_dynamic_manager read err: %zd\n", ret);
pr_info("load_dynamic_sign read err: %zd\n", ret);
goto exit;
}
if (ret != sizeof(loaded_config)) {
pr_err("load_dynamic_manager read incomplete config: %zd/%zu\n", ret, sizeof(loaded_config));
pr_err("load_dynamic_sign read incomplete config: %zd/%zu\n", ret, sizeof(loaded_config));
goto exit;
}
@@ -324,9 +324,9 @@ static void do_load_dynamic_manager(struct work_struct *work)
}
}
spin_lock_irqsave(&dynamic_manager_lock, flags);
dynamic_manager = loaded_config;
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
spin_lock_irqsave(&dynamic_sign_lock, flags);
dynamic_sign = loaded_config;
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
pr_info("Dynamic sign config loaded: size=0x%x, hash=%.16s...\n",
loaded_config.size, loaded_config.hash);
@@ -335,12 +335,12 @@ exit:
filp_close(fp, 0);
}
static bool persistent_dynamic_manager(void)
static bool persistent_dynamic_sign(void)
{
return ksu_queue_work(&ksu_save_dynamic_manager_work);
return ksu_queue_work(&ksu_save_dynamic_sign_work);
}
static void do_clear_dynamic_manager(struct work_struct *work)
static void do_clear_dynamic_sign(struct work_struct *work)
{
loff_t off = 0;
struct file *fp;
@@ -348,15 +348,15 @@ static void do_clear_dynamic_manager(struct work_struct *work)
memset(zero_buffer, 0, sizeof(zero_buffer));
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) {
pr_err("clear_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
pr_err("clear_dynamic_sign create file failed: %ld\n", PTR_ERR(fp));
return;
}
// Write null bytes to overwrite the file content
if (ksu_kernel_write_compat(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) {
pr_err("clear_dynamic_manager write null bytes failed.\n");
pr_err("clear_dynamic_sign write null bytes failed.\n");
} else {
pr_info("Dynamic sign config file cleared successfully\n");
}
@@ -364,12 +364,12 @@ static void do_clear_dynamic_manager(struct work_struct *work)
filp_close(fp, 0);
}
static bool clear_dynamic_manager_file(void)
static bool clear_dynamic_sign_file(void)
{
return ksu_queue_work(&ksu_clear_dynamic_manager_work);
return ksu_queue_work(&ksu_clear_dynamic_sign_work);
}
int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config)
{
unsigned long flags;
int ret = 0;
@@ -380,7 +380,7 @@ int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
}
switch (config->operation) {
case DYNAMIC_MANAGER_OP_SET:
case DYNAMIC_SIGN_OP_SET:
if (config->size < 0x100 || config->size > 0x1000) {
pr_err("invalid size: 0x%x\n", config->size);
return -EINVAL;
@@ -400,106 +400,106 @@ int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
}
}
spin_lock_irqsave(&dynamic_manager_lock, flags);
dynamic_manager.size = config->size;
spin_lock_irqsave(&dynamic_sign_lock, flags);
dynamic_sign.size = config->size;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(dynamic_manager.hash, config->hash, sizeof(dynamic_manager.hash));
strscpy(dynamic_sign.hash, config->hash, sizeof(dynamic_sign.hash));
#else
strlcpy(dynamic_manager.hash, config->hash, sizeof(dynamic_manager.hash));
strlcpy(dynamic_sign.hash, config->hash, sizeof(dynamic_sign.hash));
#endif
dynamic_manager.is_set = 1;
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
dynamic_sign.is_set = 1;
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
persistent_dynamic_manager();
pr_info("dynamic manager updated: size=0x%x, hash=%.16s... (multi-manager enabled)\n",
persistent_dynamic_sign();
pr_info("dynamic sign updated: size=0x%x, hash=%.16s... (multi-manager enabled)\n",
config->size, config->hash);
break;
case DYNAMIC_MANAGER_OP_GET:
spin_lock_irqsave(&dynamic_manager_lock, flags);
if (dynamic_manager.is_set) {
config->size = dynamic_manager.size;
case DYNAMIC_SIGN_OP_GET:
spin_lock_irqsave(&dynamic_sign_lock, flags);
if (dynamic_sign.is_set) {
config->size = dynamic_sign.size;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(config->hash, dynamic_manager.hash, sizeof(config->hash));
strscpy(config->hash, dynamic_sign.hash, sizeof(config->hash));
#else
strlcpy(config->hash, dynamic_manager.hash, sizeof(config->hash));
strlcpy(config->hash, dynamic_sign.hash, sizeof(config->hash));
#endif
ret = 0;
} else {
ret = -ENODATA;
}
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
break;
case DYNAMIC_MANAGER_OP_CLEAR:
spin_lock_irqsave(&dynamic_manager_lock, flags);
dynamic_manager.size = 0x300;
strcpy(dynamic_manager.hash, "0000000000000000000000000000000000000000000000000000000000000000");
dynamic_manager.is_set = 0;
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
case DYNAMIC_SIGN_OP_CLEAR:
spin_lock_irqsave(&dynamic_sign_lock, flags);
dynamic_sign.size = 0x300;
strcpy(dynamic_sign.hash, "0000000000000000000000000000000000000000000000000000000000000000");
dynamic_sign.is_set = 0;
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
// Clear only dynamic managers, preserve default manager
clear_dynamic_manager();
// Clear file using the same method as save
clear_dynamic_manager_file();
clear_dynamic_sign_file();
pr_info("Dynamic sign config cleared (multi-manager disabled)\n");
break;
default:
pr_err("Invalid dynamic manager operation: %d\n", config->operation);
pr_err("Invalid dynamic sign operation: %d\n", config->operation);
return -EINVAL;
}
return ret;
}
bool ksu_load_dynamic_manager(void)
bool ksu_load_dynamic_sign(void)
{
return ksu_queue_work(&ksu_load_dynamic_manager_work);
return ksu_queue_work(&ksu_load_dynamic_sign_work);
}
void ksu_dynamic_manager_init(void)
void ksu_dynamic_sign_init(void)
{
int i;
INIT_WORK(&ksu_save_dynamic_manager_work, do_save_dynamic_manager);
INIT_WORK(&ksu_load_dynamic_manager_work, do_load_dynamic_manager);
INIT_WORK(&ksu_clear_dynamic_manager_work, do_clear_dynamic_manager);
INIT_WORK(&ksu_save_dynamic_sign_work, do_save_dynamic_sign);
INIT_WORK(&ksu_load_dynamic_sign_work, do_load_dynamic_sign);
INIT_WORK(&ksu_clear_dynamic_sign_work, do_clear_dynamic_sign);
// Initialize manager slots
for (i = 0; i < MAX_MANAGERS; i++) {
active_managers[i].is_active = false;
}
ksu_load_dynamic_manager();
ksu_load_dynamic_sign();
pr_info("Dynamic sign initialized with conditional multi-manager support\n");
}
void ksu_dynamic_manager_exit(void)
void ksu_dynamic_sign_exit(void)
{
clear_dynamic_manager();
// Save current config before exit
do_save_dynamic_manager(NULL);
do_save_dynamic_sign(NULL);
pr_info("Dynamic sign exited with persistent storage\n");
}
// Get dynamic manager configuration for signature verification
bool ksu_get_dynamic_manager_config(unsigned int *size, const char **hash)
// Get dynamic sign configuration for signature verification
bool ksu_get_dynamic_sign_config(unsigned int *size, const char **hash)
{
unsigned long flags;
bool valid = false;
spin_lock_irqsave(&dynamic_manager_lock, flags);
if (dynamic_manager.is_set) {
if (size) *size = dynamic_manager.size;
if (hash) *hash = dynamic_manager.hash;
spin_lock_irqsave(&dynamic_sign_lock, flags);
if (dynamic_sign.is_set) {
if (size) *size = dynamic_sign.size;
if (hash) *hash = dynamic_sign.hash;
valid = true;
}
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
return valid;
}

43
kernel/dynamic_sign.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef __KSU_H_DYNAMIC_SIGN
#define __KSU_H_DYNAMIC_SIGN
#include <linux/types.h>
#include "ksu.h"
#define DYNAMIC_SIGN_FILE_MAGIC 0x7f445347 // 'DSG', u32
#define DYNAMIC_SIGN_FILE_VERSION 1 // u32
#define KERNEL_SU_DYNAMIC_SIGN "/data/adb/ksu/.dynamic_sign"
struct dynamic_sign_config {
unsigned int size;
char hash[65];
int is_set;
};
struct manager_info {
uid_t uid;
int signature_index;
bool is_active;
};
// Dynamic sign operations
int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config);
void ksu_dynamic_sign_init(void);
void ksu_dynamic_sign_exit(void);
bool ksu_load_dynamic_sign(void);
bool ksu_is_dynamic_sign_enabled(void);
// Multi-manager operations
void ksu_add_manager(uid_t uid, int signature_index);
void ksu_remove_manager(uid_t uid);
bool ksu_is_any_manager(uid_t uid);
int ksu_get_manager_signature_index(uid_t uid);
int ksu_get_active_managers(struct manager_list_info *info);
// Multi-manager APK verification
bool ksu_is_multi_manager_apk(char *path, int *signature_index);
// Configuration access for signature verification
bool ksu_get_dynamic_sign_config(unsigned int *size, const char **hash);
#endif

View File

@@ -28,7 +28,7 @@
#define CMD_ENABLE_KPM 100
#define CMD_HOOK_TYPE 101
#define CMD_DYNAMIC_MANAGER 103
#define CMD_DYNAMIC_SIGN 103
#define CMD_GET_MANAGERS 104
#define EVENT_POST_FS_DATA 1
@@ -47,11 +47,11 @@
#endif
#define KSU_FULL_VERSION_STRING 255
#define DYNAMIC_MANAGER_OP_SET 0
#define DYNAMIC_MANAGER_OP_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2
#define DYNAMIC_SIGN_OP_SET 0
#define DYNAMIC_SIGN_OP_GET 1
#define DYNAMIC_SIGN_OP_CLEAR 2
struct dynamic_manager_user_config {
struct dynamic_sign_user_config {
unsigned int operation;
unsigned int size;
char hash[65];

View File

@@ -12,7 +12,7 @@
#include "manager.h"
#include "throne_tracker.h"
#include "kernel_compat.h"
#include "dynamic_manager.h"
#include "dynamic_sign.h"
uid_t ksu_manager_uid = KSU_INVALID_UID;
@@ -196,7 +196,7 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
}
int signature_index = -1;
bool is_multi_manager = ksu_is_dynamic_manager_apk(dirpath, &signature_index);
bool is_multi_manager = ksu_is_multi_manager_apk(dirpath, &signature_index);
pr_info("Found new base.apk at path: %s, is_multi_manager: %d, signature_index: %d\n",
dirpath, is_multi_manager, signature_index);
@@ -401,7 +401,7 @@ void track_throne()
}
// Check for dynamic managers
if (!dynamic_manager_exist && ksu_is_dynamic_manager_enabled()) {
if (!dynamic_manager_exist && ksu_is_dynamic_sign_enabled()) {
list_for_each_entry (np, &uid_list, list) {
if (ksu_is_any_manager(np->uid)) {
dynamic_manager_exist = true;
@@ -419,8 +419,8 @@ void track_throne()
pr_info("Searching manager...\n");
search_manager("/data/app", 2, &uid_list);
pr_info("Search manager finished\n");
} else if (!dynamic_manager_exist && ksu_is_dynamic_manager_enabled()) {
// Always perform search when called from dynamic manager rescan
} else if (!dynamic_manager_exist && ksu_is_dynamic_sign_enabled()) {
// Always perform search when called from dynamic sign rescan
pr_info("Dynamic sign enabled, Searching manager...\n");
search_manager("/data/app", 2, &uid_list);
pr_info("Search Dynamic sign manager finished\n");

View File

@@ -41,7 +41,10 @@ android {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
/**debug {
signingConfig = signingConfigs.named("Debug").get() as ApkSigningConfig
@@ -131,6 +134,8 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.compose.destinations.core)
ksp(libs.compose.destinations.ksp)
@@ -159,7 +164,16 @@ dependencies {
implementation(libs.mmrl.platform)
compileOnly(libs.mmrl.hidden.api)
implementation(libs.mmrl.webui)
/**
* Compile only `Java-Native-Access` since plugins are disabled in both WebUI X and KSU WebUI
* Avoid using:
* - fun WXInterface.registerLibrary(clazz: Class<*>, name: String): Unit
* - fun WXInterface.unregisterLibrary(clazz: Class<*>): Unit
* - fun WXInterface.isLibraryRegistered(libName: String): Boolean
*/
compileOnly(libs.mmrl.webui.jna)
implementation(libs.mmrl.webui.portable)
implementation(libs.mmrl.ui)
implementation(libs.accompanist.drawablepainter)

View File

@@ -4,43 +4,14 @@
-dontwarn org.conscrypt.**
-dontwarn kotlinx.serialization.**
# Please add these rules to your existing keep rules in order to suppress warnings.
# This is generated automatically by the Android Gradle plugin.
-dontwarn com.google.auto.service.AutoService
-dontwarn com.google.j2objc.annotations.RetainedWith
-dontwarn javax.lang.model.SourceVersion
-dontwarn javax.lang.model.element.AnnotationMirror
-dontwarn javax.lang.model.element.AnnotationValue
-dontwarn javax.lang.model.element.Element
-dontwarn javax.lang.model.element.ElementKind
-dontwarn javax.lang.model.element.ElementVisitor
-dontwarn javax.lang.model.element.ExecutableElement
-dontwarn javax.lang.model.element.Modifier
-dontwarn javax.lang.model.element.Name
-dontwarn javax.lang.model.element.PackageElement
-dontwarn javax.lang.model.element.TypeElement
-dontwarn javax.lang.model.element.TypeParameterElement
-dontwarn javax.lang.model.element.VariableElement
-dontwarn javax.lang.model.type.ArrayType
-dontwarn javax.lang.model.type.DeclaredType
-dontwarn javax.lang.model.type.ExecutableType
-dontwarn javax.lang.model.type.TypeKind
-dontwarn javax.lang.model.type.TypeMirror
-dontwarn javax.lang.model.type.TypeVariable
-dontwarn javax.lang.model.type.TypeVisitor
-dontwarn javax.lang.model.util.AbstractAnnotationValueVisitor8
-dontwarn javax.lang.model.util.AbstractTypeVisitor8
-dontwarn javax.lang.model.util.ElementFilter
-dontwarn javax.lang.model.util.Elements
-dontwarn javax.lang.model.util.SimpleElementVisitor8
-dontwarn javax.lang.model.util.SimpleTypeVisitor7
-dontwarn javax.lang.model.util.SimpleTypeVisitor8
-dontwarn javax.lang.model.util.Types
-dontwarn javax.tools.Diagnostic$Kind
# MMRL:webui reflection
-keep class com.dergoogler.mmrl.webui.model.ModId { *; }
-keep class androidx.compose.ui.graphics.Color { *; }
-keep class androidx.compose.material3.ButtonColors { *; }
-keep class androidx.compose.material3.CardColors { *; }
-keep class androidx.compose.material3.ColorScheme { *; }
-keep class com.dergoogler.mmrl.platform.model.ModId { *; }
-keep class com.dergoogler.mmrl.webui.interfaces.WXOptions { *; }
-keep class com.dergoogler.mmrl.webui.interfaces.WXInterface { *; }
-keep class com.dergoogler.mmrl.webui.interfaces.** { *; }
-keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; }

View File

@@ -341,43 +341,43 @@ NativeBridgeNP(getSusfsFeatureStatus, jobject) {
return obj;
}
// dynamic manager
NativeBridge(setDynamicManager, jboolean, jint size, jstring hash) {
// dynamic sign
NativeBridge(setDynamicSign, jboolean, jint size, jstring hash) {
if (!hash) {
LogDebug("setDynamicManager: hash is null");
LogDebug("setDynamicSign: hash is null");
return false;
}
const char* chash = GetEnvironment()->GetStringUTFChars(env, hash, nullptr);
bool result = set_dynamic_manager((unsigned int)size, chash);
bool result = set_dynamic_sign((unsigned int)size, chash);
GetEnvironment()->ReleaseStringUTFChars(env, hash, chash);
LogDebug("setDynamicManager: size=0x%x, result=%d", size, result);
LogDebug("setDynamicSign: size=0x%x, result=%d", size, result);
return result;
}
NativeBridgeNP(getDynamicManager, jobject) {
struct dynamic_manager_user_config config;
bool result = get_dynamic_manager(&config);
NativeBridgeNP(getDynamicSign, jobject) {
struct dynamic_sign_user_config config;
bool result = get_dynamic_sign(&config);
if (!result) {
LogDebug("getDynamicManager: failed to get dynamic manager config");
LogDebug("getDynamicSign: failed to get dynamic sign config");
return NULL;
}
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicManagerConfig");
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicManagerConfig");
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicSignConfig");
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicSignConfig");
SET_INT_FIELD(obj, cls, size, (jint)config.size);
SET_STRING_FIELD(obj, cls, hash, config.hash);
LogDebug("getDynamicManager: size=0x%x, hash=%.16s...", config.size, config.hash);
LogDebug("getDynamicSign: size=0x%x, hash=%.16s...", config.size, config.hash);
return obj;
}
NativeBridgeNP(clearDynamicManager, jboolean) {
bool result = clear_dynamic_manager();
LogDebug("clearDynamicManager: result=%d", result);
NativeBridgeNP(clearDynamicSign, jboolean) {
bool result = clear_dynamic_sign();
LogDebug("clearDynamicSign: result=%d", result);
return result;
}

View File

@@ -46,12 +46,12 @@ extern const char* zako_file_verrcidx2str(uint8_t index);
#define CMD_ENABLE_KPM 100
#define CMD_HOOK_TYPE 101
#define CMD_GET_SUSFS_FEATURE_STATUS 102
#define CMD_DYNAMIC_MANAGER 103
#define CMD_DYNAMIC_SIGN 103
#define CMD_GET_MANAGERS 104
#define DYNAMIC_MANAGER_OP_SET 0
#define DYNAMIC_MANAGER_OP_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2
#define DYNAMIC_SIGN_OP_SET 0
#define DYNAMIC_SIGN_OP_GET 1
#define DYNAMIC_SIGN_OP_CLEAR 2
static bool ksuctl(int cmd, void* arg1, void* arg2) {
int32_t result = 0;
@@ -157,33 +157,33 @@ bool get_susfs_feature_status(struct susfs_feature_status* status) {
return ksuctl(CMD_GET_SUSFS_FEATURE_STATUS, status, NULL);
}
bool set_dynamic_manager(unsigned int size, const char* hash) {
bool set_dynamic_sign(unsigned int size, const char* hash) {
if (hash == NULL) {
return false;
}
struct dynamic_manager_user_config config;
config.operation = DYNAMIC_MANAGER_OP_SET;
struct dynamic_sign_user_config config;
config.operation = DYNAMIC_SIGN_OP_SET;
config.size = size;
strncpy(config.hash, hash, sizeof(config.hash) - 1);
config.hash[sizeof(config.hash) - 1] = '\0';
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
return ksuctl(CMD_DYNAMIC_SIGN, &config, NULL);
}
bool get_dynamic_manager(struct dynamic_manager_user_config* config) {
bool get_dynamic_sign(struct dynamic_sign_user_config* config) {
if (config == NULL) {
return false;
}
config->operation = DYNAMIC_MANAGER_OP_GET;
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL);
config->operation = DYNAMIC_SIGN_OP_GET;
return ksuctl(CMD_DYNAMIC_SIGN, config, NULL);
}
bool clear_dynamic_manager() {
struct dynamic_manager_user_config config;
config.operation = DYNAMIC_MANAGER_OP_CLEAR;
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
bool clear_dynamic_sign() {
struct dynamic_sign_user_config config;
config.operation = DYNAMIC_SIGN_OP_CLEAR;
return ksuctl(CMD_DYNAMIC_SIGN, &config, NULL);
}
bool get_managers_list(struct manager_list_info* info) {

View File

@@ -29,11 +29,11 @@ bool is_lkm_mode();
#define KSU_MAX_GROUPS 32
#define KSU_SELINUX_DOMAIN 64
#define DYNAMIC_MANAGER_OP_SET 0
#define DYNAMIC_MANAGER_OP_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2
#define DYNAMIC_SIGN_OP_SET 0
#define DYNAMIC_SIGN_OP_GET 1
#define DYNAMIC_SIGN_OP_CLEAR 2
struct dynamic_manager_user_config {
struct dynamic_sign_user_config {
unsigned int operation;
unsigned int size;
char hash[65];
@@ -128,11 +128,11 @@ bool get_hook_type(char* hook_type, size_t size);
bool get_susfs_feature_status(struct susfs_feature_status* status);
bool set_dynamic_manager(unsigned int size, const char* hash);
bool set_dynamic_sign(unsigned int size, const char* hash);
bool get_dynamic_manager(struct dynamic_manager_user_config* config);
bool get_dynamic_sign(struct dynamic_sign_user_config* config);
bool clear_dynamic_manager();
bool clear_dynamic_sign();
bool get_managers_list(struct manager_list_info* info);

View File

@@ -11,7 +11,7 @@ import android.os.Build
import android.os.Bundle
import coil.Coil
import coil.ImageLoader
import com.dergoogler.mmrl.platform.Platform
import com.dergoogler.mmrl.platform.PlatformManager
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import java.io.File
@@ -90,7 +90,7 @@ class KernelSUApplication : Application() {
// 注册Activity生命周期回调
registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
Platform.setHiddenApiExemptions()
PlatformManager.setHiddenApiExemptions()
val context = this
val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size)

View File

@@ -29,7 +29,7 @@ object Natives {
const val MINIMAL_SUPPORTED_KPM = 12800
const val MINIMAL_SUPPORTED_DYNAMIC_MANAGER = 13215
const val MINIMAL_SUPPORTED_DYNAMIC_SIGN = 13215
const val ROOT_UID = 0
const val ROOT_GID = 0
@@ -107,28 +107,28 @@ object Natives {
external fun getSusfsFeatureStatus(): SusfsFeatureStatus?
/**
* Set dynamic managerature configuration
* Set dynamic signature configuration
* @param size APK signature size
* @param hash APK signature hash (64 character hex string)
* @return true if successful, false otherwise
*/
external fun setDynamicManager(size: Int, hash: String): Boolean
external fun setDynamicSign(size: Int, hash: String): Boolean
/**
* Get current dynamic managerature configuration
* @return DynamicManagerConfig object containing current configuration, or null if not set
* Get current dynamic signature configuration
* @return DynamicSignConfig object containing current configuration, or null if not set
*/
external fun getDynamicManager(): DynamicManagerConfig?
external fun getDynamicSign(): DynamicSignConfig?
/**
* Clear dynamic managerature configuration
* Clear dynamic signature configuration
* @return true if successful, false otherwise
*/
external fun clearDynamicManager(): Boolean
external fun clearDynamicSign(): Boolean
/**
* Get active managers list when dynamic manager is enabled
* Get active managers list when dynamic sign is enabled
* @return ManagersList object containing active managers, or null if failed or not enabled
*/
external fun getManagersList(): ManagersList?
@@ -185,7 +185,7 @@ object Natives {
@Immutable
@Parcelize
@Keep
data class DynamicManagerConfig(
data class DynamicSignConfig(
val size: Int = 0,
val hash: String = ""
) : Parcelable {

View File

@@ -747,7 +747,7 @@ private fun InfoCard(
append(
when (signatureIndex) {
0 -> "(${stringResource(R.string.default_signature)})"
1 -> "(${stringResource(R.string.dynamic_managerature)})"
1 -> "(${stringResource(R.string.dynamic_signature)})"
else -> if (signatureIndex >= 2) "(${
stringResource(
R.string.signature_index,

View File

@@ -84,8 +84,8 @@ import java.util.concurrent.TimeUnit
import androidx.core.content.edit
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.webui.WebUIXActivity
import com.dergoogler.mmrl.platform.Platform
import androidx.core.net.toUri
import com.dergoogler.mmrl.platform.PlatformManager
import com.dergoogler.mmrl.platform.model.ModuleConfig
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
import com.sukisu.ultra.ui.component.AnimatedFab
@@ -460,7 +460,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
"wx" -> wxEngine
"ksu" -> ksuEngine
else -> {
if (Platform.isAlive) {
if (PlatformManager.isAlive) {
wxEngine
} else {
ksuEngine

View File

@@ -2,6 +2,7 @@ package com.sukisu.ultra.ui.theme
import android.content.ContentResolver
import android.content.Context
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.util.Log
@@ -48,8 +49,10 @@ import androidx.activity.SystemBarStyle
import androidx.activity.ComponentActivity
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalConfiguration
/**
* 主题配置对象,管理应用的主题相关状态
@@ -84,13 +87,13 @@ object ThemeConfig {
*/
@Composable
fun KernelSUTheme(
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
darkTheme: Boolean = when (ThemeConfig.forceDarkMode) {
true -> true
false -> false
null -> isSystemInDarkTheme()
},
dynamicColor: Boolean = ThemeConfig.useDynamicColor,
content: @Composable () -> Unit
content: @Composable () -> Unit,
) {
val context = LocalContext.current
val systemIsDark = isSystemInDarkTheme()
@@ -135,18 +138,17 @@ fun KernelSUTheme(
ThemeConfig.backgroundImageLoaded = false
}
ThemeConfig.preventBackgroundRefresh = context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
ThemeConfig.preventBackgroundRefresh =
context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getBoolean("prevent_background_refresh", true)
}
// 创建颜色方案
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) createDynamicDarkColorScheme(context) else createDynamicLightColorScheme(context)
}
darkTheme -> createDarkColorScheme()
else -> createLightColorScheme()
}
val colorScheme = createColorScheme(
context = context,
darkTheme = darkTheme,
dynamicColor = dynamicColor
)
// 根据暗色模式和自定义背景调整卡片配置
val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null
@@ -217,8 +219,18 @@ fun KernelSUTheme(
modifier = Modifier
.fillMaxSize()
.zIndex(-2f)
.background(if (darkTheme) if (CardConfig.isCustomBackgroundEnabled) { colorScheme.surfaceContainerLow } else { colorScheme.background }
else if (CardConfig.isCustomBackgroundEnabled) { colorScheme.surfaceContainerLow } else { colorScheme.background })
.background(
if (darkTheme) if (CardConfig.isCustomBackgroundEnabled) {
colorScheme.surfaceContainerLow
} else {
colorScheme.background
}
else if (CardConfig.isCustomBackgroundEnabled) {
colorScheme.surfaceContainerLow
} else {
colorScheme.background
}
)
)
// 自定义背景层
@@ -239,7 +251,9 @@ fun KernelSUTheme(
contentScale = ContentScale.Crop
)
.graphicsLayer {
alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f
alpha =
(painter.state as? AsyncImagePainter.State.Success)?.let { 1f }
?: 0f
}
)
}
@@ -288,7 +302,6 @@ fun KernelSUTheme(
* 创建动态深色颜色方案
*/
@RequiresApi(Build.VERSION_CODES.S)
@Composable
private fun createDynamicDarkColorScheme(context: Context): ColorScheme {
val scheme = dynamicDarkColorScheme(context)
return scheme.copy(
@@ -303,7 +316,6 @@ private fun createDynamicDarkColorScheme(context: Context): ColorScheme {
* 创建动态浅色颜色方案
*/
@RequiresApi(Build.VERSION_CODES.S)
@Composable
private fun createDynamicLightColorScheme(context: Context): ColorScheme {
val scheme = dynamicLightColorScheme(context)
return scheme.copy(
@@ -314,12 +326,35 @@ private fun createDynamicLightColorScheme(context: Context): ColorScheme {
)
}
internal fun createColorScheme(
context: Context,
darkTheme: Boolean = when (ThemeConfig.forceDarkMode) {
true -> true
false -> false
null -> _isSystemInDarkTheme(context)
},
dynamicColor: Boolean = ThemeConfig.useDynamicColor,
) = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) createDynamicDarkColorScheme(context) else createDynamicLightColorScheme(
context
)
}
darkTheme -> createDarkColorScheme()
else -> createLightColorScheme()
}
@Suppress("FunctionName")
internal fun _isSystemInDarkTheme(context: Context): Boolean {
val configuration = context.resources.configuration
val uiMode = configuration.uiMode
return (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
}
/**
* 创建深色颜色方案
*/
@Composable
private fun createDarkColorScheme() = darkColorScheme(
primary = ThemeConfig.currentTheme.primaryDark,
onPrimary = ThemeConfig.currentTheme.onPrimaryDark,
@@ -361,7 +396,6 @@ private fun createDarkColorScheme() = darkColorScheme(
/**
* 创建浅色颜色方案
*/
@Composable
private fun createLightColorScheme() = lightColorScheme(
primary = ThemeConfig.currentTheme.primaryLight,
onPrimary = ThemeConfig.currentTheme.onPrimaryLight,
@@ -440,7 +474,10 @@ private fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
/**
* 保存并应用自定义背景
*/
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
fun Context.saveAndApplyCustomBackground(
uri: Uri,
transformation: BackgroundTransformation? = null,
) {
val finalUri = if (transformation != null) {
saveTransformedBackground(uri, transformation)
} else {
@@ -536,7 +573,7 @@ fun Context.loadThemeMode() {
val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("theme_mode", "system")
ThemeConfig.forceDarkMode = when(mode) {
ThemeConfig.forceDarkMode = when (mode) {
"dark" -> true
"light" -> false
else -> null

View File

@@ -7,7 +7,7 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.util.Log
import android.widget.Toast
import com.dergoogler.mmrl.platform.Platform.Companion.context
import com.dergoogler.mmrl.platform.PlatformManager.context
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.topjohnwu.superuser.Shell

View File

@@ -10,7 +10,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dergoogler.mmrl.platform.Platform.Companion.context
import com.dergoogler.mmrl.platform.PlatformManager.context
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.sukisu.ultra.KernelVersion
@@ -431,18 +431,18 @@ class HomeViewModel : ViewModel() {
}
}
// 获取动态管理器状态和管理器列表
// 获取动态签名状态和管理器列表
val dynamicSignConfig = try {
Natives.getDynamicManager()
Natives.getDynamicSign()
} catch (e: Exception) {
Log.w(TAG, "Failed to get dynamic manager config", e)
Log.w(TAG, "Failed to get dynamic sign config", e)
null
}
val isDynamicSignEnabled = try {
dynamicSignConfig?.isValid() == true
} catch (e: Exception) {
Log.w(TAG, "Failed to check dynamic manager validity", e)
Log.w(TAG, "Failed to check dynamic sign validity", e)
false
}

View File

@@ -1,60 +1,57 @@
package com.sukisu.ultra.ui.webui
import android.content.Context
import android.content.ServiceConnection
import android.util.Log
import android.content.pm.PackageInfo
import com.dergoogler.mmrl.platform.Platform
import com.dergoogler.mmrl.platform.Platform.Companion.createPlatformIntent
import com.dergoogler.mmrl.platform.PlatformManager
import com.dergoogler.mmrl.platform.PlatformManager.packageManager
import com.dergoogler.mmrl.platform.PlatformManager.userManager
import com.dergoogler.mmrl.platform.model.IProvider
import com.dergoogler.mmrl.platform.model.PlatformIntent
import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.Natives
import com.topjohnwu.superuser.ipc.RootService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
class KsuLibSuProvider : IProvider {
override val name = "KsuLibSu"
class KsuLibSuProvider(
private val context: Context,
) : IProvider {
override val name = "SukiLibSu"
override fun isAvailable() = true
override suspend fun isAuthorized() = Natives.becomeManager(ksuApp.packageName)
override suspend fun isAuthorized() = Natives.becomeManager(context.packageName)
private val serviceIntent
get() = PlatformIntent(
ksuApp,
Platform.KsuNext,
SuService::class.java
)
private val intent by lazy {
context.createPlatformIntent<SuService>(Platform.SukiSU)
}
override fun bind(connection: ServiceConnection) {
RootService.bind(serviceIntent.intent, connection)
RootService.bind(intent, connection)
}
override fun unbind(connection: ServiceConnection) {
RootService.stop(serviceIntent.intent)
RootService.stop(intent)
}
}
// webui x
suspend fun initPlatform() = withContext(Dispatchers.IO) {
suspend fun CoroutineScope.initPlatform(context: Context = ksuApp): Deferred<Boolean> =
try {
val active = Platform.init {
this.context = ksuApp
this.platform = Platform.KsuNext
this.provider = from(KsuLibSuProvider())
val active = PlatformManager.init(this) {
from(KsuLibSuProvider(context))
}
while (!active) {
delay(1000)
}
return@withContext active
active
} catch (e: Exception) {
Log.e("KsuLibSu", "Failed to initialize platform", e)
return@withContext false
CompletableDeferred(false)
}
}
fun Platform.Companion.getInstalledPackagesAll(catch: (Exception) -> Unit = {}): List<PackageInfo> =
try {

View File

@@ -2,13 +2,13 @@ package com.sukisu.ultra.ui.webui
import android.content.Intent
import android.os.IBinder
import com.dergoogler.mmrl.platform.model.PlatformIntent.Companion.getPlatform
import com.dergoogler.mmrl.platform.Platform.Companion.getPlatform
import com.dergoogler.mmrl.platform.service.ServiceManager
import com.topjohnwu.superuser.ipc.RootService
class SuService : RootService() {
override fun onBind(intent: Intent): IBinder {
val mode = intent.getPlatform()
val mode = intent.getPlatform() ?: throw Exception("Platform not found")
return ServiceManager(mode)
}
}

View File

@@ -1,10 +1,10 @@
package com.sukisu.ultra.ui.webui
import android.annotation.SuppressLint
import android.app.ActivityManager
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
@@ -14,21 +14,30 @@ import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.webkit.WebViewAssetLoader
import com.dergoogler.mmrl.platform.model.ModId
import com.dergoogler.mmrl.platform.model.ModId.Companion.getModId
import com.dergoogler.mmrl.platform.model.ModId.Companion.webrootDir
import com.dergoogler.mmrl.ui.component.dialog.ConfirmData
import com.dergoogler.mmrl.ui.component.dialog.confirm
import com.dergoogler.mmrl.webui.activity.WXActivity.Companion.createLoadingRenderer
import com.topjohnwu.superuser.Shell
import com.sukisu.ultra.ui.util.createRootShell
import java.io.File
import com.dergoogler.mmrl.webui.interfaces.WXOptions
import com.dergoogler.mmrl.webui.util.WebUIOptions
import com.dergoogler.mmrl.webui.view.WebUIView
import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme._isSystemInDarkTheme
import com.sukisu.ultra.ui.theme.createColorScheme
import kotlinx.coroutines.launch
@SuppressLint("SetJavaScriptEnabled")
class WebUIActivity : ComponentActivity() {
private lateinit var webviewInterface: WebViewInterface
val modId get() = intent.getModId() ?: throw IllegalArgumentException("Invalid Module ID")
val prefs: SharedPreferences get() = getSharedPreferences("settings", MODE_PRIVATE)
val context: Context get() = this
private var rootShell: Shell? = null
override fun onCreate(savedInstanceState: Bundle?) {
// Enable edge to edge
enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -37,44 +46,76 @@ class WebUIActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
val moduleId = intent.getStringExtra("id")!!
val name = intent.getStringExtra("name")!!
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
@Suppress("DEPRECATION")
setTaskDescription(ActivityManager.TaskDescription("SukiSU-Ultra - $name"))
} else {
val taskDescription =
ActivityManager.TaskDescription.Builder().setLabel("SukiSU-Ultra - $name").build()
setTaskDescription(taskDescription)
val darkTheme = when (ThemeConfig.forceDarkMode) {
true -> true
false -> false
null -> _isSystemInDarkTheme(context)
}
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
WebView.setWebContentsDebuggingEnabled(prefs.getBoolean("enable_web_debugging", false))
val colorScheme = createColorScheme(
context = context,
darkTheme = darkTheme
)
val loading = createLoadingRenderer(colorScheme)
setContentView(loading)
lifecycleScope.launch {
val ready = initPlatform(context)
if (ready.await()) {
init()
return@launch
}
confirm(
ConfirmData(
title = "Failed!",
description = "Failed to initialize platform. Please try again.",
confirmText = "Close",
onConfirm = {
finish()
},
),
colorScheme = colorScheme
)
}
}
private fun init() {
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
val options = WebUIOptions(
modId = modId,
debug = webDebugging,
// keep plugins disabled for security reasons
pluginsEnabled = false,
context = context,
)
val moduleDir = "/data/adb/modules/${moduleId}"
val webRoot = File("${moduleDir}/webroot")
val rootShell = createRootShell(true).also { this.rootShell = it }
val webViewAssetLoader = WebViewAssetLoader.Builder()
.setDomain("mui.kernelsu.org")
.addPathHandler(
"/",
SuFilePathHandler(this, webRoot, rootShell)
SuFilePathHandler(this, modId.webrootDir, rootShell)
)
.build()
val webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
request: WebResourceRequest,
): WebResourceResponse? {
return webViewAssetLoader.shouldInterceptRequest(request.url)
}
}
val webView = WebView(this).apply {
val webView = WebUIView(options).apply {
ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updateLayoutParams<MarginLayoutParams> {
view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = inset.left
rightMargin = inset.right
topMargin = inset.top
@@ -82,20 +123,17 @@ class WebUIActivity : ComponentActivity() {
}
return@setOnApplyWindowInsetsListener insets
}
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.allowFileAccess = false
webviewInterface = WebViewInterface(WXOptions(this@WebUIActivity, this, ModId(moduleId)))
addJavascriptInterface(webviewInterface, "ksu")
addJavascriptInterface<WebViewInterface>()
setWebViewClient(webViewClient)
loadUrl("https://mui.kernelsu.org/index.html")
loadDomain()
}
setContentView(webView)
}
override fun onDestroy() {
super.onDestroy()
runCatching { rootShell?.close() }
super.onDestroy()
}
}

View File

@@ -1,110 +1,126 @@
package com.sukisu.ultra.ui.webui
import android.app.ActivityManager
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.webkit.WebView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.lifecycleScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.dergoogler.mmrl.platform.Platform
import com.dergoogler.mmrl.platform.model.ModId
import com.dergoogler.mmrl.ui.component.Loading
import com.dergoogler.mmrl.webui.screen.WebUIScreen
import com.dergoogler.mmrl.webui.util.rememberWebUIOptions
import com.dergoogler.mmrl.platform.PlatformManager
import com.dergoogler.mmrl.webui.activity.WXActivity
import com.dergoogler.mmrl.webui.util.WebUIOptions
import com.dergoogler.mmrl.webui.view.WebUIXView
import com.sukisu.ultra.BuildConfig
import com.sukisu.ultra.ui.theme.KernelSUTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class WebUIXActivity : ComponentActivity() {
private lateinit var webView: WebView
import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme._isSystemInDarkTheme
import kotlinx.coroutines.CoroutineScope
import kotlin.jvm.java
class WebUIXActivity : WXActivity() {
private val userAgent
get(): String {
val ksuVersion = BuildConfig.VERSION_CODE
val platform = Platform.get("Unknown") {
platform.name
val platform = PlatformManager.get(Platform.Unknown) {
platform
}
val platformVersion = Platform.get(-1) {
val platformVersion = PlatformManager.get(-1) {
moduleManager.versionCode
}
val osVersion = Build.VERSION.RELEASE
val deviceModel = Build.MODEL
return "SukiSU-Ultra /$ksuVersion (Linux; Android $osVersion; $deviceModel; $platform/$platformVersion)"
return "SukiSU-Ultra/$ksuVersion (Linux; Android $osVersion; $deviceModel; ${platform.name}/$platformVersion)"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
webView = WebView(this)
val prefs: SharedPreferences get() = getSharedPreferences("settings", MODE_PRIVATE)
val context: Context get() = this
lifecycleScope.launch {
initPlatform()
}
override suspend fun onRender(scope: CoroutineScope) {
super.onRender(scope)
val moduleId = intent.getStringExtra("id")!!
val name = intent.getStringExtra("name")!!
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
@Suppress("DEPRECATION")
setTaskDescription(ActivityManager.TaskDescription("SukiSU-Ultra - $name"))
} else {
val taskDescription =
ActivityManager.TaskDescription.Builder().setLabel("SukiSU-Ultra - $name").build()
setTaskDescription(taskDescription)
}
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
setContent {
KernelSUTheme {
var isLoading by remember { mutableStateOf(true) }
LaunchedEffect(Platform.isAlive) {
while (!Platform.isAlive) {
delay(1000)
}
isLoading = false
}
if (isLoading) {
Loading()
return@KernelSUTheme
}
val modId =
this.modId
?: throw IllegalArgumentException("modId cannot be null or empty")
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
val erudaInject = prefs.getBoolean("use_webuix_eruda", false)
val dark = isSystemInDarkTheme()
val options = rememberWebUIOptions(
modId = ModId(moduleId),
setContent {
// keep the compose logic so custom background continue to work
KernelSUTheme {
var ready by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
val init = initPlatform(context)
ready = init.await()
}
if (!ready) {
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
return@KernelSUTheme
}
val darkTheme = remember(ThemeConfig) {
when (ThemeConfig.forceDarkMode) {
true -> true
false -> false
null -> _isSystemInDarkTheme(context)
}
}
val options = WebUIOptions(
modId = modId,
context = context,
debug = webDebugging,
appVersionCode = BuildConfig.VERSION_CODE,
isDarkMode = dark,
isDarkMode = darkTheme,
// keep plugins disabled for security reasons
pluginsEnabled = false,
enableEruda = erudaInject,
cls = WebUIXActivity::class.java,
userAgentString = userAgent
userAgentString = userAgent,
colorScheme = MaterialTheme.colorScheme
)
WebUIScreen(
webView = webView,
options = options,
interfaces = listOf(
WebViewInterface.factory()
)
// Activity Title
config {
if (title != null) {
setActivityTitle("SukiSU-Ultra - $title")
}
}
AndroidView(
factory = { WebUIXView(options) },
update = { view ->
val v = view.apply {
wx.addJavascriptInterface<WebViewInterface>()
}
// pass it for the activity
this.view = v
}
)
}
}

View File

@@ -1,17 +1,23 @@
package com.sukisu.ultra.ui.webui
import android.app.Activity
import android.content.Context
import android.webkit.WebView
import com.dergoogler.mmrl.platform.model.ModId
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import android.util.Log
import android.view.Window
import android.webkit.JavascriptInterface
import android.widget.Toast
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.dergoogler.mmrl.platform.file.ExtFile
import com.dergoogler.mmrl.platform.model.ModId.Companion.moduleDir
import com.dergoogler.mmrl.webui.interfaces.WXInterface
import com.dergoogler.mmrl.webui.interfaces.WXOptions
import com.dergoogler.mmrl.webui.model.JavaScriptInterface
import com.dergoogler.mmrl.webui.util.WebUIOptions
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler
@@ -21,19 +27,14 @@ import com.sukisu.ultra.ui.util.withNewRootShell
import org.json.JSONArray
import org.json.JSONObject
import com.sukisu.ultra.ui.util.*
import java.io.File
import java.util.concurrent.CompletableFuture
import kotlin.collections.iterator
class WebViewInterface(
wxOptions: WXOptions,
) : WXInterface(wxOptions) {
override var name: String = "ksu"
internal class WebViewInterface(wxOptions: WXOptions) : WXInterface(wxOptions) {
// `ExtFile` to make sure that the platform won't get called when it is used within KSU WebUI
private val modDir: ExtFile get() = modId.moduleDir.toExtFile()
companion object {
fun factory() = JavaScriptInterface(WebViewInterface::class.java)
}
private val modDir get() = "/data/adb/modules/${modId.id}"
override var name = "ksu"
@JavascriptInterface
fun exec(cmd: String): String {
@@ -66,7 +67,7 @@ class WebViewInterface(
fun exec(
cmd: String,
options: String?,
callbackFunc: String
callbackFunc: String,
) {
val finalCommand = StringBuilder()
processOptions(finalCommand, options)
@@ -172,14 +173,18 @@ class WebViewInterface(
@JavascriptInterface
fun fullScreen(enable: Boolean) {
if (context is Activity) {
if (activity == null) return
try {
Handler(Looper.getMainLooper()).post {
if (enable) {
hideSystemUI(activity.window)
hideSystemUI(activity!!.window)
} else {
showSystemUI(activity.window)
showSystemUI(activity!!.window)
}
}
} catch (e: Exception) {
Log.e("WebViewInterface", "fullScreen", e)
}
}
@@ -187,8 +192,8 @@ class WebViewInterface(
fun moduleInfo(): String {
val moduleInfos = JSONArray(listModules())
val currentModuleInfo = JSONObject()
currentModuleInfo.put("moduleDir", modDir)
val moduleId = File(modDir).getName()
currentModuleInfo.put("moduleDir", modDir.path)
val moduleId = modDir.getName()
for (i in 0 until moduleInfos.length()) {
val currentInfo = moduleInfos.getJSONObject(i)
@@ -208,21 +213,25 @@ class WebViewInterface(
// =================== KPM支持 =============================
@JavascriptInterface
fun listAllKpm() : String {
fun listAllKpm(): String {
return listKpmModules()
}
@JavascriptInterface
fun controlKpm(name: String, args: String) : Int {
fun controlKpm(name: String, args: String): Int {
return controlKpmModule(name, args)
}
}
fun hideSystemUI(window: Window) =
private fun hideSystemUI(window: Window) =
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
fun showSystemUI(window: Window) =
WindowInsetsControllerCompat(window, window.decorView).show(WindowInsetsCompat.Type.systemBars())
private fun showSystemUI(window: Window) =
WindowInsetsControllerCompat(
window,
window.decorView
).show(WindowInsetsCompat.Type.systemBars())

View File

@@ -148,8 +148,8 @@ fun MoreSettingsScreen(
var showDpiConfirmDialog by remember { mutableStateOf(false) }
var showImageEditor by remember { mutableStateOf(false) }
// 动态管理器配置状态
var dynamicSignConfig by remember { mutableStateOf<Natives.DynamicManagerConfig?>(null) }
// 动态签名配置状态
var dynamicSignConfig by remember { mutableStateOf<Natives.DynamicSignConfig?>(null) }
var isDynamicSignEnabled by remember { mutableStateOf(false) }
var dynamicSignSize by remember { mutableStateOf("") }
var dynamicSignHash by remember { mutableStateOf("") }
@@ -674,8 +674,8 @@ fun MoreSettingsScreen(
}
LaunchedEffect(Unit) {
// 初始化动态管理器配置
dynamicSignConfig = Natives.getDynamicManager()
// 初始化动态签名配置
dynamicSignConfig = Natives.getDynamicSign()
dynamicSignConfig?.let { config ->
if (config.isValid()) {
isDynamicSignEnabled = true
@@ -696,11 +696,11 @@ fun MoreSettingsScreen(
}
}
// 动态管理器配置对话框
// 动态签名配置对话框
if (showDynamicSignDialog) {
AlertDialog(
onDismissRequest = { showDynamicSignDialog = false },
title = { Text(stringResource(R.string.dynamic_manager_title)) },
title = { Text(stringResource(R.string.dynamic_sign_title)) },
text = {
Column(
modifier = Modifier.verticalScroll(rememberScrollState())
@@ -718,7 +718,7 @@ fun MoreSettingsScreen(
onCheckedChange = { isDynamicSignEnabled = it }
)
Spacer(modifier = Modifier.width(12.dp))
Text(stringResource(R.string.enable_dynamic_manager))
Text(stringResource(R.string.enable_dynamic_sign))
}
Spacer(modifier = Modifier.height(16.dp))
@@ -773,18 +773,18 @@ fun MoreSettingsScreen(
if (isDynamicSignEnabled) {
val size = parseDynamicSignSize(dynamicSignSize)
if (size != null && size > 0 && dynamicSignHash.length == 64) {
val success = Natives.setDynamicManager(size, dynamicSignHash)
val success = Natives.setDynamicSign(size, dynamicSignHash)
if (success) {
dynamicSignConfig = Natives.DynamicManagerConfig(size, dynamicSignHash)
dynamicSignConfig = Natives.DynamicSignConfig(size, dynamicSignHash)
Toast.makeText(
context,
context.getString(R.string.dynamic_manager_set_success),
context.getString(R.string.dynamic_sign_set_success),
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
context,
context.getString(R.string.dynamic_manager_set_failed),
context.getString(R.string.dynamic_sign_set_failed),
Toast.LENGTH_SHORT
).show()
}
@@ -797,20 +797,20 @@ fun MoreSettingsScreen(
return@Button
}
} else {
val success = Natives.clearDynamicManager()
val success = Natives.clearDynamicSign()
if (success) {
dynamicSignConfig = null
dynamicSignSize = ""
dynamicSignHash = ""
Toast.makeText(
context,
context.getString(R.string.dynamic_manager_disabled_success),
context.getString(R.string.dynamic_sign_disabled_success),
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
context,
context.getString(R.string.dynamic_manager_clear_failed),
context.getString(R.string.dynamic_sign_clear_failed),
Toast.LENGTH_SHORT
).show()
return@Button
@@ -1417,18 +1417,18 @@ fun MoreSettingsScreen(
}
)
}
// 动态管理器设置
if (Natives.version >= Natives.MINIMAL_SUPPORTED_DYNAMIC_MANAGER) {
// 动态签名设置
if (Natives.version >= Natives.MINIMAL_SUPPORTED_DYNAMIC_SIGN) {
SettingItem(
icon = Icons.Filled.Security,
title = stringResource(R.string.dynamic_manager_title),
title = stringResource(R.string.dynamic_sign_title),
subtitle = if (isDynamicSignEnabled) {
stringResource(
R.string.dynamic_manager_enabled_summary,
R.string.dynamic_sign_enabled_summary,
dynamicSignSize
)
} else {
stringResource(R.string.dynamic_manager_disabled)
stringResource(R.string.dynamic_sign_disabled)
},
onClick = { showDynamicSignDialog = true }
)

View File

@@ -570,19 +570,19 @@
<string name="selected_apps_count">%1$d aplikasi dipilih</string>
<string name="already_added_apps_count">%1$d aplikasi sudah ditambahkan</string>
<string name="all_apps_already_added">Semua aplikasi telah ditambahkan</string>
<string name="dynamic_manager_title">Konfigurasi Tanda Tangan Dinamis</string>
<string name="dynamic_manager_enabled_summary">Diaktifkan (Ukuran: %s)</string>
<string name="dynamic_manager_disabled">Dinonaktifkan</string>
<string name="enable_dynamic_manager">Aktifkan Tanda Tangan Dinamis</string>
<string name="dynamic_sign_title">Konfigurasi Tanda Tangan Dinamis</string>
<string name="dynamic_sign_enabled_summary">Diaktifkan (Ukuran: %s)</string>
<string name="dynamic_sign_disabled">Dinonaktifkan</string>
<string name="enable_dynamic_sign">Aktifkan Tanda Tangan Dinamis</string>
<string name="signature_size">Ukuran Tanda Tangan</string>
<string name="signature_hash">Hash Tanda Tangan</string>
<string name="hash_must_be_64_chars">Hash harus 64 karakter heksadesimal</string>
<string name="dynamic_manager_set_success">Konfigurasi tanda tangan dinamis berhasil diatur</string>
<string name="dynamic_manager_set_failed">Gagal mengatur konfigurasi tanda tangan dinamis</string>
<string name="dynamic_sign_set_success">Konfigurasi tanda tangan dinamis berhasil diatur</string>
<string name="dynamic_sign_set_failed">Gagal mengatur konfigurasi tanda tangan dinamis</string>
<string name="invalid_sign_config">Konfigurasi tanda tangan tidak valid</string>
<string name="dynamic_manager_disabled_success">Tanda tangan dinamis dinonaktifkan</string>
<string name="dynamic_manager_clear_failed">Gagal membersihkan tanda tangan dinamis</string>
<string name="dynamic_managerature">Dinamis</string>
<string name="dynamic_sign_disabled_success">Tanda tangan dinamis dinonaktifkan</string>
<string name="dynamic_sign_clear_failed">Gagal membersihkan tanda tangan dinamis</string>
<string name="dynamic_signature">Dinamis</string>
<string name="signature_index">Tanda Tangan %1$d</string>
<string name="unknown_signature">Tidak diketahui</string>
<string name="multi_manager_list">Manajer Aktif</string>

View File

@@ -570,19 +570,19 @@
<string name="selected_apps_count">%1$d 個のアプリを選択済み</string>
<string name="already_added_apps_count">%1$d 個のアプリを追加済み</string>
<string name="all_apps_already_added">すべてのアプリが追加されました</string>
<string name="dynamic_manager_title">動的な署名の構成</string>
<string name="dynamic_manager_enabled_summary">有効 (サイズ: %s)</string>
<string name="dynamic_manager_disabled">無効</string>
<string name="enable_dynamic_manager">動的な署名を有効化</string>
<string name="dynamic_sign_title">動的な署名の構成</string>
<string name="dynamic_sign_enabled_summary">有効 (サイズ: %s)</string>
<string name="dynamic_sign_disabled">無効</string>
<string name="enable_dynamic_sign">動的な署名を有効化</string>
<string name="signature_size">署名のサイズ</string>
<string name="signature_hash">署名のハッシュ</string>
<string name="hash_must_be_64_chars">ハッシュは 64 桁の 16 進数の文字列でなければなりません。</string>
<string name="dynamic_manager_set_success">動的な署名の構成が正常に設定されました</string>
<string name="dynamic_manager_set_failed">動的な署名の構成の設定に失敗しました</string>
<string name="dynamic_sign_set_success">動的な署名の構成が正常に設定されました</string>
<string name="dynamic_sign_set_failed">動的な署名の構成の設定に失敗しました</string>
<string name="invalid_sign_config">無効な署名の構成</string>
<string name="dynamic_manager_disabled_success">動的な署名が無効です</string>
<string name="dynamic_manager_clear_failed">動的な署名の消去に失敗しました</string>
<string name="dynamic_managerature">動的</string>
<string name="dynamic_sign_disabled_success">動的な署名が無効です</string>
<string name="dynamic_sign_clear_failed">動的な署名の消去に失敗しました</string>
<string name="dynamic_signature">動的</string>
<string name="signature_index">署名 %1$d</string>
<string name="unknown_signature">不明</string>
<string name="multi_manager_list">有効なマネージャー</string>

View File

@@ -570,19 +570,19 @@
<string name="selected_apps_count">Выбрано %1$d приложений</string>
<string name="already_added_apps_count">%1$d приложений уже добавлено</string>
<string name="all_apps_already_added">Все приложения были добавлены</string>
<string name="dynamic_manager_title">Конфигурация динамической подписи</string>
<string name="dynamic_manager_enabled_summary">Включено (размер: %s)</string>
<string name="dynamic_manager_disabled">Выключено</string>
<string name="enable_dynamic_manager">Включить динамическую подпись</string>
<string name="dynamic_sign_title">Конфигурация динамической подписи</string>
<string name="dynamic_sign_enabled_summary">Включено (размер: %s)</string>
<string name="dynamic_sign_disabled">Выключено</string>
<string name="enable_dynamic_sign">Включить динамическую подпись</string>
<string name="signature_size">Размер подписи</string>
<string name="signature_hash">Хэш подписи</string>
<string name="hash_must_be_64_chars">Хеш должен содержать 64 шестнадцатеричных символа</string>
<string name="dynamic_manager_set_success">Конфигурация динамической подписи успешно установлена</string>
<string name="dynamic_manager_set_failed">Не удалось установить конфигурацию динамической подписи</string>
<string name="dynamic_sign_set_success">Конфигурация динамической подписи успешно установлена</string>
<string name="dynamic_sign_set_failed">Не удалось установить конфигурацию динамической подписи</string>
<string name="invalid_sign_config">Неверная конфигурация подписи</string>
<string name="dynamic_manager_disabled_success">Динамическая подпись отключена</string>
<string name="dynamic_manager_clear_failed">Не удалось очистить динамическую подпись</string>
<string name="dynamic_managerature">Динамическая</string>
<string name="dynamic_sign_disabled_success">Динамическая подпись отключена</string>
<string name="dynamic_sign_clear_failed">Не удалось очистить динамическую подпись</string>
<string name="dynamic_signature">Динамическая</string>
<string name="signature_index">Подпись %1$d</string>
<string name="unknown_signature">Неизвестно</string>
<string name="multi_manager_list">Активный менеджер</string>

View File

@@ -568,19 +568,19 @@
<string name="selected_apps_count">%1$d uygulama seçildi</string>
<string name="already_added_apps_count">%1$d uygulama zaten eklendi</string>
<string name="all_apps_already_added">Tüm uygulamalar eklendi</string>
<string name="dynamic_manager_title">Dinamik İmza Yapılandırması</string>
<string name="dynamic_manager_enabled_summary">Etkin (Boyut: %s)</string>
<string name="dynamic_manager_disabled">Devre Dışı</string>
<string name="enable_dynamic_manager">Dinamik İmzayı Etkinleştir</string>
<string name="dynamic_sign_title">Dinamik İmza Yapılandırması</string>
<string name="dynamic_sign_enabled_summary">Etkin (Boyut: %s)</string>
<string name="dynamic_sign_disabled">Devre Dışı</string>
<string name="enable_dynamic_sign">Dinamik İmzayı Etkinleştir</string>
<string name="signature_size">İmza Boyutu</string>
<string name="signature_hash">İmza Hash</string>
<string name="hash_must_be_64_chars">Hash, 64 adet onaltılık karakterden oluşmalıdır</string>
<string name="dynamic_manager_set_success">Dinamik imza yapılandırması başarıyla ayarlandı</string>
<string name="dynamic_manager_set_failed">Dinamik imza yapılandırması ayarlanamadı</string>
<string name="dynamic_sign_set_success">Dinamik imza yapılandırması başarıyla ayarlandı</string>
<string name="dynamic_sign_set_failed">Dinamik imza yapılandırması ayarlanamadı</string>
<string name="invalid_sign_config">Geçersiz imza yapılandırması</string>
<string name="dynamic_manager_disabled_success">Dinamik imza devre dışı bırakıldı</string>
<string name="dynamic_manager_clear_failed">Dinamik imza temizlenemedi</string>
<string name="dynamic_managerature">Dinamik</string>
<string name="dynamic_sign_disabled_success">Dinamik imza devre dışı bırakıldı</string>
<string name="dynamic_sign_clear_failed">Dinamik imza temizlenemedi</string>
<string name="dynamic_signature">Dinamik</string>
<string name="signature_index">İmza %1$d</string>
<string name="unknown_signature">Bilinmiyor</string>
<string name="multi_manager_list">Aktif Yönetici</string>

View File

@@ -568,19 +568,19 @@
<string name="selected_apps_count">%1$d ứng dụng đã chọn</string>
<string name="already_added_apps_count">%1$d ứng dụng đã thêm</string>
<string name="all_apps_already_added">Tất cả các ứng dụng đã được thêm vào</string>
<string name="dynamic_manager_title">Cấu hình chữ ký động</string>
<string name="dynamic_manager_enabled_summary">Đã kích hoạt (Size: %s)</string>
<string name="dynamic_manager_disabled">Đã vô hiệu hoá</string>
<string name="enable_dynamic_manager">Kích hoạt chữ ký động</string>
<string name="dynamic_sign_title">Cấu hình chữ ký động</string>
<string name="dynamic_sign_enabled_summary">Đã kích hoạt (Size: %s)</string>
<string name="dynamic_sign_disabled">Đã vô hiệu hoá</string>
<string name="enable_dynamic_sign">Kích hoạt chữ ký động</string>
<string name="signature_size">Size chữ ký</string>
<string name="signature_hash">Hash chữ ký</string>
<string name="hash_must_be_64_chars">Hash phải dài 64 ký tự thập lục phân</string>
<string name="dynamic_manager_set_success">Cấu hình chữ ký động đã được thiết lập thành công</string>
<string name="dynamic_manager_set_failed">Thiết lập cấu hình chữ ký động thất bại</string>
<string name="dynamic_sign_set_success">Cấu hình chữ ký động đã được thiết lập thành công</string>
<string name="dynamic_sign_set_failed">Thiết lập cấu hình chữ ký động thất bại</string>
<string name="invalid_sign_config">Cấu hình chữ ký không hợp lệ</string>
<string name="dynamic_manager_disabled_success">Chữ ký động đã bị vô hiệu hoá</string>
<string name="dynamic_manager_clear_failed">Xoá chữ ký động thất bại</string>
<string name="dynamic_managerature">Chữ ký động</string>
<string name="dynamic_sign_disabled_success">Chữ ký động đã bị vô hiệu hoá</string>
<string name="dynamic_sign_clear_failed">Xoá chữ ký động thất bại</string>
<string name="dynamic_signature">Chữ ký động</string>
<string name="signature_index">Chữ ký %1$d</string>
<string name="unknown_signature">Không xác định</string>
<string name="multi_manager_list">Trình quản lý đang hoạt động</string>

View File

@@ -568,19 +568,19 @@
<string name="selected_apps_count">%1$d 个已选应用</string>
<string name="already_added_apps_count">%1$d 个已添加应用</string>
<string name="all_apps_already_added">所有应用均已添加</string>
<string name="dynamic_manager_title">动态管理器配置</string>
<string name="dynamic_manager_enabled_summary">已启用(大小: %s</string>
<string name="dynamic_manager_disabled">未启用</string>
<string name="enable_dynamic_manager">启用动态管理器</string>
<string name="signature_size">动态管理器签名大小</string>
<string name="signature_hash">动态管理器签名哈希值</string>
<string name="dynamic_sign_title">动态签名配置</string>
<string name="dynamic_sign_enabled_summary">已启用(大小: %s</string>
<string name="dynamic_sign_disabled">未启用</string>
<string name="enable_dynamic_sign">启用动态签名</string>
<string name="signature_size">签名大小</string>
<string name="signature_hash">签名哈希值</string>
<string name="hash_must_be_64_chars">哈希值必须是 64 位十六进制字符</string>
<string name="dynamic_manager_set_success">动态管理器配置设置成功</string>
<string name="dynamic_manager_set_failed">动态管理器配置设置失败</string>
<string name="dynamic_sign_set_success">动态签名配置设置成功</string>
<string name="dynamic_sign_set_failed">动态签名配置设置失败</string>
<string name="invalid_sign_config">无效的签名配置</string>
<string name="dynamic_manager_disabled_success">动态管理器已禁用</string>
<string name="dynamic_manager_clear_failed">清除动态管理器错误</string>
<string name="dynamic_managerature">动态</string>
<string name="dynamic_sign_disabled_success">动态签名已禁用</string>
<string name="dynamic_sign_clear_failed">清除动态签名错误</string>
<string name="dynamic_signature">动态</string>
<string name="signature_index">签名 %1$d</string>
<string name="unknown_signature">未知</string>
<string name="multi_manager_list">活跃管理器</string>

View File

@@ -565,19 +565,19 @@
<string name="selected_apps_count">%1$d 個已選應用程式</string>
<string name="already_added_apps_count">%1$d 個已添加應用程式</string>
<string name="all_apps_already_added">所有应用均已添加</string>
<string name="dynamic_manager_title">動態簽名配置</string>
<string name="dynamic_manager_enabled_summary">已啟用(大小: %s</string>
<string name="dynamic_manager_disabled">未啟用</string>
<string name="enable_dynamic_manager">啟用動態簽名</string>
<string name="dynamic_sign_title">動態簽名配置</string>
<string name="dynamic_sign_enabled_summary">已啟用(大小: %s</string>
<string name="dynamic_sign_disabled">未啟用</string>
<string name="enable_dynamic_sign">啟用動態簽名</string>
<string name="signature_size">簽名大小</string>
<string name="signature_hash">簽名哈希值</string>
<string name="hash_must_be_64_chars">哈希值必須是 64 位十六進制字符</string>
<string name="dynamic_manager_set_success">動態簽名配置設定成功</string>
<string name="dynamic_manager_set_failed">動態簽名配置設定失敗</string>
<string name="dynamic_sign_set_success">動態簽名配置設定成功</string>
<string name="dynamic_sign_set_failed">動態簽名配置設定失敗</string>
<string name="invalid_sign_config">無效嘅簽名配置</string>
<string name="dynamic_manager_disabled_success">動態簽名已禁用</string>
<string name="dynamic_manager_clear_failed">清除動態簽名錯誤</string>
<string name="dynamic_managerature">動態</string>
<string name="dynamic_sign_disabled_success">動態簽名已禁用</string>
<string name="dynamic_sign_clear_failed">清除動態簽名錯誤</string>
<string name="dynamic_signature">動態</string>
<string name="signature_index">簽名 %1$d</string>
<string name="unknown_signature">未知</string>
<string name="multi_manager_list">活躍管理器</string>

View File

@@ -568,19 +568,19 @@
<string name="selected_apps_count">%1$d 個已選應用程式</string>
<string name="already_added_apps_count">%1$d 個已添加應用程式</string>
<string name="all_apps_already_added">所有应用均已添加</string>
<string name="dynamic_manager_title">动态签名配置</string>
<string name="dynamic_manager_enabled_summary">已启用(大小: %s</string>
<string name="dynamic_manager_disabled">未启用</string>
<string name="enable_dynamic_manager">啟用動態簽名</string>
<string name="dynamic_sign_title">动态签名配置</string>
<string name="dynamic_sign_enabled_summary">已启用(大小: %s</string>
<string name="dynamic_sign_disabled">未启用</string>
<string name="enable_dynamic_sign">啟用動態簽名</string>
<string name="signature_size">簽名大小</string>
<string name="signature_hash">簽名哈希值</string>
<string name="hash_must_be_64_chars">哈希值必須是 64 位十六進制字符</string>
<string name="dynamic_manager_set_success">動態簽名配置設定成功</string>
<string name="dynamic_manager_set_failed">動態簽名配置設定失敗</string>
<string name="dynamic_sign_set_success">動態簽名配置設定成功</string>
<string name="dynamic_sign_set_failed">動態簽名配置設定失敗</string>
<string name="invalid_sign_config">無效的簽名配置</string>
<string name="dynamic_manager_disabled_success">動態簽名已禁用</string>
<string name="dynamic_manager_clear_failed">清除動態簽名錯誤</string>
<string name="dynamic_managerature">動態</string>
<string name="dynamic_sign_disabled_success">動態簽名已禁用</string>
<string name="dynamic_sign_clear_failed">清除動態簽名錯誤</string>
<string name="dynamic_signature">動態</string>
<string name="signature_index">簽名 %1$d</string>
<string name="unknown_signature">未知</string>
<string name="multi_manager_list">活躍管理器</string>

View File

@@ -570,19 +570,19 @@
<string name="selected_apps_count">%1$d apps selected</string>
<string name="already_added_apps_count">%1$d apps already added</string>
<string name="all_apps_already_added">All apps have been added</string>
<string name="dynamic_manager_title">Dynamic Manager Configuration</string>
<string name="dynamic_manager_enabled_summary">Enabled (Size: %s)</string>
<string name="dynamic_manager_disabled">Disabled</string>
<string name="enable_dynamic_manager">Enable Dynamic Manager</string>
<string name="signature_size">Dynamic Manager Signature Size</string>
<string name="signature_hash">Dynamic Manager Signature Hash</string>
<string name="dynamic_sign_title">Dynamic Signature Configuration</string>
<string name="dynamic_sign_enabled_summary">Enabled (Size: %s)</string>
<string name="dynamic_sign_disabled">Disabled</string>
<string name="enable_dynamic_sign">Enable Dynamic Signature</string>
<string name="signature_size">Signature Size</string>
<string name="signature_hash">Signature Hash</string>
<string name="hash_must_be_64_chars">Hash must be 64 hexadecimal characters</string>
<string name="dynamic_manager_set_success">Dynamic Manager configuration set successfully</string>
<string name="dynamic_manager_set_failed">Failed to set dynamic Manager configuration</string>
<string name="invalid_sign_config">Invalid Manager configuration</string>
<string name="dynamic_manager_disabled_success">Dynamic Manager disabled</string>
<string name="dynamic_manager_clear_failed">Failed to clear dynamic Manager</string>
<string name="dynamic_managerature">Dynamic</string>
<string name="dynamic_sign_set_success">Dynamic signature configuration set successfully</string>
<string name="dynamic_sign_set_failed">Failed to set dynamic signature configuration</string>
<string name="invalid_sign_config">Invalid signature configuration</string>
<string name="dynamic_sign_disabled_success">Dynamic signature disabled</string>
<string name="dynamic_sign_clear_failed">Failed to clear dynamic signature</string>
<string name="dynamic_signature">Dynamic</string>
<string name="signature_index">Signature %1$d</string>
<string name="unknown_signature">Unknown</string>
<string name="multi_manager_list">Active Manager</string>

View File

@@ -23,7 +23,9 @@ compose-material = "1.8.3"
compose-material3 = "1.3.2"
compose-ui = "1.8.3"
documentfile = "1.1.0"
mmrl = "2bb00b3c2b"
mmrl = "v33953"
webui-x-portable = "953fad192a"
swiperefreshlayout = "1.1.0"
[plugins]
agp-app = { id = "com.android.application", version.ref = "agp" }
@@ -57,6 +59,8 @@ androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecyc
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" }
@@ -84,8 +88,8 @@ markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown
lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "29.0.13599879-beta2" }
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
mmrl-webui = { group = "com.github.MMRLApp.MMRL", name = "webui", version.ref = "mmrl" }
mmrl-webui-portable = { group = "com.github.MMRLApp.WebUI-X-Portable", name = "webui", version.ref = "webui-x-portable" }
mmrl-webui-jna = { group = "com.github.MMRLApp.WebUI-X-Portable", name = "jna", version.ref = "webui-x-portable" }
mmrl-platform = { group = "com.github.MMRLApp.MMRL", name = "platform", version.ref = "mmrl" }
mmrl-ui = { group = "com.github.MMRLApp.MMRL", name = "ui", version.ref = "mmrl" }
mmrl-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" }

Binary file not shown.

Binary file not shown.