mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-25 02:55:33 +00:00
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:
parent
61d52991f1
commit
ec8fffe61c
5
app/.gitignore
vendored
5
app/.gitignore
vendored
@ -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
|
||||
|
@ -1,3 +1,4 @@
|
||||
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)
|
||||
|
@ -1,13 +1,30 @@
|
||||
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
|
||||
|
@ -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()
|
||||
|
||||
}
|
@ -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,8 +68,6 @@ 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
|
||||
@ -81,7 +78,6 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
|
||||
handleAPK(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
val newId = notifyFinish(this)
|
||||
if (ForegroundTracker.hasForeground)
|
||||
onFinish(this, newId)
|
||||
|
@ -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 {
|
||||
installDir.deleteRecursively()
|
||||
installDir.mkdirs()
|
||||
|
||||
zipFile = if (mUri.scheme == "file") {
|
||||
mUri.toFile()
|
||||
} else {
|
||||
File(installDir, "install.zip").also {
|
||||
console.add("- Copying zip to temp directory")
|
||||
|
||||
runCatching {
|
||||
mUri.inputStream().writeTo(tmpFile)
|
||||
}.getOrElse {
|
||||
when (it) {
|
||||
try {
|
||||
mUri.inputStream().writeTo(it)
|
||||
} catch (e: IOException) {
|
||||
when (e) {
|
||||
is FileNotFoundException -> console.add("! Invalid Uri")
|
||||
is IOException -> console.add("! Cannot copy to cache")
|
||||
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 {
|
||||
|
@ -1,25 +1,25 @@
|
||||
package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.DynAPK
|
||||
import com.topjohnwu.magisk.R
|
||||
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.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.di.Protected
|
||||
import com.topjohnwu.magisk.events.dialog.EnvFixDialog
|
||||
import com.topjohnwu.magisk.ktx.reboot
|
||||
import com.topjohnwu.magisk.ktx.symlink
|
||||
import com.topjohnwu.magisk.ktx.withStreams
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.signing.SignBoot
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@ -37,7 +37,6 @@ import org.kamranzafar.jtar.TarHeader
|
||||
import org.kamranzafar.jtar.TarInputStream
|
||||
import org.kamranzafar.jtar.TarOutputStream
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
import org.koin.core.inject
|
||||
import timber.log.Timber
|
||||
import java.io.*
|
||||
@ -46,10 +45,8 @@ import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.collections.set
|
||||
|
||||
abstract class MagiskInstallImpl protected constructor(
|
||||
private var zipUri: Uri,
|
||||
protected val console: MutableList<String> = NOPList.getInstance(),
|
||||
private val logs: MutableList<String> = NOPList.getInstance()
|
||||
) : KoinComponent {
|
||||
@ -59,17 +56,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
|
||||
private var tarOut: TarOutputStream? = null
|
||||
private val service: NetworkService by inject()
|
||||
protected val context: Context by inject()
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
protected val context: Context by inject(Protected)
|
||||
|
||||
private fun findImage(): Boolean {
|
||||
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
|
||||
@ -98,69 +85,85 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun extractZip(): Boolean {
|
||||
val arch: String
|
||||
val arch32: String
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
arch = ABI_MAP[Build.SUPPORTED_ABIS[0]]!!
|
||||
arch32 = ABI_MAP[Build.SUPPORTED_32_BIT_ABIS[0]]!!
|
||||
} else {
|
||||
arch = ABI_MAP[Build.CPU_ABI]!!
|
||||
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 {
|
||||
private fun installDirFile(name: String): File {
|
||||
return if (installDir is SuFile)
|
||||
SuFile(installDir, name)
|
||||
else
|
||||
File(installDir, name)
|
||||
}
|
||||
|
||||
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 {
|
||||
binDir
|
||||
}
|
||||
|
||||
try {
|
||||
ZipInputStream(zipUri.inputStream().buffered()).use { zi ->
|
||||
// Extract binaries
|
||||
if (isRunningAsStub) {
|
||||
ZipInputStream(DynAPK.current(context).inputStream().buffered()).use { zi ->
|
||||
lateinit var ze: ZipEntry
|
||||
while (zi.nextEntry?.let { ze = it } != null) {
|
||||
if (ze.isDirectory)
|
||||
continue
|
||||
|
||||
var name: String? = null
|
||||
|
||||
if (ze.name.startsWith("chromeos/")) {
|
||||
name = ze.name
|
||||
val name = if (ze.name.startsWith("lib/${Const.CPU_ABI_32}/")) {
|
||||
val n = ze.name.substring(ze.name.lastIndexOf('/') + 1)
|
||||
n.substring(3, n.length - 3)
|
||||
} else {
|
||||
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
|
||||
continue
|
||||
}
|
||||
|
||||
val dest = File(binDir, name)
|
||||
dest.outputStream().use { zi.copyTo(it) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val libs = Const.NATIVE_LIB_DIR.listFiles { _, name ->
|
||||
name.startsWith("lib") && name.endsWith(".so")
|
||||
} ?: emptyArray()
|
||||
for (lib in libs) {
|
||||
val name = lib.name.substring(3, lib.name.length - 3)
|
||||
val bin = File(binDir, name)
|
||||
symlink(lib.path, bin.path)
|
||||
}
|
||||
}
|
||||
|
||||
name ?: continue
|
||||
|
||||
val dest = newFile(name)
|
||||
dest.parentFile!!.mkdirs()
|
||||
SuFileOutputStream(dest).use { s -> zi.copyTo(s) }
|
||||
// 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: IOException) {
|
||||
console.add("! Cannot unzip zip")
|
||||
} catch (e: Exception) {
|
||||
console.add("! Unable to extract files")
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
|
||||
val init64 = newFile("magiskinit64")
|
||||
if (init64.exists() && arch != arch32) {
|
||||
init64.renameTo(newFile("magiskinit"))
|
||||
} else {
|
||||
init64.delete()
|
||||
if (installDir !== binDir) {
|
||||
arrayOf(
|
||||
"rm -rf $installDir",
|
||||
"mkdir -p $installDir",
|
||||
"cp_readlink $binDir $installDir",
|
||||
"rm -rf $binDir"
|
||||
).sh()
|
||||
}
|
||||
"cd $installDir; chmod 755 *".sh()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -177,7 +180,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
lateinit var entry: TarEntry
|
||||
|
||||
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) {
|
||||
if (entry.name.contains("boot.img") ||
|
||||
@ -186,12 +189,9 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
console.add("-- Extracting: $name")
|
||||
|
||||
val extract = File(installDir, name)
|
||||
FileOutputStream(extract).use { decompressedStream().copyTo(it) }
|
||||
decompressedStream().writeTo(extract)
|
||||
} else if (entry.name.contains("vbmeta.img")) {
|
||||
val rawData = ByteArrayOutputStream().let {
|
||||
decompressedStream().copyTo(it)
|
||||
it.toByteArray()
|
||||
}
|
||||
val rawData = decompressedStream().readBytes()
|
||||
// Valid vbmeta.img should be at least 256 bytes
|
||||
if (rawData.size < 256)
|
||||
continue
|
||||
@ -208,8 +208,8 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
|
||||
}
|
||||
}
|
||||
val boot = SuFile.open(installDir, "boot.img")
|
||||
val recovery = SuFile.open(installDir, "recovery.img")
|
||||
val boot = installDirFile("boot.img")
|
||||
val recovery = installDirFile("recovery.img")
|
||||
if (Config.recovery && recovery.exists() && boot.exists()) {
|
||||
// Install Magisk to recovery
|
||||
srcBoot = recovery.path
|
||||
@ -306,10 +306,19 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
return false
|
||||
}
|
||||
|
||||
// Fix up binaries
|
||||
if (installDir is SuFile) {
|
||||
"fix_env $installDir".sh()
|
||||
} else {
|
||||
"cp_readlink $installDir".sh()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun patchBoot(): Boolean {
|
||||
"cd $installDir".sh()
|
||||
|
||||
var srcNand = ""
|
||||
if ("[ -c $srcBoot ] && nanddump -f boot.img $srcBoot".sh().isSuccess) {
|
||||
srcNand = srcBoot
|
||||
@ -334,26 +343,20 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
"KEEPVERITY=${Config.keepVerity} " +
|
||||
"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
|
||||
}
|
||||
|
||||
if (srcNand.isNotEmpty()) {
|
||||
if (srcNand.isNotEmpty())
|
||||
srcBoot = srcNand
|
||||
}
|
||||
|
||||
val job = Shell.sh(
|
||||
"./magiskboot cleanup",
|
||||
"mv bin/busybox busybox",
|
||||
"rm -rf magisk.apk bin boot.img update-binary",
|
||||
"cd /")
|
||||
val job = Shell.sh("./magiskboot cleanup", "cd /")
|
||||
|
||||
val patched = File(installDir, "new-boot.img")
|
||||
val patched = installDirFile("new-boot.img")
|
||||
if (isSigned) {
|
||||
console.add("- Signing boot image with verity keys")
|
||||
val signed = File(installDir, "signed.img")
|
||||
val signed = installDirFile("signed.img")
|
||||
try {
|
||||
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) {
|
||||
withStreams(SuFileInputStream(patched), SuFileOutputStream(signed)) {
|
||||
input, out -> SignBoot.doSignature(null, null, input, out, "/boot")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
@ -368,18 +371,10 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
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 {
|
||||
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
||||
return false
|
||||
"run_migrations".sh()
|
||||
arrayOf("run_migrations", "copy_sepolicy_rules").sh()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -403,24 +398,28 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
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 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() &&
|
||||
copySepolicyRules() && flashBoot()
|
||||
protected fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
|
||||
|
||||
protected suspend fun secondSlot() = findSecondaryImage() && extractZip() &&
|
||||
patchBoot() && copySepolicyRules() && flashBoot() && postOTA()
|
||||
protected suspend fun secondSlot() =
|
||||
findSecondaryImage() && extractFiles() && patchBoot() && flashBoot() && postOTA()
|
||||
|
||||
protected fun fixEnv(): Boolean {
|
||||
installDir = SuFile("/data/adb/magisk")
|
||||
Shell.su("rm -rf /data/adb/magisk/*").exec()
|
||||
return extractZip() && Shell.su("fix_env").exec().isSuccess
|
||||
}
|
||||
protected fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
|
||||
|
||||
@WorkerThread
|
||||
protected abstract suspend fun operations(): Boolean
|
||||
@ -429,79 +428,77 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
}
|
||||
|
||||
abstract class MagiskInstaller(
|
||||
zip: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstallImpl(zip, console, logs) {
|
||||
|
||||
init {
|
||||
installDir = File(get<Context>(Protected).filesDir.parent, "install")
|
||||
"rm -rf $installDir".sh()
|
||||
installDir.mkdirs()
|
||||
}
|
||||
) : MagiskInstallImpl(console, logs) {
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
if (success) {
|
||||
console.add("- All done!")
|
||||
} else {
|
||||
if (installDir is SuFile) {
|
||||
Shell.sh("rm -rf ${Const.TMPDIR}").submit()
|
||||
} else {
|
||||
Shell.sh("rm -rf $installDir").submit()
|
||||
}
|
||||
console.add("! Installation failed")
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
class Patch(
|
||||
zip: Uri,
|
||||
private val uri: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(zip, console, logs) {
|
||||
) : MagiskInstaller(console, logs) {
|
||||
override suspend fun operations() = doPatchFile(uri)
|
||||
}
|
||||
|
||||
class SecondSlot(
|
||||
zip: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(zip, console, logs) {
|
||||
) : MagiskInstaller(console, logs) {
|
||||
override suspend fun operations() = secondSlot()
|
||||
}
|
||||
|
||||
class Direct(
|
||||
zip: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(zip, console, logs) {
|
||||
) : MagiskInstaller(console, logs) {
|
||||
override suspend fun operations() = direct()
|
||||
}
|
||||
|
||||
class Emulator(
|
||||
zip: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstallImpl(zip, console, logs) {
|
||||
) : MagiskInstaller(console, logs) {
|
||||
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 {
|
||||
val success = super.exec()
|
||||
if (success) {
|
||||
console.add("- All done!")
|
||||
} else {
|
||||
console.add("! Installation failed")
|
||||
UiThreadHandler.handler.postDelayed(3000) {
|
||||
Shell.su("pm uninstall ${context.packageName}").exec()
|
||||
}
|
||||
}
|
||||
return success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EnvFixTask(zip: Uri) : MagiskInstallImpl(zip) {
|
||||
|
||||
class FixEnv(private val callback: () -> Unit) : MagiskInstallImpl() {
|
||||
override suspend fun operations() = fixEnv()
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(EnvFixDialog.DISMISS))
|
||||
callback()
|
||||
Utils.toast(
|
||||
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
|
||||
Toast.LENGTH_LONG
|
||||
@ -510,4 +507,5 @@ class EnvFixTask(zip: Uri) : MagiskInstallImpl(zip) {
|
||||
UiThreadHandler.handler.postDelayed(5000) { reboot() }
|
||||
return success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
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()
|
||||
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}")
|
||||
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")
|
||||
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)
|
||||
|
@ -1,15 +1,11 @@
|
||||
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)
|
||||
}.exec()
|
||||
}
|
||||
}, IntentFilter(DISMISS))
|
||||
DownloadService.start(dialog.context, Magisk(EnvFix))
|
||||
}
|
||||
}
|
||||
.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()))
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -25,14 +25,9 @@
|
||||
app:fitsSystemWindowsInsets="top|bottom"
|
||||
tools:paddingTop="24dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
goneUnless="@{viewModel.loaded && viewModel.progress <= 0}"
|
||||
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">
|
||||
@ -188,14 +183,6 @@
|
||||
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"
|
||||
@ -250,82 +237,6 @@
|
||||
|
||||
</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_marginEnd="@dimen/l1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/loading"
|
||||
android:textAppearance="@style/AppearanceFoundation.Title" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/l1">
|
||||
|
||||
<ProgressBar
|
||||
style="@style/WidgetFoundation.ProgressBar"
|
||||
invisible="@{viewModel.progress <= 0}"
|
||||
android:layout_width="100dp"
|
||||
android:layout_gravity="center"
|
||||
android:progress="@{viewModel.progress}" />
|
||||
|
||||
<ProgressBar
|
||||
style="@style/WidgetFoundation.ProgressBar.Indeterminate"
|
||||
invisibleUnless="@{viewModel.progress <= 0}"
|
||||
android:layout_width="100dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
goneUnless="@{viewModel.loaded && viewModel.progress >= 100}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/download_complete"
|
||||
android:textAppearance="@style/AppearanceFoundation.Title" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
goneUnless="@{viewModel.loadFailed}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/download_file_error"
|
||||
android:textAppearance="@style/AppearanceFoundation.Title" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</layout>
|
||||
|
@ -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" />
|
||||
|
1
app/src/main/res/raw/.gitignore
vendored
1
app/src/main/res/raw/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/util_functions.sh
|
@ -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}")
|
||||
|
||||
|
142
build.py
142
build.py
@ -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,96 +384,20 @@ 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'
|
||||
|
||||
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):
|
||||
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'
|
||||
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', 'magisk_uninstaller.sh')
|
||||
target = op.join('META-INF', 'com', 'google',
|
||||
@ -504,17 +405,23 @@ def zip_uninstaller(args):
|
||||
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')
|
||||
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)
|
||||
|
||||
# util_functions.sh
|
||||
source = op.join('scripts', 'util_functions.sh')
|
||||
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}')
|
||||
zipf.writestr(target, script.read())
|
||||
zipf.writestr(target, util_func)
|
||||
|
||||
# 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
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
#!/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
|
||||
;;
|
||||
|
@ -1,31 +1,26 @@
|
||||
#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
|
||||
|
||||
|
@ -1,72 +1,75 @@
|
||||
#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
|
||||
|
@ -1,37 +1,18 @@
|
||||
#!/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" "$@"
|
||||
|
@ -1,8 +1,5 @@
|
||||
############################################
|
||||
#
|
||||
# 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")
|
||||
|
Loading…
Reference in New Issue
Block a user