Support modern Samsung AP.tar patching

This commit is contained in:
topjohnwu 2020-09-26 13:32:51 -07:00
parent 41f5c8d96c
commit 2f232fc670

View File

@ -37,11 +37,7 @@ import org.koin.core.KoinComponent
import org.koin.core.get import org.koin.core.get
import org.koin.core.inject import org.koin.core.inject
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.*
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.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@ -49,7 +45,6 @@ abstract class MagiskInstallImpl : KoinComponent {
protected lateinit var installDir: File protected lateinit var installDir: File
private lateinit var srcBoot: String private lateinit var srcBoot: String
private lateinit var destFile: MediaStoreUtils.UriFile
private lateinit var zipUri: Uri private lateinit var zipUri: Uri
protected val console: MutableList<String> protected val console: MutableList<String>
@ -163,15 +158,14 @@ abstract class MagiskInstallImpl : KoinComponent {
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun handleTar(input: InputStream) { private fun handleTar(input: InputStream, output: OutputStream): OutputStream {
console.add("- Processing tar file") console.add("- Processing tar file")
var vbmeta = false val tarOut = TarOutputStream(output)
val tarOut = TarOutputStream(destFile.uri.outputStream())
this.tarOut = tarOut
TarInputStream(input).use { tarIn -> TarInputStream(input).use { tarIn ->
lateinit var entry: TarEntry lateinit var entry: TarEntry
while (tarIn.nextEntry?.let { entry = it } != null) { while (tarIn.nextEntry?.let { entry = it } != null) {
if (entry.name.contains("boot.img") || entry.name.contains("recovery.img")) { if (entry.name.contains("boot.img") ||
(Config.recovery && entry.name.contains("recovery.img"))) {
val name = entry.name val name = entry.name
console.add("-- Extracting: $name") console.add("-- Extracting: $name")
val extract = File(installDir, name) val extract = File(installDir, name)
@ -180,25 +174,15 @@ abstract class MagiskInstallImpl : KoinComponent {
console.add("-- Decompressing: $name") console.add("-- Decompressing: $name")
"./magiskboot decompress $extract".sh() "./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 { } else {
console.add("-- Writing: " + entry.name) console.add("-- Copying: " + entry.name)
tarOut.putNextEntry(entry) tarOut.putNextEntry(entry)
tarIn.copyTo(tarOut) tarIn.copyTo(tarOut)
} }
} }
val boot = SuFile.open(installDir, "boot.img") val boot = SuFile.open(installDir, "boot.img")
val recovery = SuFile.open(installDir, "recovery.img") val recovery = SuFile.open(installDir, "recovery.img")
if (vbmeta && recovery.exists() && boot.exists()) { if (recovery.exists() && boot.exists()) {
// Install Magisk to recovery // Install Magisk to recovery
srcBoot = recovery.path srcBoot = recovery.path
// Repack boot image to prevent restore // Repack boot image to prevent restore
@ -220,27 +204,33 @@ abstract class MagiskInstallImpl : KoinComponent {
srcBoot = boot.path srcBoot = boot.path
} }
} }
return tarOut
} }
private fun handleFile(uri: Uri): Boolean { private fun handleFile(uri: Uri): Boolean {
val outStream: OutputStream
val outFile: MediaStoreUtils.UriFile
// Process input file
try { try {
uri.inputStream().buffered().use { uri.inputStream().buffered().use { src ->
it.mark(500) src.mark(500)
val magic = ByteArray(5) val magic = ByteArray(5)
if (it.skip(257) != 257L || it.read(magic) != magic.size) { if (src.skip(257) != 257L || src.read(magic) != magic.size) {
console.add("! Invalid file") console.add("! Invalid input file")
return false return false
} }
it.reset() src.reset()
if (magic.contentEquals("ustar".toByteArray())) { outStream = if (magic.contentEquals("ustar".toByteArray())) {
destFile = MediaStoreUtils.getFile("magisk_patched.tar") outFile = MediaStoreUtils.getFile("magisk_patched.tar")
handleTar(it) handleTar(src, outFile.uri.outputStream())
} else { } else {
// Raw image // Raw image
srcBoot = File(installDir, "boot.img").path srcBoot = File(installDir, "boot.img").path
destFile = MediaStoreUtils.getFile("magisk_patched.img")
console.add("- Copying image to cache") console.add("- Copying image to cache")
FileOutputStream(srcBoot).use { out -> it.copyTo(out) } FileOutputStream(srcBoot).use { src.copyTo(it) }
outFile = MediaStoreUtils.getFile("magisk_patched.img")
outFile.uri.outputStream()
} }
} }
} catch (e: IOException) { } catch (e: IOException) {
@ -249,6 +239,31 @@ abstract class MagiskInstallImpl : KoinComponent {
return false return false
} }
// Patch file
if (!patchBoot())
return false
// Output file
try {
val patched = SuFile.open(installDir, "new-boot.img")
if (outStream is TarOutputStream) {
val name = if (srcBoot.contains("recovery")) "recovery.img" else "boot.img"
outStream.putNextEntry(newEntry(name, patched.length()))
}
withStreams(SuFileInputStream(patched), outStream) { src, out -> src.copyTo(out) }
patched.delete()
console.add("")
console.add("****************************")
console.add(" Output file is written to ")
console.add(" $outFile ")
console.add("****************************")
} catch (e: IOException) {
console.add("! Failed to output to $outFile")
Timber.e(e)
return false
}
return true return true
} }
@ -311,33 +326,7 @@ abstract class MagiskInstallImpl : KoinComponent {
private fun flashBoot(): Boolean { private fun flashBoot(): Boolean {
if (!"direct_install $installDir $srcBoot".sh().isSuccess) if (!"direct_install $installDir $srcBoot".sh().isSuccess)
return false return false
arrayOf("run_migrations").sh() "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.uri.outputStream()
SuFileInputStream(patched).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 return true
} }
@ -366,8 +355,7 @@ abstract class MagiskInstallImpl : KoinComponent {
private fun String.fsh() = ShellUtils.fastCmd(this) private fun String.fsh() = ShellUtils.fastCmd(this)
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this) private fun Array<String>.fsh() = ShellUtils.fastCmd(*this)
protected fun doPatchFile(patchFile: Uri) = protected fun doPatchFile(patchFile: Uri) = extractZip() && handleFile(patchFile)
extractZip() && handleFile(patchFile) && patchBoot() && storeBoot()
protected fun direct() = findImage() && extractZip() && patchBoot() && flashBoot() protected fun direct() = findImage() && extractZip() && patchBoot() && flashBoot()