Merge Magisk install zip into Magisk Manager

Distribute Magisk directly with Magisk Manager APK. The APK will
contain all required binaries and scripts for installation and
uninstallation. App versions will now align with Magisk releases.

Extra effort is spent to make the APK itself also a flashable zip that
can be used in custom recoveries, so those still prefer to install
Magisk with recoveries will not be affected with this change.

As a bonus, this makes the whole installation and uninstallation
process 100% offline. The existing Magisk Manager was not really
functional without an Internet connection, as the installation process
was highly tied to zips hosted on the server.

An additional bonus: since all binaries are now shipped as "native
libraries" of the APK, we can finally bump the target SDK version
higher than 28. The target SDK version was stuck at 28 for a long time
because newer SELinux restricts running executables from internal
storage. More details can be found here: https://github.com/termux/termux-app/issues/1072
The target SDK bump will be addressed in a future commit.

Co-authored with @vvb2060
This commit is contained in:
topjohnwu 2021-01-22 02:28:53 -08:00
parent 61d52991f1
commit ec8fffe61c
37 changed files with 784 additions and 1025 deletions

5
app/.gitignore vendored
View File

@ -6,6 +6,7 @@
app/release app/release
*.hprof *.hprof
.externalNativeBuild/ .externalNativeBuild/
public.certificate.x509.pem
private.key.pk8
*.apk *.apk
src/main/assets
src/main/jniLibs
src/main/resources

View File

