manager: install: add choose partition support
manager: fix KsuCli cmd userspace: reuse choose_boot_device - manager: simplify find boot image Co-authored-by: weishu <twsxtd@gmail.com> Co-authored-by: YuKongA <70465933+YuKongA@users.noreply.github.com> Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,250 @@
|
|||||||
|
package com.sukisu.ultra.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SuperDropdown(
|
||||||
|
items: List<String>,
|
||||||
|
selectedIndex: Int,
|
||||||
|
title: String,
|
||||||
|
summary: String? = null,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
showValue: Boolean = true,
|
||||||
|
maxHeight: Dp? = 400.dp,
|
||||||
|
colors: SuperDropdownColors = SuperDropdownDefaults.colors(),
|
||||||
|
leftAction: (@Composable () -> Unit)? = null,
|
||||||
|
onSelectedIndexChange: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
val selectedItemText = items.getOrNull(selectedIndex) ?: ""
|
||||||
|
val itemsNotEmpty = items.isNotEmpty()
|
||||||
|
val actualEnabled = enabled && itemsNotEmpty
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(enabled = actualEnabled) { showDialog = true }
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.Top
|
||||||
|
) {
|
||||||
|
if (leftAction != null) {
|
||||||
|
leftAction()
|
||||||
|
} else if (icon != null) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (actualEnabled) colors.iconColor else colors.disabledIconColor,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = if (actualEnabled) colors.titleColor else colors.disabledTitleColor
|
||||||
|
)
|
||||||
|
|
||||||
|
if (summary != null) {
|
||||||
|
Spacer(modifier = Modifier.height(3.dp))
|
||||||
|
Text(
|
||||||
|
text = summary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = if (actualEnabled) colors.summaryColor else colors.disabledSummaryColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showValue && itemsNotEmpty) {
|
||||||
|
Spacer(modifier = Modifier.height(3.dp))
|
||||||
|
Text(
|
||||||
|
text = selectedItemText,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = if (actualEnabled) colors.valueColor else colors.disabledValueColor,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (actualEnabled) colors.arrowColor else colors.disabledArrowColor,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDialog && itemsNotEmpty) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDialog = false },
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
val dialogMaxHeight = maxHeight ?: 400.dp
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(max = dialogMaxHeight),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
items(items.size) { index ->
|
||||||
|
DropdownItem(
|
||||||
|
text = items[index],
|
||||||
|
isSelected = selectedIndex == index,
|
||||||
|
colors = colors,
|
||||||
|
onClick = {
|
||||||
|
onSelectedIndexChange(index)
|
||||||
|
showDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { showDialog = false }) {
|
||||||
|
Text(text = stringResource(id = android.R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerColor = colors.dialogBackgroundColor,
|
||||||
|
shape = MaterialTheme.shapes.extraLarge,
|
||||||
|
tonalElevation = 4.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DropdownItem(
|
||||||
|
text: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
colors: SuperDropdownColors,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val backgroundColor = if (isSelected) {
|
||||||
|
colors.selectedBackgroundColor
|
||||||
|
} else {
|
||||||
|
Color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentColor = if (isSelected) {
|
||||||
|
colors.selectedContentColor
|
||||||
|
} else {
|
||||||
|
colors.contentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(backgroundColor)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(vertical = 12.dp, horizontal = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = null,
|
||||||
|
colors = RadioButtonDefaults.colors(
|
||||||
|
selectedColor = colors.selectedContentColor,
|
||||||
|
unselectedColor = colors.contentColor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = contentColor,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = colors.selectedContentColor,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class SuperDropdownColors(
|
||||||
|
val titleColor: Color,
|
||||||
|
val summaryColor: Color,
|
||||||
|
val valueColor: Color,
|
||||||
|
val iconColor: Color,
|
||||||
|
val arrowColor: Color,
|
||||||
|
val disabledTitleColor: Color,
|
||||||
|
val disabledSummaryColor: Color,
|
||||||
|
val disabledValueColor: Color,
|
||||||
|
val disabledIconColor: Color,
|
||||||
|
val disabledArrowColor: Color,
|
||||||
|
val dialogBackgroundColor: Color,
|
||||||
|
val contentColor: Color,
|
||||||
|
val selectedContentColor: Color,
|
||||||
|
val selectedBackgroundColor: Color
|
||||||
|
)
|
||||||
|
|
||||||
|
object SuperDropdownDefaults {
|
||||||
|
@Composable
|
||||||
|
fun colors(
|
||||||
|
titleColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
summaryColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
valueColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
iconColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
arrowColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
disabledTitleColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
||||||
|
disabledSummaryColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||||
|
disabledValueColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||||
|
disabledIconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||||
|
disabledArrowColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||||
|
dialogBackgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
selectedContentColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
selectedBackgroundColor: Color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
|
||||||
|
): SuperDropdownColors {
|
||||||
|
return SuperDropdownColors(
|
||||||
|
titleColor = titleColor,
|
||||||
|
summaryColor = summaryColor,
|
||||||
|
valueColor = valueColor,
|
||||||
|
iconColor = iconColor,
|
||||||
|
arrowColor = arrowColor,
|
||||||
|
disabledTitleColor = disabledTitleColor,
|
||||||
|
disabledSummaryColor = disabledSummaryColor,
|
||||||
|
disabledValueColor = disabledValueColor,
|
||||||
|
disabledIconColor = disabledIconColor,
|
||||||
|
disabledArrowColor = disabledArrowColor,
|
||||||
|
dialogBackgroundColor = dialogBackgroundColor,
|
||||||
|
contentColor = contentColor,
|
||||||
|
selectedContentColor = selectedContentColor,
|
||||||
|
selectedBackgroundColor = selectedBackgroundColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -707,7 +707,7 @@ suspend fun getModuleNameFromUri(context: Context, uri: Uri): String {
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
sealed class FlashIt : Parcelable {
|
sealed class FlashIt : Parcelable {
|
||||||
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : FlashIt()
|
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean, val partition: String? = null) : FlashIt()
|
||||||
data class FlashModule(val uri: Uri) : FlashIt()
|
data class FlashModule(val uri: Uri) : FlashIt()
|
||||||
data class FlashModules(val uris: List<Uri>, val currentIndex: Int = 0) : FlashIt()
|
data class FlashModules(val uris: List<Uri>, val currentIndex: Int = 0) : FlashIt()
|
||||||
data class FlashModuleUpdate(val uri: Uri) : FlashIt() // 模块更新
|
data class FlashModuleUpdate(val uri: Uri) : FlashIt() // 模块更新
|
||||||
@@ -736,6 +736,7 @@ fun flashIt(
|
|||||||
flashIt.boot,
|
flashIt.boot,
|
||||||
flashIt.lkm,
|
flashIt.lkm,
|
||||||
flashIt.ota,
|
flashIt.ota,
|
||||||
|
flashIt.partition,
|
||||||
onFinish,
|
onFinish,
|
||||||
onStdout,
|
onStdout,
|
||||||
onStderr
|
onStderr
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.filled.Input
|
import androidx.compose.material.icons.automirrored.filled.Input
|
||||||
import androidx.compose.material.icons.filled.AutoFixHigh
|
import androidx.compose.material.icons.filled.AutoFixHigh
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.filled.FileUpload
|
import androidx.compose.material.icons.filled.FileUpload
|
||||||
import androidx.compose.material.icons.filled.Security
|
import androidx.compose.material.icons.filled.Security
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@@ -51,7 +52,7 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.getKernelVersion
|
import com.sukisu.ultra.getKernelVersion
|
||||||
import com.sukisu.ultra.ui.component.DialogHandle
|
import com.sukisu.ultra.ui.component.DialogHandle
|
||||||
import zako.zako.zako.zakoui.screen.kernelFlash.component.SlotSelectionDialog
|
import com.sukisu.ultra.ui.component.SuperDropdown
|
||||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||||
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig
|
import com.sukisu.ultra.ui.theme.CardConfig
|
||||||
@@ -60,6 +61,7 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
|||||||
import com.sukisu.ultra.ui.theme.getCardColors
|
import com.sukisu.ultra.ui.theme.getCardColors
|
||||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||||
import com.sukisu.ultra.ui.util.*
|
import com.sukisu.ultra.ui.util.*
|
||||||
|
import zako.zako.zako.zakoui.screen.kernelFlash.component.SlotSelectionDialog
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ShirkNeko
|
* @author ShirkNeko
|
||||||
@@ -87,9 +89,12 @@ fun InstallScreen(
|
|||||||
var showSlotSelectionDialog by remember { mutableStateOf(false) }
|
var showSlotSelectionDialog by remember { mutableStateOf(false) }
|
||||||
var showKpmPatchDialog by remember { mutableStateOf(false) }
|
var showKpmPatchDialog by remember { mutableStateOf(false) }
|
||||||
var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
|
var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
|
||||||
|
|
||||||
val kernelVersion = getKernelVersion()
|
val kernelVersion = getKernelVersion()
|
||||||
val isGKI = kernelVersion.isGKI()
|
val isGKI = kernelVersion.isGKI()
|
||||||
val isAbDevice = isAbDevice()
|
val isAbDevice = produceState(initialValue = false) {
|
||||||
|
value = isAbDevice()
|
||||||
|
}.value
|
||||||
val summary = stringResource(R.string.horizon_kernel_summary)
|
val summary = stringResource(R.string.horizon_kernel_summary)
|
||||||
|
|
||||||
// 处理预选的内核文件
|
// 处理预选的内核文件
|
||||||
@@ -103,6 +108,7 @@ fun InstallScreen(
|
|||||||
)
|
)
|
||||||
installMethod = horizonMethod
|
installMethod = horizonMethod
|
||||||
tempKernelUri = preselectedUri
|
tempKernelUri = preselectedUri
|
||||||
|
|
||||||
if (isAbDevice) {
|
if (isAbDevice) {
|
||||||
showSlotSelectionDialog = true
|
showSlotSelectionDialog = true
|
||||||
} else {
|
} else {
|
||||||
@@ -133,6 +139,10 @@ fun InstallScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var partitionSelectionIndex by remember { mutableIntStateOf(0) }
|
||||||
|
var partitionsState by remember { mutableStateOf<List<String>>(emptyList()) }
|
||||||
|
var hasCustomSelected by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val onInstall = {
|
val onInstall = {
|
||||||
installMethod?.let { method ->
|
installMethod?.let { method ->
|
||||||
when (method) {
|
when (method) {
|
||||||
@@ -149,10 +159,13 @@ fun InstallScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
val isOta = method is InstallMethod.DirectInstallToInactiveSlot
|
||||||
|
val partitionSelection = partitionsState.getOrNull(partitionSelectionIndex)
|
||||||
val flashIt = FlashIt.FlashBoot(
|
val flashIt = FlashIt.FlashBoot(
|
||||||
boot = if (method is InstallMethod.SelectFile) method.uri else null,
|
boot = if (method is InstallMethod.SelectFile) method.uri else null,
|
||||||
lkm = lkmSelection,
|
lkm = lkmSelection,
|
||||||
ota = method is InstallMethod.DirectInstallToInactiveSlot
|
ota = isOta,
|
||||||
|
partition = partitionSelection
|
||||||
)
|
)
|
||||||
navigator.navigate(FlashScreenDestination(flashIt))
|
navigator.navigate(FlashScreenDestination(flashIt))
|
||||||
}
|
}
|
||||||
@@ -173,6 +186,7 @@ fun InstallScreen(
|
|||||||
summary = summary
|
summary = summary
|
||||||
)
|
)
|
||||||
installMethod = horizonMethod
|
installMethod = horizonMethod
|
||||||
|
|
||||||
if (preselectedKernelUri != null) {
|
if (preselectedKernelUri != null) {
|
||||||
showKpmPatchDialog = true
|
showKpmPatchDialog = true
|
||||||
}
|
}
|
||||||
@@ -274,11 +288,72 @@ fun InstallScreen(
|
|||||||
selectedMethod = installMethod
|
selectedMethod = installMethod
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 选择LKM直接安装分区
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = installMethod is InstallMethod.DirectInstall || installMethod is InstallMethod.DirectInstallToInactiveSlot,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = shrinkVertically() + fadeOut()
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
|
ElevatedCard(
|
||||||
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
|
elevation = getCardElevation(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
|
) {
|
||||||
|
val isOta = installMethod is InstallMethod.DirectInstallToInactiveSlot
|
||||||
|
val suffix = produceState(initialValue = "", isOta) {
|
||||||
|
value = getSlotSuffix(isOta)
|
||||||
|
}.value
|
||||||
|
|
||||||
|
val partitions = produceState(initialValue = emptyList()) {
|
||||||
|
value = getAvailablePartitions()
|
||||||
|
}.value
|
||||||
|
|
||||||
|
val defaultPartition = produceState(initialValue = "") {
|
||||||
|
value = getDefaultPartition()
|
||||||
|
}.value
|
||||||
|
|
||||||
|
partitionsState = partitions
|
||||||
|
val displayPartitions = partitions.map { name ->
|
||||||
|
if (defaultPartition == name) "$name (default)" else name
|
||||||
|
}
|
||||||
|
|
||||||
|
val defaultIndex = partitions.indexOf(defaultPartition).takeIf { it >= 0 } ?: 0
|
||||||
|
if (!hasCustomSelected) partitionSelectionIndex = defaultIndex
|
||||||
|
|
||||||
|
SuperDropdown(
|
||||||
|
items = displayPartitions,
|
||||||
|
selectedIndex = partitionSelectionIndex,
|
||||||
|
title = "${stringResource(R.string.install_select_partition)} (${suffix})",
|
||||||
|
onSelectedIndexChange = { index ->
|
||||||
|
hasCustomSelected = true
|
||||||
|
partitionSelectionIndex = index
|
||||||
|
},
|
||||||
|
leftAction = {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Edit,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
modifier = Modifier.padding(end = 16.dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
// 使用本地的LKM文件
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
elevation = getCardElevation(),
|
elevation = getCardElevation(),
|
||||||
@@ -311,6 +386,7 @@ fun InstallScreen(
|
|||||||
.clickable { onLkmUpload() }
|
.clickable { onLkmUpload() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
|
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
|
||||||
if (method.slot != null) {
|
if (method.slot != null) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
@@ -319,12 +395,6 @@ fun InstallScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(bottom = 12.dp)
|
.padding(bottom = 12.dp)
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
.shadow(
|
|
||||||
elevation = cardElevation,
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
@@ -346,12 +416,6 @@ fun InstallScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(bottom = 12.dp)
|
.padding(bottom = 12.dp)
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
.shadow(
|
|
||||||
elevation = cardElevation,
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = when (kpmPatchOption) {
|
text = when (kpmPatchOption) {
|
||||||
@@ -496,15 +560,15 @@ private fun SelectInstallMethod(
|
|||||||
selectedMethod: InstallMethod? = null
|
selectedMethod: InstallMethod? = null
|
||||||
) {
|
) {
|
||||||
val rootAvailable = rootAvailable()
|
val rootAvailable = rootAvailable()
|
||||||
val isAbDevice = isAbDevice()
|
val isAbDevice = produceState(initialValue = false) {
|
||||||
|
value = isAbDevice()
|
||||||
|
}.value
|
||||||
|
val defaultPartitionName = produceState(initialValue = "boot") {
|
||||||
|
value = getDefaultPartition()
|
||||||
|
}.value
|
||||||
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
|
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
|
||||||
val selectFileTip = stringResource(
|
val selectFileTip = stringResource(
|
||||||
id = R.string.select_file_tip,
|
id = R.string.select_file_tip, defaultPartitionName
|
||||||
if (isInitBoot()) {
|
|
||||||
"init_boot / vendor_boot ${stringResource(R.string.select_file_tip_vendor)}"
|
|
||||||
} else {
|
|
||||||
"boot"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val radioOptions = mutableListOf<InstallMethod>(
|
val radioOptions = mutableListOf<InstallMethod>(
|
||||||
@@ -601,7 +665,6 @@ private fun SelectInstallMethod(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(bottom = 16.dp)
|
.padding(bottom = 16.dp)
|
||||||
.clip(MaterialTheme.shapes.large)
|
|
||||||
) {
|
) {
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = MaterialTheme.colorScheme.copy(
|
colorScheme = MaterialTheme.colorScheme.copy(
|
||||||
@@ -640,7 +703,7 @@ private fun SelectInstallMethod(
|
|||||||
bottom = 16.dp
|
bottom = 16.dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
radioOptions.take(3).forEach { option ->
|
radioOptions.filter { it !is InstallMethod.HorizonKernel }.forEach { option ->
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
Surface(
|
Surface(
|
||||||
color = if (option.javaClass == selectedOption?.javaClass)
|
color = if (option.javaClass == selectedOption?.javaClass)
|
||||||
@@ -708,7 +771,6 @@ private fun SelectInstallMethod(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(bottom = 12.dp)
|
.padding(bottom = 12.dp)
|
||||||
.clip(MaterialTheme.shapes.large)
|
|
||||||
) {
|
) {
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = MaterialTheme.colorScheme.copy(
|
colorScheme = MaterialTheme.colorScheme.copy(
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SettingDropdown(
|
SuperDropdown(
|
||||||
icon = Icons.Rounded.EnhancedEncryption,
|
icon = Icons.Rounded.EnhancedEncryption,
|
||||||
title = stringResource(id = R.string.settings_enable_enhanced_security),
|
title = stringResource(id = R.string.settings_enable_enhanced_security),
|
||||||
summary = stringResource(id = R.string.settings_enable_enhanced_security_summary),
|
summary = stringResource(id = R.string.settings_enable_enhanced_security_summary),
|
||||||
@@ -193,7 +193,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SettingDropdown(
|
SuperDropdown(
|
||||||
icon = Icons.Rounded.RemoveModerator,
|
icon = Icons.Rounded.RemoveModerator,
|
||||||
title = stringResource(id = R.string.settings_disable_su),
|
title = stringResource(id = R.string.settings_disable_su),
|
||||||
summary = stringResource(id = R.string.settings_disable_su_summary),
|
summary = stringResource(id = R.string.settings_disable_su_summary),
|
||||||
@@ -236,7 +236,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SettingDropdown(
|
SuperDropdown(
|
||||||
icon = Icons.Rounded.RemoveCircle,
|
icon = Icons.Rounded.RemoveCircle,
|
||||||
title = stringResource(id = R.string.settings_disable_kernel_umount),
|
title = stringResource(id = R.string.settings_disable_kernel_umount),
|
||||||
summary = stringResource(id = R.string.settings_disable_kernel_umount_summary),
|
summary = stringResource(id = R.string.settings_disable_kernel_umount_summary),
|
||||||
@@ -1077,103 +1077,3 @@ private fun UidScannerSection(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun SettingDropdown(
|
|
||||||
icon: ImageVector,
|
|
||||||
title: String,
|
|
||||||
summary: String,
|
|
||||||
items: List<String>,
|
|
||||||
selectedIndex: Int,
|
|
||||||
leftAction: (@Composable () -> Unit)? = null,
|
|
||||||
onSelectedIndexChange: (Int) -> Unit
|
|
||||||
) {
|
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
|
||||||
val selectedItemText = items.getOrNull(selectedIndex) ?: ""
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { showDialog = true }
|
|
||||||
.padding(horizontal = SPACING_LARGE, vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.Top
|
|
||||||
) {
|
|
||||||
if (leftAction != null) {
|
|
||||||
leftAction()
|
|
||||||
} else {
|
|
||||||
Icon(
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = SPACING_LARGE)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleMedium
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(SPACING_SMALL))
|
|
||||||
Text(
|
|
||||||
text = summary,
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(SPACING_SMALL))
|
|
||||||
Text(
|
|
||||||
text = selectedItemText,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showDialog) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showDialog = false },
|
|
||||||
title = { Text(text = title) },
|
|
||||||
text = {
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
items(items.size) { index ->
|
|
||||||
val item = items[index]
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
onSelectedIndexChange(index)
|
|
||||||
showDialog = false
|
|
||||||
}
|
|
||||||
.padding(vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
RadioButton(
|
|
||||||
selected = selectedIndex == index,
|
|
||||||
onClick = null
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
Text(text = item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { showDialog = false }) {
|
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1573,7 +1573,9 @@ private fun BasicSettingsContent(
|
|||||||
onEnableAvcLogSpoofingChange: (Boolean) -> Unit
|
onEnableAvcLogSpoofingChange: (Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
var scriptLocationExpanded by remember { mutableStateOf(false) }
|
var scriptLocationExpanded by remember { mutableStateOf(false) }
|
||||||
val isAbDevice = isAbDevice()
|
val isAbDevice = produceState(initialValue = false) {
|
||||||
|
value = isAbDevice()
|
||||||
|
}.value
|
||||||
val isSusVersion159 = isSusVersion159()
|
val isSusVersion159 = isSusVersion159()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -2063,7 +2065,9 @@ private fun SlotInfoDialog(
|
|||||||
onUseUname: (String) -> Unit,
|
onUseUname: (String) -> Unit,
|
||||||
onUseBuildTime: (String) -> Unit
|
onUseBuildTime: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val isAbDevice = isAbDevice()
|
val isAbDevice = produceState(initialValue = false) {
|
||||||
|
value = isAbDevice()
|
||||||
|
}.value
|
||||||
|
|
||||||
if (showDialog && isAbDevice) {
|
if (showDialog && isAbDevice) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ fun installBoot(
|
|||||||
bootUri: Uri?,
|
bootUri: Uri?,
|
||||||
lkm: LkmSelection,
|
lkm: LkmSelection,
|
||||||
ota: Boolean,
|
ota: Boolean,
|
||||||
|
partition: String?,
|
||||||
onFinish: (Boolean, Int) -> Unit,
|
onFinish: (Boolean, Int) -> Unit,
|
||||||
onStdout: (String) -> Unit,
|
onStdout: (String) -> Unit,
|
||||||
onStderr: (String) -> Unit,
|
onStderr: (String) -> Unit,
|
||||||
@@ -314,6 +315,10 @@ fun installBoot(
|
|||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
cmd += " -o $downloadsDir"
|
cmd += " -o $downloadsDir"
|
||||||
|
|
||||||
|
partition?.let { part ->
|
||||||
|
cmd += " --partition $part"
|
||||||
|
}
|
||||||
|
|
||||||
val result = flashWithIO("${getKsuDaemonPath()} $cmd", onStdout, onStderr)
|
val result = flashWithIO("${getKsuDaemonPath()} $cmd", onStdout, onStderr)
|
||||||
Log.i("KernelSU", "install boot result: ${result.isSuccess}")
|
Log.i("KernelSU", "install boot result: ${result.isSuccess}")
|
||||||
|
|
||||||
@@ -344,18 +349,6 @@ fun rootAvailable(): Boolean {
|
|||||||
return shell.isRoot
|
return shell.isRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isAbDevice(): Boolean {
|
|
||||||
val shell = getRootShell()
|
|
||||||
return ShellUtils.fastCmd(shell, "getprop ro.build.ab_update").trim().toBoolean()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isInitBoot(): Boolean {
|
|
||||||
val shell = getRootShell();
|
|
||||||
if (shell.isRoot) {
|
|
||||||
return SuFile("/dev/block/by-name/init_boot").exists() || SuFile("/dev/block/by-name/init_boot_a").exists()
|
|
||||||
}
|
|
||||||
return !Os.uname().release.contains("android12-")
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getCurrentKmi(): String = withContext(Dispatchers.IO) {
|
suspend fun getCurrentKmi(): String = withContext(Dispatchers.IO) {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
@@ -365,7 +358,36 @@ suspend fun getCurrentKmi(): String = withContext(Dispatchers.IO) {
|
|||||||
|
|
||||||
suspend fun getSupportedKmis(): List<String> = withContext(Dispatchers.IO) {
|
suspend fun getSupportedKmis(): List<String> = withContext(Dispatchers.IO) {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
val cmd = "boot-info supported-kmi"
|
val cmd = "boot-info supported-kmis"
|
||||||
|
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
|
||||||
|
out.filter { it.isNotBlank() }.map { it.trim() }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun isAbDevice(): Boolean = withContext(Dispatchers.IO) {
|
||||||
|
val shell = getRootShell()
|
||||||
|
val cmd = "boot-info is-ab-device"
|
||||||
|
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim().toBoolean()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getDefaultPartition(): String = withContext(Dispatchers.IO) {
|
||||||
|
val shell = getRootShell()
|
||||||
|
val cmd = "boot-info default-partition"
|
||||||
|
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSlotSuffix(ota: Boolean): String = withContext(Dispatchers.IO) {
|
||||||
|
val shell = getRootShell()
|
||||||
|
val cmd = if (ota) {
|
||||||
|
"boot-info slot-suffix --ota"
|
||||||
|
} else {
|
||||||
|
"boot-info slot-suffix"
|
||||||
|
}
|
||||||
|
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAvailablePartitions(): List<String> = withContext(Dispatchers.IO) {
|
||||||
|
val shell = getRootShell()
|
||||||
|
val cmd = "boot-info available-partitions"
|
||||||
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
|
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
|
||||||
out.filter { it.isNotBlank() }.map { it.trim() }
|
out.filter { it.isNotBlank() }.map { it.trim() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,8 +108,9 @@
|
|||||||
<string name="direct_install">直接安装(推荐)</string>
|
<string name="direct_install">直接安装(推荐)</string>
|
||||||
<string name="select_file">选择一个需要修补的镜像</string>
|
<string name="select_file">选择一个需要修补的镜像</string>
|
||||||
<string name="install_inactive_slot">安装到未使用的槽位(OTA 后)</string>
|
<string name="install_inactive_slot">安装到未使用的槽位(OTA 后)</string>
|
||||||
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认?</string>
|
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!注意只能在 OTA 更新完成后的重启之前使用。</string>
|
||||||
<string name="install_next">下一步</string>
|
<string name="install_next">下一步</string>
|
||||||
|
<string name="install_select_partition">选择分区</string>
|
||||||
<string name="install_upload_lkm_file">使用本地 LKM 文件</string>
|
<string name="install_upload_lkm_file">使用本地 LKM 文件</string>
|
||||||
<string name="install_only_support_ko_file">仅支持选择 .ko 文件</string>
|
<string name="install_only_support_ko_file">仅支持选择 .ko 文件</string>
|
||||||
<string name="select_file_tip">建议选择 %1$s 分区镜像</string>
|
<string name="select_file_tip">建议选择 %1$s 分区镜像</string>
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
<string name="install_inactive_slot">Install to inactive slot (After OTA)</string>
|
<string name="install_inactive_slot">Install to inactive slot (After OTA)</string>
|
||||||
<string name="install_inactive_slot_warning">Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue?</string>
|
<string name="install_inactive_slot_warning">Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue?</string>
|
||||||
<string name="install_next">Next</string>
|
<string name="install_next">Next</string>
|
||||||
|
<string name="install_select_partition">Select partition</string>
|
||||||
<string name="install_upload_lkm_file">Use local LKM file</string>
|
<string name="install_upload_lkm_file">Use local LKM file</string>
|
||||||
<string name="install_only_support_ko_file">Only .ko files are supported</string>
|
<string name="install_only_support_ko_file">Only .ko files are supported</string>
|
||||||
<string name="select_file_tip">%1$s partition image is recommended</string>
|
<string name="select_file_tip">%1$s partition image is recommended</string>
|
||||||
|
|||||||
@@ -150,126 +150,40 @@ fn parse_kmi_from_boot(magiskboot: &Path, image: &PathBuf, workdir: &Path) -> Re
|
|||||||
parse_kmi_from_kernel(&image_path, workdir)
|
parse_kmi_from_kernel(&image_path, workdir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_cpio_cmd(magiskboot: &Path, workdir: &Path, cmd: &str) -> Result<()> {
|
fn do_cpio_cmd(magiskboot: &Path, workdir: &Path, cpio_path: &Path, cmd: &str) -> Result<()> {
|
||||||
let status = Command::new(magiskboot)
|
let status = Command::new(magiskboot)
|
||||||
.current_dir(workdir)
|
.current_dir(workdir)
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.arg("cpio")
|
.arg("cpio")
|
||||||
.arg("ramdisk.cpio")
|
.arg(cpio_path)
|
||||||
.arg(cmd)
|
.arg(cmd)
|
||||||
.status()?;
|
.status()?;
|
||||||
|
|
||||||
ensure!(status.success(), "magiskboot cpio {} failed", cmd);
|
ensure!(status.success(), "magiskboot cpio {} failed", cmd);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_vendor_init_boot_cpio_cmd(magiskboot: &Path, workdir: &Path, cmd: &str) -> Result<()> {
|
fn is_magisk_patched(magiskboot: &Path, workdir: &Path, cpio_path: &Path) -> Result<bool> {
|
||||||
let vendor_init_boot_cpio = workdir.join("vendor_ramdisk").join("init_boot.cpio");
|
|
||||||
let status = Command::new(magiskboot)
|
let status = Command::new(magiskboot)
|
||||||
.current_dir(workdir)
|
.current_dir(workdir)
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.arg("cpio")
|
.arg("cpio")
|
||||||
.arg(vendor_init_boot_cpio)
|
.arg(cpio_path)
|
||||||
.arg(cmd)
|
.arg("test")
|
||||||
.status()?;
|
.status()?;
|
||||||
|
// 0: stock, 1: magisk
|
||||||
ensure!(status.success(), "magiskboot cpio {} failed", cmd);
|
Ok(status.code() == Some(1))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_vendor_ramdisk_cpio_cmd(magiskboot: &Path, workdir: &Path, cmd: &str) -> Result<()> {
|
fn is_kernelsu_patched(magiskboot: &Path, workdir: &Path, cpio_path: &Path) -> Result<bool> {
|
||||||
let vendor_ramdisk_cpio = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
|
|
||||||
let status = Command::new(magiskboot)
|
let status = Command::new(magiskboot)
|
||||||
.current_dir(workdir)
|
.current_dir(workdir)
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.arg("cpio")
|
.arg("cpio")
|
||||||
.arg(vendor_ramdisk_cpio)
|
.arg(cpio_path)
|
||||||
.arg(cmd)
|
.arg("exists kernelsu.ko")
|
||||||
.status()?;
|
|
||||||
|
|
||||||
ensure!(status.success(), "magiskboot cpio {} failed", cmd);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_magisk_patched(magiskboot: &Path, workdir: &Path) -> Result<bool> {
|
|
||||||
let status = Command::new(magiskboot)
|
|
||||||
.current_dir(workdir)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.args(["cpio", "ramdisk.cpio", "test"])
|
|
||||||
.status()?;
|
|
||||||
|
|
||||||
// 0: stock, 1: magisk
|
|
||||||
Ok(status.code() == Some(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_magisk_patched_vendor_init_boot(magiskboot: &Path, workdir: &Path) -> Result<bool> {
|
|
||||||
let vendor_init_boot_cpio = workdir.join("vendor_ramdisk").join("init_boot.cpio");
|
|
||||||
let status = Command::new(magiskboot)
|
|
||||||
.current_dir(workdir)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.args(["cpio", vendor_init_boot_cpio.to_str().unwrap(), "test"])
|
|
||||||
.status()?;
|
|
||||||
|
|
||||||
// 0: stock, 1: magisk
|
|
||||||
Ok(status.code() == Some(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_magisk_patched_vendor_ramdisk(magiskboot: &Path, workdir: &Path) -> Result<bool> {
|
|
||||||
let vendor_ramdisk_cpio = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
|
|
||||||
let status = Command::new(magiskboot)
|
|
||||||
.current_dir(workdir)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.args(["cpio", vendor_ramdisk_cpio.to_str().unwrap(), "test"])
|
|
||||||
.status()?;
|
|
||||||
|
|
||||||
// 0: stock, 1: magisk
|
|
||||||
Ok(status.code() == Some(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_kernelsu_patched(magiskboot: &Path, workdir: &Path) -> Result<bool> {
|
|
||||||
let status = Command::new(magiskboot)
|
|
||||||
.current_dir(workdir)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.args(["cpio", "ramdisk.cpio", "exists kernelsu.ko"])
|
|
||||||
.status()?;
|
|
||||||
|
|
||||||
Ok(status.success())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_kernelsu_patched_vendor_init_boot(magiskboot: &Path, workdir: &Path) -> Result<bool> {
|
|
||||||
let vendor_ramdisk_cpio = workdir.join("vendor_ramdisk").join("init_boot.cpio");
|
|
||||||
let status = Command::new(magiskboot)
|
|
||||||
.current_dir(workdir)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.args([
|
|
||||||
"cpio",
|
|
||||||
vendor_ramdisk_cpio.to_str().unwrap(),
|
|
||||||
"exists kernelsu.ko",
|
|
||||||
])
|
|
||||||
.status()?;
|
|
||||||
|
|
||||||
Ok(status.success())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_kernelsu_patched_vendor_ramdisk(magiskboot: &Path, workdir: &Path) -> Result<bool> {
|
|
||||||
let vendor_ramdisk_cpio = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
|
|
||||||
let status = Command::new(magiskboot)
|
|
||||||
.current_dir(workdir)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.args([
|
|
||||||
"cpio",
|
|
||||||
vendor_ramdisk_cpio.to_str().unwrap(),
|
|
||||||
"exists kernelsu.ko",
|
|
||||||
])
|
|
||||||
.status()?;
|
.status()?;
|
||||||
|
|
||||||
Ok(status.success())
|
Ok(status.success())
|
||||||
@@ -305,10 +219,7 @@ pub fn restore(
|
|||||||
|
|
||||||
let kmi = get_current_kmi().unwrap_or_else(|_| String::from(""));
|
let kmi = get_current_kmi().unwrap_or_else(|_| String::from(""));
|
||||||
|
|
||||||
let skip_init = kmi.starts_with("android12-");
|
let (bootimage, bootdevice) = find_boot_image(&image, &kmi, false, false, workdir, &None)?;
|
||||||
|
|
||||||
let (bootimage, bootdevice) =
|
|
||||||
find_boot_image(&image, skip_init, false, false, workdir, &magiskboot)?;
|
|
||||||
|
|
||||||
println!("- Unpacking boot image");
|
println!("- Unpacking boot image");
|
||||||
let status = Command::new(&magiskboot)
|
let status = Command::new(&magiskboot)
|
||||||
@@ -320,33 +231,36 @@ pub fn restore(
|
|||||||
.status()?;
|
.status()?;
|
||||||
ensure!(status.success(), "magiskboot unpack failed");
|
ensure!(status.success(), "magiskboot unpack failed");
|
||||||
|
|
||||||
let no_ramdisk = !workdir.join("ramdisk.cpio").exists();
|
let mut ramdisk = workdir.join("ramdisk.cpio");
|
||||||
// let no_ramdisk = !workdir.join("ramdisk.cpio").exists();
|
if !ramdisk.exists() {
|
||||||
let no_vendor_init_boot = !workdir
|
ramdisk = workdir.join("vendor_ramdisk").join("init_boot.cpio")
|
||||||
.join("vendor_ramdisk")
|
}
|
||||||
.join("init_boot.cpio")
|
if !ramdisk.exists() {
|
||||||
.exists();
|
ramdisk = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
|
||||||
let no_vendor_ramdisk = !workdir.join("vendor_ramdisk").join("ramdisk.cpio").exists();
|
}
|
||||||
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir)?;
|
if !ramdisk.exists() {
|
||||||
let is_kernelsu_patched_vendor_init_boot =
|
bail!("No compatible ramdisk found.")
|
||||||
is_kernelsu_patched_vendor_init_boot(&magiskboot, workdir)?;
|
}
|
||||||
let is_kernelsu_patched_vendor_ramdisk =
|
let ramdisk = ramdisk.as_path();
|
||||||
is_kernelsu_patched_vendor_ramdisk(&magiskboot, workdir)?;
|
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir, ramdisk)?;
|
||||||
ensure!(
|
ensure!(is_kernelsu_patched, "boot image is not patched by KernelSU");
|
||||||
is_kernelsu_patched
|
|
||||||
|| is_kernelsu_patched_vendor_init_boot
|
|
||||||
|| is_kernelsu_patched_vendor_ramdisk,
|
|
||||||
"boot image is not patched by KernelSU"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut new_boot = None;
|
let mut new_boot = None;
|
||||||
let mut from_backup = false;
|
let mut from_backup = false;
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
if do_cpio_cmd(&magiskboot, workdir, &format!("exists {BACKUP_FILENAME}")).is_ok() {
|
if do_cpio_cmd(
|
||||||
|
&magiskboot,
|
||||||
|
workdir,
|
||||||
|
ramdisk,
|
||||||
|
&format!("exists {BACKUP_FILENAME}"),
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
do_cpio_cmd(
|
do_cpio_cmd(
|
||||||
&magiskboot,
|
&magiskboot,
|
||||||
workdir,
|
workdir,
|
||||||
|
ramdisk,
|
||||||
&format!("extract {BACKUP_FILENAME} {BACKUP_FILENAME}"),
|
&format!("extract {BACKUP_FILENAME} {BACKUP_FILENAME}"),
|
||||||
)?;
|
)?;
|
||||||
let sha = std::fs::read(workdir.join(BACKUP_FILENAME))?;
|
let sha = std::fs::read(workdir.join(BACKUP_FILENAME))?;
|
||||||
@@ -369,81 +283,13 @@ pub fn restore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if new_boot.is_none() {
|
if new_boot.is_none() {
|
||||||
if !no_ramdisk {
|
|
||||||
println!("- Restoring /ramdisk");
|
|
||||||
println!("- Removing /ramdisk/kernelsu.ko");
|
|
||||||
// remove kernelsu.ko
|
// remove kernelsu.ko
|
||||||
do_cpio_cmd(&magiskboot, workdir, "rm kernelsu.ko")?;
|
do_cpio_cmd(&magiskboot, workdir, ramdisk, "rm kernelsu.ko")?;
|
||||||
|
|
||||||
// if init.real exists, restore it
|
// if init.real exists, restore it
|
||||||
println!("- Checking if init.real exists");
|
let status = do_cpio_cmd(&magiskboot, workdir, ramdisk, "exists init.real").is_ok();
|
||||||
let status = do_cpio_cmd(&magiskboot, workdir, "exists init.real").is_ok();
|
|
||||||
if status {
|
if status {
|
||||||
println!("- /ramdisk/init.real exists");
|
do_cpio_cmd(&magiskboot, workdir, ramdisk, "mv init.real init")?;
|
||||||
println!("- Restoring /ramdisk/init.real to init");
|
|
||||||
do_cpio_cmd(&magiskboot, workdir, "mv init.real init")?;
|
|
||||||
} else {
|
|
||||||
println!("- /ramdisk/init.real not found");
|
|
||||||
println!("- Removing ramdisk.cpio");
|
|
||||||
let ramdisk = workdir.join("ramdisk.cpio");
|
|
||||||
std::fs::remove_file(ramdisk)?;
|
|
||||||
}
|
|
||||||
} else if !no_vendor_init_boot {
|
|
||||||
println!("- Restoring /vendor_ramdisk/init_boot");
|
|
||||||
println!("- Removing /vendor_ramdisk/init_boot/kernelsu.ko");
|
|
||||||
// vendor init_boot restore
|
|
||||||
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "rm kernelsu.ko")?;
|
|
||||||
|
|
||||||
println!("- Checking if init.real exists");
|
|
||||||
let status =
|
|
||||||
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "exists init.real").is_ok();
|
|
||||||
if status {
|
|
||||||
println!("- /vendor_ramdisk/init_boot/init.real exists");
|
|
||||||
println!("- Restoring /vendor_ramdisk/init_boot/init.real to init");
|
|
||||||
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "mv init.real init")?;
|
|
||||||
} else {
|
|
||||||
println!("- /vendor_ramdisk/init_boot/init.real not found");
|
|
||||||
println!("- Removing vendor_ramdisk/init_boot.cpio");
|
|
||||||
let vendor_init_boot = workdir.join("vendor_ramdisk").join("init_boot.cpio");
|
|
||||||
std::fs::remove_file(vendor_init_boot)?;
|
|
||||||
}
|
|
||||||
} else if !no_vendor_ramdisk {
|
|
||||||
println!("- Restoring /vendor_ramdisk/ramdisk");
|
|
||||||
println!("- Removing /vendor_ramdisk/ramdisk/kernelsu.ko");
|
|
||||||
// vendor ramdisk restore
|
|
||||||
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "rm kernelsu.ko")?;
|
|
||||||
|
|
||||||
let status =
|
|
||||||
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "exists init.real").is_ok();
|
|
||||||
if status {
|
|
||||||
println!("- /vendor_ramdisk/ramdisk/init.real exists");
|
|
||||||
println!("- Restoring /vendor_ramdisk/ramdisk/init.real to init");
|
|
||||||
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "mv init.real init")?;
|
|
||||||
} else {
|
|
||||||
println!("- /vendor_ramdisk/ramdisk/init.real not found");
|
|
||||||
println!("- Removing vendor_ramdisk/ramdisk.cpio");
|
|
||||||
let vendor_ramdisk = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
|
|
||||||
std::fs::remove_file(vendor_ramdisk)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("- Restoring /ramdisk");
|
|
||||||
println!("- Removing /ramdisk/kernelsu.ko");
|
|
||||||
// remove kernelsu.ko
|
|
||||||
do_cpio_cmd(&magiskboot, workdir, "rm kernelsu.ko")?;
|
|
||||||
|
|
||||||
// if init.real exists, restore it
|
|
||||||
println!("- Checking if init.real exists");
|
|
||||||
let status = do_cpio_cmd(&magiskboot, workdir, "exists init.real").is_ok();
|
|
||||||
if status {
|
|
||||||
println!("- /ramdisk/init.real exists");
|
|
||||||
println!("- Restoring /ramdisk/init.real to init");
|
|
||||||
do_cpio_cmd(&magiskboot, workdir, "mv init.real init")?;
|
|
||||||
} else {
|
|
||||||
println!("- /ramdisk/init.real not found");
|
|
||||||
println!("- Removing ramdisk.cpio");
|
|
||||||
let ramdisk = workdir.join("ramdisk.cpio");
|
|
||||||
std::fs::remove_file(ramdisk)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("- Repacking boot image");
|
println!("- Repacking boot image");
|
||||||
@@ -452,7 +298,7 @@ pub fn restore(
|
|||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.arg("repack")
|
.arg("repack")
|
||||||
.arg(bootimage.display().to_string())
|
.arg(&bootimage)
|
||||||
.status()?;
|
.status()?;
|
||||||
ensure!(status.success(), "magiskboot repack failed");
|
ensure!(status.success(), "magiskboot repack failed");
|
||||||
new_boot = Some(workdir.join("new-boot.img"));
|
new_boot = Some(workdir.join("new-boot.img"));
|
||||||
@@ -498,8 +344,11 @@ pub fn patch(
|
|||||||
out: Option<PathBuf>,
|
out: Option<PathBuf>,
|
||||||
magiskboot: Option<PathBuf>,
|
magiskboot: Option<PathBuf>,
|
||||||
kmi: Option<String>,
|
kmi: Option<String>,
|
||||||
|
partition: Option<String>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let result = do_patch(image, kernel, kmod, init, ota, flash, out, magiskboot, kmi);
|
let result = do_patch(
|
||||||
|
image, kernel, kmod, init, ota, flash, out, magiskboot, kmi, partition,
|
||||||
|
);
|
||||||
if let Err(ref e) = result {
|
if let Err(ref e) = result {
|
||||||
println!("- Install Error: {e}");
|
println!("- Install Error: {e}");
|
||||||
}
|
}
|
||||||
@@ -517,6 +366,7 @@ fn do_patch(
|
|||||||
out: Option<PathBuf>,
|
out: Option<PathBuf>,
|
||||||
magiskboot_path: Option<PathBuf>,
|
magiskboot_path: Option<PathBuf>,
|
||||||
kmi: Option<String>,
|
kmi: Option<String>,
|
||||||
|
partition: Option<String>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
println!(include_str!("banner"));
|
println!(include_str!("banner"));
|
||||||
|
|
||||||
@@ -571,18 +421,10 @@ fn do_patch(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let skip_init = kmi.starts_with("android12-");
|
let (bootimage, bootdevice) =
|
||||||
|
find_boot_image(&image, &kmi, ota, is_replace_kernel, workdir, &partition)?;
|
||||||
|
|
||||||
let (bootimage, bootdevice) = find_boot_image(
|
let bootimage = bootimage.as_path();
|
||||||
&image,
|
|
||||||
skip_init,
|
|
||||||
ota,
|
|
||||||
is_replace_kernel,
|
|
||||||
workdir,
|
|
||||||
&magiskboot,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let bootimage = bootimage.display().to_string();
|
|
||||||
|
|
||||||
// try extract magiskboot/bootctl
|
// try extract magiskboot/bootctl
|
||||||
let _ = assets::ensure_binaries(false);
|
let _ = assets::ensure_binaries(false);
|
||||||
@@ -617,98 +459,48 @@ fn do_patch(
|
|||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.arg("unpack")
|
.arg("unpack")
|
||||||
.arg(&bootimage)
|
.arg(bootimage)
|
||||||
.status()?;
|
.status()?;
|
||||||
ensure!(status.success(), "magiskboot unpack failed");
|
ensure!(status.success(), "magiskboot unpack failed");
|
||||||
|
|
||||||
let no_ramdisk = !workdir.join("ramdisk.cpio").exists();
|
let mut ramdisk = workdir.join("ramdisk.cpio");
|
||||||
let no_vendor_init_boot = !workdir
|
if !ramdisk.exists() {
|
||||||
.join("vendor_ramdisk")
|
ramdisk = workdir.join("vendor_ramdisk").join("init_boot.cpio")
|
||||||
.join("init_boot.cpio")
|
|
||||||
.exists();
|
|
||||||
let no_vendor_ramdisk = !workdir.join("vendor_ramdisk").join("ramdisk.cpio").exists();
|
|
||||||
if no_ramdisk && no_vendor_init_boot && no_vendor_ramdisk {
|
|
||||||
println!("- No compatible ramdisk found.");
|
|
||||||
println!("- Will create our own ramdisk!");
|
|
||||||
}
|
}
|
||||||
let is_magisk_patched = is_magisk_patched(&magiskboot, workdir)?;
|
if !ramdisk.exists() {
|
||||||
let is_magisk_patched_vendor_init_boot =
|
ramdisk = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
|
||||||
is_magisk_patched_vendor_init_boot(&magiskboot, workdir)?;
|
}
|
||||||
let is_magisk_patched_vendor_ramdisk = is_magisk_patched_vendor_ramdisk(&magiskboot, workdir)?;
|
if !ramdisk.exists() {
|
||||||
ensure!(
|
println!("- No ramdisk, create by default");
|
||||||
!is_magisk_patched
|
ramdisk = "ramdisk.cpio".into();
|
||||||
|| !is_magisk_patched_vendor_init_boot
|
}
|
||||||
|| !is_magisk_patched_vendor_ramdisk,
|
let ramdisk = ramdisk.as_path();
|
||||||
"Cannot work with Magisk patched image"
|
let is_magisk_patched = is_magisk_patched(&magiskboot, workdir, ramdisk)?;
|
||||||
);
|
ensure!(!is_magisk_patched, "Cannot work with Magisk patched image");
|
||||||
|
|
||||||
println!("- Adding KernelSU LKM");
|
println!("- Adding KernelSU LKM");
|
||||||
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir)?;
|
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir, ramdisk)?;
|
||||||
let is_kernelsu_patched_vendor_init_boot =
|
|
||||||
is_kernelsu_patched_vendor_init_boot(&magiskboot, workdir)?;
|
|
||||||
let is_kernelsu_patched_vendor_ramdisk =
|
|
||||||
is_kernelsu_patched_vendor_ramdisk(&magiskboot, workdir)?;
|
|
||||||
|
|
||||||
let mut need_backup = false;
|
let mut need_backup = false;
|
||||||
if (no_ramdisk && !is_kernelsu_patched_vendor_init_boot)
|
if !is_kernelsu_patched {
|
||||||
|| (no_ramdisk && no_vendor_init_boot && !is_kernelsu_patched_vendor_ramdisk)
|
// kernelsu.ko is not exist, backup init if necessary
|
||||||
|| !is_kernelsu_patched
|
let status = do_cpio_cmd(&magiskboot, workdir, ramdisk, "exists init");
|
||||||
{
|
|
||||||
if !no_ramdisk {
|
|
||||||
println!("- Checking if /ramdisk/init exists");
|
|
||||||
let status = do_cpio_cmd(&magiskboot, workdir, "exists init");
|
|
||||||
if status.is_ok() {
|
if status.is_ok() {
|
||||||
println!("- Backing up ramdisk/init");
|
do_cpio_cmd(&magiskboot, workdir, ramdisk, "mv init init.real")?;
|
||||||
do_cpio_cmd(&magiskboot, workdir, "mv init init.real")?;
|
|
||||||
}
|
}
|
||||||
need_backup = flash;
|
need_backup = flash;
|
||||||
} else if !no_vendor_init_boot {
|
|
||||||
println!("- Checking if /vendor_ramdisk/init_boot/init exists");
|
|
||||||
let status = do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "exists init");
|
|
||||||
if status.is_ok() {
|
|
||||||
println!("- Backing up vendor_ramdisk/init_boot/init");
|
|
||||||
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "mv init init.real")?;
|
|
||||||
}
|
|
||||||
need_backup = flash;
|
|
||||||
} else if !no_vendor_ramdisk {
|
|
||||||
println!("- Checking if /vendor_ramdisk/ramdisk/init exists");
|
|
||||||
let status = do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "exists init");
|
|
||||||
if status.is_ok() {
|
|
||||||
println!("- Backing up vendor_ramdisk/ramdisk/init");
|
|
||||||
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "mv init init.real")?;
|
|
||||||
}
|
|
||||||
need_backup = flash;
|
|
||||||
} else {
|
|
||||||
println!("- Checking if /ramdisk/init exists");
|
|
||||||
let status = do_cpio_cmd(&magiskboot, workdir, "exists init");
|
|
||||||
if status.is_ok() {
|
|
||||||
println!("- Backing up ramdisk/init");
|
|
||||||
do_cpio_cmd(&magiskboot, workdir, "mv init init.real")?;
|
|
||||||
}
|
|
||||||
need_backup = flash;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !no_ramdisk {
|
do_cpio_cmd(&magiskboot, workdir, ramdisk, "add 0755 init init")?;
|
||||||
println!("- Patching /ramdisk");
|
do_cpio_cmd(
|
||||||
do_cpio_cmd(&magiskboot, workdir, "add 0755 init init")?;
|
&magiskboot,
|
||||||
do_cpio_cmd(&magiskboot, workdir, "add 0755 kernelsu.ko kernelsu.ko")?;
|
workdir,
|
||||||
} else if !no_vendor_init_boot {
|
ramdisk,
|
||||||
println!("- Patching /vendor_ramdisk/init_boot");
|
"add 0755 kernelsu.ko kernelsu.ko",
|
||||||
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "add 0755 init init")?;
|
)?;
|
||||||
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "add 0755 kernelsu.ko kernelsu.ko")?;
|
|
||||||
} else if !no_vendor_ramdisk {
|
|
||||||
println!("- Patching /vendor_ramdisk/ramdisk");
|
|
||||||
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "add 0750 init init")?;
|
|
||||||
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "add 0750 kernelsu.ko kernelsu.ko")?;
|
|
||||||
} else {
|
|
||||||
println!("- Creating and Patching /ramdisk");
|
|
||||||
do_cpio_cmd(&magiskboot, workdir, "add 0755 init init")?;
|
|
||||||
do_cpio_cmd(&magiskboot, workdir, "add 0755 kernelsu.ko kernelsu.ko")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
if need_backup && let Err(e) = do_backup(&magiskboot, workdir, &bootimage) {
|
if need_backup && let Err(e) = do_backup(&magiskboot, workdir, ramdisk, bootimage) {
|
||||||
println!("- Backup stock image failed: {e}");
|
println!("- Backup stock image failed: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,7 +511,7 @@ fn do_patch(
|
|||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.arg("repack")
|
.arg("repack")
|
||||||
.arg(&bootimage)
|
.arg(bootimage)
|
||||||
.status()?;
|
.status()?;
|
||||||
ensure!(status.success(), "magiskboot repack failed");
|
ensure!(status.success(), "magiskboot repack failed");
|
||||||
let new_boot = workdir.join("new-boot.img");
|
let new_boot = workdir.join("new-boot.img");
|
||||||
@@ -774,7 +566,7 @@ fn calculate_sha1(file_path: impl AsRef<Path>) -> Result<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
fn do_backup(magiskboot: &Path, workdir: &Path, image: &str) -> Result<()> {
|
fn do_backup(magiskboot: &Path, workdir: &Path, cpio_path: &Path, image: &Path) -> Result<()> {
|
||||||
let sha1 = calculate_sha1(image)?;
|
let sha1 = calculate_sha1(image)?;
|
||||||
let filename = format!("{KSU_BACKUP_FILE_PREFIX}{sha1}");
|
let filename = format!("{KSU_BACKUP_FILE_PREFIX}{sha1}");
|
||||||
|
|
||||||
@@ -786,6 +578,7 @@ fn do_backup(magiskboot: &Path, workdir: &Path, image: &str) -> Result<()> {
|
|||||||
do_cpio_cmd(
|
do_cpio_cmd(
|
||||||
magiskboot,
|
magiskboot,
|
||||||
workdir,
|
workdir,
|
||||||
|
cpio_path,
|
||||||
&format!("add 0755 {BACKUP_FILENAME} {BACKUP_FILENAME}"),
|
&format!("add 0755 {BACKUP_FILENAME} {BACKUP_FILENAME}"),
|
||||||
)?;
|
)?;
|
||||||
println!("- Stock image has been backup to");
|
println!("- Stock image has been backup to");
|
||||||
@@ -855,139 +648,115 @@ fn find_magiskboot(magiskboot_path: Option<PathBuf>, workdir: &Path) -> Result<P
|
|||||||
|
|
||||||
fn find_boot_image(
|
fn find_boot_image(
|
||||||
image: &Option<PathBuf>,
|
image: &Option<PathBuf>,
|
||||||
skip_init: bool,
|
kmi: &str,
|
||||||
ota: bool,
|
ota: bool,
|
||||||
is_replace_kernel: bool,
|
is_replace_kernel: bool,
|
||||||
workdir: &Path,
|
workdir: &Path,
|
||||||
magiskboot: &Path,
|
partition: &Option<String>,
|
||||||
) -> Result<(PathBuf, Option<String>)> {
|
) -> Result<(PathBuf, Option<String>)> {
|
||||||
let bootimage;
|
let bootimage;
|
||||||
let mut bootdevice = None;
|
let mut bootdevice = None;
|
||||||
if let Some(ref image) = *image {
|
if let Some(ref image) = *image {
|
||||||
ensure!(image.exists(), "- Boot image not found");
|
ensure!(image.exists(), "boot image not found");
|
||||||
bootimage = std::fs::canonicalize(image)?;
|
bootimage = std::fs::canonicalize(image)?;
|
||||||
} else {
|
} else {
|
||||||
if cfg!(not(target_os = "android")) {
|
if cfg!(not(target_os = "android")) {
|
||||||
println!("- Current OS is not android, refusing auto bootimage/bootdevice detection");
|
println!("- Current OS is not android, refusing auto bootimage/bootdevice detection");
|
||||||
bail!("- Please specify a boot image");
|
bail!("Please specify a boot image");
|
||||||
}
|
}
|
||||||
let mut slot_suffix =
|
|
||||||
utils::getprop("ro.boot.slot_suffix").unwrap_or_else(|| String::from(""));
|
|
||||||
|
|
||||||
|
let slot_suffix = get_slot_suffix(ota);
|
||||||
|
let boot_partition_name = choose_boot_partition(kmi, is_replace_kernel, partition);
|
||||||
|
let boot_partition = format!("/dev/block/by-name/{boot_partition_name}{slot_suffix}");
|
||||||
|
|
||||||
|
println!("- Bootdevice: {boot_partition}");
|
||||||
|
let tmp_boot_path = workdir.join("boot.img");
|
||||||
|
|
||||||
|
dd(&boot_partition, &tmp_boot_path)?;
|
||||||
|
|
||||||
|
ensure!(tmp_boot_path.exists(), "boot image not found");
|
||||||
|
|
||||||
|
bootimage = tmp_boot_path;
|
||||||
|
bootdevice = Some(boot_partition);
|
||||||
|
};
|
||||||
|
Ok((bootimage, bootdevice))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub fn choose_boot_partition(
|
||||||
|
kmi: &str,
|
||||||
|
is_replace_kernel: bool,
|
||||||
|
partition: &Option<String>,
|
||||||
|
) -> String {
|
||||||
|
let slot_suffix = get_slot_suffix(false);
|
||||||
|
let skip_init_boot = kmi.starts_with("android12-");
|
||||||
|
|
||||||
|
let init_boot_exist = Path::new(&format!("/dev/block/by-name/init_boot{slot_suffix}")).exists();
|
||||||
|
let vendor_boot_exist =
|
||||||
|
Path::new(&format!("/dev/block/by-name/vendor_boot{slot_suffix}")).exists();
|
||||||
|
|
||||||
|
// if specific partition is specified, use it
|
||||||
|
if let Some(part) = partition {
|
||||||
|
return match part.as_str() {
|
||||||
|
"boot" | "init_boot" | "vendor_boot" => part.clone(),
|
||||||
|
_ => "boot".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// if init_boot exists and not skipping it, use it
|
||||||
|
if !is_replace_kernel && init_boot_exist && !skip_init_boot {
|
||||||
|
return "init_boot".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if vendor_boot exists and not skipping it, use it
|
||||||
|
if !is_replace_kernel && vendor_boot_exist && !skip_init_boot {
|
||||||
|
return "vendor_boot".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
"boot".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
pub fn choose_boot_partition(
|
||||||
|
_kmi: &str,
|
||||||
|
_is_replace_kernel: bool,
|
||||||
|
_partition: &Option<String>,
|
||||||
|
) -> String {
|
||||||
|
"boot".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub fn get_slot_suffix(ota: bool) -> String {
|
||||||
|
let mut slot_suffix = utils::getprop("ro.boot.slot_suffix").unwrap_or_else(|| String::from(""));
|
||||||
if !slot_suffix.is_empty() && ota {
|
if !slot_suffix.is_empty() && ota {
|
||||||
if slot_suffix == "_a" {
|
if slot_suffix == "_a" {
|
||||||
slot_suffix = "_b".to_string()
|
slot_suffix = "_b".to_string()
|
||||||
} else {
|
} else {
|
||||||
slot_suffix = "_a".to_string()
|
slot_suffix = "_a".to_string()
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let init_boot_partition = format!("/dev/block/by-name/init_boot{slot_suffix}");
|
|
||||||
let vendor_boot_partition = format!("/dev/block/by-name/vendor_boot{slot_suffix}");
|
|
||||||
let boot_partition = format!("/dev/block/by-name/boot{slot_suffix}");
|
|
||||||
|
|
||||||
let init_boot_exist = Path::new(&init_boot_partition).exists();
|
|
||||||
let vendor_boot_exist = Path::new(&vendor_boot_partition).exists();
|
|
||||||
|
|
||||||
// helper: unpack a partition and check for a ramdisk and init
|
|
||||||
fn unpack_and_check_init(
|
|
||||||
magiskboot: &Path,
|
|
||||||
workdir: &Path,
|
|
||||||
partition: &str,
|
|
||||||
ramdisk_cpio: &str,
|
|
||||||
) -> Result<bool> {
|
|
||||||
let tmp_img = workdir.join("probe.img");
|
|
||||||
dd(partition, &tmp_img)?;
|
|
||||||
let status = Command::new(magiskboot)
|
|
||||||
.current_dir(workdir)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.arg("unpack")
|
|
||||||
.arg(&tmp_img)
|
|
||||||
.status()?;
|
|
||||||
if !status.success() {
|
|
||||||
let _ = std::fs::remove_file(&tmp_img);
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
let ramdisk_path = workdir.join(ramdisk_cpio);
|
|
||||||
let has_init = if ramdisk_path.exists() {
|
|
||||||
Command::new(magiskboot)
|
|
||||||
.current_dir(workdir)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.arg("cpio")
|
|
||||||
.arg(ramdisk_cpio)
|
|
||||||
.arg("exists init")
|
|
||||||
.status()
|
|
||||||
.map(|s| s.success())
|
|
||||||
.unwrap_or(false)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
// Clean up
|
|
||||||
let _ = std::fs::remove_file(&tmp_img);
|
|
||||||
let _ = std::fs::remove_file(workdir.join("ramdisk.cpio"));
|
|
||||||
let _ = std::fs::remove_dir_all(workdir.join("vendor_ramdisk"));
|
|
||||||
Ok(has_init)
|
|
||||||
}
|
}
|
||||||
|
slot_suffix
|
||||||
|
}
|
||||||
|
|
||||||
let mut selected_partition = &boot_partition;
|
#[cfg(not(target_os = "android"))]
|
||||||
|
pub fn get_slot_suffix(_ota: bool) -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
if !is_replace_kernel && init_boot_exist && !skip_init {
|
#[cfg(target_os = "android")]
|
||||||
// try init_boot/ramdisk.cpio
|
pub fn list_available_partitions() -> Vec<String> {
|
||||||
if unpack_and_check_init(magiskboot, workdir, &init_boot_partition, "ramdisk.cpio")? {
|
let slot_suffix = get_slot_suffix(false);
|
||||||
println!("- Using init_boot partition (ramdisk.cpio).");
|
let candidates = vec!["boot", "init_boot", "vendor_boot"];
|
||||||
selected_partition = &init_boot_partition;
|
candidates
|
||||||
}
|
.into_iter()
|
||||||
}
|
.filter(|name| Path::new(&format!("/dev/block/by-name/{}{}", name, slot_suffix)).exists())
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
// try vendor_boot/vendor_ramdisk/init_boot.cpio
|
#[cfg(not(target_os = "android"))]
|
||||||
if selected_partition == &boot_partition
|
pub fn list_available_partitions() -> Vec<String> {
|
||||||
&& !is_replace_kernel
|
Vec::new()
|
||||||
&& vendor_boot_exist
|
|
||||||
&& !skip_init
|
|
||||||
&& unpack_and_check_init(
|
|
||||||
magiskboot,
|
|
||||||
workdir,
|
|
||||||
&vendor_boot_partition,
|
|
||||||
"vendor_ramdisk/init_boot.cpio",
|
|
||||||
)?
|
|
||||||
{
|
|
||||||
println!("- Using vendor_boot partition (vendor_ramdisk/init_boot.cpio).");
|
|
||||||
selected_partition = &vendor_boot_partition;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try vendor_boot/vendor_ramdisk/ramdisk.cpio
|
|
||||||
if selected_partition == &boot_partition
|
|
||||||
&& !is_replace_kernel
|
|
||||||
&& vendor_boot_exist
|
|
||||||
&& !skip_init
|
|
||||||
&& unpack_and_check_init(
|
|
||||||
magiskboot,
|
|
||||||
workdir,
|
|
||||||
&vendor_boot_partition,
|
|
||||||
"vendor_ramdisk/ramdisk.cpio",
|
|
||||||
)?
|
|
||||||
{
|
|
||||||
println!("- Using vendor_boot partition (vendor_ramdisk/ramdisk.cpio).");
|
|
||||||
selected_partition = &vendor_boot_partition;
|
|
||||||
}
|
|
||||||
|
|
||||||
if selected_partition == &boot_partition {
|
|
||||||
println!("- Using boot partition (ramdisk.cpio).");
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("- Bootdevice: {selected_partition}");
|
|
||||||
let tmp_boot_path = workdir.join("boot.img");
|
|
||||||
|
|
||||||
dd(selected_partition, &tmp_boot_path)?;
|
|
||||||
|
|
||||||
ensure!(tmp_boot_path.exists(), "- Tmp boot image not found");
|
|
||||||
|
|
||||||
bootimage = tmp_boot_path;
|
|
||||||
bootdevice = Some(selected_partition.to_string());
|
|
||||||
};
|
|
||||||
Ok((bootimage, bootdevice))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_ota() -> Result<()> {
|
fn post_ota() -> Result<()> {
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ enum Commands {
|
|||||||
/// KMI version, if specified, will use the specified KMI
|
/// KMI version, if specified, will use the specified KMI
|
||||||
#[arg(long, default_value = None)]
|
#[arg(long, default_value = None)]
|
||||||
kmi: Option<String>,
|
kmi: Option<String>,
|
||||||
|
|
||||||
|
/// target partition override (init_boot | boot | vendor_boot)
|
||||||
|
#[arg(long, default_value = None)]
|
||||||
|
partition: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Restore boot or init_boot images patched by KernelSU
|
/// Restore boot or init_boot images patched by KernelSU
|
||||||
@@ -156,7 +160,23 @@ enum BootInfo {
|
|||||||
CurrentKmi,
|
CurrentKmi,
|
||||||
|
|
||||||
/// show supported kmi versions
|
/// show supported kmi versions
|
||||||
SupportedKmi,
|
SupportedKmis,
|
||||||
|
|
||||||
|
/// check if device is A/B capable
|
||||||
|
IsAbDevice,
|
||||||
|
|
||||||
|
/// show auto-selected boot partition name
|
||||||
|
DefaultPartition,
|
||||||
|
|
||||||
|
/// list available partitions for current or OTA toggled slot
|
||||||
|
AvailablePartitions,
|
||||||
|
|
||||||
|
/// show slot suffix for current or OTA toggled slot
|
||||||
|
SlotSuffix {
|
||||||
|
/// toggle to another slot
|
||||||
|
#[arg(short = 'u', long, default_value = "false")]
|
||||||
|
ota: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Subcommand, Debug)]
|
#[derive(clap::Subcommand, Debug)]
|
||||||
@@ -524,7 +544,10 @@ pub fn run() -> Result<()> {
|
|||||||
out,
|
out,
|
||||||
magiskboot,
|
magiskboot,
|
||||||
kmi,
|
kmi,
|
||||||
} => crate::boot_patch::patch(boot, kernel, module, init, ota, flash, out, magiskboot, kmi),
|
partition,
|
||||||
|
} => crate::boot_patch::patch(
|
||||||
|
boot, kernel, module, init, ota, flash, out, magiskboot, kmi, partition,
|
||||||
|
),
|
||||||
|
|
||||||
Commands::BootInfo { command } => match command {
|
Commands::BootInfo { command } => match command {
|
||||||
BootInfo::CurrentKmi => {
|
BootInfo::CurrentKmi => {
|
||||||
@@ -533,11 +556,34 @@ pub fn run() -> Result<()> {
|
|||||||
// return here to avoid printing the error message
|
// return here to avoid printing the error message
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
BootInfo::SupportedKmi => {
|
BootInfo::SupportedKmis => {
|
||||||
let kmi = crate::assets::list_supported_kmi()?;
|
let kmi = crate::assets::list_supported_kmi()?;
|
||||||
kmi.iter().for_each(|kmi| println!("{kmi}"));
|
kmi.iter().for_each(|kmi| println!("{kmi}"));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
BootInfo::IsAbDevice => {
|
||||||
|
let val = crate::utils::getprop("ro.build.ab_update")
|
||||||
|
.unwrap_or_else(|| String::from("false"));
|
||||||
|
let is_ab = val.trim().to_lowercase() == "true";
|
||||||
|
println!("{}", if is_ab { "true" } else { "false" });
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
BootInfo::DefaultPartition => {
|
||||||
|
let kmi = crate::boot_patch::get_current_kmi().unwrap_or_else(|_| String::from(""));
|
||||||
|
let name = crate::boot_patch::choose_boot_partition(&kmi, false, &None);
|
||||||
|
println!("{name}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
BootInfo::SlotSuffix { ota } => {
|
||||||
|
let suffix = crate::boot_patch::get_slot_suffix(ota);
|
||||||
|
println!("{suffix}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
BootInfo::AvailablePartitions => {
|
||||||
|
let parts = crate::boot_patch::list_available_partitions();
|
||||||
|
parts.iter().for_each(|p| println!("{p}"));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Commands::BootRestore {
|
Commands::BootRestore {
|
||||||
boot,
|
boot,
|
||||||
|
|||||||
Reference in New Issue
Block a user