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:
ShirkNeko
2025-06-28 23:19:18 +08:00
parent c49a66d1af
commit 8399f14fad
11 changed files with 589 additions and 68 deletions

View File

@@ -1,6 +1,8 @@
package com.sukisu.ultra.ui.screen
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.Box
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.isSusVersion_1_5_8
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
/**
* 标签页枚举类
@@ -123,8 +127,6 @@ fun SuSFSConfigScreen(
var isLoading by remember { mutableStateOf(false) }
var showConfirmReset 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) }
// 槽位信息相关状态
@@ -165,6 +167,13 @@ fun SuSFSConfigScreen(
var showResetUmountsDialog 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())
// 实时判断是否可以启用开机自启动
@@ -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() {
coroutineScope.launch {
@@ -198,8 +266,6 @@ fun SuSFSConfigScreen(
unameValue = SuSFSManager.getUnameValue(context)
buildTimeValue = SuSFSManager.getBuildTimeValue(context)
autoStartEnabled = SuSFSManager.isAutoStartEnabled(context)
lastAppliedValue = SuSFSManager.getLastAppliedValue(context)
lastAppliedBuildTime = SuSFSManager.getLastAppliedBuildTime(context)
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
susPaths = SuSFSManager.getSusPaths(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(
showDialog = showSlotInfoDialog,
@@ -366,8 +609,6 @@ fun SuSFSConfigScreen(
if (SuSFSManager.resetToDefault(context)) {
unameValue = "default"
buildTimeValue = "default"
lastAppliedValue = "default"
lastAppliedBuildTime = "default"
autoStartEnabled = false
}
isLoading = false
@@ -530,8 +771,6 @@ fun SuSFSConfigScreen(
val finalBuildTimeValue = buildTimeValue.trim().ifBlank { "default" }
val success = SuSFSManager.setUname(context, finalUnameValue, finalBuildTimeValue)
if (success) {
lastAppliedValue = finalUnameValue
lastAppliedBuildTime = finalBuildTimeValue
SuSFSManager.saveExecuteInPostFsData(context, executeInPostFsData)
}
isLoading = false
@@ -780,7 +1019,9 @@ fun SuSFSConfigScreen(
}
},
onShowSlotInfo = { showSlotInfoDialog = true },
context = context
context = context,
onShowBackupDialog = { showBackupDialog = true },
onShowRestoreDialog = { showRestoreDialog = true }
)
}
SuSFSTab.SUS_PATHS -> {
@@ -939,7 +1180,9 @@ private fun BasicSettingsContent(
isLoading: Boolean,
onAutoStartToggle: (Boolean) -> Unit,
onShowSlotInfo: () -> Unit,
context: android.content.Context
context: android.content.Context,
onShowBackupDialog: () -> Unit,
onShowRestoreDialog: () -> Unit
) {
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
)
}
}
}
}

View File

@@ -16,6 +16,9 @@ import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import androidx.core.content.edit
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
/**
* SuSFS 配置管理器
@@ -44,6 +47,7 @@ object SuSFSManager {
private const val MODULE_ID = "susfs_manager"
private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID"
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 CommandResult(val isSuccess: Boolean, val output: String, val errorOutput: String = "")
@@ -54,6 +58,60 @@ object SuSFSManager {
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 =
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) =
getPrefs(context).edit { putBoolean(KEY_AUTO_START_ENABLED, enabled) }
@@ -261,6 +316,154 @@ object SuSFSManager {
fun getSdcardPath(context: Context): String =
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) {
try {

View File

@@ -53,6 +53,7 @@ object ScriptGenerator {
/**
* 生成service.sh脚本内容
*/
@SuppressLint("SdCardPath")
private fun generateServiceScript(config: SuSFSManager.ModuleConfig): String {
return buildString {
appendLine("#!/system/bin/sh")
@@ -67,6 +68,9 @@ object ScriptGenerator {
if (shouldConfigureInService(config)) {
// 添加SUS路径 (仅在不支持隐藏挂载时)
if (!config.support158 && config.susPaths.isNotEmpty()) {
appendLine()
appendLine("until [ -d \"/sdcard/Android\" ]; do sleep 1; done")
appendLine("sleep 45")
generateSusPathsSection(config.susPaths)
}
@@ -162,45 +166,48 @@ object ScriptGenerator {
appendLine("# 隐藏BL 来自 Shamiko 脚本")
appendLine(
"""
RESETPROP_BIN="/data/adb/ksu/bin/resetprop"
check_reset_prop() {
local NAME=$1
local EXPECTED=$2
local VALUE=$(resetprop ${'$'}NAME)
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || resetprop ${'$'}NAME ${'$'}EXPECTED
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
}
check_missing_prop() {
local NAME=$1
local EXPECTED=$2
local VALUE=$(resetprop ${'$'}NAME)
[ -z ${'$'}VALUE ] && resetprop ${'$'}NAME ${'$'}EXPECTED
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
[ -z ${'$'}VALUE ] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
}
check_missing_match_prop() {
local NAME=$1
local EXPECTED=$2
local VALUE=$(resetprop ${'$'}NAME)
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || resetprop ${'$'}NAME ${'$'}EXPECTED
[ -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
local NEWVAL=$3
[[ "$(resetprop ${'$'}NAME)" = *"${'$'}CONTAINS"* ]] && resetprop ${'$'}NAME ${'$'}NEWVAL
[[ "$("${'$'}RESETPROP_BIN" ${'$'}NAME)" = *"${'$'}CONTAINS"* ]] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}NEWVAL
}
""".trimIndent())
appendLine()
appendLine("resetprop -w sys.boot_completed 0")
appendLine("sleep 30")
appendLine()
appendLine("\"${'$'}RESETPROP_BIN\" -w sys.boot_completed 0")
// 添加所有系统属性重置
val systemProps = listOf(
"ro.boot.vbmeta.invalidate_on_error" to "yes",
"ro.boot.vbmeta.avb_version" to "1.2",
"ro.boot.vbmeta.hash_alg" to "sha256",
"ro.boot.vbmeta.size" to "19968",
"ro.boot.vbmeta.device_state" to "locked",
"ro.boot.verifiedbootstate" to "green",
"ro.boot.flash.locked" to "1",
@@ -327,6 +334,7 @@ object ScriptGenerator {
/**
* 生成boot-completed.sh脚本内容
*/
@SuppressLint("SdCardPath")
private fun generateBootCompletedScript(config: SuSFSManager.ModuleConfig): String {
return buildString {
appendLine("#!/system/bin/sh")
@@ -352,6 +360,10 @@ object ScriptGenerator {
// 路径设置和SUS路径设置
if (config.susPaths.isNotEmpty()) {
generatePathSettingSection(config.androidDataPath, config.sdcardPath)
appendLine()
appendLine("until [ -d \"/sdcard/Android\" ]; do sleep 1; done")
appendLine("sleep 45")
appendLine()
generateSusPathsSection(config.susPaths)
}
}
@@ -378,8 +390,8 @@ object ScriptGenerator {
* 生成module.prop文件内容
*/
fun generateModuleProp(moduleId: String): String {
val moduleVersion = "v1.0.1"
val moduleVersionCode = "1001"
val moduleVersion = "v1.0.2"
val moduleVersionCode = "1002"
return """
id=$moduleId

View File

@@ -368,7 +368,6 @@
<string name="susfs_apply">適用</string>
<!-- SuSFS Reset Confirmation -->
<string name="susfs_reset_confirm_title">リセットを確認</string>
<string name="susfs_reset_confirm">リセットを確認</string>
<!-- SuSFS Toast Messages -->
<string name="susfs_binary_not_found">ksu_susfs ファイルが見つかりません</string>
<string name="susfs_command_failed">SuSFS コマンドの実行に失敗しました</string>
@@ -490,11 +489,9 @@
<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">このコマンドはパスがバインドマウントまたは、オーバーレイを開始する前に追加し、元の状態情報をカーネルメモリに保存するために使用されます。</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_description_title">Kstat の構成の説明</string>
<string name="kstat_config_description_add_statically">• add_sus_kstat_statically: ファイル、ディレクトリの静的な状態情報</string>
<string name="kstat_config_description_add">• add_sus_kstat: バインドマウント前にパスを追加して元の状態情報を保存します</string>

View File

@@ -369,7 +369,6 @@
<string name="susfs_apply">Применить</string>
<!-- SuSFS Reset Confirmation -->
<string name="susfs_reset_confirm_title">Подтвердить сброс</string>
<string name="susfs_reset_confirm">Подтвердить сброс</string>
<!-- SuSFS Toast Messages -->
<string name="susfs_binary_not_found">Не удалось найти файл ksu_susfs</string>
<string name="susfs_command_failed">Выполнение команды SuSFS не удалось</string>
@@ -476,11 +475,9 @@
<string name="file_or_directory_path_label">Путь к файлу/папке</string>
<string name="hint_use_default_value">Подсказка: Вы можете использовать «по умолчанию» для использования оригинального значения</string>
<string name="add_kstat_path_title">Добавить путь Kstat</string>
<string name="kstat_command_description">Эта команда используется для добавления перед тем как путь монтируется в bind-mounted или overlaid, сохраняя исходную информацию о состоянии в памяти ядра.</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_description_title">Описание конфигурации Kstat</string>
<string name="kstat_config_description_add_statically">• add_sus_kstat_staticall: Статическая статистика информации о файлах/директориях</string>
<string name="kstat_config_description_add">• add_sus_kstat: Добавить путь перед привязкой, сохраняя исходную статистику</string>

View File

@@ -367,7 +367,6 @@
<string name="susfs_apply">Uygula</string>
<!-- SuSFS Reset Confirmation -->
<string name="susfs_reset_confirm_title">Sıfırlamayı Onayla</string>
<string name="susfs_reset_confirm">Sıfırlamayı Onayla</string>
<!-- SuSFS Toast Messages -->
<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>

View File

@@ -367,7 +367,6 @@
<string name="susfs_apply">Áp dụng</string>
<!-- SuSFS Reset Confirmation -->
<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 -->
<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>
@@ -489,11 +488,9 @@
<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="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="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="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_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>

View File

@@ -367,7 +367,6 @@
<string name="susfs_apply">应用</string>
<!-- SuSFS Reset Confirmation -->
<string name="susfs_reset_confirm_title">确认重置</string>
<string name="susfs_reset_confirm">确认重置</string>
<!-- SuSFS Toast Messages -->
<string name="susfs_binary_not_found">无法找到 ksu_susfs 文件</string>
<string name="susfs_command_failed">SuSFS 命令执行失败</string>
@@ -489,11 +488,9 @@
<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_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>
@@ -520,4 +517,24 @@
<string name="susfs_android_data_path_set">Android Data路径已设置为: %s</string>
<string name="susfs_sdcard_path_set">SD卡路径已设置为: %s</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>

View File

@@ -366,7 +366,6 @@
<string name="susfs_apply">應用</string>
<!-- SuSFS Reset Confirmation -->
<string name="susfs_reset_confirm_title">確認重置</string>
<string name="susfs_reset_confirm">確認重置</string>
<!-- SuSFS Toast Messages -->
<string name="susfs_binary_not_found">無法找到 ksu_susfs 文件</string>
<string name="susfs_command_failed">SuSFS 命令執行失敗</string>
@@ -488,11 +487,9 @@
<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_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>

View File

@@ -367,7 +367,6 @@
<string name="susfs_apply">應用</string>
<!-- SuSFS Reset Confirmation -->
<string name="susfs_reset_confirm_title">確認重設</string>
<string name="susfs_reset_confirm">確認重設</string>
<!-- SuSFS Toast Messages -->
<string name="susfs_binary_not_found">無法找到 ksu_susfs 檔案</string>
<string name="susfs_command_failed">SuSFS 指令執行失敗</string>
@@ -489,11 +488,9 @@
<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_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>

View File

@@ -369,7 +369,6 @@
<string name="susfs_apply">Apply</string>
<!-- SuSFS Reset Confirmation -->
<string name="susfs_reset_confirm_title">Confirm Reset</string>
<string name="susfs_reset_confirm">Confirm Reset</string>
<!-- SuSFS Toast Messages -->
<string name="susfs_binary_not_found">Cannot find ksu_susfs file</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="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_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>
@@ -522,4 +519,24 @@
<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_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>