@ -1,3 +1,4 @@
import org.apache.tools.ant.filters.FixCrLfFilter
import java.io.PrintStream import java.io.PrintStream
plugins { plugins {
@ -22,8 +23,8 @@ android {
applicationId = "com.topjohnwu.magisk" applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
multiDexEnabled = true multiDexEnabled = true
versionName = Config.appVersion versionName = Config.version
versionCode = Config.appVersionCode versionCode = Config.versionCode
javaCompileOptions.annotationProcessorOptions.arguments( javaCompileOptions.annotationProcessorOptions.arguments(
mapOf("room.incremental" to "true") mapOf("room.incremental" to "true")
@ -51,13 +52,14 @@ android {
} }
packagingOptions { packagingOptions {
exclude("/META-INF/**") exclude("/META-INF/*")
exclude("/org/bouncycastle/**") exclude("/org/bouncycastle/**")
exclude("/kotlin/**") exclude("/kotlin/**")
exclude("/kotlinx/**") exclude("/kotlinx/**")
exclude("/okhttp3/**") exclude("/okhttp3/**")
exclude("/*.txt") exclude("/*.txt")
exclude("/*.bin") exclude("/*.bin")
doNotStrip("**/*.so")
} }
kotlinOptions { kotlinOptions {
@ -65,10 +67,82 @@ android {
} }
} }
tasks["preBuild"]?.dependsOn(tasks.register("copyUtils", Copy::class) { val syncLibs by tasks.registering(Sync::class) {
from(rootProject.file("scripts/util_functions.sh")) into("src/main/jniLibs")
into("src/main/res/raw") into("armeabi-v7a") {
}) from(rootProject.file("native/out/armeabi-v7a")) {
include("busybox", "magiskboot", "magiskinit", "magisk")
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
}
from(rootProject.file("native/out/arm64-v8a")) {
include("magisk")
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
}
}
into("x86") {
from(rootProject.file("native/out/x86")) {
include("busybox", "magiskboot", "magiskinit", "magisk")
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
}
from(rootProject.file("native/out/x86_64")) {
include("magisk")
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
}
}
doFirst {
if (inputs.sourceFiles.files.size != 10)
throw StopExecutionException("Build binary files first")
}
}
val createStubLibs by tasks.registering {
dependsOn(syncLibs)
doLast {
val arm64 = project.file("src/main/jniLibs/arm64-v8a/libstub.so")
arm64.parentFile.mkdirs()
arm64.createNewFile()
val x64 = project.file("src/main/jniLibs/x86_64/libstub.so")
x64.parentFile.mkdirs()
x64.createNewFile()
}
}
val syncAssets by tasks.registering(Sync::class) {
dependsOn(createStubLibs)
inputs.property("version", Config.version)
inputs.property("versionCode", Config.versionCode)
into("src/main/assets")
from(rootProject.file("scripts")) {
include("util_functions.sh", "boot_patch.sh", "magisk_uninstaller.sh", "addon.d.sh")
}
into("chromeos") {
from(rootProject.file("tools/futility"))
from(rootProject.file("tools/keys")) {
include("kernel_data_key.vbprivk", "kernel.keyblock")
}
}
filesMatching("**/util_functions.sh") {
filter {
it.replace("#MAGISK_VERSION_STUB",
"MAGISK_VER='${Config.version}'\n" +
"MAGISK_VER_CODE=${Config.versionCode}")
}
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
}
}
val syncResources by tasks.registering(Sync::class) {
dependsOn(syncAssets)
into("src/main/resources/META-INF/com/google/android")
from(rootProject.file("scripts/update_binary.sh")) {
rename { "update-binary" }
}
from(rootProject.file("scripts/flash_script.sh")) {
rename { "updater-script" }
}
}
tasks["preBuild"]?.dependsOn(syncResources)
android.applicationVariants.all { android.applicationVariants.all {
val keysDir = rootProject.file("tools/keys") val keysDir = rootProject.file("tools/keys")
@ -178,6 +252,5 @@ dependencies {
implementation("androidx.transition:transition:1.3.1") implementation("androidx.transition:transition:1.3.1")
implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.core:core-ktx:1.3.2") implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
implementation("com.google.android.material:material:1.2.1") implementation("com.google.android.material:material:1.2.1")
} }

View File

@ -13,6 +13,8 @@
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:name="a.e" android:name="a.e"
android:allowBackup="false" android:allowBackup="false"
android:multiArch="true"
android:extractNativeLibs="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"> tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
<!-- Splash --> <!-- Splash -->

View File

@ -19,6 +19,7 @@ import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import timber.log.Timber import timber.log.Timber
import java.io.File
import kotlin.system.exitProcess import kotlin.system.exitProcess
open class App() : Application() { open class App() : Application() {
@ -61,6 +62,12 @@ open class App() : Application() {
val wrapped = impl.wrap() val wrapped = impl.wrap()
super.attachBaseContext(wrapped) super.attachBaseContext(wrapped)
val info = base.applicationInfo
val libDir = runCatching {
info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String?
}.getOrNull() ?: info.nativeLibraryDir
Const.NATIVE_LIB_DIR = File(libDir)
// Normal startup // Normal startup
startKoin { startKoin {
androidContext(wrapped) androidContext(wrapped)

View File

@ -1,13 +1,30 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.os.Build
import android.os.Process import android.os.Process
import java.io.File
@Suppress("DEPRECATION")
object Const { object Const {
val CPU_ABI: String
val CPU_ABI_32: String
init {
if (Build.VERSION.SDK_INT >= 21) {
CPU_ABI = Build.SUPPORTED_ABIS[0]
CPU_ABI_32 = Build.SUPPORTED_32_BIT_ABIS[0]
} else {
CPU_ABI = Build.CPU_ABI
CPU_ABI_32 = CPU_ABI
}
}
// Paths // Paths
lateinit var MAGISKTMP: String lateinit var MAGISKTMP: String
lateinit var NATIVE_LIB_DIR: File
val MAGISK_PATH get() = "$MAGISKTMP/modules" val MAGISK_PATH get() = "$MAGISKTMP/modules"
const val TMP_FOLDER_PATH = "/dev/tmp" const val TMPDIR = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log" const val MAGISK_LOG = "/cache/magisk.log"
// Versions // Versions
@ -19,11 +36,9 @@ object Const {
val USER_ID = Process.myUid() / 100000 val USER_ID = Process.myUid() / 100000
object Version { object Version {
const val MIN_VERSION = "v19.0" const val MIN_VERSION = "v20.4"
const val MIN_VERCODE = 19000 const val MIN_VERCODE = 20400
fun atLeast_20_2() = Info.env.magiskVersionCode >= 20200 || isCanary()
fun atLeast_20_4() = Info.env.magiskVersionCode >= 20400 || isCanary()
fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary() fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary()
fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary() fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary()
fun isCanary() = Info.env.magiskVersionCode % 100 != 0 fun isCanary() = Info.env.magiskVersionCode % 100 != 0

View File

@ -1,31 +0,0 @@
package com.topjohnwu.magisk.core.download
import android.net.Uri
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
sealed class Action : Parcelable {
sealed class Flash : Action() {
@Parcelize
object Primary : Flash()
@Parcelize
object Secondary : Flash()
}
@Parcelize
object Download : Action()
@Parcelize
object Uninstall : Action()
@Parcelize
object EnvFix : Action()
@Parcelize
data class Patch(val fileUri: Uri) : Action()
}

View File

@ -8,7 +8,6 @@ import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.ForegroundTracker import com.topjohnwu.magisk.core.ForegroundTracker
import com.topjohnwu.magisk.core.base.BaseService import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.checkSum
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.core.utils.ProgressInputStream import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.data.repository.NetworkService
@ -69,17 +68,14 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
// -- Download logic // -- Download logic
private suspend fun Subject.startDownload() { private suspend fun Subject.startDownload() {
val skip = this is Subject.Magisk && file.checkSum("MD5", magisk.md5) val stream = service.fetchFile(url).toProgressStream(this)
if (!skip) { when (this) {
val stream = service.fetchFile(url).toProgressStream(this) is Subject.Module -> // Download and process on-the-fly
when (this) { stream.toModule(file, service.fetchInstaller().byteStream())
is Subject.Module -> // Download and process on-the-fly else -> {
stream.toModule(file, service.fetchInstaller().byteStream()) withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) }
else -> { if (this is Subject.Manager)
withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) } handleAPK(this)
if (this is Subject.Manager)
handleAPK(this)
}
} }
} }
val newId = notifyFinish(this) val newId = notifyFinish(this)

View File

@ -7,11 +7,10 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import androidx.core.net.toFile import androidx.core.net.toFile
import com.topjohnwu.magisk.core.download.Action.* import com.topjohnwu.magisk.core.download.Action.Flash
import com.topjohnwu.magisk.core.download.Action.Flash.Secondary import com.topjohnwu.magisk.core.download.Subject.Manager
import com.topjohnwu.magisk.core.download.Subject.* import com.topjohnwu.magisk.core.download.Subject.Module
import com.topjohnwu.magisk.core.intent import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.tasks.EnvFixTask
import com.topjohnwu.magisk.ui.flash.FlashFragment import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.APKInstall
import kotlin.random.Random.Default.nextInt import kotlin.random.Random.Default.nextInt
@ -22,25 +21,12 @@ open class DownloadService : BaseDownloader() {
private val context get() = this private val context get() = this
override suspend fun onFinish(subject: Subject, id: Int) = when (subject) { override suspend fun onFinish(subject: Subject, id: Int) = when (subject) {
is Magisk -> subject.onFinish(id)
is Module -> subject.onFinish(id) is Module -> subject.onFinish(id)
is Manager -> subject.onFinish(id) is Manager -> subject.onFinish(id)
} }
private suspend fun Magisk.onFinish(id: Int) = when (val action = action) {
Uninstall -> FlashFragment.uninstall(file, id)
EnvFix -> {
remove(id)
EnvFixTask(file).exec()
Unit
}
is Patch -> FlashFragment.patch(file, action.fileUri, id)
is Flash -> FlashFragment.flash(file, action is Secondary, id)
else -> Unit
}
private fun Module.onFinish(id: Int) = when (action) { private fun Module.onFinish(id: Int) = when (action) {
is Flash -> FlashFragment.install(file, id) Flash -> FlashFragment.install(file, id)
else -> Unit else -> Unit
} }
@ -53,22 +39,13 @@ open class DownloadService : BaseDownloader() {
override fun Notification.Builder.setIntent(subject: Subject) override fun Notification.Builder.setIntent(subject: Subject)
= when (subject) { = when (subject) {
is Magisk -> setIntent(subject)
is Module -> setIntent(subject) is Module -> setIntent(subject)
is Manager -> setIntent(subject) is Manager -> setIntent(subject)
} }
private fun Notification.Builder.setIntent(subject: Magisk)
= when (val action = subject.action) {
Uninstall -> setContentIntent(FlashFragment.uninstallIntent(context, subject.file))
is Flash -> setContentIntent(FlashFragment.flashIntent(context, subject.file, action is Secondary))
is Patch -> setContentIntent(FlashFragment.patchIntent(context, subject.file, action.fileUri))
else -> setContentIntent(Intent())
}
private fun Notification.Builder.setIntent(subject: Module) private fun Notification.Builder.setIntent(subject: Module)
= when (subject.action) { = when (subject.action) {
is Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file)) Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
else -> setContentIntent(Intent()) else -> setContentIntent(Intent())
} }

View File

@ -5,7 +5,6 @@ import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
import androidx.core.net.toUri import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.model.MagiskJson
import com.topjohnwu.magisk.core.model.ManagerJson import com.topjohnwu.magisk.core.model.ManagerJson
import com.topjohnwu.magisk.core.model.StubJson import com.topjohnwu.magisk.core.model.StubJson
import com.topjohnwu.magisk.core.model.module.OnlineModule import com.topjohnwu.magisk.core.model.module.OnlineModule
@ -53,57 +52,12 @@ sealed class Subject : Parcelable {
} }
} }
}
abstract class Magisk : Subject() {
sealed class Action : Parcelable {
val magisk: MagiskJson = Info.remote.magisk @Parcelize
object Flash : Action()
@Parcelize
private class Internal( @Parcelize
override val action: Action object Download : Action()
) : Magisk() {
override val url: String get() = magisk.link
override val title: String get() = "Magisk-${magisk.version}(${magisk.versionCode})"
@IgnoredOnParcel
override val file by lazy {
cachedFile("magisk.zip")
}
}
@Parcelize
private class Uninstall : Magisk() {
override val action get() = Action.Uninstall
override val url: String get() = Info.remote.uninstaller.link
override val title: String get() = "uninstall.zip"
@IgnoredOnParcel
override val file by lazy {
cachedFile(title)
}
}
@Parcelize
private class Download : Magisk() {
override val action get() = Action.Download
override val url: String get() = magisk.link
override val title: String get() = "Magisk-${magisk.version}(${magisk.versionCode}).zip"
@IgnoredOnParcel
override val file by lazy {
MediaStoreUtils.getFile(title).uri
}
}
companion object {
operator fun invoke(config: Action) = when (config) {
Action.Download -> Download()
Action.Uninstall -> Uninstall()
Action.EnvFix, is Action.Flash, is Action.Patch -> Internal(config)
}
}
}
} }

View File

@ -2,14 +2,13 @@ package com.topjohnwu.magisk.core.tasks
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.core.os.postDelayed import androidx.core.net.toFile
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
@ -25,61 +24,50 @@ open class FlashZip(
private val logs: MutableList<String> private val logs: MutableList<String>
): KoinComponent { ): KoinComponent {
val context: Context by inject() private val context: Context by inject()
private val installFolder = File(context.cacheDir, "flash").apply { private val installDir = File(context.cacheDir, "flash")
if (!exists()) mkdirs() private lateinit var zipFile: File
}
private val tmpFile: File = File(installFolder, "install.zip")
@Throws(IOException::class)
private fun unzipAndCheck(): Boolean {
val parentFile = tmpFile.parentFile ?: return false
tmpFile.unzip(parentFile, "META-INF/com/google/android", true)
val updaterScript = File(parentFile, "updater-script")
return Shell
.su("grep -q '#MAGISK' $updaterScript")
.exec()
.isSuccess
}
@Throws(IOException::class) @Throws(IOException::class)
private fun flash(): Boolean { private fun flash(): Boolean {
console.add("- Copying zip to temp directory") installDir.deleteRecursively()
installDir.mkdirs()
runCatching { zipFile = if (mUri.scheme == "file") {
mUri.inputStream().writeTo(tmpFile) mUri.toFile()
}.getOrElse { } else {
when (it) { File(installDir, "install.zip").also {
is FileNotFoundException -> console.add("! Invalid Uri") console.add("- Copying zip to temp directory")
is IOException -> console.add("! Cannot copy to cache") try {
mUri.inputStream().writeTo(it)
} catch (e: IOException) {
when (e) {
is FileNotFoundException -> console.add("! Invalid Uri")
else -> console.add("! Cannot copy to cache")
}
throw e
}
} }
throw it
} }
val isMagiskModule = runCatching { val isValid = runCatching {
unzipAndCheck() zipFile.unzip(installDir, "META-INF/com/google/android", true)
val script = File(installDir, "updater-script")
script.readText().contains("#MAGISK")
}.getOrElse { }.getOrElse {
console.add("! Unzip error") console.add("! Unzip error")
throw it throw it
} }
if (!isMagiskModule) { if (!isValid) {
console.add("! This zip is not a Magisk Module!") console.add("! This zip is not a Magisk module!")
return false return false
} }
console.add("- Installing ${mUri.displayName}") console.add("- Installing ${mUri.displayName}")
val parentFile = tmpFile.parent ?: return false return Shell.su("sh $installDir/update-binary dummy 1 $zipFile")
.to(console, logs).exec().isSuccess
return Shell
.su(
"cd $parentFile",
"BOOTMODE=true sh update-binary dummy 1 $tmpFile"
)
.to(console, logs)
.exec().isSuccess
} }
open suspend fun exec() = withContext(Dispatchers.IO) { open suspend fun exec() = withContext(Dispatchers.IO) {
@ -94,25 +82,7 @@ open class FlashZip(
Timber.e(e) Timber.e(e)
false false
} finally { } finally {
Shell.su("cd /", "rm -rf ${tmpFile.parent} ${Const.TMP_FOLDER_PATH}").submit() Shell.su("cd /", "rm -rf $installDir ${Const.TMPDIR}").submit()
} }
} }
class Uninstall(
uri: Uri,
console: MutableList<String>,
log: MutableList<String>
) : FlashZip(uri, console, log) {
override suspend fun exec(): Boolean {
val success = super.exec()
if (success) {
UiThreadHandler.handler.postDelayed(3000) {
Shell.su("pm uninstall " + context.packageName).exec()
}
}
return success
}
}
} }

View File

@ -91,8 +91,7 @@ object HideAPK {
} }
private suspend fun patchAndHide(context: Context, label: String): Boolean { private suspend fun patchAndHide(context: Context, label: String): Boolean {
val dlStub = !isRunningAsStub && SDK_INT >= 28 && Const.Version.atLeast_20_2() val src = if (!isRunningAsStub && SDK_INT >= 28) {
val src = if (dlStub) {
val stub = File(context.cacheDir, "stub.apk") val stub = File(context.cacheDir, "stub.apk")
try { try {
svc.fetchFile(Info.remote.stub.link).byteStream().use { svc.fetchFile(Info.remote.stub.link).byteStream().use {

View File

@ -1,25 +1,25 @@
package com.topjohnwu.magisk.core.tasks package com.topjohnwu.magisk.core.tasks
import android.content.Context import android.content.Context
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.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.di.Protected import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.events.dialog.EnvFixDialog
import com.topjohnwu.magisk.ktx.reboot import com.topjohnwu.magisk.ktx.reboot
import com.topjohnwu.magisk.ktx.symlink
import com.topjohnwu.magisk.ktx.withStreams import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.signing.SignBoot import com.topjohnwu.signing.SignBoot
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@ -37,7 +37,6 @@ import org.kamranzafar.jtar.TarHeader
import org.kamranzafar.jtar.TarInputStream import org.kamranzafar.jtar.TarInputStream
import org.kamranzafar.jtar.TarOutputStream import org.kamranzafar.jtar.TarOutputStream
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
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.* import java.io.*
@ -46,10 +45,8 @@ import java.security.SecureRandom
import java.util.* import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import kotlin.collections.set
abstract class MagiskInstallImpl protected constructor( abstract class MagiskInstallImpl protected constructor(
private var zipUri: Uri,
protected val console: MutableList<String> = NOPList.getInstance(), protected val console: MutableList<String> = NOPList.getInstance(),
private val logs: MutableList<String> = NOPList.getInstance() private val logs: MutableList<String> = NOPList.getInstance()
) : KoinComponent { ) : KoinComponent {
@ -59,17 +56,7 @@ abstract class MagiskInstallImpl protected constructor(
private var tarOut: TarOutputStream? = null private var tarOut: TarOutputStream? = null
private val service: NetworkService by inject() private val service: NetworkService by inject()
protected val context: Context by inject() protected val context: Context by inject(Protected)
companion object {
private val ABI_MAP = TreeMap<String, String>()
init {
ABI_MAP["armeabi-v7a"] = "arm"
ABI_MAP["arm64-v8a"] = "arm64"
ABI_MAP["x86"] = "x86"
ABI_MAP["x86_64"] = "x64"
}
}
private fun findImage(): Boolean { private fun findImage(): Boolean {
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh() srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
@ -98,69 +85,85 @@ abstract class MagiskInstallImpl protected constructor(
return true return true
} }
@Suppress("DEPRECATION") private fun installDirFile(name: String): File {
private fun extractZip(): Boolean { return if (installDir is SuFile)
val arch: String SuFile(installDir, name)
val arch32: String else
if (Build.VERSION.SDK_INT >= 21) { File(installDir, name)
arch = ABI_MAP[Build.SUPPORTED_ABIS[0]]!! }
arch32 = ABI_MAP[Build.SUPPORTED_32_BIT_ABIS[0]]!!
private fun extractFiles(): Boolean {
console.add("- Device platform: ${Const.CPU_ABI}")
console.add("- Installing: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
val binDir = File(context.filesDir.parent, "install")
binDir.deleteRecursively()
binDir.mkdirs()
installDir = if (Shell.rootAccess()) {
SuFile("${Const.TMPDIR}/install")
} else { } else {
arch = ABI_MAP[Build.CPU_ABI]!! binDir
arch32 = arch
}
console.add("- Device platform: $arch")
console.add("- Magisk Manager: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
console.add("- Install target: ${Info.remote.magisk.version} (${Info.remote.magisk.versionCode})")
fun newFile(name: String): File {
return if (installDir is SuFile)
SuFile(installDir, name)
else
File(installDir, name)
} }
try { try {
ZipInputStream(zipUri.inputStream().buffered()).use { zi -> // Extract binaries
lateinit var ze: ZipEntry if (isRunningAsStub) {
while (zi.nextEntry?.let { ze = it } != null) { ZipInputStream(DynAPK.current(context).inputStream().buffered()).use { zi ->
if (ze.isDirectory) lateinit var ze: ZipEntry
continue while (zi.nextEntry?.let { ze = it } != null) {
if (ze.isDirectory)
continue
var name: String? = null val name = if (ze.name.startsWith("lib/${Const.CPU_ABI_32}/")) {
val n = ze.name.substring(ze.name.lastIndexOf('/') + 1)
if (ze.name.startsWith("chromeos/")) { n.substring(3, n.length - 3)
name = ze.name } else {
} else { continue
for (n in listOf("$arch32/", "common/", "META-INF/com/google/android/update-binary")) {
if (ze.name.startsWith(n)) {
name = ze.name.substring(ze.name.lastIndexOf('/') + 1)
break
}
} }
val dest = File(binDir, name)
dest.outputStream().use { zi.copyTo(it) }
} }
}
name ?: continue } else {
val libs = Const.NATIVE_LIB_DIR.listFiles { _, name ->
val dest = newFile(name) name.startsWith("lib") && name.endsWith(".so")
dest.parentFile!!.mkdirs() } ?: emptyArray()
SuFileOutputStream(dest).use { s -> zi.copyTo(s) } for (lib in libs) {
val name = lib.name.substring(3, lib.name.length - 3)
val bin = File(binDir, name)
symlink(lib.path, bin.path)
} }
} }
} catch (e: IOException) {
console.add("! Cannot unzip zip") // Extract scripts
for (script in listOf("util_functions.sh", "boot_patch.sh", "addon.d.sh")) {
val dest = File(binDir, script)
context.assets.open(script).use { it.writeTo(dest) }
}
// Extract chromeos tools
File(binDir, "chromeos").mkdir()
for (file in listOf("futility", "kernel_data_key.vbprivk", "kernel.keyblock")) {
val name = "chromeos/$file"
val dest = File(binDir, name)
context.assets.open(name).use { it.writeTo(dest) }
}
} catch (e: Exception) {
console.add("! Unable to extract files")
Timber.e(e) Timber.e(e)
return false return false
} }
val init64 = newFile("magiskinit64") if (installDir !== binDir) {
if (init64.exists() && arch != arch32) { arrayOf(
init64.renameTo(newFile("magiskinit")) "rm -rf $installDir",
} else { "mkdir -p $installDir",
init64.delete() "cp_readlink $binDir $installDir",
"rm -rf $binDir"
).sh()
} }
"cd $installDir; chmod 755 *".sh()
return true return true
} }
@ -177,7 +180,7 @@ abstract class MagiskInstallImpl protected constructor(
lateinit var entry: TarEntry lateinit var entry: TarEntry
fun decompressedStream() = fun decompressedStream() =
if (entry.name.contains(".lz4")) LZ4FrameInputStream(tarIn) else tarIn if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
while (tarIn.nextEntry?.let { entry = it } != null) { while (tarIn.nextEntry?.let { entry = it } != null) {
if (entry.name.contains("boot.img") || if (entry.name.contains("boot.img") ||
@ -186,12 +189,9 @@ abstract class MagiskInstallImpl protected constructor(
console.add("-- Extracting: $name") console.add("-- Extracting: $name")
val extract = File(installDir, name) val extract = File(installDir, name)
FileOutputStream(extract).use { decompressedStream().copyTo(it) } decompressedStream().writeTo(extract)
} else if (entry.name.contains("vbmeta.img")) { } else if (entry.name.contains("vbmeta.img")) {
val rawData = ByteArrayOutputStream().let { val rawData = decompressedStream().readBytes()
decompressedStream().copyTo(it)
it.toByteArray()
}
// Valid vbmeta.img should be at least 256 bytes // Valid vbmeta.img should be at least 256 bytes
if (rawData.size < 256) if (rawData.size < 256)
continue continue
@ -208,8 +208,8 @@ abstract class MagiskInstallImpl protected constructor(
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024) tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
} }
} }
val boot = SuFile.open(installDir, "boot.img") val boot = installDirFile("boot.img")
val recovery = SuFile.open(installDir, "recovery.img") val recovery = installDirFile("recovery.img")
if (Config.recovery && recovery.exists() && boot.exists()) { if (Config.recovery && recovery.exists() && boot.exists()) {
// Install Magisk to recovery // Install Magisk to recovery
srcBoot = recovery.path srcBoot = recovery.path
@ -306,10 +306,19 @@ abstract class MagiskInstallImpl protected constructor(
return false return false
} }
// Fix up binaries
if (installDir is SuFile) {
"fix_env $installDir".sh()
} else {
"cp_readlink $installDir".sh()
}
return true return true
} }
private fun patchBoot(): Boolean { private fun patchBoot(): Boolean {
"cd $installDir".sh()
var srcNand = "" var srcNand = ""
if ("[ -c $srcBoot ] && nanddump -f boot.img $srcBoot".sh().isSuccess) { if ("[ -c $srcBoot ] && nanddump -f boot.img $srcBoot".sh().isSuccess) {
srcNand = srcBoot srcNand = srcBoot
@ -334,26 +343,20 @@ abstract class MagiskInstallImpl protected constructor(
"KEEPVERITY=${Config.keepVerity} " + "KEEPVERITY=${Config.keepVerity} " +
"RECOVERYMODE=${Config.recovery}" "RECOVERYMODE=${Config.recovery}"
if (!("$FLAGS sh update-binary sh boot_patch.sh $srcBoot").sh().isSuccess) { if (!"$FLAGS sh boot_patch.sh $srcBoot".sh().isSuccess)
return false return false
}
if (srcNand.isNotEmpty()) { if (srcNand.isNotEmpty())
srcBoot = srcNand srcBoot = srcNand
}
val job = Shell.sh( val job = Shell.sh("./magiskboot cleanup", "cd /")
"./magiskboot cleanup",
"mv bin/busybox busybox",
"rm -rf magisk.apk bin boot.img update-binary",
"cd /")
val patched = File(installDir, "new-boot.img") val patched = installDirFile("new-boot.img")
if (isSigned) { if (isSigned) {
console.add("- Signing boot image with verity keys") console.add("- Signing boot image with verity keys")
val signed = File(installDir, "signed.img") val signed = installDirFile("signed.img")
try { try {
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) { withStreams(SuFileInputStream(patched), SuFileOutputStream(signed)) {
input, out -> SignBoot.doSignature(null, null, input, out, "/boot") input, out -> SignBoot.doSignature(null, null, input, out, "/boot")
} }
} catch (e: IOException) { } catch (e: IOException) {
@ -368,18 +371,10 @@ abstract class MagiskInstallImpl protected constructor(
return true return true
} }
private fun copySepolicyRules(): Boolean {
if (Info.remote.magisk.versionCode >= 21100) {
// Copy existing rules for migration
"copy_sepolicy_rules".sh()
}
return true
}
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
"run_migrations".sh() arrayOf("run_migrations", "copy_sepolicy_rules").sh()
return true return true
} }
@ -403,24 +398,28 @@ abstract class MagiskInstallImpl protected constructor(
return true return true
} }
protected fun String.sh() = Shell.sh(this).to(console, logs).exec() protected fun uninstall(): Boolean {
val apk = if (isRunningAsStub) {
DynAPK.current(context).path
} else {
context.packageCodePath
}
return "run_uninstaller $apk".sh().isSuccess
}
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 Array<String>.sh() = Shell.sh(*this).to(console, logs).exec()
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) = extractZip() && handleFile(patchFile) protected fun doPatchFile(patchFile: Uri) = extractFiles() && handleFile(patchFile)
protected fun direct() = findImage() && extractZip() && patchBoot() && protected fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
copySepolicyRules() && flashBoot()
protected suspend fun secondSlot() = findSecondaryImage() && extractZip() && protected suspend fun secondSlot() =
patchBoot() && copySepolicyRules() && flashBoot() && postOTA() findSecondaryImage() && extractFiles() && patchBoot() && flashBoot() && postOTA()
protected fun fixEnv(): Boolean { protected fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
installDir = SuFile("/data/adb/magisk")
Shell.su("rm -rf /data/adb/magisk/*").exec()
return extractZip() && Shell.su("fix_env").exec().isSuccess
}
@WorkerThread @WorkerThread
protected abstract suspend fun operations(): Boolean protected abstract suspend fun operations(): Boolean
@ -429,85 +428,84 @@ abstract class MagiskInstallImpl protected constructor(
} }
abstract class MagiskInstaller( abstract class MagiskInstaller(
zip: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String> logs: MutableList<String>
) : MagiskInstallImpl(zip, console, logs) { ) : MagiskInstallImpl(console, logs) {
init {
installDir = File(get<Context>(Protected).filesDir.parent, "install")
"rm -rf $installDir".sh()
installDir.mkdirs()
}
override suspend fun exec(): Boolean { override suspend fun exec(): Boolean {
val success = super.exec() val success = super.exec()
if (success) { if (success) {
console.add("- All done!") console.add("- All done!")
} else { } else {
Shell.sh("rm -rf $installDir").submit() if (installDir is SuFile) {
Shell.sh("rm -rf ${Const.TMPDIR}").submit()
} else {
Shell.sh("rm -rf $installDir").submit()
}
console.add("! Installation failed") console.add("! Installation failed")
} }
return success return success
} }
class Patch( class Patch(
zip: Uri,
private val uri: Uri, private val uri: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String> logs: MutableList<String>
) : MagiskInstaller(zip, console, logs) { ) : MagiskInstaller(console, logs) {
override suspend fun operations() = doPatchFile(uri) override suspend fun operations() = doPatchFile(uri)
} }
class SecondSlot( class SecondSlot(
zip: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String> logs: MutableList<String>
) : MagiskInstaller(zip, console, logs) { ) : MagiskInstaller(console, logs) {
override suspend fun operations() = secondSlot() override suspend fun operations() = secondSlot()
} }
class Direct( class Direct(
zip: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String> logs: MutableList<String>
) : MagiskInstaller(zip, console, logs) { ) : MagiskInstaller(console, logs) {
override suspend fun operations() = direct() override suspend fun operations() = direct()
} }
class Emulator( class Emulator(
zip: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String> logs: MutableList<String>
) : MagiskInstallImpl(zip, console, logs) { ) : MagiskInstaller(console, logs) {
override suspend fun operations() = fixEnv() override suspend fun operations() = fixEnv()
}
class Uninstall(
console: MutableList<String>,
logs: MutableList<String>
) : MagiskInstallImpl(console, logs) {
override suspend fun operations() = uninstall()
override suspend fun exec(): Boolean { override suspend fun exec(): Boolean {
val success = super.exec() val success = super.exec()
if (success) { if (success) {
console.add("- All done!") UiThreadHandler.handler.postDelayed(3000) {
} else { Shell.su("pm uninstall ${context.packageName}").exec()
console.add("! Installation failed") }
} }
return success return success
} }
} }
}
class EnvFixTask(zip: Uri) : MagiskInstallImpl(zip) { class FixEnv(private val callback: () -> Unit) : MagiskInstallImpl() {
override suspend fun operations() = fixEnv()
override suspend fun operations() = fixEnv() override suspend fun exec(): Boolean {
val success = super.exec()
override suspend fun exec(): Boolean { callback()
val success = super.exec() Utils.toast(
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(EnvFixDialog.DISMISS)) if (success) R.string.reboot_delay_toast else R.string.setup_fail,
Utils.toast( Toast.LENGTH_LONG
if (success) R.string.reboot_delay_toast else R.string.setup_fail, )
Toast.LENGTH_LONG if (success)
) UiThreadHandler.handler.postDelayed(5000) { reboot() }
if (success) return success
UiThreadHandler.handler.postDelayed(5000) { reboot() } }
return success
} }
} }

View File

@ -2,14 +2,17 @@ package com.topjohnwu.magisk.core.utils
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.ktx.deviceProtectedContext
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.rawResource import com.topjohnwu.magisk.ktx.rawResource
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import java.io.File
import java.util.jar.JarFile
class RootInit : Shell.Initializer() { class RootInit : Shell.Initializer() {
@ -17,31 +20,50 @@ class RootInit : Shell.Initializer() {
return init(context.wrap(), shell) return init(context.wrap(), shell)
} }
fun init(context: Context, shell: Shell): Boolean { private fun init(context: Context, shell: Shell): Boolean {
shell.newJob().apply {
add("export SDK_INT=${Build.VERSION.SDK_INT}")
if (Const.Version.atLeast_20_4()) {
add("export MAGISKTMP=\$(magisk --path)/.magisk")
} else {
add("export MAGISKTMP=/sbin/.magisk")
}
if (Const.Version.atLeast_21_0()) {
add("export ASH_STANDALONE=1")
add("[ -x /data/adb/magisk/busybox ] && exec /data/adb/magisk/busybox sh")
} else {
add("export PATH=\"\$MAGISKTMP/busybox:\$PATH\"")
}
add(context.rawResource(R.raw.manager))
if (shell.isRoot) {
add(context.rawResource(R.raw.util_functions))
}
add("mm_init")
}.exec()
fun fastCmd(cmd: String) = ShellUtils.fastCmd(shell, cmd) fun fastCmd(cmd: String) = ShellUtils.fastCmd(shell, cmd)
fun getVar(name: String) = fastCmd("echo \$$name") fun getVar(name: String) = fastCmd("echo \$$name")
fun getBool(name: String) = getVar(name).toBoolean() fun getBool(name: String) = getVar(name).toBoolean()
shell.newJob().apply {
add("export SDK_INT=${Build.VERSION.SDK_INT}")
add("export ASH_STANDALONE=1")
val localBB: File
if (isRunningAsStub) {
val jar = JarFile(DynAPK.current(context))
val bb = jar.getJarEntry("lib/${Const.CPU_ABI_32}/libbusybox.so")
localBB = context.deviceProtectedContext.cachedFile("busybox")
jar.getInputStream(bb).writeTo(localBB)
localBB.setExecutable(true)
} else {
localBB = File(Const.NATIVE_LIB_DIR, "libbusybox.so")
}
if (!shell.isRoot) {
// Directly execute the file
add("exec $localBB sh")
} else {
// Copy it out of /data to workaround Samsung BS
add(
"export MAGISKTMP=\$(magisk --path)/.magisk",
"if [ -x \$MAGISKTMP/busybox/busybox ]; then",
" cp -af $localBB \$MAGISKTMP/busybox/busybox",
" exec \$MAGISKTMP/busybox/busybox sh",
"else",
" exec $localBB sh",
"fi"
)
}
add(context.rawResource(R.raw.manager))
if (shell.isRoot) {
add(context.assets.open("util_functions.sh"))
}
add("mm_init")
}.exec()
Const.MAGISKTMP = getVar("MAGISKTMP") Const.MAGISKTMP = getVar("MAGISKTMP")
Info.isSAR = getBool("SYSTEM_ROOT") Info.isSAR = getBool("SYSTEM_ROOT")
Info.ramdisk = getBool("RAMDISKEXIST") Info.ramdisk = getBool("RAMDISKEXIST")

View File

@ -110,7 +110,7 @@ class SelectModuleEvent : ViewEvent(), FragmentExecutor {
activity.startActivityForResult(intent, Const.ID.FETCH_ZIP) { code, intent -> activity.startActivityForResult(intent, Const.ID.FETCH_ZIP) { code, intent ->
if (code == Activity.RESULT_OK && intent != null) { if (code == Activity.RESULT_OK && intent != null) {
intent.data?.also { intent.data?.also {
MainDirections.actionFlashFragment(it, Const.Value.FLASH_ZIP).navigate() MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate()
} }
} }
} }

View File

@ -10,7 +10,9 @@ abstract class DialogEvent : ViewEvent(), ActivityExecutor {
protected lateinit var dialog: MagiskDialog protected lateinit var dialog: MagiskDialog
override fun invoke(activity: BaseUIActivity<*, *>) { override fun invoke(activity: BaseUIActivity<*, *>) {
dialog = MagiskDialog(activity).apply(this::build).reveal() dialog = MagiskDialog(activity)
.apply { setOwnerActivity(activity) }
.apply(this::build).reveal()
} }
abstract fun build(dialog: MagiskDialog) abstract fun build(dialog: MagiskDialog)

View File

@ -1,15 +1,11 @@
package com.topjohnwu.magisk.events.dialog package com.topjohnwu.magisk.events.dialog
import android.content.BroadcastReceiver import androidx.lifecycle.lifecycleScope
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.download.Action.EnvFix import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.download.DownloadService import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.download.Subject.Magisk
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import kotlinx.coroutines.launch
class EnvFixDialog : DialogEvent() { class EnvFixDialog : DialogEvent() {
@ -24,20 +20,17 @@ class EnvFixDialog : DialogEvent() {
.applyMessage(R.string.setup_msg) .applyMessage(R.string.setup_msg)
.resetButtons() .resetButtons()
.cancellable(false) .cancellable(false)
val lbm = LocalBroadcastManager.getInstance(dialog.context) (dialog.ownerActivity as BaseActivity).lifecycleScope.launch {
lbm.registerReceiver(object : BroadcastReceiver() { MagiskInstaller.FixEnv {
override fun onReceive(context: Context, intent: Intent?) {
dialog.dismiss() dialog.dismiss()
lbm.unregisterReceiver(this) }.exec()
} }
}, IntentFilter(DISMISS))
DownloadService.start(dialog.context, Magisk(EnvFix))
} }
} }
.applyButton(MagiskDialog.ButtonType.NEGATIVE) { .applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.cancel titleRes = android.R.string.cancel
} }
.let { Unit } .let { }
companion object { companion object {
const val DISMISS = "com.topjohnwu.magisk.ENV_DONE" const val DISMISS = "com.topjohnwu.magisk.ENV_DONE"

View File

@ -14,7 +14,7 @@ class ModuleInstallDialog(private val item: OnlineModule) : DialogEvent() {
with(dialog) { with(dialog) {
fun download(install: Boolean) { fun download(install: Boolean) {
val config = if (install) Action.Flash.Primary else Action.Download val config = if (install) Action.Flash else Action.Download
val subject = Subject.Module(item, config) val subject = Subject.Module(item, config)
DownloadService.start(context, subject) DownloadService.start(context, subject)
} }

View File

@ -4,9 +4,7 @@ import android.app.ProgressDialog
import android.widget.Toast import android.widget.Toast
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Action import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@ -46,7 +44,7 @@ class UninstallDialog : DialogEvent() {
} }
private fun completeUninstall() { private fun completeUninstall() {
DownloadService.start(dialog.context, Subject.Magisk(Action.Uninstall)) FlashFragment.uninstall()
} }
} }

View File

@ -23,6 +23,7 @@ import android.graphics.drawable.LayerDrawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION.SDK_INT
import android.system.Os
import android.text.PrecomputedText import android.text.PrecomputedText
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -59,6 +60,20 @@ import java.lang.reflect.Array as JArray
val packageName: String get() = get<Context>().packageName val packageName: String get() = get<Context>().packageName
fun symlink(oldPath: String, newPath: String) {
if (SDK_INT >= 21) {
Os.symlink(oldPath, newPath)
} else {
// Just copy the files pre 5.0
val old = File(oldPath)
val newFile = File(newPath)
old.copyTo(newFile)
if (old.canExecute())
newFile.setExecutable(true)
}
}
val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0 val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
@get:SuppressLint("InlinedApi") @get:SuppressLint("InlinedApi")
@ -83,6 +98,11 @@ fun Context.getBitmap(id: Int): Bitmap {
return bitmap return bitmap
} }
val Context.deviceProtectedContext: Context get() =
if (SDK_INT >= 24) {
createDeviceProtectedStorageContext()
} else { this }
fun Intent.startActivity(context: Context) = context.startActivity(this) fun Intent.startActivity(context: Context) = context.startActivity(this)
fun Intent.startActivityWithRoot() { fun Intent.startActivityWithRoot() {

View File

@ -17,13 +17,12 @@ import com.topjohnwu.magisk.ui.MainActivity
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import com.topjohnwu.magisk.MainDirections.Companion.actionFlashFragment as toFlash import com.topjohnwu.magisk.MainDirections.Companion.actionFlashFragment as toFlash
import com.topjohnwu.magisk.ui.flash.FlashFragmentArgs as args
class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() { class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() {
override val layoutRes = R.layout.fragment_flash_md2 override val layoutRes = R.layout.fragment_flash_md2
override val viewModel by viewModel<FlashViewModel> { override val viewModel by viewModel<FlashViewModel> {
parametersOf(args.fromBundle(requireArguments())) parametersOf(FlashFragmentArgs.fromBundle(requireArguments()))
} }
private var defaultOrientation = -1 private var defaultOrientation = -1
@ -78,7 +77,7 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
companion object { companion object {
private fun createIntent(context: Context, args: args) = private fun createIntent(context: Context, args: FlashFragmentArgs) =
NavDeepLinkBuilder(context) NavDeepLinkBuilder(context)
.setGraph(R.navigation.main) .setGraph(R.navigation.main)
.setComponentName(MainActivity::class.java.cmp(context.packageName)) .setComponentName(MainActivity::class.java.cmp(context.packageName))
@ -91,59 +90,34 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
/* Flashing is understood as installing / flashing magisk itself */ /* Flashing is understood as installing / flashing magisk itself */
fun flashIntent(context: Context, file: Uri, isSecondSlot: Boolean, id: Int = -1) = args( fun flash(isSecondSlot: Boolean) = toFlash(
installer = file, action = flashType(isSecondSlot)
action = flashType(isSecondSlot),
dismissId = id
).let { createIntent(context, it) }
fun flash(file: Uri, isSecondSlot: Boolean, id: Int) = toFlash(
installer = file,
action = flashType(isSecondSlot),
dismissId = id
).let { BaseUIActivity.postDirections(it) } ).let { BaseUIActivity.postDirections(it) }
/* Patching is understood as injecting img files with magisk */ /* Patching is understood as injecting img files with magisk */
fun patchIntent(context: Context, file: Uri, uri: Uri, id: Int = -1) = args( fun patch(uri: Uri) = toFlash(
installer = file,
action = Const.Value.PATCH_FILE, action = Const.Value.PATCH_FILE,
additionalData = uri, additionalData = uri
dismissId = id
).let { createIntent(context, it) }
fun patch(file: Uri, uri: Uri, id: Int) = toFlash(
installer = file,
action = Const.Value.PATCH_FILE,
additionalData = uri,
dismissId = id
).let { BaseUIActivity.postDirections(it) } ).let { BaseUIActivity.postDirections(it) }
/* Uninstalling is understood as removing magisk entirely */ /* Uninstalling is understood as removing magisk entirely */
fun uninstallIntent(context: Context, file: Uri, id: Int = -1) = args( fun uninstall() = toFlash(
installer = file, action = Const.Value.UNINSTALL
action = Const.Value.UNINSTALL,
dismissId = id
).let { createIntent(context, it) }
fun uninstall(file: Uri, id: Int) = toFlash(
installer = file,
action = Const.Value.UNINSTALL,
dismissId = id
).let { BaseUIActivity.postDirections(it) } ).let { BaseUIActivity.postDirections(it) }
/* Installing is understood as flashing modules / zips */ /* Installing is understood as flashing modules / zips */
fun installIntent(context: Context, file: Uri, id: Int = -1) = args( fun installIntent(context: Context, file: Uri, id: Int = -1) = FlashFragmentArgs(
installer = file,
action = Const.Value.FLASH_ZIP, action = Const.Value.FLASH_ZIP,
additionalData = file,
dismissId = id dismissId = id
).let { createIntent(context, it) } ).let { createIntent(context, it) }
fun install(file: Uri, id: Int) = toFlash( fun install(file: Uri, id: Int) = toFlash(
installer = file,
action = Const.Value.FLASH_ZIP, action = Const.Value.FLASH_ZIP,
additionalData = file,
dismissId = id dismissId = id
).let { BaseUIActivity.postDirections(it) } ).let { BaseUIActivity.postDirections(it) }
} }

View File

@ -27,9 +27,7 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class FlashViewModel( class FlashViewModel(args: FlashFragmentArgs) : BaseViewModel() {
args: FlashFragmentArgs
) : BaseViewModel() {
@get:Bindable @get:Bindable
var showReboot = Shell.rootAccess() var showReboot = Shell.rootAccess()
@ -52,36 +50,35 @@ class FlashViewModel(
} }
init { init {
args.dismissId.takeIf { it != -1 }?.also { val (action, uri, id) = args
Notifications.mgr.cancel(it) if (id != -1)
} Notifications.mgr.cancel(id)
val (installer, action, uri) = args startFlashing(action, uri)
startFlashing(installer, uri, action)
} }
private fun startFlashing(installer: Uri, uri: Uri?, action: String) { private fun startFlashing(action: String, uri: Uri?) {
viewModelScope.launch { viewModelScope.launch {
val result = when (action) { val result = when (action) {
Const.Value.FLASH_ZIP -> { Const.Value.FLASH_ZIP -> {
FlashZip(installer, outItems, logItems).exec() FlashZip(uri!!, outItems, logItems).exec()
} }
Const.Value.UNINSTALL -> { Const.Value.UNINSTALL -> {
showReboot = false showReboot = false
FlashZip.Uninstall(installer, outItems, logItems).exec() MagiskInstaller.Uninstall(outItems, logItems).exec()
} }
Const.Value.FLASH_MAGISK -> { Const.Value.FLASH_MAGISK -> {
if (Info.isEmulator) if (Info.isEmulator)
MagiskInstaller.Emulator(installer, outItems, logItems).exec() MagiskInstaller.Emulator(outItems, logItems).exec()
else else
MagiskInstaller.Direct(installer, outItems, logItems).exec() MagiskInstaller.Direct(outItems, logItems).exec()
} }
Const.Value.FLASH_INACTIVE_SLOT -> { Const.Value.FLASH_INACTIVE_SLOT -> {
MagiskInstaller.SecondSlot(installer, outItems, logItems).exec() MagiskInstaller.SecondSlot(outItems, logItems).exec()
} }
Const.Value.PATCH_FILE -> { Const.Value.PATCH_FILE -> {
uri ?: return@launch uri ?: return@launch
showReboot = false showReboot = false
MagiskInstaller.Patch(installer, uri, outItems, logItems).exec() MagiskInstaller.Patch(uri, outItems, logItems).exec()
} }
else -> { else -> {
back() back()

View File

@ -7,7 +7,6 @@ import android.view.ViewGroup
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.core.download.BaseDownloader
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
import com.topjohnwu.magisk.ktx.coroutineScope import com.topjohnwu.magisk.ktx.coroutineScope
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
@ -23,7 +22,6 @@ class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Bindi
// Allow markwon to run in viewmodel scope // Allow markwon to run in viewmodel scope
binding.releaseNotes.coroutineScope = viewModel.viewModelScope binding.releaseNotes.coroutineScope = viewModel.viewModelScope
BaseDownloader.observeProgress(this, viewModel::onProgressUpdate)
} }
override fun onCreateView( override fun onCreateView(

View File

@ -8,23 +8,19 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Action
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.events.MagiskInstallFileEvent import com.topjohnwu.magisk.events.MagiskInstallFileEvent
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.get
import timber.log.Timber import timber.log.Timber
import java.io.IOException import java.io.IOException
import kotlin.math.roundToInt
class InstallViewModel( class InstallViewModel(
svc: NetworkService svc: NetworkService
) : BaseViewModel(State.LOADED) { ) : BaseViewModel() {
val isRooted = Shell.rootAccess() val isRooted = Shell.rootAccess()
val skipOptions = Info.isEmulator || (Info.ramdisk && !Info.isFDE && Info.isSAR) val skipOptions = Info.isEmulator || (Info.ramdisk && !Info.isFDE && Info.isSAR)
@ -53,10 +49,6 @@ class InstallViewModel(
} }
} }
@get:Bindable
var progress = 0
set(value) = set(value, field, { field = it }, BR.progress)
@get:Bindable @get:Bindable
var data: Uri? = null var data: Uri? = null
set(value) = set(value, field, { field = it }, BR.data) set(value) = set(value, field, { field = it }, BR.data)
@ -75,34 +67,17 @@ class InstallViewModel(
} }
} }
fun onProgressUpdate(progress: Float, subject: Subject) {
if (subject !is Subject.Magisk) {
return
}
this.progress = progress.times(100).roundToInt()
if (this.progress >= 100) {
state = State.LOADED
} else if (this.progress < -150) {
state = State.LOADING_FAILED
}
}
fun step(nextStep: Int) { fun step(nextStep: Int) {
step = nextStep step = nextStep
} }
fun install() { fun install() {
DownloadService.start(get(), Subject.Magisk(resolveAction())) when (method) {
R.id.method_patch -> FlashFragment.patch(data!!)
R.id.method_direct -> FlashFragment.flash(false)
R.id.method_inactive_slot -> FlashFragment.flash(true)
else -> error("Unknown value")
}
state = State.LOADING state = State.LOADING
} }
// ---
private fun resolveAction() = when (method) {
R.id.method_download -> Action.Download
R.id.method_patch -> Action.Patch(data!!)
R.id.method_direct -> Action.Flash.Primary
R.id.method_inactive_slot -> Action.Flash.Secondary
else -> error("Unknown value")
}
} }

View File

@ -25,306 +25,217 @@
app:fitsSystemWindowsInsets="top|bottom" app:fitsSystemWindowsInsets="top|bottom"
tools:paddingTop="24dp"> tools:paddingTop="24dp">
<FrameLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingTop="@dimen/l_50">
<LinearLayout <com.google.android.material.card.MaterialCardView
goneUnless="@{viewModel.loaded &amp;&amp; viewModel.progress &lt;= 0}" style="@style/WidgetFoundation.Card"
gone="@{viewModel.skipOptions}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingTop="@dimen/l_50">
<com.google.android.material.card.MaterialCardView
style="@style/WidgetFoundation.Card"
gone="@{viewModel.skipOptions}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:focusable="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
style="@style/WidgetFoundation.Icon"
isSelected="@{viewModel.step > 0}"
android:layout_marginStart="@dimen/l_25"
app:srcCompat="@drawable/ic_check_circle_md2" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/l1"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/install_options_title"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textStyle="bold" />
<Button
style="@style/WidgetFoundation.Button.Text"
gone="@{viewModel.step != 0}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.step(1)}"
android:text="@string/install_next" />
</LinearLayout>
<LinearLayout
gone="@{viewModel.step != 0}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1"
android:layout_marginTop="@dimen/l_50"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l_50"
android:orientation="vertical"
android:paddingStart="3dp"
android:paddingEnd="3dp"
tools:layout_gravity="center">
<CheckBox
style="@style/WidgetFoundation.Checkbox"
gone="@{Info.isSAR}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@={Config.keepVerity}"
android:text="@string/keep_dm_verity"
tools:checked="true" />
<CheckBox
style="@style/WidgetFoundation.Checkbox"
goneUnless="@{Info.isFDE}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@={Config.keepEnc}"
android:text="@string/keep_force_encryption"
app:tint="?colorPrimary" />
<CheckBox
style="@style/WidgetFoundation.Checkbox"
gone="@{Info.ramdisk}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@={Config.recovery}"
android:text="@string/recovery_mode"
app:tint="?colorPrimary" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
style="@style/WidgetFoundation.Card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1"
android:layout_marginTop="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:focusable="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
style="@style/WidgetFoundation.Icon"
isSelected="@{viewModel.step > 1}"
android:layout_marginStart="@dimen/l_25"
app:srcCompat="@drawable/ic_check_circle_md2" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/l1"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/install_method_title"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textStyle="bold" />
<Button
style="@style/WidgetFoundation.Button.Text"
gone="@{viewModel.step != 1}"
isEnabled="@{viewModel.method == @id/method_patch ? viewModel.data != null : viewModel.method != -1}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.install()}"
android:text="@string/install_start"
app:icon="@drawable/ic_forth_md2"
app:iconGravity="textEnd" />
</LinearLayout>
<RadioGroup
gone="@{viewModel.step != 1}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1"
android:layout_marginTop="@dimen/l_50"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l_50"
android:checkedButton="@={viewModel.method}"
android:paddingStart="3dp"
android:paddingEnd="3dp">
<RadioButton
android:id="@+id/method_download"
style="@style/WidgetFoundation.RadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/download_zip_only"
tools:checked="true" />
<RadioButton
android:id="@+id/method_patch"
style="@style/WidgetFoundation.RadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/select_patch_file" />
<RadioButton
android:id="@+id/method_direct"
style="@style/WidgetFoundation.RadioButton"
gone="@{!viewModel.rooted}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/direct_install" />
<RadioButton
android:id="@+id/method_inactive_slot"
style="@style/WidgetFoundation.RadioButton"
gone="@{viewModel.noSecondSlot}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/install_inactive_slot" />
</RadioGroup>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
style="@style/WidgetFoundation.Card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/l1"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:focusable="false">
<TextView
android:id="@+id/release_notes"
markdownText="@{viewModel.notes}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textAppearance="@style/AppearanceFoundation.Caption"
android:visibility="gone"
tools:maxLines="5"
tools:text="@tools:sample/lorem/random"
tools:visibility="visible" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
goneUnless="@{viewModel.loading}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginStart="@dimen/l1" android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1" android:layout_marginEnd="@dimen/l1"
android:gravity="center" android:focusable="false">
android:orientation="vertical">
<TextView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:orientation="vertical">
android:text="@string/loading"
android:textAppearance="@style/AppearanceFoundation.Title" />
<FrameLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/l1"> android:orientation="horizontal">
<ProgressBar <ImageView
style="@style/WidgetFoundation.ProgressBar" style="@style/WidgetFoundation.Icon"
invisible="@{viewModel.progress &lt;= 0}" isSelected="@{viewModel.step > 0}"
android:layout_width="100dp" android:layout_marginStart="@dimen/l_25"
android:layout_gravity="center" app:srcCompat="@drawable/ic_check_circle_md2" />
android:progress="@{viewModel.progress}" />
<ProgressBar <TextView
style="@style/WidgetFoundation.ProgressBar.Indeterminate" android:layout_width="0dp"
invisibleUnless="@{viewModel.progress &lt;= 0}" android:layout_height="match_parent"
android:layout_width="100dp" android:layout_marginStart="@dimen/l1"
android:layout_gravity="center" /> android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/install_options_title"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textStyle="bold" />
</FrameLayout> <Button
style="@style/WidgetFoundation.Button.Text"
gone="@{viewModel.step != 0}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.step(1)}"
android:text="@string/install_next" />
</LinearLayout>
</LinearLayout> <LinearLayout
gone="@{viewModel.step != 0}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1"
android:layout_marginTop="@dimen/l_50"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l_50"
android:orientation="vertical"
android:paddingStart="3dp"
android:paddingEnd="3dp"
tools:layout_gravity="center">
<LinearLayout <CheckBox
goneUnless="@{viewModel.loaded &amp;&amp; viewModel.progress >= 100}" style="@style/WidgetFoundation.Checkbox"
gone="@{Info.isSAR}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@={Config.keepVerity}"
android:text="@string/keep_dm_verity"
tools:checked="true" />
<CheckBox
style="@style/WidgetFoundation.Checkbox"
goneUnless="@{Info.isFDE}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@={Config.keepEnc}"
android:text="@string/keep_force_encryption"
app:tint="?colorPrimary" />
<CheckBox
style="@style/WidgetFoundation.Checkbox"
gone="@{Info.ramdisk}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@={Config.recovery}"
android:text="@string/recovery_mode"
app:tint="?colorPrimary" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
style="@style/WidgetFoundation.Card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginStart="@dimen/l1"
android:gravity="center" android:layout_marginTop="@dimen/l1"
android:orientation="vertical"> android:layout_marginEnd="@dimen/l1"
android:focusable="false">
<TextView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:orientation="vertical">
android:text="@string/download_complete"
android:textAppearance="@style/AppearanceFoundation.Title" />
</LinearLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout <ImageView
goneUnless="@{viewModel.loadFailed}" style="@style/WidgetFoundation.Icon"
isSelected="@{viewModel.step > 1}"
android:layout_marginStart="@dimen/l_25"
app:srcCompat="@drawable/ic_check_circle_md2" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/l1"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/install_method_title"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textStyle="bold" />
<Button
style="@style/WidgetFoundation.Button.Text"
gone="@{viewModel.step != 1}"
isEnabled="@{viewModel.method == @id/method_patch ? viewModel.data != null : viewModel.method != -1}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.install()}"
android:text="@string/install_start"
app:icon="@drawable/ic_forth_md2"
app:iconGravity="textEnd" />
</LinearLayout>
<RadioGroup
gone="@{viewModel.step != 1}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1"
android:layout_marginTop="@dimen/l_50"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l_50"
android:checkedButton="@={viewModel.method}"
android:paddingStart="3dp"
android:paddingEnd="3dp">
<RadioButton
android:id="@+id/method_patch"
style="@style/WidgetFoundation.RadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/select_patch_file" />
<RadioButton
android:id="@+id/method_direct"
style="@style/WidgetFoundation.RadioButton"
gone="@{!viewModel.rooted}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/direct_install" />
<RadioButton
android:id="@+id/method_inactive_slot"
style="@style/WidgetFoundation.RadioButton"
gone="@{viewModel.noSecondSlot}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/install_inactive_slot" />
</RadioGroup>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
style="@style/WidgetFoundation.Card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginTop="@dimen/l1"
android:gravity="center" android:layout_marginStart="@dimen/l1"
android:orientation="vertical"> android:layout_marginEnd="@dimen/l1"
android:focusable="false">
<TextView <TextView
android:id="@+id/release_notes"
markdownText="@{viewModel.notes}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:layout_margin="15dp"
android:text="@string/download_file_error" android:textAppearance="@style/AppearanceFoundation.Caption"
android:textAppearance="@style/AppearanceFoundation.Title" /> android:visibility="gone"
tools:maxLines="5"
tools:text="@tools:sample/lorem/random"
tools:visibility="visible" />
</LinearLayout> </com.google.android.material.card.MaterialCardView>
</FrameLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@ -49,10 +49,6 @@
android:label="FlashFragment" android:label="FlashFragment"
tools:layout="@layout/fragment_flash_md2"> tools:layout="@layout/fragment_flash_md2">
<argument
android:name="installer"
app:argType="android.net.Uri" />
<argument <argument
android:name="action" android:name="action"
app:argType="string" /> app:argType="string" />

View File

@ -1 +0,0 @@
/util_functions.sh

View File

@ -13,21 +13,35 @@ env_check() {
return 0 return 0
} }
fix_env() { cp_readlink() {
cd $MAGISKBIN if [ -z $2 ]; then
PATH=/system/bin /system/bin/sh update-binary -x cd $1
./busybox rm -f update-binary magisk.apk else
./busybox chmod -R 755 . cp -af $1/. $2
./magiskinit -x magisk magisk cd $2
fi
for file in *; do
if [ -L $file ]; then
local full=$(readlink -f $file)
rm $file
cp -af $full $file
fi
done
chmod -R 755 .
cd / cd /
} }
direct_install() { fix_env() {
rm -rf $MAGISKBIN/* 2>/dev/null # Cleanup and make dirs
rm -rf $MAGISKBIN/*
mkdir -p $MAGISKBIN 2>/dev/null mkdir -p $MAGISKBIN 2>/dev/null
chmod 700 $NVBASE chmod 700 $NVBASE
cp -af $1/. $MAGISKBIN cp -af $1/. $MAGISKBIN
rm -f $MAGISKBIN/new-boot.img rm -rf $1
chown -R 0:0 $MAGISKBIN
}
direct_install() {
echo "- Flashing new boot image" echo "- Flashing new boot image"
flash_image $1/new-boot.img $2 flash_image $1/new-boot.img $2
case $? in case $? in
@ -40,10 +54,20 @@ direct_install() {
return 2 return 2
;; ;;
esac esac
rm -rf $1
rm -f $1/new-boot.img
fix_env $1
return 0 return 0
} }
run_uninstaller() {
rm -rf /dev/tmp
mkdir -p /dev/tmp/install
unzip -o "$1" "assets/*" "lib/*" -d /dev/tmp/install
INSTALLER=/dev/tmp/install sh /dev/tmp/install/assets/magisk_uninstaller.sh dummy 1 "$1"
}
restore_imgs() { restore_imgs() {
[ -z $SHA1 ] && return 1 [ -z $SHA1 ] && return 1
local BACKUPDIR=/data/magisk_backup_$SHA1 local BACKUPDIR=/data/magisk_backup_$SHA1

View File

@ -18,7 +18,7 @@ buildscript {
extra["vNav"] = vNav extra["vNav"] = vNav
dependencies { dependencies {
classpath("com.android.tools.build:gradle:4.1.1") classpath("com.android.tools.build:gradle:4.1.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21")
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:${vNav}") classpath("androidx.navigation:navigation-safe-args-gradle-plugin:${vNav}")

144
build.py
View File

@ -222,7 +222,7 @@ def sign_zip(unsigned):
msg = '* Signing APK' msg = '* Signing APK'
apksigner = op.join(find_build_tools(), 'apksigner') apksigner = op.join(find_build_tools(), 'apksigner')
execArgs = [apksigner, 'sign', exec_args = [apksigner, 'sign',
'--ks', config['keyStore'], '--ks', config['keyStore'],
'--ks-pass', f'pass:{config["keyStorePass"]}', '--ks-pass', f'pass:{config["keyStorePass"]}',
'--ks-key-alias', config['keyAlias'], '--ks-key-alias', config['keyAlias'],
@ -232,14 +232,14 @@ def sign_zip(unsigned):
if unsigned.endswith('.zip'): if unsigned.endswith('.zip'):
msg = '* Signing zip' msg = '* Signing zip'
execArgs.extend(['--min-sdk-version', '17', exec_args.extend(['--min-sdk-version', '17',
'--v2-signing-enabled', 'false', '--v2-signing-enabled', 'false',
'--v3-signing-enabled', 'false']) '--v3-signing-enabled', 'false'])
execArgs.append(unsigned) exec_args.append(unsigned)
header(msg) header(msg)
proc = execv(execArgs) proc = execv(exec_args)
if proc.returncode != 0: if proc.returncode != 0:
error('Signing failed!') error('Signing failed!')
@ -254,29 +254,6 @@ def binary_dump(src, out, var_name):
out.flush() out.flush()
def gen_update_binary():
bs = 1024
update_bin = bytearray(bs)
file = op.join('native', 'out', 'x86', 'busybox')
with open(file, 'rb') as f:
x86_bb = f.read()
file = op.join('native', 'out', 'armeabi-v7a', 'busybox')
with open(file, 'rb') as f:
arm_bb = f.read()
file = op.join('scripts', 'update_binary.sh')
with open(file, 'rb') as f:
script = f.read()
# Align x86 busybox to bs
blk_cnt = (len(x86_bb) - 1) // bs + 1
script = script.replace(b'__X86_CNT__', b'%d' % blk_cnt)
update_bin[:len(script)] = script
update_bin.extend(x86_bb)
# Padding for alignment
update_bin.extend(b'\0' * (blk_cnt * bs - len(x86_bb)))
update_bin.extend(arm_bb)
return update_bin
def run_ndk_build(flags): def run_ndk_build(flags):
os.chdir('native') os.chdir('native')
proc = system(f'{ndk_build} {base_flags} {flags} -j{cpu_count}') proc = system(f'{ndk_build} {base_flags} {flags} -j{cpu_count}')
@ -407,96 +384,20 @@ def build_snet(args):
header('Output: ' + target) header('Output: ' + target)
def zip_main(args):
header('* Packing Flashable Zip')
if config['prettyName']:
name = f'Magisk-v{config["version"]}.zip'
elif args.release:
name = 'magisk-release.zip'
else:
name = 'magisk-debug.zip'
output = op.join(config['outdir'], name)
with zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf:
# update-binary
target = op.join('META-INF', 'com', 'google',
'android', 'update-binary')
vprint('zip: ' + target)
zipf.writestr(target, gen_update_binary())
# updater-script
source = op.join('scripts', 'flash_script.sh')
target = op.join('META-INF', 'com', 'google',
'android', 'updater-script')
zip_with_msg(zipf, source, target)
# Binaries
for lib_dir, zip_dir in zip(archs, ('arm', 'x86')):
for binary in ['magiskinit', 'magiskboot', 'magisk']:
source = op.join('native', 'out', lib_dir, binary)
target = op.join(zip_dir, 'magisk32' if binary == 'magisk' else binary)
zip_with_msg(zipf, source, target)
for lib_dir, zip_dir in zip(arch64, ('arm', 'x86')):
source = op.join('native', 'out', lib_dir, 'magisk')
target = op.join(zip_dir, 'magisk64')
zip_with_msg(zipf, source, target)
# APK
source = op.join(
config['outdir'], 'app-release.apk' if args.release else 'app-debug.apk')
target = op.join('common', 'magisk.apk')
zip_with_msg(zipf, source, target)
# boot_patch.sh
source = op.join('scripts', 'boot_patch.sh')
target = op.join('common', 'boot_patch.sh')
zip_with_msg(zipf, source, target)
# util_functions.sh
source = op.join('scripts', 'util_functions.sh')
with open(source, 'r') as script:
# Add version info util_functions.sh
util_func = script.read().replace(
'#MAGISK_VERSION_STUB',
f'MAGISK_VER="{config["version"]}"\nMAGISK_VER_CODE={config["versionCode"]}')
target = op.join('common', 'util_functions.sh')
vprint(f'zip: {source} -> {target}')
zipf.writestr(target, util_func)
# addon.d.sh
source = op.join('scripts', 'addon.d.sh')
target = op.join('common', 'addon.d.sh')
zip_with_msg(zipf, source, target)
# chromeos
for tool in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']:
if tool == 'futility':
source = op.join('tools', tool)
else:
source = op.join('tools', 'keys', tool)
target = op.join('chromeos', tool)
zip_with_msg(zipf, source, target)
# End of zipping
sign_zip(output)
header('Output: ' + output)
def zip_uninstaller(args): def zip_uninstaller(args):
header('* Packing Uninstaller Zip') header('* Packing Uninstaller Zip')
datestr = datetime.datetime.now().strftime("%Y%m%d") datestr = f'{datetime.datetime.now():%Y%m%d}'
name = f'Magisk-uninstaller-{datestr}.zip' if config['prettyName'] else 'magisk-uninstaller.zip' name = f'Magisk-uninstaller-{datestr}.zip' if config['prettyName'] else 'magisk-uninstaller.zip'
output = op.join(config['outdir'], name) output = op.join(config['outdir'], name)
with zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf: with zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf:
# update-binary # update-binary
source = op.join('scripts', 'update_binary.sh')
target = op.join('META-INF', 'com', 'google', target = op.join('META-INF', 'com', 'google',
'android', 'update-binary') 'android', 'update-binary')
vprint('zip: ' + target) zip_with_msg(zipf, source, target)
zipf.writestr(target, gen_update_binary())
# updater-script # updater-script
source = op.join('scripts', 'magisk_uninstaller.sh') source = op.join('scripts', 'magisk_uninstaller.sh')
target = op.join('META-INF', 'com', 'google', target = op.join('META-INF', 'com', 'google',
@ -504,17 +405,23 @@ def zip_uninstaller(args):
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# Binaries # Binaries
for lib_dir, zip_dir in [('armeabi-v7a', 'arm'), ('x86', 'x86')]: for exe in ('busybox', 'magiskboot'):
source = op.join('native', 'out', lib_dir, 'magiskboot') for arch in archs:
target = op.join(zip_dir, 'magiskboot') source = op.join('native', 'out', arch, exe)
zip_with_msg(zipf, source, target) target = op.join('lib', arch, f'lib{exe}.so')
zip_with_msg(zipf, source, target)
# util_functions.sh # util_functions.sh
source = op.join('scripts', 'util_functions.sh') source = op.join('scripts', 'util_functions.sh')
with open(source, 'r') as script: with open(source, 'r') as script:
target = op.join('util_functions.sh') # Add version info util_functions.sh
util_func = script.read().replace(
'#MAGISK_VERSION_STUB',
f'MAGISK_VER="{config["version"]}"\n'
f'MAGISK_VER_CODE={config["versionCode"]}')
target = op.join('assets', 'util_functions.sh')
vprint(f'zip: {source} -> {target}') vprint(f'zip: {source} -> {target}')
zipf.writestr(target, script.read()) zipf.writestr(target, util_func)
# chromeos # chromeos
for tool in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']: for tool in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']:
@ -522,7 +429,7 @@ def zip_uninstaller(args):
source = op.join('tools', tool) source = op.join('tools', tool)
else: else:
source = op.join('tools', 'keys', tool) source = op.join('tools', 'keys', tool)
target = op.join('chromeos', tool) target = op.join('assets', 'chromeos', tool)
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# End of zipping # End of zipping
@ -597,9 +504,8 @@ def setup_ndk(args):
def build_all(args): def build_all(args):
vars(args)['target'] = [] vars(args)['target'] = []
build_stub(args) build_stub(args)
build_app(args)
build_binary(args) build_binary(args)
zip_main(args) build_app(args)
zip_uninstaller(args) zip_uninstaller(args)
@ -614,7 +520,7 @@ parser.add_argument('-c', '--config', default='config.prop',
subparsers = parser.add_subparsers(title='actions') subparsers = parser.add_subparsers(title='actions')
all_parser = subparsers.add_parser( all_parser = subparsers.add_parser(
'all', help='build binaries, apks, zips') 'all', help='build everything')
all_parser.set_defaults(func=build_all) all_parser.set_defaults(func=build_all)
binary_parser = subparsers.add_parser('binary', help='build binaries') binary_parser = subparsers.add_parser('binary', help='build binaries')
@ -636,10 +542,6 @@ snet_parser = subparsers.add_parser(
'snet', help='build snet extension') 'snet', help='build snet extension')
snet_parser.set_defaults(func=build_snet) snet_parser.set_defaults(func=build_snet)
zip_parser = subparsers.add_parser(
'zip', help='zip Magisk into a flashable zip')
zip_parser.set_defaults(func=zip_main)
un_parser = subparsers.add_parser( un_parser = subparsers.add_parser(
'uninstaller', help='create flashable uninstaller') 'uninstaller', help='create flashable uninstaller')
un_parser.set_defaults(func=zip_uninstaller) un_parser.set_defaults(func=zip_uninstaller)

View File

@ -18,12 +18,13 @@ object Config {
} }
fun contains(key: String) = get(key) != null fun contains(key: String) = get(key) != null
val appVersion: String get() = get("appVersion") ?: commitHash val version: String = get("version") ?: commitHash
val appVersionCode: Int get() = commitCount val versionCode: Int get() = get("magisk.versionCode")!!.toInt()
} }
class MagiskPlugin : Plugin<Project> { class MagiskPlugin : Plugin<Project> {
override fun apply(project: Project) { override fun apply(project: Project) {
project.rootProject.file("gradle.properties").inputStream().use { props.load(it) }
val configPath: String? by project val configPath: String? by project
val config = configPath?.let { File(it) } ?: project.rootProject.file("config.prop") val config = configPath?.let { File(it) } ?: project.rootProject.file("config.prop")
if (config.exists()) if (config.exists())

View File

@ -6,9 +6,6 @@
# The version name of Magisk. Default: git HEAD short SHA1 # The version name of Magisk. Default: git HEAD short SHA1
version=string version=string
# The version name of Magisk Manager. Default: git HEAD short SHA1
appVersion=string
# Output path. Default: out # Output path. Default: out
outdir=string outdir=string

View File

@ -1,8 +1,7 @@
#!/system/bin/sh #!/system/bin/sh
########################################################################################### #######################################################################################
#
# Magisk Boot Image Patcher # Magisk Boot Image Patcher
# by topjohnwu #######################################################################################
# #
# Usage: boot_patch.sh <bootimage> # Usage: boot_patch.sh <bootimage>
# #
@ -14,16 +13,17 @@
# File name Type Description # File name Type Description
# #
# boot_patch.sh script A script to patch boot image for Magisk. # boot_patch.sh script A script to patch boot image for Magisk.
# (this file) The script will use binaries and files in its same directory # (this file) The script will use files in its same
# to complete the patching process # directory to complete the patching process
# util_functions.sh script A script which hosts all functions required for this script # util_functions.sh script A script which hosts all functions required
# to work properly # for this script to work properly
# magiskinit binary The binary to replace /init; magisk binary embedded # magiskinit binary The binary to replace /init
# magisk(32/64) binary The magisk binaries
# magiskboot binary A tool to manipulate boot images # magiskboot binary A tool to manipulate boot images
# chromeos folder This folder includes all the utilities and keys to sign # chromeos folder This folder includes the utility and keys to sign
# (optional) chromeos boot images. Currently only used for Pixel C # (optional) chromeos boot images. Only used for Pixel C.
# #
########################################################################################### #######################################################################################
############ ############
# Functions # Functions
@ -50,7 +50,7 @@ getdir() {
if [ -z $SOURCEDMODE ]; then if [ -z $SOURCEDMODE ]; then
# Switch to the location of the script file # Switch to the location of the script file
cd "`getdir "${BASH_SOURCE:-$0}"`" cd "$(getdir "${BASH_SOURCE:-$0}")"
# Load utility functions # Load utility functions
. ./util_functions.sh . ./util_functions.sh
# Check if 64-bit # Check if 64-bit
@ -106,14 +106,14 @@ fi
case $((STATUS & 3)) in case $((STATUS & 3)) in
0 ) # Stock boot 0 ) # Stock boot
ui_print "- Stock boot image detected" ui_print "- Stock boot image detected"
SHA1=`./magiskboot sha1 "$BOOTIMAGE" 2>/dev/null` SHA1=$(./magiskboot sha1 "$BOOTIMAGE" 2>/dev/null)
cat $BOOTIMAGE > stock_boot.img cat $BOOTIMAGE > stock_boot.img
cp -af ramdisk.cpio ramdisk.cpio.orig 2>/dev/null cp -af ramdisk.cpio ramdisk.cpio.orig 2>/dev/null
;; ;;
1 ) # Magisk patched 1 ) # Magisk patched
ui_print "- Magisk patched boot image detected" ui_print "- Magisk patched boot image detected"
# Find SHA1 of stock boot image # Find SHA1 of stock boot image
[ -z $SHA1 ] && SHA1=`./magiskboot cpio ramdisk.cpio sha1 2>/dev/null` [ -z $SHA1 ] && SHA1=$(./magiskboot cpio ramdisk.cpio sha1 2>/dev/null)
./magiskboot cpio ramdisk.cpio restore ./magiskboot cpio ramdisk.cpio restore
cp -af ramdisk.cpio ramdisk.cpio.orig cp -af ramdisk.cpio ramdisk.cpio.orig
;; ;;

View File

@ -1,31 +1,26 @@
#MAGISK #MAGISK
############################################ ############################################
#
# Magisk Flash Script (updater-script) # Magisk Flash Script (updater-script)
# by topjohnwu
#
############################################ ############################################
############## ##############
# Preparation # Preparation
############## ##############
COMMONDIR=$INSTALLER/common
APK=$COMMONDIR/magisk.apk
CHROMEDIR=$INSTALLER/chromeos
# Default permissions # Default permissions
umask 022 umask 022
OUTFD=$2 OUTFD=$2
ZIP=$3 APK="$3"
COMMONDIR=$INSTALLER/assets
CHROMEDIR=$INSTALLER/assets/chromeos
if [ ! -f $COMMONDIR/util_functions.sh ]; then if [ ! -f $COMMONDIR/util_functions.sh ]; then
echo "! Unable to extract zip file!" echo "! Unable to extract zip file!"
exit 1 exit 1
fi fi
# Load utility fuctions # Load utility functions
. $COMMONDIR/util_functions.sh . $COMMONDIR/util_functions.sh
setup_flashable setup_flashable
@ -57,7 +52,11 @@ api_level_arch_detect
ui_print "- Device platform: $ARCH" ui_print "- Device platform: $ARCH"
BINDIR=$INSTALLER/$ARCH32 BINDIR=$INSTALLER/lib/$ARCH32
[ ! -d "$BINDIR" ] && BINDIR=$INSTALLER/lib/armeabi-v7a
cd $BINDIR
for file in lib*.so; do mv "$file" "${file:3:${#file}-6}"; done
cd /
chmod -R 755 $CHROMEDIR $BINDIR chmod -R 755 $CHROMEDIR $BINDIR
# Check if system root is installed and remove # Check if system root is installed and remove
@ -72,24 +71,22 @@ ui_print "- Constructing environment"
# Copy required files # Copy required files
rm -rf $MAGISKBIN/* 2>/dev/null rm -rf $MAGISKBIN/* 2>/dev/null
mkdir -p $MAGISKBIN 2>/dev/null mkdir -p $MAGISKBIN 2>/dev/null
cp -af $BINDIR/. $COMMONDIR/. $CHROMEDIR $BBBIN $MAGISKBIN cp -af $BINDIR/. $COMMONDIR/. $BBBIN $MAGISKBIN
chmod -R 755 $MAGISKBIN chmod -R 755 $MAGISKBIN
# addon.d # addon.d
if [ -d /system/addon.d ]; then if [ -d /system/addon.d ]; then
ui_print "- Adding addon.d survival script" ui_print "- Adding addon.d survival script"
blockdev --setrw /dev/block/mapper/system$SLOT 2>/dev/null blockdev --setrw /dev/block/mapper/system$SLOT 2>/dev/null
mount -o rw,remount /system mount -o rw,remount /system || mount -o rw,remount /
ADDOND=/system/addon.d/99-magisk.sh ADDOND=/system/addon.d/99-magisk.sh
cp -af $COMMONDIR/addon.d.sh $ADDOND cp -af $COMMONDIR/addon.d.sh $ADDOND
chmod 755 $ADDOND chmod 755 $ADDOND
fi fi
$BOOTMODE || recovery_actions ##################
# Image Patching
##################### ##################
# Boot/DTBO Patching
#####################
install_magisk install_magisk

View File

@ -1,72 +1,75 @@
#MAGISK #MAGISK
############################################ ############################################
# # Magisk Uninstaller (updater-script)
# Magisk Uninstaller
# by topjohnwu
#
############################################ ############################################
############## ##############
# Preparation # Preparation
############## ##############
# This path should work in any cases
TMPDIR=/dev/tmp
INSTALLER=$TMPDIR/install
CHROMEDIR=$INSTALLER/chromeos
# Default permissions # Default permissions
umask 022 umask 022
OUTFD=$2 OUTFD=$2
ZIP=$3 APK="$3"
COMMONDIR=$INSTALLER/assets
CHROMEDIR=$INSTALLER/assets/chromeos
if [ ! -f $INSTALLER/util_functions.sh ]; then if [ ! -f $COMMONDIR/util_functions.sh ]; then
echo "! Unable to extract zip file!" echo "! Unable to extract zip file!"
exit 1 exit 1
fi fi
# Load utility functions # Load utility functions
. $INSTALLER/util_functions.sh . $COMMONDIR/util_functions.sh
setup_flashable setup_flashable
print_title "Magisk Uninstaller" ############
# Detection
############
if echo $MAGISK_VER | grep -q '\.'; then
PRETTY_VER=$MAGISK_VER
else
PRETTY_VER="$MAGISK_VER($MAGISK_VER_CODE)"
fi
print_title "Magisk $PRETTY_VER Uninstaller"
is_mounted /data || mount /data || abort "! Unable to mount /data, please uninstall with Magisk Manager" is_mounted /data || mount /data || abort "! Unable to mount /data, please uninstall with Magisk Manager"
mount_partitions
check_data
$DATA_DE || abort "! Cannot access /data, please uninstall with Magisk Manager"
if ! $BOOTMODE; then if ! $BOOTMODE; then
# Mounting stuffs in recovery (best effort) # Mounting stuffs in recovery (best effort)
mount_name metadata /metadata mount_name metadata /metadata
mount_name "cache cac" /cache mount_name "cache cac" /cache
mount_name persist /persist mount_name persist /persist
fi fi
mount_partitions get_flags
find_boot_image
[ -z $BOOTIMAGE ] && abort "! Unable to detect target image"
ui_print "- Target image: $BOOTIMAGE"
# Detect version and architecture
api_level_arch_detect api_level_arch_detect
ui_print "- Device platform: $ARCH" ui_print "- Device platform: $ARCH"
MAGISKBIN=$INSTALLER/$ARCH32
mv $CHROMEDIR $MAGISKBIN
chmod -R 755 $MAGISKBIN
check_data BINDIR=$INSTALLER/lib/$ARCH32
$DATA_DE || abort "! Cannot access /data, please uninstall with Magisk Manager" [ ! -d "$BINDIR" ] && BINDIR=$INSTALLER/lib/armeabi-v7a
$BOOTMODE || recovery_actions cd $BINDIR
run_migrations for file in lib*.so; do mv "$file" "${file:3:${#file}-6}"; done
cd /
chmod -R 755 $CHROMEDIR $BINDIR
cp -af $CHROMEDIR/. $BINDIR/chromeos
############ ############
# Uninstall # Uninstall
############ ############
get_flags cd $BINDIR
find_boot_image
[ -e $BOOTIMAGE ] || abort "! Unable to detect boot image"
ui_print "- Found target image: $BOOTIMAGE"
[ -z $DTBOIMAGE ] || ui_print "- Found dtbo image: $DTBOIMAGE"
cd $MAGISKBIN
CHROMEOS=false CHROMEOS=false
@ -108,14 +111,14 @@ case $((STATUS & 3)) in
1 ) # Magisk patched 1 ) # Magisk patched
ui_print "- Magisk patched image detected" ui_print "- Magisk patched image detected"
# Find SHA1 of stock boot image # Find SHA1 of stock boot image
SHA1=`./magiskboot cpio ramdisk.cpio sha1 2>/dev/null` SHA1=$(./magiskboot cpio ramdisk.cpio sha1 2>/dev/null)
BACKUPDIR=/data/magisk_backup_$SHA1 BACKUPDIR=/data/magisk_backup_$SHA1
if [ -d $BACKUPDIR ]; then if [ -d $BACKUPDIR ]; then
ui_print "- Restoring stock boot image" ui_print "- Restoring stock boot image"
flash_image $BACKUPDIR/boot.img.gz $BOOTIMAGE flash_image $BACKUPDIR/boot.img.gz $BOOTIMAGE
for name in dtb dtbo dtbs; do for name in dtb dtbo dtbs; do
[ -f $BACKUPDIR/${name}.img.gz ] || continue [ -f $BACKUPDIR/${name}.img.gz ] || continue
IMAGE=`find_block $name$SLOT` IMAGE=$(find_block $name$SLOT)
[ -z $IMAGE ] && continue [ -z $IMAGE ] && continue
ui_print "- Restoring stock $name image" ui_print "- Restoring stock $name image"
flash_image $BACKUPDIR/${name}.img.gz $IMAGE flash_image $BACKUPDIR/${name}.img.gz $IMAGE
@ -124,7 +127,7 @@ case $((STATUS & 3)) in
ui_print "! Boot image backup unavailable" ui_print "! Boot image backup unavailable"
ui_print "- Restoring ramdisk with internal backup" ui_print "- Restoring ramdisk with internal backup"
./magiskboot cpio ramdisk.cpio restore ./magiskboot cpio ramdisk.cpio restore
if ! ./magiskboot cpio ramdisk.cpio "exists init.rc"; then if ! ./magiskboot cpio ramdisk.cpio "exists init"; then
# A only system-as-root # A only system-as-root
rm -f ramdisk.cpio rm -f ramdisk.cpio
fi fi
@ -148,10 +151,11 @@ rm -rf \
/data/adb/post-fs-data.d /data/adb/service.d /data/adb/modules* \ /data/adb/post-fs-data.d /data/adb/service.d /data/adb/modules* \
/data/unencrypted/magisk /metadata/magisk /persist/magisk /mnt/vendor/persist/magisk /data/unencrypted/magisk /metadata/magisk /persist/magisk /mnt/vendor/persist/magisk
if [ -f /system/addon.d/99-magisk.sh ]; then ADDOND=/system/addon.d/99-magisk.sh
if [ -f $ADDOND ]; then
blockdev --setrw /dev/block/mapper/system$SLOT 2>/dev/null blockdev --setrw /dev/block/mapper/system$SLOT 2>/dev/null
mount -o rw,remount /system || mount -o rw,remount / mount -o rw,remount /system || mount -o rw,remount /
rm -f /system/addon.d/99-magisk.sh rm -f $ADDOND
fi fi
cd / cd /
@ -163,7 +167,10 @@ if $BOOTMODE; then
ui_print "********************************************" ui_print "********************************************"
(sleep 8; /system/bin/reboot)& (sleep 8; /system/bin/reboot)&
else else
rm -rf /data/data/*magisk* /data/user*/*/*magisk* /data/app/*magisk* /data/app/*/*magisk* ui_print "********************************************"
ui_print " Magisk Manager will not be uninstalled"
ui_print " Please uninstall it manually after reboot"
ui_print "********************************************"
recovery_cleanup recovery_cleanup
ui_print "- Done" ui_print "- Done"
fi fi

View File

@ -1,37 +1,18 @@
#!/sbin/sh #!/sbin/sh
X86_CNT=__X86_CNT__
extract_bb() { TMPDIR=/dev/tmp
touch "$BBBIN" rm -rf $TMPDIR
chmod 755 "$BBBIN" mkdir -p $TMPDIR 2>/dev/null
dd if="$0" of="$BBBIN" bs=1024 skip=1 count=$X86_CNT
"$BBBIN" >/dev/null 2>&1 || dd if="$0" of="$BBBIN" bs=1024 skip=$(($X86_CNT + 1)) export BBBIN=$TMPDIR/busybox
} unzip -o "$3" lib/x86/libbusybox.so lib/armeabi-v7a/libbusybox.so -d $TMPDIR >&2
setup_bb() { chmod -R 755 $TMPDIR/lib
mkdir -p $TMPDIR 2>/dev/null mv -f $TMPDIR/lib/x86/libbusybox.so $BBBIN
BBBIN=$TMPDIR/busybox $BBBIN >/dev/null 2>&1 || mv -f $TMPDIR/lib/armeabi-v7a/libbusybox.so $BBBIN
extract_bb $BBBIN rm -rf $TMPDIR/lib
}
export BBBIN export INSTALLER=$TMPDIR/install
case "$1" in $BBBIN mkdir -p $INSTALLER
"extract"|"-x") $BBBIN unzip -o "$3" "assets/*" "lib/*" "META-INF/com/google/*" -x "lib/*/libbusybox.so" -d $INSTALLER >&2
BBBIN=./busybox export ASH_STANDALONE=1
[ -z "$2" ] || BBBIN="$2" exec $BBBIN sh "$INSTALLER/META-INF/com/google/android/updater-script" "$@"
extract_bb
;;
"sh")
TMPDIR=.
setup_bb
shift
exec ./busybox sh -o standalone "$@"
;;
*)
TMPDIR=/dev/tmp
rm -rf $TMPDIR 2>/dev/null
setup_bb
export INSTALLER=$TMPDIR/install
$BBBIN mkdir -p $INSTALLER
$BBBIN unzip -o "$3" -d $INSTALLER >&2
exec $BBBIN sh -o standalone $INSTALLER/META-INF/com/google/android/updater-script "$@"
;;
esac
exit

View File

@ -1,8 +1,5 @@
############################################ ############################################
#
# Magisk General Utility Functions # Magisk General Utility Functions
# by topjohnwu
#
############################################ ############################################
#MAGISK_VERSION_STUB #MAGISK_VERSION_STUB
@ -33,7 +30,7 @@ grep_prop() {
shift shift
local FILES=$@ local FILES=$@
[ -z "$FILES" ] && FILES='/system/build.prop' [ -z "$FILES" ] && FILES='/system/build.prop'
cat $FILES | dos2unix | sed -n "$REGEX" 2>/dev/null | head -n 1 cat $FILES 2>/dev/null | dos2unix | sed -n "$REGEX" | head -n 1
} }
getvar() { getvar() {
@ -42,11 +39,11 @@ getvar() {
local PROPPATH='/data/.magisk /cache/.magisk' local PROPPATH='/data/.magisk /cache/.magisk'
[ -n $MAGISKTMP ] && PROPPATH="$MAGISKTMP/config $PROPPATH" [ -n $MAGISKTMP ] && PROPPATH="$MAGISKTMP/config $PROPPATH"
VALUE=$(grep_prop $VARNAME $PROPPATH) VALUE=$(grep_prop $VARNAME $PROPPATH)
[ ! -z $VALUE ] && eval $VARNAME=\$VALUE [ -n $VALUE ] && eval $VARNAME=\$VALUE
} }
is_mounted() { is_mounted() {
grep -q " `readlink -f $1` " /proc/mounts 2>/dev/null grep -q " $(readlink -f $1) " /proc/mounts 2>/dev/null
return $? return $?
} }
@ -96,6 +93,7 @@ setup_flashable() {
fi fi
done done
fi fi
recovery_actions
} }
ensure_bb() { ensure_bb() {
@ -309,51 +307,57 @@ mount_apex() {
$BOOTMODE || [ ! -d /system/apex ] && return $BOOTMODE || [ ! -d /system/apex ] && return
local APEX DEST local APEX DEST
setup_mntpoint /apex setup_mntpoint /apex
mount -t tmpfs tmpfs /apex -o mode=755
local PATTERN='s/.*"name":[^"]*"\([^"]*\).*/\1/p'
for APEX in /system/apex/*; do for APEX in /system/apex/*; do
DEST=/apex/$(basename $APEX .apex)
[ "$DEST" == /apex/com.android.runtime.release ] && DEST=/apex/com.android.runtime
mkdir -p $DEST 2>/dev/null
if [ -f $APEX ]; then if [ -f $APEX ]; then
# APEX APKs, extract and loop mount # APEX APKs, extract and loop mount
unzip -qo $APEX apex_payload.img -d /apex unzip -qo $APEX apex_payload.img -d /apex
loop_setup apex_payload.img DEST=/apex/$(unzip -qp $APEX apex_manifest.pb | strings | head -n 1)
[ -z $DEST ] && DEST=/apex/$(unzip -qp $APEX apex_manifest.json | sed -n $PATTERN)
[ -z $DEST ] && continue
mkdir -p $DEST
loop_setup /apex/apex_payload.img
if [ ! -z $LOOPDEV ]; then if [ ! -z $LOOPDEV ]; then
ui_print "- Mounting $DEST" ui_print "- Mounting $DEST"
mount -t ext4 -o ro,noatime $LOOPDEV $DEST mount -t ext4 -o ro,noatime $LOOPDEV $DEST
fi fi
rm -f apex_payload.img rm -f /apex/apex_payload.img
elif [ -d $APEX ]; then elif [ -d $APEX ]; then
# APEX folders, bind mount directory # APEX folders, bind mount directory
if [ -f $APEX/apex_manifest.json ]; then
DEST=/apex/$(sed -n $PATTERN $APEX/apex_manifest.json)
elif [ -f $APEX/apex_manifest.pb ]; then
DEST=/apex/$(strings apex_manifest.pb | head -n 1)
else
continue
fi
mkdir -p $DEST
ui_print "- Mounting $DEST" ui_print "- Mounting $DEST"
mount -o bind $APEX $DEST mount -o bind $APEX $DEST
fi fi
done done
export ANDROID_RUNTIME_ROOT=/apex/com.android.runtime export ANDROID_RUNTIME_ROOT=/apex/com.android.runtime
export ANDROID_TZDATA_ROOT=/apex/com.android.tzdata export ANDROID_TZDATA_ROOT=/apex/com.android.tzdata
local APEXRJPATH=/apex/com.android.runtime/javalib export ANDROID_ART_ROOT=/apex/com.android.art
local SYSFRAME=/system/framework export ANDROID_I18N_ROOT=/apex/com.android.i18n
export BOOTCLASSPATH=\ local APEXJARS=$(find /apex -name '*.jar' | sort | tr '\n' ':')
$APEXRJPATH/core-oj.jar:$APEXRJPATH/core-libart.jar:$APEXRJPATH/okhttp.jar:\ local FWK=/system/framework
$APEXRJPATH/bouncycastle.jar:$APEXRJPATH/apache-xml.jar:$SYSFRAME/framework.jar:\ export BOOTCLASSPATH=${APEXJARS}\
$SYSFRAME/ext.jar:$SYSFRAME/telephony-common.jar:$SYSFRAME/voip-common.jar:\ $FWK/framework.jar:$FWK/ext.jar:$FWK/telephony-common.jar:\
$SYSFRAME/ims-common.jar:$SYSFRAME/android.test.base.jar:$SYSFRAME/telephony-ext.jar:\ $FWK/voip-common.jar:$FWK/ims-common.jar:$FWK/telephony-ext.jar
/apex/com.android.conscrypt/javalib/conscrypt.jar:\
/apex/com.android.media/javalib/updatable-media.jar
} }
umount_apex() { umount_apex() {
[ -d /apex ] || return [ -d /apex ] || return
local DEST SRC umount -l /apex
for DEST in /apex/*; do for loop in /dev/block/loop*; do
[ "$DEST" = '/apex/*' ] && break losetup -d $loop 2>/dev/null
SRC=$(grep $DEST /proc/mounts | awk '{ print $1 }')
umount -l $DEST
# Detach loop device just in case
losetup -d $SRC 2>/dev/null
done done
rm -rf /apex
unset ANDROID_RUNTIME_ROOT unset ANDROID_RUNTIME_ROOT
unset ANDROID_TZDATA_ROOT unset ANDROID_TZDATA_ROOT
unset ANDROID_ART_ROOT
unset ANDROID_I18N_ROOT
unset BOOTCLASSPATH unset BOOTCLASSPATH
} }
@ -389,7 +393,7 @@ find_boot_image() {
BOOTIMAGE= BOOTIMAGE=
if $RECOVERYMODE; then if $RECOVERYMODE; then
BOOTIMAGE=`find_block recovery_ramdisk$SLOT recovery$SLOT sos` BOOTIMAGE=`find_block recovery_ramdisk$SLOT recovery$SLOT sos`
elif [ ! -z $SLOT ]; then elif [ -n $SLOT ]; then
BOOTIMAGE=`find_block ramdisk$SLOT recovery_ramdisk$SLOT boot$SLOT` BOOTIMAGE=`find_block ramdisk$SLOT recovery_ramdisk$SLOT boot$SLOT`
else else
BOOTIMAGE=`find_block ramdisk recovery_ramdisk kern-a android_boot kernel bootimg boot lnx boot_a` BOOTIMAGE=`find_block ramdisk recovery_ramdisk kern-a android_boot kernel bootimg boot lnx boot_a`
@ -402,7 +406,7 @@ find_boot_image() {
flash_image() { flash_image() {
case "$1" in case "$1" in
*.gz) CMD1="$MAGISKBIN/magiskboot decompress '$1' - 2>/dev/null";; *.gz) CMD1="gzip -d < '$1' 2>/dev/null";;
*) CMD1="cat '$1'";; *) CMD1="cat '$1'";;
esac esac
if $BOOTSIGNED; then if $BOOTSIGNED; then

View File

@ -3,12 +3,12 @@ plugins {
} }
android { android {
val canary = !Config.appVersion.contains(".") val canary = !Config.version.contains(".")
defaultConfig { defaultConfig {
applicationId = "com.topjohnwu.magisk" applicationId = "com.topjohnwu.magisk"
versionCode = 1 versionCode = 1
versionName = Config.appVersion versionName = Config.version
buildConfigField("int", "STUB_VERSION", "15") buildConfigField("int", "STUB_VERSION", "15")
buildConfigField("String", "DEV_CHANNEL", Config["DEV_CHANNEL"] ?: "null") buildConfigField("String", "DEV_CHANNEL", Config["DEV_CHANNEL"] ?: "null")
buildConfigField("boolean", "CANARY", if (canary) "true" else "false") buildConfigField("boolean", "CANARY", if (canary) "true" else "false")