ksud: backup stock image and use it when restore (#1619)
This commit is contained in:
@@ -12,9 +12,21 @@ 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.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material.icons.filled.Save
|
import androidx.compose.material.icons.filled.Save
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
@@ -37,9 +49,17 @@ import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
|||||||
import me.weishu.kernelsu.ui.util.installBoot
|
import me.weishu.kernelsu.ui.util.installBoot
|
||||||
import me.weishu.kernelsu.ui.util.installModule
|
import me.weishu.kernelsu.ui.util.installModule
|
||||||
import me.weishu.kernelsu.ui.util.reboot
|
import me.weishu.kernelsu.ui.util.reboot
|
||||||
|
import me.weishu.kernelsu.ui.util.restoreBoot
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
enum class FlashingStatus {
|
||||||
|
FLASHING,
|
||||||
|
SUCCESS,
|
||||||
|
FAILED
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
@@ -57,19 +77,24 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
val snackBarHost = LocalSnackbarHost.current
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
|
var flashing by rememberSaveable {
|
||||||
|
mutableStateOf(FlashingStatus.FLASHING)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (text.isNotEmpty()) {
|
if (text.isNotEmpty()) {
|
||||||
return@LaunchedEffect
|
return@LaunchedEffect
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
flashIt(flashIt, onFinish = { showReboot ->
|
flashIt(flashIt, onFinish = { showReboot, code ->
|
||||||
|
if (code != 0) {
|
||||||
|
text += "Error: exit code = $code.\nPlease save and check the log.\n"
|
||||||
|
}
|
||||||
if (showReboot) {
|
if (showReboot) {
|
||||||
for (i in 0..2) {
|
text += "\n\n\n"
|
||||||
text += "\n"
|
|
||||||
}
|
|
||||||
showFloatAction = true
|
showFloatAction = true
|
||||||
}
|
}
|
||||||
|
flashing = if (code == 0) FlashingStatus.SUCCESS else FlashingStatus.FAILED
|
||||||
}, onStdout = {
|
}, onStdout = {
|
||||||
text += "$it\n"
|
text += "$it\n"
|
||||||
logContent.append(it).append("\n")
|
logContent.append(it).append("\n")
|
||||||
@@ -82,6 +107,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
|
flashing,
|
||||||
onBack = {
|
onBack = {
|
||||||
navigator.popBackStack()
|
navigator.popBackStack()
|
||||||
},
|
},
|
||||||
@@ -146,10 +172,12 @@ sealed class FlashIt : Parcelable {
|
|||||||
FlashIt()
|
FlashIt()
|
||||||
|
|
||||||
data class FlashModule(val uri: Uri) : FlashIt()
|
data class FlashModule(val uri: Uri) : FlashIt()
|
||||||
|
|
||||||
|
data object FlashRestore : FlashIt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun flashIt(
|
fun flashIt(
|
||||||
flashIt: FlashIt, onFinish: (Boolean) -> Unit,
|
flashIt: FlashIt, onFinish: (Boolean, Int) -> Unit,
|
||||||
onStdout: (String) -> Unit,
|
onStdout: (String) -> Unit,
|
||||||
onStderr: (String) -> Unit
|
onStderr: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -164,14 +192,26 @@ fun flashIt(
|
|||||||
)
|
)
|
||||||
|
|
||||||
is FlashIt.FlashModule -> installModule(flashIt.uri, onFinish, onStdout, onStderr)
|
is FlashIt.FlashModule -> installModule(flashIt.uri, onFinish, onStdout, onStderr)
|
||||||
|
|
||||||
|
FlashIt.FlashRestore -> restoreBoot(onFinish, onStdout, onStderr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
private fun TopBar(status: FlashingStatus, onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.install)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
when (status) {
|
||||||
|
FlashingStatus.FLASHING -> R.string.flashing
|
||||||
|
FlashingStatus.SUCCESS -> R.string.flash_success
|
||||||
|
FlashingStatus.FAILED -> R.string.flash_failed
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onBack
|
onClick = onBack
|
||||||
|
|||||||
@@ -123,11 +123,19 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
installMethod = method
|
installMethod = method
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
|
(lkmSelection as? LkmSelection.LkmUri)?.let {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
id = R.string.selected_lkm,
|
||||||
|
it.uri.lastPathSegment ?: "(file)"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
Button(modifier = Modifier.fillMaxWidth(),
|
Button(modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = installMethod != null,
|
enabled = installMethod != null,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
|||||||
import me.weishu.kernelsu.ui.component.rememberCustomDialog
|
import me.weishu.kernelsu.ui.component.rememberCustomDialog
|
||||||
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination
|
||||||
|
import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination
|
||||||
import me.weishu.kernelsu.ui.util.getBugreportFile
|
import me.weishu.kernelsu.ui.util.getBugreportFile
|
||||||
import me.weishu.kernelsu.ui.util.shrinkModules
|
import me.weishu.kernelsu.ui.util.shrinkModules
|
||||||
|
|
||||||
@@ -214,7 +215,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
||||||
if (lkmMode) {
|
if (lkmMode) {
|
||||||
UninstallItem {
|
UninstallItem(navigator) {
|
||||||
loadingDialog.withLoading(it)
|
loadingDialog.withLoading(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,7 +238,10 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UninstallItem(withLoading: suspend (suspend () -> Unit) -> Unit) {
|
fun UninstallItem(
|
||||||
|
navigator: DestinationsNavigator,
|
||||||
|
withLoading: suspend (suspend () -> Unit) -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val uninstallConfirmDialog = rememberConfirmDialog()
|
val uninstallConfirmDialog = rememberConfirmDialog()
|
||||||
@@ -255,7 +259,9 @@ fun UninstallItem(withLoading: suspend (suspend () -> Unit) -> Unit) {
|
|||||||
when (uninstallType) {
|
when (uninstallType) {
|
||||||
UninstallType.TEMPORARY -> showTodo()
|
UninstallType.TEMPORARY -> showTodo()
|
||||||
UninstallType.PERMANENT -> showTodo()
|
UninstallType.PERMANENT -> showTodo()
|
||||||
UninstallType.RESTORE_STOCK_IMAGE -> showTodo()
|
UninstallType.RESTORE_STOCK_IMAGE -> navigator.navigate(
|
||||||
|
FlashScreenDestination(FlashIt.FlashRestore)
|
||||||
|
)
|
||||||
UninstallType.NONE -> Unit
|
UninstallType.NONE -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,10 @@ fun uninstallModule(id: String): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun installModule(
|
fun installModule(
|
||||||
uri: Uri, onFinish: (Boolean) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
uri: Uri,
|
||||||
|
onFinish: (Boolean, Int) -> Unit,
|
||||||
|
onStdout: (String) -> Unit,
|
||||||
|
onStderr: (String) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val resolver = ksuApp.contentResolver
|
val resolver = ksuApp.contentResolver
|
||||||
with(resolver.openInputStream(uri)) {
|
with(resolver.openInputStream(uri)) {
|
||||||
@@ -137,11 +140,38 @@ fun installModule(
|
|||||||
|
|
||||||
file.delete()
|
file.delete()
|
||||||
|
|
||||||
onFinish(result.isSuccess)
|
onFinish(result.isSuccess, result.code)
|
||||||
return result.isSuccess
|
return result.isSuccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun restoreBoot(
|
||||||
|
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
val shell = createRootShell()
|
||||||
|
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
||||||
|
|
||||||
|
val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||||
|
override fun onAddElement(s: String?) {
|
||||||
|
onStdout(s ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val stderrCallback: CallbackList<String?> = object : CallbackList<String?>() {
|
||||||
|
override fun onAddElement(s: String?) {
|
||||||
|
onStderr(s ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val result =
|
||||||
|
shell.newJob().add("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot")
|
||||||
|
.to(stdoutCallback, stderrCallback)
|
||||||
|
.exec()
|
||||||
|
|
||||||
|
onFinish(result.isSuccess, result.code)
|
||||||
|
return result.isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) {
|
suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) {
|
||||||
execKsud("module shrink", true)
|
execKsud("module shrink", true)
|
||||||
}
|
}
|
||||||
@@ -157,7 +187,7 @@ fun installBoot(
|
|||||||
bootUri: Uri?,
|
bootUri: Uri?,
|
||||||
lkm: LkmSelection,
|
lkm: LkmSelection,
|
||||||
ota: Boolean,
|
ota: Boolean,
|
||||||
onFinish: (Boolean) -> Unit,
|
onFinish: (Boolean, Int) -> Unit,
|
||||||
onStdout: (String) -> Unit,
|
onStdout: (String) -> Unit,
|
||||||
onStderr: (String) -> Unit,
|
onStderr: (String) -> Unit,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
@@ -238,7 +268,7 @@ fun installBoot(
|
|||||||
lkmFile?.delete()
|
lkmFile?.delete()
|
||||||
|
|
||||||
// if boot uri is empty, it is direct install, when success, we should show reboot button
|
// if boot uri is empty, it is direct install, when success, we should show reboot button
|
||||||
onFinish(bootUri == null && result.isSuccess)
|
onFinish(bootUri == null && result.isSuccess, result.code)
|
||||||
return result.isSuccess
|
return result.isSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,4 +124,8 @@
|
|||||||
<string name="settings_uninstall_temporary_message">临时卸载 KernelSU,下次重启后恢复</string>
|
<string name="settings_uninstall_temporary_message">临时卸载 KernelSU,下次重启后恢复</string>
|
||||||
<string name="settings_uninstall_permanent_message">完全并永久移除 KernelSU 和所有模块</string>
|
<string name="settings_uninstall_permanent_message">完全并永久移除 KernelSU 和所有模块</string>
|
||||||
<string name="settings_restore_stock_image_message">恢复原厂镜像,一般在 OTA 前使用;如需卸载请使用“永久卸载”</string>
|
<string name="settings_restore_stock_image_message">恢复原厂镜像,一般在 OTA 前使用;如需卸载请使用“永久卸载”</string>
|
||||||
|
<string name="flashing">刷写中</string>
|
||||||
|
<string name="flash_success">刷写完成</string>
|
||||||
|
<string name="flash_failed">刷写失败</string>
|
||||||
|
<string name="selected_lkm">选择的 LKM :%s</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -126,4 +126,8 @@
|
|||||||
<string name="settings_uninstall_temporary_message">Temporarily uninstall KernelSU, restore to original state after next reboot.</string>
|
<string name="settings_uninstall_temporary_message">Temporarily uninstall KernelSU, restore to original state after next reboot.</string>
|
||||||
<string name="settings_uninstall_permanent_message">Uninstalling KernelSU(Root and all modules) completely and permanently.</string>
|
<string name="settings_uninstall_permanent_message">Uninstalling KernelSU(Root and all modules) completely and permanently.</string>
|
||||||
<string name="settings_restore_stock_image_message">Restore the stock factory image (if a backup exists), usually used before OTA; if you need to uninstall KernelSU, please use \"Permanent Uninstall\".</string>
|
<string name="settings_restore_stock_image_message">Restore the stock factory image (if a backup exists), usually used before OTA; if you need to uninstall KernelSU, please use \"Permanent Uninstall\".</string>
|
||||||
|
<string name="flashing">Flashing</string>
|
||||||
|
<string name="flash_success">Flash success</string>
|
||||||
|
<string name="flash_failed">Flash failed</string>
|
||||||
|
<string name="selected_lkm">Selected lkm: %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use anyhow::ensure;
|
use anyhow::ensure;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
use std::process::Stdio;
|
|
||||||
use which::which;
|
use which::which;
|
||||||
|
|
||||||
|
use crate::defs::{KSU_BACKUP_DIR, KSU_BACKUP_FILE_PREFIX};
|
||||||
use crate::{assets, utils};
|
use crate::{assets, utils};
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
@@ -152,8 +153,7 @@ pub fn restore(
|
|||||||
magiskboot_path: Option<PathBuf>,
|
magiskboot_path: Option<PathBuf>,
|
||||||
flash: bool,
|
flash: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let workding_dir =
|
let workding_dir = tempdir::TempDir::new("KernelSU").context("create temp dir failed")?;
|
||||||
tempdir::TempDir::new("KernelSU").with_context(|| "create temp dir failed")?;
|
|
||||||
let magiskboot = find_magiskboot(magiskboot_path, workding_dir.path())?;
|
let magiskboot = find_magiskboot(magiskboot_path, workding_dir.path())?;
|
||||||
|
|
||||||
let (bootimage, bootdevice) = find_boot_image(&image, false, false, workding_dir.path())?;
|
let (bootimage, bootdevice) = find_boot_image(&image, false, false, workding_dir.path())?;
|
||||||
@@ -171,29 +171,57 @@ pub fn restore(
|
|||||||
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workding_dir.path())?;
|
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workding_dir.path())?;
|
||||||
ensure!(is_kernelsu_patched, "boot image is not patched by KernelSU");
|
ensure!(is_kernelsu_patched, "boot image is not patched by KernelSU");
|
||||||
|
|
||||||
// remove kernelsu.ko
|
let mut new_boot = None;
|
||||||
do_cpio_cmd(&magiskboot, workding_dir.path(), "rm kernelsu.ko")?;
|
let mut from_backup = false;
|
||||||
|
|
||||||
// if init.real is exist, restore it
|
#[cfg(target_os = "android")]
|
||||||
let status = do_cpio_cmd(&magiskboot, workding_dir.path(), "exists init.real").is_ok();
|
if do_cpio_cmd(&magiskboot, workding_dir.path(), "exists orig.ksu").is_ok() {
|
||||||
if status {
|
do_cpio_cmd(
|
||||||
do_cpio_cmd(&magiskboot, workding_dir.path(), "mv init.real init")?;
|
&magiskboot,
|
||||||
|
workding_dir.path(),
|
||||||
|
"extract orig.ksu orig.ksu",
|
||||||
|
)?;
|
||||||
|
let sha = std::fs::read(workding_dir.path().join("orig.ksu"))?;
|
||||||
|
let sha = String::from_utf8(sha)?;
|
||||||
|
let sha = sha.trim();
|
||||||
|
let backup_path = format!("{KSU_BACKUP_DIR}/{KSU_BACKUP_FILE_PREFIX}{sha}");
|
||||||
|
if Path::new(&backup_path).is_file() {
|
||||||
|
new_boot = Some(PathBuf::from(backup_path));
|
||||||
|
from_backup = true;
|
||||||
|
} else {
|
||||||
|
println!("Warning: no backup {KSU_BACKUP_DIR}/{KSU_BACKUP_FILE_PREFIX}{sha} found!");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let ramdisk = workding_dir.path().join("ramdisk.cpio");
|
println!("Warning: no backup found!");
|
||||||
std::fs::remove_file(ramdisk)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("- Repacking boot image");
|
if new_boot.is_none() {
|
||||||
let status = Command::new(&magiskboot)
|
// remove kernelsu.ko
|
||||||
.current_dir(workding_dir.path())
|
do_cpio_cmd(&magiskboot, workding_dir.path(), "rm kernelsu.ko")?;
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
// if init.real exists, restore it
|
||||||
.arg("repack")
|
let status = do_cpio_cmd(&magiskboot, workding_dir.path(), "exists init.real").is_ok();
|
||||||
.arg(bootimage.display().to_string())
|
if status {
|
||||||
.status()?;
|
do_cpio_cmd(&magiskboot, workding_dir.path(), "mv init.real init")?;
|
||||||
ensure!(status.success(), "magiskboot repack failed");
|
} else {
|
||||||
|
let ramdisk = workding_dir.path().join("ramdisk.cpio");
|
||||||
|
std::fs::remove_file(ramdisk)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("- Repacking boot image");
|
||||||
|
let status = Command::new(&magiskboot)
|
||||||
|
.current_dir(workding_dir.path())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.arg("repack")
|
||||||
|
.arg(bootimage.display().to_string())
|
||||||
|
.status()?;
|
||||||
|
ensure!(status.success(), "magiskboot repack failed");
|
||||||
|
new_boot = Some(workding_dir.path().join("new-boot.img"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_boot = new_boot.unwrap();
|
||||||
|
|
||||||
let new_boot = workding_dir.path().join("new-boot.img");
|
|
||||||
if image.is_some() {
|
if image.is_some() {
|
||||||
// if image is specified, write to output file
|
// if image is specified, write to output file
|
||||||
let output_dir = std::env::current_dir()?;
|
let output_dir = std::env::current_dir()?;
|
||||||
@@ -203,16 +231,19 @@ pub fn restore(
|
|||||||
now.format("%Y%m%d_%H%M%S")
|
now.format("%Y%m%d_%H%M%S")
|
||||||
));
|
));
|
||||||
|
|
||||||
if std::fs::rename(&new_boot, &output_image).is_err() {
|
if from_backup || std::fs::rename(&new_boot, &output_image).is_err() {
|
||||||
std::fs::copy(&new_boot, &output_image)
|
std::fs::copy(&new_boot, &output_image).context("copy out new boot failed")?;
|
||||||
.with_context(|| "copy out new boot failed".to_string())?;
|
|
||||||
}
|
}
|
||||||
println!("- Output file is written to");
|
println!("- Output file is written to");
|
||||||
println!("- {}", output_image.display().to_string().trim_matches('"'));
|
println!("- {}", output_image.display().to_string().trim_matches('"'));
|
||||||
}
|
}
|
||||||
if flash {
|
if flash {
|
||||||
println!("- Flashing new boot image");
|
if from_backup {
|
||||||
flash_boot(bootdevice, new_boot)?;
|
println!("- Flashing new boot image from {}", new_boot.display());
|
||||||
|
} else {
|
||||||
|
println!("- Flashing new boot image");
|
||||||
|
}
|
||||||
|
flash_boot(&bootdevice, new_boot)?;
|
||||||
}
|
}
|
||||||
println!("- Done!");
|
println!("- Done!");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -265,12 +296,13 @@ fn do_patch(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let workding_dir =
|
let workding_dir = tempdir::TempDir::new("KernelSU").context("create temp dir failed")?;
|
||||||
tempdir::TempDir::new("KernelSU").with_context(|| "create temp dir failed")?;
|
|
||||||
|
|
||||||
let (bootimage, bootdevice) =
|
let (bootimage, bootdevice) =
|
||||||
find_boot_image(&image, ota, is_replace_kernel, workding_dir.path())?;
|
find_boot_image(&image, ota, is_replace_kernel, workding_dir.path())?;
|
||||||
|
|
||||||
|
let bootimage = bootimage.display().to_string();
|
||||||
|
|
||||||
// try extract magiskboot/bootctl
|
// try extract magiskboot/bootctl
|
||||||
let _ = assets::ensure_binaries(false);
|
let _ = assets::ensure_binaries(false);
|
||||||
|
|
||||||
@@ -279,20 +311,20 @@ fn do_patch(
|
|||||||
|
|
||||||
if let Some(kernel) = kernel {
|
if let Some(kernel) = kernel {
|
||||||
std::fs::copy(kernel, workding_dir.path().join("kernel"))
|
std::fs::copy(kernel, workding_dir.path().join("kernel"))
|
||||||
.with_context(|| "copy kernel from failed".to_string())?;
|
.context("copy kernel from failed")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("- Preparing assets");
|
println!("- Preparing assets");
|
||||||
|
|
||||||
let kmod_file = workding_dir.path().join("kernelsu.ko");
|
let kmod_file = workding_dir.path().join("kernelsu.ko");
|
||||||
if let Some(kmod) = kmod {
|
if let Some(kmod) = kmod {
|
||||||
std::fs::copy(kmod, kmod_file).with_context(|| "copy kernel module failed".to_string())?;
|
std::fs::copy(kmod, kmod_file).context("copy kernel module failed")?;
|
||||||
} else {
|
} else {
|
||||||
// If kmod is not specified, extract from assets
|
// If kmod is not specified, extract from assets
|
||||||
let kmi = if let Some(kmi) = kmi {
|
let kmi = if let Some(kmi) = kmi {
|
||||||
kmi
|
kmi
|
||||||
} else {
|
} else {
|
||||||
get_current_kmi().with_context(|| "Unknown KMI, please choose LKM manually")?
|
get_current_kmi().context("Unknown KMI, please choose LKM manually")?
|
||||||
};
|
};
|
||||||
println!("- KMI: {kmi}");
|
println!("- KMI: {kmi}");
|
||||||
let name = format!("{kmi}_kernelsu.ko");
|
let name = format!("{kmi}_kernelsu.ko");
|
||||||
@@ -302,9 +334,9 @@ fn do_patch(
|
|||||||
|
|
||||||
let init_file = workding_dir.path().join("init");
|
let init_file = workding_dir.path().join("init");
|
||||||
if let Some(init) = init {
|
if let Some(init) = init {
|
||||||
std::fs::copy(init, init_file).with_context(|| "copy init failed".to_string())?;
|
std::fs::copy(init, init_file).context("copy init failed")?;
|
||||||
} else {
|
} else {
|
||||||
assets::copy_assets_to_file("ksuinit", init_file).with_context(|| "copy ksuinit failed")?;
|
assets::copy_assets_to_file("ksuinit", init_file).context("copy ksuinit failed")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// magiskboot unpack boot.img
|
// magiskboot unpack boot.img
|
||||||
@@ -318,7 +350,7 @@ fn do_patch(
|
|||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.arg("unpack")
|
.arg("unpack")
|
||||||
.arg(bootimage.display().to_string())
|
.arg(&bootimage)
|
||||||
.status()?;
|
.status()?;
|
||||||
ensure!(status.success(), "magiskboot unpack failed");
|
ensure!(status.success(), "magiskboot unpack failed");
|
||||||
|
|
||||||
@@ -331,12 +363,45 @@ fn do_patch(
|
|||||||
|
|
||||||
println!("- Adding KernelSU LKM");
|
println!("- Adding KernelSU LKM");
|
||||||
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workding_dir.path())?;
|
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workding_dir.path())?;
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
let mut backup = None;
|
||||||
if !is_kernelsu_patched {
|
if !is_kernelsu_patched {
|
||||||
// kernelsu.ko is not exist, backup init if necessary
|
// kernelsu.ko is not exist, backup init if necessary
|
||||||
let status = do_cpio_cmd(&magiskboot, workding_dir.path(), "exists init");
|
let status = do_cpio_cmd(&magiskboot, workding_dir.path(), "exists init");
|
||||||
if status.is_ok() {
|
if status.is_ok() {
|
||||||
do_cpio_cmd(&magiskboot, workding_dir.path(), "mv init init.real")?;
|
do_cpio_cmd(&magiskboot, workding_dir.path(), "mv init init.real")?;
|
||||||
}
|
}
|
||||||
|
println!("- Backup stock boot image");
|
||||||
|
// magiskboot cpio ramdisk.cpio 'add 0755 orig.ksu'
|
||||||
|
let output = Command::new(&magiskboot)
|
||||||
|
.current_dir(workding_dir.path())
|
||||||
|
.arg("sha1")
|
||||||
|
.arg(&bootimage)
|
||||||
|
.output()?;
|
||||||
|
ensure!(
|
||||||
|
output.status.success(),
|
||||||
|
"Cannot calculate sha1 of original boot!"
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
{
|
||||||
|
let output = String::from_utf8(output.stdout)?;
|
||||||
|
let output = output.trim();
|
||||||
|
let output = format!("{KSU_BACKUP_FILE_PREFIX}{output}");
|
||||||
|
let target = format!("{KSU_BACKUP_DIR}/{output}");
|
||||||
|
std::fs::copy(&bootimage, &target).with_context(|| format!("backup to {target}"))?;
|
||||||
|
std::fs::write(workding_dir.path().join("orig.ksu"), output.as_bytes())
|
||||||
|
.context("write sha1")?;
|
||||||
|
do_cpio_cmd(
|
||||||
|
&magiskboot,
|
||||||
|
workding_dir.path(),
|
||||||
|
"add 0755 orig.ksu orig.ksu",
|
||||||
|
)?;
|
||||||
|
println!("- Stock image has been backup to");
|
||||||
|
println!("- {target}");
|
||||||
|
backup = Some(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do_cpio_cmd(&magiskboot, workding_dir.path(), "add 0755 init init")?;
|
do_cpio_cmd(&magiskboot, workding_dir.path(), "add 0755 init init")?;
|
||||||
@@ -353,7 +418,7 @@ fn do_patch(
|
|||||||
.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");
|
||||||
let new_boot = workding_dir.path().join("new-boot.img");
|
let new_boot = workding_dir.path().join("new-boot.img");
|
||||||
@@ -368,8 +433,7 @@ fn do_patch(
|
|||||||
));
|
));
|
||||||
|
|
||||||
if std::fs::rename(&new_boot, &output_image).is_err() {
|
if std::fs::rename(&new_boot, &output_image).is_err() {
|
||||||
std::fs::copy(&new_boot, &output_image)
|
std::fs::copy(&new_boot, &output_image).context("copy out new boot failed")?;
|
||||||
.with_context(|| "copy out new boot failed".to_string())?;
|
|
||||||
}
|
}
|
||||||
println!("- Output file is written to");
|
println!("- Output file is written to");
|
||||||
println!("- {}", output_image.display().to_string().trim_matches('"'));
|
println!("- {}", output_image.display().to_string().trim_matches('"'));
|
||||||
@@ -377,27 +441,48 @@ fn do_patch(
|
|||||||
|
|
||||||
if flash {
|
if flash {
|
||||||
println!("- Flashing new boot image");
|
println!("- Flashing new boot image");
|
||||||
flash_boot(bootdevice, new_boot)?;
|
flash_boot(&bootdevice, new_boot)?;
|
||||||
|
|
||||||
if ota {
|
if ota {
|
||||||
post_ota()?;
|
post_ota()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
if let Some(backup) = backup {
|
||||||
|
println!("- Clean up backup");
|
||||||
|
if let Ok(dir) = std::fs::read_dir("/data") {
|
||||||
|
for entry in dir.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_file() {
|
||||||
|
if let Some(name) = path.file_name() {
|
||||||
|
let name = name.to_string_lossy().to_string();
|
||||||
|
if name != backup
|
||||||
|
&& name.starts_with(KSU_BACKUP_FILE_PREFIX)
|
||||||
|
&& std::fs::remove_file(path).is_ok()
|
||||||
|
{
|
||||||
|
println!("- removed {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
println!("- Done!");
|
println!("- Done!");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flash_boot(bootdevice: Option<String>, new_boot: PathBuf) -> Result<()> {
|
fn flash_boot(bootdevice: &Option<String>, new_boot: PathBuf) -> Result<()> {
|
||||||
let Some(bootdevice) = bootdevice else {
|
let Some(bootdevice) = bootdevice else {
|
||||||
bail!("boot device not found")
|
bail!("boot device not found")
|
||||||
};
|
};
|
||||||
let status = Command::new("blockdev")
|
let status = Command::new("blockdev")
|
||||||
.arg("--setrw")
|
.arg("--setrw")
|
||||||
.arg(&bootdevice)
|
.arg(bootdevice)
|
||||||
.status()?;
|
.status()?;
|
||||||
ensure!(status.success(), "set boot device rw failed");
|
ensure!(status.success(), "set boot device rw failed");
|
||||||
dd(new_boot, &bootdevice).with_context(|| "flash boot failed")?;
|
dd(new_boot, bootdevice).context("flash boot failed")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +498,7 @@ fn find_magiskboot(magiskboot_path: Option<PathBuf>, workding_dir: &Path) -> Res
|
|||||||
} else {
|
} else {
|
||||||
let magiskboot_path = workding_dir.join("magiskboot");
|
let magiskboot_path = workding_dir.join("magiskboot");
|
||||||
assets::copy_assets_to_file("magiskboot", &magiskboot_path)
|
assets::copy_assets_to_file("magiskboot", &magiskboot_path)
|
||||||
.with_context(|| "copy magiskboot failed")?;
|
.context("copy magiskboot failed")?;
|
||||||
magiskboot_path
|
magiskboot_path
|
||||||
};
|
};
|
||||||
ensure!(magiskboot.exists(), "{magiskboot:?} is not exist");
|
ensure!(magiskboot.exists(), "{magiskboot:?} is not exist");
|
||||||
|
|||||||
@@ -38,3 +38,6 @@ pub const SKIP_MOUNT_FILE_NAME: &str = "skip_mount";
|
|||||||
|
|
||||||
pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE"));
|
pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE"));
|
||||||
pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME"));
|
pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME"));
|
||||||
|
|
||||||
|
pub const KSU_BACKUP_DIR: &str = "/data";
|
||||||
|
pub const KSU_BACKUP_FILE_PREFIX: &str = "ksu_backup_";
|
||||||
|
|||||||
Reference in New Issue
Block a user