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 [`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) - 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). --> <!-- 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: 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)--> <!-- - 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` - 需要 `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) - 需要 [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). --> <!-- This part refer to [rsuntk/KernelSU](https://github.com/rsuntk/KernelSU). -->
如果您能够构建可启动内核,有两种方法可以将 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 := ksu.o
kernelsu-objs += allowlist.o kernelsu-objs += allowlist.o
kernelsu-objs += dynamic_manager.o kernelsu-objs += dynamic_sign.o
kernelsu-objs += apk_sign.o kernelsu-objs += apk_sign.o
kernelsu-objs += sucompat.o kernelsu-objs += sucompat.o
kernelsu-objs += throne_tracker.o kernelsu-objs += throne_tracker.o
@@ -9,7 +9,7 @@ kernelsu-objs += ksud.o
kernelsu-objs += embed_ksud.o kernelsu-objs += embed_ksud.o
kernelsu-objs += kernel_compat.o kernelsu-objs += kernel_compat.o
ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y) ifeq ($(strip $(CONFIG_KSU_TRACEPOINT_HOOK)),y)
kernelsu-objs += ksu_trace.o kernelsu-objs += ksu_trace.o
endif endif
@@ -28,7 +28,7 @@ obj-$(CONFIG_KPM) += kpm/
REPO_OWNER := SukiSU-Ultra REPO_OWNER := SukiSU-Ultra
REPO_NAME := SukiSU-Ultra REPO_NAME := SukiSU-Ultra
REPO_BRANCH := main 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 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 CURL_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin curl

View File

@@ -15,7 +15,7 @@
#endif #endif
#include "apk_sign.h" #include "apk_sign.h"
#include "dynamic_manager.h" #include "dynamic_sign.h"
#include "klog.h" // IWYU pragma: keep #include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h" #include "kernel_compat.h"
#include "manager_sign.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 if (i == 1) { // Dynamic Sign indexing
unsigned int size; unsigned int size;
const char *hash; 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.size = size;
sign_key.sha256 = hash; sign_key.sha256 = hash;
} }
@@ -219,8 +219,8 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana
return false; return false;
} }
// If you want to check for multi-manager APK signing, but dynamic managering is not enabled, skip // If you want to check for multi-manager APK signing, but dynamic signing is not enabled, skip
if (check_multi_manager && !ksu_is_dynamic_manager_enabled()) { if (check_multi_manager && !ksu_is_dynamic_sign_enabled()) {
filp_close(fp, 0); filp_close(fp, 0);
return 0; return 0;
} }
@@ -328,7 +328,7 @@ clean:
if (check_multi_manager) { if (check_multi_manager) {
// 0: ShirkNeko/SukiSU, 1: Dynamic Sign // 0: ShirkNeko/SukiSU, 1: Dynamic Sign
if (matched_index == 0 || matched_index == 1) { 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 true;
} }
return false; return false;
@@ -369,7 +369,7 @@ bool is_manager_apk(char *path)
return check_v2_signature(path, false, NULL); 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); return check_v2_signature(path, true, signature_index);
} }

View File

@@ -45,7 +45,7 @@
#include "kernel_compat.h" #include "kernel_compat.h"
#include "kpm/kpm.h" #include "kpm/kpm.h"
#include "dynamic_manager.h" #include "dynamic_sign.h"
static bool ksu_module_mounted = false; static bool ksu_module_mounted = false;
@@ -345,31 +345,31 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
return 0; return 0;
} }
// Allow the root manager to configure dynamic manageratures // Allow the root manager to configure dynamic signatures
if (arg2 == CMD_DYNAMIC_MANAGER) { if (arg2 == CMD_DYNAMIC_SIGN) {
if (!from_root && !from_manager) { if (!from_root && !from_manager) {
return 0; return 0;
} }
struct dynamic_manager_user_config config; struct dynamic_sign_user_config config;
if (copy_from_user(&config, (void __user *)arg3, sizeof(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; 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))) { 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; return 0;
} }
} }
if (ret == 0) { if (ret == 0) {
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { 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; 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"); pr_info("post-fs-data triggered\n");
on_post_fs_data(); on_post_fs_data();
// Initializing Dynamic Signatures // Initializing Dynamic Signatures
ksu_dynamic_manager_init(); ksu_dynamic_sign_init();
pr_info("Dynamic sign config loaded during post-fs-data\n"); pr_info("Dynamic sign config loaded during post-fs-data\n");
} }
break; 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> #include <crypto/sha.h>
#endif #endif
#include "dynamic_manager.h" #include "dynamic_sign.h"
#include "klog.h" // IWYU pragma: keep #include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h" #include "kernel_compat.h"
#include "manager.h" #include "manager.h"
@@ -23,7 +23,7 @@
#define MAX_MANAGERS 2 #define MAX_MANAGERS 2
// Dynamic sign configuration // Dynamic sign configuration
static struct dynamic_manager_config dynamic_manager = { static struct dynamic_sign_config dynamic_sign = {
.size = 0x300, .size = 0x300,
.hash = "0000000000000000000000000000000000000000000000000000000000000000", .hash = "0000000000000000000000000000000000000000000000000000000000000000",
.is_set = 0 .is_set = 0
@@ -32,21 +32,21 @@ static struct dynamic_manager_config dynamic_manager = {
// Multi-manager state // Multi-manager state
static struct manager_info active_managers[MAX_MANAGERS]; static struct manager_info active_managers[MAX_MANAGERS];
static DEFINE_SPINLOCK(managers_lock); static DEFINE_SPINLOCK(managers_lock);
static DEFINE_SPINLOCK(dynamic_manager_lock); static DEFINE_SPINLOCK(dynamic_sign_lock);
// Work queues for persistent storage // Work queues for persistent storage
static struct work_struct ksu_save_dynamic_manager_work; static struct work_struct ksu_save_dynamic_sign_work;
static struct work_struct ksu_load_dynamic_manager_work; static struct work_struct ksu_load_dynamic_sign_work;
static struct work_struct ksu_clear_dynamic_manager_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; unsigned long flags;
bool enabled; bool enabled;
spin_lock_irqsave(&dynamic_manager_lock, flags); spin_lock_irqsave(&dynamic_sign_lock, flags);
enabled = dynamic_manager.is_set; enabled = dynamic_sign.is_set;
spin_unlock_irqrestore(&dynamic_manager_lock, flags); spin_unlock_irqrestore(&dynamic_sign_lock, flags);
return enabled; return enabled;
} }
@@ -56,7 +56,7 @@ void ksu_add_manager(uid_t uid, int signature_index)
unsigned long flags; unsigned long flags;
int i; 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"); pr_info("Dynamic sign not enabled, skipping multi-manager add\n");
return; return;
} }
@@ -94,7 +94,7 @@ void ksu_remove_manager(uid_t uid)
unsigned long flags; unsigned long flags;
int i; int i;
if (!ksu_is_dynamic_manager_enabled()) { if (!ksu_is_dynamic_sign_enabled()) {
return; return;
} }
@@ -117,7 +117,7 @@ bool ksu_is_any_manager(uid_t uid)
bool is_manager = false; bool is_manager = false;
int i; int i;
if (!ksu_is_dynamic_manager_enabled()) { if (!ksu_is_dynamic_sign_enabled()) {
return false; return false;
} }
@@ -145,7 +145,7 @@ int ksu_get_manager_signature_index(uid_t uid)
return 1; return 1;
} }
if (!ksu_is_dynamic_manager_enabled()) { if (!ksu_is_dynamic_sign_enabled()) {
return -1; return -1;
} }
@@ -197,7 +197,7 @@ int ksu_get_active_managers(struct manager_list_info *info)
} }
// Add dynamic managers // Add dynamic managers
if (ksu_is_dynamic_manager_enabled()) { if (ksu_is_dynamic_sign_enabled()) {
spin_lock_irqsave(&managers_lock, flags); spin_lock_irqsave(&managers_lock, flags);
for (i = 0; i < MAX_MANAGERS && count < 2; i++) { 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; 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 magic = DYNAMIC_SIGN_FILE_MAGIC;
u32 version = DYNAMIC_MANAGER_FILE_VERSION; u32 version = DYNAMIC_SIGN_FILE_VERSION;
struct dynamic_manager_config config_to_save; struct dynamic_sign_config config_to_save;
loff_t off = 0; loff_t off = 0;
unsigned long flags; unsigned long flags;
struct file *fp; struct file *fp;
spin_lock_irqsave(&dynamic_manager_lock, flags); spin_lock_irqsave(&dynamic_sign_lock, flags);
config_to_save = dynamic_manager; config_to_save = dynamic_sign;
spin_unlock_irqrestore(&dynamic_manager_lock, flags); spin_unlock_irqrestore(&dynamic_sign_lock, flags);
if (!config_to_save.is_set) { if (!config_to_save.is_set) {
pr_info("Dynamic sign config not set, skipping save\n"); pr_info("Dynamic sign config not set, skipping save\n");
return; 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)) { 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; return;
} }
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) { 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; goto exit;
} }
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) { 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; goto exit;
} }
if (ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) { 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; goto exit;
} }
@@ -260,48 +260,48 @@ exit:
filp_close(fp, 0); 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; loff_t off = 0;
ssize_t ret = 0; ssize_t ret = 0;
struct file *fp = NULL; struct file *fp = NULL;
u32 magic; u32 magic;
u32 version; u32 version;
struct dynamic_manager_config loaded_config; struct dynamic_sign_config loaded_config;
unsigned long flags; unsigned long flags;
int i; 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 (IS_ERR(fp)) {
if (PTR_ERR(fp) == -ENOENT) { if (PTR_ERR(fp) == -ENOENT) {
pr_info("No saved dynamic manager config found\n"); pr_info("No saved dynamic sign config found\n");
} else { } 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; return;
} }
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) || if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
magic != DYNAMIC_MANAGER_FILE_MAGIC) { magic != DYNAMIC_SIGN_FILE_MAGIC) {
pr_err("dynamic manager file invalid magic: %x!\n", magic); pr_err("dynamic sign file invalid magic: %x!\n", magic);
goto exit; goto exit;
} }
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) { 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; 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); ret = ksu_kernel_read_compat(fp, &loaded_config, sizeof(loaded_config), &off);
if (ret <= 0) { 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; goto exit;
} }
if (ret != sizeof(loaded_config)) { 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; goto exit;
} }
@@ -324,9 +324,9 @@ static void do_load_dynamic_manager(struct work_struct *work)
} }
} }
spin_lock_irqsave(&dynamic_manager_lock, flags); spin_lock_irqsave(&dynamic_sign_lock, flags);
dynamic_manager = loaded_config; dynamic_sign = loaded_config;
spin_unlock_irqrestore(&dynamic_manager_lock, flags); spin_unlock_irqrestore(&dynamic_sign_lock, flags);
pr_info("Dynamic sign config loaded: size=0x%x, hash=%.16s...\n", pr_info("Dynamic sign config loaded: size=0x%x, hash=%.16s...\n",
loaded_config.size, loaded_config.hash); loaded_config.size, loaded_config.hash);
@@ -335,12 +335,12 @@ exit:
filp_close(fp, 0); 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; loff_t off = 0;
struct file *fp; struct file *fp;
@@ -348,15 +348,15 @@ static void do_clear_dynamic_manager(struct work_struct *work)
memset(zero_buffer, 0, sizeof(zero_buffer)); 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)) { 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; return;
} }
// Write null bytes to overwrite the file content // Write null bytes to overwrite the file content
if (ksu_kernel_write_compat(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) { 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 { } else {
pr_info("Dynamic sign config file cleared successfully\n"); 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); 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; unsigned long flags;
int ret = 0; int ret = 0;
@@ -380,7 +380,7 @@ int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
} }
switch (config->operation) { switch (config->operation) {
case DYNAMIC_MANAGER_OP_SET: case DYNAMIC_SIGN_OP_SET:
if (config->size < 0x100 || config->size > 0x1000) { if (config->size < 0x100 || config->size > 0x1000) {
pr_err("invalid size: 0x%x\n", config->size); pr_err("invalid size: 0x%x\n", config->size);
return -EINVAL; return -EINVAL;
@@ -400,106 +400,106 @@ int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
} }
} }
spin_lock_irqsave(&dynamic_manager_lock, flags); spin_lock_irqsave(&dynamic_sign_lock, flags);
dynamic_manager.size = config->size; dynamic_sign.size = config->size;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) #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 #else
strlcpy(dynamic_manager.hash, config->hash, sizeof(dynamic_manager.hash)); strlcpy(dynamic_sign.hash, config->hash, sizeof(dynamic_sign.hash));
#endif #endif
dynamic_manager.is_set = 1; dynamic_sign.is_set = 1;
spin_unlock_irqrestore(&dynamic_manager_lock, flags); spin_unlock_irqrestore(&dynamic_sign_lock, flags);
persistent_dynamic_manager(); persistent_dynamic_sign();
pr_info("dynamic manager updated: size=0x%x, hash=%.16s... (multi-manager enabled)\n", pr_info("dynamic sign updated: size=0x%x, hash=%.16s... (multi-manager enabled)\n",
config->size, config->hash); config->size, config->hash);
break; break;
case DYNAMIC_MANAGER_OP_GET: case DYNAMIC_SIGN_OP_GET:
spin_lock_irqsave(&dynamic_manager_lock, flags); spin_lock_irqsave(&dynamic_sign_lock, flags);
if (dynamic_manager.is_set) { if (dynamic_sign.is_set) {
config->size = dynamic_manager.size; config->size = dynamic_sign.size;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) #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 #else
strlcpy(config->hash, dynamic_manager.hash, sizeof(config->hash)); strlcpy(config->hash, dynamic_sign.hash, sizeof(config->hash));
#endif #endif
ret = 0; ret = 0;
} else { } else {
ret = -ENODATA; ret = -ENODATA;
} }
spin_unlock_irqrestore(&dynamic_manager_lock, flags); spin_unlock_irqrestore(&dynamic_sign_lock, flags);
break; break;
case DYNAMIC_MANAGER_OP_CLEAR: case DYNAMIC_SIGN_OP_CLEAR:
spin_lock_irqsave(&dynamic_manager_lock, flags); spin_lock_irqsave(&dynamic_sign_lock, flags);
dynamic_manager.size = 0x300; dynamic_sign.size = 0x300;
strcpy(dynamic_manager.hash, "0000000000000000000000000000000000000000000000000000000000000000"); strcpy(dynamic_sign.hash, "0000000000000000000000000000000000000000000000000000000000000000");
dynamic_manager.is_set = 0; dynamic_sign.is_set = 0;
spin_unlock_irqrestore(&dynamic_manager_lock, flags); spin_unlock_irqrestore(&dynamic_sign_lock, flags);
// Clear only dynamic managers, preserve default manager // Clear only dynamic managers, preserve default manager
clear_dynamic_manager(); clear_dynamic_manager();
// Clear file using the same method as save // 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"); pr_info("Dynamic sign config cleared (multi-manager disabled)\n");
break; break;
default: default:
pr_err("Invalid dynamic manager operation: %d\n", config->operation); pr_err("Invalid dynamic sign operation: %d\n", config->operation);
return -EINVAL; return -EINVAL;
} }
return ret; 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; int i;
INIT_WORK(&ksu_save_dynamic_manager_work, do_save_dynamic_manager); INIT_WORK(&ksu_save_dynamic_sign_work, do_save_dynamic_sign);
INIT_WORK(&ksu_load_dynamic_manager_work, do_load_dynamic_manager); INIT_WORK(&ksu_load_dynamic_sign_work, do_load_dynamic_sign);
INIT_WORK(&ksu_clear_dynamic_manager_work, do_clear_dynamic_manager); INIT_WORK(&ksu_clear_dynamic_sign_work, do_clear_dynamic_sign);
// Initialize manager slots // Initialize manager slots
for (i = 0; i < MAX_MANAGERS; i++) { for (i = 0; i < MAX_MANAGERS; i++) {
active_managers[i].is_active = false; 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"); 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(); clear_dynamic_manager();
// Save current config before exit // Save current config before exit
do_save_dynamic_manager(NULL); do_save_dynamic_sign(NULL);
pr_info("Dynamic sign exited with persistent storage\n"); pr_info("Dynamic sign exited with persistent storage\n");
} }
// Get dynamic manager configuration for signature verification // Get dynamic sign configuration for signature verification
bool ksu_get_dynamic_manager_config(unsigned int *size, const char **hash) bool ksu_get_dynamic_sign_config(unsigned int *size, const char **hash)
{ {
unsigned long flags; unsigned long flags;
bool valid = false; bool valid = false;
spin_lock_irqsave(&dynamic_manager_lock, flags); spin_lock_irqsave(&dynamic_sign_lock, flags);
if (dynamic_manager.is_set) { if (dynamic_sign.is_set) {
if (size) *size = dynamic_manager.size; if (size) *size = dynamic_sign.size;
if (hash) *hash = dynamic_manager.hash; if (hash) *hash = dynamic_sign.hash;
valid = true; valid = true;
} }
spin_unlock_irqrestore(&dynamic_manager_lock, flags); spin_unlock_irqrestore(&dynamic_sign_lock, flags);
return valid; 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_ENABLE_KPM 100
#define CMD_HOOK_TYPE 101 #define CMD_HOOK_TYPE 101
#define CMD_DYNAMIC_MANAGER 103 #define CMD_DYNAMIC_SIGN 103
#define CMD_GET_MANAGERS 104 #define CMD_GET_MANAGERS 104
#define EVENT_POST_FS_DATA 1 #define EVENT_POST_FS_DATA 1
@@ -47,11 +47,11 @@
#endif #endif
#define KSU_FULL_VERSION_STRING 255 #define KSU_FULL_VERSION_STRING 255
#define DYNAMIC_MANAGER_OP_SET 0 #define DYNAMIC_SIGN_OP_SET 0
#define DYNAMIC_MANAGER_OP_GET 1 #define DYNAMIC_SIGN_OP_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2 #define DYNAMIC_SIGN_OP_CLEAR 2
struct dynamic_manager_user_config { struct dynamic_sign_user_config {
unsigned int operation; unsigned int operation;
unsigned int size; unsigned int size;
char hash[65]; char hash[65];

View File

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

View File

@@ -28,12 +28,12 @@ apksign {
android { android {
/**signingConfigs { /**signingConfigs {
create("Debug") { create("Debug") {
storeFile = file("D:\\other\\AndroidTool\\android_key\\keystore\\release-key.keystore") storeFile = file("D:\\other\\AndroidTool\\android_key\\keystore\\release-key.keystore")
storePassword = "" storePassword = ""
keyAlias = "" keyAlias = ""
keyPassword = "" keyPassword = ""
} }
}**/ }**/
namespace = "com.sukisu.ultra" namespace = "com.sukisu.ultra"
@@ -41,10 +41,13 @@ android {
release { release {
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
} }
/**debug { /**debug {
signingConfig = signingConfigs.named("Debug").get() as ApkSigningConfig signingConfig = signingConfigs.named("Debug").get() as ApkSigningConfig
}**/ }**/
} }
@@ -131,6 +134,8 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.compose.destinations.core) implementation(libs.compose.destinations.core)
ksp(libs.compose.destinations.ksp) ksp(libs.compose.destinations.ksp)
@@ -159,7 +164,16 @@ dependencies {
implementation(libs.mmrl.platform) implementation(libs.mmrl.platform)
compileOnly(libs.mmrl.hidden.api) 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.mmrl.ui)
implementation(libs.accompanist.drawablepainter) implementation(libs.accompanist.drawablepainter)

View File

@@ -4,43 +4,14 @@
-dontwarn org.conscrypt.** -dontwarn org.conscrypt.**
-dontwarn kotlinx.serialization.** -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 # 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.dergoogler.mmrl.webui.interfaces.** { *; }
-keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; } -keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; }

View File

@@ -341,43 +341,43 @@ NativeBridgeNP(getSusfsFeatureStatus, jobject) {
return obj; return obj;
} }
// dynamic manager // dynamic sign
NativeBridge(setDynamicManager, jboolean, jint size, jstring hash) { NativeBridge(setDynamicSign, jboolean, jint size, jstring hash) {
if (!hash) { if (!hash) {
LogDebug("setDynamicManager: hash is null"); LogDebug("setDynamicSign: hash is null");
return false; return false;
} }
const char* chash = GetEnvironment()->GetStringUTFChars(env, hash, nullptr); 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); 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; return result;
} }
NativeBridgeNP(getDynamicManager, jobject) { NativeBridgeNP(getDynamicSign, jobject) {
struct dynamic_manager_user_config config; struct dynamic_sign_user_config config;
bool result = get_dynamic_manager(&config); bool result = get_dynamic_sign(&config);
if (!result) { if (!result) {
LogDebug("getDynamicManager: failed to get dynamic manager config"); LogDebug("getDynamicSign: failed to get dynamic sign config");
return NULL; return NULL;
} }
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicManagerConfig"); jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicSignConfig");
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicManagerConfig"); jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicSignConfig");
SET_INT_FIELD(obj, cls, size, (jint)config.size); SET_INT_FIELD(obj, cls, size, (jint)config.size);
SET_STRING_FIELD(obj, cls, hash, config.hash); 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; return obj;
} }
NativeBridgeNP(clearDynamicManager, jboolean) { NativeBridgeNP(clearDynamicSign, jboolean) {
bool result = clear_dynamic_manager(); bool result = clear_dynamic_sign();
LogDebug("clearDynamicManager: result=%d", result); LogDebug("clearDynamicSign: result=%d", result);
return 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_ENABLE_KPM 100
#define CMD_HOOK_TYPE 101 #define CMD_HOOK_TYPE 101
#define CMD_GET_SUSFS_FEATURE_STATUS 102 #define CMD_GET_SUSFS_FEATURE_STATUS 102
#define CMD_DYNAMIC_MANAGER 103 #define CMD_DYNAMIC_SIGN 103
#define CMD_GET_MANAGERS 104 #define CMD_GET_MANAGERS 104
#define DYNAMIC_MANAGER_OP_SET 0 #define DYNAMIC_SIGN_OP_SET 0
#define DYNAMIC_MANAGER_OP_GET 1 #define DYNAMIC_SIGN_OP_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2 #define DYNAMIC_SIGN_OP_CLEAR 2
static bool ksuctl(int cmd, void* arg1, void* arg2) { static bool ksuctl(int cmd, void* arg1, void* arg2) {
int32_t result = 0; 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); 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) { if (hash == NULL) {
return false; return false;
} }
struct dynamic_manager_user_config config; struct dynamic_sign_user_config config;
config.operation = DYNAMIC_MANAGER_OP_SET; config.operation = DYNAMIC_SIGN_OP_SET;
config.size = size; config.size = size;
strncpy(config.hash, hash, sizeof(config.hash) - 1); strncpy(config.hash, hash, sizeof(config.hash) - 1);
config.hash[sizeof(config.hash) - 1] = '\0'; 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) { if (config == NULL) {
return false; return false;
} }
config->operation = DYNAMIC_MANAGER_OP_GET; config->operation = DYNAMIC_SIGN_OP_GET;
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL); return ksuctl(CMD_DYNAMIC_SIGN, config, NULL);
} }
bool clear_dynamic_manager() { bool clear_dynamic_sign() {
struct dynamic_manager_user_config config; struct dynamic_sign_user_config config;
config.operation = DYNAMIC_MANAGER_OP_CLEAR; config.operation = DYNAMIC_SIGN_OP_CLEAR;
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL); return ksuctl(CMD_DYNAMIC_SIGN, &config, NULL);
} }
bool get_managers_list(struct manager_list_info* info) { 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_MAX_GROUPS 32
#define KSU_SELINUX_DOMAIN 64 #define KSU_SELINUX_DOMAIN 64
#define DYNAMIC_MANAGER_OP_SET 0 #define DYNAMIC_SIGN_OP_SET 0
#define DYNAMIC_MANAGER_OP_GET 1 #define DYNAMIC_SIGN_OP_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2 #define DYNAMIC_SIGN_OP_CLEAR 2
struct dynamic_manager_user_config { struct dynamic_sign_user_config {
unsigned int operation; unsigned int operation;
unsigned int size; unsigned int size;
char hash[65]; 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 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); bool get_managers_list(struct manager_list_info* info);

View File

@@ -11,7 +11,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import coil.Coil import coil.Coil
import coil.ImageLoader 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.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import java.io.File import java.io.File
@@ -90,7 +90,7 @@ class KernelSUApplication : Application() {
// 注册Activity生命周期回调 // 注册Activity生命周期回调
registerActivityLifecycleCallbacks(activityLifecycleCallbacks) registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
Platform.setHiddenApiExemptions() PlatformManager.setHiddenApiExemptions()
val context = this val context = this
val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size) 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_KPM = 12800
const val MINIMAL_SUPPORTED_DYNAMIC_MANAGER = 13215 const val MINIMAL_SUPPORTED_DYNAMIC_SIGN = 13215
const val ROOT_UID = 0 const val ROOT_UID = 0
const val ROOT_GID = 0 const val ROOT_GID = 0
@@ -107,28 +107,28 @@ object Natives {
external fun getSusfsFeatureStatus(): SusfsFeatureStatus? external fun getSusfsFeatureStatus(): SusfsFeatureStatus?
/** /**
* Set dynamic managerature configuration * Set dynamic signature configuration
* @param size APK signature size * @param size APK signature size
* @param hash APK signature hash (64 character hex string) * @param hash APK signature hash (64 character hex string)
* @return true if successful, false otherwise * @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 * Get current dynamic signature configuration
* @return DynamicManagerConfig object containing current configuration, or null if not set * @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 * @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 * @return ManagersList object containing active managers, or null if failed or not enabled
*/ */
external fun getManagersList(): ManagersList? external fun getManagersList(): ManagersList?
@@ -185,7 +185,7 @@ object Natives {
@Immutable @Immutable
@Parcelize @Parcelize
@Keep @Keep
data class DynamicManagerConfig( data class DynamicSignConfig(
val size: Int = 0, val size: Int = 0,
val hash: String = "" val hash: String = ""
) : Parcelable { ) : Parcelable {

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.util.Log import android.util.Log
import android.widget.Toast 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.Natives
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
package com.sukisu.ultra.ui.webui package com.sukisu.ultra.ui.webui
import android.annotation.SuppressLint import android.content.Context
import android.app.ActivityManager import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse import android.webkit.WebResourceResponse
import android.webkit.WebView import android.webkit.WebView
@@ -14,21 +14,30 @@ import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.webkit.WebViewAssetLoader 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.topjohnwu.superuser.Shell
import com.sukisu.ultra.ui.util.createRootShell import com.sukisu.ultra.ui.util.createRootShell
import java.io.File import com.dergoogler.mmrl.webui.util.WebUIOptions
import com.dergoogler.mmrl.webui.interfaces.WXOptions 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() { 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 private var rootShell: Shell? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// Enable edge to edge // Enable edge to edge
enableEdgeToEdge() enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -37,44 +46,76 @@ class WebUIActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val moduleId = intent.getStringExtra("id")!! val darkTheme = when (ThemeConfig.forceDarkMode) {
val name = intent.getStringExtra("name")!! true -> true
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { false -> false
@Suppress("DEPRECATION") null -> _isSystemInDarkTheme(context)
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) val colorScheme = createColorScheme(
WebView.setWebContentsDebuggingEnabled(prefs.getBoolean("enable_web_debugging", false)) 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 rootShell = createRootShell(true).also { this.rootShell = it }
val webViewAssetLoader = WebViewAssetLoader.Builder() val webViewAssetLoader = WebViewAssetLoader.Builder()
.setDomain("mui.kernelsu.org") .setDomain("mui.kernelsu.org")
.addPathHandler( .addPathHandler(
"/", "/",
SuFilePathHandler(this, webRoot, rootShell) SuFilePathHandler(this, modId.webrootDir, rootShell)
) )
.build() .build()
val webViewClient = object : WebViewClient() { val webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest( override fun shouldInterceptRequest(
view: WebView, view: WebView,
request: WebResourceRequest request: WebResourceRequest,
): WebResourceResponse? { ): WebResourceResponse? {
return webViewAssetLoader.shouldInterceptRequest(request.url) return webViewAssetLoader.shouldInterceptRequest(request.url)
} }
} }
val webView = WebView(this).apply { val webView = WebUIView(options).apply {
ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets -> ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updateLayoutParams<MarginLayoutParams> { view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = inset.left leftMargin = inset.left
rightMargin = inset.right rightMargin = inset.right
topMargin = inset.top topMargin = inset.top
@@ -82,20 +123,17 @@ class WebUIActivity : ComponentActivity() {
} }
return@setOnApplyWindowInsetsListener insets return@setOnApplyWindowInsetsListener insets
} }
settings.javaScriptEnabled = true
settings.domStorageEnabled = true addJavascriptInterface<WebViewInterface>()
settings.allowFileAccess = false
webviewInterface = WebViewInterface(WXOptions(this@WebUIActivity, this, ModId(moduleId)))
addJavascriptInterface(webviewInterface, "ksu")
setWebViewClient(webViewClient) setWebViewClient(webViewClient)
loadUrl("https://mui.kernelsu.org/index.html") loadDomain()
} }
setContentView(webView) setContentView(webView)
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy()
runCatching { rootShell?.close() } runCatching { rootShell?.close() }
super.onDestroy()
} }
} }

View File

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

View File

@@ -1,228 +1,237 @@
package com.sukisu.ultra.ui.webui package com.sukisu.ultra.ui.webui
import android.app.Activity import android.app.Activity
import android.os.Handler import android.content.Context
import android.os.Looper import android.webkit.WebView
import android.text.TextUtils import com.dergoogler.mmrl.platform.model.ModId
import android.view.Window import android.os.Handler
import android.webkit.JavascriptInterface import android.os.Looper
import android.widget.Toast import android.text.TextUtils
import androidx.core.view.WindowInsetsCompat import android.util.Log
import androidx.core.view.WindowInsetsControllerCompat import android.view.Window
import com.dergoogler.mmrl.webui.interfaces.WXInterface import android.webkit.JavascriptInterface
import com.dergoogler.mmrl.webui.interfaces.WXOptions import android.widget.Toast
import com.dergoogler.mmrl.webui.model.JavaScriptInterface import androidx.core.view.WindowInsetsCompat
import com.topjohnwu.superuser.CallbackList import androidx.core.view.WindowInsetsControllerCompat
import com.topjohnwu.superuser.ShellUtils import com.dergoogler.mmrl.platform.file.ExtFile
import com.topjohnwu.superuser.internal.UiThreadHandler import com.dergoogler.mmrl.platform.model.ModId.Companion.moduleDir
import com.sukisu.ultra.ui.util.createRootShell import com.dergoogler.mmrl.webui.interfaces.WXInterface
import com.sukisu.ultra.ui.util.listModules import com.dergoogler.mmrl.webui.interfaces.WXOptions
import com.sukisu.ultra.ui.util.withNewRootShell import com.dergoogler.mmrl.webui.util.WebUIOptions
import org.json.JSONArray import com.topjohnwu.superuser.CallbackList
import org.json.JSONObject import com.topjohnwu.superuser.ShellUtils
import com.sukisu.ultra.ui.util.* import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File import com.sukisu.ultra.ui.util.createRootShell
import java.util.concurrent.CompletableFuture import com.sukisu.ultra.ui.util.listModules
import com.sukisu.ultra.ui.util.withNewRootShell
class WebViewInterface( import org.json.JSONArray
wxOptions: WXOptions, import org.json.JSONObject
) : WXInterface(wxOptions) { import com.sukisu.ultra.ui.util.*
override var name: String = "ksu" import java.util.concurrent.CompletableFuture
import kotlin.collections.iterator
companion object {
fun factory() = JavaScriptInterface(WebViewInterface::class.java) 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()
private val modDir get() = "/data/adb/modules/${modId.id}"
override var name = "ksu"
@JavascriptInterface
fun exec(cmd: String): String { @JavascriptInterface
return withNewRootShell(true) { ShellUtils.fastCmd(this, cmd) } fun exec(cmd: String): String {
} return withNewRootShell(true) { ShellUtils.fastCmd(this, cmd) }
}
@JavascriptInterface
fun exec(cmd: String, callbackFunc: String) { @JavascriptInterface
exec(cmd, null, callbackFunc) fun exec(cmd: String, callbackFunc: String) {
} exec(cmd, null, callbackFunc)
}
private fun processOptions(sb: StringBuilder, options: String?) {
val opts = if (options == null) JSONObject() else { private fun processOptions(sb: StringBuilder, options: String?) {
JSONObject(options) val opts = if (options == null) JSONObject() else {
} JSONObject(options)
}
val cwd = opts.optString("cwd")
if (!TextUtils.isEmpty(cwd)) { val cwd = opts.optString("cwd")
sb.append("cd ${cwd};") if (!TextUtils.isEmpty(cwd)) {
} sb.append("cd ${cwd};")
}
opts.optJSONObject("env")?.let { env ->
env.keys().forEach { key -> opts.optJSONObject("env")?.let { env ->
sb.append("export ${key}=${env.getString(key)};") env.keys().forEach { key ->
} sb.append("export ${key}=${env.getString(key)};")
} }
} }
}
@JavascriptInterface
fun exec( @JavascriptInterface
cmd: String, fun exec(
options: String?, cmd: String,
callbackFunc: String options: String?,
) { callbackFunc: String,
val finalCommand = StringBuilder() ) {
processOptions(finalCommand, options) val finalCommand = StringBuilder()
finalCommand.append(cmd) processOptions(finalCommand, options)
finalCommand.append(cmd)
val result = withNewRootShell(true) {
newJob().add(finalCommand.toString()).to(ArrayList(), ArrayList()).exec() val result = withNewRootShell(true) {
} newJob().add(finalCommand.toString()).to(ArrayList(), ArrayList()).exec()
val stdout = result.out.joinToString(separator = "\n") }
val stderr = result.err.joinToString(separator = "\n") val stdout = result.out.joinToString(separator = "\n")
val stderr = result.err.joinToString(separator = "\n")
val jsCode =
"javascript: (function() { try { ${callbackFunc}(${result.code}, ${ val jsCode =
JSONObject.quote( "javascript: (function() { try { ${callbackFunc}(${result.code}, ${
stdout JSONObject.quote(
) stdout
}, ${JSONObject.quote(stderr)}); } catch(e) { console.error(e); } })();" )
webView.post { }, ${JSONObject.quote(stderr)}); } catch(e) { console.error(e); } })();"
webView.loadUrl(jsCode) webView.post {
} webView.loadUrl(jsCode)
} }
}
@JavascriptInterface
fun spawn(command: String, args: String, options: String?, callbackFunc: String) { @JavascriptInterface
val finalCommand = StringBuilder() fun spawn(command: String, args: String, options: String?, callbackFunc: String) {
val finalCommand = StringBuilder()
processOptions(finalCommand, options)
processOptions(finalCommand, options)
if (!TextUtils.isEmpty(args)) {
finalCommand.append(command).append(" ") if (!TextUtils.isEmpty(args)) {
JSONArray(args).let { argsArray -> finalCommand.append(command).append(" ")
for (i in 0 until argsArray.length()) { JSONArray(args).let { argsArray ->
finalCommand.append(argsArray.getString(i)) for (i in 0 until argsArray.length()) {
finalCommand.append(" ") finalCommand.append(argsArray.getString(i))
} finalCommand.append(" ")
} }
} else { }
finalCommand.append(command) } else {
} finalCommand.append(command)
}
val shell = createRootShell(true)
val shell = createRootShell(true)
val emitData = fun(name: String, data: String) {
val jsCode = val emitData = fun(name: String, data: String) {
"javascript: (function() { try { ${callbackFunc}.${name}.emit('data', ${ val jsCode =
JSONObject.quote( "javascript: (function() { try { ${callbackFunc}.${name}.emit('data', ${
data JSONObject.quote(
) data
}); } catch(e) { console.error('emitData', e); } })();" )
webView.post { }); } catch(e) { console.error('emitData', e); } })();"
webView.loadUrl(jsCode) webView.post {
} webView.loadUrl(jsCode)
} }
}
val stdout = object : CallbackList<String>(UiThreadHandler::runAndWait) {
override fun onAddElement(s: String) { val stdout = object : CallbackList<String>(UiThreadHandler::runAndWait) {
emitData("stdout", s) override fun onAddElement(s: String) {
} emitData("stdout", s)
} }
}
val stderr = object : CallbackList<String>(UiThreadHandler::runAndWait) {
override fun onAddElement(s: String) { val stderr = object : CallbackList<String>(UiThreadHandler::runAndWait) {
emitData("stderr", s) override fun onAddElement(s: String) {
} emitData("stderr", s)
} }
}
val future = shell.newJob().add(finalCommand.toString()).to(stdout, stderr).enqueue()
val completableFuture = CompletableFuture.supplyAsync { val future = shell.newJob().add(finalCommand.toString()).to(stdout, stderr).enqueue()
future.get() val completableFuture = CompletableFuture.supplyAsync {
} future.get()
}
completableFuture.thenAccept { result ->
val emitExitCode = completableFuture.thenAccept { result ->
"javascript: (function() { try { ${callbackFunc}.emit('exit', ${result.code}); } catch(e) { console.error(`emitExit error: \${e}`); } })();" val emitExitCode =
webView.post { "javascript: (function() { try { ${callbackFunc}.emit('exit', ${result.code}); } catch(e) { console.error(`emitExit error: \${e}`); } })();"
webView.loadUrl(emitExitCode) webView.post {
} webView.loadUrl(emitExitCode)
}
if (result.code != 0) {
val emitErrCode = if (result.code != 0) {
"javascript: (function() { try { var err = new Error(); err.exitCode = ${result.code}; err.message = ${ val emitErrCode =
JSONObject.quote( "javascript: (function() { try { var err = new Error(); err.exitCode = ${result.code}; err.message = ${
result.err.joinToString( JSONObject.quote(
"\n" result.err.joinToString(
) "\n"
) )
};${callbackFunc}.emit('error', err); } catch(e) { console.error('emitErr', e); } })();" )
webView.post { };${callbackFunc}.emit('error', err); } catch(e) { console.error('emitErr', e); } })();"
webView.loadUrl(emitErrCode) webView.post {
} webView.loadUrl(emitErrCode)
} }
}.whenComplete { _, _ -> }
runCatching { shell.close() } }.whenComplete { _, _ ->
} runCatching { shell.close() }
} }
}
@JavascriptInterface
fun toast(msg: String) { @JavascriptInterface
webView.post { fun toast(msg: String) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() webView.post {
} Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
} }
}
@JavascriptInterface
fun fullScreen(enable: Boolean) { @JavascriptInterface
if (context is Activity) { fun fullScreen(enable: Boolean) {
Handler(Looper.getMainLooper()).post { if (activity == null) return
if (enable) {
hideSystemUI(activity.window) try {
} else { Handler(Looper.getMainLooper()).post {
showSystemUI(activity.window) if (enable) {
} hideSystemUI(activity!!.window)
} } else {
} showSystemUI(activity!!.window)
} }
}
@JavascriptInterface } catch (e: Exception) {
fun moduleInfo(): String { Log.e("WebViewInterface", "fullScreen", e)
val moduleInfos = JSONArray(listModules()) }
val currentModuleInfo = JSONObject() }
currentModuleInfo.put("moduleDir", modDir)
val moduleId = File(modDir).getName() @JavascriptInterface
for (i in 0 until moduleInfos.length()) { fun moduleInfo(): String {
val currentInfo = moduleInfos.getJSONObject(i) val moduleInfos = JSONArray(listModules())
val currentModuleInfo = JSONObject()
if (currentInfo.getString("id") != moduleId) { currentModuleInfo.put("moduleDir", modDir.path)
continue val moduleId = modDir.getName()
} for (i in 0 until moduleInfos.length()) {
val currentInfo = moduleInfos.getJSONObject(i)
val keys = currentInfo.keys()
for (key in keys) { if (currentInfo.getString("id") != moduleId) {
currentModuleInfo.put(key, currentInfo.get(key)) continue
} }
break
} val keys = currentInfo.keys()
return currentModuleInfo.toString() for (key in keys) {
} currentModuleInfo.put(key, currentInfo.get(key))
}
// =================== KPM支持 ============================= break
}
@JavascriptInterface return currentModuleInfo.toString()
fun listAllKpm() : String { }
return listKpmModules()
} // =================== KPM支持 =============================
@JavascriptInterface @JavascriptInterface
fun controlKpm(name: String, args: String) : Int { fun listAllKpm(): String {
return controlKpmModule(name, args) return listKpmModules()
} }
}
@JavascriptInterface
fun hideSystemUI(window: Window) = fun controlKpm(name: String, args: String): Int {
WindowInsetsControllerCompat(window, window.decorView).let { controller -> return controlKpmModule(name, args)
controller.hide(WindowInsetsCompat.Type.systemBars()) }
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE }
}
private fun hideSystemUI(window: Window) =
fun showSystemUI(window: Window) = WindowInsetsControllerCompat(window, window.decorView).let { controller ->
WindowInsetsControllerCompat(window, window.decorView).show(WindowInsetsCompat.Type.systemBars()) controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
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 showDpiConfirmDialog by remember { mutableStateOf(false) }
var showImageEditor 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 isDynamicSignEnabled by remember { mutableStateOf(false) }
var dynamicSignSize by remember { mutableStateOf("") } var dynamicSignSize by remember { mutableStateOf("") }
var dynamicSignHash by remember { mutableStateOf("") } var dynamicSignHash by remember { mutableStateOf("") }
@@ -674,8 +674,8 @@ fun MoreSettingsScreen(
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
// 初始化动态管理器配置 // 初始化动态签名配置
dynamicSignConfig = Natives.getDynamicManager() dynamicSignConfig = Natives.getDynamicSign()
dynamicSignConfig?.let { config -> dynamicSignConfig?.let { config ->
if (config.isValid()) { if (config.isValid()) {
isDynamicSignEnabled = true isDynamicSignEnabled = true
@@ -696,11 +696,11 @@ fun MoreSettingsScreen(
} }
} }
// 动态管理器配置对话框 // 动态签名配置对话框
if (showDynamicSignDialog) { if (showDynamicSignDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { showDynamicSignDialog = false }, onDismissRequest = { showDynamicSignDialog = false },
title = { Text(stringResource(R.string.dynamic_manager_title)) }, title = { Text(stringResource(R.string.dynamic_sign_title)) },
text = { text = {
Column( Column(
modifier = Modifier.verticalScroll(rememberScrollState()) modifier = Modifier.verticalScroll(rememberScrollState())
@@ -718,7 +718,7 @@ fun MoreSettingsScreen(
onCheckedChange = { isDynamicSignEnabled = it } onCheckedChange = { isDynamicSignEnabled = it }
) )
Spacer(modifier = Modifier.width(12.dp)) 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)) Spacer(modifier = Modifier.height(16.dp))
@@ -773,18 +773,18 @@ fun MoreSettingsScreen(
if (isDynamicSignEnabled) { if (isDynamicSignEnabled) {
val size = parseDynamicSignSize(dynamicSignSize) val size = parseDynamicSignSize(dynamicSignSize)
if (size != null && size > 0 && dynamicSignHash.length == 64) { if (size != null && size > 0 && dynamicSignHash.length == 64) {
val success = Natives.setDynamicManager(size, dynamicSignHash) val success = Natives.setDynamicSign(size, dynamicSignHash)
if (success) { if (success) {
dynamicSignConfig = Natives.DynamicManagerConfig(size, dynamicSignHash) dynamicSignConfig = Natives.DynamicSignConfig(size, dynamicSignHash)
Toast.makeText( Toast.makeText(
context, context,
context.getString(R.string.dynamic_manager_set_success), context.getString(R.string.dynamic_sign_set_success),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} else { } else {
Toast.makeText( Toast.makeText(
context, context,
context.getString(R.string.dynamic_manager_set_failed), context.getString(R.string.dynamic_sign_set_failed),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
@@ -797,20 +797,20 @@ fun MoreSettingsScreen(
return@Button return@Button
} }
} else { } else {
val success = Natives.clearDynamicManager() val success = Natives.clearDynamicSign()
if (success) { if (success) {
dynamicSignConfig = null dynamicSignConfig = null
dynamicSignSize = "" dynamicSignSize = ""
dynamicSignHash = "" dynamicSignHash = ""
Toast.makeText( Toast.makeText(
context, context,
context.getString(R.string.dynamic_manager_disabled_success), context.getString(R.string.dynamic_sign_disabled_success),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} else { } else {
Toast.makeText( Toast.makeText(
context, context,
context.getString(R.string.dynamic_manager_clear_failed), context.getString(R.string.dynamic_sign_clear_failed),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
return@Button 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( SettingItem(
icon = Icons.Filled.Security, icon = Icons.Filled.Security,
title = stringResource(R.string.dynamic_manager_title), title = stringResource(R.string.dynamic_sign_title),
subtitle = if (isDynamicSignEnabled) { subtitle = if (isDynamicSignEnabled) {
stringResource( stringResource(
R.string.dynamic_manager_enabled_summary, R.string.dynamic_sign_enabled_summary,
dynamicSignSize dynamicSignSize
) )
} else { } else {
stringResource(R.string.dynamic_manager_disabled) stringResource(R.string.dynamic_sign_disabled)
}, },
onClick = { showDynamicSignDialog = true } onClick = { showDynamicSignDialog = true }
) )

View File

@@ -570,19 +570,19 @@
<string name="selected_apps_count">%1$d aplikasi dipilih</string> <string name="selected_apps_count">%1$d aplikasi dipilih</string>
<string name="already_added_apps_count">%1$d aplikasi sudah ditambahkan</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="all_apps_already_added">Semua aplikasi telah ditambahkan</string>
<string name="dynamic_manager_title">Konfigurasi Tanda Tangan Dinamis</string> <string name="dynamic_sign_title">Konfigurasi Tanda Tangan Dinamis</string>
<string name="dynamic_manager_enabled_summary">Diaktifkan (Ukuran: %s)</string> <string name="dynamic_sign_enabled_summary">Diaktifkan (Ukuran: %s)</string>
<string name="dynamic_manager_disabled">Dinonaktifkan</string> <string name="dynamic_sign_disabled">Dinonaktifkan</string>
<string name="enable_dynamic_manager">Aktifkan Tanda Tangan Dinamis</string> <string name="enable_dynamic_sign">Aktifkan Tanda Tangan Dinamis</string>
<string name="signature_size">Ukuran Tanda Tangan</string> <string name="signature_size">Ukuran Tanda Tangan</string>
<string name="signature_hash">Hash 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="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_sign_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_failed">Gagal mengatur konfigurasi tanda tangan dinamis</string>
<string name="invalid_sign_config">Konfigurasi tanda tangan tidak valid</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_sign_disabled_success">Tanda tangan dinamis dinonaktifkan</string>
<string name="dynamic_manager_clear_failed">Gagal membersihkan tanda tangan dinamis</string> <string name="dynamic_sign_clear_failed">Gagal membersihkan tanda tangan dinamis</string>
<string name="dynamic_managerature">Dinamis</string> <string name="dynamic_signature">Dinamis</string>
<string name="signature_index">Tanda Tangan %1$d</string> <string name="signature_index">Tanda Tangan %1$d</string>
<string name="unknown_signature">Tidak diketahui</string> <string name="unknown_signature">Tidak diketahui</string>
<string name="multi_manager_list">Manajer Aktif</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="selected_apps_count">%1$d 個のアプリを選択済み</string>
<string name="already_added_apps_count">%1$d 個のアプリを追加済み</string> <string name="already_added_apps_count">%1$d 個のアプリを追加済み</string>
<string name="all_apps_already_added">すべてのアプリが追加されました</string> <string name="all_apps_already_added">すべてのアプリが追加されました</string>
<string name="dynamic_manager_title">動的な署名の構成</string> <string name="dynamic_sign_title">動的な署名の構成</string>
<string name="dynamic_manager_enabled_summary">有効 (サイズ: %s)</string> <string name="dynamic_sign_enabled_summary">有効 (サイズ: %s)</string>
<string name="dynamic_manager_disabled">無効</string> <string name="dynamic_sign_disabled">無効</string>
<string name="enable_dynamic_manager">動的な署名を有効化</string> <string name="enable_dynamic_sign">動的な署名を有効化</string>
<string name="signature_size">署名のサイズ</string> <string name="signature_size">署名のサイズ</string>
<string name="signature_hash">署名のハッシュ</string> <string name="signature_hash">署名のハッシュ</string>
<string name="hash_must_be_64_chars">ハッシュは 64 桁の 16 進数の文字列でなければなりません。</string> <string name="hash_must_be_64_chars">ハッシュは 64 桁の 16 進数の文字列でなければなりません。</string>
<string name="dynamic_manager_set_success">動的な署名の構成が正常に設定されました</string> <string name="dynamic_sign_set_success">動的な署名の構成が正常に設定されました</string>
<string name="dynamic_manager_set_failed">動的な署名の構成の設定に失敗しました</string> <string name="dynamic_sign_set_failed">動的な署名の構成の設定に失敗しました</string>
<string name="invalid_sign_config">無効な署名の構成</string> <string name="invalid_sign_config">無効な署名の構成</string>
<string name="dynamic_manager_disabled_success">動的な署名が無効です</string> <string name="dynamic_sign_disabled_success">動的な署名が無効です</string>
<string name="dynamic_manager_clear_failed">動的な署名の消去に失敗しました</string> <string name="dynamic_sign_clear_failed">動的な署名の消去に失敗しました</string>
<string name="dynamic_managerature">動的</string> <string name="dynamic_signature">動的</string>
<string name="signature_index">署名 %1$d</string> <string name="signature_index">署名 %1$d</string>
<string name="unknown_signature">不明</string> <string name="unknown_signature">不明</string>
<string name="multi_manager_list">有効なマネージャー</string> <string name="multi_manager_list">有効なマネージャー</string>

View File

@@ -570,19 +570,19 @@
<string name="selected_apps_count">Выбрано %1$d приложений</string> <string name="selected_apps_count">Выбрано %1$d приложений</string>
<string name="already_added_apps_count">%1$d приложений уже добавлено</string> <string name="already_added_apps_count">%1$d приложений уже добавлено</string>
<string name="all_apps_already_added">Все приложения были добавлены</string> <string name="all_apps_already_added">Все приложения были добавлены</string>
<string name="dynamic_manager_title">Конфигурация динамической подписи</string> <string name="dynamic_sign_title">Конфигурация динамической подписи</string>
<string name="dynamic_manager_enabled_summary">Включено (размер: %s)</string> <string name="dynamic_sign_enabled_summary">Включено (размер: %s)</string>
<string name="dynamic_manager_disabled">Выключено</string> <string name="dynamic_sign_disabled">Выключено</string>
<string name="enable_dynamic_manager">Включить динамическую подпись</string> <string name="enable_dynamic_sign">Включить динамическую подпись</string>
<string name="signature_size">Размер подписи</string> <string name="signature_size">Размер подписи</string>
<string name="signature_hash">Хэш подписи</string> <string name="signature_hash">Хэш подписи</string>
<string name="hash_must_be_64_chars">Хеш должен содержать 64 шестнадцатеричных символа</string> <string name="hash_must_be_64_chars">Хеш должен содержать 64 шестнадцатеричных символа</string>
<string name="dynamic_manager_set_success">Конфигурация динамической подписи успешно установлена</string> <string name="dynamic_sign_set_success">Конфигурация динамической подписи успешно установлена</string>
<string name="dynamic_manager_set_failed">Не удалось установить конфигурацию динамической подписи</string> <string name="dynamic_sign_set_failed">Не удалось установить конфигурацию динамической подписи</string>
<string name="invalid_sign_config">Неверная конфигурация подписи</string> <string name="invalid_sign_config">Неверная конфигурация подписи</string>
<string name="dynamic_manager_disabled_success">Динамическая подпись отключена</string> <string name="dynamic_sign_disabled_success">Динамическая подпись отключена</string>
<string name="dynamic_manager_clear_failed">Не удалось очистить динамическую подпись</string> <string name="dynamic_sign_clear_failed">Не удалось очистить динамическую подпись</string>
<string name="dynamic_managerature">Динамическая</string> <string name="dynamic_signature">Динамическая</string>
<string name="signature_index">Подпись %1$d</string> <string name="signature_index">Подпись %1$d</string>
<string name="unknown_signature">Неизвестно</string> <string name="unknown_signature">Неизвестно</string>
<string name="multi_manager_list">Активный менеджер</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="selected_apps_count">%1$d uygulama seçildi</string>
<string name="already_added_apps_count">%1$d uygulama zaten eklendi</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="all_apps_already_added">Tüm uygulamalar eklendi</string>
<string name="dynamic_manager_title">Dinamik İmza Yapılandırması</string> <string name="dynamic_sign_title">Dinamik İmza Yapılandırması</string>
<string name="dynamic_manager_enabled_summary">Etkin (Boyut: %s)</string> <string name="dynamic_sign_enabled_summary">Etkin (Boyut: %s)</string>
<string name="dynamic_manager_disabled">Devre Dışı</string> <string name="dynamic_sign_disabled">Devre Dışı</string>
<string name="enable_dynamic_manager">Dinamik İmzayı Etkinleştir</string> <string name="enable_dynamic_sign">Dinamik İmzayı Etkinleştir</string>
<string name="signature_size">İmza Boyutu</string> <string name="signature_size">İmza Boyutu</string>
<string name="signature_hash">İmza Hash</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="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_sign_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_failed">Dinamik imza yapılandırması ayarlanamadı</string>
<string name="invalid_sign_config">Geçersiz imza yapılandırması</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_sign_disabled_success">Dinamik imza devre dışı bırakıldı</string>
<string name="dynamic_manager_clear_failed">Dinamik imza temizlenemedi</string> <string name="dynamic_sign_clear_failed">Dinamik imza temizlenemedi</string>
<string name="dynamic_managerature">Dinamik</string> <string name="dynamic_signature">Dinamik</string>
<string name="signature_index">İmza %1$d</string> <string name="signature_index">İmza %1$d</string>
<string name="unknown_signature">Bilinmiyor</string> <string name="unknown_signature">Bilinmiyor</string>
<string name="multi_manager_list">Aktif Yönetici</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="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="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="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_sign_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_sign_enabled_summary">Đã kích hoạt (Size: %s)</string>
<string name="dynamic_manager_disabled">Đã vô hiệu hoá</string> <string name="dynamic_sign_disabled">Đã vô hiệu hoá</string>
<string name="enable_dynamic_manager">Kích hoạt chữ ký động</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_size">Size chữ ký</string>
<string name="signature_hash">Hash 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="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_sign_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_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="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_sign_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_sign_clear_failed">Xoá chữ ký động thất bại</string>
<string name="dynamic_managerature">Chữ ký động</string> <string name="dynamic_signature">Chữ ký động</string>
<string name="signature_index">Chữ ký %1$d</string> <string name="signature_index">Chữ ký %1$d</string>
<string name="unknown_signature">Không xác định</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> <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="selected_apps_count">%1$d 个已选应用</string>
<string name="already_added_apps_count">%1$d 个已添加应用</string> <string name="already_added_apps_count">%1$d 个已添加应用</string>
<string name="all_apps_already_added">所有应用均已添加</string> <string name="all_apps_already_added">所有应用均已添加</string>
<string name="dynamic_manager_title">动态管理器配置</string> <string name="dynamic_sign_title">动态签名配置</string>
<string name="dynamic_manager_enabled_summary">已启用(大小: %s</string> <string name="dynamic_sign_enabled_summary">已启用(大小: %s</string>
<string name="dynamic_manager_disabled">未启用</string> <string name="dynamic_sign_disabled">未启用</string>
<string name="enable_dynamic_manager">启用动态管理器</string> <string name="enable_dynamic_sign">启用动态签名</string>
<string name="signature_size">动态管理器签名大小</string> <string name="signature_size">签名大小</string>
<string name="signature_hash">动态管理器签名哈希值</string> <string name="signature_hash">签名哈希值</string>
<string name="hash_must_be_64_chars">哈希值必须是 64 位十六进制字符</string> <string name="hash_must_be_64_chars">哈希值必须是 64 位十六进制字符</string>
<string name="dynamic_manager_set_success">动态管理器配置设置成功</string> <string name="dynamic_sign_set_success">动态签名配置设置成功</string>
<string name="dynamic_manager_set_failed">动态管理器配置设置失败</string> <string name="dynamic_sign_set_failed">动态签名配置设置失败</string>
<string name="invalid_sign_config">无效的签名配置</string> <string name="invalid_sign_config">无效的签名配置</string>
<string name="dynamic_manager_disabled_success">动态管理器已禁用</string> <string name="dynamic_sign_disabled_success">动态签名已禁用</string>
<string name="dynamic_manager_clear_failed">清除动态管理器错误</string> <string name="dynamic_sign_clear_failed">清除动态签名错误</string>
<string name="dynamic_managerature">动态</string> <string name="dynamic_signature">动态</string>
<string name="signature_index">签名 %1$d</string> <string name="signature_index">签名 %1$d</string>
<string name="unknown_signature">未知</string> <string name="unknown_signature">未知</string>
<string name="multi_manager_list">活跃管理器</string> <string name="multi_manager_list">活跃管理器</string>

View File

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

View File

@@ -568,19 +568,19 @@
<string name="selected_apps_count">%1$d 個已選應用程式</string> <string name="selected_apps_count">%1$d 個已選應用程式</string>
<string name="already_added_apps_count">%1$d 個已添加應用程式</string> <string name="already_added_apps_count">%1$d 個已添加應用程式</string>
<string name="all_apps_already_added">所有应用均已添加</string> <string name="all_apps_already_added">所有应用均已添加</string>
<string name="dynamic_manager_title">动态签名配置</string> <string name="dynamic_sign_title">动态签名配置</string>
<string name="dynamic_manager_enabled_summary">已启用(大小: %s</string> <string name="dynamic_sign_enabled_summary">已启用(大小: %s</string>
<string name="dynamic_manager_disabled">未启用</string> <string name="dynamic_sign_disabled">未启用</string>
<string name="enable_dynamic_manager">啟用動態簽名</string> <string name="enable_dynamic_sign">啟用動態簽名</string>
<string name="signature_size">簽名大小</string> <string name="signature_size">簽名大小</string>
<string name="signature_hash">簽名哈希值</string> <string name="signature_hash">簽名哈希值</string>
<string name="hash_must_be_64_chars">哈希值必須是 64 位十六進制字符</string> <string name="hash_must_be_64_chars">哈希值必須是 64 位十六進制字符</string>
<string name="dynamic_manager_set_success">動態簽名配置設定成功</string> <string name="dynamic_sign_set_success">動態簽名配置設定成功</string>
<string name="dynamic_manager_set_failed">動態簽名配置設定失敗</string> <string name="dynamic_sign_set_failed">動態簽名配置設定失敗</string>
<string name="invalid_sign_config">無效的簽名配置</string> <string name="invalid_sign_config">無效的簽名配置</string>
<string name="dynamic_manager_disabled_success">動態簽名已禁用</string> <string name="dynamic_sign_disabled_success">動態簽名已禁用</string>
<string name="dynamic_manager_clear_failed">清除動態簽名錯誤</string> <string name="dynamic_sign_clear_failed">清除動態簽名錯誤</string>
<string name="dynamic_managerature">動態</string> <string name="dynamic_signature">動態</string>
<string name="signature_index">簽名 %1$d</string> <string name="signature_index">簽名 %1$d</string>
<string name="unknown_signature">未知</string> <string name="unknown_signature">未知</string>
<string name="multi_manager_list">活躍管理器</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="selected_apps_count">%1$d apps selected</string>
<string name="already_added_apps_count">%1$d apps already added</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="all_apps_already_added">All apps have been added</string>
<string name="dynamic_manager_title">Dynamic Manager Configuration</string> <string name="dynamic_sign_title">Dynamic Signature Configuration</string>
<string name="dynamic_manager_enabled_summary">Enabled (Size: %s)</string> <string name="dynamic_sign_enabled_summary">Enabled (Size: %s)</string>
<string name="dynamic_manager_disabled">Disabled</string> <string name="dynamic_sign_disabled">Disabled</string>
<string name="enable_dynamic_manager">Enable Dynamic Manager</string> <string name="enable_dynamic_sign">Enable Dynamic Signature</string>
<string name="signature_size">Dynamic Manager Signature Size</string> <string name="signature_size">Signature Size</string>
<string name="signature_hash">Dynamic Manager Signature Hash</string> <string name="signature_hash">Signature Hash</string>
<string name="hash_must_be_64_chars">Hash must be 64 hexadecimal characters</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_sign_set_success">Dynamic signature configuration set successfully</string>
<string name="dynamic_manager_set_failed">Failed to set dynamic Manager configuration</string> <string name="dynamic_sign_set_failed">Failed to set dynamic signature configuration</string>
<string name="invalid_sign_config">Invalid Manager configuration</string> <string name="invalid_sign_config">Invalid signature configuration</string>
<string name="dynamic_manager_disabled_success">Dynamic Manager disabled</string> <string name="dynamic_sign_disabled_success">Dynamic signature disabled</string>
<string name="dynamic_manager_clear_failed">Failed to clear dynamic Manager</string> <string name="dynamic_sign_clear_failed">Failed to clear dynamic signature</string>
<string name="dynamic_managerature">Dynamic</string> <string name="dynamic_signature">Dynamic</string>
<string name="signature_index">Signature %1$d</string> <string name="signature_index">Signature %1$d</string>
<string name="unknown_signature">Unknown</string> <string name="unknown_signature">Unknown</string>
<string name="multi_manager_list">Active Manager</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-material3 = "1.3.2"
compose-ui = "1.8.3" compose-ui = "1.8.3"
documentfile = "1.1.0" documentfile = "1.1.0"
mmrl = "2bb00b3c2b" mmrl = "v33953"
webui-x-portable = "953fad192a"
swiperefreshlayout = "1.1.0"
[plugins] [plugins]
agp-app = { id = "com.android.application", version.ref = "agp" } 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-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-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" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } 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" } lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "29.0.13599879-beta2" }
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
mmrl-webui-portable = { group = "com.github.MMRLApp.WebUI-X-Portable", name = "webui", version.ref = "webui-x-portable" }
mmrl-webui = { group = "com.github.MMRLApp.MMRL", name = "webui", version.ref = "mmrl" } 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-platform = { group = "com.github.MMRLApp.MMRL", name = "platform", version.ref = "mmrl" }
mmrl-ui = { group = "com.github.MMRLApp.MMRL", name = "ui", 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" } mmrl-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" }

Binary file not shown.

Binary file not shown.