diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt index 6fe28a43..2fdbef20 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt @@ -8,14 +8,14 @@ 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.File import java.io.FileOutputStream import java.io.IOException -import java.io.File +import androidx.core.content.edit /** * SuSFS 配置管理器 @@ -25,10 +25,7 @@ object SuSFSManager { private const val PREFS_NAME = "susfs_config" private const val KEY_UNAME_VALUE = "uname_value" private const val KEY_BUILD_TIME_VALUE = "build_time_value" - private const val KEY_IS_ENABLED = "is_enabled" private const val KEY_AUTO_START_ENABLED = "auto_start_enabled" - private const val KEY_LAST_APPLIED_VALUE = "last_applied_value" - private const val KEY_LAST_APPLIED_BUILD_TIME = "last_applied_build_time" private const val KEY_SUS_PATHS = "sus_paths" private const val KEY_SUS_MOUNTS = "sus_mounts" private const val KEY_TRY_UMOUNTS = "try_umounts" @@ -38,70 +35,35 @@ object SuSFSManager { private const val KEY_EXECUTE_IN_POST_FS_DATA = "execute_in_post_fs_data" private const val KEY_KSTAT_CONFIGS = "kstat_configs" private const val KEY_ADD_KSTAT_PATHS = "add_kstat_paths" + // 常量 private const val SUSFS_BINARY_BASE_NAME = "ksu_susfs" private const val DEFAULT_UNAME = "default" private const val DEFAULT_BUILD_TIME = "default" - - // KSU模块路径 private const val MODULE_ID = "susfs_manager" private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID" - /** - * 槽位信息数据类 - */ - data class SlotInfo( - val slotName: String, - val uname: String, - val buildTime: String - ) - private fun getSuSFS(): String { - return try { - getSuSFSVersion() - } catch (_: Exception) { - "1.5.8" - } - } - - private fun getSuSFSBinaryName(): String { - val variant = getSuSFS().removePrefix("v") - return "${SUSFS_BINARY_BASE_NAME}_${variant}" - } - - /** - * 获取SuSFS二进制文件的完整路径 - */ - private fun getSuSFSTargetPath(): String { - return "/data/adb/ksu/bin/${getSuSFSBinaryName()}" - } - - /** - * 启用功能状态数据类 - */ + 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 EnabledFeature( val name: String, val isEnabled: Boolean, val statusText: String = if (isEnabled) context.getString(R.string.susfs_feature_enabled) else context.getString(R.string.susfs_feature_disabled), - val canConfigure: Boolean = false // 是否可配置(通过弹窗) + val canConfigure: Boolean = false ) - /** - * 获取Root Shell实例 - */ - private fun getRootShell(): Shell { - return Shell.getShell() - } + // 命令执行 + private fun getPrefs(context: Context): SharedPreferences = + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - /** - * 获取SuSFS配置的SharedPreferences - */ - private fun getPrefs(context: Context): SharedPreferences { - return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - } + private fun getSuSFSVersionUse(): String = try { + getSuSFSVersion() + } catch (_: Exception) { "1.5.8" } + + private fun getSuSFSBinaryName(): String = "${SUSFS_BINARY_BASE_NAME}_${getSuSFSVersionUse().removePrefix("v")}" + + private fun getSuSFSTargetPath(): String = "/data/adb/ksu/bin/${getSuSFSBinaryName()}" - /** - * 执行命令的通用方法 - */ private fun runCmd(shell: Shell, cmd: String): String { return shell.newJob() .add(cmd) @@ -110,36 +72,119 @@ object SuSFSManager { .joinToString("\n") } - /** - * 获取当前槽位信息 - */ + private fun runCmdWithResult(cmd: String): CommandResult { + val result = Shell.getShell().newJob().add(cmd).exec() + return CommandResult(result.isSuccess, result.out.joinToString("\n"), result.err.joinToString("\n")) + } + + + // 配置存取方法 + fun saveUnameValue(context: Context, value: String) = + getPrefs(context).edit { putString(KEY_UNAME_VALUE, value) } + + fun getUnameValue(context: Context): String = + getPrefs(context).getString(KEY_UNAME_VALUE, DEFAULT_UNAME) ?: DEFAULT_UNAME + + fun saveBuildTimeValue(context: Context, value: String) = + getPrefs(context).edit { putString(KEY_BUILD_TIME_VALUE, value)} + + 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) } + + fun isAutoStartEnabled(context: Context): Boolean = + getPrefs(context).getBoolean(KEY_AUTO_START_ENABLED, false) + + fun saveEnableLogState(context: Context, enabled: Boolean) = + getPrefs(context).edit { putBoolean(KEY_ENABLE_LOG, enabled) } + + fun getEnableLogState(context: Context): Boolean = + getPrefs(context).getBoolean(KEY_ENABLE_LOG, false) + + fun getExecuteInPostFsData(context: Context): Boolean = + getPrefs(context).getBoolean(KEY_EXECUTE_IN_POST_FS_DATA, false) + + fun saveExecuteInPostFsData(context: Context, executeInPostFsData: Boolean) { + getPrefs(context).edit { putBoolean(KEY_EXECUTE_IN_POST_FS_DATA, executeInPostFsData) } + if (isAutoStartEnabled(context)) { + kotlinx.coroutines.CoroutineScope(Dispatchers.Default).launch { + removeMagiskModule() + createMagiskModule(context) + } + } + } + + // 路径和配置管理 + fun saveSusPaths(context: Context, paths: Set) = + getPrefs(context).edit { putStringSet(KEY_SUS_PATHS, paths) } + + fun getSusPaths(context: Context): Set = + getPrefs(context).getStringSet(KEY_SUS_PATHS, emptySet()) ?: emptySet() + + fun saveSusMounts(context: Context, mounts: Set) = + getPrefs(context).edit { putStringSet(KEY_SUS_MOUNTS, mounts) } + + fun getSusMounts(context: Context): Set = + getPrefs(context).getStringSet(KEY_SUS_MOUNTS, emptySet()) ?: emptySet() + + fun saveTryUmounts(context: Context, umounts: Set) = + getPrefs(context).edit { putStringSet(KEY_TRY_UMOUNTS, umounts) } + + fun getTryUmounts(context: Context): Set = + getPrefs(context).getStringSet(KEY_TRY_UMOUNTS, emptySet()) ?: emptySet() + + fun saveKstatConfigs(context: Context, configs: Set) = + getPrefs(context).edit { putStringSet(KEY_KSTAT_CONFIGS, configs) } + + fun getKstatConfigs(context: Context): Set = + getPrefs(context).getStringSet(KEY_KSTAT_CONFIGS, emptySet()) ?: emptySet() + + fun saveAddKstatPaths(context: Context, paths: Set) = + getPrefs(context).edit { putStringSet(KEY_ADD_KSTAT_PATHS, paths) } + + fun getAddKstatPaths(context: Context): Set = + getPrefs(context).getStringSet(KEY_ADD_KSTAT_PATHS, emptySet()) ?: emptySet() + + @SuppressLint("SdCardPath") + fun saveAndroidDataPath(context: Context, path: String) = + getPrefs(context).edit { putString(KEY_ANDROID_DATA_PATH, path) } + + @SuppressLint("SdCardPath") + fun getAndroidDataPath(context: Context): String = + getPrefs(context).getString(KEY_ANDROID_DATA_PATH, "/sdcard/Android/data") ?: "/sdcard/Android/data" + + @SuppressLint("SdCardPath") + fun saveSdcardPath(context: Context, path: String) = + getPrefs(context).edit { putString(KEY_SDCARD_PATH, path) } + + @SuppressLint("SdCardPath") + fun getSdcardPath(context: Context): String = + getPrefs(context).getString(KEY_SDCARD_PATH, "/sdcard") ?: "/sdcard" + + + + // 槽位信息获取 suspend fun getCurrentSlotInfo(): List = withContext(Dispatchers.IO) { try { - val shell = getRootShell() val slotInfoList = mutableListOf() + val shell = Shell.getShell() - // 获取boot_a槽位信息 - val bootAUname = runCmd(shell, - "strings -n 20 /dev/block/by-name/boot_a | awk '/Linux version/ && ++c==2 {print $3; exit}'" - ).trim() - val bootABuildTime = runCmd(shell, "strings -n 20 /dev/block/by-name/boot_a | sed -n '/Linux version.*#/{s/.*#/#/p;q}'").trim() + listOf("boot_a", "boot_b").forEach { slot -> + val unameCmd = + "strings -n 20 /dev/block/by-name/$slot | awk '/Linux version/ && ++c==2 {print $3; exit}'" + val buildTimeCmd = "strings -n 20 /dev/block/by-name/$slot | sed -n '/Linux version.*#/{s/.*#/#/p;q}'" - if (bootAUname.isNotEmpty() && bootABuildTime.isNotEmpty()) { - val uname = bootAUname.ifEmpty { "unknown" } - val buildTime = bootABuildTime.ifEmpty { "unknown" } - slotInfoList.add(SlotInfo("boot_a", uname, buildTime)) - } + val uname = runCmd(shell, unameCmd).trim() + val buildTime = runCmd(shell, buildTimeCmd).trim() - // 获取boot_b槽位信息 - val bootBUname = runCmd(shell, - "strings -n 20 /dev/block/by-name/boot_b | awk '/Linux version/ && ++c==2 {print $3; exit}'" - ).trim() - val bootBBuildTime = runCmd(shell, "strings -n 20 /dev/block/by-name/boot_b | sed -n '/Linux version.*#/{s/.*#/#/p;q}'").trim() - - if (bootBUname.isNotEmpty() && bootBBuildTime.isNotEmpty()) { - val uname = bootBUname.ifEmpty { "unknown" } - val buildTime = bootBBuildTime.ifEmpty { "unknown" } - slotInfoList.add(SlotInfo("boot_b", uname, buildTime)) + if (uname.isNotEmpty() && buildTime.isNotEmpty()) { + slotInfoList.add(SlotInfo(slot, uname.ifEmpty { "unknown" }, buildTime.ifEmpty { "unknown" })) + } } slotInfoList @@ -149,566 +194,172 @@ object SuSFSManager { } } - /** - * 获取当前活动槽位 - */ suspend fun getCurrentActiveSlot(): String = withContext(Dispatchers.IO) { try { - val shell = getRootShell() + val shell = Shell.getShell() val suffix = runCmd(shell, "getprop ro.boot.slot_suffix").trim() when (suffix) { "_a" -> "boot_a" "_b" -> "boot_b" else -> "unknown" } - } catch (e: Exception) { - e.printStackTrace() + } catch (_: Exception) { "unknown" } } - /** - * 保存uname值 - */ - fun saveUnameValue(context: Context, value: String) { - getPrefs(context).edit().apply { - putString(KEY_UNAME_VALUE, value) - apply() - } - } - - /** - * 获取保存的uname值 - */ - fun getUnameValue(context: Context): String { - return getPrefs(context).getString(KEY_UNAME_VALUE, DEFAULT_UNAME) ?: DEFAULT_UNAME - } - - /** - * 保存构建时间值 - */ - fun saveBuildTimeValue(context: Context, value: String) { - getPrefs(context).edit().apply { - putString(KEY_BUILD_TIME_VALUE, value) - apply() - } - } - - /** - * 获取保存的构建时间值 - */ - fun getBuildTimeValue(context: Context): String { - return getPrefs(context).getString(KEY_BUILD_TIME_VALUE, DEFAULT_BUILD_TIME) ?: DEFAULT_BUILD_TIME - } - - /** - * 保存执行位置设置并实时更新状态 - */ - 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() - } - } - } - } - } - - /** - * 获取执行位置设置 - */ - fun getExecuteInPostFsData(context: Context): Boolean { - return getPrefs(context).getBoolean(KEY_EXECUTE_IN_POST_FS_DATA, false) - } - - /** - * 保存最后应用的值 - */ - private fun saveLastAppliedValue(context: Context, value: String) { - getPrefs(context).edit().apply { - putString(KEY_LAST_APPLIED_VALUE, value) - apply() - } - } - - /** - * 获取最后应用的值 - */ - fun getLastAppliedValue(context: Context): String { - return getPrefs(context).getString(KEY_LAST_APPLIED_VALUE, DEFAULT_UNAME) ?: DEFAULT_UNAME - } - - /** - * 保存最后应用的构建时间值 - */ - private fun saveLastAppliedBuildTime(context: Context, value: String) { - getPrefs(context).edit().apply { - putString(KEY_LAST_APPLIED_BUILD_TIME, value) - apply() - } - } - - /** - * 获取最后应用的构建时间值 - */ - fun getLastAppliedBuildTime(context: Context): String { - return getPrefs(context).getString(KEY_LAST_APPLIED_BUILD_TIME, DEFAULT_BUILD_TIME) ?: DEFAULT_BUILD_TIME - } - - /** - * 保存SuSFS启用状态 - */ - fun setEnabled(context: Context, enabled: Boolean) { - getPrefs(context).edit().apply { - putBoolean(KEY_IS_ENABLED, enabled) - apply() - } - } - - /** - * 设置开机自启动状态 - */ - fun setAutoStartEnabled(context: Context, enabled: Boolean) { - getPrefs(context).edit().apply { - putBoolean(KEY_AUTO_START_ENABLED, enabled) - apply() - } - } - - /** - * 获取开机自启动状态 - */ - fun isAutoStartEnabled(context: Context): Boolean { - return getPrefs(context).getBoolean(KEY_AUTO_START_ENABLED, false) - } - - /** - * 保存日志启用状态 - */ - fun saveEnableLogState(context: Context, enabled: Boolean) { - getPrefs(context).edit().apply { - putBoolean(KEY_ENABLE_LOG, enabled) - apply() - } - } - - /** - * 获取日志启用状态 - */ - fun getEnableLogState(context: Context): Boolean { - return getPrefs(context).getBoolean(KEY_ENABLE_LOG, false) - } - - /** - * 保存SUS路径列表 - */ - fun saveSusPaths(context: Context, paths: Set) { - getPrefs(context).edit().apply { - putStringSet(KEY_SUS_PATHS, paths) - apply() - } - } - - /** - * 获取SUS路径列表 - */ - fun getSusPaths(context: Context): Set { - return getPrefs(context).getStringSet(KEY_SUS_PATHS, emptySet()) ?: emptySet() - } - - /** - * 保存SUS挂载列表 - */ - fun saveSusMounts(context: Context, mounts: Set) { - getPrefs(context).edit().apply { - putStringSet(KEY_SUS_MOUNTS, mounts) - apply() - } - } - - /** - * 获取SUS挂载列表 - */ - fun getSusMounts(context: Context): Set { - return getPrefs(context).getStringSet(KEY_SUS_MOUNTS, emptySet()) ?: emptySet() - } - - /** - * 保存尝试卸载列表 - */ - fun saveTryUmounts(context: Context, umounts: Set) { - getPrefs(context).edit().apply { - putStringSet(KEY_TRY_UMOUNTS, umounts) - apply() - } - } - - /** - * 获取尝试卸载列表 - */ - fun getTryUmounts(context: Context): Set { - return getPrefs(context).getStringSet(KEY_TRY_UMOUNTS, emptySet()) ?: emptySet() - } - - /** - * 保存Kstat配置列表 - */ - fun saveKstatConfigs(context: Context, configs: Set) { - getPrefs(context).edit().apply { - putStringSet(KEY_KSTAT_CONFIGS, configs) - apply() - } - } - - /** - * 获取Kstat配置列表 - */ - fun getKstatConfigs(context: Context): Set { - return getPrefs(context).getStringSet(KEY_KSTAT_CONFIGS, emptySet()) ?: emptySet() - } - - /** - * 保存Add Kstat路径列表 - */ - fun saveAddKstatPaths(context: Context, paths: Set) { - getPrefs(context).edit().apply { - putStringSet(KEY_ADD_KSTAT_PATHS, paths) - apply() - } - } - - /** - * 获取Add Kstat路径列表 - */ - fun getAddKstatPaths(context: Context): Set { - return getPrefs(context).getStringSet(KEY_ADD_KSTAT_PATHS, emptySet()) ?: emptySet() - } - - /** - * 保存Android Data路径 - */ - fun saveAndroidDataPath(context: Context, path: String) { - getPrefs(context).edit().apply { - putString(KEY_ANDROID_DATA_PATH, path) - apply() - } - } - - /** - * 获取Android Data路径 - */ - @SuppressLint("SdCardPath") - fun getAndroidDataPath(context: Context): String { - return getPrefs(context).getString(KEY_ANDROID_DATA_PATH, "/sdcard/Android/data") ?: "/sdcard/Android/data" - } - - /** - * 保存SD卡路径 - */ - fun saveSdcardPath(context: Context, path: String) { - getPrefs(context).edit().apply { - putString(KEY_SDCARD_PATH, path) - apply() - } - } - - /** - * 获取SD卡路径 - */ - @SuppressLint("SdCardPath") - fun getSdcardPath(context: Context): String { - return getPrefs(context).getString(KEY_SDCARD_PATH, "/sdcard") ?: "/sdcard" - } - - /** - * 从assets复制ksu_susfs文件到/data/adb/ksu/bin/ - */ + // 二进制文件管理 private suspend fun copyBinaryFromAssets(context: Context): String? = withContext(Dispatchers.IO) { try { val binaryName = getSuSFSBinaryName() val targetPath = getSuSFSTargetPath() - val inputStream = context.assets.open(binaryName) val tempFile = File(context.cacheDir, binaryName) - FileOutputStream(tempFile).use { outputStream -> - inputStream.copyTo(outputStream) - } - - // 创建目标目录并复制文件到/data/adb/ksu/bin/ - val shell = getRootShell() - val commands = arrayOf( - "cp '${tempFile.absolutePath}' '$targetPath'", - "chmod 755 '$targetPath'", - ) - - var success = true - for (command in commands) { - val result = shell.newJob().add(command).exec() - if (!result.isSuccess) { - success = false - break + context.assets.open(binaryName).use { input -> + FileOutputStream(tempFile).use { output -> + input.copyTo(output) } } - // 清理临时文件 + val success = runCmdWithResult("cp '${tempFile.absolutePath}' '$targetPath' && chmod 755 '$targetPath'").isSuccess tempFile.delete() - if (success) { - val verifyResult = shell.newJob().add("test -f '$targetPath'").exec() - if (verifyResult.isSuccess) { - targetPath - } else { - null - } - } else { - null - } + if (success && runCmdWithResult("test -f '$targetPath'").isSuccess) targetPath else null } catch (e: IOException) { e.printStackTrace() null } } - /** - * 创建模块结构 - */ - @SuppressLint("SdCardPath") - private suspend fun createMagiskModule(context: Context): Boolean = withContext(Dispatchers.IO) { - try { - val shell = getRootShell() - val targetPath = getSuSFSTargetPath() + fun isBinaryAvailable(context: Context): Boolean = try { + context.assets.open(getSuSFSBinaryName()).use { true } + } catch (_: IOException) { false } - // 创建模块目录结构 - val createDirResult = shell.newJob().add("mkdir -p $MODULE_PATH").exec() - if (!createDirResult.isSuccess) { - return@withContext false - } - - // 创建module.prop文件 - val moduleProp = ScriptGenerator.generateModuleProp(MODULE_ID) - val createModulePropResult = shell.newJob() - .add("cat > $MODULE_PATH/module.prop << 'EOF'\n$moduleProp\nEOF") - .exec() - if (!createModulePropResult.isSuccess) { - return@withContext false - } - - // 获取配置信息 - val unameValue = getUnameValue(context) - val buildTimeValue = getBuildTimeValue(context) - val executeInPostFsData = getExecuteInPostFsData(context) - val susPaths = getSusPaths(context) - val susMounts = getSusMounts(context) - val tryUmounts = getTryUmounts(context) - 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, - kstatConfigs, addKstatPaths - ) - val createServiceResult = shell.newJob() - .add("cat > $MODULE_PATH/service.sh << 'EOF'\n$serviceScript\nEOF") - .add("chmod 755 $MODULE_PATH/service.sh") - .exec() - if (!createServiceResult.isSuccess) { - return@withContext false - } - - // 生成并创建post-fs-data.sh - val postFsDataScript = ScriptGenerator.generatePostFsDataScript( - targetPath, unameValue, buildTimeValue, executeInPostFsData - ) - val createPostFsDataResult = shell.newJob() - .add("cat > $MODULE_PATH/post-fs-data.sh << 'EOF'\n$postFsDataScript\nEOF") - .add("chmod 755 $MODULE_PATH/post-fs-data.sh") - .exec() - if (!createPostFsDataResult.isSuccess) { - return@withContext false - } - - // 生成并创建post-mount.sh - val postMountScript = ScriptGenerator.generatePostMountScript(targetPath, susMounts, tryUmounts) - val createPostMountResult = shell.newJob() - .add("cat > $MODULE_PATH/post-mount.sh << 'EOF'\n$postMountScript\nEOF") - .add("chmod 755 $MODULE_PATH/post-mount.sh") - .exec() - if (!createPostMountResult.isSuccess) { - return@withContext false - } - - // 生成并创建boot-completed.sh - val bootCompletedScript = ScriptGenerator.generateBootCompletedScript(targetPath) - val createBootCompletedResult = shell.newJob() - .add("cat > $MODULE_PATH/boot-completed.sh << 'EOF'\n$bootCompletedScript\nEOF") - .add("chmod 755 $MODULE_PATH/boot-completed.sh") - .exec() - if (!createBootCompletedResult.isSuccess) { - return@withContext false - } - - true - } catch (e: Exception) { - e.printStackTrace() - false - } - } - - /** - * 删除模块 - */ - private suspend fun removeMagiskModule(): Boolean = withContext(Dispatchers.IO) { - try { - val shell = getRootShell() - val result = shell.newJob().add("rm -rf $MODULE_PATH").exec() - result.isSuccess - } catch (e: Exception) { - e.printStackTrace() - false - } - } - - /** - * 执行SuSFS命令 - */ + // 命令执行 private suspend fun executeSusfsCommand(context: Context, command: String): Boolean = withContext(Dispatchers.IO) { try { - // 确保二进制文件存在 - val binaryPath = copyBinaryFromAssets(context) - if (binaryPath == null) { - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_binary_not_found), - Toast.LENGTH_SHORT - ).show() - } + val binaryPath = copyBinaryFromAssets(context) ?: run { + showToast(context, context.getString(R.string.susfs_binary_not_found)) return@withContext false } - // 执行命令 - val fullCommand = "$binaryPath $command" - val result = getRootShell().newJob().add(fullCommand).exec() + val result = runCmdWithResult("$binaryPath $command") if (!result.isSuccess) { - withContext(Dispatchers.Main) { - val errorOutput = result.out.joinToString("\n") + "\n" + result.err.joinToString("\n") - Toast.makeText( - context, - context.getString(R.string.susfs_command_failed) + "\n$errorOutput", - Toast.LENGTH_LONG - ).show() - } + showToast(context, "${context.getString(R.string.susfs_command_failed)}\n${result.output}\n${result.errorOutput}") } result.isSuccess } catch (e: Exception) { e.printStackTrace() - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_command_error, e.message ?: "Unknown error"), - Toast.LENGTH_SHORT - ).show() - } + showToast(context, context.getString(R.string.susfs_command_error, e.message ?: "Unknown error")) false } } - /** - * 启用或禁用日志功能 - */ - suspend fun setEnableLog(context: Context, enabled: Boolean): Boolean { - val value = if (enabled) 1 else 0 - val success = executeSusfsCommand(context, "enable_log $value") - if (success) { - saveEnableLogState(context, enabled) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } - - withContext(Dispatchers.Main) { - Toast.makeText( - context, - if (enabled) context.getString(R.string.susfs_log_enabled) else context.getString(R.string.susfs_log_disabled), - Toast.LENGTH_SHORT - ).show() - } + private suspend fun executeSusfsCommandWithOutput(context: Context, command: String): CommandResult = withContext(Dispatchers.IO) { + try { + val binaryPath = copyBinaryFromAssets(context) ?: return@withContext CommandResult( + false, "", context.getString(R.string.susfs_binary_not_found) + ) + runCmdWithResult("$binaryPath $command") + } catch (e: Exception) { + e.printStackTrace() + CommandResult(false, "", e.message ?: "Unknown error") } - return success } - /** - * 获取SuSFS启用功能状态 - */ + private suspend fun showToast(context: Context, message: String) = withContext(Dispatchers.Main) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + + private inline fun Map.getSetSafe(key: String): Set { + return when (val value = this[key]) { + is Set<*> -> value.filterIsInstance().toSet() + else -> emptySet() + } + } + + // 模块管理 + private suspend fun createMagiskModule(context: Context): Boolean = withContext(Dispatchers.IO) { + try { + val targetPath = getSuSFSTargetPath() + + // 创建模块目录 + if (!runCmdWithResult("mkdir -p $MODULE_PATH").isSuccess) return@withContext false + + // 创建module.prop + val moduleProp = ScriptGenerator.generateModuleProp(MODULE_ID) + if (!runCmdWithResult("cat > $MODULE_PATH/module.prop << 'EOF'\n$moduleProp\nEOF").isSuccess) return@withContext false + + // 获取配置 + val config = mapOf( + "unameValue" to getUnameValue(context), + "buildTimeValue" to getBuildTimeValue(context), + "executeInPostFsData" to getExecuteInPostFsData(context), + "susPaths" to getSusPaths(context), + "susMounts" to getSusMounts(context), + "tryUmounts" to getTryUmounts(context), + "androidDataPath" to getAndroidDataPath(context), + "sdcardPath" to getSdcardPath(context), + "enableLog" to getEnableLogState(context), + "kstatConfigs" to getKstatConfigs(context), + "addKstatPaths" to getAddKstatPaths(context) + ) + + // 生成脚本 + val scripts = mapOf( + "service.sh" to ScriptGenerator.generateServiceScript( + targetPath, config["unameValue"] as String, config["buildTimeValue"] as String, + config.getSetSafe("susPaths"), config["androidDataPath"] as String, + config["sdcardPath"] as String, config["enableLog"] as Boolean, + config["executeInPostFsData"] as Boolean, config.getSetSafe("kstatConfigs"), + config.getSetSafe("addKstatPaths") + ), + "post-fs-data.sh" to ScriptGenerator.generatePostFsDataScript( + targetPath, config["unameValue"] as String, config["buildTimeValue"] as String, + config["executeInPostFsData"] as Boolean + ), + "post-mount.sh" to ScriptGenerator.generatePostMountScript( + targetPath, config.getSetSafe("susMounts"), config.getSetSafe("tryUmounts") + ), + "boot-completed.sh" to ScriptGenerator.generateBootCompletedScript(targetPath) + ) + + // 创建脚本文件 + scripts.all { (filename, content) -> + runCmdWithResult("cat > $MODULE_PATH/$filename << 'EOF'\n$content\nEOF").isSuccess && + runCmdWithResult("chmod 755 $MODULE_PATH/$filename").isSuccess + } + } catch (e: Exception) { + e.printStackTrace() + false + } + } + + private suspend fun removeMagiskModule(): Boolean = withContext(Dispatchers.IO) { + try { + runCmdWithResult("rm -rf $MODULE_PATH").isSuccess + } catch (e: Exception) { + e.printStackTrace() + false + } + } + + + // 功能状态获取 suspend fun getEnabledFeatures(context: Context): List = withContext(Dispatchers.IO) { try { - val susfsStatus = Natives.getSusfsFeatureStatus() - if (susfsStatus != null) { - parseEnabledFeaturesFromStatus(context, susfsStatus) - } else { - emptyList() - } + Natives.getSusfsFeatureStatus()?.let { status -> + parseEnabledFeaturesFromStatus(context, status) + } ?: emptyList() } catch (e: Exception) { e.printStackTrace() emptyList() } } - /** - * 解析SuSFS启用功能状态 - */ private fun parseEnabledFeaturesFromStatus(context: Context, status: Natives.SusfsFeatureStatus): List { - val features = mutableListOf() - - // 定义功能名称和状态的映射 val featureList = listOf( Triple("status_sus_path", context.getString(R.string.sus_path_feature_label), status.statusSusPath), Triple("status_sus_mount", context.getString(R.string.sus_mount_feature_label), status.statusSusMount), @@ -727,445 +378,205 @@ object SuSFSManager { Triple("status_sus_su", context.getString(R.string.sus_su_feature_label), status.statusSusSu) ) - // 根据功能列表创建EnabledFeature对象 - featureList.forEach { (id, displayName, isEnabled) -> + return featureList.map { (id, displayName, isEnabled) -> val statusText = if (isEnabled) context.getString(R.string.susfs_feature_enabled) else context.getString(R.string.susfs_feature_disabled) - // 只有对应功能可以配置 val canConfigure = id == "status_enable_log" - features.add(EnabledFeature(displayName, isEnabled, statusText, canConfigure)) - } - - return features.sortedBy { it.name } + EnabledFeature(displayName, isEnabled, statusText, canConfigure) + }.sortedBy { it.name } } - /** - * 添加SUS路径 - */ - suspend fun addSusPath(context: Context, path: String): Boolean { - val success = executeSusfsCommand(context, "add_sus_path '$path'") + // sus日志开关 + suspend fun setEnableLog(context: Context, enabled: Boolean): Boolean { + val success = executeSusfsCommand(context, "enable_log ${if (enabled) 1 else 0}") if (success) { - val currentPaths = getSusPaths(context).toMutableSet() - currentPaths.add(path) - saveSusPaths(context, currentPaths) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } + saveEnableLogState(context, enabled) + if (isAutoStartEnabled(context)) createMagiskModule(context) + showToast(context, if (enabled) context.getString(R.string.susfs_log_enabled) else context.getString(R.string.susfs_log_disabled)) } return success } - /** - * 移除SUS路径 - */ + // uname和构建时间 + @SuppressLint("StringFormatMatches") + suspend fun setUname(context: Context, unameValue: String, buildTimeValue: String): Boolean { + val success = executeSusfsCommand(context, "set_uname '$unameValue' '$buildTimeValue'") + if (success) { + saveUnameValue(context, unameValue) + saveBuildTimeValue(context, buildTimeValue) + if (isAutoStartEnabled(context)) createMagiskModule(context) + showToast(context, context.getString(R.string.susfs_uname_set_success, unameValue, buildTimeValue)) + } + return success + } + + // 添加SUS路径 + @SuppressLint("StringFormatInvalid") + suspend fun addSusPath(context: Context, path: String): Boolean { + val result = executeSusfsCommandWithOutput(context, "add_sus_path '$path'") + val isActuallySuccessful = result.isSuccess && !result.output.contains("not found, skip adding") + + if (isActuallySuccessful) { + saveSusPaths(context, getSusPaths(context) + path) + if (isAutoStartEnabled(context)) createMagiskModule(context) + showToast(context, context.getString(R.string.susfs_sus_path_added_success, path)) + } else { + val errorMessage = if (result.output.contains("not found, skip adding")) { + context.getString(R.string.susfs_path_not_found_error, path) + } else { + "${context.getString(R.string.susfs_command_failed)}\n${result.output}\n${result.errorOutput}" + } + showToast(context, errorMessage) + } + return isActuallySuccessful + } + suspend fun removeSusPath(context: Context, path: String): Boolean { - val currentPaths = getSusPaths(context).toMutableSet() - currentPaths.remove(path) - saveSusPaths(context, currentPaths) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } - - withContext(Dispatchers.Main) { - Toast.makeText(context, "SUS path removed: $path", Toast.LENGTH_SHORT).show() - } + saveSusPaths(context, getSusPaths(context) - path) + if (isAutoStartEnabled(context)) createMagiskModule(context) + showToast(context, "SUS path removed: $path") return true } - /** - * 添加SUS挂载 - */ + // 添加SUS挂载 suspend fun addSusMount(context: Context, mount: String): Boolean { val success = executeSusfsCommand(context, "add_sus_mount '$mount'") if (success) { - val currentMounts = getSusMounts(context).toMutableSet() - currentMounts.add(mount) - saveSusMounts(context, currentMounts) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } + saveSusMounts(context, getSusMounts(context) + mount) + if (isAutoStartEnabled(context)) createMagiskModule(context) } return success } - /** - * 移除SUS挂载 - */ suspend fun removeSusMount(context: Context, mount: String): Boolean { - val currentMounts = getSusMounts(context).toMutableSet() - currentMounts.remove(mount) - saveSusMounts(context, currentMounts) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } - - withContext(Dispatchers.Main) { - Toast.makeText(context, "Removed SUS mount: $mount", Toast.LENGTH_SHORT).show() - } + saveSusMounts(context, getSusMounts(context) - mount) + if (isAutoStartEnabled(context)) createMagiskModule(context) + showToast(context, "Removed SUS mount: $mount") return true } - /** - * 添加尝试卸载 - * 即使命令执行失败,也要保存配置并更新开机自启动脚本 - */ + // 添加尝试卸载 suspend fun addTryUmount(context: Context, path: String, mode: Int): Boolean { - // 先尝试执行命令 val commandSuccess = executeSusfsCommand(context, "add_try_umount '$path' $mode") + saveTryUmounts(context, getTryUmounts(context) + "$path|$mode") + if (isAutoStartEnabled(context)) createMagiskModule(context) - // 无论命令是否成功,都保存配置 - val currentUmounts = getTryUmounts(context).toMutableSet() - currentUmounts.add("$path|$mode") - saveTryUmounts(context, currentUmounts) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } - - // 显示相应的提示信息 - withContext(Dispatchers.Main) { - if (commandSuccess) { - Toast.makeText( - context, - context.getString(R.string.susfs_try_umount_added_success, path), - Toast.LENGTH_SHORT - ).show() - } else { - Toast.makeText( - context, - context.getString(R.string.susfs_try_umount_added_saved, path), - Toast.LENGTH_LONG - ).show() - } - } - + showToast(context, if (commandSuccess) { + context.getString(R.string.susfs_try_umount_added_success, path) + } else { + context.getString(R.string.susfs_try_umount_added_saved, path) + }) return true } - /** - * 移除尝试卸载 - */ suspend fun removeTryUmount(context: Context, umountEntry: String): Boolean { - val currentUmounts = getTryUmounts(context).toMutableSet() - currentUmounts.remove(umountEntry) - saveTryUmounts(context, currentUmounts) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } - - val parts = umountEntry.split("|") - val path = if (parts.isNotEmpty()) parts[0] else umountEntry - withContext(Dispatchers.Main) { - Toast.makeText(context, "Removed Try to uninstall: $path", Toast.LENGTH_SHORT).show() - } + saveTryUmounts(context, getTryUmounts(context) - umountEntry) + if (isAutoStartEnabled(context)) createMagiskModule(context) + val path = umountEntry.split("|").firstOrNull() ?: umountEntry + showToast(context, "Removed Try to uninstall: $path") return true } - /** - * 运行尝试卸载 - */ - suspend fun runTryUmount(context: Context): Boolean { - return executeSusfsCommand(context, "run_try_umount") - } + suspend fun runTryUmount(context: Context): Boolean = 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 { + // 添加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() - } + saveKstatConfigs(context, getKstatConfigs(context) + configEntry) + if (isAutoStartEnabled(context)) createMagiskModule(context) + showToast(context, context.getString(R.string.kstat_static_config_added, path)) } 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() - } + saveKstatConfigs(context, getKstatConfigs(context) - config) + if (isAutoStartEnabled(context)) createMagiskModule(context) + val path = config.split("|").firstOrNull() ?: config + showToast(context, context.getString(R.string.kstat_config_removed, path)) return true } - /** - * 添加Kstat路径 - */ + // 添加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() - } + saveAddKstatPaths(context, getAddKstatPaths(context) + path) + if (isAutoStartEnabled(context)) createMagiskModule(context) + showToast(context, context.getString(R.string.kstat_path_added, path)) } 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() - } + saveAddKstatPaths(context, getAddKstatPaths(context) - path) + if (isAutoStartEnabled(context)) createMagiskModule(context) + showToast(context, context.getString(R.string.kstat_path_removed, path)) return true } - /** - * 更新Kstat - */ + // 更新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() - } - } + if (success) showToast(context, context.getString(R.string.kstat_updated, path)) return success } - /** - * 完整克隆更新Kstat - */ + // 更新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() - } - } + if (success) showToast(context, context.getString(R.string.kstat_full_clone_updated, path)) return success } - /** - * 设置Android Data路径 - */ + // 设置Android数据路径和SD卡路径 suspend fun setAndroidDataPath(context: Context, path: String): Boolean { val success = executeSusfsCommand(context, "set_android_data_root_path '$path'") if (success) { saveAndroidDataPath(context, path) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } + if (isAutoStartEnabled(context)) createMagiskModule(context) } return success } - /** - * 设置SD卡路径 - */ + // 设置SD卡路径 suspend fun setSdcardPath(context: Context, path: String): Boolean { val success = executeSusfsCommand(context, "set_sdcard_root_path '$path'") if (success) { saveSdcardPath(context, path) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } + if (isAutoStartEnabled(context)) createMagiskModule(context) } return success } - /** - * 执行SuSFS命令设置uname和构建时间 - */ - @SuppressLint("StringFormatMatches") - suspend fun setUname(context: Context, unameValue: String, buildTimeValue: String): Boolean = withContext(Dispatchers.IO) { - try { - // 首先复制二进制文件到/data/adb/ksu/bin/ - val binaryPath = copyBinaryFromAssets(context) - if (binaryPath == null) { - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_binary_not_found), - Toast.LENGTH_SHORT - ).show() - } - return@withContext false - } - - // 构建命令 - val command = "$binaryPath set_uname '$unameValue' '$buildTimeValue'" - - // 执行命令 - val result = getRootShell().newJob().add(command).exec() - - if (result.isSuccess) { - // 保存配置 - saveUnameValue(context, unameValue) - saveBuildTimeValue(context, buildTimeValue) - saveLastAppliedValue(context, unameValue) - saveLastAppliedBuildTime(context, buildTimeValue) - setEnabled(context, true) - - // 如果开启了开机自启动,更新模块 - if (isAutoStartEnabled(context)) { - createMagiskModule(context) - } - - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_uname_set_success, unameValue, buildTimeValue), - Toast.LENGTH_SHORT - ).show() - } - true - } else { - withContext(Dispatchers.Main) { - val errorOutput = result.out.joinToString("\n") + "\n" + result.err.joinToString("\n") - Toast.makeText( - context, - context.getString(R.string.susfs_command_failed) + "\n$errorOutput", - Toast.LENGTH_LONG - ).show() - } - false - } - } catch (e: Exception) { - e.printStackTrace() - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_command_error, e.message ?: "Unknown error"), - Toast.LENGTH_SHORT - ).show() - } - false - } - } - - /** - * 检查是否有任何配置可以启用开机自启动 - */ fun hasConfigurationForAutoStart(context: Context): Boolean { - val unameValue = getUnameValue(context) - val buildTimeValue = getBuildTimeValue(context) - 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) - } - - return (unameValue != DEFAULT_UNAME) || - (buildTimeValue != DEFAULT_BUILD_TIME) || - susPaths.isNotEmpty() || - susMounts.isNotEmpty() || - tryUmounts.isNotEmpty() || - kstatConfigs.isNotEmpty() || - addKstatPaths.isNotEmpty() || + val enabledFeatures = runBlocking { getEnabledFeatures(context) } + return getUnameValue(context) != DEFAULT_UNAME || + getBuildTimeValue(context) != DEFAULT_BUILD_TIME || + getSusPaths(context).isNotEmpty() || + getSusMounts(context).isNotEmpty() || + getTryUmounts(context).isNotEmpty() || + getKstatConfigs(context).isNotEmpty() || + getAddKstatPaths(context).isNotEmpty() || enabledFeatures.any { it.isEnabled } } - /** - * 配置开机自启动 - */ suspend fun configureAutoStart(context: Context, enabled: Boolean): Boolean = withContext(Dispatchers.IO) { try { if (enabled) { - // 启用开机自启动 if (!hasConfigurationForAutoStart(context)) { - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_no_config_to_autostart), - Toast.LENGTH_SHORT - ).show() - } + showToast(context, context.getString(R.string.susfs_no_config_to_autostart)) return@withContext false } - // 确保二进制文件存在于目标位置 - val shell = getRootShell() val targetPath = getSuSFSTargetPath() - val checkResult = shell.newJob().add("test -f '$targetPath'").exec() - - if (!checkResult.isSuccess) { - // 如果不存在,尝试复制 - val binaryPath = copyBinaryFromAssets(context) - if (binaryPath == null) { - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_binary_not_found), - Toast.LENGTH_SHORT - ).show() - } + if (!runCmdWithResult("test -f '$targetPath'").isSuccess) { + copyBinaryFromAssets(context) ?: run { + showToast(context, context.getString(R.string.susfs_binary_not_found)) return@withContext false } } @@ -1173,85 +584,33 @@ object SuSFSManager { val success = createMagiskModule(context) if (success) { setAutoStartEnabled(context, true) - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_autostart_enabled_success, MODULE_PATH), - Toast.LENGTH_LONG - ).show() - } + showToast(context, context.getString(R.string.susfs_autostart_enabled_success, MODULE_PATH)) } else { - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_autostart_enable_failed), - Toast.LENGTH_SHORT - ).show() - } + showToast(context, context.getString(R.string.susfs_autostart_enable_failed)) } success } else { - // 禁用开机自启动 val success = removeMagiskModule() if (success) { setAutoStartEnabled(context, false) - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_autostart_disabled_success), - Toast.LENGTH_SHORT - ).show() - } + showToast(context, context.getString(R.string.susfs_autostart_disabled_success)) } else { - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_autostart_disable_failed), - Toast.LENGTH_SHORT - ).show() - } + showToast(context, context.getString(R.string.susfs_autostart_disable_failed)) } success } } catch (e: Exception) { e.printStackTrace() - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.susfs_autostart_error, e.message ?: "Unknown error"), - Toast.LENGTH_SHORT - ).show() - } + showToast(context, context.getString(R.string.susfs_autostart_error, e.message ?: "Unknown error")) false } } - /** - * 重置为默认值 - */ suspend fun resetToDefault(context: Context): Boolean { val success = setUname(context, DEFAULT_UNAME, DEFAULT_BUILD_TIME) - if (success) { - // 重置时清除最后应用的值 - saveLastAppliedValue(context, DEFAULT_UNAME) - saveLastAppliedBuildTime(context, DEFAULT_BUILD_TIME) - // 如果开启了开机自启动,需要禁用它 - if (isAutoStartEnabled(context)) { - configureAutoStart(context, false) - } + if (success && isAutoStartEnabled(context)) { + configureAutoStart(context, false) } return success } - - /** - * 检查ksu_susfs文件是否存在于assets中 - */ - fun isBinaryAvailable(context: Context): Boolean { - return try { - val binaryName = getSuSFSBinaryName() - context.assets.open(binaryName).use { true } - } catch (_: IOException) { - false - } - } } \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt index 711a8b4e..fa1b0379 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt @@ -8,6 +8,40 @@ import android.annotation.SuppressLint */ object ScriptGenerator { + // 常量定义 + @SuppressLint("SdCardPath") + private const val DEFAULT_ANDROID_DATA_PATH = "/sdcard/Android/data" + @SuppressLint("SdCardPath") + private const val DEFAULT_SDCARD_PATH = "/sdcard" + private const val DEFAULT_UNAME = "default" + private const val DEFAULT_BUILD_TIME = "default" + private const val LOG_DIR = "/data/adb/ksu/log" + + // 日志相关的通用脚本片段 + private fun generateLogSetup(logFileName: String): String = """ + # 日志目录 + LOG_DIR="$LOG_DIR" + LOG_FILE="${'$'}LOG_DIR/$logFileName" + + # 创建日志目录 + mkdir -p "${'$'}LOG_DIR" + + # 获取当前时间 + get_current_time() { + date '+%Y-%m-%d %H:%M:%S' + } + """.trimIndent() + + // 二进制文件检查的通用脚本片段 + private fun generateBinaryCheck(targetPath: String): String = """ + # 检查SuSFS二进制文件 + SUSFS_BIN="$targetPath" + if [ ! -f "${'$'}SUSFS_BIN" ]; then + echo "$(get_current_time): SuSFS二进制文件未找到: ${'$'}SUSFS_BIN" >> "${'$'}LOG_FILE" + exit 1 + fi + """.trimIndent() + /** * 生成service.sh脚本内容 */ @@ -29,177 +63,209 @@ object ScriptGenerator { appendLine("# SuSFS Service Script") appendLine("# 在系统服务启动后执行") appendLine() - appendLine("# 日志目录") - appendLine("LOG_DIR=\"/data/adb/ksu/log\"") - appendLine("LOG_FILE=\"\$LOG_DIR/susfs_service.log\"") + appendLine(generateLogSetup("susfs_service.log")) appendLine() - appendLine("# 创建日志目录") - appendLine("mkdir -p \"\$LOG_DIR\"") - appendLine() - appendLine("# 获取当前时间") - appendLine("get_current_time() {") - appendLine(" date '+%Y-%m-%d %H:%M:%S'") - appendLine("}") - appendLine() - appendLine("# 检查SuSFS二进制文件") - appendLine("SUSFS_BIN=\"$targetPath\"") - appendLine("if [ ! -f \"\$SUSFS_BIN\" ]; then") - appendLine(" echo \"$(get_current_time): SuSFS二进制文件未找到: \$SUSFS_BIN\" >> \"\$LOG_FILE\"") - appendLine(" exit 1") - appendLine("fi") + appendLine(generateBinaryCheck(targetPath)) appendLine() // 设置日志启用状态 - 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() + generateLogSettingSection(enableLog) - // 设置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() - } - - // 设置SD卡路径 - if (sdcardPath != "/sdcard") { - appendLine("# 设置SD卡路径") - appendLine("\"\$SUSFS_BIN\" set_sdcard_root_path '$sdcardPath'") - appendLine("echo \"$(get_current_time): SD卡路径设置为: $sdcardPath\" >> \"\$LOG_FILE\"") - appendLine() - } + // 设置路径 + generatePathSettingSection(androidDataPath, sdcardPath) // 添加SUS路径 - if (susPaths.isNotEmpty()) { - appendLine("# 添加SUS路径") - susPaths.forEach { path -> - appendLine("\"\$SUSFS_BIN\" add_sus_path '$path'") - appendLine("echo \"$(get_current_time): 添加SUS路径: $path\" >> \"\$LOG_FILE\"") - } - appendLine() - } + generateSusPathsSection(susPaths) - // 添加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配置 + generateKstatSection(kstatConfigs, addKstatPaths) - // 添加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() - } + // 设置uname和构建时间 + generateUnameSection(unameValue, buildTimeValue, executeInPostFsData) - // 设置uname和构建时间 - 只有不在service中执行 - 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() - } + // 隐藏BL相关配置 + generateHideBlSection() - appendLine("# 隐弱BL 来自 Shamiko 脚本") - appendLine("check_reset_prop() {") - 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(" [ -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(" [ -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("}") - appendLine() - appendLine("resetprop -w sys.boot_completed 0") - appendLine() - appendLine("check_missing_prop \"ro.boot.vbmeta.invalidate_on_error\" \"yes\"") - appendLine("check_missing_prop \"ro.boot.vbmeta.avb_version\" \"1.2\"") - appendLine("check_missing_prop \"ro.boot.vbmeta.hash_alg\" \"sha256\"") - appendLine() - appendLine("check_missing_match_prop \"ro.boot.vbmeta.device_state\" \"locked\"") - appendLine("check_missing_match_prop \"ro.boot.verifiedbootstate\" \"green\"") - appendLine("check_missing_match_prop \"ro.boot.flash.locked\" \"1\"") - appendLine("check_missing_match_prop \"ro.boot.veritymode\" \"enforcing\"") - appendLine("check_missing_match_prop \"ro.boot.warranty_bit\" \"0\"") - appendLine("check_reset_prop \"ro.boot.vbmeta.device_state\" \"locked\"") - appendLine("check_reset_prop \"ro.boot.verifiedbootstate\" \"green\"") - appendLine("check_reset_prop \"ro.boot.flash.locked\" \"1\"") - appendLine("check_reset_prop \"ro.boot.veritymode\" \"enforcing\"") - appendLine("check_reset_prop \"ro.boot.warranty_bit\" \"0\"") - appendLine("check_reset_prop \"ro.warranty_bit\" \"0\"") - appendLine("check_reset_prop \"ro.debuggable\" \"0\"") - appendLine("check_reset_prop \"ro.force.debuggable\" \"0\"") - appendLine("check_reset_prop \"ro.secure\" \"1\"") - appendLine("check_reset_prop \"ro.adb.secure\" \"1\"") - appendLine("check_reset_prop \"ro.build.type\" \"user\"") - appendLine("check_reset_prop \"ro.build.tags\" \"release-keys\"") - appendLine("check_reset_prop \"ro.vendor.boot.warranty_bit\" \"0\"") - appendLine("check_reset_prop \"ro.vendor.warranty_bit\" \"0\"") - appendLine("check_reset_prop \"vendor.boot.vbmeta.device_state\" \"locked\"") - appendLine("check_reset_prop \"vendor.boot.verifiedbootstate\" \"green\"") - appendLine("check_reset_prop \"sys.oem_unlock_allowed\" \"0\"") - appendLine() - appendLine("#Hide adb debugging traces") - appendLine("resetprop \"sys.usb.adb.disabled\" \" \"") - appendLine() - appendLine("# MIUI specific") - appendLine("check_reset_prop \"ro.secureboot.lockstate\" \"locked\"") - appendLine() - appendLine("# Realme specific") - appendLine("check_reset_prop \"ro.boot.realmebootstate\" \"green\"") - appendLine("check_reset_prop \"ro.boot.realme.lockstate\" \"1\"") - appendLine() - appendLine("# Hide that we booted from recovery when magisk is in recovery mode") - appendLine("contains_reset_prop \"ro.bootmode\" \"recovery\" \"unknown\"") - appendLine("contains_reset_prop \"ro.boot.bootmode\" \"recovery\" \"unknown\"") - 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() - 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\"") } } + private fun StringBuilder.generateLogSettingSection(enableLog: Boolean) { + 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() + } + + private fun StringBuilder.generatePathSettingSection( + androidDataPath: String, + sdcardPath: String + ) { + // 设置Android Data路径 + if (androidDataPath != DEFAULT_ANDROID_DATA_PATH) { + appendLine("# 设置Android Data路径") + appendLine("\"${'$'}SUSFS_BIN\" set_android_data_root_path '$androidDataPath'") + appendLine("echo \"$(get_current_time): Android Data路径设置为: $androidDataPath\" >> \"${'$'}LOG_FILE\"") + appendLine() + } + + // 设置SD卡路径 + if (sdcardPath != DEFAULT_SDCARD_PATH) { + appendLine("# 设置SD卡路径") + appendLine("\"${'$'}SUSFS_BIN\" set_sdcard_root_path '$sdcardPath'") + appendLine("echo \"$(get_current_time): SD卡路径设置为: $sdcardPath\" >> \"${'$'}LOG_FILE\"") + appendLine() + } + } + + private fun StringBuilder.generateSusPathsSection(susPaths: Set) { + if (susPaths.isNotEmpty()) { + appendLine("# 添加SUS路径") + susPaths.forEach { path -> + appendLine("\"${'$'}SUSFS_BIN\" add_sus_path '$path'") + appendLine("echo \"$(get_current_time): 添加SUS路径: $path\" >> \"${'$'}LOG_FILE\"") + } + appendLine() + } + } + + private fun StringBuilder.generateKstatSection( + kstatConfigs: Set, + addKstatPaths: Set + ) { + // 添加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() + } + } + + private fun StringBuilder.generateUnameSection( + unameValue: String, + buildTimeValue: String, + executeInPostFsData: Boolean + ) { + if (!executeInPostFsData && (unameValue != DEFAULT_UNAME || buildTimeValue != DEFAULT_BUILD_TIME)) { + appendLine("# 设置uname和构建时间") + appendLine("\"${'$'}SUSFS_BIN\" set_uname '$unameValue' '$buildTimeValue'") + appendLine("echo \"$(get_current_time): 设置uname为: $unameValue, 构建时间为: $buildTimeValue\" >> \"${'$'}LOG_FILE\"") + appendLine() + } + } + + private fun StringBuilder.generateHideBlSection() { + appendLine("# 隐藏BL 来自 Shamiko 脚本") + appendLine( + """ + check_reset_prop() { + local NAME=$1 + local EXPECTED=$2 + local VALUE=$(resetprop ${'$'}NAME) + [ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || resetprop ${'$'}NAME ${'$'}EXPECTED + } + + check_missing_prop() { + local NAME=$1 + local EXPECTED=$2 + local VALUE=$(resetprop ${'$'}NAME) + [ -z ${'$'}VALUE ] && resetprop ${'$'}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 + } + + contains_reset_prop() { + local NAME=$1 + local CONTAINS=$2 + local NEWVAL=$3 + [[ "$(resetprop ${'$'}NAME)" = *"${'$'}CONTAINS"* ]] && resetprop ${'$'}NAME ${'$'}NEWVAL + } + """.trimIndent()) + appendLine() + + appendLine("resetprop -w sys.boot_completed 0") + appendLine() + + // 添加所有系统属性重置 + 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.device_state" to "locked", + "ro.boot.verifiedbootstate" to "green", + "ro.boot.flash.locked" to "1", + "ro.boot.veritymode" to "enforcing", + "ro.boot.warranty_bit" to "0", + "ro.warranty_bit" to "0", + "ro.debuggable" to "0", + "ro.force.debuggable" to "0", + "ro.secure" to "1", + "ro.adb.secure" to "1", + "ro.build.type" to "user", + "ro.build.tags" to "release-keys", + "ro.vendor.boot.warranty_bit" to "0", + "ro.vendor.warranty_bit" to "0", + "vendor.boot.vbmeta.device_state" to "locked", + "vendor.boot.verifiedbootstate" to "green", + "sys.oem_unlock_allowed" to "0", + "ro.secureboot.lockstate" to "locked", + "ro.boot.realmebootstate" to "green", + "ro.boot.realme.lockstate" to "1", + "ro.crypto.state" to "encrypted" + ) + + systemProps.forEach { (prop, value) -> + when { + prop.startsWith("ro.boot.vbmeta") && prop.endsWith("_on_error") -> + appendLine("check_missing_prop \"$prop\" \"$value\"") + prop.contains("device_state") || prop.contains("verifiedbootstate") -> + appendLine("check_missing_match_prop \"$prop\" \"$value\"") + else -> + appendLine("check_reset_prop \"$prop\" \"$value\"") + } + } + + appendLine() + appendLine("# Hide adb debugging traces") + appendLine("resetprop \"sys.usb.adb.disabled\" \" \"") + appendLine() + + appendLine("# Hide recovery boot mode") + appendLine("contains_reset_prop \"ro.bootmode\" \"recovery\" \"unknown\"") + appendLine("contains_reset_prop \"ro.boot.bootmode\" \"recovery\" \"unknown\"") + 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() + } + /** * 生成post-fs-data.sh脚本内容 */ @@ -214,37 +280,22 @@ object ScriptGenerator { appendLine("# SuSFS Post-FS-Data Script") appendLine("# 在文件系统挂载后但在系统完全启动前执行") appendLine() - appendLine("# 日志目录") - appendLine("LOG_DIR=\"/data/adb/ksu/log\"") - appendLine("LOG_FILE=\"\$LOG_DIR/susfs_post_fs_data.log\"") + appendLine(generateLogSetup("susfs_post_fs_data.log")) appendLine() - appendLine("# 创建日志目录") - appendLine("mkdir -p \"\$LOG_DIR\"") + appendLine(generateBinaryCheck(targetPath)) appendLine() - appendLine("# 获取当前时间") - appendLine("get_current_time() {") - appendLine(" date '+%Y-%m-%d %H:%M:%S'") - appendLine("}") - appendLine() - appendLine("# 检查SuSFS二进制文件") - appendLine("SUSFS_BIN=\"$targetPath\"") - appendLine("if [ ! -f \"\$SUSFS_BIN\" ]; then") - 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")) { + if (executeInPostFsData && (unameValue != DEFAULT_UNAME || buildTimeValue != DEFAULT_BUILD_TIME)) { appendLine("# 设置uname和构建时间") - appendLine("\"\$SUSFS_BIN\" set_uname '$unameValue' '$buildTimeValue'") - appendLine("echo \"$(get_current_time): 设置uname为: $unameValue, 构建时间为: $buildTimeValue\" >> \"\$LOG_FILE\"") + appendLine("\"${'$'}SUSFS_BIN\" set_uname '$unameValue' '$buildTimeValue'") + 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\"") } } @@ -261,34 +312,19 @@ object ScriptGenerator { appendLine("# SuSFS Post-Mount Script") appendLine("# 在所有分区挂载完成后执行") appendLine() - appendLine("# 日志目录") - appendLine("LOG_DIR=\"/data/adb/ksu/log\"") - appendLine("LOG_FILE=\"\$LOG_DIR/susfs_post_mount.log\"") + appendLine(generateLogSetup("susfs_post_mount.log")) appendLine() - appendLine("# 创建日志目录") - appendLine("mkdir -p \"\$LOG_DIR\"") + appendLine("echo \"$(get_current_time): Post-Mount脚本开始执行\" >> \"${'$'}LOG_FILE\"") appendLine() - appendLine("# 获取当前时间") - appendLine("get_current_time() {") - appendLine(" date '+%Y-%m-%d %H:%M:%S'") - appendLine("}") - appendLine() - 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(" exit 1") - appendLine("fi") + appendLine(generateBinaryCheck(targetPath)) appendLine() // 添加SUS挂载 if (susMounts.isNotEmpty()) { appendLine("# 添加SUS挂载") susMounts.forEach { mount -> - appendLine("\"\$SUSFS_BIN\" add_sus_mount '$mount'") - appendLine("echo \"$(get_current_time): 添加SUS挂载: $mount\" >> \"\$LOG_FILE\"") + appendLine("\"${'$'}SUSFS_BIN\" add_sus_mount '$mount'") + appendLine("echo \"$(get_current_time): 添加SUS挂载: $mount\" >> \"${'$'}LOG_FILE\"") } appendLine() } @@ -301,14 +337,14 @@ object ScriptGenerator { if (parts.size == 2) { 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("\"${'$'}SUSFS_BIN\" add_try_umount '$path' $mode") + 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\"") } } @@ -321,28 +357,13 @@ object ScriptGenerator { appendLine("# SuSFS Boot-Completed Script") appendLine("# 在系统完全启动后执行") appendLine() - appendLine("# 日志目录") - appendLine("LOG_DIR=\"/data/adb/ksu/log\"") - appendLine("LOG_FILE=\"\$LOG_DIR/susfs_boot_completed.log\"") + appendLine(generateLogSetup("susfs_boot_completed.log")) appendLine() - appendLine("# 创建日志目录") - appendLine("mkdir -p \"\$LOG_DIR\"") + appendLine("echo \"$(get_current_time): Boot-Completed脚本开始执行\" >> \"${'$'}LOG_FILE\"") appendLine() - appendLine("# 获取当前时间") - appendLine("get_current_time() {") - appendLine(" date '+%Y-%m-%d %H:%M:%S'") - appendLine("}") + appendLine(generateBinaryCheck(targetPath)) appendLine() - 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(" exit 1") - appendLine("fi") - appendLine() - appendLine("echo \"$(get_current_time): Boot-Completed脚本执行完成\" >> \"\$LOG_FILE\"") + appendLine("echo \"$(get_current_time): Boot-Completed脚本执行完成\" >> \"${'$'}LOG_FILE\"") } } diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index b0bcf6d4..6cf4f324 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -450,6 +450,9 @@ 添加SUS路径 添加SUS挂载 添加尝试卸载 + SUS 路径添加成功 + 路径未找到错误 + 未知错误 路径 挂载路径 例如: /system/addon.d diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index ddb70e92..3de60da3 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -452,6 +452,9 @@ Add SUS Path Add SUS Mount Add Try Umount + SUS path added successfully + Path not found error + Unknown error Path Mount Path e.g.: /system/addon.d