mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-24 02:25:28 +00:00
Merge files
This commit is contained in:
parent
97b72a5941
commit
05cf53fe6f
@ -1,10 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.core.tasks
|
|
||||||
|
|
||||||
import androidx.annotation.MainThread
|
|
||||||
|
|
||||||
interface FlashResultListener {
|
|
||||||
|
|
||||||
@MainThread
|
|
||||||
fun onResult(success: Boolean)
|
|
||||||
|
|
||||||
}
|
|
@ -1,377 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.core.tasks
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.text.TextUtils
|
|
||||||
import androidx.annotation.WorkerThread
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.topjohnwu.magisk.core.Config
|
|
||||||
import com.topjohnwu.magisk.core.Info
|
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
|
||||||
import com.topjohnwu.magisk.di.Protected
|
|
||||||
import com.topjohnwu.magisk.extensions.*
|
|
||||||
import com.topjohnwu.signing.SignBoot
|
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
|
||||||
import com.topjohnwu.superuser.internal.NOPList
|
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
|
||||||
import com.topjohnwu.superuser.io.SuFileInputStream
|
|
||||||
import com.topjohnwu.superuser.io.SuFileOutputStream
|
|
||||||
import io.reactivex.Single
|
|
||||||
import org.kamranzafar.jtar.TarEntry
|
|
||||||
import org.kamranzafar.jtar.TarHeader
|
|
||||||
import org.kamranzafar.jtar.TarInputStream
|
|
||||||
import org.kamranzafar.jtar.TarOutputStream
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipInputStream
|
|
||||||
|
|
||||||
abstract class MagiskInstallImpl : FlashResultListener {
|
|
||||||
|
|
||||||
protected lateinit var installDir: File
|
|
||||||
private lateinit var srcBoot: String
|
|
||||||
private lateinit var destFile: File
|
|
||||||
private lateinit var zipUri: Uri
|
|
||||||
|
|
||||||
private val console: MutableList<String>
|
|
||||||
private val logs: MutableList<String>
|
|
||||||
private var tarOut: TarOutputStream? = null
|
|
||||||
|
|
||||||
private val service: GithubRawServices by inject()
|
|
||||||
protected val context: Context by inject()
|
|
||||||
|
|
||||||
protected constructor() {
|
|
||||||
console = NOPList.getInstance()
|
|
||||||
logs = NOPList.getInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(zip: Uri, out: MutableList<String>, err: MutableList<String>) {
|
|
||||||
console = out
|
|
||||||
logs = err
|
|
||||||
zipUri = zip
|
|
||||||
installDir = File(get<Context>(Protected).filesDir.parent, "install")
|
|
||||||
"rm -rf $installDir".sh()
|
|
||||||
installDir.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findImage(): Boolean {
|
|
||||||
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
|
|
||||||
if (srcBoot.isEmpty()) {
|
|
||||||
console.add("! Unable to detect target image")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
console.add("- Target image: $srcBoot")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findSecondaryImage(): Boolean {
|
|
||||||
val slot = "echo \$SLOT".fsh()
|
|
||||||
val target = if (slot == "_a") "_b" else "_a"
|
|
||||||
console.add("- Target slot: $target")
|
|
||||||
srcBoot = arrayOf(
|
|
||||||
"SLOT=$target",
|
|
||||||
"find_boot_image",
|
|
||||||
"SLOT=$slot",
|
|
||||||
"echo \"\$BOOTIMAGE\"").fsh()
|
|
||||||
if (srcBoot.isEmpty()) {
|
|
||||||
console.add("! Unable to detect target image")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
console.add("- Target image: $srcBoot")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun extractZip(): Boolean {
|
|
||||||
val arch: String
|
|
||||||
arch = if (Build.VERSION.SDK_INT >= 21) {
|
|
||||||
val abis = listOf(*Build.SUPPORTED_ABIS)
|
|
||||||
if (abis.contains("x86")) "x86" else "arm"
|
|
||||||
} else {
|
|
||||||
if (TextUtils.equals(Build.CPU_ABI, "x86")) "x86" else "arm"
|
|
||||||
}
|
|
||||||
|
|
||||||
console.add("- Device platform: " + Build.CPU_ABI)
|
|
||||||
|
|
||||||
try {
|
|
||||||
ZipInputStream(context.readUri(zipUri).buffered()).use { zi ->
|
|
||||||
lateinit var ze: ZipEntry
|
|
||||||
while (zi.nextEntry?.let { ze = it } != null) {
|
|
||||||
if (ze.isDirectory)
|
|
||||||
continue
|
|
||||||
var name: String? = null
|
|
||||||
val names = arrayOf("$arch/", "common/", "META-INF/com/google/android/update-binary")
|
|
||||||
for (n in names) {
|
|
||||||
ze.name.run {
|
|
||||||
if (startsWith(n)) {
|
|
||||||
name = substring(lastIndexOf('/') + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name ?: continue
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (name == null && ze.name.startsWith("chromeos/"))
|
|
||||||
name = ze.name
|
|
||||||
if (name == null)
|
|
||||||
continue
|
|
||||||
val dest = if (installDir is SuFile)
|
|
||||||
SuFile(installDir, name)
|
|
||||||
else
|
|
||||||
File(installDir, name)
|
|
||||||
dest.parentFile!!.mkdirs()
|
|
||||||
SuFileOutputStream(dest).use { zi.copyTo(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
console.add("! Cannot unzip zip")
|
|
||||||
Timber.e(e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val init64 = SuFile.open(installDir, "magiskinit64")
|
|
||||||
if (Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_64_BIT_ABIS.isNotEmpty()) {
|
|
||||||
init64.renameTo(SuFile.open(installDir, "magiskinit"))
|
|
||||||
} else {
|
|
||||||
init64.delete()
|
|
||||||
}
|
|
||||||
"cd $installDir; chmod 755 *".sh()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newEntry(name: String, size: Long): TarEntry {
|
|
||||||
console.add("-- Writing: $name")
|
|
||||||
return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
private fun handleTar(input: InputStream) {
|
|
||||||
console.add("- Processing tar file")
|
|
||||||
var vbmeta = false
|
|
||||||
val tarOut = TarOutputStream(destFile)
|
|
||||||
this.tarOut = tarOut
|
|
||||||
TarInputStream(input).use { tarIn ->
|
|
||||||
lateinit var entry: TarEntry
|
|
||||||
while (tarIn.nextEntry?.let { entry = it } != null) {
|
|
||||||
if (entry.name.contains("boot.img") || entry.name.contains("recovery.img")) {
|
|
||||||
val name = entry.name
|
|
||||||
console.add("-- Extracting: $name")
|
|
||||||
val extract = File(installDir, name)
|
|
||||||
FileOutputStream(extract).use { tarIn.copyTo(it) }
|
|
||||||
if (name.contains(".lz4")) {
|
|
||||||
console.add("-- Decompressing: $name")
|
|
||||||
"./magiskboot decompress $extract".sh()
|
|
||||||
}
|
|
||||||
} else if (entry.name.contains("vbmeta.img")) {
|
|
||||||
vbmeta = true
|
|
||||||
val buf = ByteBuffer.allocate(256)
|
|
||||||
buf.put("AVB0".toByteArray()) // magic
|
|
||||||
buf.putInt(1) // required_libavb_version_major
|
|
||||||
buf.putInt(120, 2) // flags
|
|
||||||
buf.position(128) // release_string
|
|
||||||
buf.put("avbtool 1.1.0".toByteArray())
|
|
||||||
tarOut.putNextEntry(newEntry("vbmeta.img", 256))
|
|
||||||
tarOut.write(buf.array())
|
|
||||||
} else {
|
|
||||||
console.add("-- Writing: " + entry.name)
|
|
||||||
tarOut.putNextEntry(entry)
|
|
||||||
tarIn.copyTo(tarOut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val boot = SuFile.open(installDir, "boot.img")
|
|
||||||
val recovery = SuFile.open(installDir, "recovery.img")
|
|
||||||
if (vbmeta && recovery.exists() && boot.exists()) {
|
|
||||||
// Install Magisk to recovery
|
|
||||||
srcBoot = recovery.path
|
|
||||||
// Repack boot image to prevent restore
|
|
||||||
arrayOf(
|
|
||||||
"./magiskboot unpack boot.img",
|
|
||||||
"./magiskboot repack boot.img",
|
|
||||||
"./magiskboot cleanup",
|
|
||||||
"mv new-boot.img boot.img").sh()
|
|
||||||
SuFileInputStream(boot).use {
|
|
||||||
tarOut.putNextEntry(newEntry("boot.img", boot.length()))
|
|
||||||
it.copyTo(tarOut)
|
|
||||||
}
|
|
||||||
boot.delete()
|
|
||||||
} else {
|
|
||||||
if (!boot.exists()) {
|
|
||||||
console.add("! No boot image found")
|
|
||||||
throw IOException()
|
|
||||||
}
|
|
||||||
srcBoot = boot.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleFile(uri: Uri): Boolean {
|
|
||||||
try {
|
|
||||||
context.readUri(uri).buffered().use {
|
|
||||||
it.mark(500)
|
|
||||||
val magic = ByteArray(5)
|
|
||||||
if (it.skip(257) != 257L || it.read(magic) != magic.size) {
|
|
||||||
console.add("! Invalid file")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
it.reset()
|
|
||||||
if (magic.contentEquals("ustar".toByteArray())) {
|
|
||||||
destFile = File(Config.downloadDirectory, "magisk_patched.tar")
|
|
||||||
handleTar(it)
|
|
||||||
} else {
|
|
||||||
// Raw image
|
|
||||||
srcBoot = File(installDir, "boot.img").path
|
|
||||||
destFile = File(Config.downloadDirectory, "magisk_patched.img")
|
|
||||||
console.add("- Copying image to cache")
|
|
||||||
FileOutputStream(srcBoot).use { out -> it.copyTo(out) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
console.add("! Process error")
|
|
||||||
Timber.e(e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun patchBoot(): Boolean {
|
|
||||||
var srcNand = ""
|
|
||||||
if ("[ -c $srcBoot ] && nanddump -f boot.img $srcBoot".sh().isSuccess) {
|
|
||||||
srcNand = srcBoot
|
|
||||||
srcBoot = File(installDir, "boot.img").path
|
|
||||||
}
|
|
||||||
|
|
||||||
var isSigned = false
|
|
||||||
try {
|
|
||||||
SuFileInputStream(srcBoot).use {
|
|
||||||
isSigned = SignBoot.verifySignature(it, null)
|
|
||||||
if (isSigned) {
|
|
||||||
console.add("- Boot image is signed with AVB 1.0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
console.add("! Unable to check signature")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!("KEEPFORCEENCRYPT=${Info.keepEnc} KEEPVERITY=${Info.keepVerity} " +
|
|
||||||
"RECOVERYMODE=${Info.recovery} sh update-binary " +
|
|
||||||
"sh boot_patch.sh $srcBoot").sh().isSuccess) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (srcNand.isNotEmpty()) {
|
|
||||||
srcBoot = srcNand
|
|
||||||
}
|
|
||||||
|
|
||||||
val job = Shell.sh(
|
|
||||||
"./magiskboot cleanup",
|
|
||||||
"mv bin/busybox busybox",
|
|
||||||
"rm -rf magisk.apk bin boot.img update-binary",
|
|
||||||
"cd /")
|
|
||||||
|
|
||||||
val patched = File(installDir, "new-boot.img")
|
|
||||||
if (isSigned) {
|
|
||||||
console.add("- Signing boot image with verity keys")
|
|
||||||
val signed = File(installDir, "signed.img")
|
|
||||||
try {
|
|
||||||
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) {
|
|
||||||
input, out -> SignBoot.doSignature("/boot", input, out, null, null)
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
console.add("! Unable to sign image")
|
|
||||||
Timber.e(e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
job.add("mv -f $signed $patched")
|
|
||||||
}
|
|
||||||
job.exec()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun flashBoot(): Boolean {
|
|
||||||
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
|
||||||
return false
|
|
||||||
arrayOf("run_migrations").sh()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun storeBoot(): Boolean {
|
|
||||||
val patched = SuFile.open(installDir, "new-boot.img")
|
|
||||||
try {
|
|
||||||
val os = tarOut?.let {
|
|
||||||
it.putNextEntry(newEntry(
|
|
||||||
if (srcBoot.contains("recovery")) "recovery.img" else "boot.img",
|
|
||||||
patched.length()))
|
|
||||||
tarOut = null
|
|
||||||
it
|
|
||||||
} ?: destFile.outputStream()
|
|
||||||
patched.suInputStream().use { it.copyTo(os); os.close() }
|
|
||||||
} catch (e: IOException) {
|
|
||||||
console.add("! Failed to output to $destFile")
|
|
||||||
Timber.e(e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
patched.delete()
|
|
||||||
console.add("")
|
|
||||||
console.add("****************************")
|
|
||||||
console.add(" Output file is placed in ")
|
|
||||||
console.add(" $destFile ")
|
|
||||||
console.add("****************************")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun postOTA(): Boolean {
|
|
||||||
val bootctl = SuFile("/data/adb/bootctl")
|
|
||||||
try {
|
|
||||||
withStreams(service.fetchBootctl().blockingGet().byteStream(), bootctl.suOutputStream()) {
|
|
||||||
input, out -> input.copyTo(out)
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
console.add("! Unable to download bootctl")
|
|
||||||
Timber.e(e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
"post_ota ${bootctl.parent}".sh()
|
|
||||||
|
|
||||||
console.add("***************************************")
|
|
||||||
console.add(" Next reboot will boot to second slot!")
|
|
||||||
console.add("***************************************")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.sh() = Shell.sh(this).to(console, logs).exec()
|
|
||||||
private fun Array<String>.sh() = Shell.sh(*this).to(console, logs).exec()
|
|
||||||
private fun String.fsh() = ShellUtils.fastCmd(this)
|
|
||||||
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this)
|
|
||||||
|
|
||||||
protected fun doPatchFile(patchFile: Uri) =
|
|
||||||
extractZip() && handleFile(patchFile) && patchBoot() && storeBoot()
|
|
||||||
|
|
||||||
protected fun direct() = findImage() && extractZip() && patchBoot() && flashBoot()
|
|
||||||
|
|
||||||
protected fun secondSlot() =
|
|
||||||
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
|
|
||||||
|
|
||||||
protected fun fixEnv(zip: File): Boolean {
|
|
||||||
installDir = SuFile("/data/adb/magisk")
|
|
||||||
Shell.su("rm -rf /data/adb/magisk/*").exec()
|
|
||||||
zipUri = zip.toUri()
|
|
||||||
return extractZip() && Shell.su("fix_env").exec().isSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
protected abstract fun operations(): Boolean
|
|
||||||
|
|
||||||
fun exec() {
|
|
||||||
Single.fromCallable { operations() }.subscribeK { onResult(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,21 +1,401 @@
|
|||||||
package com.topjohnwu.magisk.core.tasks
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.utils.Utils
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
import com.topjohnwu.magisk.extensions.reboot
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
|
import com.topjohnwu.magisk.di.Protected
|
||||||
|
import com.topjohnwu.magisk.extensions.*
|
||||||
import com.topjohnwu.magisk.model.events.dialog.EnvFixDialog
|
import com.topjohnwu.magisk.model.events.dialog.EnvFixDialog
|
||||||
|
import com.topjohnwu.signing.SignBoot
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
|
import com.topjohnwu.superuser.internal.NOPList
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||||
|
import com.topjohnwu.superuser.io.SuFileOutputStream
|
||||||
|
import io.reactivex.Single
|
||||||
|
import org.kamranzafar.jtar.TarEntry
|
||||||
|
import org.kamranzafar.jtar.TarHeader
|
||||||
|
import org.kamranzafar.jtar.TarInputStream
|
||||||
|
import org.kamranzafar.jtar.TarOutputStream
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.get
|
||||||
|
import org.koin.core.inject
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
interface FlashResultListener {
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
fun onResult(success: Boolean)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class MagiskInstallImpl : FlashResultListener, KoinComponent {
|
||||||
|
|
||||||
|
protected lateinit var installDir: File
|
||||||
|
private lateinit var srcBoot: String
|
||||||
|
private lateinit var destFile: File
|
||||||
|
private lateinit var zipUri: Uri
|
||||||
|
|
||||||
|
protected val console: MutableList<String>
|
||||||
|
private val logs: MutableList<String>
|
||||||
|
private var tarOut: TarOutputStream? = null
|
||||||
|
|
||||||
|
private val service: GithubRawServices by inject()
|
||||||
|
protected val context: Context by inject()
|
||||||
|
|
||||||
|
protected constructor() {
|
||||||
|
console = NOPList.getInstance()
|
||||||
|
logs = NOPList.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected constructor(zip: Uri, out: MutableList<String>, err: MutableList<String>) {
|
||||||
|
console = out
|
||||||
|
logs = err
|
||||||
|
zipUri = zip
|
||||||
|
installDir = File(get<Context>(Protected).filesDir.parent, "install")
|
||||||
|
"rm -rf $installDir".sh()
|
||||||
|
installDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findImage(): Boolean {
|
||||||
|
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
|
||||||
|
if (srcBoot.isEmpty()) {
|
||||||
|
console.add("! Unable to detect target image")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
console.add("- Target image: $srcBoot")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSecondaryImage(): Boolean {
|
||||||
|
val slot = "echo \$SLOT".fsh()
|
||||||
|
val target = if (slot == "_a") "_b" else "_a"
|
||||||
|
console.add("- Target slot: $target")
|
||||||
|
srcBoot = arrayOf(
|
||||||
|
"SLOT=$target",
|
||||||
|
"find_boot_image",
|
||||||
|
"SLOT=$slot",
|
||||||
|
"echo \"\$BOOTIMAGE\"").fsh()
|
||||||
|
if (srcBoot.isEmpty()) {
|
||||||
|
console.add("! Unable to detect target image")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
console.add("- Target image: $srcBoot")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractZip(): Boolean {
|
||||||
|
val arch: String
|
||||||
|
arch = if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
val abis = listOf(*Build.SUPPORTED_ABIS)
|
||||||
|
if (abis.contains("x86")) "x86" else "arm"
|
||||||
|
} else {
|
||||||
|
if (Build.CPU_ABI == "x86") "x86" else "arm"
|
||||||
|
}
|
||||||
|
|
||||||
|
console.add("- Device platform: " + Build.CPU_ABI)
|
||||||
|
|
||||||
|
try {
|
||||||
|
ZipInputStream(context.readUri(zipUri).buffered()).use { zi ->
|
||||||
|
lateinit var ze: ZipEntry
|
||||||
|
while (zi.nextEntry?.let { ze = it } != null) {
|
||||||
|
if (ze.isDirectory)
|
||||||
|
continue
|
||||||
|
var name: String? = null
|
||||||
|
val names = arrayOf("$arch/", "common/", "META-INF/com/google/android/update-binary")
|
||||||
|
for (n in names) {
|
||||||
|
ze.name.run {
|
||||||
|
if (startsWith(n)) {
|
||||||
|
name = substring(lastIndexOf('/') + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name ?: continue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (name == null && ze.name.startsWith("chromeos/"))
|
||||||
|
name = ze.name
|
||||||
|
if (name == null)
|
||||||
|
continue
|
||||||
|
val dest = if (installDir is SuFile)
|
||||||
|
SuFile(installDir, name)
|
||||||
|
else
|
||||||
|
File(installDir, name)
|
||||||
|
dest.parentFile!!.mkdirs()
|
||||||
|
SuFileOutputStream(dest).use { zi.copyTo(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
console.add("! Cannot unzip zip")
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val init64 = SuFile.open(installDir, "magiskinit64")
|
||||||
|
if (Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_64_BIT_ABIS.isNotEmpty()) {
|
||||||
|
init64.renameTo(SuFile.open(installDir, "magiskinit"))
|
||||||
|
} else {
|
||||||
|
init64.delete()
|
||||||
|
}
|
||||||
|
"cd $installDir; chmod 755 *".sh()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newEntry(name: String, size: Long): TarEntry {
|
||||||
|
console.add("-- Writing: $name")
|
||||||
|
return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun handleTar(input: InputStream) {
|
||||||
|
console.add("- Processing tar file")
|
||||||
|
var vbmeta = false
|
||||||
|
val tarOut = TarOutputStream(destFile)
|
||||||
|
this.tarOut = tarOut
|
||||||
|
TarInputStream(input).use { tarIn ->
|
||||||
|
lateinit var entry: TarEntry
|
||||||
|
while (tarIn.nextEntry?.let { entry = it } != null) {
|
||||||
|
if (entry.name.contains("boot.img") || entry.name.contains("recovery.img")) {
|
||||||
|
val name = entry.name
|
||||||
|
console.add("-- Extracting: $name")
|
||||||
|
val extract = File(installDir, name)
|
||||||
|
FileOutputStream(extract).use { tarIn.copyTo(it) }
|
||||||
|
if (name.contains(".lz4")) {
|
||||||
|
console.add("-- Decompressing: $name")
|
||||||
|
"./magiskboot decompress $extract".sh()
|
||||||
|
}
|
||||||
|
} else if (entry.name.contains("vbmeta.img")) {
|
||||||
|
vbmeta = true
|
||||||
|
val buf = ByteBuffer.allocate(256)
|
||||||
|
buf.put("AVB0".toByteArray()) // magic
|
||||||
|
buf.putInt(1) // required_libavb_version_major
|
||||||
|
buf.putInt(120, 2) // flags
|
||||||
|
buf.position(128) // release_string
|
||||||
|
buf.put("avbtool 1.1.0".toByteArray())
|
||||||
|
tarOut.putNextEntry(newEntry("vbmeta.img", 256))
|
||||||
|
tarOut.write(buf.array())
|
||||||
|
} else {
|
||||||
|
console.add("-- Writing: " + entry.name)
|
||||||
|
tarOut.putNextEntry(entry)
|
||||||
|
tarIn.copyTo(tarOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val boot = SuFile.open(installDir, "boot.img")
|
||||||
|
val recovery = SuFile.open(installDir, "recovery.img")
|
||||||
|
if (vbmeta && recovery.exists() && boot.exists()) {
|
||||||
|
// Install Magisk to recovery
|
||||||
|
srcBoot = recovery.path
|
||||||
|
// Repack boot image to prevent restore
|
||||||
|
arrayOf(
|
||||||
|
"./magiskboot unpack boot.img",
|
||||||
|
"./magiskboot repack boot.img",
|
||||||
|
"./magiskboot cleanup",
|
||||||
|
"mv new-boot.img boot.img").sh()
|
||||||
|
SuFileInputStream(boot).use {
|
||||||
|
tarOut.putNextEntry(newEntry("boot.img", boot.length()))
|
||||||
|
it.copyTo(tarOut)
|
||||||
|
}
|
||||||
|
boot.delete()
|
||||||
|
} else {
|
||||||
|
if (!boot.exists()) {
|
||||||
|
console.add("! No boot image found")
|
||||||
|
throw IOException()
|
||||||
|
}
|
||||||
|
srcBoot = boot.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleFile(uri: Uri): Boolean {
|
||||||
|
try {
|
||||||
|
context.readUri(uri).buffered().use {
|
||||||
|
it.mark(500)
|
||||||
|
val magic = ByteArray(5)
|
||||||
|
if (it.skip(257) != 257L || it.read(magic) != magic.size) {
|
||||||
|
console.add("! Invalid file")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.reset()
|
||||||
|
if (magic.contentEquals("ustar".toByteArray())) {
|
||||||
|
destFile = File(Config.downloadDirectory, "magisk_patched.tar")
|
||||||
|
handleTar(it)
|
||||||
|
} else {
|
||||||
|
// Raw image
|
||||||
|
srcBoot = File(installDir, "boot.img").path
|
||||||
|
destFile = File(Config.downloadDirectory, "magisk_patched.img")
|
||||||
|
console.add("- Copying image to cache")
|
||||||
|
FileOutputStream(srcBoot).use { out -> it.copyTo(out) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
console.add("! Process error")
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun patchBoot(): Boolean {
|
||||||
|
var srcNand = ""
|
||||||
|
if ("[ -c $srcBoot ] && nanddump -f boot.img $srcBoot".sh().isSuccess) {
|
||||||
|
srcNand = srcBoot
|
||||||
|
srcBoot = File(installDir, "boot.img").path
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSigned = false
|
||||||
|
try {
|
||||||
|
SuFileInputStream(srcBoot).use {
|
||||||
|
isSigned = SignBoot.verifySignature(it, null)
|
||||||
|
if (isSigned) {
|
||||||
|
console.add("- Boot image is signed with AVB 1.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
console.add("! Unable to check signature")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!("KEEPFORCEENCRYPT=${Info.keepEnc} KEEPVERITY=${Info.keepVerity} " +
|
||||||
|
"RECOVERYMODE=${Info.recovery} sh update-binary " +
|
||||||
|
"sh boot_patch.sh $srcBoot").sh().isSuccess) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srcNand.isNotEmpty()) {
|
||||||
|
srcBoot = srcNand
|
||||||
|
}
|
||||||
|
|
||||||
|
val job = Shell.sh(
|
||||||
|
"./magiskboot cleanup",
|
||||||
|
"mv bin/busybox busybox",
|
||||||
|
"rm -rf magisk.apk bin boot.img update-binary",
|
||||||
|
"cd /")
|
||||||
|
|
||||||
|
val patched = File(installDir, "new-boot.img")
|
||||||
|
if (isSigned) {
|
||||||
|
console.add("- Signing boot image with verity keys")
|
||||||
|
val signed = File(installDir, "signed.img")
|
||||||
|
try {
|
||||||
|
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) {
|
||||||
|
input, out -> SignBoot.doSignature("/boot", input, out, null, null)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
console.add("! Unable to sign image")
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
job.add("mv -f $signed $patched")
|
||||||
|
}
|
||||||
|
job.exec()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun flashBoot(): Boolean {
|
||||||
|
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
||||||
|
return false
|
||||||
|
arrayOf("run_migrations").sh()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun storeBoot(): Boolean {
|
||||||
|
val patched = SuFile.open(installDir, "new-boot.img")
|
||||||
|
try {
|
||||||
|
val os = tarOut?.let {
|
||||||
|
it.putNextEntry(newEntry(
|
||||||
|
if (srcBoot.contains("recovery")) "recovery.img" else "boot.img",
|
||||||
|
patched.length()))
|
||||||
|
tarOut = null
|
||||||
|
it
|
||||||
|
} ?: destFile.outputStream()
|
||||||
|
patched.suInputStream().use { it.copyTo(os); os.close() }
|
||||||
|
} catch (e: IOException) {
|
||||||
|
console.add("! Failed to output to $destFile")
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
patched.delete()
|
||||||
|
console.add("")
|
||||||
|
console.add("****************************")
|
||||||
|
console.add(" Output file is placed in ")
|
||||||
|
console.add(" $destFile ")
|
||||||
|
console.add("****************************")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postOTA(): Boolean {
|
||||||
|
val bootctl = SuFile("/data/adb/bootctl")
|
||||||
|
try {
|
||||||
|
withStreams(service.fetchBootctl().blockingGet().byteStream(), bootctl.suOutputStream()) {
|
||||||
|
input, out -> input.copyTo(out)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
console.add("! Unable to download bootctl")
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
"post_ota ${bootctl.parent}".sh()
|
||||||
|
|
||||||
|
console.add("***************************************")
|
||||||
|
console.add(" Next reboot will boot to second slot!")
|
||||||
|
console.add("***************************************")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.sh() = Shell.sh(this).to(console, logs).exec()
|
||||||
|
private fun Array<String>.sh() = Shell.sh(*this).to(console, logs).exec()
|
||||||
|
private fun String.fsh() = ShellUtils.fastCmd(this)
|
||||||
|
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this)
|
||||||
|
|
||||||
|
protected fun doPatchFile(patchFile: Uri) =
|
||||||
|
extractZip() && handleFile(patchFile) && patchBoot() && storeBoot()
|
||||||
|
|
||||||
|
protected fun direct() = findImage() && extractZip() && patchBoot() && flashBoot()
|
||||||
|
|
||||||
|
protected fun secondSlot() =
|
||||||
|
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
|
||||||
|
|
||||||
|
protected fun fixEnv(zip: File): Boolean {
|
||||||
|
installDir = SuFile("/data/adb/magisk")
|
||||||
|
Shell.su("rm -rf /data/adb/magisk/*").exec()
|
||||||
|
zipUri = zip.toUri()
|
||||||
|
return extractZip() && Shell.su("fix_env").exec().isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
protected abstract fun operations(): Boolean
|
||||||
|
|
||||||
|
fun exec() {
|
||||||
|
Single.fromCallable { operations() }.subscribeK { onResult(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed class MagiskInstaller(
|
sealed class MagiskInstaller(
|
||||||
file: Uri,
|
file: Uri,
|
||||||
private val console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
logs: MutableList<String>,
|
logs: MutableList<String>,
|
||||||
private val resultListener: FlashResultListener
|
private val resultListener: FlashResultListener
|
||||||
) : MagiskInstallImpl(file, console, logs) {
|
) : MagiskInstallImpl(file, console, logs) {
|
||||||
|
Loading…
Reference in New Issue
Block a user