diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt index 5aaddb935..f706cbec3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt @@ -1,6 +1,9 @@ package com.topjohnwu.magisk.core.tasks import android.net.Uri +import android.os.Build +import android.os.ParcelFileDescriptor +import android.system.ErrnoException import android.system.Os import android.widget.Toast import androidx.annotation.WorkerThread @@ -15,6 +18,7 @@ import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.ktx.withStreams import com.topjohnwu.magisk.core.ktx.writeTo import com.topjohnwu.magisk.core.utils.MediaStoreUtils +import com.topjohnwu.magisk.core.utils.MediaStoreUtils.fileDescriptor import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.RootUtils @@ -40,6 +44,7 @@ import java.util.* import java.util.concurrent.atomic.AtomicBoolean import java.util.zip.ZipEntry import java.util.zip.ZipFile +import java.util.zip.ZipInputStream abstract class MagiskInstallImpl protected constructor( protected val console: MutableList = NOPList.getInstance(), @@ -119,7 +124,8 @@ abstract class MagiskInstallImpl protected constructor( } ?: emptyArray() // Also symlink magisk32 on non 64-bit only 64-bit devices - val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String? + val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir") + .get(info) as String? if (lib32 != null) { libs += File(lib32, "libmagisk32.so") } @@ -173,6 +179,73 @@ abstract class MagiskInstallImpl protected constructor( return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */)) } + @Throws(IOException::class) + private fun processZip(input: InputStream) { + ZipInputStream(input).use { zipIn -> + lateinit var entry: ZipEntry + while (zipIn.nextEntry?.also { entry = it } != null) { + if (entry.isDirectory) continue + when (entry.name.substringAfterLast('/')) { + "payload.bin" -> { + console.add("- Extracting payload") + val dest = File(installDir, "payload.bin") + FileOutputStream(dest).use { zipIn.copyTo(it) } + processPayload(Uri.fromFile(dest)) + break + } + "init_boot.img" -> { + console.add("- Extracting init_boot image") + FileOutputStream("$installDir/boot.img").use { zipIn.copyTo(it) } + break + } + "boot.img" -> { + console.add("- Extracting boot image") + FileOutputStream("$installDir/boot.img").use { zipIn.copyTo(it) } + // no break here since there might be an init_boot.img + } + } + } + } + } + + @Throws(IOException::class) + @Synchronized + private fun processPayload(input: Uri) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + throw IOException("Payload is only supported on Android Oreo or above") + } + try { + console.add("- Processing payload.bin") + console.add("-- Extracting boot.img") + input.fileDescriptor("r").use { fd -> + val bk = ParcelFileDescriptor.fromFd(0) + try { + Os.dup2(fd.fileDescriptor, 0) + val process = ProcessBuilder() + .redirectInput(ProcessBuilder.Redirect.INHERIT) + .command( + "$installDir/magiskboot", + "extract", + "-", + "$installDir/boot.img" + ) + .start() + if (process.waitFor() != 0) { + throw IOException( + "magiskboot extract failed with code ${ + process.errorStream.readBytes().toString(Charsets.UTF_8) + }" + ) + } + } finally { + Os.dup2(bk.fileDescriptor, 0) + } + } + } catch (e: ErrnoException) { + throw IOException(e) + } + } + @Throws(IOException::class) private fun processTar(input: InputStream, output: OutputStream): OutputStream { console.add("- Processing tar file") @@ -259,7 +332,10 @@ abstract class MagiskInstallImpl protected constructor( uri.inputStream().buffered().use { src -> src.mark(500) val magic = ByteArray(5) - if (src.skip(257) != 257L || src.read(magic) != magic.size) { + val headMagic = ByteArray(4) + if (src.read(headMagic) != headMagic.size || src.skip(253) != 253L || + src.read(magic) != magic.size + ) { console.add("! Invalid input file") return false } @@ -280,10 +356,16 @@ abstract class MagiskInstallImpl protected constructor( outFile = MediaStoreUtils.getFile("$filename.tar", true) processTar(src, outFile!!.uri.outputStream()) } else { - // raw image srcBoot = installDir.getChildFile("boot.img") - console.add("- Copying image to cache") - src.cleanPump(srcBoot.newOutputStream()) + if (headMagic.contentEquals("CrAU".toByteArray())) { + processPayload(uri) + } else if (headMagic.contentEquals("PK\u0003\u0004".toByteArray())) { + processZip(src) + } else { + console.add("- Copying image to cache") + src.cleanPump(srcBoot.newOutputStream()) + } + // raw image outFile = MediaStoreUtils.getFile("$filename.img", true) outFile!!.uri.outputStream() } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/MediaStoreUtils.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/MediaStoreUtils.kt index 54e5dbf8e..e2acff636 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/MediaStoreUtils.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/MediaStoreUtils.kt @@ -102,6 +102,8 @@ object MediaStoreUtils { fun Uri.outputStream() = cr.openOutputStream(this, "rwt") ?: throw FileNotFoundException() + fun Uri.fileDescriptor(mode: String) = cr.openFileDescriptor(this, mode) ?: throw FileNotFoundException() + val Uri.displayName: String get() { if (scheme == "file") { // Simple uri wrapper over file, directly get file name diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f3d362342..7f5b245a0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,7 +46,7 @@ Your device will be FORCED to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue? Additional Setup Select and Patch a File - Select a raw image (*.img) or an ODIN tarfile (*.tar) + Select a raw image (*.img) or an ODIN tarfile (*.tar) or a payload.bin (*.bin) Rebooting in 5 seconds… Installation diff --git a/native/src/boot/payload.rs b/native/src/boot/payload.rs index 4b214f04a..5916fa5de 100644 --- a/native/src/boot/payload.rs +++ b/native/src/boot/payload.rs @@ -1,7 +1,7 @@ use std::fs::File; use std::io; use std::io::{BufReader, ErrorKind, Read, Seek, SeekFrom, Write}; -use std::os::fd::AsRawFd; +use std::os::fd::{AsRawFd, FromRawFd}; use byteorder::{BigEndian, ReadBytesExt}; use protobuf::{EnumFull, Message}; @@ -24,7 +24,11 @@ macro_rules! data_err { static PAYLOAD_MAGIC: &str = "CrAU"; fn do_extract_boot_from_payload(in_path: &str, out_path: &str) -> io::Result<()> { - let mut reader = BufReader::new(File::open(in_path)?); + let mut reader = BufReader::new(if in_path == "-" { + unsafe { File::from_raw_fd(0) } + } else { + File::open(in_path)? + }); let buf = &mut [0u8; 4]; reader.read_exact(buf)?; @@ -79,7 +83,11 @@ fn do_extract_boot_from_payload(in_path: &str, out_path: &str) -> io::Result<()> .block_size .ok_or(data_err!("block size not found"))? as u64; - let mut out_file = File::create(out_path)?; + let mut out_file = if out_path == "-" { + unsafe { File::from_raw_fd(1) } + } else { + File::create(out_path)? + }; for operation in boot.operations.iter() { let data_len = operation