From ddbbeafc64bf73e0bcedb2b632fb6cd59e207f6e Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sat, 21 Jun 2025 23:39:19 +0800 Subject: [PATCH] manager: Added the SUS Mount Hide Control feature added in SuSFS version 1.5.8 --- .../com/sukisu/ultra/ui/screen/SuSFSConfig.kt | 19 +++ .../extensions/SuSFSConfigExtensions.kt | 139 +++++++++++++++++- .../com/sukisu/ultra/ui/util/SuSFSManager.kt | 65 +++++++- .../ultra/ui/util/SuSFSModuleScripts.kt | 48 ++++-- .../src/main/res/values-zh-rCN/strings.xml | 12 ++ manager/app/src/main/res/values/strings.xml | 12 ++ 6 files changed, 276 insertions(+), 19 deletions(-) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt index 3260ce5e..ebd2e8df 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt @@ -138,6 +138,9 @@ fun SuSFSConfigScreen( var androidDataPath by remember { mutableStateOf("") } var sdcardPath by remember { mutableStateOf("") } + // SUS挂载隐藏控制状态 + var hideSusMountsForAllProcs by remember { mutableStateOf(true) } + // Kstat配置相关状态 var kstatConfigs by remember { mutableStateOf(emptySet()) } var addKstatPaths by remember { mutableStateOf(emptySet()) } @@ -223,6 +226,7 @@ fun SuSFSConfigScreen( sdcardPath = SuSFSManager.getSdcardPath(context) kstatConfigs = SuSFSManager.getKstatConfigs(context) addKstatPaths = SuSFSManager.getAddKstatPaths(context) + hideSusMountsForAllProcs = SuSFSManager.getHideSusMountsForAllProcs(context) // 加载槽位信息 loadSlotInfo() @@ -1533,8 +1537,13 @@ fun SuSFSConfigScreen( ) } SuSFSTab.SUS_MOUNTS -> { + // 检查版本支持 + val isSusMountHidingSupported = remember { SuSFSManager.isSusMountHidingSupported() } + SusMountsContent( susMounts = susMounts, + hideSusMountsForAllProcs = hideSusMountsForAllProcs, + isSusMountHidingSupported = isSusMountHidingSupported, isLoading = isLoading, onAddMount = { showAddMountDialog = true }, onRemoveMount = { mount -> @@ -1545,9 +1554,19 @@ fun SuSFSConfigScreen( } isLoading = false } + }, + onToggleHideSusMountsForAllProcs = { hideForAll -> + coroutineScope.launch { + isLoading = true + if (SuSFSManager.setHideSusMountsForAllProcs(context, hideForAll)) { + hideSusMountsForAllProcs = hideForAll + } + isLoading = false + } } ) } + SuSFSTab.TRY_UMOUNT -> { TryUmountContent( tryUmounts = tryUmounts, diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/extensions/SuSFSConfigExtensions.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/extensions/SuSFSConfigExtensions.kt index a68d8c31..39133271 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/extensions/SuSFSConfigExtensions.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/extensions/SuSFSConfigExtensions.kt @@ -22,6 +22,8 @@ import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Storage import androidx.compose.material.icons.filled.Update import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card @@ -49,6 +51,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.sukisu.ultra.R import com.sukisu.ultra.ui.util.SuSFSManager import kotlinx.coroutines.launch @@ -551,15 +554,134 @@ fun SusPathsContent( } } +/** + * SUS挂载隐藏控制卡片组件 + */ +@Composable +fun SusMountHidingControlCard( + hideSusMountsForAllProcs: Boolean, + isLoading: Boolean, + onToggleHiding: (Boolean) -> Unit +) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface + ), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + // 标题行 + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = if (hideSusMountsForAllProcs) Icons.Default.VisibilityOff else Icons.Default.Visibility, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.susfs_hide_mounts_control_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + } + + // 描述文本 + Text( + text = stringResource(R.string.susfs_hide_mounts_control_description), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + lineHeight = 16.sp + ) + + // 控制开关行 + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = stringResource(R.string.susfs_hide_mounts_for_all_procs_label), + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = if (hideSusMountsForAllProcs) { + stringResource(R.string.susfs_hide_mounts_for_all_procs_enabled_description) + } else { + stringResource(R.string.susfs_hide_mounts_for_all_procs_disabled_description) + }, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + lineHeight = 14.sp + ) + } + Switch( + checked = hideSusMountsForAllProcs, + onCheckedChange = onToggleHiding, + enabled = !isLoading + ) + } + + // 当前设置显示 + Text( + text = stringResource( + R.string.susfs_hide_mounts_current_setting, + if (hideSusMountsForAllProcs) { + stringResource(R.string.susfs_hide_mounts_setting_all) + } else { + stringResource(R.string.susfs_hide_mounts_setting_non_ksu) + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Medium + ) + + // 建议文本 + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f) + ), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = stringResource(R.string.susfs_hide_mounts_recommendation), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + lineHeight = 14.sp, + modifier = Modifier.padding(12.dp) + ) + } + } + } +} + /** * SUS挂载内容组件 */ @Composable fun SusMountsContent( susMounts: Set, + hideSusMountsForAllProcs: Boolean, + isSusMountHidingSupported: Boolean, isLoading: Boolean, onAddMount: () -> Unit, - onRemoveMount: (String) -> Unit + onRemoveMount: (String) -> Unit, + onToggleHideSusMountsForAllProcs: (Boolean) -> Unit ) { Column( modifier = Modifier.fillMaxWidth(), @@ -589,6 +711,14 @@ fun SusMountsContent( } ) + // SUS挂载隐藏控制卡片 - 仅在支持的版本显示 + if (isSusMountHidingSupported) { + SusMountHidingControlCard( + hideSusMountsForAllProcs = hideSusMountsForAllProcs, + isLoading = isLoading, + onToggleHiding = onToggleHideSusMountsForAllProcs + ) + } if (susMounts.isEmpty()) { EmptyStateCard( message = stringResource(R.string.susfs_no_mounts_configured) @@ -611,6 +741,7 @@ fun SusMountsContent( } } + /** * 尝试卸载内容组件 */ @@ -801,7 +932,7 @@ fun KstatConfigContent( } } -// 静态Kstat配置列表 + // 静态Kstat配置列表 if (kstatConfigs.isNotEmpty()) { Text( text = stringResource(R.string.static_kstat_config), @@ -822,7 +953,7 @@ fun KstatConfigContent( } } -// Add Kstat路径列表 + // Add Kstat路径列表 if (addKstatPaths.isNotEmpty()) { Text( text = stringResource(R.string.kstat_path_management), @@ -845,7 +976,7 @@ fun KstatConfigContent( } } -// 空状态显示 + // 空状态显示 if (kstatConfigs.isEmpty() && addKstatPaths.isEmpty()) { EmptyStateCard( message = stringResource(R.string.no_kstat_config_message) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt index 23df0c16..c779c4cd 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt @@ -35,6 +35,7 @@ object SuSFSManager { private const val KEY_EXECUTE_IN_POST_FS_DATA = "execute_in_post_fs_data" private const val KEY_KSTAT_CONFIGS = "kstat_configs" private const val KEY_ADD_KSTAT_PATHS = "add_kstat_paths" + private const val KEY_HIDE_SUS_MOUNTS_FOR_ALL_PROCS = "hide_sus_mounts_for_all_procs" // 常量 private const val SUSFS_BINARY_BASE_NAME = "ksu_susfs" private const val DEFAULT_UNAME = "default" @@ -77,8 +78,37 @@ object SuSFSManager { return CommandResult(result.isSuccess, result.out.joinToString("\n"), result.err.joinToString("\n")) } + // 版本比较 + private fun compareVersions(version1: String, version2: String): Int { + val v1Parts = version1.removePrefix("v").split(".").map { it.toIntOrNull() ?: 0 } + val v2Parts = version2.removePrefix("v").split(".").map { it.toIntOrNull() ?: 0 } - // 配置存取方法 + val maxLength = maxOf(v1Parts.size, v2Parts.size) + + for (i in 0 until maxLength) { + val v1Part = v1Parts.getOrNull(i) ?: 0 + val v2Part = v2Parts.getOrNull(i) ?: 0 + + when { + v1Part > v2Part -> return 1 + v1Part < v2Part -> return -1 + } + } + return 0 + } + + // 检查当前SuSFS版本是否支持SUS挂载隐藏控制功能 + fun isSusMountHidingSupported(): Boolean { + return try { + val currentVersion = getSuSFSVersion() + compareVersions(currentVersion, "1.5.8") >= 0 + } catch (_: Exception) { + true + } + } + + + // 配置存取方法 fun saveUnameValue(context: Context, value: String) = getPrefs(context).edit { putString(KEY_UNAME_VALUE, value) } @@ -119,6 +149,13 @@ object SuSFSManager { } } + // SUS挂载隐藏控制 + fun saveHideSusMountsForAllProcs(context: Context, hideForAll: Boolean) = + getPrefs(context).edit { putBoolean(KEY_HIDE_SUS_MOUNTS_FOR_ALL_PROCS, hideForAll) } + + fun getHideSusMountsForAllProcs(context: Context): Boolean = + getPrefs(context).getBoolean(KEY_HIDE_SUS_MOUNTS_FOR_ALL_PROCS, true) + // 路径和配置管理 fun saveSusPaths(context: Context, paths: Set) = getPrefs(context).edit { putStringSet(KEY_SUS_PATHS, paths) } @@ -304,7 +341,8 @@ object SuSFSManager { "sdcardPath" to getSdcardPath(context), "enableLog" to getEnableLogState(context), "kstatConfigs" to getKstatConfigs(context), - "addKstatPaths" to getAddKstatPaths(context) + "addKstatPaths" to getAddKstatPaths(context), + "hideSusMountsForAllProcs" to getHideSusMountsForAllProcs(context) ) // 生成脚本 @@ -323,7 +361,9 @@ object SuSFSManager { "post-mount.sh" to ScriptGenerator.generatePostMountScript( targetPath, config.getSetSafe("susMounts"), config.getSetSafe("tryUmounts") ), - "boot-completed.sh" to ScriptGenerator.generateBootCompletedScript(targetPath) + "boot-completed.sh" to ScriptGenerator.generateBootCompletedScript( + targetPath, config["hideSusMountsForAllProcs"] as Boolean + ) ) // 创建脚本文件 @@ -395,6 +435,25 @@ object SuSFSManager { return success } + // SUS挂载隐藏控制 + suspend fun setHideSusMountsForAllProcs(context: Context, hideForAll: Boolean): Boolean { + if (!isSusMountHidingSupported()) { + return false + } + + val success = executeSusfsCommand(context, "hide_sus_mnts_for_all_procs ${if (hideForAll) 1 else 0}") + if (success) { + saveHideSusMountsForAllProcs(context, hideForAll) + if (isAutoStartEnabled(context)) createMagiskModule(context) + showToast(context, if (hideForAll) + context.getString(R.string.susfs_hide_mounts_all_enabled) + else + context.getString(R.string.susfs_hide_mounts_all_disabled) + ) + } + return success + } + // uname和构建时间 @SuppressLint("StringFormatMatches") suspend fun setUname(context: Context, unameValue: String, buildTimeValue: String): Boolean { diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt index fa1b0379..ab43d58d 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt @@ -134,6 +134,16 @@ object ScriptGenerator { kstatConfigs: Set, addKstatPaths: Set ) { + // 添加Kstat路径 + if (addKstatPaths.isNotEmpty()) { + appendLine("# 添加Kstat路径") + addKstatPaths.forEach { path -> + appendLine("\"${'$'}SUSFS_BIN\" add_sus_kstat '$path'") + appendLine("echo \"$(get_current_time): 添加Kstat路径: $path\" >> \"${'$'}LOG_FILE\"") + } + appendLine() + } + // 添加Kstat静态配置 if (kstatConfigs.isNotEmpty()) { appendLine("# 添加Kstat静态配置") @@ -142,19 +152,12 @@ object ScriptGenerator { if (parts.size >= 13) { val path = parts[0] val params = parts.drop(1).joinToString("' '", "'", "'") - appendLine("\"${'$'}SUSFS_BIN\" add_sus_kstat_statically $params") + appendLine("\"${'$'}SUSFS_BIN\" add_sus_kstat_statically '$path' $params") appendLine("echo \"$(get_current_time): 添加Kstat静态配置: $path\" >> \"${'$'}LOG_FILE\"") - } - } - appendLine() - } - // 添加Kstat路径 - if (addKstatPaths.isNotEmpty()) { - appendLine("# 添加Kstat路径") - addKstatPaths.forEach { path -> - appendLine("\"${'$'}SUSFS_BIN\" add_sus_kstat '$path'") - appendLine("echo \"$(get_current_time): 添加Kstat路径: $path\" >> \"${'$'}LOG_FILE\"") + appendLine("\"${'$'}SUSFS_BIN\" update_sus_kstat '$path'") + appendLine("echo \"$(get_current_time): 更新Kstat配置: $path\" >> \"${'$'}LOG_FILE\"") + } } appendLine() } @@ -351,7 +354,10 @@ object ScriptGenerator { /** * 生成boot-completed.sh脚本内容 */ - fun generateBootCompletedScript(targetPath: String): String { + fun generateBootCompletedScript( + targetPath: String, + hideSusMountsForAllProcs: Boolean = true + ): String { return buildString { appendLine("#!/system/bin/sh") appendLine("# SuSFS Boot-Completed Script") @@ -363,6 +369,24 @@ object ScriptGenerator { appendLine() appendLine(generateBinaryCheck(targetPath)) appendLine() + + // SUS挂载隐藏控制仅限1.5.8及以上版本 + appendLine("# 设置SUS挂载隐藏控制(仅限1.5.8及以上版本)") + appendLine("SUSFS_VERSION=$(${'$'}SUSFS_BIN show version 2>/dev/null | grep -o 'v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+' | head -1)") + appendLine("if [ -n \"${'$'}SUSFS_VERSION\" ]; then") + appendLine(" VERSION_NUM=$(echo \"${'$'}SUSFS_VERSION\" | sed 's/v//' | awk -F. '{printf \"%d%02d%02d\", $1, $2, $3}')") + appendLine(" if [ \"${'$'}VERSION_NUM\" -ge 10508 ]; then") + val hideValue = if (hideSusMountsForAllProcs) 1 else 0 + appendLine(" \"${'$'}SUSFS_BIN\" hide_sus_mnts_for_all_procs $hideValue") + appendLine(" echo \"$(get_current_time): SUS挂载隐藏控制设置为: ${if (hideSusMountsForAllProcs) "对所有进程隐藏" else "仅对非KSU进程隐藏"}\" >> \"${'$'}LOG_FILE\"") + appendLine(" else") + appendLine(" echo \"$(get_current_time): 当前版本 ${'$'}SUSFS_VERSION 不支持SUS挂载隐藏控制功能,需要1.5.8及以上版本\" >> \"${'$'}LOG_FILE\"") + appendLine(" fi") + appendLine("else") + appendLine(" echo \"$(get_current_time): 无法获取SuSFS版本信息,跳过SUS挂载隐藏控制设置\" >> \"${'$'}LOG_FILE\"") + appendLine("fi") + appendLine() + appendLine("echo \"$(get_current_time): Boot-Completed脚本执行完成\" >> \"${'$'}LOG_FILE\"") } } diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index cccbf149..9460feac 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -509,4 +509,16 @@ 静态 Kstat 配置 Kstat 路径管理 暂无 Kstat 配置,点击上方按钮添加配置 + + SUS挂载隐藏控制 + 控制SUS挂载对进程的隐藏行为 + 对所有进程隐藏SUS挂载 + 启用后,SUS挂载将对所有进程隐藏,包括KSU进程 + 禁用后,SUS挂载仅对非KSU进程隐藏,KSU进程可以看到挂载 + 已启用对所有进程隐藏SUS挂载 + 已禁用对所有进程隐藏SUS挂载 + 建议在屏幕解锁后或在service.sh或boot-completed.sh阶段设置为禁用,这可以修复一些依赖KSU进程挂载的root应用的问题 + 当前设置: %s + 对所有进程隐藏 + 仅对非KSU进程隐藏 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 521056ff..0683b57c 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -511,4 +511,16 @@ Static Kstat Configuration Kstat Path Management No Kstat configuration yet, click the button above to add + + SUS Mount Hiding Control + Control the hiding behavior of SUS mounts for processes + Hide SUS mounts for all processes + When enabled, SUS mounts will be hidden from all processes, including KSU processes + When disabled, SUS mounts will only be hidden from non-KSU processes, KSU processes can see the mounts + Enabled hiding SUS mounts for all processes + Disabled hiding SUS mounts for all processes + It is recommended to set to disabled after screen is unlocked, or during service.sh or boot-completed.sh stage, as this should fix the issue on some rooted apps that rely on mounts mounted by KSU process + Current setting: %s + Hide for all processes + Hide only for non-KSU processes \ No newline at end of file