manager: Add SuSFS Kstat-related configuration
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -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\"")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user