diff --git a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt index b0de89ab..7a34e41f 100644 --- a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt @@ -24,6 +24,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.RangeSlider +import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable @@ -50,6 +51,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.andbootmgr.app.CreatePartDataHolder.Part import org.andbootmgr.app.util.ConfigFile +import org.andbootmgr.app.util.SDLessUtils import org.andbootmgr.app.util.SDUtils import org.andbootmgr.app.util.SOUtils import org.json.JSONObject @@ -66,7 +68,10 @@ class CreatePartFlow(private val desiredStartSector: Long?): WizardFlow() { NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, NavButton("") {} ) { - Start(c) + if (c.vm.deviceInfo.metaonsd) + Start(c) + else + StartSdLess(c) }, WizardPage("shop", NavButton(vm.activity.getString(R.string.prev)) { it.navigate("start") }, NavButton("") {} @@ -92,10 +97,12 @@ class CreatePartFlow(private val desiredStartSector: Long?): WizardFlow() { } private class CreatePartDataHolder(val vm: WizardState, val desiredStartSector: Long?) { - var meta by mutableStateOf(null) - lateinit var p: SDUtils.Partition.FreeSpace - var startSectorRelative = 0L - var endSectorRelative = 0L + var meta by mutableStateOf(null) // metaonsd only + lateinit var p: SDUtils.Partition.FreeSpace // metaonsd only + var freeSpace: Long? = null // !metaonsd only + var startSectorRelative = 0L // metaonsd only + var endSectorRelative = 0L // metaonsd only + var desiredSize = 0L // !metaonsd only var partitionName: String? = null var painter: @Composable (() -> Painter)? = null @@ -108,13 +115,20 @@ private class CreatePartDataHolder(val vm: WizardState, val desiredStartSector: var code by mutableStateOf(code) var id by mutableStateOf(id) var sparse by mutableStateOf(sparse) - fun resolveSectorSize(c: CreatePartDataHolder, remaining: Long): Long { + fun resolveSectorSize(c: CreatePartDataHolder, remaining: Long): Long { // metaonsd only return if (!isPercent /*bytes*/) { size / c.meta!!.logicalSectorSizeBytes } else /*percent*/ { (BigDecimal(remaining).multiply(BigDecimal(size).divide(BigDecimal(100)))).toLong() } } + fun resolveBytesSize(c: CreatePartDataHolder, remaining: Long): Long { // !metaonsd only + return if (!isPercent /*bytes*/) { + size + } else /*percent*/ { + (BigDecimal(remaining).multiply(BigDecimal(size).divide(BigDecimal(100)))).toLong() + } + } } val parts = mutableStateListOf() val extraIdNeeded = mutableListOf() @@ -134,17 +148,15 @@ private class CreatePartDataHolder(val vm: WizardState, val desiredStartSector: @Composable private fun Start(c: CreatePartDataHolder) { - LaunchedEffect(Unit) { - if (c.meta == null) { + if (c.meta == null) { + LaunchedEffect(Unit) { withContext(Dispatchers.IO) { - val meta = SDUtils.generateMeta(c.vm.deviceInfo.asMetaOnSdDeviceInfo())!! // TODO !metaonsd + val meta = SDUtils.generateMeta(c.vm.deviceInfo.asMetaOnSdDeviceInfo())!! c.p = meta.s.find { c.desiredStartSector == it.startSector } as SDUtils.Partition.FreeSpace c.meta = meta } } - } - if (c.meta == null) { LoadingCircle(stringResource(R.string.loading), modifier = Modifier.fillMaxSize()) return } @@ -256,6 +268,116 @@ private fun Start(c: CreatePartDataHolder) { } } +@Composable +private fun StartSdLess(c: CreatePartDataHolder) { + if (c.freeSpace == null) { + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + c.freeSpace = SDLessUtils.getFreeSpaceBytes() + } + } + LoadingCircle(stringResource(R.string.loading), modifier = Modifier.fillMaxSize()) + return + } + + val verticalScrollState = rememberScrollState() + var size by remember { mutableStateOf("0") } + val sizeInvalid by remember { derivedStateOf { !size.matches(numberRegex) } } + //var partitionName by remember { mutableStateOf("") } + //val partitionNameInvalid by remember { derivedStateOf { !partitionName.matches(asciiNonEmptyRegex) } } + Column( + Modifier + .fillMaxWidth() + .verticalScroll(verticalScrollState)) { + Card(modifier = Modifier + .fillMaxWidth() + .padding(10.dp)) { + Column(modifier = Modifier + .fillMaxWidth() + .padding(10.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(10.dp)) { + Icon(painterResource(id = R.drawable.ic_settings), stringResource(R.string.icon_content_desc), Modifier.padding(end = 10.dp)) + Text(stringResource(R.string.general_settings)) + } + Column( + Modifier + .fillMaxWidth() + .padding(5.dp)) { + TextField(modifier = Modifier.fillMaxWidth(), value = size, onValueChange = { + size = it + }, isError = sizeInvalid, label = { + Text(stringResource(R.string.size)) + }) + Slider(modifier = Modifier.fillMaxWidth(), + value = size.toLongOrNull()?.toFloat() ?: c.freeSpace!!.toFloat(), onValueChange = { + size = it.toLong().toString() + }, valueRange = 0F..c.freeSpace!!.toFloat()) + + Text(stringResource(R.string.approx_size, if (!sizeInvalid) + SOUtils.humanReadableByteCountBin(size.toLong()) else stringResource(R.string.invalid_input))) + Text(stringResource(R.string.available_space_bytes, + SOUtils.humanReadableByteCountBin(c.freeSpace!!), c.freeSpace!!)) + } + } + } + + /*if (c.vm.mvm.noobMode) TODO support portable partition for sd-less + MyInfoCard(stringResource(R.string.option_select), padding = 10.dp) + + Card(modifier = Modifier + .fillMaxWidth() + .padding(10.dp)) { + Column(modifier = Modifier + .fillMaxWidth() + .padding(10.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(10.dp)) { + Icon(painterResource(id = R.drawable.ic_sd), stringResource(R.string.icon_content_desc), Modifier.padding(end = 10.dp)) + Text(stringResource(R.string.portable_part)) + } + TextField(value = partitionName, onValueChange = { + partitionName = it + }, isError = partitionNameInvalid && partitionName.isNotEmpty(), label = { + Text(stringResource(R.string.part_name)) + }) + Row(horizontalArrangement = Arrangement.End, modifier = Modifier + .fillMaxWidth() + .padding(5.dp)) { + Button(enabled = !(sizeInvalid || partitionNameInvalid), onClick = { + c.desiredSize = size.toLong() + c.partitionName = partitionName + c.vm.navigate("flash") + }) { + Text(stringResource(id = R.string.create)) + } + } + } + }*/ + Card(modifier = Modifier + .fillMaxWidth() + .padding(10.dp)) { + Column(modifier = Modifier + .fillMaxWidth() + .padding(10.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(10.dp)) { + Icon(painterResource(id = R.drawable.ic_droidbooticon), stringResource(R.string.icon_content_desc), Modifier.padding(end = 10.dp)) + Text(stringResource(R.string.install_os)) + } + Row(horizontalArrangement = Arrangement.End, modifier = Modifier + .fillMaxWidth() + .padding(5.dp)) { + Button(enabled = !sizeInvalid, onClick = { + c.desiredSize = size.toLong() + c.partitionName = null + c.vm.navigate("shop") + }) { + Text(stringResource(R.string.cont)) + } + } + } + } + } +} + @Composable private fun Shop(c: CreatePartDataHolder) { var loading by remember { mutableStateOf(true) } @@ -621,55 +743,100 @@ private fun Flash(c: CreatePartDataHolder) { c.vm.logic.extractToolkit(terminal) c.vm.downloadRemainingFiles(terminal) if (c.partitionName == null) { // OS install - val createdParts = mutableListOf>() // order is important + val createdParts = mutableListOf>>() // order is important val fn = c.romFolderName terminal.add(vm.activity.getString(R.string.term_f_name, fn)) terminal.add(vm.activity.getString(R.string.term_g_name, c.romDisplayName)) val tmpFile = c.vm.chosen["_install.sh_"]!!.toFile(vm) tmpFile.setExecutable(true) + val entryFolder = File(vm.logic.abmBootset, fn) + if (!SuFile.open(entryFolder.toURI()).mkdir()) { + terminal.add(vm.activity.getString(R.string.term_mkdir_failed)) + return@WizardTerminalWork + } terminal.add(vm.activity.getString(R.string.term_creating_pt)) - vm.logic.unmountBootset(vm.deviceInfo) - val startSectorAbsolute = c.p.startSector + c.startSectorRelative - val endSectorAbsolute = c.p.startSector + c.endSectorRelative - if (endSectorAbsolute > c.p.endSector) - throw IllegalArgumentException("$endSectorAbsolute can't be bigger than ${c.p.endSector}") - c.parts.forEachIndexed { index, part -> // TODO !metaonsd - terminal.add(vm.activity.getString(R.string.term_create_part)) - val start = c.p.startSector.coerceAtLeast(startSectorAbsolute) - val end = c.p.endSector.coerceAtMost(endSectorAbsolute) - val k = part.resolveSectorSize(c, end - start) - if (start + k > end) - throw IllegalStateException("$start + $k = ${start + k} shouldn't be bigger than $end") - if (k < 0) - throw IllegalStateException("$k shouldn't be smaller than 0") - // create(start, end) values are relative to the free space area - val r = vm.logic.create(c.p, start - c.p.startSector, - (start + k) - c.p.startSector, part.code, "").to(terminal).exec() - if (r.out.joinToString("\n").contains("kpartx")) { - terminal.add(vm.activity.getString(R.string.term_reboot_asap)) + if (vm.deviceInfo.metaonsd) { + vm.logic.unmountBootset(vm.deviceInfo) + val startSectorAbsolute = c.p.startSector + c.startSectorRelative + val endSectorAbsolute = c.p.startSector + c.endSectorRelative + if (endSectorAbsolute > c.p.endSector) + throw IllegalArgumentException("$endSectorAbsolute can't be bigger than ${c.p.endSector}") + c.parts.forEachIndexed { index, part -> + terminal.add(vm.activity.getString(R.string.term_create_part)) + val start = c.p.startSector.coerceAtLeast(startSectorAbsolute) + val end = c.p.endSector.coerceAtMost(endSectorAbsolute) + val k = part.resolveSectorSize(c, end - start) + if (start + k > end) + throw IllegalStateException("$start + $k = ${start + k} shouldn't be bigger than $end") + if (k < 0) + throw IllegalStateException("$k shouldn't be smaller than 0") + // create(start, end) values are relative to the free space area + val r = vm.logic.create( + c.p, start - c.p.startSector, + (start + k) - c.p.startSector, part.code, "" + ).to(terminal).exec() + if (r.out.joinToString("\n").contains("kpartx")) { + terminal.add(vm.activity.getString(R.string.term_reboot_asap)) + } + val nid = c.meta!!.nid + c.meta = SDUtils.generateMeta(c.vm.deviceInfo.asMetaOnSdDeviceInfo())!! + createdParts.add(part to (nid to File(c.meta!!.dumpKernelPartition(nid).path))) + // do not assert there is leftover space if we just created the last partition we want to create + if (index < c.parts.size - 1) { + c.p = + c.meta!!.s.find { it.type == SDUtils.PartitionType.FREE && start + k < it.startSector } as SDUtils.Partition.FreeSpace + } + if (r.isSuccess) { + terminal.add(vm.activity.getString(R.string.term_created_part)) + } else { + terminal.add(vm.activity.getString(R.string.term_failure)) + return@WizardTerminalWork + } } - createdParts.add(Pair(part, c.meta!!.nid)) - c.meta = SDUtils.generateMeta(c.vm.deviceInfo.asMetaOnSdDeviceInfo()) - // do not assert there is leftover space if we just created the last partition we want to create - if (index < c.parts.size - 1) { - c.p = - c.meta!!.s.find { it.type == SDUtils.PartitionType.FREE && start + k < it.startSector } as SDUtils.Partition.FreeSpace + if (c.meta == null) { + terminal.add(vm.activity.getString(R.string.term_cant_get_meta)) + return@WizardTerminalWork } - if (r.isSuccess) { + vm.logic.mountBootset(vm.deviceInfo) + } else { + var space = c.desiredSize + val imgFolder = File(c.vm.logic.abmSdLessBootset, fn) + var i = 0 + if (imgFolder.exists()) + throw IllegalStateException("image folder ${imgFolder.absolutePath} already exists") + if (!imgFolder.mkdir()) + throw IllegalStateException("image folder ${imgFolder.absolutePath} could not be created") + c.parts.forEachIndexed { index, part -> + terminal.add(vm.activity.getString(R.string.term_create_part)) + val id = i++ + val img = File(imgFolder, id.toString()) + val map = File(entryFolder, "$id.map") + val mappedName = "abm_${fn}_$id" + val bytes = part.resolveBytesSize(c, space) + space -= bytes + if (space < 0) + throw IllegalStateException("remaining space $space shouldn't be smaller than 0") + if (!Shell.cmd("fallocate -l $bytes" + img.absolutePath).to(terminal).exec().isSuccess) { + terminal.add(vm.activity.getString(R.string.term_failed_fallocate)) + return@WizardTerminalWork + } + if (!Shell.cmd("uncrypt ${img.absolutePath} " + map.absolutePath).to(terminal).exec().isSuccess) { + terminal.add(vm.activity.getString(R.string.term_failed_uncrypt)) + return@WizardTerminalWork + } + if (!SDLessUtils.unmap(vm.logic, mappedName, false, terminal)) + throw IllegalStateException("failed to unmap $mappedName which shouldn't even exist?") + if (!SDLessUtils.map(vm.logic, mappedName, map, terminal)) { + terminal.add(vm.activity.getString(R.string.term_failed_map_other)) + return@WizardTerminalWork + } + val mapped = File(vm.logic.dmBase, mappedName) + createdParts.add(part to (i to mapped)) terminal.add(vm.activity.getString(R.string.term_created_part)) - } else { - terminal.add(vm.activity.getString(R.string.term_failure)) - return@WizardTerminalWork } } terminal.add(vm.activity.getString(R.string.term_created_pt)) - vm.logic.mountBootset(vm.deviceInfo) - val meta = SDUtils.generateMeta(vm.deviceInfo.asMetaOnSdDeviceInfo()) - if (meta == null) { - terminal.add(vm.activity.getString(R.string.term_cant_get_meta)) - return@WizardTerminalWork - } terminal.add(vm.activity.getString(R.string.term_building_cfg)) val entry = ConfigFile() @@ -681,21 +848,17 @@ private fun Flash(c: CreatePartDataHolder) { entry["dtbo"] = "$fn/dtbo.dtbo" entry["options"] = c.cmdline entry["xtype"] = c.rtype - entry["xpart"] = createdParts.map { it.second }.joinToString(":") + entry["xpart"] = createdParts.map { it.second.first }.joinToString(":") if (c.dmaMeta.contains("updateJson") && c.dmaMeta["updateJson"] != null) entry["xupdate"] = c.dmaMeta["updateJson"]!! entry.exportToFile(File(vm.logic.abmEntries, "$fn.conf")) - if (!SuFile.open(File(vm.logic.abmBootset, fn).toURI()).mkdir()) { - terminal.add(vm.activity.getString(R.string.term_mkdir_failed)) - return@WizardTerminalWork - } terminal.add(vm.activity.getString(R.string.term_flashing_imgs)) for (part in c.parts) { if (!c.vm.idNeeded.contains(part.id)) continue terminal.add(vm.activity.getString(R.string.term_flashing_s, part.id)) val f = c.vm.chosen[part.id]!! - val tp = File(meta.dumpKernelPartition(createdParts.find { it.first == part }!!.second).path) + val tp = createdParts.first { it.first == part }.second.second if (part.sparse) { val result2 = Shell.cmd( File( @@ -720,7 +883,12 @@ private fun Flash(c: CreatePartDataHolder) { cmd += " " + c.vm.chosen[i]!!.toFile(vm).absolutePath } for (i in c.parts) { - cmd += " " + createdParts.find { it.first == i }!!.second + cmd += " " + createdParts.first { it.first == i }.second.let { + if (vm.deviceInfo.metaonsd) + it.first // partition number + else + it.second.absolutePath // path to .img file + } } val result = vm.logic.runShFileWithArgs(cmd).to(terminal).exec() if (!result.isSuccess) { diff --git a/app/src/main/java/org/andbootmgr/app/DeviceInfo.kt b/app/src/main/java/org/andbootmgr/app/DeviceInfo.kt index 30f4c109..97b36577 100644 --- a/app/src/main/java/org/andbootmgr/app/DeviceInfo.kt +++ b/app/src/main/java/org/andbootmgr/app/DeviceInfo.kt @@ -90,7 +90,7 @@ abstract class SdLessDeviceInfo : DeviceInfo { return !SuFile.open(logic.abmDb, "db.conf").exists() } override fun getAbmSettings(logic: DeviceLogic): String? { - return logic.dmPath.absolutePath + return File(logic.dmBase, logic.dmName).absolutePath } } @@ -120,7 +120,7 @@ class JsonDeviceInfoFactory(private val ctx: Context) { val jsonText = try { try { ctx.assets.open("abm.json").readBytes().toString(Charsets.UTF_8) - } catch (e: FileNotFoundException) { + } catch (_: FileNotFoundException) { URL("https://raw.githubusercontent.com/Android-Boot-Manager/ABM-json/master/devices/$codename.json").readText() } } catch (e: Exception) { diff --git a/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt b/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt index 64fbd442..694546b0 100644 --- a/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt +++ b/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.Log import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.io.SuFile +import org.andbootmgr.app.util.SDLessUtils import org.andbootmgr.app.util.SDUtils import org.andbootmgr.app.util.Toolkit import java.io.File @@ -22,7 +23,6 @@ class DeviceLogic(private val ctx: Context) { val metadataMap = File(metadata, "abm_settings.map") val dmBase = File("/dev/block/mapper") val dmName = "abmbootset" - val dmPath = File(dmBase, dmName) val abmDb = File(abmBootset, "db") val abmEntries = File(abmDb, "entries") val abmDbConf = File(abmDb, "db.conf") @@ -59,15 +59,14 @@ class DeviceLogic(private val ctx: Context) { val out = result.out.joinToString("\n") + result.err.joinToString("\n") if (out.contains("Device or resource busy")) { mounted = true - } - if (out.contains("Invalid argument")) { + } else if (out.contains("Invalid argument")) { mounted = false } Log.e("ABM_UMOUNT", out) return !mounted } - if (!d.metaonsd) unmapBootset() mounted = false + if (!d.metaonsd && !unmapBootset()) return false return true } fun checkMounted(): Boolean { @@ -78,20 +77,11 @@ class DeviceLogic(private val ctx: Context) { } return mounted } - private fun mapBootset(): Boolean { - if (SuFile.open(dmPath.toURI()).exists()) - return true - val tempFile = File(cacheDir, "${System.currentTimeMillis()}.txt") - if (!Shell.cmd(File(toolkitDir, "droidboot_map_to_dm") - .absolutePath + " " + metadataMap.absolutePath + " " + tempFile.absolutePath - ).exec().isSuccess) { - return false - } - return Shell.cmd("dmsetup create $dmName ${tempFile.absolutePath}").exec().isSuccess + fun mapBootset(): Boolean { + return SDLessUtils.map(this, dmName, metadataMap) } - private fun unmapBootset() { - if (SuFile.open(dmPath.toURI()).exists()) - Shell.cmd("dmsetup remove -f --retry $dmName").exec() + private fun unmapBootset(): Boolean { + return SDLessUtils.unmap(this, dmName, true) } fun mount(p: SDUtils.Partition): Shell.Job { return Shell.cmd(p.mount()) diff --git a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt index b862a1bc..7920503f 100644 --- a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.andbootmgr.app.util.ConfigFile +import org.andbootmgr.app.util.SDLessUtils import org.andbootmgr.app.util.SDUtils import org.json.JSONObject import org.json.JSONTokener @@ -264,23 +265,16 @@ private fun Flash(d: DroidBootFlowDataHolder) { terminal.add(vm.activity.getString(R.string.term_failed_uncrypt)) return@WizardTerminalWork } - val tempFile = File(vm.logic.cacheDir, "${System.currentTimeMillis()}.txt") - if (!Shell.cmd(File(vm.logic.toolkitDir, "droidboot_map_to_dm") - .absolutePath + " " + vm.logic.metadataMap.absolutePath + " " + tempFile.absolutePath - ).to(terminal).exec().isSuccess) { - terminal.add(vm.activity.getString(R.string.term_failed_mapconv)) + val ast = vm.deviceInfo.getAbmSettings(vm.logic) + if (ast == null) { + terminal.add(vm.activity.getString(R.string.term_failed_prepare_map)) return@WizardTerminalWork } - if (SuFile.open(vm.logic.dmPath.toURI()).exists()) { - if (!Shell.cmd("dmsetup remove --retry ${vm.logic.dmName}") - .to(terminal).exec().isSuccess - ) { - terminal.add(vm.activity.getString(R.string.term_failed_unmap)) - return@WizardTerminalWork - } + if (!SDLessUtils.unmap(vm.logic, vm.logic.dmName, false, terminal)) { + terminal.add(vm.activity.getString(R.string.term_failed_unmap)) + return@WizardTerminalWork } - if (!Shell.cmd("dmsetup create ${vm.logic.dmName} ${tempFile.absolutePath}") - .to(terminal).exec().isSuccess) { + if (!SDLessUtils.map(vm.logic, vm.logic.dmName, vm.logic.metadataMap, terminal)) { terminal.add(vm.activity.getString(R.string.term_failed_map)) return@WizardTerminalWork } diff --git a/app/src/main/java/org/andbootmgr/app/util/SDLessUtils.kt b/app/src/main/java/org/andbootmgr/app/util/SDLessUtils.kt new file mode 100644 index 00000000..31276b32 --- /dev/null +++ b/app/src/main/java/org/andbootmgr/app/util/SDLessUtils.kt @@ -0,0 +1,49 @@ +package org.andbootmgr.app.util + +import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.io.SuFile +import org.andbootmgr.app.DeviceLogic +import java.io.File + +object SDLessUtils { + fun getFreeSpaceBytes(): Long { + return 4L * 1024L * 1024L * 1024L // TODO + } + + fun map(logic: DeviceLogic, name: String, mapFile: File, terminal: MutableList? = null): Boolean { + val dmPath = File(logic.dmBase, name) + if (SuFile.open(dmPath.toURI()).exists()) + return true + val tempFile = File(logic.cacheDir, "${System.currentTimeMillis()}.txt") + if (!Shell.cmd( + File(logic.toolkitDir, "droidboot_map_to_dm") + .absolutePath + " " + mapFile.absolutePath + " " + tempFile.absolutePath + ).let { + if (terminal != null) + it.to(terminal) + else it + }.exec().isSuccess + ) { + return false + } + return Shell.cmd("dmsetup create $name ${tempFile.absolutePath}").let { + if (terminal != null) + it.to(terminal) + else it + }.exec().isSuccess + } + + fun unmap(logic: DeviceLogic, name: String, force: Boolean, terminal: MutableList? = null): Boolean { + val dmPath = File(logic.dmBase, name) + if (SuFile.open(dmPath.toURI()).exists()) + return !Shell.cmd( + "dmsetup remove " + (if (force) "-f " else "") + + "--retry $name" + ).let { + if (terminal != null) + it.to(terminal) + else it + }.exec().isSuccess || SuFile.open(dmPath.toURI()).exists() + return true + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6fcaeb2e..c84a75b5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,6 +24,7 @@ Start sector (relative) End sector (relative) Available space: %s (%d sectors) + Available space: %s (%d bytes) Approximate size: %s (invalid input) Please select one of the below options. @@ -266,8 +267,9 @@ -- failed to create bootset dir -- failed to create bootset.img -- failed to decrypt bootset.img - -- failed to convert bootset.img map -- failed to map bootset.img + -- failed to map image + -- failed to prepare to map bootset.img -- failed to create file system in bootset -- failed to unmap bootset.img You need to install the bootloader manually first. Please check the ABM wiki for more infos.