manager: Add SuSFS Kstat-related configuration

This commit is contained in:
ShirkNeko
2025-06-19 11:26:15 +08:00
parent 2ff3b5ee06
commit c83baad6d5
6 changed files with 1101 additions and 37 deletions

View File

@@ -82,6 +82,7 @@ import com.sukisu.ultra.ui.screen.extensions.FeatureStatusCard
import com.sukisu.ultra.ui.screen.extensions.SusPathsContent
import com.sukisu.ultra.ui.screen.extensions.SusMountsContent
import com.sukisu.ultra.ui.screen.extensions.TryUmountContent
import com.sukisu.ultra.ui.screen.extensions.KstatConfigContent
import kotlinx.coroutines.launch
/**
@@ -92,6 +93,7 @@ enum class SuSFSTab(val displayNameRes: Int) {
SUS_PATHS(R.string.susfs_tab_sus_paths),
SUS_MOUNTS(R.string.susfs_tab_sus_mounts),
TRY_UMOUNT(R.string.susfs_tab_try_umount),
KSTAT_CONFIG(R.string.susfs_tab_kstat_config),
PATH_SETTINGS(R.string.susfs_tab_path_settings),
ENABLED_FEATURES(R.string.susfs_tab_enabled_features);
@@ -136,6 +138,10 @@ fun SuSFSConfigScreen(
var androidDataPath by remember { mutableStateOf("") }
var sdcardPath by remember { mutableStateOf("") }
// Kstat配置相关状态
var kstatConfigs by remember { mutableStateOf(emptySet<String>()) }
var addKstatPaths by remember { mutableStateOf(emptySet<String>()) }
// 启用功能状态相关
var enabledFeatures by remember { mutableStateOf(emptyList<SuSFSManager.EnabledFeature>()) }
var isLoadingFeatures by remember { mutableStateOf(false) }
@@ -151,14 +157,32 @@ fun SuSFSConfigScreen(
var newUmountMode by remember { mutableIntStateOf(0) }
var umountModeExpanded by remember { mutableStateOf(false) }
// Kstat配置对话框状态
var showAddKstatStaticallyDialog by remember { mutableStateOf(false) }
var showAddKstatDialog by remember { mutableStateOf(false) }
var newKstatPath by remember { mutableStateOf("") }
var newKstatIno by remember { mutableStateOf("") }
var newKstatDev by remember { mutableStateOf("") }
var newKstatNlink by remember { mutableStateOf("") }
var newKstatSize by remember { mutableStateOf("") }
var newKstatAtime by remember { mutableStateOf("") }
var newKstatAtimeNsec by remember { mutableStateOf("") }
var newKstatMtime by remember { mutableStateOf("") }
var newKstatMtimeNsec by remember { mutableStateOf("") }
var newKstatCtime by remember { mutableStateOf("") }
var newKstatCtimeNsec by remember { mutableStateOf("") }
var newKstatBlocks by remember { mutableStateOf("") }
var newKstatBlksize by remember { mutableStateOf("") }
// 重置确认对话框状态
var showResetPathsDialog by remember { mutableStateOf(false) }
var showResetMountsDialog by remember { mutableStateOf(false) }
var showResetUmountsDialog by remember { mutableStateOf(false) }
var showResetKstatDialog by remember { mutableStateOf(false) }
val allTabs = SuSFSTab.getAllTabs()
// 实时判断是否可以启用开机自启动 - 修改逻辑
// 实时判断是否可以启用开机自启动
val canEnableAutoStart by remember {
derivedStateOf {
SuSFSManager.hasConfigurationForAutoStart(context)
@@ -197,6 +221,8 @@ fun SuSFSConfigScreen(
tryUmounts = SuSFSManager.getTryUmounts(context)
androidDataPath = SuSFSManager.getAndroidDataPath(context)
sdcardPath = SuSFSManager.getSdcardPath(context)
kstatConfigs = SuSFSManager.getKstatConfigs(context)
addKstatPaths = SuSFSManager.getAddKstatPaths(context)
// 加载槽位信息
loadSlotInfo()
@@ -581,6 +607,313 @@ fun SuSFSConfigScreen(
)
}
// 添加Kstat静态配置对话框
if (showAddKstatStaticallyDialog) {
AlertDialog(
onDismissRequest = { showAddKstatStaticallyDialog = false },
title = {
Text(
stringResource(R.string.add_kstat_statically_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
},
text = {
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = newKstatPath,
onValueChange = { newKstatPath = it },
label = { Text(stringResource(R.string.file_or_directory_path_label)) },
placeholder = { Text("/path/to/file_or_directory") },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = newKstatIno,
onValueChange = { newKstatIno = it },
label = { Text("ino") },
placeholder = { Text("1234") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
OutlinedTextField(
value = newKstatDev,
onValueChange = { newKstatDev = it },
label = { Text("dev") },
placeholder = { Text("1234") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = newKstatNlink,
onValueChange = { newKstatNlink = it },
label = { Text("nlink") },
placeholder = { Text("2") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
OutlinedTextField(
value = newKstatSize,
onValueChange = { newKstatSize = it },
label = { Text("size") },
placeholder = { Text("223344") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = newKstatAtime,
onValueChange = { newKstatAtime = it },
label = { Text("atime") },
placeholder = { Text("1712592355") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
OutlinedTextField(
value = newKstatAtimeNsec,
onValueChange = { newKstatAtimeNsec = it },
label = { Text("atime_nsec") },
placeholder = { Text("0") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = newKstatMtime,
onValueChange = { newKstatMtime = it },
label = { Text("mtime") },
placeholder = { Text("1712592355") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
OutlinedTextField(
value = newKstatMtimeNsec,
onValueChange = { newKstatMtimeNsec = it },
label = { Text("mtime_nsec") },
placeholder = { Text("0") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = newKstatCtime,
onValueChange = { newKstatCtime = it },
label = { Text("ctime") },
placeholder = { Text("1712592355") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
OutlinedTextField(
value = newKstatCtimeNsec,
onValueChange = { newKstatCtimeNsec = it },
label = { Text("ctime_nsec") },
placeholder = { Text("0") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = newKstatBlocks,
onValueChange = { newKstatBlocks = it },
label = { Text("blocks") },
placeholder = { Text("16") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
OutlinedTextField(
value = newKstatBlksize,
onValueChange = { newKstatBlksize = it },
label = { Text("blksize") },
placeholder = { Text("512") },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp)
)
}
Text(
text = stringResource(R.string.hint_use_default_value),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
},
confirmButton = {
Button(
onClick = {
if (newKstatPath.isNotBlank()) {
coroutineScope.launch {
isLoading = true
if (SuSFSManager.addKstatStatically(
context, newKstatPath.trim(),
newKstatIno.trim().ifBlank { "default" },
newKstatDev.trim().ifBlank { "default" },
newKstatNlink.trim().ifBlank { "default" },
newKstatSize.trim().ifBlank { "default" },
newKstatAtime.trim().ifBlank { "default" },
newKstatAtimeNsec.trim().ifBlank { "default" },
newKstatMtime.trim().ifBlank { "default" },
newKstatMtimeNsec.trim().ifBlank { "default" },
newKstatCtime.trim().ifBlank { "default" },
newKstatCtimeNsec.trim().ifBlank { "default" },
newKstatBlocks.trim().ifBlank { "default" },
newKstatBlksize.trim().ifBlank { "default" }
)) {
kstatConfigs = SuSFSManager.getKstatConfigs(context)
}
isLoading = false
// 清空所有字段
newKstatPath = ""
newKstatIno = ""
newKstatDev = ""
newKstatNlink = ""
newKstatSize = ""
newKstatAtime = ""
newKstatAtimeNsec = ""
newKstatMtime = ""
newKstatMtimeNsec = ""
newKstatCtime = ""
newKstatCtimeNsec = ""
newKstatBlocks = ""
newKstatBlksize = ""
showAddKstatStaticallyDialog = false
}
}
},
enabled = newKstatPath.isNotBlank() && !isLoading,
shape = RoundedCornerShape(8.dp)
) {
Text("添加")
}
},
dismissButton = {
TextButton(
onClick = {
showAddKstatStaticallyDialog = false
// 清空所有字段
newKstatPath = ""
newKstatIno = ""
newKstatDev = ""
newKstatNlink = ""
newKstatSize = ""
newKstatAtime = ""
newKstatAtimeNsec = ""
newKstatMtime = ""
newKstatMtimeNsec = ""
newKstatCtime = ""
newKstatCtimeNsec = ""
newKstatBlocks = ""
newKstatBlksize = ""
},
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.cancel))
}
},
shape = RoundedCornerShape(12.dp)
)
}
// 添加Kstat路径对话框
if (showAddKstatDialog) {
AlertDialog(
onDismissRequest = { showAddKstatDialog = false },
title = {
Text(
stringResource(R.string.add_kstat_path_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
},
text = {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
OutlinedTextField(
value = newKstatPath,
onValueChange = { newKstatPath = it },
label = { Text(stringResource(R.string.file_or_directory_path_label)) },
placeholder = { Text("/path/to/file_or_directory") },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp)
)
Text(
text = stringResource(R.string.kstat_command_description),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
},
confirmButton = {
Button(
onClick = {
if (newKstatPath.isNotBlank()) {
coroutineScope.launch {
isLoading = true
if (SuSFSManager.addKstat(context, newKstatPath.trim())) {
addKstatPaths = SuSFSManager.getAddKstatPaths(context)
}
isLoading = false
newKstatPath = ""
showAddKstatDialog = false
}
}
},
enabled = newKstatPath.isNotBlank() && !isLoading,
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.add))
}
},
dismissButton = {
TextButton(
onClick = {
showAddKstatDialog = false
newKstatPath = ""
},
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.cancel))
}
},
shape = RoundedCornerShape(12.dp)
)
}
// 运行尝试卸载确认对话框
if (showRunUmountDialog) {
AlertDialog(
@@ -762,6 +1095,55 @@ fun SuSFSConfigScreen(
)
}
// 重置Kstat配置确认对话框
if (showResetKstatDialog) {
AlertDialog(
onDismissRequest = { showResetKstatDialog = false },
title = {
Text(
stringResource(R.string.reset_kstat_config_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
},
text = { Text(stringResource(R.string.reset_kstat_config_message)) },
confirmButton = {
Button(
onClick = {
coroutineScope.launch {
isLoading = true
SuSFSManager.saveKstatConfigs(context, emptySet())
SuSFSManager.saveAddKstatPaths(context, emptySet())
kstatConfigs = emptySet()
addKstatPaths = emptySet()
if (SuSFSManager.isAutoStartEnabled(context)) {
SuSFSManager.configureAutoStart(context, true)
}
isLoading = false
showResetKstatDialog = false
}
},
enabled = !isLoading,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
),
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.confirm_reset))
}
},
dismissButton = {
TextButton(
onClick = { showResetKstatDialog = false },
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.cancel))
}
},
shape = RoundedCornerShape(12.dp)
)
}
// 重置确认对话框
if (showConfirmReset) {
AlertDialog(
@@ -985,6 +1367,29 @@ fun SuSFSConfigScreen(
}
}
SuSFSTab.KSTAT_CONFIG -> {
// 重置按钮
OutlinedButton(
onClick = { showResetKstatDialog = true },
enabled = !isLoading && (kstatConfigs.isNotEmpty() || addKstatPaths.isNotEmpty()),
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
) {
Icon(
imageVector = Icons.Default.RestoreFromTrash,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(6.dp))
Text(
stringResource(R.string.reset_kstat_config_title),
fontWeight = FontWeight.Medium
)
}
}
SuSFSTab.PATH_SETTINGS -> {
// 重置按钮
OutlinedButton(
@@ -1160,6 +1565,47 @@ fun SuSFSConfigScreen(
}
)
}
SuSFSTab.KSTAT_CONFIG -> {
KstatConfigContent(
kstatConfigs = kstatConfigs,
addKstatPaths = addKstatPaths,
isLoading = isLoading,
onAddKstatStatically = { showAddKstatStaticallyDialog = true },
onAddKstat = { showAddKstatDialog = true },
onRemoveKstatConfig = { config ->
coroutineScope.launch {
isLoading = true
if (SuSFSManager.removeKstatConfig(context, config)) {
kstatConfigs = SuSFSManager.getKstatConfigs(context)
}
isLoading = false
}
},
onRemoveAddKstat = { path ->
coroutineScope.launch {
isLoading = true
if (SuSFSManager.removeAddKstat(context, path)) {
addKstatPaths = SuSFSManager.getAddKstatPaths(context)
}
isLoading = false
}
},
onUpdateKstat = { path ->
coroutineScope.launch {
isLoading = true
SuSFSManager.updateKstat(context, path)
isLoading = false
}
},
onUpdateKstatFullClone = { path ->
coroutineScope.launch {
isLoading = true
SuSFSManager.updateKstatFullClone(context, path)
isLoading = false
}
}
)
}
SuSFSTab.PATH_SETTINGS -> {
PathSettingsContent(
androidDataPath = androidDataPath,

View File

@@ -20,6 +20,8 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Folder
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.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
@@ -175,6 +177,166 @@ fun PathItemCard(
}
}
/**
* Kstat配置项目卡片组件
*/
@Composable
fun KstatConfigItemCard(
config: String,
onDelete: () -> Unit,
isLoading: Boolean = false
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 1.dp),
shape = RoundedCornerShape(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Column {
val parts = config.split("|")
if (parts.isNotEmpty()) {
Text(
text = parts[0], // 路径
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Medium
)
if (parts.size > 1) {
Text(
text = "参数: ${parts.drop(1).joinToString(" ")}",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
} else {
Text(
text = config,
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Medium
)
}
}
}
IconButton(
onClick = onDelete,
enabled = !isLoading,
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(16.dp)
)
}
}
}
}
/**
* Add Kstat路径项目卡片组件
*/
@Composable
fun AddKstatPathItemCard(
path: String,
onDelete: () -> Unit,
onUpdate: () -> Unit,
onUpdateFullClone: () -> Unit,
isLoading: Boolean = false
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 1.dp),
shape = RoundedCornerShape(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Icon(
imageVector = Icons.Default.Folder,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = path,
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Medium
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
IconButton(
onClick = onUpdate,
enabled = !isLoading,
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Default.Update,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(16.dp)
)
}
IconButton(
onClick = onUpdateFullClone,
enabled = !isLoading,
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Default.PlayArrow,
contentDescription = null,
tint = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.size(16.dp)
)
}
IconButton(
onClick = onDelete,
enabled = !isLoading,
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(16.dp)
)
}
}
}
}
}
/**
* 启用功能状态卡片组件
*/
@@ -537,3 +699,157 @@ fun TryUmountContent(
}
}
}
/**
* Kstat配置内容组件
*/
@Composable
fun KstatConfigContent(
kstatConfigs: Set<String>,
addKstatPaths: Set<String>,
isLoading: Boolean,
onAddKstatStatically: () -> Unit,
onAddKstat: () -> Unit,
onRemoveKstatConfig: (String) -> Unit,
onRemoveAddKstat: (String) -> Unit,
onUpdateKstat: (String) -> Unit,
onUpdateKstatFullClone: (String) -> Unit
) {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// 标题和添加按钮
UnifiedButtonRow(
primaryButton = {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
FloatingActionButton(
onClick = onAddKstatStatically,
modifier = Modifier.size(48.dp),
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = null,
modifier = Modifier.size(20.dp)
)
}
FloatingActionButton(
onClick = onAddKstat,
modifier = Modifier.size(48.dp),
containerColor = MaterialTheme.colorScheme.secondary,
contentColor = MaterialTheme.colorScheme.onSecondary
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = null,
modifier = Modifier.size(20.dp)
)
}
}
},
secondaryButtons = {
Text(
text = stringResource(R.string.kstat_config_management),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
}
)
// 说明卡片
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
),
shape = RoundedCornerShape(12.dp)
) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(R.string.kstat_config_description_title),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary
)
Text(
text = stringResource(R.string.kstat_config_description_add_statically),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = stringResource(R.string.kstat_config_description_add),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = stringResource(R.string.kstat_config_description_update),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = stringResource(R.string.kstat_config_description_update_full_clone),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
// 静态Kstat配置列表
if (kstatConfigs.isNotEmpty()) {
Text(
text = stringResource(R.string.static_kstat_config),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
LazyColumn(
modifier = Modifier.weight(0.5f),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
items(kstatConfigs.toList()) { config ->
KstatConfigItemCard(
config = config,
onDelete = { onRemoveKstatConfig(config) },
isLoading = isLoading
)
}
}
}
// Add Kstat路径列表
if (addKstatPaths.isNotEmpty()) {
Text(
text = stringResource(R.string.kstat_path_management),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
LazyColumn(
modifier = Modifier.weight(0.5f),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
items(addKstatPaths.toList()) { path ->
AddKstatPathItemCard(
path = path,
onDelete = { onRemoveAddKstat(path) },
onUpdate = { onUpdateKstat(path) },
onUpdateFullClone = { onUpdateKstatFullClone(path) },
isLoading = isLoading
)
}
}
}
// 空状态显示
if (kstatConfigs.isEmpty() && addKstatPaths.isEmpty()) {
EmptyStateCard(
message = stringResource(R.string.no_kstat_config_message)
)
}
}
}

View File

@@ -8,7 +8,9 @@ import com.dergoogler.mmrl.platform.Platform.Companion.context
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.FileOutputStream
@@ -34,6 +36,8 @@ object SuSFSManager {
private const val KEY_SDCARD_PATH = "sdcard_path"
private const val KEY_ENABLE_LOG = "enable_log"
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 SUSFS_BINARY_BASE_NAME = "ksu_susfs"
private const val DEFAULT_UNAME = "default"
private const val DEFAULT_BUILD_TIME = "default"
@@ -198,13 +202,56 @@ object SuSFSManager {
}
/**
* 保存执行位置设置
* 保存执行位置设置并实时更新状态
*/
fun saveExecuteInPostFsData(context: Context, executeInPostFsData: Boolean) {
val oldExecuteInPostFsData = getExecuteInPostFsData(context)
getPrefs(context).edit().apply {
putBoolean(KEY_EXECUTE_IN_POST_FS_DATA, executeInPostFsData)
apply()
}
if (oldExecuteInPostFsData != executeInPostFsData && isAutoStartEnabled(context)) {
CoroutineScope(Dispatchers.Default).launch {
try {
// 删除旧的模块状态
removeMagiskModule()
// 重新创建模块以应用新的执行位置设置
val success = createMagiskModule(context)
withContext(Dispatchers.Main) {
if (success) {
Toast.makeText(
context,
if (executeInPostFsData) {
context.getString(R.string.susfs_execute_location_updated_post_fs_data)
} else {
context.getString(R.string.susfs_execute_location_updated_service)
},
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
context,
context.getString(R.string.susfs_execute_location_update_failed),
Toast.LENGTH_SHORT
).show()
}
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(
context,
context.getString(R.string.susfs_execute_location_update_error, e.message ?: "Unknown error"),
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
/**
@@ -343,6 +390,40 @@ object SuSFSManager {
return getPrefs(context).getStringSet(KEY_TRY_UMOUNTS, emptySet()) ?: emptySet()
}
/**
* 保存Kstat配置列表
*/
fun saveKstatConfigs(context: Context, configs: Set<String>) {
getPrefs(context).edit().apply {
putStringSet(KEY_KSTAT_CONFIGS, configs)
apply()
}
}
/**
* 获取Kstat配置列表
*/
fun getKstatConfigs(context: Context): Set<String> {
return getPrefs(context).getStringSet(KEY_KSTAT_CONFIGS, emptySet()) ?: emptySet()
}
/**
* 保存Add Kstat路径列表
*/
fun saveAddKstatPaths(context: Context, paths: Set<String>) {
getPrefs(context).edit().apply {
putStringSet(KEY_ADD_KSTAT_PATHS, paths)
apply()
}
}
/**
* 获取Add Kstat路径列表
*/
fun getAddKstatPaths(context: Context): Set<String> {
return getPrefs(context).getStringSet(KEY_ADD_KSTAT_PATHS, emptySet()) ?: emptySet()
}
/**
* 保存Android Data路径
*/
@@ -462,11 +543,14 @@ object SuSFSManager {
val androidDataPath = getAndroidDataPath(context)
val sdcardPath = getSdcardPath(context)
val enableLog = getEnableLogState(context)
val kstatConfigs = getKstatConfigs(context)
val addKstatPaths = getAddKstatPaths(context)
// 生成并创建service.sh
val serviceScript = ScriptGenerator.generateServiceScript(
targetPath, unameValue, buildTimeValue, susPaths,
androidDataPath, sdcardPath, enableLog, executeInPostFsData
androidDataPath, sdcardPath, enableLog, executeInPostFsData,
kstatConfigs, addKstatPaths
)
val createServiceResult = shell.newJob()
.add("cat > $MODULE_PATH/service.sh << 'EOF'\n$serviceScript\nEOF")
@@ -794,6 +878,133 @@ object SuSFSManager {
return executeSusfsCommand(context, "run_try_umount")
}
/**
* 添加Kstat静态配置
*/
suspend fun addKstatStatically(
context: Context,
path: String,
ino: String,
dev: String,
nlink: String,
size: String,
atime: String,
atimeNsec: String,
mtime: String,
mtimeNsec: String,
ctime: String,
ctimeNsec: String,
blocks: String,
blksize: String
): Boolean {
val command = "add_sus_kstat_statically '$path' '$ino' '$dev' '$nlink' '$size' '$atime' '$atimeNsec' '$mtime' '$mtimeNsec' '$ctime' '$ctimeNsec' '$blocks' '$blksize'"
val success = executeSusfsCommand(context, command)
if (success) {
val currentConfigs = getKstatConfigs(context).toMutableSet()
val configEntry = "$path|$ino|$dev|$nlink|$size|$atime|$atimeNsec|$mtime|$mtimeNsec|$ctime|$ctimeNsec|$blocks|$blksize"
currentConfigs.add(configEntry)
saveKstatConfigs(context, currentConfigs)
// 如果开启了开机自启动,更新模块
if (isAutoStartEnabled(context)) {
createMagiskModule(context)
}
withContext(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.kstat_static_config_added, path), Toast.LENGTH_SHORT).show()
}
}
return success
}
/**
* 移除Kstat配置
*/
suspend fun removeKstatConfig(context: Context, config: String): Boolean {
val currentConfigs = getKstatConfigs(context).toMutableSet()
currentConfigs.remove(config)
saveKstatConfigs(context, currentConfigs)
// 如果开启了开机自启动,更新模块
if (isAutoStartEnabled(context)) {
createMagiskModule(context)
}
val parts = config.split("|")
val path = if (parts.isNotEmpty()) parts[0] else config
withContext(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.kstat_config_removed, path), Toast.LENGTH_SHORT).show()
}
return true
}
/**
* 添加Kstat路径
*/
suspend fun addKstat(context: Context, path: String): Boolean {
val success = executeSusfsCommand(context, "add_sus_kstat '$path'")
if (success) {
val currentPaths = getAddKstatPaths(context).toMutableSet()
currentPaths.add(path)
saveAddKstatPaths(context, currentPaths)
// 如果开启了开机自启动,更新模块
if (isAutoStartEnabled(context)) {
createMagiskModule(context)
}
withContext(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.kstat_path_added, path), Toast.LENGTH_SHORT).show()
}
}
return success
}
/**
* 移除Add Kstat路径
*/
suspend fun removeAddKstat(context: Context, path: String): Boolean {
val currentPaths = getAddKstatPaths(context).toMutableSet()
currentPaths.remove(path)
saveAddKstatPaths(context, currentPaths)
// 如果开启了开机自启动,更新模块
if (isAutoStartEnabled(context)) {
createMagiskModule(context)
}
withContext(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.kstat_path_removed, path), Toast.LENGTH_SHORT).show()
}
return true
}
/**
* 更新Kstat
*/
suspend fun updateKstat(context: Context, path: String): Boolean {
val success = executeSusfsCommand(context, "update_sus_kstat '$path'")
if (success) {
withContext(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.kstat_updated, path), Toast.LENGTH_SHORT).show()
}
}
return success
}
/**
* 完整克隆更新Kstat
*/
suspend fun updateKstatFullClone(context: Context, path: String): Boolean {
val success = executeSusfsCommand(context, "update_sus_kstat_full_clone '$path'")
if (success) {
withContext(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.kstat_full_clone_updated, path), Toast.LENGTH_SHORT).show()
}
}
return success
}
/**
* 设置Android Data路径
*/
@@ -905,6 +1116,8 @@ object SuSFSManager {
val susPaths = getSusPaths(context)
val susMounts = getSusMounts(context)
val tryUmounts = getTryUmounts(context)
val kstatConfigs = getKstatConfigs(context)
val addKstatPaths = getAddKstatPaths(context)
val enabledFeatures = runBlocking {
getEnabledFeatures(context)
}
@@ -914,6 +1127,8 @@ object SuSFSManager {
susPaths.isNotEmpty() ||
susMounts.isNotEmpty() ||
tryUmounts.isNotEmpty() ||
kstatConfigs.isNotEmpty() ||
addKstatPaths.isNotEmpty() ||
enabledFeatures.any { it.isEnabled }
}

View File

@@ -20,7 +20,9 @@ object ScriptGenerator {
androidDataPath: String,
sdcardPath: String,
enableLog: Boolean,
executeInPostFsData: Boolean = false
executeInPostFsData: Boolean = false,
kstatConfigs: Set<String> = emptySet(),
addKstatPaths: Set<String> = emptySet()
): String {
return buildString {
appendLine("#!/system/bin/sh")
@@ -42,7 +44,7 @@ object ScriptGenerator {
appendLine("# 检查SuSFS二进制文件")
appendLine("SUSFS_BIN=\"$targetPath\"")
appendLine("if [ ! -f \"\$SUSFS_BIN\" ]; then")
appendLine(" echo \"\$(get_current_time): SuSFS二进制文件未找到: \$SUSFS_BIN\" >> \"\$LOG_FILE\"")
appendLine(" echo \"$(get_current_time): SuSFS二进制文件未找到: \$SUSFS_BIN\" >> \"\$LOG_FILE\"")
appendLine(" exit 1")
appendLine("fi")
appendLine()
@@ -51,14 +53,14 @@ object ScriptGenerator {
appendLine("# 设置日志启用状态")
val logValue = if (enableLog) 1 else 0
appendLine("\"\$SUSFS_BIN\" enable_log $logValue")
appendLine("echo \"\$(get_current_time): 日志功能设置为: ${if (enableLog) "启用" else "禁用"}\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): 日志功能设置为: ${if (enableLog) "启用" else "禁用"}\" >> \"\$LOG_FILE\"")
appendLine()
// 设置Android Data路径
if (androidDataPath != "/sdcard/Android/data") {
appendLine("# 设置Android Data路径")
appendLine("\"\$SUSFS_BIN\" set_android_data_root_path '$androidDataPath'")
appendLine("echo \"\$(get_current_time): Android Data路径设置为: $androidDataPath\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): Android Data路径设置为: $androidDataPath\" >> \"\$LOG_FILE\"")
appendLine()
}
@@ -66,7 +68,7 @@ object ScriptGenerator {
if (sdcardPath != "/sdcard") {
appendLine("# 设置SD卡路径")
appendLine("\"\$SUSFS_BIN\" set_sdcard_root_path '$sdcardPath'")
appendLine("echo \"\$(get_current_time): SD卡路径设置为: $sdcardPath\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): SD卡路径设置为: $sdcardPath\" >> \"\$LOG_FILE\"")
appendLine()
}
@@ -75,7 +77,32 @@ object ScriptGenerator {
appendLine("# 添加SUS路径")
susPaths.forEach { path ->
appendLine("\"\$SUSFS_BIN\" add_sus_path '$path'")
appendLine("echo \"\$(get_current_time): 添加SUS路径: $path\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): 添加SUS路径: $path\" >> \"\$LOG_FILE\"")
}
appendLine()
}
// 添加Kstat静态配置
if (kstatConfigs.isNotEmpty()) {
appendLine("# 添加Kstat静态配置")
kstatConfigs.forEach { config ->
val parts = config.split("|")
if (parts.size >= 13) {
val path = parts[0]
val params = parts.drop(1).joinToString("' '", "'", "'")
appendLine("\"\$SUSFS_BIN\" add_sus_kstat_statically $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()
}
@@ -84,38 +111,38 @@ object ScriptGenerator {
if (!executeInPostFsData && (unameValue != "default" || buildTimeValue != "default")) {
appendLine("# 设置uname和构建时间")
appendLine("\"\$SUSFS_BIN\" set_uname '$unameValue' '$buildTimeValue'")
appendLine("echo \"\$(get_current_time): 设置uname为: $unameValue, 构建时间为: $buildTimeValue\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): 设置uname为: $unameValue, 构建时间为: $buildTimeValue\" >> \"\$LOG_FILE\"")
appendLine()
}
appendLine("# 隐弱BL 来自 Shamiko 脚本")
appendLine("check_reset_prop() {")
appendLine("local NAME=\$1")
appendLine("local EXPECTED=\$2")
appendLine("local VALUE=\$(resetprop \$NAME)")
appendLine("local NAME=$1")
appendLine("local EXPECTED=$2")
appendLine("local VALUE=$(resetprop \$NAME)")
appendLine("[ -z \$VALUE ] || [ \$VALUE = \$EXPECTED ] || resetprop \$NAME \$EXPECTED")
appendLine("}")
appendLine()
appendLine("check_missing_prop() {")
appendLine(" local NAME=\$1")
appendLine(" local EXPECTED=\$2")
appendLine(" local VALUE=\$(resetprop \$NAME)")
appendLine(" local NAME=$1")
appendLine(" local EXPECTED=$2")
appendLine(" local VALUE=$(resetprop \$NAME)")
appendLine(" [ -z \$VALUE ] && resetprop \$NAME \$EXPECTED")
appendLine("}")
appendLine()
appendLine("check_missing_match_prop() {")
appendLine(" local NAME=\$1")
appendLine(" local EXPECTED=\$2")
appendLine(" local VALUE=\$(resetprop \$NAME)")
appendLine(" local NAME=$1")
appendLine(" local EXPECTED=$2")
appendLine(" local VALUE=$(resetprop \$NAME)")
appendLine(" [ -z \$VALUE ] || [ \$VALUE = \$EXPECTED ] || resetprop \$NAME \$EXPECTED")
appendLine(" [ -z \$VALUE ] && resetprop \$NAME \$EXPECTED")
appendLine("}")
appendLine()
appendLine("contains_reset_prop() {")
appendLine("local NAME=\$1")
appendLine("local CONTAINS=\$2")
appendLine("local NEWVAL=\$3")
appendLine("[[ \"\$(resetprop \$NAME)\" = *\"\$CONTAINS\"* ]] && resetprop \$NAME \$NEWVAL")
appendLine("local NAME=$1")
appendLine("local CONTAINS=$2")
appendLine("local NEWVAL=$3")
appendLine("[[ \"$(resetprop \$NAME)\" = *\"\$CONTAINS\"* ]] && resetprop \$NAME \$NEWVAL")
appendLine("}")
appendLine()
appendLine("resetprop -w sys.boot_completed 0")
@@ -163,13 +190,13 @@ object ScriptGenerator {
appendLine("contains_reset_prop \"vendor.boot.bootmode\" \"recovery\" \"unknown\"")
appendLine()
appendLine("# Hide cloudphone detection")
appendLine("[ -n \"\$(resetprop ro.kernel.qemu)\" ] && resetprop ro.kernel.qemu \"\"")
appendLine("[ -n \"$(resetprop ro.kernel.qemu)\" ] && resetprop ro.kernel.qemu \"\"")
appendLine()
appendLine("# fake encryption status")
appendLine("check_reset_prop \"ro.crypto.state\" \"encrypted\"")
appendLine()
appendLine("echo \"\$(get_current_time): Service脚本执行完成\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): Service脚本执行完成\" >> \"\$LOG_FILE\"")
}
}
@@ -202,22 +229,22 @@ object ScriptGenerator {
appendLine("# 检查SuSFS二进制文件")
appendLine("SUSFS_BIN=\"$targetPath\"")
appendLine("if [ ! -f \"\$SUSFS_BIN\" ]; then")
appendLine(" echo \"\$(get_current_time): SuSFS二进制文件未找到: \$SUSFS_BIN\" >> \"\$LOG_FILE\"")
appendLine(" echo \"$(get_current_time): SuSFS二进制文件未找到: \$SUSFS_BIN\" >> \"\$LOG_FILE\"")
appendLine(" exit 1")
appendLine("fi")
appendLine()
appendLine("echo \"\$(get_current_time): Post-FS-Data脚本开始执行\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): Post-FS-Data脚本开始执行\" >> \"\$LOG_FILE\"")
appendLine()
// 设置uname和构建时间 - 只有在选择在post-fs-data中执行时才执行
if (executeInPostFsData && (unameValue != "default" || buildTimeValue != "default")) {
appendLine("# 设置uname和构建时间")
appendLine("\"\$SUSFS_BIN\" set_uname '$unameValue' '$buildTimeValue'")
appendLine("echo \"\$(get_current_time): 设置uname为: $unameValue, 构建时间为: $buildTimeValue\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): 设置uname为: $unameValue, 构建时间为: $buildTimeValue\" >> \"\$LOG_FILE\"")
appendLine()
}
appendLine("echo \"\$(get_current_time): Post-FS-Data脚本执行完成\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): Post-FS-Data脚本执行完成\" >> \"\$LOG_FILE\"")
}
}
@@ -246,12 +273,12 @@ object ScriptGenerator {
appendLine(" date '+%Y-%m-%d %H:%M:%S'")
appendLine("}")
appendLine()
appendLine("echo \"\$(get_current_time): Post-Mount脚本开始执行\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): Post-Mount脚本开始执行\" >> \"\$LOG_FILE\"")
appendLine()
appendLine("# 检查SuSFS二进制文件")
appendLine("SUSFS_BIN=\"$targetPath\"")
appendLine("if [ ! -f \"\$SUSFS_BIN\" ]; then")
appendLine(" echo \"\$(get_current_time): SuSFS二进制文件未找到: \$SUSFS_BIN\" >> \"\$LOG_FILE\"")
appendLine(" echo \"$(get_current_time): SuSFS二进制文件未找到: \$SUSFS_BIN\" >> \"\$LOG_FILE\"")
appendLine(" exit 1")
appendLine("fi")
appendLine()
@@ -261,7 +288,7 @@ object ScriptGenerator {
appendLine("# 添加SUS挂载")
susMounts.forEach { mount ->
appendLine("\"\$SUSFS_BIN\" add_sus_mount '$mount'")
appendLine("echo \"\$(get_current_time): 添加SUS挂载: $mount\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): 添加SUS挂载: $mount\" >> \"\$LOG_FILE\"")
}
appendLine()
}
@@ -275,13 +302,13 @@ object ScriptGenerator {
val path = parts[0]
val mode = parts[1]
appendLine("\"\$SUSFS_BIN\" add_try_umount '$path' $mode")
appendLine("echo \"\$(get_current_time): 添加尝试卸载: $path (模式: $mode)\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): 添加尝试卸载: $path (模式: $mode)\" >> \"\$LOG_FILE\"")
}
}
appendLine()
}
appendLine("echo \"\$(get_current_time): Post-Mount脚本执行完成\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): Post-Mount脚本执行完成\" >> \"\$LOG_FILE\"")
}
}
@@ -306,16 +333,16 @@ object ScriptGenerator {
appendLine(" date '+%Y-%m-%d %H:%M:%S'")
appendLine("}")
appendLine()
appendLine("echo \"\$(get_current_time): Boot-Completed脚本开始执行\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): Boot-Completed脚本开始执行\" >> \"\$LOG_FILE\"")
appendLine()
appendLine("# 检查SuSFS二进制文件")
appendLine("SUSFS_BIN=\"$targetPath\"")
appendLine("if [ ! -f \"\$SUSFS_BIN\" ]; then")
appendLine(" echo \"\$(get_current_time): SuSFS二进制文件未找到: \$SUSFS_BIN\" >> \"\$LOG_FILE\"")
appendLine(" echo \"$(get_current_time): SuSFS二进制文件未找到: \$SUSFS_BIN\" >> \"\$LOG_FILE\"")
appendLine(" exit 1")
appendLine("fi")
appendLine()
appendLine("echo \"\$(get_current_time): Boot-Completed脚本执行完成\" >> \"\$LOG_FILE\"")
appendLine("echo \"$(get_current_time): Boot-Completed脚本执行完成\" >> \"\$LOG_FILE\"")
}
}

View File

@@ -543,4 +543,34 @@
<!-- SuSFS 自启动相关字符串 -->
<string name="susfs_autostart_enabled_success">SuSFS自启动模块已启用模块路径%s</string>
<string name="susfs_autostart_disabled_success">SuSFS自启动模块已禁用</string>
<string name="susfs_execute_location_updated_post_fs_data">执行位置已更新到 post-fs-data</string>
<string name="susfs_execute_location_updated_service">执行位置已更新到 service</string>
<string name="susfs_execute_location_update_failed">执行位置更新失败</string>
<string name="susfs_execute_location_update_error">更新执行位置时出错:%s</string>
<!-- SuSFS Kstat相关字符串 -->
<string name="susfs_tab_kstat_config">Kstat 配置</string>
<string name="kstat_static_config_added">Kstat静态配置已添加: %1$s</string>
<string name="kstat_config_removed">已移除Kstat配置: %1$s</string>
<string name="kstat_path_added">Kstat路径已添加: %1$s</string>
<string name="kstat_path_removed">已移除Kstat路径: %1$s</string>
<string name="kstat_updated">Kstat已更新: %1$s</string>
<string name="kstat_full_clone_updated">Kstat完整克隆已更新: %1$s</string>
<string name="add_kstat_statically_title">添加Kstat静态配置</string>
<string name="file_or_directory_path_label">文件/目录路径</string>
<string name="hint_use_default_value">提示:可以使用 ”default“ 来使用原始值</string>
<string name="add_kstat_path_title">添加Kstat路径</string>
<string name="kstat_command_description">此命令用于在路径被绑定挂载或覆盖之前添加用于在内核内存中存储原始stat信息。</string>
<string name="add">添加</string>
<string name="reset_kstat_config_title">重置Kstat配置</string>
<string name="reset_kstat_config_message">确定要清除所有Kstat配置吗此操作不可撤销。</string>
<string name="confirm_reset">确认重置</string>
<string name="kstat_config_management">Kstat配置管理</string>
<string name="kstat_config_description_title">Kstat配置说明</string>
<string name="kstat_config_description_add_statically">• add_sus_kstat_statically: 静态配置文件/目录的stat信息</string>
<string name="kstat_config_description_add">• add_sus_kstat: 在绑定挂载前添加路径存储原始stat信息</string>
<string name="kstat_config_description_update">• update_sus_kstat: 更新目标ino保持size和blocks不变</string>
<string name="kstat_config_description_update_full_clone">• update_sus_kstat_full_clone: 仅更新ino其他保持原始值</string>
<string name="static_kstat_config">静态Kstat配置</string>
<string name="kstat_path_management">Kstat路径管理</string>
<string name="no_kstat_config_message">暂无Kstat配置点击上方按钮添加配置</string>
</resources>

View File

@@ -545,4 +545,34 @@
<!-- SuSFS 自启动相关字符串 -->
<string name="susfs_autostart_enabled_success">SuSFS auto-start module enabled, module path: %s</string>
<string name="susfs_autostart_disabled_success">SuSFS auto-start module disabled</string>
<string name="susfs_execute_location_updated_post_fs_data">Execution location updated to post-fs-data</string>
<string name="susfs_execute_location_updated_service">Execution location updated to service</string>
<string name="susfs_execute_location_update_failed">Failed to update execution location</string>
<string name="susfs_execute_location_update_error">Error updating execution location: %s</string>
<!-- SuSFS Kstat相关字符串 -->
<string name="susfs_tab_kstat_config">Kstat Configuration</string>
<string name="kstat_static_config_added">Kstat static configuration added: %1$s</string>
<string name="kstat_config_removed">Kstat configuration removed: %1$s</string>
<string name="kstat_path_added">Kstat path added: %1$s</string>
<string name="kstat_path_removed">Kstat path removed: %1$s</string>
<string name="kstat_updated">Kstat updated: %1$s</string>
<string name="kstat_full_clone_updated">Kstat full clone updated: %1$s</string>
<string name="add_kstat_statically_title">Add Kstat Static Configuration</string>
<string name="file_or_directory_path_label">File/Directory Path</string>
<string name="hint_use_default_value">Hint: You can use ”default“ to use the original value</string>
<string name="add_kstat_path_title">Add Kstat Path</string>
<string name="kstat_command_description">This command is used to add before the path is bind-mounted or overlaid, storing the original stat information in kernel memory.</string>
<string name="add">Add</string>
<string name="reset_kstat_config_title">Reset Kstat Configuration</string>
<string name="reset_kstat_config_message">Are you sure you want to clear all Kstat configurations? This action cannot be undone.</string>
<string name="confirm_reset">Confirm Reset</string>
<string name="kstat_config_management">Kstat Configuration Management</string>
<string name="kstat_config_description_title">Kstat Configuration Description</string>
<string name="kstat_config_description_add_statically">• add_sus_kstat_statically: Static stat info of files/directories</string>
<string name="kstat_config_description_add">• add_sus_kstat: Add path before bind mount, storing original stat info</string>
<string name="kstat_config_description_update">• update_sus_kstat: Update target ino, keep size and blocks unchanged</string>
<string name="kstat_config_description_update_full_clone">• update_sus_kstat_full_clone: Update ino only, keep other original values</string>
<string name="static_kstat_config">Static Kstat Configuration</string>
<string name="kstat_path_management">Kstat Path Management</string>
<string name="no_kstat_config_message">No Kstat configuration yet, click the button above to add</string>
</resources>