Commit ec8fffe6 authored by topjohnwu's avatar topjohnwu

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
parent 61d52991
......@@ -6,6 +6,7 @@
app/release
*.hprof
.externalNativeBuild/
public.certificate.x509.pem
private.key.pk8
*.apk
src/main/assets
src/main/jniLibs
src/main/resources
import org.apache.tools.ant.filters.FixCrLfFilter
import java.io.PrintStream
plugins {
......@@ -22,8 +23,8 @@ android {
applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
versionName = Config.appVersion
versionCode = Config.appVersionCode
versionName = Config.version
versionCode = Config.versionCode
javaCompileOptions.annotationProcessorOptions.arguments(
mapOf("room.incremental" to "true")
......@@ -51,13 +52,14 @@ android {
}
packagingOptions {
exclude("/META-INF/**")
exclude("/META-INF/*")
exclude("/org/bouncycastle/**")
exclude("/kotlin/**")
exclude("/kotlinx/**")
exclude("/okhttp3/**")
exclude("/*.txt")
exclude("/*.bin")
doNotStrip("**/*.so")
}
kotlinOptions {
......@@ -65,10 +67,82 @@ android {
}
}
tasks["preBuild"]?.dependsOn(tasks.register("copyUtils", Copy::class) {
from(rootProject.file("scripts/util_functions.sh"))
into("src/main/res/raw")
})
val syncLibs by tasks.registering(Sync::class) {
into("src/main/jniLibs")
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 {
val keysDir = rootProject.file("tools/keys")
......@@ -178,6 +252,5 @@ dependencies {
implementation("androidx.transition:transition:1.3.1")
implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
implementation("com.google.android.material:material:1.2.1")
}
......@@ -13,6 +13,8 @@
android:icon="@drawable/ic_launcher"
android:name="a.e"
android:allowBackup="false"
android:multiArch="true"
android:extractNativeLibs="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
<!-- Splash -->
......
......@@ -19,6 +19,7 @@ import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import timber.log.Timber
import java.io.File
import kotlin.system.exitProcess
open class App() : Application() {
......@@ -61,6 +62,12 @@ open class App() : Application() {
val wrapped = impl.wrap()
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
startKoin {
androidContext(wrapped)
......
package com.topjohnwu.magisk.core
import android.os.Build
import android.os.Process
import java.io.File
@Suppress("DEPRECATION")
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
lateinit var MAGISKTMP: String
lateinit var NATIVE_LIB_DIR: File
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"
// Versions
......@@ -19,11 +36,9 @@ object Const {
val USER_ID = Process.myUid() / 100000
object Version {
const val MIN_VERSION = "v19.0"
const val MIN_VERCODE = 19000
const val MIN_VERSION = "v20.4"
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_2() = Info.env.magiskVersionCode >= 21200 || isCanary()
fun isCanary() = Info.env.magiskVersionCode % 100 != 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()
}
......@@ -8,7 +8,6 @@ import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.ForegroundTracker
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.ProgressInputStream
import com.topjohnwu.magisk.data.repository.NetworkService
......@@ -69,17 +68,14 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
// -- Download logic
private suspend fun Subject.startDownload() {
val skip = this is Subject.Magisk && file.checkSum("MD5", magisk.md5)
if (!skip) {
val stream = service.fetchFile(url).toProgressStream(this)
when (this) {
is Subject.Module -> // Download and process on-the-fly
stream.toModule(file, service.fetchInstaller().byteStream())
else -> {
withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) }
if (this is Subject.Manager)
handleAPK(this)
}
val stream = service.fetchFile(url).toProgressStream(this)
when (this) {
is Subject.Module -> // Download and process on-the-fly
stream.toModule(file, service.fetchInstaller().byteStream())
else -> {
withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) }
if (this is Subject.Manager)
handleAPK(this)
}
}
val newId = notifyFinish(this)
......
......@@ -7,11 +7,10 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.net.toFile
import com.topjohnwu.magisk.core.download.Action.*
import com.topjohnwu.magisk.core.download.Action.Flash.Secondary
import com.topjohnwu.magisk.core.download.Subject.*
import com.topjohnwu.magisk.core.download.Action.Flash
import com.topjohnwu.magisk.core.download.Subject.Manager
import com.topjohnwu.magisk.core.download.Subject.Module
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.tasks.EnvFixTask
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall
import kotlin.random.Random.Default.nextInt
......@@ -22,25 +21,12 @@ open class DownloadService : BaseDownloader() {
private val context get() = this
override suspend fun onFinish(subject: Subject, id: Int) = when (subject) {
is Magisk -> subject.onFinish(id)
is Module -> 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) {
is Flash -> FlashFragment.install(file, id)
Flash -> FlashFragment.install(file, id)
else -> Unit
}
......@@ -53,22 +39,13 @@ open class DownloadService : BaseDownloader() {
override fun Notification.Builder.setIntent(subject: Subject)
= when (subject) {
is Magisk -> setIntent(subject)
is Module -> 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)
= when (subject.action) {
is Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
else -> setContentIntent(Intent())
}
......
......@@ -5,7 +5,6 @@ import android.net.Uri
import android.os.Parcelable
import androidx.core.net.toUri
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.StubJson
import com.topjohnwu.magisk.core.model.module.OnlineModule
......@@ -53,57 +52,12 @@ sealed class Subject : Parcelable {
}
}
}
abstract class Magisk : Subject() {
val magisk: MagiskJson = Info.remote.magisk
@Parcelize
private class Internal(
override val action: 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)
}
}
}
sealed class Action : Parcelable {
@Parcelize
object Flash : Action()
@Parcelize
object Download : Action()
}
......@@ -2,14 +2,13 @@ package com.topjohnwu.magisk.core.tasks
import android.content.Context
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.utils.MediaStoreUtils.displayName
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
......@@ -25,61 +24,50 @@ open class FlashZip(
private val logs: MutableList<String>
): KoinComponent {
val context: Context by inject()
private val installFolder = File(context.cacheDir, "flash").apply {
if (!exists()) mkdirs()
}
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
}
private val context: Context by inject()
private val installDir = File(context.cacheDir, "flash")
private lateinit var zipFile: File
@Throws(IOException::class)
private fun flash(): Boolean {
console.add("- Copying zip to temp directory")
runCatching {
mUri.inputStream().writeTo(tmpFile)
}.getOrElse {
when (it) {
is FileNotFoundException -> console.add("! Invalid Uri")
is IOException -> console.add("! Cannot copy to cache")
installDir.deleteRecursively()
installDir.mkdirs()
zipFile = if (mUri.scheme == "file") {
mUri.toFile()
} else {
File(installDir, "install.zip").also {
console.add("- Copying zip to temp directory")
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 {
unzipAndCheck()
val isValid = runCatching {
zipFile.unzip(installDir, "META-INF/com/google/android", true)
val script = File(installDir, "updater-script")
script.readText().contains("#MAGISK")
}.getOrElse {
console.add("! Unzip error")
throw it
}
if (!isMagiskModule) {
console.add("! This zip is not a Magisk Module!")
if (!isValid) {
console.add("! This zip is not a Magisk module!")
return false
}
console.add("- Installing ${mUri.displayName}")
val parentFile = tmpFile.parent ?: return false
return Shell
.su(
"cd $parentFile",
"BOOTMODE=true sh update-binary dummy 1 $tmpFile"
)
.to(console, logs)
.exec().isSuccess
return Shell.su("sh $installDir/update-binary dummy 1 $zipFile")
.to(console, logs).exec().isSuccess
}
open suspend fun exec() = withContext(Dispatchers.IO) {
......@@ -94,25 +82,7 @@ open class FlashZip(
Timber.e(e)
false
} 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
}
}
}
......@@ -91,8 +91,7 @@ object HideAPK {
}
private suspend fun patchAndHide(context: Context, label: String): Boolean {
val dlStub = !isRunningAsStub && SDK_INT >= 28 && Const.Version.atLeast_20_2()
val src = if (dlStub) {
val src = if (!isRunningAsStub && SDK_INT >= 28) {
val stub = File(context.cacheDir, "stub.apk")
try {
svc.fetchFile(Info.remote.stub.link).byteStream().use {
......
......@@ -2,14 +2,17 @@ package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.os.Build
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ktx.deviceProtectedContext
import com.topjohnwu.magisk.ktx.rawResource
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import java.io.File
import java.util.jar.JarFile
class RootInit : Shell.Initializer() {
......@@ -17,31 +20,50 @@ class RootInit : Shell.Initializer() {
return init(context.wrap(), shell)
}
fun init(context: Context, shell: Shell): Boolean {
private fun init(context: Context, shell: Shell): Boolean {
fun fastCmd(cmd: String) = ShellUtils.fastCmd(shell, cmd)
fun getVar(name: String) = fastCmd("echo \$$name")
fun getBool(name: String) = getVar(name).toBoolean()
shell.newJob().apply {
add("export SDK_INT=${Build.VERSION.SDK_INT}")
if (Const.Version.atLeast_20_4()) {
add("export MAGISKTMP=\$(magisk --path)/.magisk")
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 {
add("export MAGISKTMP=/sbin/.magisk")
localBB = File(Const.NATIVE_LIB_DIR, "libbusybox.so")
}
if (Const.Version.atLeast_21_0()) {
add("export ASH_STANDALONE=1")
add("[ -x /data/adb/magisk/busybox ] && exec /data/adb/magisk/busybox sh")
if (!shell.isRoot) {
// Directly execute the file
add("exec $localBB sh")
} else {
add("export PATH=\"\$MAGISKTMP/busybox:\$PATH\"")
// 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.rawResource(R.raw.util_functions))
add(context.assets.open("util_functions.sh"))
}
add("mm_init")
}.exec()
fun fastCmd(cmd: String) = ShellUtils.fastCmd(shell, cmd)
fun getVar(name: String) = fastCmd("echo \$$name")
fun getBool(name: String) = getVar(name).toBoolean()
Const.MAGISKTMP = getVar("MAGISKTMP")
Info.isSAR = getBool("SYSTEM_ROOT")
Info.ramdisk = getBool("RAMDISKEXIST")
......
......@@ -110,7 +110,7 @@ class SelectModuleEvent : ViewEvent(), FragmentExecutor {
activity.startActivityForResult(intent, Const.ID.FETCH_ZIP) { code, intent ->
if (code == Activity.RESULT_OK && intent != null) {
intent.data?.also {
MainDirections.actionFlashFragment(it, Const.Value.FLASH_ZIP).navigate()
MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate()
}
}
}
......
......@@ -10,7 +10,9 @@ abstract class DialogEvent : ViewEvent(), ActivityExecutor {
protected lateinit var dialog: MagiskDialog
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)
......
package com.topjohnwu.magisk.events.dialog
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.lifecycle.lifecycleScope
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.download.Action.EnvFix
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject.Magisk
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.view.MagiskDialog
import kotlinx.coroutines.launch
class EnvFixDialog : DialogEvent() {
......@@ -24,20 +20,17 @@ class EnvFixDialog : DialogEvent() {
.applyMessage(R.string.setup_msg)
.resetButtons()
.cancellable(false)
val lbm = LocalBroadcastManager.getInstance(dialog.context)
lbm.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
(dialog.ownerActivity as BaseActivity).lifecycleScope.launch {
MagiskInstaller.FixEnv {
dialog.dismiss()
lbm.unregisterReceiver(this)
}
}, IntentFilter(DISMISS))
DownloadService.start(dialog.context, Magisk(EnvFix))
}.exec()
}
}
}
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.cancel
}
.let { Unit }
.let { }
companion object {
const val DISMISS = "com.topjohnwu.magisk.ENV_DONE"
......
......@@ -14,7 +14,7 @@ class ModuleInstallDialog(private val item: OnlineModule) : DialogEvent() {
with(dialog) {
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)
DownloadService.start(context, subject)
}
......
......@@ -4,9 +4,7 @@ import android.app.ProgressDialog
import android.widget.Toast
import com.topjohnwu.magisk.R
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.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell
......@@ -46,7 +44,7 @@ class UninstallDialog : DialogEvent() {
}
private fun completeUninstall() {
DownloadService.start(dialog.context, Subject.Magisk(Action.Uninstall))
FlashFragment.uninstall()
}
}
......@@ -23,6 +23,7 @@ import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.system.Os
import android.text.PrecomputedText
import android.view.View
import android.view.ViewGroup
......@@ -59,6 +60,20 @@ import java.lang.reflect.Array as JArray
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
@get:SuppressLint("InlinedApi")
......@@ -83,6 +98,11 @@ fun Context.getBitmap(id: Int): 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.startActivityWithRoot() {
......
......@@ -17,13 +17,12 @@ import com.topjohnwu.magisk.ui.MainActivity
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import com.topjohnwu.magisk.MainDirections.Companion.actionFlashFragment as toFlash
import com.topjohnwu.magisk.ui.flash.FlashFragmentArgs as args
class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() {
override val layoutRes = R.layout.fragment_flash_md2
override val viewModel by viewModel<FlashViewModel> {
parametersOf(args.fromBundle(requireArguments()))
parametersOf(FlashFragmentArgs.fromBundle(requireArguments()))
}
private var defaultOrientation = -1
......@@ -78,7 +77,7 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
companion object {
private fun createIntent(context: Context, args: args) =
private fun createIntent(context: Context, args: FlashFragmentArgs) =
NavDeepLinkBuilder(context)
.setGraph(R.navigation.main)
.setComponentName(MainActivity::class.java.cmp(context.packageName))
......@@ -91,59 +90,34 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
/* Flashing is understood as installing / flashing magisk itself */
fun flashIntent(context: Context, file: Uri, isSecondSlot: Boolean, id: Int = -1) = args(
installer = file,
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
fun flash(isSecondSlot: Boolean) = toFlash(
action = flashType(isSecondSlot)
).let { BaseUIActivity.postDirections(it) }
/* Patching is understood as injecting img files with magisk */
fun patchIntent(context: Context, file: Uri, uri: Uri, id: Int = -1) = args(
installer = file,
fun patch(uri: Uri) = toFlash(
action = Const.Value.PATCH_FILE,
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
additionalData = uri
).let { BaseUIActivity.postDirections(it) }
/* Uninstalling is understood as removing magisk entirely */
fun uninstallIntent(context: Context, file: Uri, id: Int = -1) = args(
installer = file,
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
fun uninstall() = toFlash(
action = Const.Value.UNINSTALL
).let { BaseUIActivity.postDirections(it) }
/* Installing is understood as flashing modules / zips */
fun installIntent(context: Context, file: Uri, id: Int = -1) = args(
installer = file,
fun installIntent(context: Context, file: Uri, id: Int = -1) = FlashFragmentArgs(
action = Const.Value.FLASH_ZIP,
additionalData = file,
dismissId = id
).let { createIntent(context, it) }
fun install(file: Uri, id: Int) = toFlash(
installer = file,
action = Const.Value.FLASH_ZIP,
additionalData = file,
dismissId = id
).let { BaseUIActivity.postDirections(it) }
}
......
......@@ -27,9 +27,7 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class FlashViewModel(
args: FlashFragmentArgs
) : BaseViewModel() {
class FlashViewModel(args: FlashFragmentArgs) : BaseViewModel() {
@get:Bindable
var showReboot = Shell.rootAccess()
......@@ -52,36 +50,35 @@ class FlashViewModel(
}
init {
args.dismissId.takeIf { it != -1 }?.also {
Notifications.mgr.cancel(it)
}
val (installer, action, uri) = args
startFlashing(installer, uri, action)
val (action, uri, id) = args
if (id != -1)
Notifications.mgr.cancel(id)
startFlashing(action, uri)
}
private fun startFlashing(installer: Uri, uri: Uri?, action: String) {
private fun startFlashing(action: String, uri: Uri?) {
viewModelScope.launch {
val result = when (action) {
Const.Value.FLASH_ZIP -> {
FlashZip(installer, outItems, logItems).exec()
FlashZip(uri!!, outItems, logItems).exec()
}
Const.Value.UNINSTALL -> {
showReboot = false
FlashZip.Uninstall(installer, outItems, logItems).exec()
MagiskInstaller.Uninstall(outItems, logItems).exec()
}
Const.Value.FLASH_MAGISK -> {
if (Info.isEmulator)
MagiskInstaller.Emulator(installer, outItems, logItems).exec()
MagiskInstaller.Emulator(outItems, logItems).exec()
else
MagiskInstaller.Direct(installer, outItems, logItems).exec()
MagiskInstaller.Direct(outItems, logItems).exec()
}
Const.Value.FLASH_INACTIVE_SLOT -> {
MagiskInstaller.SecondSlot(installer, outItems, logItems).exec()
MagiskInstaller.SecondSlot(outItems, logItems).exec()
}
Const.Value.PATCH_FILE -> {
uri ?: return@launch
showReboot = false
MagiskInstaller.Patch(installer, uri, outItems, logItems).exec()
MagiskInstaller.Patch(uri, outItems, logItems).exec()
}
else -> {
back()
......
......@@ -7,7 +7,6 @@ import android.view.ViewGroup
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.core.download.BaseDownloader
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
import com.topjohnwu.magisk.ktx.coroutineScope
import org.koin.androidx.viewmodel.ext.android.viewModel
......@@ -23,7 +22,6 @@ class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Bindi
// Allow markwon to run in viewmodel scope
binding.releaseNotes.coroutineScope = viewModel.viewModelScope
BaseDownloader.observeProgress(this, viewModel::onProgressUpdate)
}
override fun onCreateView(
......
......@@ -8,23 +8,19 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel
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.events.MagiskInstallFileEvent
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
import org.koin.core.get
import timber.log.Timber
import java.io.IOException
import kotlin.math.roundToInt
class InstallViewModel(
svc: NetworkService
) : BaseViewModel(State.LOADED) {
) : BaseViewModel() {
val isRooted = Shell.rootAccess()
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
var data: Uri? = null
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) {
step = nextStep
}
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
}
// ---
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")
}
}
......@@ -49,10 +49,6 @@
android:label="FlashFragment"
tools:layout="@layout/fragment_flash_md2">
<argument
android:name="installer"
app:argType="android.net.Uri" />
<argument
android:name="action"
app:argType="string" />
......
......@@ -13,21 +13,35 @@ env_check() {
return 0
}
fix_env() {
cd $MAGISKBIN
PATH=/system/bin /system/bin/sh update-binary -x
./busybox rm -f update-binary magisk.apk
./busybox chmod -R 755 .
./magiskinit -x magisk magisk
cp_readlink() {
if [ -z $2 ]; then
cd $1
else
cp -af $1/. $2
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 /
}
direct_install() {
rm -rf $MAGISKBIN/* 2>/dev/null
fix_env() {
# Cleanup and make dirs
rm -rf $MAGISKBIN/*
mkdir -p $MAGISKBIN 2>/dev/null
chmod 700 $NVBASE
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"
flash_image $1/new-boot.img $2
case $? in
......@@ -40,10 +54,20 @@ direct_install() {
return 2
;;
esac
rm -rf $1
rm -f $1/new-boot.img
fix_env $1
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() {
[ -z $SHA1 ] && return 1
local BACKUPDIR=/data/magisk_backup_$SHA1
......
......@@ -18,7 +18,7 @@ buildscript {
extra["vNav"] = vNav
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("androidx.navigation:navigation-safe-args-gradle-plugin:${vNav}")
......
......@@ -222,7 +222,7 @@ def sign_zip(unsigned):
msg = '* Signing APK'
apksigner = op.join(find_build_tools(), 'apksigner')
execArgs = [apksigner, 'sign',
exec_args = [apksigner, 'sign',
'--ks', config['keyStore'],
'--ks-pass', f'pass:{config["keyStorePass"]}',
'--ks-key-alias', config['keyAlias'],
......@@ -232,14 +232,14 @@ def sign_zip(unsigned):
if unsigned.endswith('.zip'):
msg = '* Signing zip'
execArgs.extend(['--min-sdk-version', '17',
exec_args.extend(['--min-sdk-version', '17',
'--v2-signing-enabled', 'false',
'--v3-signing-enabled', 'false'])
execArgs.append(unsigned)
exec_args.append(unsigned)
header(msg)
proc = execv(execArgs)
proc = execv(exec_args)
if proc.returncode != 0:
error('Signing failed!')
......@@ -254,29 +254,6 @@ def binary_dump(src, out, var_name):
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):
os.chdir('native')
proc = system(f'{ndk_build} {base_flags} {flags} -j{cpu_count}')
......@@ -407,114 +384,44 @@ def build_snet(args):
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'
def zip_uninstaller(args):
header('* Packing Uninstaller Zip')
datestr = f'{datetime.datetime.now():%Y%m%d}'
name = f'Magisk-uninstaller-{datestr}.zip' if config['prettyName'] else 'magisk-uninstaller.zip'
output = op.join(config['outdir'], name)
with zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf:
# update-binary
source = op.join('scripts', 'update_binary.sh')
target = op.join('META-INF', 'com', 'google',
'android', 'update-binary')
vprint('zip: ' + target)
zipf.writestr(target, gen_update_binary())
zip_with_msg(zipf, source, target)
# updater-script
source = op.join('scripts', 'flash_script.sh')
source = op.join('scripts', 'magisk_uninstaller.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)
for exe in ('busybox', 'magiskboot'):
for arch in archs:
source = op.join('native', 'out', arch, exe)
target = op.join('lib', arch, f'lib{exe}.so')
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')
f'MAGISK_VER="{config["version"]}"\n'
f'MAGISK_VER_CODE={config["versionCode"]}')
target = op.join('assets', '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):
header('* Packing Uninstaller Zip')
datestr = datetime.datetime.now().strftime("%Y%m%d")
name = f'Magisk-uninstaller-{datestr}.zip' if config['prettyName'] else 'magisk-uninstaller.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', 'magisk_uninstaller.sh')
target = op.join('META-INF', 'com', 'google',
'android', 'updater-script')
zip_with_msg(zipf, source, target)
# Binaries
for lib_dir, zip_dir in [('armeabi-v7a', 'arm'), ('x86', 'x86')]:
source = op.join('native', 'out', lib_dir, 'magiskboot')
target = op.join(zip_dir, 'magiskboot')
zip_with_msg(zipf, source, target)
# util_functions.sh
source = op.join('scripts', 'util_functions.sh')
with open(source, 'r') as script:
target = op.join('util_functions.sh')
vprint(f'zip: {source} -> {target}')
zipf.writestr(target, script.read())
# chromeos
for tool in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']:
......@@ -522,7 +429,7 @@ def zip_uninstaller(args):
source = op.join('tools', tool)
else:
source = op.join('tools', 'keys', tool)
target = op.join('chromeos', tool)
target = op.join('assets', 'chromeos', tool)
zip_with_msg(zipf, source, target)
# End of zipping
......@@ -597,9 +504,8 @@ def setup_ndk(args):
def build_all(args):
vars(args)['target'] = []
build_stub(args)
build_app(args)
build_binary(args)
zip_main(args)
build_app(args)
zip_uninstaller(args)
......@@ -614,7 +520,7 @@ parser.add_argument('-c', '--config', default='config.prop',
subparsers = parser.add_subparsers(title='actions')
all_parser = subparsers.add_parser(
'all', help='build binaries, apks, zips')
'all', help='build everything')
all_parser.set_defaults(func=build_all)
binary_parser = subparsers.add_parser('binary', help='build binaries')
......@@ -636,10 +542,6 @@ snet_parser = subparsers.add_parser(
'snet', help='build snet extension')
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(
'uninstaller', help='create flashable uninstaller')
un_parser.set_defaults(func=zip_uninstaller)
......
......@@ -18,12 +18,13 @@ object Config {
}
fun contains(key: String) = get(key) != null
val appVersion: String get() = get("appVersion") ?: commitHash
val appVersionCode: Int get() = commitCount
val version: String = get("version") ?: commitHash
val versionCode: Int get() = get("magisk.versionCode")!!.toInt()
}
class MagiskPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.rootProject.file("gradle.properties").inputStream().use { props.load(it) }
val configPath: String? by project
val config = configPath?.let { File(it) } ?: project.rootProject.file("config.prop")
if (config.exists())
......
......@@ -6,9 +6,6 @@
# The version name of Magisk. Default: git HEAD short SHA1
version=string
# The version name of Magisk Manager. Default: git HEAD short SHA1
appVersion=string
# Output path. Default: out
outdir=string
......
#!/system/bin/sh
###########################################################################################
#
#######################################################################################
# Magisk Boot Image Patcher
# by topjohnwu
#######################################################################################
#
# Usage: boot_patch.sh <bootimage>
#
......@@ -14,16 +13,17 @@
# File name Type Description
#
# 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
# to complete the patching process
# util_functions.sh script A script which hosts all functions required for this script
# to work properly
# magiskinit binary The binary to replace /init; magisk binary embedded
# (this file) The script will use files in its same
# directory to complete the patching process
# util_functions.sh script A script which hosts all functions required
# for this script to work properly
# magiskinit binary The binary to replace /init
# magisk(32/64) binary The magisk binaries
# magiskboot binary A tool to manipulate boot images
# chromeos folder This folder includes all the utilities and keys to sign
# (optional) chromeos boot images. Currently only used for Pixel C
# chromeos folder This folder includes the utility and keys to sign
# (optional) chromeos boot images. Only used for Pixel C.
#
###########################################################################################
#######################################################################################
############
# Functions
......@@ -50,7 +50,7 @@ getdir() {
if [ -z $SOURCEDMODE ]; then
# Switch to the location of the script file
cd "`getdir "${BASH_SOURCE:-$0}"`"
cd "$(getdir "${BASH_SOURCE:-$0}")"
# Load utility functions
. ./util_functions.sh
# Check if 64-bit
......@@ -106,14 +106,14 @@ fi
case $((STATUS & 3)) in
0 ) # Stock boot
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
cp -af ramdisk.cpio ramdisk.cpio.orig 2>/dev/null
;;
1 ) # Magisk patched
ui_print "- Magisk patched boot image detected"
# 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
cp -af ramdisk.cpio ramdisk.cpio.orig
;;
......
#MAGISK
############################################
#
# Magisk Flash Script (updater-script)
# by topjohnwu
#
############################################
##############
# Preparation
##############
COMMONDIR=$INSTALLER/common
APK=$COMMONDIR/magisk.apk
CHROMEDIR=$INSTALLER/chromeos
# Default permissions
umask 022
OUTFD=$2
ZIP=$3
APK="$3"
COMMONDIR=$INSTALLER/assets
CHROMEDIR=$INSTALLER/assets/chromeos
if [ ! -f $COMMONDIR/util_functions.sh ]; then
echo "! Unable to extract zip file!"
exit 1
fi
# Load utility fuctions
# Load utility functions
. $COMMONDIR/util_functions.sh
setup_flashable
......@@ -57,7 +52,11 @@ api_level_arch_detect
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
# Check if system root is installed and remove
......@@ -72,24 +71,22 @@ ui_print "- Constructing environment"
# Copy required files
rm -rf $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
# addon.d
if [ -d /system/addon.d ]; then
ui_print "- Adding addon.d survival script"
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
cp -af $COMMONDIR/addon.d.sh $ADDOND
chmod 755 $ADDOND
fi
$BOOTMODE || recovery_actions
#####################
# Boot/DTBO Patching
#####################
##################
# Image Patching
##################
install_magisk
......
#MAGISK
############################################
#
# Magisk Uninstaller
# by topjohnwu
#
# Magisk Uninstaller (updater-script)
############################################
##############
# Preparation
##############
# This path should work in any cases
TMPDIR=/dev/tmp
INSTALLER=$TMPDIR/install
CHROMEDIR=$INSTALLER/chromeos
# Default permissions
umask 022
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!"
exit 1
fi
# Load utility functions
. $INSTALLER/util_functions.sh
. $COMMONDIR/util_functions.sh
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"
mount_partitions
check_data
$DATA_DE || abort "! Cannot access /data, please uninstall with Magisk Manager"
if ! $BOOTMODE; then
# Mounting stuffs in recovery (best effort)
mount_name metadata /metadata
mount_name "cache cac" /cache
mount_name persist /persist
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
ui_print "- Device platform: $ARCH"
MAGISKBIN=$INSTALLER/$ARCH32
mv $CHROMEDIR $MAGISKBIN
chmod -R 755 $MAGISKBIN
check_data
$DATA_DE || abort "! Cannot access /data, please uninstall with Magisk Manager"
$BOOTMODE || recovery_actions
run_migrations
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
cp -af $CHROMEDIR/. $BINDIR/chromeos
############
# Uninstall
############
get_flags
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
cd $BINDIR
CHROMEOS=false
......@@ -108,14 +111,14 @@ case $((STATUS & 3)) in
1 ) # Magisk patched
ui_print "- Magisk patched image detected"
# 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
if [ -d $BACKUPDIR ]; then
ui_print "- Restoring stock boot image"
flash_image $BACKUPDIR/boot.img.gz $BOOTIMAGE
for name in dtb dtbo dtbs; do
[ -f $BACKUPDIR/${name}.img.gz ] || continue
IMAGE=`find_block $name$SLOT`
IMAGE=$(find_block $name$SLOT)
[ -z $IMAGE ] && continue
ui_print "- Restoring stock $name image"
flash_image $BACKUPDIR/${name}.img.gz $IMAGE
......@@ -124,7 +127,7 @@ case $((STATUS & 3)) in
ui_print "! Boot image backup unavailable"
ui_print "- Restoring ramdisk with internal backup"
./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
rm -f ramdisk.cpio
fi
......@@ -148,10 +151,11 @@ rm -rf \
/data/adb/post-fs-data.d /data/adb/service.d /data/adb/modules* \
/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
mount -o rw,remount /system || mount -o rw,remount /
rm -f /system/addon.d/99-magisk.sh
rm -f $ADDOND
fi
cd /
......@@ -163,7 +167,10 @@ if $BOOTMODE; then
ui_print "********************************************"
(sleep 8; /system/bin/reboot)&
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
ui_print "- Done"
fi
......
#!/sbin/sh
X86_CNT=__X86_CNT__
extract_bb() {
touch "$BBBIN"
chmod 755 "$BBBIN"
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))
}
setup_bb() {
mkdir -p $TMPDIR 2>/dev/null
BBBIN=$TMPDIR/busybox
extract_bb
}
export BBBIN
case "$1" in
"extract"|"-x")
BBBIN=./busybox
[ -z "$2" ] || BBBIN="$2"
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
TMPDIR=/dev/tmp
rm -rf $TMPDIR
mkdir -p $TMPDIR 2>/dev/null
export BBBIN=$TMPDIR/busybox
unzip -o "$3" lib/x86/libbusybox.so lib/armeabi-v7a/libbusybox.so -d $TMPDIR >&2
chmod -R 755 $TMPDIR/lib
mv -f $TMPDIR/lib/x86/libbusybox.so $BBBIN
$BBBIN >/dev/null 2>&1 || mv -f $TMPDIR/lib/armeabi-v7a/libbusybox.so $BBBIN
$BBBIN rm -rf $TMPDIR/lib
export INSTALLER=$TMPDIR/install
$BBBIN mkdir -p $INSTALLER
$BBBIN unzip -o "$3" "assets/*" "lib/*" "META-INF/com/google/*" -x "lib/*/libbusybox.so" -d $INSTALLER >&2
export ASH_STANDALONE=1
exec $BBBIN sh "$INSTALLER/META-INF/com/google/android/updater-script" "$@"
############################################
#
# Magisk General Utility Functions
# by topjohnwu
#
############################################
#MAGISK_VERSION_STUB
......@@ -33,7 +30,7 @@ grep_prop() {
shift
local FILES=$@
[ -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() {
......@@ -42,11 +39,11 @@ getvar() {
local PROPPATH='/data/.magisk /cache/.magisk'
[ -n $MAGISKTMP ] && PROPPATH="$MAGISKTMP/config $PROPPATH"
VALUE=$(grep_prop $VARNAME $PROPPATH)
[ ! -z $VALUE ] && eval $VARNAME=\$VALUE
[ -n $VALUE ] && eval $VARNAME=\$VALUE
}
is_mounted() {
grep -q " `readlink -f $1` " /proc/mounts 2>/dev/null
grep -q " $(readlink -f $1) " /proc/mounts 2>/dev/null
return $?
}
......@@ -96,6 +93,7 @@ setup_flashable() {
fi
done
fi
recovery_actions
}
ensure_bb() {
......@@ -309,51 +307,57 @@ mount_apex() {
$BOOTMODE || [ ! -d /system/apex ] && return
local APEX DEST
setup_mntpoint /apex
mount -t tmpfs tmpfs /apex -o mode=755
local PATTERN='s/.*"name":[^"]*"\([^"]*\).*/\1/p'
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
# APEX APKs, extract and loop mount
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
ui_print "- Mounting $DEST"
mount -t ext4 -o ro,noatime $LOOPDEV $DEST
fi
rm -f apex_payload.img
rm -f /apex/apex_payload.img
elif [ -d $APEX ]; then
# 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"
mount -o bind $APEX $DEST
fi
done
export ANDROID_RUNTIME_ROOT=/apex/com.android.runtime
export ANDROID_TZDATA_ROOT=/apex/com.android.tzdata
local APEXRJPATH=/apex/com.android.runtime/javalib
local SYSFRAME=/system/framework
export BOOTCLASSPATH=\
$APEXRJPATH/core-oj.jar:$APEXRJPATH/core-libart.jar:$APEXRJPATH/okhttp.jar:\
$APEXRJPATH/bouncycastle.jar:$APEXRJPATH/apache-xml.jar:$SYSFRAME/framework.jar:\
$SYSFRAME/ext.jar:$SYSFRAME/telephony-common.jar:$SYSFRAME/voip-common.jar:\
$SYSFRAME/ims-common.jar:$SYSFRAME/android.test.base.jar:$SYSFRAME/telephony-ext.jar:\
/apex/com.android.conscrypt/javalib/conscrypt.jar:\
/apex/com.android.media/javalib/updatable-media.jar
export ANDROID_ART_ROOT=/apex/com.android.art
export ANDROID_I18N_ROOT=/apex/com.android.i18n
local APEXJARS=$(find /apex -name '*.jar' | sort | tr '\n' ':')
local FWK=/system/framework
export BOOTCLASSPATH=${APEXJARS}\
$FWK/framework.jar:$FWK/ext.jar:$FWK/telephony-common.jar:\
$FWK/voip-common.jar:$FWK/ims-common.jar:$FWK/telephony-ext.jar
}
umount_apex() {
[ -d /apex ] || return
local DEST SRC
for DEST in /apex/*; do
[ "$DEST" = '/apex/*' ] && break
SRC=$(grep $DEST /proc/mounts | awk '{ print $1 }')
umount -l $DEST
# Detach loop device just in case
losetup -d $SRC 2>/dev/null
umount -l /apex
for loop in /dev/block/loop*; do
losetup -d $loop 2>/dev/null
done
rm -rf /apex
unset ANDROID_RUNTIME_ROOT
unset ANDROID_TZDATA_ROOT
unset ANDROID_ART_ROOT
unset ANDROID_I18N_ROOT
unset BOOTCLASSPATH
}
......@@ -389,7 +393,7 @@ find_boot_image() {
BOOTIMAGE=
if $RECOVERYMODE; then
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`
else
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() {
case "$1" in
*.gz) CMD1="$MAGISKBIN/magiskboot decompress '$1' - 2>/dev/null";;
*.gz) CMD1="gzip -d < '$1' 2>/dev/null";;
*) CMD1="cat '$1'";;
esac
if $BOOTSIGNED; then
......
......@@ -3,12 +3,12 @@ plugins {
}
android {
val canary = !Config.appVersion.contains(".")
val canary = !Config.version.contains(".")
defaultConfig {
applicationId = "com.topjohnwu.magisk"
versionCode = 1
versionName = Config.appVersion
versionName = Config.version
buildConfigField("int", "STUB_VERSION", "15")
buildConfigField("String", "DEV_CHANNEL", Config["DEV_CHANNEL"] ?: "null")
buildConfigField("boolean", "CANARY", if (canary) "true" else "false")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment