manager: Add SuSFS configuration backup and restore feature
- Optimize susfs self-boot scripts - Solve some invalid issues where startup duration does not match or is too fast. Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package com.sukisu.ultra.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -80,6 +82,8 @@ import com.sukisu.ultra.ui.theme.CardConfig
|
|||||||
import com.sukisu.ultra.ui.util.SuSFSManager
|
import com.sukisu.ultra.ui.util.SuSFSManager
|
||||||
import com.sukisu.ultra.ui.util.SuSFSManager.isSusVersion_1_5_8
|
import com.sukisu.ultra.ui.util.SuSFSManager.isSusVersion_1_5_8
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标签页枚举类
|
* 标签页枚举类
|
||||||
@@ -123,8 +127,6 @@ fun SuSFSConfigScreen(
|
|||||||
var isLoading by remember { mutableStateOf(false) }
|
var isLoading by remember { mutableStateOf(false) }
|
||||||
var showConfirmReset by remember { mutableStateOf(false) }
|
var showConfirmReset by remember { mutableStateOf(false) }
|
||||||
var autoStartEnabled by remember { mutableStateOf(false) }
|
var autoStartEnabled by remember { mutableStateOf(false) }
|
||||||
var lastAppliedValue by remember { mutableStateOf("") }
|
|
||||||
var lastAppliedBuildTime by remember { mutableStateOf("") }
|
|
||||||
var executeInPostFsData by remember { mutableStateOf(false) }
|
var executeInPostFsData by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// 槽位信息相关状态
|
// 槽位信息相关状态
|
||||||
@@ -165,6 +167,13 @@ fun SuSFSConfigScreen(
|
|||||||
var showResetUmountsDialog by remember { mutableStateOf(false) }
|
var showResetUmountsDialog by remember { mutableStateOf(false) }
|
||||||
var showResetKstatDialog by remember { mutableStateOf(false) }
|
var showResetKstatDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// 备份还原相关状态
|
||||||
|
var showBackupDialog by remember { mutableStateOf(false) }
|
||||||
|
var showRestoreDialog by remember { mutableStateOf(false) }
|
||||||
|
var showRestoreConfirmDialog by remember { mutableStateOf(false) }
|
||||||
|
var selectedBackupFile by remember { mutableStateOf<String?>(null) }
|
||||||
|
var backupInfo by remember { mutableStateOf<SuSFSManager.BackupData?>(null) }
|
||||||
|
|
||||||
val allTabs = SuSFSTab.getAllTabs(isSusVersion_1_5_8())
|
val allTabs = SuSFSTab.getAllTabs(isSusVersion_1_5_8())
|
||||||
|
|
||||||
// 实时判断是否可以启用开机自启动
|
// 实时判断是否可以启用开机自启动
|
||||||
@@ -174,6 +183,65 @@ fun SuSFSConfigScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文件选择器
|
||||||
|
val backupFileLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.CreateDocument("application/json")
|
||||||
|
) { uri ->
|
||||||
|
uri?.let { fileUri ->
|
||||||
|
val fileName = SuSFSManager.getRecommendedBackupPath(context)
|
||||||
|
coroutineScope.launch {
|
||||||
|
isLoading = true
|
||||||
|
val success = SuSFSManager.createBackup(context, fileName)
|
||||||
|
if (success) {
|
||||||
|
// 复制到用户选择的位置
|
||||||
|
try {
|
||||||
|
context.contentResolver.openOutputStream(fileUri)?.use { outputStream ->
|
||||||
|
java.io.File(fileName).inputStream().use { inputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
showBackupDialog = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val restoreFileLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.OpenDocument()
|
||||||
|
) { uri ->
|
||||||
|
uri?.let { fileUri ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
// 复制到临时文件
|
||||||
|
val tempFile = java.io.File(context.cacheDir, "temp_restore.susfs_backup")
|
||||||
|
context.contentResolver.openInputStream(fileUri)?.use { inputStream ->
|
||||||
|
tempFile.outputStream().use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证备份文件
|
||||||
|
val backup = SuSFSManager.validateBackupFile(tempFile.absolutePath)
|
||||||
|
if (backup != null) {
|
||||||
|
selectedBackupFile = tempFile.absolutePath
|
||||||
|
backupInfo = backup
|
||||||
|
showRestoreConfirmDialog = true
|
||||||
|
} else {
|
||||||
|
// 显示错误消息
|
||||||
|
}
|
||||||
|
tempFile.deleteOnExit()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
showRestoreDialog = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载启用功能状态
|
// 加载启用功能状态
|
||||||
fun loadEnabledFeatures() {
|
fun loadEnabledFeatures() {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
@@ -198,8 +266,6 @@ fun SuSFSConfigScreen(
|
|||||||
unameValue = SuSFSManager.getUnameValue(context)
|
unameValue = SuSFSManager.getUnameValue(context)
|
||||||
buildTimeValue = SuSFSManager.getBuildTimeValue(context)
|
buildTimeValue = SuSFSManager.getBuildTimeValue(context)
|
||||||
autoStartEnabled = SuSFSManager.isAutoStartEnabled(context)
|
autoStartEnabled = SuSFSManager.isAutoStartEnabled(context)
|
||||||
lastAppliedValue = SuSFSManager.getLastAppliedValue(context)
|
|
||||||
lastAppliedBuildTime = SuSFSManager.getLastAppliedBuildTime(context)
|
|
||||||
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
|
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
|
||||||
susPaths = SuSFSManager.getSusPaths(context)
|
susPaths = SuSFSManager.getSusPaths(context)
|
||||||
susMounts = SuSFSManager.getSusMounts(context)
|
susMounts = SuSFSManager.getSusMounts(context)
|
||||||
@@ -228,6 +294,183 @@ fun SuSFSConfigScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 备份对话框
|
||||||
|
if (showBackupDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showBackupDialog = false },
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.susfs_backup_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.susfs_backup_description))
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
|
||||||
|
val timestamp = dateFormat.format(Date())
|
||||||
|
backupFileLauncher.launch("SuSFS_Config_$timestamp.susfs_backup")
|
||||||
|
},
|
||||||
|
enabled = !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.susfs_backup_create))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = { showBackupDialog = false },
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 还原对话框
|
||||||
|
if (showRestoreDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showRestoreDialog = false },
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.susfs_restore_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.susfs_restore_description))
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
restoreFileLauncher.launch(arrayOf("application/json", "*/*"))
|
||||||
|
},
|
||||||
|
enabled = !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.susfs_restore_select_file))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = { showRestoreDialog = false },
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 还原确认对话框
|
||||||
|
if (showRestoreConfirmDialog && backupInfo != null) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
showRestoreConfirmDialog = false
|
||||||
|
selectedBackupFile = null
|
||||||
|
backupInfo = null
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.susfs_restore_confirm_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.susfs_restore_confirm_description))
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.susfs_backup_info_date,
|
||||||
|
dateFormat.format(Date(backupInfo!!.timestamp))),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.susfs_backup_info_device, backupInfo!!.deviceInfo),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.susfs_backup_info_version, backupInfo!!.version),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
selectedBackupFile?.let { filePath ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
isLoading = true
|
||||||
|
val success = SuSFSManager.restoreFromBackup(context, filePath)
|
||||||
|
if (success) {
|
||||||
|
// 重新加载所有配置
|
||||||
|
unameValue = SuSFSManager.getUnameValue(context)
|
||||||
|
buildTimeValue = SuSFSManager.getBuildTimeValue(context)
|
||||||
|
autoStartEnabled = SuSFSManager.isAutoStartEnabled(context)
|
||||||
|
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
|
||||||
|
susPaths = SuSFSManager.getSusPaths(context)
|
||||||
|
susMounts = SuSFSManager.getSusMounts(context)
|
||||||
|
tryUmounts = SuSFSManager.getTryUmounts(context)
|
||||||
|
androidDataPath = SuSFSManager.getAndroidDataPath(context)
|
||||||
|
sdcardPath = SuSFSManager.getSdcardPath(context)
|
||||||
|
kstatConfigs = SuSFSManager.getKstatConfigs(context)
|
||||||
|
addKstatPaths = SuSFSManager.getAddKstatPaths(context)
|
||||||
|
hideSusMountsForAllProcs = SuSFSManager.getHideSusMountsForAllProcs(context)
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
showRestoreConfirmDialog = false
|
||||||
|
selectedBackupFile = null
|
||||||
|
backupInfo = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.susfs_restore_confirm))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showRestoreConfirmDialog = false
|
||||||
|
selectedBackupFile = null
|
||||||
|
backupInfo = null
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 槽位信息对话框
|
// 槽位信息对话框
|
||||||
SlotInfoDialog(
|
SlotInfoDialog(
|
||||||
showDialog = showSlotInfoDialog,
|
showDialog = showSlotInfoDialog,
|
||||||
@@ -366,8 +609,6 @@ fun SuSFSConfigScreen(
|
|||||||
if (SuSFSManager.resetToDefault(context)) {
|
if (SuSFSManager.resetToDefault(context)) {
|
||||||
unameValue = "default"
|
unameValue = "default"
|
||||||
buildTimeValue = "default"
|
buildTimeValue = "default"
|
||||||
lastAppliedValue = "default"
|
|
||||||
lastAppliedBuildTime = "default"
|
|
||||||
autoStartEnabled = false
|
autoStartEnabled = false
|
||||||
}
|
}
|
||||||
isLoading = false
|
isLoading = false
|
||||||
@@ -530,8 +771,6 @@ fun SuSFSConfigScreen(
|
|||||||
val finalBuildTimeValue = buildTimeValue.trim().ifBlank { "default" }
|
val finalBuildTimeValue = buildTimeValue.trim().ifBlank { "default" }
|
||||||
val success = SuSFSManager.setUname(context, finalUnameValue, finalBuildTimeValue)
|
val success = SuSFSManager.setUname(context, finalUnameValue, finalBuildTimeValue)
|
||||||
if (success) {
|
if (success) {
|
||||||
lastAppliedValue = finalUnameValue
|
|
||||||
lastAppliedBuildTime = finalBuildTimeValue
|
|
||||||
SuSFSManager.saveExecuteInPostFsData(context, executeInPostFsData)
|
SuSFSManager.saveExecuteInPostFsData(context, executeInPostFsData)
|
||||||
}
|
}
|
||||||
isLoading = false
|
isLoading = false
|
||||||
@@ -780,7 +1019,9 @@ fun SuSFSConfigScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShowSlotInfo = { showSlotInfoDialog = true },
|
onShowSlotInfo = { showSlotInfoDialog = true },
|
||||||
context = context
|
context = context,
|
||||||
|
onShowBackupDialog = { showBackupDialog = true },
|
||||||
|
onShowRestoreDialog = { showRestoreDialog = true }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SuSFSTab.SUS_PATHS -> {
|
SuSFSTab.SUS_PATHS -> {
|
||||||
@@ -939,7 +1180,9 @@ private fun BasicSettingsContent(
|
|||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
onAutoStartToggle: (Boolean) -> Unit,
|
onAutoStartToggle: (Boolean) -> Unit,
|
||||||
onShowSlotInfo: () -> Unit,
|
onShowSlotInfo: () -> Unit,
|
||||||
context: android.content.Context
|
context: android.content.Context,
|
||||||
|
onShowBackupDialog: () -> Unit,
|
||||||
|
onShowRestoreDialog: () -> Unit
|
||||||
) {
|
) {
|
||||||
var scriptLocationExpanded by remember { mutableStateOf(false) }
|
var scriptLocationExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -1204,6 +1447,51 @@ private fun BasicSettingsContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
// 备份按钮
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onShowBackupDialog,
|
||||||
|
enabled = !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(40.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Backup,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.susfs_backup_title),
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 还原按钮
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onShowRestoreDialog,
|
||||||
|
enabled = !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(40.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Restore,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.susfs_restore_title),
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import java.io.File
|
|||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SuSFS 配置管理器
|
* SuSFS 配置管理器
|
||||||
@@ -44,6 +47,7 @@ object SuSFSManager {
|
|||||||
private const val MODULE_ID = "susfs_manager"
|
private const val MODULE_ID = "susfs_manager"
|
||||||
private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID"
|
private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID"
|
||||||
private const val MIN_VERSION_FOR_HIDE_MOUNT = "1.5.8"
|
private const val MIN_VERSION_FOR_HIDE_MOUNT = "1.5.8"
|
||||||
|
private const val BACKUP_FILE_EXTENSION = ".susfs_backup"
|
||||||
|
|
||||||
data class SlotInfo(val slotName: String, val uname: String, val buildTime: String)
|
data class SlotInfo(val slotName: String, val uname: String, val buildTime: String)
|
||||||
data class CommandResult(val isSuccess: Boolean, val output: String, val errorOutput: String = "")
|
data class CommandResult(val isSuccess: Boolean, val output: String, val errorOutput: String = "")
|
||||||
@@ -54,6 +58,60 @@ object SuSFSManager {
|
|||||||
val canConfigure: Boolean = false
|
val canConfigure: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备份数据类
|
||||||
|
*/
|
||||||
|
data class BackupData(
|
||||||
|
val version: String,
|
||||||
|
val timestamp: Long,
|
||||||
|
val deviceInfo: String,
|
||||||
|
val configurations: Map<String, Any>
|
||||||
|
) {
|
||||||
|
fun toJson(): String {
|
||||||
|
val jsonObject = JSONObject().apply {
|
||||||
|
put("version", version)
|
||||||
|
put("timestamp", timestamp)
|
||||||
|
put("deviceInfo", deviceInfo)
|
||||||
|
put("configurations", JSONObject(configurations))
|
||||||
|
}
|
||||||
|
return jsonObject.toString(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromJson(jsonString: String): BackupData? {
|
||||||
|
return try {
|
||||||
|
val jsonObject = JSONObject(jsonString)
|
||||||
|
val configurationsJson = jsonObject.getJSONObject("configurations")
|
||||||
|
val configurations = mutableMapOf<String, Any>()
|
||||||
|
|
||||||
|
configurationsJson.keys().forEach { key ->
|
||||||
|
val value = configurationsJson.get(key)
|
||||||
|
configurations[key] = when (value) {
|
||||||
|
is org.json.JSONArray -> {
|
||||||
|
val set = mutableSetOf<String>()
|
||||||
|
for (i in 0 until value.length()) {
|
||||||
|
set.add(value.getString(i))
|
||||||
|
}
|
||||||
|
set
|
||||||
|
}
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupData(
|
||||||
|
version = jsonObject.getString("version"),
|
||||||
|
timestamp = jsonObject.getLong("timestamp"),
|
||||||
|
deviceInfo = jsonObject.getString("deviceInfo"),
|
||||||
|
configurations = configurations
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模块配置数据类
|
* 模块配置数据类
|
||||||
*/
|
*/
|
||||||
@@ -180,9 +238,6 @@ object SuSFSManager {
|
|||||||
fun getBuildTimeValue(context: Context): String =
|
fun getBuildTimeValue(context: Context): String =
|
||||||
getPrefs(context).getString(KEY_BUILD_TIME_VALUE, DEFAULT_BUILD_TIME) ?: DEFAULT_BUILD_TIME
|
getPrefs(context).getString(KEY_BUILD_TIME_VALUE, DEFAULT_BUILD_TIME) ?: DEFAULT_BUILD_TIME
|
||||||
|
|
||||||
fun getLastAppliedValue(context: Context): String = getUnameValue(context)
|
|
||||||
fun getLastAppliedBuildTime(context: Context): String = getBuildTimeValue(context)
|
|
||||||
|
|
||||||
fun setAutoStartEnabled(context: Context, enabled: Boolean) =
|
fun setAutoStartEnabled(context: Context, enabled: Boolean) =
|
||||||
getPrefs(context).edit { putBoolean(KEY_AUTO_START_ENABLED, enabled) }
|
getPrefs(context).edit { putBoolean(KEY_AUTO_START_ENABLED, enabled) }
|
||||||
|
|
||||||
@@ -261,6 +316,154 @@ object SuSFSManager {
|
|||||||
fun getSdcardPath(context: Context): String =
|
fun getSdcardPath(context: Context): String =
|
||||||
getPrefs(context).getString(KEY_SDCARD_PATH, "/sdcard") ?: "/sdcard"
|
getPrefs(context).getString(KEY_SDCARD_PATH, "/sdcard") ?: "/sdcard"
|
||||||
|
|
||||||
|
// 获取所有配置的Map
|
||||||
|
private fun getAllConfigurations(context: Context): Map<String, Any> {
|
||||||
|
return mapOf(
|
||||||
|
KEY_UNAME_VALUE to getUnameValue(context),
|
||||||
|
KEY_BUILD_TIME_VALUE to getBuildTimeValue(context),
|
||||||
|
KEY_AUTO_START_ENABLED to isAutoStartEnabled(context),
|
||||||
|
KEY_SUS_PATHS to getSusPaths(context),
|
||||||
|
KEY_SUS_MOUNTS to getSusMounts(context),
|
||||||
|
KEY_TRY_UMOUNTS to getTryUmounts(context),
|
||||||
|
KEY_ANDROID_DATA_PATH to getAndroidDataPath(context),
|
||||||
|
KEY_SDCARD_PATH to getSdcardPath(context),
|
||||||
|
KEY_ENABLE_LOG to getEnableLogState(context),
|
||||||
|
KEY_EXECUTE_IN_POST_FS_DATA to getExecuteInPostFsData(context),
|
||||||
|
KEY_KSTAT_CONFIGS to getKstatConfigs(context),
|
||||||
|
KEY_ADD_KSTAT_PATHS to getAddKstatPaths(context),
|
||||||
|
KEY_HIDE_SUS_MOUNTS_FOR_ALL_PROCS to getHideSusMountsForAllProcs(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//生成备份文件名
|
||||||
|
private fun generateBackupFileName(): String {
|
||||||
|
val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
|
||||||
|
val timestamp = dateFormat.format(Date())
|
||||||
|
return "SuSFS_Config_$timestamp$BACKUP_FILE_EXTENSION"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备信息
|
||||||
|
private fun getDeviceInfo(): String {
|
||||||
|
return try {
|
||||||
|
"${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL} (${android.os.Build.VERSION.RELEASE})"
|
||||||
|
} catch (_: Exception) {
|
||||||
|
"Unknown Device"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建配置备份
|
||||||
|
suspend fun createBackup(context: Context, backupFilePath: String): Boolean = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val configurations = getAllConfigurations(context)
|
||||||
|
val backupData = BackupData(
|
||||||
|
version = getSuSFSVersion(),
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
deviceInfo = getDeviceInfo(),
|
||||||
|
configurations = configurations
|
||||||
|
)
|
||||||
|
|
||||||
|
val backupFile = File(backupFilePath)
|
||||||
|
backupFile.parentFile?.mkdirs()
|
||||||
|
|
||||||
|
backupFile.writeText(backupData.toJson())
|
||||||
|
|
||||||
|
showToast(context, context.getString(R.string.susfs_backup_success, backupFile.name))
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
showToast(context, context.getString(R.string.susfs_backup_failed, e.message ?: "Unknown error"))
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//从备份文件还原配置
|
||||||
|
suspend fun restoreFromBackup(context: Context, backupFilePath: String): Boolean = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val backupFile = File(backupFilePath)
|
||||||
|
if (!backupFile.exists()) {
|
||||||
|
showToast(context, context.getString(R.string.susfs_backup_file_not_found))
|
||||||
|
return@withContext false
|
||||||
|
}
|
||||||
|
|
||||||
|
val backupContent = backupFile.readText()
|
||||||
|
val backupData = BackupData.fromJson(backupContent)
|
||||||
|
|
||||||
|
if (backupData == null) {
|
||||||
|
showToast(context, context.getString(R.string.susfs_backup_invalid_format))
|
||||||
|
return@withContext false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查备份版本兼容性
|
||||||
|
if (backupData.version != getSuSFSVersion()) {
|
||||||
|
showToast(context, context.getString(R.string.susfs_backup_version_mismatch))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 还原所有配置
|
||||||
|
restoreConfigurations(context, backupData.configurations)
|
||||||
|
|
||||||
|
// 如果自启动已启用,更新模块
|
||||||
|
if (isAutoStartEnabled(context)) {
|
||||||
|
updateMagiskModule(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||||
|
val backupDate = dateFormat.format(Date(backupData.timestamp))
|
||||||
|
|
||||||
|
showToast(context, context.getString(R.string.susfs_restore_success, backupDate, backupData.deviceInfo))
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
showToast(context, context.getString(R.string.susfs_restore_failed, e.message ?: "Unknown error"))
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 还原配置到SharedPreferences
|
||||||
|
private fun restoreConfigurations(context: Context, configurations: Map<String, Any>) {
|
||||||
|
val prefs = getPrefs(context)
|
||||||
|
prefs.edit {
|
||||||
|
configurations.forEach { (key, value) ->
|
||||||
|
when (value) {
|
||||||
|
is String -> putString(key, value)
|
||||||
|
is Boolean -> putBoolean(key, value)
|
||||||
|
is Set<*> -> {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
putStringSet(key, value as Set<String>)
|
||||||
|
}
|
||||||
|
is Int -> putInt(key, value)
|
||||||
|
is Long -> putLong(key, value)
|
||||||
|
is Float -> putFloat(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证备份文件
|
||||||
|
suspend fun validateBackupFile(backupFilePath: String): BackupData? = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val backupFile = File(backupFilePath)
|
||||||
|
if (!backupFile.exists()) {
|
||||||
|
return@withContext null
|
||||||
|
}
|
||||||
|
|
||||||
|
val backupContent = backupFile.readText()
|
||||||
|
BackupData.fromJson(backupContent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取备份文件路径
|
||||||
|
fun getRecommendedBackupPath(context: Context): String {
|
||||||
|
val documentsDir = File(context.getExternalFilesDir(null), "SuSFS_Backups")
|
||||||
|
if (!documentsDir.exists()) {
|
||||||
|
documentsDir.mkdirs()
|
||||||
|
}
|
||||||
|
return File(documentsDir, generateBackupFileName()).absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
// 槽位信息获取
|
// 槽位信息获取
|
||||||
suspend fun getCurrentSlotInfo(): List<SlotInfo> = withContext(Dispatchers.IO) {
|
suspend fun getCurrentSlotInfo(): List<SlotInfo> = withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ object ScriptGenerator {
|
|||||||
/**
|
/**
|
||||||
* 生成service.sh脚本内容
|
* 生成service.sh脚本内容
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("SdCardPath")
|
||||||
private fun generateServiceScript(config: SuSFSManager.ModuleConfig): String {
|
private fun generateServiceScript(config: SuSFSManager.ModuleConfig): String {
|
||||||
return buildString {
|
return buildString {
|
||||||
appendLine("#!/system/bin/sh")
|
appendLine("#!/system/bin/sh")
|
||||||
@@ -67,6 +68,9 @@ object ScriptGenerator {
|
|||||||
if (shouldConfigureInService(config)) {
|
if (shouldConfigureInService(config)) {
|
||||||
// 添加SUS路径 (仅在不支持隐藏挂载时)
|
// 添加SUS路径 (仅在不支持隐藏挂载时)
|
||||||
if (!config.support158 && config.susPaths.isNotEmpty()) {
|
if (!config.support158 && config.susPaths.isNotEmpty()) {
|
||||||
|
appendLine()
|
||||||
|
appendLine("until [ -d \"/sdcard/Android\" ]; do sleep 1; done")
|
||||||
|
appendLine("sleep 45")
|
||||||
generateSusPathsSection(config.susPaths)
|
generateSusPathsSection(config.susPaths)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,45 +166,48 @@ object ScriptGenerator {
|
|||||||
appendLine("# 隐藏BL 来自 Shamiko 脚本")
|
appendLine("# 隐藏BL 来自 Shamiko 脚本")
|
||||||
appendLine(
|
appendLine(
|
||||||
"""
|
"""
|
||||||
check_reset_prop() {
|
RESETPROP_BIN="/data/adb/ksu/bin/resetprop"
|
||||||
local NAME=$1
|
|
||||||
local EXPECTED=$2
|
check_reset_prop() {
|
||||||
local VALUE=$(resetprop ${'$'}NAME)
|
local NAME=$1
|
||||||
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || resetprop ${'$'}NAME ${'$'}EXPECTED
|
local EXPECTED=$2
|
||||||
}
|
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
|
||||||
|
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
|
||||||
check_missing_prop() {
|
}
|
||||||
local NAME=$1
|
|
||||||
local EXPECTED=$2
|
check_missing_prop() {
|
||||||
local VALUE=$(resetprop ${'$'}NAME)
|
local NAME=$1
|
||||||
[ -z ${'$'}VALUE ] && resetprop ${'$'}NAME ${'$'}EXPECTED
|
local EXPECTED=$2
|
||||||
}
|
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
|
||||||
|
[ -z ${'$'}VALUE ] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
|
||||||
check_missing_match_prop() {
|
}
|
||||||
local NAME=$1
|
|
||||||
local EXPECTED=$2
|
check_missing_match_prop() {
|
||||||
local VALUE=$(resetprop ${'$'}NAME)
|
local NAME=$1
|
||||||
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || resetprop ${'$'}NAME ${'$'}EXPECTED
|
local EXPECTED=$2
|
||||||
[ -z ${'$'}VALUE ] && resetprop ${'$'}NAME ${'$'}EXPECTED
|
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
|
||||||
}
|
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
|
||||||
|
[ -z ${'$'}VALUE ] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
|
||||||
contains_reset_prop() {
|
}
|
||||||
local NAME=$1
|
|
||||||
local CONTAINS=$2
|
contains_reset_prop() {
|
||||||
local NEWVAL=$3
|
local NAME=$1
|
||||||
[[ "$(resetprop ${'$'}NAME)" = *"${'$'}CONTAINS"* ]] && resetprop ${'$'}NAME ${'$'}NEWVAL
|
local CONTAINS=$2
|
||||||
}
|
local NEWVAL=$3
|
||||||
""".trimIndent())
|
[[ "$("${'$'}RESETPROP_BIN" ${'$'}NAME)" = *"${'$'}CONTAINS"* ]] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}NEWVAL
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
appendLine()
|
appendLine()
|
||||||
|
appendLine("sleep 30")
|
||||||
appendLine("resetprop -w sys.boot_completed 0")
|
|
||||||
appendLine()
|
appendLine()
|
||||||
|
appendLine("\"${'$'}RESETPROP_BIN\" -w sys.boot_completed 0")
|
||||||
|
|
||||||
// 添加所有系统属性重置
|
// 添加所有系统属性重置
|
||||||
val systemProps = listOf(
|
val systemProps = listOf(
|
||||||
"ro.boot.vbmeta.invalidate_on_error" to "yes",
|
"ro.boot.vbmeta.invalidate_on_error" to "yes",
|
||||||
"ro.boot.vbmeta.avb_version" to "1.2",
|
"ro.boot.vbmeta.avb_version" to "1.2",
|
||||||
"ro.boot.vbmeta.hash_alg" to "sha256",
|
"ro.boot.vbmeta.hash_alg" to "sha256",
|
||||||
|
"ro.boot.vbmeta.size" to "19968",
|
||||||
"ro.boot.vbmeta.device_state" to "locked",
|
"ro.boot.vbmeta.device_state" to "locked",
|
||||||
"ro.boot.verifiedbootstate" to "green",
|
"ro.boot.verifiedbootstate" to "green",
|
||||||
"ro.boot.flash.locked" to "1",
|
"ro.boot.flash.locked" to "1",
|
||||||
@@ -327,6 +334,7 @@ object ScriptGenerator {
|
|||||||
/**
|
/**
|
||||||
* 生成boot-completed.sh脚本内容
|
* 生成boot-completed.sh脚本内容
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("SdCardPath")
|
||||||
private fun generateBootCompletedScript(config: SuSFSManager.ModuleConfig): String {
|
private fun generateBootCompletedScript(config: SuSFSManager.ModuleConfig): String {
|
||||||
return buildString {
|
return buildString {
|
||||||
appendLine("#!/system/bin/sh")
|
appendLine("#!/system/bin/sh")
|
||||||
@@ -352,6 +360,10 @@ object ScriptGenerator {
|
|||||||
// 路径设置和SUS路径设置
|
// 路径设置和SUS路径设置
|
||||||
if (config.susPaths.isNotEmpty()) {
|
if (config.susPaths.isNotEmpty()) {
|
||||||
generatePathSettingSection(config.androidDataPath, config.sdcardPath)
|
generatePathSettingSection(config.androidDataPath, config.sdcardPath)
|
||||||
|
appendLine()
|
||||||
|
appendLine("until [ -d \"/sdcard/Android\" ]; do sleep 1; done")
|
||||||
|
appendLine("sleep 45")
|
||||||
|
appendLine()
|
||||||
generateSusPathsSection(config.susPaths)
|
generateSusPathsSection(config.susPaths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,8 +390,8 @@ object ScriptGenerator {
|
|||||||
* 生成module.prop文件内容
|
* 生成module.prop文件内容
|
||||||
*/
|
*/
|
||||||
fun generateModuleProp(moduleId: String): String {
|
fun generateModuleProp(moduleId: String): String {
|
||||||
val moduleVersion = "v1.0.1"
|
val moduleVersion = "v1.0.2"
|
||||||
val moduleVersionCode = "1001"
|
val moduleVersionCode = "1002"
|
||||||
|
|
||||||
return """
|
return """
|
||||||
id=$moduleId
|
id=$moduleId
|
||||||
|
|||||||
@@ -368,7 +368,6 @@
|
|||||||
<string name="susfs_apply">適用</string>
|
<string name="susfs_apply">適用</string>
|
||||||
<!-- SuSFS Reset Confirmation -->
|
<!-- SuSFS Reset Confirmation -->
|
||||||
<string name="susfs_reset_confirm_title">リセットを確認</string>
|
<string name="susfs_reset_confirm_title">リセットを確認</string>
|
||||||
<string name="susfs_reset_confirm">リセットを確認</string>
|
|
||||||
<!-- SuSFS Toast Messages -->
|
<!-- SuSFS Toast Messages -->
|
||||||
<string name="susfs_binary_not_found">ksu_susfs ファイルが見つかりません</string>
|
<string name="susfs_binary_not_found">ksu_susfs ファイルが見つかりません</string>
|
||||||
<string name="susfs_command_failed">SuSFS コマンドの実行に失敗しました</string>
|
<string name="susfs_command_failed">SuSFS コマンドの実行に失敗しました</string>
|
||||||
@@ -490,11 +489,9 @@
|
|||||||
<string name="file_or_directory_path_label">ファイルまたはディレクトリのパス</string>
|
<string name="file_or_directory_path_label">ファイルまたはディレクトリのパス</string>
|
||||||
<string name="hint_use_default_value">ヒント: オリジナルの値を使用するには「default」を使用します</string>
|
<string name="hint_use_default_value">ヒント: オリジナルの値を使用するには「default」を使用します</string>
|
||||||
<string name="add_kstat_path_title">Kstat のパスを追加</string>
|
<string name="add_kstat_path_title">Kstat のパスを追加</string>
|
||||||
<string name="kstat_command_description">このコマンドはパスがバインドマウントまたは、オーバーレイを開始する前に追加し、元の状態情報をカーネルメモリに保存するために使用されます。</string>
|
|
||||||
<string name="add">追加</string>
|
<string name="add">追加</string>
|
||||||
<string name="reset_kstat_config_title">Kstat の構成をリセット</string>
|
<string name="reset_kstat_config_title">Kstat の構成をリセット</string>
|
||||||
<string name="reset_kstat_config_message">すべての Kstat の構成を消去しますか?この操作は元に戻せません。</string>
|
<string name="reset_kstat_config_message">すべての Kstat の構成を消去しますか?この操作は元に戻せません。</string>
|
||||||
<string name="confirm_reset">リセットを確認</string>
|
|
||||||
<string name="kstat_config_description_title">Kstat の構成の説明</string>
|
<string name="kstat_config_description_title">Kstat の構成の説明</string>
|
||||||
<string name="kstat_config_description_add_statically">• add_sus_kstat_statically: ファイル、ディレクトリの静的な状態情報</string>
|
<string name="kstat_config_description_add_statically">• add_sus_kstat_statically: ファイル、ディレクトリの静的な状態情報</string>
|
||||||
<string name="kstat_config_description_add">• add_sus_kstat: バインドマウント前にパスを追加して元の状態情報を保存します</string>
|
<string name="kstat_config_description_add">• add_sus_kstat: バインドマウント前にパスを追加して元の状態情報を保存します</string>
|
||||||
|
|||||||
@@ -369,7 +369,6 @@
|
|||||||
<string name="susfs_apply">Применить</string>
|
<string name="susfs_apply">Применить</string>
|
||||||
<!-- SuSFS Reset Confirmation -->
|
<!-- SuSFS Reset Confirmation -->
|
||||||
<string name="susfs_reset_confirm_title">Подтвердить сброс</string>
|
<string name="susfs_reset_confirm_title">Подтвердить сброс</string>
|
||||||
<string name="susfs_reset_confirm">Подтвердить сброс</string>
|
|
||||||
<!-- SuSFS Toast Messages -->
|
<!-- SuSFS Toast Messages -->
|
||||||
<string name="susfs_binary_not_found">Не удалось найти файл ksu_susfs</string>
|
<string name="susfs_binary_not_found">Не удалось найти файл ksu_susfs</string>
|
||||||
<string name="susfs_command_failed">Выполнение команды SuSFS не удалось</string>
|
<string name="susfs_command_failed">Выполнение команды SuSFS не удалось</string>
|
||||||
@@ -476,11 +475,9 @@
|
|||||||
<string name="file_or_directory_path_label">Путь к файлу/папке</string>
|
<string name="file_or_directory_path_label">Путь к файлу/папке</string>
|
||||||
<string name="hint_use_default_value">Подсказка: Вы можете использовать «по умолчанию» для использования оригинального значения</string>
|
<string name="hint_use_default_value">Подсказка: Вы можете использовать «по умолчанию» для использования оригинального значения</string>
|
||||||
<string name="add_kstat_path_title">Добавить путь Kstat</string>
|
<string name="add_kstat_path_title">Добавить путь Kstat</string>
|
||||||
<string name="kstat_command_description">Эта команда используется для добавления перед тем как путь монтируется в bind-mounted или overlaid, сохраняя исходную информацию о состоянии в памяти ядра.</string>
|
|
||||||
<string name="add">Добавить</string>
|
<string name="add">Добавить</string>
|
||||||
<string name="reset_kstat_config_title">Сбросить конфигурацию Kstat</string>
|
<string name="reset_kstat_config_title">Сбросить конфигурацию Kstat</string>
|
||||||
<string name="reset_kstat_config_message">Вы уверены, что хотите очистить все конфигурации Kstat? Это действие нельзя отменить.</string>
|
<string name="reset_kstat_config_message">Вы уверены, что хотите очистить все конфигурации Kstat? Это действие нельзя отменить.</string>
|
||||||
<string name="confirm_reset">Подтвердить сброс</string>
|
|
||||||
<string name="kstat_config_description_title">Описание конфигурации Kstat</string>
|
<string name="kstat_config_description_title">Описание конфигурации Kstat</string>
|
||||||
<string name="kstat_config_description_add_statically">• add_sus_kstat_staticall: Статическая статистика информации о файлах/директориях</string>
|
<string name="kstat_config_description_add_statically">• add_sus_kstat_staticall: Статическая статистика информации о файлах/директориях</string>
|
||||||
<string name="kstat_config_description_add">• add_sus_kstat: Добавить путь перед привязкой, сохраняя исходную статистику</string>
|
<string name="kstat_config_description_add">• add_sus_kstat: Добавить путь перед привязкой, сохраняя исходную статистику</string>
|
||||||
|
|||||||
@@ -367,7 +367,6 @@
|
|||||||
<string name="susfs_apply">Uygula</string>
|
<string name="susfs_apply">Uygula</string>
|
||||||
<!-- SuSFS Reset Confirmation -->
|
<!-- SuSFS Reset Confirmation -->
|
||||||
<string name="susfs_reset_confirm_title">Sıfırlamayı Onayla</string>
|
<string name="susfs_reset_confirm_title">Sıfırlamayı Onayla</string>
|
||||||
<string name="susfs_reset_confirm">Sıfırlamayı Onayla</string>
|
|
||||||
<!-- SuSFS Toast Messages -->
|
<!-- SuSFS Toast Messages -->
|
||||||
<string name="susfs_binary_not_found">ksu_susfs dosyası bulunamadı</string>
|
<string name="susfs_binary_not_found">ksu_susfs dosyası bulunamadı</string>
|
||||||
<string name="susfs_command_failed">SuSFS komut çalıştırma başarısız</string>
|
<string name="susfs_command_failed">SuSFS komut çalıştırma başarısız</string>
|
||||||
|
|||||||
@@ -367,7 +367,6 @@
|
|||||||
<string name="susfs_apply">Áp dụng</string>
|
<string name="susfs_apply">Áp dụng</string>
|
||||||
<!-- SuSFS Reset Confirmation -->
|
<!-- SuSFS Reset Confirmation -->
|
||||||
<string name="susfs_reset_confirm_title">Xác nhận khôi phục</string>
|
<string name="susfs_reset_confirm_title">Xác nhận khôi phục</string>
|
||||||
<string name="susfs_reset_confirm">Xác nhận khôi phục</string>
|
|
||||||
<!-- SuSFS Toast Messages -->
|
<!-- SuSFS Toast Messages -->
|
||||||
<string name="susfs_binary_not_found">Không tìm thấy file ksu_susfs</string>
|
<string name="susfs_binary_not_found">Không tìm thấy file ksu_susfs</string>
|
||||||
<string name="susfs_command_failed">Thực hiện lệnh SuSFS thất bại</string>
|
<string name="susfs_command_failed">Thực hiện lệnh SuSFS thất bại</string>
|
||||||
@@ -489,11 +488,9 @@
|
|||||||
<string name="file_or_directory_path_label">Đường dẫn File/Folder</string>
|
<string name="file_or_directory_path_label">Đường dẫn File/Folder</string>
|
||||||
<string name="hint_use_default_value">Gợi ý: Bạn có thể sử dụng \"default\" để thiết lập giá trị ban đầu</string>
|
<string name="hint_use_default_value">Gợi ý: Bạn có thể sử dụng \"default\" để thiết lập giá trị ban đầu</string>
|
||||||
<string name="add_kstat_path_title">Thêm Đường dẫn Kstat</string>
|
<string name="add_kstat_path_title">Thêm Đường dẫn Kstat</string>
|
||||||
<string name="kstat_command_description">Lệnh này được sử dụng để thêm trước khi đường dẫn được mount hoặc ghi đè nhằm lưu trữ thông tin trạng thái ban đầu trong bộ nhớ hạt nhân</string>
|
|
||||||
<string name="add">Thêm</string>
|
<string name="add">Thêm</string>
|
||||||
<string name="reset_kstat_config_title">Khôi phục Cấu hình Kstat</string>
|
<string name="reset_kstat_config_title">Khôi phục Cấu hình Kstat</string>
|
||||||
<string name="reset_kstat_config_message">Bạn có chắc chắn muốn xóa tất cả cấu hình Kstat không? Không thể hoàn tác hành động này</string>
|
<string name="reset_kstat_config_message">Bạn có chắc chắn muốn xóa tất cả cấu hình Kstat không? Không thể hoàn tác hành động này</string>
|
||||||
<string name="confirm_reset">Xác nhận khôi phục</string>
|
|
||||||
<string name="kstat_config_description_title">Mô tả cấu hình Kstat</string>
|
<string name="kstat_config_description_title">Mô tả cấu hình Kstat</string>
|
||||||
<string name="kstat_config_description_add_statically">• add_sus_kstat_statically: Thông tin thống kê cấu hình tĩnh của các File/Folder</string>
|
<string name="kstat_config_description_add_statically">• add_sus_kstat_statically: Thông tin thống kê cấu hình tĩnh của các File/Folder</string>
|
||||||
<string name="kstat_config_description_add">• add_sus_kstat: Thêm đường dẫn trước khi mount để lưu trữ thông tin trạng thái ban đầu</string>
|
<string name="kstat_config_description_add">• add_sus_kstat: Thêm đường dẫn trước khi mount để lưu trữ thông tin trạng thái ban đầu</string>
|
||||||
|
|||||||
@@ -367,7 +367,6 @@
|
|||||||
<string name="susfs_apply">应用</string>
|
<string name="susfs_apply">应用</string>
|
||||||
<!-- SuSFS Reset Confirmation -->
|
<!-- SuSFS Reset Confirmation -->
|
||||||
<string name="susfs_reset_confirm_title">确认重置</string>
|
<string name="susfs_reset_confirm_title">确认重置</string>
|
||||||
<string name="susfs_reset_confirm">确认重置</string>
|
|
||||||
<!-- SuSFS Toast Messages -->
|
<!-- SuSFS Toast Messages -->
|
||||||
<string name="susfs_binary_not_found">无法找到 ksu_susfs 文件</string>
|
<string name="susfs_binary_not_found">无法找到 ksu_susfs 文件</string>
|
||||||
<string name="susfs_command_failed">SuSFS 命令执行失败</string>
|
<string name="susfs_command_failed">SuSFS 命令执行失败</string>
|
||||||
@@ -489,11 +488,9 @@
|
|||||||
<string name="file_or_directory_path_label">文件/目录路径</string>
|
<string name="file_or_directory_path_label">文件/目录路径</string>
|
||||||
<string name="hint_use_default_value">提示:可以使用 “default” 来使用原始值</string>
|
<string name="hint_use_default_value">提示:可以使用 “default” 来使用原始值</string>
|
||||||
<string name="add_kstat_path_title">添加 Kstat 路径</string>
|
<string name="add_kstat_path_title">添加 Kstat 路径</string>
|
||||||
<string name="kstat_command_description">此命令用于在路径被绑定挂载或覆盖之前添加,用于在内核内存中存储原始 stat 信息</string>
|
|
||||||
<string name="add">添加</string>
|
<string name="add">添加</string>
|
||||||
<string name="reset_kstat_config_title">重置 Kstat 配置</string>
|
<string name="reset_kstat_config_title">重置 Kstat 配置</string>
|
||||||
<string name="reset_kstat_config_message">确定要清除所有 Kstat 配置吗?此操作不可撤销</string>
|
<string name="reset_kstat_config_message">确定要清除所有 Kstat 配置吗?此操作不可撤销</string>
|
||||||
<string name="confirm_reset">确认重置</string>
|
|
||||||
<string name="kstat_config_description_title">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_statically">• add_sus_kstat_statically: 静态配置文件/目录的 stat 信息</string>
|
||||||
<string name="kstat_config_description_add">• add_sus_kstat: 在绑定挂载前添加路径,存储原始 stat 信息</string>
|
<string name="kstat_config_description_add">• add_sus_kstat: 在绑定挂载前添加路径,存储原始 stat 信息</string>
|
||||||
@@ -520,4 +517,24 @@
|
|||||||
<string name="susfs_android_data_path_set">Android Data路径已设置为: %s</string>
|
<string name="susfs_android_data_path_set">Android Data路径已设置为: %s</string>
|
||||||
<string name="susfs_sdcard_path_set">SD卡路径已设置为: %s</string>
|
<string name="susfs_sdcard_path_set">SD卡路径已设置为: %s</string>
|
||||||
<string name="susfs_path_setup_warning">路径设置可能未完全成功,但将继续添加SUS路径</string>
|
<string name="susfs_path_setup_warning">路径设置可能未完全成功,但将继续添加SUS路径</string>
|
||||||
|
<!-- 备份和还原相关字符串 -->
|
||||||
|
<string name="susfs_backup_title">备份</string>
|
||||||
|
<string name="susfs_backup_description">创建所有SuSFS配置的备份。备份文件将包含所有设置、路径和配置信息。</string>
|
||||||
|
<string name="susfs_backup_create">创建备份</string>
|
||||||
|
<string name="susfs_backup_success">备份创建成功:%s</string>
|
||||||
|
<string name="susfs_backup_failed">备份创建失败:%s</string>
|
||||||
|
<string name="susfs_backup_file_not_found">备份文件未找到</string>
|
||||||
|
<string name="susfs_backup_invalid_format">无效的备份文件格式</string>
|
||||||
|
<string name="susfs_backup_version_mismatch">备份版本不匹配,但将尝试还原</string>
|
||||||
|
<string name="susfs_restore_title">还原</string>
|
||||||
|
<string name="susfs_restore_description">从备份文件还原SuSFS配置。这将覆盖所有当前设置。</string>
|
||||||
|
<string name="susfs_restore_select_file">选择备份文件</string>
|
||||||
|
<string name="susfs_restore_success">配置还原成功,备份创建于 %s,来自设备:%s</string>
|
||||||
|
<string name="susfs_restore_failed">还原失败:%s</string>
|
||||||
|
<string name="susfs_restore_confirm_title">确认还原</string>
|
||||||
|
<string name="susfs_restore_confirm_description">这将覆盖所有当前的SuSFS配置。您确定要继续吗?</string>
|
||||||
|
<string name="susfs_restore_confirm">还原</string>
|
||||||
|
<string name="susfs_backup_info_date">备份日期:%s</string>
|
||||||
|
<string name="susfs_backup_info_device">设备:%s</string>
|
||||||
|
<string name="susfs_backup_info_version">版本:%s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -366,7 +366,6 @@
|
|||||||
<string name="susfs_apply">應用</string>
|
<string name="susfs_apply">應用</string>
|
||||||
<!-- SuSFS Reset Confirmation -->
|
<!-- SuSFS Reset Confirmation -->
|
||||||
<string name="susfs_reset_confirm_title">確認重置</string>
|
<string name="susfs_reset_confirm_title">確認重置</string>
|
||||||
<string name="susfs_reset_confirm">確認重置</string>
|
|
||||||
<!-- SuSFS Toast Messages -->
|
<!-- SuSFS Toast Messages -->
|
||||||
<string name="susfs_binary_not_found">無法找到 ksu_susfs 文件</string>
|
<string name="susfs_binary_not_found">無法找到 ksu_susfs 文件</string>
|
||||||
<string name="susfs_command_failed">SuSFS 命令執行失敗</string>
|
<string name="susfs_command_failed">SuSFS 命令執行失敗</string>
|
||||||
@@ -488,11 +487,9 @@
|
|||||||
<string name="file_or_directory_path_label">文件/目錄路徑</string>
|
<string name="file_or_directory_path_label">文件/目錄路徑</string>
|
||||||
<string name="hint_use_default_value">提示:可以使用 “default” 來使用原始值</string>
|
<string name="hint_use_default_value">提示:可以使用 “default” 來使用原始值</string>
|
||||||
<string name="add_kstat_path_title">添加 Kstat 路徑</string>
|
<string name="add_kstat_path_title">添加 Kstat 路徑</string>
|
||||||
<string name="kstat_command_description">此命令用於在路徑被綁定掛載或覆蓋之前添加,用於在核心內存中存儲原始 stat 信息</string>
|
|
||||||
<string name="add">添加</string>
|
<string name="add">添加</string>
|
||||||
<string name="reset_kstat_config_title">重置 Kstat 配置</string>
|
<string name="reset_kstat_config_title">重置 Kstat 配置</string>
|
||||||
<string name="reset_kstat_config_message">確定要清除所有 Kstat 配置嗎?此操作不可撤銷</string>
|
<string name="reset_kstat_config_message">確定要清除所有 Kstat 配置嗎?此操作不可撤銷</string>
|
||||||
<string name="confirm_reset">確認重置</string>
|
|
||||||
<string name="kstat_config_description_title">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_statically">• add_sus_kstat_statically: 靜態配置文件/目錄嘅 stat 信息</string>
|
||||||
<string name="kstat_config_description_add">• add_sus_kstat: 在綁定掛載前添加路徑,存儲原始 stat 信息</string>
|
<string name="kstat_config_description_add">• add_sus_kstat: 在綁定掛載前添加路徑,存儲原始 stat 信息</string>
|
||||||
|
|||||||
@@ -367,7 +367,6 @@
|
|||||||
<string name="susfs_apply">應用</string>
|
<string name="susfs_apply">應用</string>
|
||||||
<!-- SuSFS Reset Confirmation -->
|
<!-- SuSFS Reset Confirmation -->
|
||||||
<string name="susfs_reset_confirm_title">確認重設</string>
|
<string name="susfs_reset_confirm_title">確認重設</string>
|
||||||
<string name="susfs_reset_confirm">確認重設</string>
|
|
||||||
<!-- SuSFS Toast Messages -->
|
<!-- SuSFS Toast Messages -->
|
||||||
<string name="susfs_binary_not_found">無法找到 ksu_susfs 檔案</string>
|
<string name="susfs_binary_not_found">無法找到 ksu_susfs 檔案</string>
|
||||||
<string name="susfs_command_failed">SuSFS 指令執行失敗</string>
|
<string name="susfs_command_failed">SuSFS 指令執行失敗</string>
|
||||||
@@ -489,11 +488,9 @@
|
|||||||
<string name="file_or_directory_path_label">檔案/目錄路徑</string>
|
<string name="file_or_directory_path_label">檔案/目錄路徑</string>
|
||||||
<string name="hint_use_default_value">提示:可使用「default」來使用原始值</string>
|
<string name="hint_use_default_value">提示:可使用「default」來使用原始值</string>
|
||||||
<string name="add_kstat_path_title">新增 Kstat 路徑</string>
|
<string name="add_kstat_path_title">新增 Kstat 路徑</string>
|
||||||
<string name="kstat_command_description">此命令用於在路徑被綁定掛載或覆蓋之前新增,用於在核心記憶體中儲存原始 stat 資訊</string>
|
|
||||||
<string name="add">新增</string>
|
<string name="add">新增</string>
|
||||||
<string name="reset_kstat_config_title">重置 Kstat 設定</string>
|
<string name="reset_kstat_config_title">重置 Kstat 設定</string>
|
||||||
<string name="reset_kstat_config_message">確定要清除所有 Kstat 設定嗎?此操作不可撤銷</string>
|
<string name="reset_kstat_config_message">確定要清除所有 Kstat 設定嗎?此操作不可撤銷</string>
|
||||||
<string name="confirm_reset">確認重置</string>
|
|
||||||
<string name="kstat_config_description_title">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_statically">• add_sus_kstat_statically:靜態設定檔案/目錄的 stat 資訊</string>
|
||||||
<string name="kstat_config_description_add">• add_sus_kstat:在綁定掛載前新增路徑,儲存原始 stat 資訊</string>
|
<string name="kstat_config_description_add">• add_sus_kstat:在綁定掛載前新增路徑,儲存原始 stat 資訊</string>
|
||||||
|
|||||||
@@ -369,7 +369,6 @@
|
|||||||
<string name="susfs_apply">Apply</string>
|
<string name="susfs_apply">Apply</string>
|
||||||
<!-- SuSFS Reset Confirmation -->
|
<!-- SuSFS Reset Confirmation -->
|
||||||
<string name="susfs_reset_confirm_title">Confirm Reset</string>
|
<string name="susfs_reset_confirm_title">Confirm Reset</string>
|
||||||
<string name="susfs_reset_confirm">Confirm Reset</string>
|
|
||||||
<!-- SuSFS Toast Messages -->
|
<!-- SuSFS Toast Messages -->
|
||||||
<string name="susfs_binary_not_found">Cannot find ksu_susfs file</string>
|
<string name="susfs_binary_not_found">Cannot find ksu_susfs file</string>
|
||||||
<string name="susfs_command_failed">SuSFS command execution failed</string>
|
<string name="susfs_command_failed">SuSFS command execution failed</string>
|
||||||
@@ -491,11 +490,9 @@
|
|||||||
<string name="file_or_directory_path_label">File/Directory Path</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="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="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="add">Add</string>
|
||||||
<string name="reset_kstat_config_title">Reset Kstat Configuration</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="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_description_title">Kstat Configuration Description</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_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_add">• add_sus_kstat: Add path before bind mount, storing original stat info</string>
|
||||||
@@ -522,4 +519,24 @@
|
|||||||
<string name="susfs_android_data_path_set">Android Data path has been set to: %s</string>
|
<string name="susfs_android_data_path_set">Android Data path has been set to: %s</string>
|
||||||
<string name="susfs_sdcard_path_set">SD card path has been set to: %s</string>
|
<string name="susfs_sdcard_path_set">SD card path has been set to: %s</string>
|
||||||
<string name="susfs_path_setup_warning">Path setup may not be fully successful, but SUS paths will continue to be added</string>
|
<string name="susfs_path_setup_warning">Path setup may not be fully successful, but SUS paths will continue to be added</string>
|
||||||
|
<!-- 备份和还原相关字符串 -->
|
||||||
|
<string name="susfs_backup_title">Backup</string>
|
||||||
|
<string name="susfs_backup_description">Create a backup of all SuSFS configurations. The backup file will include all settings, paths, and configurations.</string>
|
||||||
|
<string name="susfs_backup_create">Create Backup</string>
|
||||||
|
<string name="susfs_backup_success">Backup created successfully: %s</string>
|
||||||
|
<string name="susfs_backup_failed">Backup creation failed: %s</string>
|
||||||
|
<string name="susfs_backup_file_not_found">Backup file not found</string>
|
||||||
|
<string name="susfs_backup_invalid_format">Invalid backup file format</string>
|
||||||
|
<string name="susfs_backup_version_mismatch">Backup version mismatch, but will attempt to restore</string>
|
||||||
|
<string name="susfs_restore_title">Restore</string>
|
||||||
|
<string name="susfs_restore_description">Restore SuSFS configurations from a backup file. This will overwrite all current settings.</string>
|
||||||
|
<string name="susfs_restore_select_file">Select Backup File</string>
|
||||||
|
<string name="susfs_restore_success">Configuration restored successfully from backup created on %s from device: %s</string>
|
||||||
|
<string name="susfs_restore_failed">Restore failed: %s</string>
|
||||||
|
<string name="susfs_restore_confirm_title">Confirm Restore</string>
|
||||||
|
<string name="susfs_restore_confirm_description">This will overwrite all current SuSFS configurations. Are you sure you want to continue?</string>
|
||||||
|
<string name="susfs_restore_confirm">Restore</string>
|
||||||
|
<string name="susfs_backup_info_date">Backup Date: %s</string>
|
||||||
|
<string name="susfs_backup_info_device">Device: %s</string>
|
||||||
|
<string name="susfs_backup_info_version">Version: %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user