mirror of
https://github.com/topjohnwu/Magisk
synced 2025-11-11 03:47:37 +01:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
975120d6a6 | ||
|
|
e489b3b6dd | ||
|
|
589a270b8d | ||
|
|
7961be5cfa | ||
|
|
959430e030 | ||
|
|
2923c8ccd1 | ||
|
|
7df4a9d74f | ||
|
|
bf4ed295da | ||
|
|
a5fca960dc | ||
|
|
f99912b9db | ||
|
|
a54bdb54e4 | ||
|
|
cd9851a1fe | ||
|
|
9ca469898c | ||
|
|
0665549473 | ||
|
|
9d7a14b335 | ||
|
|
62e29fee74 | ||
|
|
e472db552b | ||
|
|
466e4bd4e1 | ||
|
|
4cf525c588 | ||
|
|
c8aec2510d | ||
|
|
ccbfe0e66e | ||
|
|
23ea28de6f | ||
|
|
55c3ee3a6f | ||
|
|
2a42ca2b8f | ||
|
|
a897e82fa4 | ||
|
|
ffa15831d3 | ||
|
|
a344ebf28c |
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@@ -45,7 +45,7 @@ runs:
|
|||||||
env:
|
env:
|
||||||
SCCACHE_DIRECT: false
|
SCCACHE_DIRECT: false
|
||||||
SCCACHE_DIR: ${{ github.workspace }}/.sccache
|
SCCACHE_DIR: ${{ github.workspace }}/.sccache
|
||||||
SCCACHE_CACHE_SIZE: 2G
|
SCCACHE_CACHE_SIZE: ${{ inputs.is-asset-build == 'true' && '2G' || '300M' }}
|
||||||
SCCACHE_IDLE_TIMEOUT: 0
|
SCCACHE_IDLE_TIMEOUT: 0
|
||||||
run: |
|
run: |
|
||||||
bash $GITHUB_ACTION_PATH/sccache.sh
|
bash $GITHUB_ACTION_PATH/sccache.sh
|
||||||
|
|||||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -82,12 +82,10 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
|
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, "36.0-CANARY"]
|
||||||
type: [""]
|
type: [""]
|
||||||
include:
|
include:
|
||||||
- version: 36
|
- version: "36.0-CANARY"
|
||||||
type: "google_apis"
|
|
||||||
- version: 36
|
|
||||||
type: "google_apis_ps16k"
|
type: "google_apis_ps16k"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ import com.topjohnwu.magisk.core.ktx.activity
|
|||||||
import com.topjohnwu.magisk.core.ktx.toast
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||||
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||||
|
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||||
import com.topjohnwu.magisk.databinding.bindExtra
|
import com.topjohnwu.magisk.databinding.bindExtra
|
||||||
import com.topjohnwu.magisk.events.AddHomeIconEvent
|
import com.topjohnwu.magisk.events.AddHomeIconEvent
|
||||||
import com.topjohnwu.magisk.events.AuthEvent
|
import com.topjohnwu.magisk.events.AuthEvent
|
||||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
||||||
@@ -130,7 +130,8 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createHosts() {
|
private fun createHosts() {
|
||||||
Shell.cmd("add_hosts_module").submit {
|
viewModelScope.launch {
|
||||||
|
RootUtils.addSystemlessHosts()
|
||||||
AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,6 @@ gradlePlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
|
||||||
compilerOptions {
|
|
||||||
languageVersion = KotlinVersion.KOTLIN_2_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlin("gradle-plugin", libs.versions.kotlin.get()))
|
implementation(kotlin("gradle-plugin", libs.versions.kotlin.get()))
|
||||||
implementation(libs.android.gradle.plugin)
|
implementation(libs.android.gradle.plugin)
|
||||||
|
|||||||
@@ -309,9 +309,9 @@ fun Project.setupStubApk() {
|
|||||||
outputs.dir(outResDir)
|
outputs.dir(outResDir)
|
||||||
doLast {
|
doLast {
|
||||||
val apkTmp = File("${apk}.tmp")
|
val apkTmp = File("${apk}.tmp")
|
||||||
exec {
|
providers.exec {
|
||||||
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
|
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
|
||||||
}
|
}.result.get()
|
||||||
|
|
||||||
val bos = ByteArrayOutputStream()
|
val bos = ByteArrayOutputStream()
|
||||||
ZipFile(apkTmp).use { src ->
|
ZipFile(apkTmp).use { src ->
|
||||||
|
|||||||
@@ -27,10 +27,15 @@ android {
|
|||||||
aidl = true
|
aidl = true
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":shared"))
|
api(project(":shared"))
|
||||||
|
coreLibraryDesugaring(libs.jdk.libs)
|
||||||
|
|
||||||
api(libs.timber)
|
api(libs.timber)
|
||||||
api(libs.markwon.core)
|
api(libs.markwon.core)
|
||||||
|
|||||||
1
app/core/proguard-rules.pro
vendored
1
app/core/proguard-rules.pro
vendored
@@ -38,3 +38,4 @@
|
|||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
|
|
||||||
-dontwarn org.junit.**
|
-dontwarn org.junit.**
|
||||||
|
-dontwarn org.apache.**
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ package com.topjohnwu.magisk.core.utils;
|
|||||||
interface IRootUtils {
|
interface IRootUtils {
|
||||||
android.app.ActivityManager.RunningAppProcessInfo getAppProcess(int pid);
|
android.app.ActivityManager.RunningAppProcessInfo getAppProcess(int pid);
|
||||||
IBinder getFileSystem();
|
IBinder getFileSystem();
|
||||||
|
boolean addSystemlessHosts();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// Try to find package name from PID
|
// Try to find package name from PID
|
||||||
val proc = RootUtils.obj?.getAppProcess(pid)
|
val proc = RootUtils.getAppProcess(pid)
|
||||||
if (proc == null) {
|
if (proc == null) {
|
||||||
if (uid == Process.SHELL_UID) {
|
if (uid == Process.SHELL_UID) {
|
||||||
// It is possible that some apps installed are sharing UID with shell.
|
// It is possible that some apps installed are sharing UID with shell.
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import java.io.IOException
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.text.DateFormat
|
import java.time.Instant
|
||||||
import java.text.SimpleDateFormat
|
import java.time.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.format.FormatStyle
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
inline fun <In : Closeable, Out : Closeable> withInOut(
|
inline fun <In : Closeable, Out : Closeable> withInOut(
|
||||||
input: In,
|
input: In,
|
||||||
@@ -83,19 +84,15 @@ inline fun <T, R> Flow<T>.concurrentMap(crossinline transform: suspend (T) -> R)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Long.toTime(format: DateFormat) = format.format(this).orEmpty()
|
fun Long.toTime(format: DateTimeFormatter): String = format.format(Instant.ofEpochMilli(this))
|
||||||
|
|
||||||
// Some devices don't allow filenames containing ":"
|
// Some devices don't allow filenames containing ":"
|
||||||
val timeFormatStandard by lazy {
|
val timeFormatStandard: DateTimeFormatter by lazy {
|
||||||
SimpleDateFormat(
|
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH.mm.ss").withZone(ZoneId.systemDefault())
|
||||||
"yyyy-MM-dd'T'HH.mm.ss",
|
|
||||||
Locale.ROOT
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
val timeDateFormat: DateFormat by lazy {
|
val timeDateFormat: DateTimeFormatter by lazy {
|
||||||
DateFormat.getDateTimeInstance(
|
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withZone(ZoneId.systemDefault())
|
||||||
DateFormat.DEFAULT,
|
}
|
||||||
DateFormat.DEFAULT,
|
val dateFormat: DateTimeFormatter by lazy {
|
||||||
Locale.ROOT
|
DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault())
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import com.squareup.moshi.JsonClass
|
|||||||
import com.squareup.moshi.JsonQualifier
|
import com.squareup.moshi.JsonQualifier
|
||||||
import com.squareup.moshi.ToJson
|
import com.squareup.moshi.ToJson
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.time.LocalDateTime
|
import java.time.Instant
|
||||||
import java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
class UpdateJson(
|
class UpdateJson(
|
||||||
@@ -40,13 +39,13 @@ data class ReleaseAssets(
|
|||||||
|
|
||||||
class DateTimeAdapter {
|
class DateTimeAdapter {
|
||||||
@ToJson
|
@ToJson
|
||||||
fun toJson(date: LocalDateTime): String {
|
fun toJson(date: Instant): String {
|
||||||
return date.toString()
|
return date.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@FromJson
|
@FromJson
|
||||||
fun fromJson(date: String): LocalDateTime {
|
fun fromJson(date: String): Instant {
|
||||||
return LocalDateTime.parse(date, ISO_OFFSET_DATE_TIME)
|
return Instant.parse(date)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +56,7 @@ data class Release(
|
|||||||
val prerelease: Boolean,
|
val prerelease: Boolean,
|
||||||
val assets: List<ReleaseAssets>,
|
val assets: List<ReleaseAssets>,
|
||||||
val body: String,
|
val body: String,
|
||||||
@Json(name = "created_at") val createdTime: LocalDateTime,
|
@Json(name = "created_at") val createdTime: Instant,
|
||||||
) {
|
) {
|
||||||
val versionCode: Int get() {
|
val versionCode: Int get() {
|
||||||
return if (tag[0] == 'v') {
|
return if (tag[0] == 'v') {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.io.IOException
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
data class LocalModule(
|
data class LocalModule(
|
||||||
private val base: ExtendedFile,
|
val base: ExtendedFile,
|
||||||
) : Module() {
|
) : Module() {
|
||||||
private val svc get() = ServiceLocator.networkService
|
private val svc get() = ServiceLocator.networkService
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL
|
|||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.data.GithubApiServices
|
import com.topjohnwu.magisk.core.data.GithubApiServices
|
||||||
import com.topjohnwu.magisk.core.data.RawUrl
|
import com.topjohnwu.magisk.core.data.RawUrl
|
||||||
|
import com.topjohnwu.magisk.core.ktx.dateFormat
|
||||||
import com.topjohnwu.magisk.core.model.Release
|
import com.topjohnwu.magisk.core.model.Release
|
||||||
import com.topjohnwu.magisk.core.model.ReleaseAssets
|
import com.topjohnwu.magisk.core.model.ReleaseAssets
|
||||||
import com.topjohnwu.magisk.core.model.UpdateInfo
|
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
class NetworkService(
|
class NetworkService(
|
||||||
private val raw: RawUrl,
|
private val raw: RawUrl,
|
||||||
@@ -74,7 +74,7 @@ class NetworkService(
|
|||||||
|
|
||||||
private inline fun Release.asPublicInfo(selector: (ReleaseAssets) -> Boolean): UpdateInfo {
|
private inline fun Release.asPublicInfo(selector: (ReleaseAssets) -> Boolean): UpdateInfo {
|
||||||
val version = tag.drop(1)
|
val version = tag.drop(1)
|
||||||
val date = createdTime.format(DateTimeFormatter.ofPattern("yyyy.M.d"))
|
val date = dateFormat.format(createdTime)
|
||||||
return UpdateInfo(
|
return UpdateInfo(
|
||||||
version = version,
|
version = version,
|
||||||
versionCode = versionCode,
|
versionCode = versionCode,
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import android.content.ServiceConnection
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import com.topjohnwu.superuser.ipc.RootService
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
import com.topjohnwu.superuser.nio.FileSystemManager
|
import com.topjohnwu.superuser.nio.FileSystemManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.locks.AbstractQueuedSynchronizer
|
import java.util.concurrent.locks.AbstractQueuedSynchronizer
|
||||||
@@ -43,16 +46,7 @@ class RootUtils(stub: Any?) : RootService() {
|
|||||||
return object : IRootUtils.Stub() {
|
return object : IRootUtils.Stub() {
|
||||||
override fun getAppProcess(pid: Int) = safe(null) { getAppProcessImpl(pid) }
|
override fun getAppProcess(pid: Int) = safe(null) { getAppProcessImpl(pid) }
|
||||||
override fun getFileSystem(): IBinder = FileSystemManager.getService()
|
override fun getFileSystem(): IBinder = FileSystemManager.getService()
|
||||||
}
|
override fun addSystemlessHosts() = safe(false) { addSystemlessHostsImpl() }
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <T> safe(default: T, block: () -> T): T {
|
|
||||||
return try {
|
|
||||||
block()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
// The process died unexpectedly
|
|
||||||
Timber.e(e)
|
|
||||||
default
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +72,26 @@ class RootUtils(stub: Any?) : RootService() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addSystemlessHostsImpl(): Boolean {
|
||||||
|
val module = File(Const.MODULE_PATH, "hosts")
|
||||||
|
if (module.exists()) return true
|
||||||
|
val hosts = File(module, "system/etc/hosts")
|
||||||
|
if (!hosts.parentFile.mkdirs()) return false
|
||||||
|
File(module, "module.prop").outputStream().writer().use {
|
||||||
|
it.write("""
|
||||||
|
id=hosts
|
||||||
|
name=Systemless Hosts
|
||||||
|
version=1.0
|
||||||
|
versionCode=1
|
||||||
|
author=Magisk
|
||||||
|
description=Magisk app built-in systemless hosts module
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
File("/system/etc/hosts").copyTo(hosts)
|
||||||
|
File(module, "update").createNewFile()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
object Connection : AbstractQueuedSynchronizer(), ServiceConnection {
|
object Connection : AbstractQueuedSynchronizer(), ServiceConnection {
|
||||||
init {
|
init {
|
||||||
state = 1
|
state = 1
|
||||||
@@ -131,11 +145,25 @@ class RootUtils(stub: Any?) : RootService() {
|
|||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
private set
|
private set
|
||||||
var obj: IRootUtils? = null
|
private var obj: IRootUtils? = null
|
||||||
get() {
|
get() {
|
||||||
Connection.await()
|
Connection.await()
|
||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
private set
|
|
||||||
|
fun getAppProcess(pid: Int) = safe(null) { obj?.getAppProcess(pid) }
|
||||||
|
|
||||||
|
suspend fun addSystemlessHosts() =
|
||||||
|
withContext(Dispatchers.IO) { safe(false) { obj?.addSystemlessHosts() ?: false } }
|
||||||
|
|
||||||
|
private inline fun <T> safe(default: T, block: () -> T): T {
|
||||||
|
return try {
|
||||||
|
block()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
// The process died unexpectedly
|
||||||
|
Timber.e(e)
|
||||||
|
default
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import com.topjohnwu.magisk.test.Environment.Companion.INVALID_ZYGISK
|
|||||||
import com.topjohnwu.magisk.test.Environment.Companion.MOUNT_TEST
|
import com.topjohnwu.magisk.test.Environment.Companion.MOUNT_TEST
|
||||||
import com.topjohnwu.magisk.test.Environment.Companion.REMOVE_TEST
|
import com.topjohnwu.magisk.test.Environment.Companion.REMOVE_TEST
|
||||||
import com.topjohnwu.magisk.test.Environment.Companion.SEPOLICY_RULE
|
import com.topjohnwu.magisk.test.Environment.Companion.SEPOLICY_RULE
|
||||||
|
import com.topjohnwu.magisk.test.Environment.Companion.UPGRADE_TEST
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
@@ -55,7 +57,7 @@ class AdditionalTest : BaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testModuleCount() {
|
fun testModuleCount() {
|
||||||
var expected = 3
|
var expected = 4
|
||||||
if (Environment.mount()) expected++
|
if (Environment.mount()) expected++
|
||||||
if (Environment.preinit()) expected++
|
if (Environment.preinit()) expected++
|
||||||
if (Environment.lsposed()) expected++
|
if (Environment.lsposed()) expected++
|
||||||
@@ -98,9 +100,10 @@ class AdditionalTest : BaseTest {
|
|||||||
RootUtils.fs.getFile("/system/bin/screenrecord").exists()
|
RootUtils.fs.getFile("/system/bin/screenrecord").exists()
|
||||||
)
|
)
|
||||||
val egg = RootUtils.fs.getFile("/system/app/EasterEgg").list() ?: arrayOf()
|
val egg = RootUtils.fs.getFile("/system/app/EasterEgg").list() ?: arrayOf()
|
||||||
assertTrue(
|
assertArrayEquals(
|
||||||
"/system/app/EasterEgg should be empty",
|
"/system/app/EasterEgg should be replaced",
|
||||||
egg.isEmpty()
|
egg,
|
||||||
|
arrayOf("newfile")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,5 +137,25 @@ class AdditionalTest : BaseTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testRemoveModule() {
|
fun testRemoveModule() {
|
||||||
assertNull("$REMOVE_TEST should be removed", modules.find { it.id == REMOVE_TEST })
|
assertNull("$REMOVE_TEST should be removed", modules.find { it.id == REMOVE_TEST })
|
||||||
|
assertTrue(
|
||||||
|
"Uninstaller of $REMOVE_TEST should be run",
|
||||||
|
RootUtils.fs.getFile(Environment.REMOVE_TEST_MARKER).exists()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testModuleUpgrade() {
|
||||||
|
val module = modules.find { it.id == UPGRADE_TEST }
|
||||||
|
assertNotNull("$UPGRADE_TEST is not installed", module)
|
||||||
|
module!!
|
||||||
|
assertFalse("$UPGRADE_TEST should be disabled", module.enable)
|
||||||
|
assertTrue(
|
||||||
|
"$UPGRADE_TEST should be updated",
|
||||||
|
module.base.getChildFile("post-fs-data.sh").exists()
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
"$UPGRADE_TEST should be updated",
|
||||||
|
module.base.getChildFile("service.sh").exists()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,12 +58,15 @@ class Environment : BaseTest {
|
|||||||
return Build.VERSION.SDK_INT >= 27
|
return Build.VERSION.SDK_INT >= 27
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val MODULE_UPDATE_PATH = "/data/adb/modules_update"
|
||||||
private const val MODULE_ERROR = "Module zip processing incorrect"
|
private const val MODULE_ERROR = "Module zip processing incorrect"
|
||||||
const val MOUNT_TEST = "mount_test"
|
const val MOUNT_TEST = "mount_test"
|
||||||
const val SEPOLICY_RULE = "sepolicy_rule"
|
const val SEPOLICY_RULE = "sepolicy_rule"
|
||||||
const val INVALID_ZYGISK = "invalid_zygisk"
|
const val INVALID_ZYGISK = "invalid_zygisk"
|
||||||
const val REMOVE_TEST = "remove_test"
|
const val REMOVE_TEST = "remove_test"
|
||||||
|
const val REMOVE_TEST_MARKER = "/dev/.remove_test_removed"
|
||||||
const val EMPTY_ZYGISK = "empty_zygisk"
|
const val EMPTY_ZYGISK = "empty_zygisk"
|
||||||
|
const val UPGRADE_TEST = "upgrade_test"
|
||||||
}
|
}
|
||||||
|
|
||||||
object TimberLog : CallbackList<String>(Runnable::run) {
|
object TimberLog : CallbackList<String>(Runnable::run) {
|
||||||
@@ -108,6 +111,9 @@ class Environment : BaseTest {
|
|||||||
assertTrue(error, egg.mkdirs())
|
assertTrue(error, egg.mkdirs())
|
||||||
assertTrue(error, egg.getChildFile(".replace").createNewFile())
|
assertTrue(error, egg.getChildFile(".replace").createNewFile())
|
||||||
|
|
||||||
|
// Create /system/app/EasterEgg/newfile
|
||||||
|
assertTrue(error, egg.getChildFile("newfile").createNewFile())
|
||||||
|
|
||||||
// Delete /system/bin/screenrecord
|
// Delete /system/bin/screenrecord
|
||||||
val bin = path.getChildFile("system").getChildFile("bin")
|
val bin = path.getChildFile("system").getChildFile("bin")
|
||||||
assertTrue(error, bin.mkdirs())
|
assertTrue(error, bin.mkdirs())
|
||||||
@@ -117,7 +123,9 @@ class Environment : BaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSystemlessHost() {
|
private fun setupSystemlessHost() {
|
||||||
assertTrue("hosts setup failed", Shell.cmd("add_hosts_module").exec().isSuccess)
|
val error = "hosts setup failed"
|
||||||
|
assertTrue(error, runBlocking { RootUtils.addSystemlessHosts() })
|
||||||
|
assertTrue(error, RootUtils.fs.getFile(Const.MODULE_PATH).getChildFile("hosts").exists())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSepolicyRuleModule(root: ExtendedFile) {
|
private fun setupSepolicyRuleModule(root: ExtendedFile) {
|
||||||
@@ -167,12 +175,39 @@ class Environment : BaseTest {
|
|||||||
// Create a new module but mark is as "remove"
|
// Create a new module but mark is as "remove"
|
||||||
val module = LocalModule(path)
|
val module = LocalModule(path)
|
||||||
assertTrue(error, path.mkdirs())
|
assertTrue(error, path.mkdirs())
|
||||||
|
// Create uninstaller script
|
||||||
|
path.getChildFile("uninstall.sh").newOutputStream().writer().use {
|
||||||
|
it.write("touch $REMOVE_TEST_MARKER")
|
||||||
|
}
|
||||||
assertTrue(error, path.getChildFile("service.sh").createNewFile())
|
assertTrue(error, path.getChildFile("service.sh").createNewFile())
|
||||||
module.remove = true
|
module.remove = true
|
||||||
|
|
||||||
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
|
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupUpgradeModule(root: ExtendedFile, update: ExtendedFile) {
|
||||||
|
val error = "$UPGRADE_TEST setup failed"
|
||||||
|
val oldPath = root.getChildFile(UPGRADE_TEST)
|
||||||
|
val newPath = update.getChildFile(UPGRADE_TEST)
|
||||||
|
|
||||||
|
// Create an existing module but mark as "disable
|
||||||
|
val module = LocalModule(oldPath)
|
||||||
|
assertTrue(error, oldPath.mkdirs())
|
||||||
|
module.enable = false
|
||||||
|
// Install service.sh into the old module
|
||||||
|
assertTrue(error, oldPath.getChildFile("service.sh").createNewFile())
|
||||||
|
|
||||||
|
// Create an upgrade module
|
||||||
|
assertTrue(error, newPath.mkdirs())
|
||||||
|
// Install post-fs-data.sh into the new module
|
||||||
|
assertTrue(error, newPath.getChildFile("post-fs-data.sh").createNewFile())
|
||||||
|
|
||||||
|
assertTrue(error, Shell.cmd(
|
||||||
|
"set_default_perm $oldPath",
|
||||||
|
"set_default_perm $newPath",
|
||||||
|
).exec().isSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun setupEnvironment() {
|
fun setupEnvironment() {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
@@ -217,12 +252,14 @@ class Environment : BaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
|
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
|
||||||
if (mount()) { setupMountTest(root) }
|
val update = RootUtils.fs.getFile(MODULE_UPDATE_PATH)
|
||||||
if (preinit()) { setupSepolicyRuleModule(root) }
|
if (mount()) { setupMountTest(update) }
|
||||||
|
if (preinit()) { setupSepolicyRuleModule(update) }
|
||||||
setupSystemlessHost()
|
setupSystemlessHost()
|
||||||
setupEmptyZygiskModule(root)
|
setupEmptyZygiskModule(update)
|
||||||
setupInvalidZygiskModule(root)
|
setupInvalidZygiskModule(update)
|
||||||
setupRemoveModule(root)
|
setupRemoveModule(root)
|
||||||
|
setupUpgradeModule(root, update)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<string name="touch_filtered_warning">Como um app está ocultando uma solicitação de SuperUsuário, o Magisk não pode verificar sua resposta.</string>
|
<string name="touch_filtered_warning">Como um app está ocultando uma solicitação de SuperUsuário, o Magisk não pode verificar sua resposta.</string>
|
||||||
<string name="deny">Negar</string>
|
<string name="deny">Negar</string>
|
||||||
<string name="prompt">Perguntar</string>
|
<string name="prompt">Perguntar</string>
|
||||||
|
<string name="restrict">Restringir</string>
|
||||||
<string name="grant">Permitir</string>
|
<string name="grant">Permitir</string>
|
||||||
<string name="su_warning">Permite acesso total ao seu dispositivo.\nNão permita se você não tiver certeza do que está fazendo!</string>
|
<string name="su_warning">Permite acesso total ao seu dispositivo.\nNão permita se você não tiver certeza do que está fazendo!</string>
|
||||||
<string name="forever">Sempre</string>
|
<string name="forever">Sempre</string>
|
||||||
@@ -170,6 +171,8 @@
|
|||||||
<string name="settings_su_auth_title">Autenticação de usuário</string>
|
<string name="settings_su_auth_title">Autenticação de usuário</string>
|
||||||
<string name="settings_su_auth_summary">Solicite autenticação de usuário durante solicitações de SuperUsuário</string>
|
<string name="settings_su_auth_summary">Solicite autenticação de usuário durante solicitações de SuperUsuário</string>
|
||||||
<string name="settings_su_auth_insecure">Nenhum método de autenticação está configurado no dispositivo</string>
|
<string name="settings_su_auth_insecure">Nenhum método de autenticação está configurado no dispositivo</string>
|
||||||
|
<string name="settings_su_restrict_title">Restringir recursos root</string>
|
||||||
|
<string name="settings_su_restrict_summary">Restringirá novos apps de SuperUsuário por padrão. Aviso: isso quebrará a maioria dos apps. Não ative se você não souber o que está fazendo.</string>
|
||||||
<string name="settings_customization">Personalizações</string>
|
<string name="settings_customization">Personalizações</string>
|
||||||
<string name="setting_add_shortcut_summary">Adicione um atalho na tela inicial, caso o nome e o ícone sejam difíceis de reconhecer logo após ocultar o app.</string>
|
<string name="setting_add_shortcut_summary">Adicione um atalho na tela inicial, caso o nome e o ícone sejam difíceis de reconhecer logo após ocultar o app.</string>
|
||||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<string name="touch_filtered_warning">Como um app está a ocultar um pedido de SuperUsuário, o Magisk não consegue verificar a sua resposta.</string>
|
<string name="touch_filtered_warning">Como um app está a ocultar um pedido de SuperUsuário, o Magisk não consegue verificar a sua resposta.</string>
|
||||||
<string name="deny">Negar</string>
|
<string name="deny">Negar</string>
|
||||||
<string name="prompt">Perguntar</string>
|
<string name="prompt">Perguntar</string>
|
||||||
|
<string name="restrict">Restringir</string>
|
||||||
<string name="grant">Permitir</string>
|
<string name="grant">Permitir</string>
|
||||||
<string name="su_warning">Permite o acesso total ao seu dispositivo.\nNão o permita se não tiver a certeza do que está a fazer!</string>
|
<string name="su_warning">Permite o acesso total ao seu dispositivo.\nNão o permita se não tiver a certeza do que está a fazer!</string>
|
||||||
<string name="forever">Sempre</string>
|
<string name="forever">Sempre</string>
|
||||||
@@ -129,7 +130,7 @@
|
|||||||
<string name="settings_restore_app_title">Restaurar app do Magisk</string>
|
<string name="settings_restore_app_title">Restaurar app do Magisk</string>
|
||||||
<string name="settings_restore_app_summary">Desoculta o app do Magisk e restaura o APK original</string>
|
<string name="settings_restore_app_summary">Desoculta o app do Magisk e restaura o APK original</string>
|
||||||
<string name="language">Idioma</string>
|
<string name="language">Idioma</string>
|
||||||
<string name="system_default">(Padrão do sistema)</string>
|
<string name="system_default">(Predefinição do sistema)</string>
|
||||||
<string name="settings_check_update_title">Verificar por atualizações</string>
|
<string name="settings_check_update_title">Verificar por atualizações</string>
|
||||||
<string name="settings_check_update_summary">Verifique automaticamente se há atualizações ao abrir o app</string>
|
<string name="settings_check_update_summary">Verifique automaticamente se há atualizações ao abrir o app</string>
|
||||||
<string name="settings_update_channel_title">Canal de atualização</string>
|
<string name="settings_update_channel_title">Canal de atualização</string>
|
||||||
@@ -170,6 +171,8 @@
|
|||||||
<string name="settings_su_auth_title">Autenticação de usuário</string>
|
<string name="settings_su_auth_title">Autenticação de usuário</string>
|
||||||
<string name="settings_su_auth_summary">Solicite autenticação de usuário durante pedidos de SuperUsuário</string>
|
<string name="settings_su_auth_summary">Solicite autenticação de usuário durante pedidos de SuperUsuário</string>
|
||||||
<string name="settings_su_auth_insecure">Nenhum método de autenticação está configurado no dispositivo</string>
|
<string name="settings_su_auth_insecure">Nenhum método de autenticação está configurado no dispositivo</string>
|
||||||
|
<string name="settings_su_restrict_title">Restringir recursos root</string>
|
||||||
|
<string name="settings_su_restrict_summary">Restringirá novos apps de SuperUsuário por predefinição. Aviso: isto quebrará a maioria dos apps. Não ative se você não souber o que está a fazer.</string>
|
||||||
<string name="settings_customization">Personalizações</string>
|
<string name="settings_customization">Personalizações</string>
|
||||||
<string name="setting_add_shortcut_summary">Adicione um atalho no ecrã inicial, caso o nome e o ícone sejam difíceis de reconhecer logo após ocultar o app.</string>
|
<string name="setting_add_shortcut_summary">Adicione um atalho no ecrã inicial, caso o nome e o ícone sejam difíceis de reconhecer logo após ocultar o app.</string>
|
||||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -172,7 +172,7 @@
|
|||||||
<string name="settings_su_auth_summary">Ask for user authentication during Superuser requests</string>
|
<string name="settings_su_auth_summary">Ask for user authentication during Superuser requests</string>
|
||||||
<string name="settings_su_auth_insecure">No authentication method is configured on the device</string>
|
<string name="settings_su_auth_insecure">No authentication method is configured on the device</string>
|
||||||
<string name="settings_su_restrict_title">Restrict root capabilities</string>
|
<string name="settings_su_restrict_title">Restrict root capabilities</string>
|
||||||
<string name="settings_su_restrict_summary">Will restrict new superuser apps by default. Warning, this will break most apps, do not enable it.</string>
|
<string name="settings_su_restrict_summary">Will restrict new Superuser apps by default. Warning: this will break most apps. Don\'t enable it unless you know what you\'re doing.</string>
|
||||||
<string name="settings_customization">Customization</string>
|
<string name="settings_customization">Customization</string>
|
||||||
<string name="setting_add_shortcut_summary">Add a pretty shortcut to the home screen in case the name and icon are difficult to recognize after hiding the app</string>
|
<string name="setting_add_shortcut_summary">Add a pretty shortcut to the home screen in case the name and icon are difficult to recognize after hiding the app</string>
|
||||||
<string name="settings_doh_title">DNS over HTTPS</string>
|
<string name="settings_doh_title">DNS over HTTPS</string>
|
||||||
|
|||||||
@@ -30,4 +30,4 @@ android.nonFinalResIds=false
|
|||||||
|
|
||||||
# Magisk
|
# Magisk
|
||||||
magisk.stubVersion=40
|
magisk.stubVersion=40
|
||||||
magisk.versionCode=30100
|
magisk.versionCode=30200
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
[versions]
|
[versions]
|
||||||
kotlin = "2.1.21"
|
kotlin = "2.2.0"
|
||||||
android = "8.11.0"
|
android = "8.12.0"
|
||||||
ksp = "2.1.21-2.0.1"
|
ksp = "2.2.0-2.0.2"
|
||||||
rikka = "1.3.0"
|
rikka = "1.3.0"
|
||||||
navigation = "2.9.0"
|
navigation = "2.9.3"
|
||||||
libsu = "6.0.0"
|
libsu = "6.0.0"
|
||||||
okhttp = "4.12.0"
|
okhttp = "5.1.0"
|
||||||
retrofit = "3.0.0"
|
retrofit = "3.0.0"
|
||||||
room = "2.7.2"
|
room = "2.7.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.81" }
|
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.81" }
|
||||||
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.27.1" }
|
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.28.0" }
|
||||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
|
retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
|
||||||
retrofit-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
|
retrofit-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
|
||||||
@@ -41,9 +41,9 @@ transition = { module = "androidx.transition:transition", version = "1.6.0" }
|
|||||||
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.5.0" }
|
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.5.0" }
|
||||||
material = { module = "com.google.android.material:material", version = "1.12.0" }
|
material = { module = "com.google.android.material:material", version = "1.12.0" }
|
||||||
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.5" }
|
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.5" }
|
||||||
test-runner = { module = "androidx.test:runner", version = "1.6.2" }
|
test-runner = { module = "androidx.test:runner", version = "1.7.0" }
|
||||||
test-rules = { module = "androidx.test:rules", version = "1.6.1" }
|
test-rules = { module = "androidx.test:rules", version = "1.7.0" }
|
||||||
test-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
|
test-junit = { module = "androidx.test.ext:junit", version = "1.3.0" }
|
||||||
test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
|
test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
|
||||||
|
|
||||||
# topjohnwu
|
# topjohnwu
|
||||||
@@ -62,6 +62,6 @@ android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref
|
|||||||
ksp-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
ksp-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
||||||
navigation-safe-args-plugin = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "navigation" }
|
navigation-safe-args-plugin = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "navigation" }
|
||||||
lsparanoid-plugin = { module = "org.lsposed.lsparanoid:gradle-plugin", version = "0.6.0" }
|
lsparanoid-plugin = { module = "org.lsposed.lsparanoid:gradle-plugin", version = "0.6.0" }
|
||||||
moshi-plugin = { module = "dev.zacsweers.moshix:dev.zacsweers.moshix.gradle.plugin", version = "0.30.0" }
|
moshi-plugin = { module = "dev.zacsweers.moshix:dev.zacsweers.moshix.gradle.plugin", version = "0.31.0" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|||||||
2
app/gradle/wrapper/gradle-wrapper.properties
vendored
2
app/gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
- [Installation Instructions](install.md)
|
- [Installation Instructions](install.md)
|
||||||
- [Frequently Asked Questions](faq.md)
|
- [Frequently Asked Questions](faq.md)
|
||||||
- [Release Notes](releases/index.md)
|
|
||||||
- [Magisk Changelog](changes.md)
|
- [Magisk Changelog](changes.md)
|
||||||
|
|
||||||
The following sections are for developers
|
The following sections are for developers
|
||||||
|
|||||||
165
docs/changes.md
165
docs/changes.md
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,17 +0,0 @@
|
|||||||
# 2019.2.4 Magisk v18.1
|
|
||||||
|
|
||||||
What is a better way to celebrate Chinese New Year than a new Magisk update!
|
|
||||||
|
|
||||||
### EMUI 9 Support
|
|
||||||
Welcome on board "again", Huawei! Even though Huawei had officially blocked bootloader unlocks, people still love to buy them (duh), and there are paid services that unlock Huawei bootloaders. So hey, get Magisk installed on that bad boy! One caveat is that since Huawei have changed the partitions, special workarounds has to be done. Details and instructions are in the newly created [instruction page](https://topjohnwu.github.io/Magisk/install.html)
|
|
||||||
|
|
||||||
### Support Down to Android 4.2
|
|
||||||
Because why not, it was quite a lot of fun LOL. All devices running KitKat and higher will have all features enabled. MagiskHide and resetprop aren't possible on Jellybean, and Magic Mount (modules) is temporarily disabled; basically it only works as a root solution for now. Android 4.1 isn't 100% usable yet, so installation is also temporarily blocked. Eventually, all Jellybean devices will have full Magic Mount and MagiskSU support.
|
|
||||||
|
|
||||||
### Major Magisk Manager Update
|
|
||||||
Aside from the obvious major UI overhaul, tons of little user experience and performance improvements are also added. The app is finally less crappy now :)
|
|
||||||
|
|
||||||
### Final Words
|
|
||||||
I'm aware that there are apps updated to detect Magisk, however no MagiskHide improvements efforts are done in this release; v18.1 is aimed to be as stable as possible. Stay tuned for future public betas, or if you are more adventurous, jump on the Canary Channel bandwagon for more aggressive hiding techniques :)
|
|
||||||
|
|
||||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,12 +0,0 @@
|
|||||||
# 2019.5.1 Magisk v19.1
|
|
||||||
Finally, a lovely stable release!
|
|
||||||
|
|
||||||
For those that were using v18.1, here are some quick highlights of v19.0
|
|
||||||
|
|
||||||
- Imageless Magisk: Although module migration was tested, there are still chances that your modules will get lost in the process. Be prepared to reinstall your existing modules in that case.
|
|
||||||
- Native 64-bit support
|
|
||||||
- Zygote Ptrace Based MagiskHide
|
|
||||||
|
|
||||||
Other than adding support for Samsung system-as-root devices, this release is mostly bug fixes from v19.0. Enjoy :)
|
|
||||||
|
|
||||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# TODO
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# TODO
|
|
||||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user