manager: SuSFS: Add SUS_MAP feature configuration item
This commit is contained in:
Binary file not shown.
@@ -26,20 +26,10 @@ import com.ramcosta.composedestinations.annotation.Destination
|
|||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.ui.susfs.component.AddAppPathDialog
|
import com.sukisu.ultra.ui.susfs.component.*
|
||||||
import com.sukisu.ultra.ui.susfs.component.AddKstatStaticallyDialog
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.AddPathDialog
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.AddTryUmountDialog
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.ConfirmDialog
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.EnabledFeaturesContent
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.KstatConfigContent
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.PathSettingsContent
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.SusLoopPathsContent
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.SusMountsContent
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.SusPathsContent
|
|
||||||
import com.sukisu.ultra.ui.susfs.component.TryUmountContent
|
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig
|
import com.sukisu.ultra.ui.theme.CardConfig
|
||||||
import com.sukisu.ultra.ui.susfs.util.SuSFSManager
|
import com.sukisu.ultra.ui.susfs.util.SuSFSManager
|
||||||
|
import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion1512
|
||||||
import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion158
|
import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion158
|
||||||
import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion159
|
import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion159
|
||||||
import com.sukisu.ultra.ui.util.getSuSFSVersion
|
import com.sukisu.ultra.ui.util.getSuSFSVersion
|
||||||
@@ -56,6 +46,7 @@ enum class SuSFSTab(val displayNameRes: Int) {
|
|||||||
BASIC_SETTINGS(R.string.susfs_tab_basic_settings),
|
BASIC_SETTINGS(R.string.susfs_tab_basic_settings),
|
||||||
SUS_PATHS(R.string.susfs_tab_sus_paths),
|
SUS_PATHS(R.string.susfs_tab_sus_paths),
|
||||||
SUS_LOOP_PATHS(R.string.susfs_tab_sus_loop_paths),
|
SUS_LOOP_PATHS(R.string.susfs_tab_sus_loop_paths),
|
||||||
|
SUS_MAPS(R.string.susfs_tab_sus_maps),
|
||||||
SUS_MOUNTS(R.string.susfs_tab_sus_mounts),
|
SUS_MOUNTS(R.string.susfs_tab_sus_mounts),
|
||||||
TRY_UMOUNT(R.string.susfs_tab_try_umount),
|
TRY_UMOUNT(R.string.susfs_tab_try_umount),
|
||||||
KSTAT_CONFIG(R.string.susfs_tab_kstat_config),
|
KSTAT_CONFIG(R.string.susfs_tab_kstat_config),
|
||||||
@@ -63,11 +54,12 @@ enum class SuSFSTab(val displayNameRes: Int) {
|
|||||||
ENABLED_FEATURES(R.string.susfs_tab_enabled_features);
|
ENABLED_FEATURES(R.string.susfs_tab_enabled_features);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getAllTabs(isSusVersion158: Boolean, isSusVersion159: Boolean): List<SuSFSTab> {
|
fun getAllTabs(isSusVersion158: Boolean, isSusVersion159: Boolean, isSusVersion1512: Boolean): List<SuSFSTab> {
|
||||||
return when {
|
return when {
|
||||||
isSusVersion159 -> entries.toList()
|
isSusVersion1512 -> entries.toList()
|
||||||
|
isSusVersion159 -> entries.filter { it != SUS_MOUNTS}
|
||||||
isSusVersion158 -> entries.filter { it != SUS_LOOP_PATHS }
|
isSusVersion158 -> entries.filter { it != SUS_LOOP_PATHS }
|
||||||
else -> entries.filter { it != PATH_SETTINGS && it != SUS_LOOP_PATHS }
|
else -> entries.filter { it != PATH_SETTINGS && it != SUS_LOOP_PATHS && it != SUS_MOUNTS }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +98,7 @@ fun SuSFSConfigScreen(
|
|||||||
// 路径管理相关状态
|
// 路径管理相关状态
|
||||||
var susPaths by remember { mutableStateOf(emptySet<String>()) }
|
var susPaths by remember { mutableStateOf(emptySet<String>()) }
|
||||||
var susLoopPaths by remember { mutableStateOf(emptySet<String>()) }
|
var susLoopPaths by remember { mutableStateOf(emptySet<String>()) }
|
||||||
|
var susMaps by remember { mutableStateOf(emptySet<String>()) }
|
||||||
var susMounts by remember { mutableStateOf(emptySet<String>()) }
|
var susMounts by remember { mutableStateOf(emptySet<String>()) }
|
||||||
var tryUmounts by remember { mutableStateOf(emptySet<String>()) }
|
var tryUmounts by remember { mutableStateOf(emptySet<String>()) }
|
||||||
var androidDataPath by remember { mutableStateOf("") }
|
var androidDataPath by remember { mutableStateOf("") }
|
||||||
@@ -130,6 +123,7 @@ fun SuSFSConfigScreen(
|
|||||||
// 对话框状态
|
// 对话框状态
|
||||||
var showAddPathDialog by remember { mutableStateOf(false) }
|
var showAddPathDialog by remember { mutableStateOf(false) }
|
||||||
var showAddLoopPathDialog by remember { mutableStateOf(false) }
|
var showAddLoopPathDialog by remember { mutableStateOf(false) }
|
||||||
|
var showAddSusMapDialog by remember { mutableStateOf(false) }
|
||||||
var showAddAppPathDialog by remember { mutableStateOf(false) }
|
var showAddAppPathDialog by remember { mutableStateOf(false) }
|
||||||
var showAddMountDialog by remember { mutableStateOf(false) }
|
var showAddMountDialog by remember { mutableStateOf(false) }
|
||||||
var showAddUmountDialog by remember { mutableStateOf(false) }
|
var showAddUmountDialog by remember { mutableStateOf(false) }
|
||||||
@@ -139,6 +133,7 @@ fun SuSFSConfigScreen(
|
|||||||
// 编辑状态
|
// 编辑状态
|
||||||
var editingPath by remember { mutableStateOf<String?>(null) }
|
var editingPath by remember { mutableStateOf<String?>(null) }
|
||||||
var editingLoopPath by remember { mutableStateOf<String?>(null) }
|
var editingLoopPath by remember { mutableStateOf<String?>(null) }
|
||||||
|
var editingSusMap by remember { mutableStateOf<String?>(null) }
|
||||||
var editingMount by remember { mutableStateOf<String?>(null) }
|
var editingMount by remember { mutableStateOf<String?>(null) }
|
||||||
var editingUmount by remember { mutableStateOf<String?>(null) }
|
var editingUmount by remember { mutableStateOf<String?>(null) }
|
||||||
var editingKstatConfig by remember { mutableStateOf<String?>(null) }
|
var editingKstatConfig by remember { mutableStateOf<String?>(null) }
|
||||||
@@ -147,6 +142,7 @@ fun SuSFSConfigScreen(
|
|||||||
// 重置确认对话框状态
|
// 重置确认对话框状态
|
||||||
var showResetPathsDialog by remember { mutableStateOf(false) }
|
var showResetPathsDialog by remember { mutableStateOf(false) }
|
||||||
var showResetLoopPathsDialog by remember { mutableStateOf(false) }
|
var showResetLoopPathsDialog by remember { mutableStateOf(false) }
|
||||||
|
var showResetSusMapsDialog by remember { mutableStateOf(false) }
|
||||||
var showResetMountsDialog by remember { mutableStateOf(false) }
|
var showResetMountsDialog by remember { mutableStateOf(false) }
|
||||||
var showResetUmountsDialog by remember { mutableStateOf(false) }
|
var showResetUmountsDialog by remember { mutableStateOf(false) }
|
||||||
var showResetKstatDialog by remember { mutableStateOf(false) }
|
var showResetKstatDialog by remember { mutableStateOf(false) }
|
||||||
@@ -160,7 +156,7 @@ fun SuSFSConfigScreen(
|
|||||||
|
|
||||||
var isNavigating by remember { mutableStateOf(false) }
|
var isNavigating by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val allTabs = SuSFSTab.getAllTabs(isSusVersion158(), isSusVersion159())
|
val allTabs = SuSFSTab.getAllTabs(isSusVersion158(), isSusVersion159(), isSusVersion1512())
|
||||||
|
|
||||||
// 实时判断是否可以启用开机自启动
|
// 实时判断是否可以启用开机自启动
|
||||||
val canEnableAutoStart by remember {
|
val canEnableAutoStart by remember {
|
||||||
@@ -307,6 +303,7 @@ fun SuSFSConfigScreen(
|
|||||||
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
|
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
|
||||||
susPaths = SuSFSManager.getSusPaths(context)
|
susPaths = SuSFSManager.getSusPaths(context)
|
||||||
susLoopPaths = SuSFSManager.getSusLoopPaths(context)
|
susLoopPaths = SuSFSManager.getSusLoopPaths(context)
|
||||||
|
susMaps = SuSFSManager.getSusMaps(context)
|
||||||
susMounts = SuSFSManager.getSusMounts(context)
|
susMounts = SuSFSManager.getSusMounts(context)
|
||||||
tryUmounts = SuSFSManager.getTryUmounts(context)
|
tryUmounts = SuSFSManager.getTryUmounts(context)
|
||||||
androidDataPath = SuSFSManager.getAndroidDataPath(context)
|
androidDataPath = SuSFSManager.getAndroidDataPath(context)
|
||||||
@@ -479,6 +476,7 @@ fun SuSFSConfigScreen(
|
|||||||
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
|
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
|
||||||
susPaths = SuSFSManager.getSusPaths(context)
|
susPaths = SuSFSManager.getSusPaths(context)
|
||||||
susLoopPaths = SuSFSManager.getSusLoopPaths(context)
|
susLoopPaths = SuSFSManager.getSusLoopPaths(context)
|
||||||
|
susMaps = SuSFSManager.getSusMaps(context)
|
||||||
susMounts = SuSFSManager.getSusMounts(context)
|
susMounts = SuSFSManager.getSusMounts(context)
|
||||||
tryUmounts = SuSFSManager.getTryUmounts(context)
|
tryUmounts = SuSFSManager.getTryUmounts(context)
|
||||||
androidDataPath = SuSFSManager.getAndroidDataPath(context)
|
androidDataPath = SuSFSManager.getAndroidDataPath(context)
|
||||||
@@ -597,6 +595,35 @@ fun SuSFSConfigScreen(
|
|||||||
initialValue = editingLoopPath ?: ""
|
initialValue = editingLoopPath ?: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
AddPathDialog(
|
||||||
|
showDialog = showAddSusMapDialog,
|
||||||
|
onDismiss = {
|
||||||
|
showAddSusMapDialog = false
|
||||||
|
editingSusMap = null
|
||||||
|
},
|
||||||
|
onConfirm = { path ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
isLoading = true
|
||||||
|
val success = if (editingSusMap != null) {
|
||||||
|
SuSFSManager.editSusMap(context, editingSusMap!!, path)
|
||||||
|
} else {
|
||||||
|
SuSFSManager.addSusMap(context, path)
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
susMaps = SuSFSManager.getSusMaps(context)
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
showAddSusMapDialog = false
|
||||||
|
editingSusMap = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isLoading = isLoading,
|
||||||
|
titleRes = if (editingSusMap != null) R.string.susfs_edit_sus_map else R.string.susfs_add_sus_map,
|
||||||
|
labelRes = R.string.susfs_sus_map_label,
|
||||||
|
placeholderRes = R.string.susfs_sus_map_placeholder,
|
||||||
|
initialValue = editingSusMap ?: ""
|
||||||
|
)
|
||||||
|
|
||||||
AddAppPathDialog(
|
AddAppPathDialog(
|
||||||
showDialog = showAddAppPathDialog,
|
showDialog = showAddAppPathDialog,
|
||||||
onDismiss = { showAddAppPathDialog = false },
|
onDismiss = { showAddAppPathDialog = false },
|
||||||
@@ -817,6 +844,27 @@ fun SuSFSConfigScreen(
|
|||||||
isDestructive = true
|
isDestructive = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ConfirmDialog(
|
||||||
|
showDialog = showResetSusMapsDialog,
|
||||||
|
onDismiss = { showResetSusMapsDialog = false },
|
||||||
|
onConfirm = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
isLoading = true
|
||||||
|
SuSFSManager.saveSusMaps(context, emptySet())
|
||||||
|
susMaps = emptySet()
|
||||||
|
if (SuSFSManager.isAutoStartEnabled(context)) {
|
||||||
|
SuSFSManager.configureAutoStart(context, true)
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
showResetSusMapsDialog = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
titleRes = R.string.susfs_reset_sus_maps_title,
|
||||||
|
messageRes = R.string.susfs_reset_sus_maps_message,
|
||||||
|
isLoading = isLoading,
|
||||||
|
isDestructive = true
|
||||||
|
)
|
||||||
|
|
||||||
ConfirmDialog(
|
ConfirmDialog(
|
||||||
showDialog = showResetMountsDialog,
|
showDialog = showResetMountsDialog,
|
||||||
onDismiss = { showResetMountsDialog = false },
|
onDismiss = { showResetMountsDialog = false },
|
||||||
@@ -1036,6 +1084,28 @@ fun SuSFSConfigScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SuSFSTab.SUS_MAPS -> {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { showResetSusMapsDialog = true },
|
||||||
|
enabled = !isLoading && susMaps.isNotEmpty(),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(40.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.RestoreFromTrash,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.susfs_reset_sus_maps_title),
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SuSFSTab.SUS_MOUNTS -> {
|
SuSFSTab.SUS_MOUNTS -> {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { showResetMountsDialog = true },
|
onClick = { showResetMountsDialog = true },
|
||||||
@@ -1300,6 +1370,26 @@ fun SuSFSConfigScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
SuSFSTab.SUS_MAPS -> {
|
||||||
|
SusMapsContent(
|
||||||
|
susMaps = susMaps,
|
||||||
|
isLoading = isLoading,
|
||||||
|
onAddSusMap = { showAddSusMapDialog = true },
|
||||||
|
onRemoveSusMap = { map ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
isLoading = true
|
||||||
|
if (SuSFSManager.removeSusMap(context, map)) {
|
||||||
|
susMaps = SuSFSManager.getSusMaps(context)
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEditSusMap = { map ->
|
||||||
|
editingSusMap = map
|
||||||
|
showAddSusMapDialog = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
SuSFSTab.SUS_MOUNTS -> {
|
SuSFSTab.SUS_MOUNTS -> {
|
||||||
val isSusVersion158 = remember { isSusVersion158() }
|
val isSusVersion158 = remember { isSusVersion158() }
|
||||||
|
|
||||||
|
|||||||
@@ -310,6 +310,116 @@ fun SusLoopPathsContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SUS Maps内容组件
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun SusMapsContent(
|
||||||
|
susMaps: Set<String>,
|
||||||
|
isLoading: Boolean,
|
||||||
|
onAddSusMap: () -> Unit,
|
||||||
|
onRemoveSusMap: (String) -> Unit,
|
||||||
|
onEditSusMap: ((String) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
// 说明卡片
|
||||||
|
item {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.sus_maps_description_title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.sus_maps_description_text),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.sus_maps_warning),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.sus_maps_debug_info),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (susMaps.isEmpty()) {
|
||||||
|
item {
|
||||||
|
EmptyStateCard(
|
||||||
|
message = stringResource(R.string.susfs_no_sus_maps_configured)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item {
|
||||||
|
SectionHeader(
|
||||||
|
title = stringResource(R.string.sus_maps_section),
|
||||||
|
subtitle = null,
|
||||||
|
icon = Icons.Default.Security,
|
||||||
|
count = susMaps.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(susMaps.toList()) { map ->
|
||||||
|
PathItemCard(
|
||||||
|
path = map,
|
||||||
|
icon = Icons.Default.Security,
|
||||||
|
onDelete = { onRemoveSusMap(map) },
|
||||||
|
onEdit = if (onEditSusMap != null) { { onEditSusMap(map) } } else null,
|
||||||
|
isLoading = isLoading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onAddSusMap,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(48.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Add,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(text = stringResource(R.string.add))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SUS挂载内容组件
|
* SUS挂载内容组件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ object SuSFSManager {
|
|||||||
private const val KEY_AUTO_START_ENABLED = "auto_start_enabled"
|
private const val KEY_AUTO_START_ENABLED = "auto_start_enabled"
|
||||||
private const val KEY_SUS_PATHS = "sus_paths"
|
private const val KEY_SUS_PATHS = "sus_paths"
|
||||||
private const val KEY_SUS_LOOP_PATHS = "sus_loop_paths"
|
private const val KEY_SUS_LOOP_PATHS = "sus_loop_paths"
|
||||||
|
|
||||||
|
private const val KEY_SUS_MAPS = "sus_maps"
|
||||||
private const val KEY_SUS_MOUNTS = "sus_mounts"
|
private const val KEY_SUS_MOUNTS = "sus_mounts"
|
||||||
private const val KEY_TRY_UMOUNTS = "try_umounts"
|
private const val KEY_TRY_UMOUNTS = "try_umounts"
|
||||||
private const val KEY_ANDROID_DATA_PATH = "android_data_path"
|
private const val KEY_ANDROID_DATA_PATH = "android_data_path"
|
||||||
@@ -65,6 +67,7 @@ object SuSFSManager {
|
|||||||
private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID"
|
private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID"
|
||||||
private const val MIN_VERSION_FOR_HIDE_MOUNT = "1.5.8"
|
private const val MIN_VERSION_FOR_HIDE_MOUNT = "1.5.8"
|
||||||
private const val MIN_VERSION_FOR_LOOP_PATH = "1.5.9"
|
private const val MIN_VERSION_FOR_LOOP_PATH = "1.5.9"
|
||||||
|
private const val MIN_VERSION_SUS_MAPS = "1.5.12"
|
||||||
const val MAX_SUSFS_VERSION = "1.5.12"
|
const val MAX_SUSFS_VERSION = "1.5.12"
|
||||||
private const val BACKUP_FILE_EXTENSION = ".susfs_backup"
|
private const val BACKUP_FILE_EXTENSION = ".susfs_backup"
|
||||||
private const val MEDIA_DATA_PATH = "/data/media/0/Android/data"
|
private const val MEDIA_DATA_PATH = "/data/media/0/Android/data"
|
||||||
@@ -153,6 +156,7 @@ object SuSFSManager {
|
|||||||
val executeInPostFsData: Boolean,
|
val executeInPostFsData: Boolean,
|
||||||
val susPaths: Set<String>,
|
val susPaths: Set<String>,
|
||||||
val susLoopPaths: Set<String>,
|
val susLoopPaths: Set<String>,
|
||||||
|
val susMaps: Set<String>,
|
||||||
val susMounts: Set<String>,
|
val susMounts: Set<String>,
|
||||||
val tryUmounts: Set<String>,
|
val tryUmounts: Set<String>,
|
||||||
val androidDataPath: String,
|
val androidDataPath: String,
|
||||||
@@ -175,6 +179,7 @@ object SuSFSManager {
|
|||||||
buildTimeValue != DEFAULT_BUILD_TIME ||
|
buildTimeValue != DEFAULT_BUILD_TIME ||
|
||||||
susPaths.isNotEmpty() ||
|
susPaths.isNotEmpty() ||
|
||||||
susLoopPaths.isNotEmpty() ||
|
susLoopPaths.isNotEmpty() ||
|
||||||
|
susMaps.isNotEmpty() ||
|
||||||
susMounts.isNotEmpty() ||
|
susMounts.isNotEmpty() ||
|
||||||
tryUmounts.isNotEmpty() ||
|
tryUmounts.isNotEmpty() ||
|
||||||
kstatConfigs.isNotEmpty() ||
|
kstatConfigs.isNotEmpty() ||
|
||||||
@@ -264,6 +269,15 @@ object SuSFSManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isSusVersion1512(): Boolean {
|
||||||
|
return try {
|
||||||
|
val currentVersion = getSuSFSVersion()
|
||||||
|
compareVersions(currentVersion, MIN_VERSION_SUS_MAPS) >= 0
|
||||||
|
} catch (_: Exception) {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前模块配置
|
* 获取当前模块配置
|
||||||
*/
|
*/
|
||||||
@@ -275,6 +289,7 @@ object SuSFSManager {
|
|||||||
executeInPostFsData = getExecuteInPostFsData(context),
|
executeInPostFsData = getExecuteInPostFsData(context),
|
||||||
susPaths = getSusPaths(context),
|
susPaths = getSusPaths(context),
|
||||||
susLoopPaths = getSusLoopPaths(context),
|
susLoopPaths = getSusLoopPaths(context),
|
||||||
|
susMaps = getSusMaps(context),
|
||||||
susMounts = getSusMounts(context),
|
susMounts = getSusMounts(context),
|
||||||
tryUmounts = getTryUmounts(context),
|
tryUmounts = getTryUmounts(context),
|
||||||
androidDataPath = getAndroidDataPath(context),
|
androidDataPath = getAndroidDataPath(context),
|
||||||
@@ -379,6 +394,12 @@ object SuSFSManager {
|
|||||||
fun getSusLoopPaths(context: Context): Set<String> =
|
fun getSusLoopPaths(context: Context): Set<String> =
|
||||||
getPrefs(context).getStringSet(KEY_SUS_LOOP_PATHS, emptySet()) ?: emptySet()
|
getPrefs(context).getStringSet(KEY_SUS_LOOP_PATHS, emptySet()) ?: emptySet()
|
||||||
|
|
||||||
|
fun saveSusMaps(context: Context, maps: Set<String>) =
|
||||||
|
getPrefs(context).edit { putStringSet(KEY_SUS_MAPS, maps) }
|
||||||
|
|
||||||
|
fun getSusMaps(context: Context): Set<String> =
|
||||||
|
getPrefs(context).getStringSet(KEY_SUS_MAPS, emptySet()) ?: emptySet()
|
||||||
|
|
||||||
fun saveSusMounts(context: Context, mounts: Set<String>) =
|
fun saveSusMounts(context: Context, mounts: Set<String>) =
|
||||||
getPrefs(context).edit { putStringSet(KEY_SUS_MOUNTS, mounts) }
|
getPrefs(context).edit { putStringSet(KEY_SUS_MOUNTS, mounts) }
|
||||||
|
|
||||||
@@ -544,6 +565,7 @@ object SuSFSManager {
|
|||||||
KEY_AUTO_START_ENABLED to isAutoStartEnabled(context),
|
KEY_AUTO_START_ENABLED to isAutoStartEnabled(context),
|
||||||
KEY_SUS_PATHS to getSusPaths(context),
|
KEY_SUS_PATHS to getSusPaths(context),
|
||||||
KEY_SUS_LOOP_PATHS to getSusLoopPaths(context),
|
KEY_SUS_LOOP_PATHS to getSusLoopPaths(context),
|
||||||
|
KEY_SUS_MAPS to getSusMaps(context),
|
||||||
KEY_SUS_MOUNTS to getSusMounts(context),
|
KEY_SUS_MOUNTS to getSusMounts(context),
|
||||||
KEY_TRY_UMOUNTS to getTryUmounts(context),
|
KEY_TRY_UMOUNTS to getTryUmounts(context),
|
||||||
KEY_ANDROID_DATA_PATH to getAndroidDataPath(context),
|
KEY_ANDROID_DATA_PATH to getAndroidDataPath(context),
|
||||||
@@ -1137,6 +1159,54 @@ object SuSFSManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加 SUS Maps
|
||||||
|
suspend fun addSusMap(context: Context, map: String): Boolean {
|
||||||
|
val success = executeSusfsCommand(context, "add_sus_map '$map'")
|
||||||
|
if (success) {
|
||||||
|
saveSusMaps(context, getSusMaps(context) + map)
|
||||||
|
if (isAutoStartEnabled(context)) updateMagiskModule(context)
|
||||||
|
showToast(context, context.getString(R.string.susfs_sus_map_added_success, map))
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removeSusMap(context: Context, map: String): Boolean {
|
||||||
|
saveSusMaps(context, getSusMaps(context) - map)
|
||||||
|
if (isAutoStartEnabled(context)) updateMagiskModule(context)
|
||||||
|
showToast(context, context.getString(R.string.susfs_sus_map_removed, map))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun editSusMap(context: Context, oldMap: String, newMap: String): Boolean {
|
||||||
|
return try {
|
||||||
|
val currentMaps = getSusMaps(context).toMutableSet()
|
||||||
|
if (!currentMaps.remove(oldMap)) {
|
||||||
|
showToast(context, "Original SUS map not found: $oldMap")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSusMaps(context, currentMaps)
|
||||||
|
|
||||||
|
val success = addSusMap(context, newMap)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
showToast(context, context.getString(R.string.susfs_sus_map_updated, oldMap, newMap))
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// 如果添加新映射失败,恢复旧映射
|
||||||
|
currentMaps.add(oldMap)
|
||||||
|
saveSusMaps(context, currentMaps)
|
||||||
|
if (isAutoStartEnabled(context)) updateMagiskModule(context)
|
||||||
|
showToast(context, "Failed to update SUS map, reverted to original")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
showToast(context, "Error updating SUS map: ${e.message}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 添加SUS挂载
|
// 添加SUS挂载
|
||||||
suspend fun addSusMount(context: Context, mount: String): Boolean {
|
suspend fun addSusMount(context: Context, mount: String): Boolean {
|
||||||
val success = executeSusfsCommand(context, "add_sus_mount '$mount'")
|
val success = executeSusfsCommand(context, "add_sus_mount '$mount'")
|
||||||
|
|||||||
@@ -497,6 +497,10 @@ object ScriptGenerator {
|
|||||||
if (config.susLoopPaths.isNotEmpty()) {
|
if (config.susLoopPaths.isNotEmpty()) {
|
||||||
generateSusLoopPathsSection(config.susLoopPaths)
|
generateSusLoopPathsSection(config.susLoopPaths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.susMaps.isNotEmpty()) {
|
||||||
|
generateSusMapsSection(config.susMaps)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,6 +508,17 @@ object ScriptGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.generateSusMapsSection(susMaps: Set<String>) {
|
||||||
|
if (susMaps.isNotEmpty()) {
|
||||||
|
appendLine("# 添加SUS映射")
|
||||||
|
susMaps.forEach { map ->
|
||||||
|
appendLine("\"${'$'}SUSFS_BIN\" add_sus_map '$map'")
|
||||||
|
appendLine("echo \"$(get_current_time): 添加SUS映射: $map\" >> \"${'$'}LOG_FILE\"")
|
||||||
|
}
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("SdCardPath")
|
@SuppressLint("SdCardPath")
|
||||||
private fun StringBuilder.generatePathSettingSection(androidDataPath: String, sdcardPath: String) {
|
private fun StringBuilder.generatePathSettingSection(androidDataPath: String, sdcardPath: String) {
|
||||||
appendLine("# 路径配置")
|
appendLine("# 路径配置")
|
||||||
|
|||||||
@@ -670,4 +670,21 @@
|
|||||||
<string name="author">作者</string>
|
<string name="author">作者</string>
|
||||||
<string name="description">描述</string>
|
<string name="description">描述</string>
|
||||||
<string name="supported_devices">支持设备</string>
|
<string name="supported_devices">支持设备</string>
|
||||||
|
<!-- SUS Map related strings -->
|
||||||
|
<string name="susfs_tab_sus_maps">SUS映射</string>
|
||||||
|
<string name="susfs_sus_map_label">库文件路径</string>
|
||||||
|
<string name="susfs_sus_map_placeholder">/data/adb/modules/my_module/zygisk/arm64-v8a.so</string>
|
||||||
|
<string name="susfs_add_sus_map">添加SUS映射</string>
|
||||||
|
<string name="susfs_edit_sus_map">编辑SUS映射</string>
|
||||||
|
<string name="susfs_sus_map_added_success">SUS映射添加成功: %1$s</string>
|
||||||
|
<string name="susfs_sus_map_removed">SUS映射已移除: %1$s</string>
|
||||||
|
<string name="susfs_sus_map_updated">SUS映射已更新: %1$s -> %2$s</string>
|
||||||
|
<string name="susfs_no_sus_maps_configured">未配置SUS映射</string>
|
||||||
|
<string name="susfs_reset_sus_maps_title">重置SUS映射</string>
|
||||||
|
<string name="susfs_reset_sus_maps_message">这将移除所有已配置的SUS映射。此操作无法撤销。</string>
|
||||||
|
<string name="sus_maps_section">内存映射隐藏</string>
|
||||||
|
<string name="sus_maps_description_title">隐藏/proc/self/中各种映射中的mmap真实文件</string>
|
||||||
|
<string name="sus_maps_description_text">从 /proc/self/[maps|smaps|smaps_rollup|map_files|mem|pagemap] 中隐藏内存映射的真实文件路径。请注意:此功能不支持隐藏匿名内存映射,也无法隐藏由注入库本身产生的内联钩子或 PLT 钩子。</string>
|
||||||
|
<string name="sus_maps_warning">重要提示:对于具备完善注入检测机制的应用,此功能可能无法有效绕过检测。</string>
|
||||||
|
<string name="sus_maps_debug_info">首先通过 ps -enf 查找目标应用的 PID 和 UID,然后检查 /proc/<pid>/maps 中的相关路径,并与 /proc/1/mountinfo 中的设备号进行比对以确保一致性。只有当设备号一致时,隐藏映射才能正常工作。</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -678,4 +678,21 @@ Important Note:\n
|
|||||||
<string name="author">Author</string>
|
<string name="author">Author</string>
|
||||||
<string name="description">Description</string>
|
<string name="description">Description</string>
|
||||||
<string name="supported_devices">Supported Devices</string>
|
<string name="supported_devices">Supported Devices</string>
|
||||||
|
<!-- SUS Map related strings -->
|
||||||
|
<string name="susfs_tab_sus_maps">SUS Maps</string>
|
||||||
|
<string name="susfs_sus_map_label">Library Path</string>
|
||||||
|
<string name="susfs_sus_map_placeholder">/data/adb/modules/my_module/zygisk/arm64-v8a.so</string>
|
||||||
|
<string name="susfs_add_sus_map">Add SUS Map</string>
|
||||||
|
<string name="susfs_edit_sus_map">Edit SUS Map</string>
|
||||||
|
<string name="susfs_sus_map_added_success">SUS map added successfully: %1$s</string>
|
||||||
|
<string name="susfs_sus_map_removed">SUS map removed: %1$s</string>
|
||||||
|
<string name="susfs_sus_map_updated">SUS map updated: %1$s -> %2$s</string>
|
||||||
|
<string name="susfs_no_sus_maps_configured">No SUS maps configured</string>
|
||||||
|
<string name="susfs_reset_sus_maps_title">Reset SUS Maps</string>
|
||||||
|
<string name="susfs_reset_sus_maps_message">This will remove all configured SUS maps. This action cannot be undone.</string>
|
||||||
|
<string name="sus_maps_section">Memory Map Hiding</string>
|
||||||
|
<string name="sus_maps_description_title">Hide the mmapped real file from various maps in /proc/self/</string>
|
||||||
|
<string name="sus_maps_description_text">Hide the real file paths of memory mappings from /proc/self/[maps|smaps|smaps_rollup|map_files|mem|pagemap]. Please note: This feature does not support hiding anonymous memory mappings, nor can it hide inline hooks or PLT hooks caused by the injected library itself.</string>
|
||||||
|
<string name="sus_maps_warning">Important Notice: For applications with well-implemented injection detection mechanisms, this feature may not effectively bypass detection.</string>
|
||||||
|
<string name="sus_maps_debug_info">First, find the target application\'s PID and UID using ps -enf, then check the relevant paths in /proc/<pid>/maps and compare the device numbers with those in /proc/1/mountinfo to ensure consistency. Only when the device numbers match can the map hiding function work properly.</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user