Compare commits

...

39 Commits
v25.0 ... v25.1

Author SHA1 Message Date
topjohnwu
bb64ba0ef6 Release Magisk v25.1 2022-06-19 02:54:22 -07:00
topjohnwu
d89a568897 Update v25.1 docs 2022-06-19 02:35:05 -07:00
topjohnwu
9fd1f41e8b Always relaunch process after package migration 2022-06-19 02:09:14 -07:00
孟武.尼德霍格.龍
c1ab348673 Improve Traditional Chinese strings
Co-authored-by: John Wu <topjohnwu@gmail.com>
2022-06-19 01:50:43 -07:00
canyie
00247c7901 Fix meizu non-SAR 2SI compatibility again
Meizu devices using 2SI won't switch root to /system and still on rootfs, and /init is the 1st stage's, which cannot handle the 2nd stage. So we have to manually execute /system/bin/init for the 2nd stage.
2022-06-19 01:22:18 -07:00
topjohnwu
3c75f474c6 Embed version info in prop format 2022-06-19 00:43:38 -07:00
topjohnwu
db1f5b0397 Reduce files relying on flags.h 2022-06-19 00:43:38 -07:00
fadlyas07
db277c3e55 app: l10n: Update Indonesian translations
* Added new strings based on the recent source.
* Fixed some words based on Indonesian National Dictionary (KBBI).

Link: https://kbbi.kemdikbud.go.id
Signed-off-by: fadlyas07 <mhmmdfdlyas@gmail.com>
2022-06-18 10:43:25 -07:00
vvb2060
b9c93c66f6 Force app version not lower than daemon 2022-06-17 11:53:16 -07:00
vvb2060
a250e2b56c Set version comment in apk 2022-06-17 11:53:16 -07:00
残页
cd96454886 Fix finding recovery image on direct install
Fix #5972, fix #5673
2022-06-17 02:53:18 -07:00
topjohnwu
741b679306 Cleanup libbase 2022-06-17 02:36:04 -07:00
topjohnwu
90013e486d Use AtomicBoolean 2022-06-17 02:03:09 -07:00
LoveSy
4e2ecdb920 Fix env overflow
Fix #5989
2022-06-17 02:02:44 -07:00
topjohnwu
6e5df1f06b Abort when unsupported dtb is detected 2022-06-16 01:47:23 -07:00
topjohnwu
9469e79e3c Proper namespacing
The IDE will get confused when #include is in a namespace
2022-06-15 02:38:56 -07:00
topjohnwu
db78c20161 Add dtb test command 2022-06-15 02:26:50 -07:00
topjohnwu
1699da1754 Update help message and make behavior consistent 2022-06-14 21:19:17 -07:00
canyie
754e690274 Fix config backup for legacy SAR 2022-06-14 02:57:47 -07:00
topjohnwu
6f74ed6ceb Cleanup manager.sh 2022-06-13 01:21:24 -07:00
canyie
71205bc530 Anchor Snackbar above reboot FAB in FlashFragment 2022-06-12 11:00:36 -07:00
Chris Renshaw
10e236abdf scripts: fix remaining instances of && ||
Looks like I may have missed this in ce84f1762c originally
2022-06-12 11:00:09 -07:00
残页
2248af00f3 Fix #5673
util_functions.sh overrides `get_flags` function (defined in manager.sh), which sets `RECOVERYMODE` and causes `check_boot_ramdisk` not overriding the incorrect value.
2022-06-12 00:32:34 -07:00
topjohnwu
7e61716277 Update Kotlin to 1.7.0 2022-06-11 03:41:02 -07:00
topjohnwu
50edb8d072 Better network detection and invalidation 2022-06-10 04:25:34 -07:00
topjohnwu
515f81944c Move coroutine job into its own class 2022-06-10 04:12:31 -07:00
topjohnwu
46d4708386 Decouple state from BaseViewModel 2022-06-10 02:13:25 -07:00
topjohnwu
aabc36f86b Maintain separate flash screen state 2022-06-10 00:33:53 -07:00
nikk gitanes
e0d5d90267 fix restart button focus on flash result 2022-06-10 00:33:53 -07:00
topjohnwu
482a5b991b Don't always refresh on network state change 2022-06-09 23:28:46 -07:00
CDzungx
20124fe410 Update vi translation 2022-06-09 21:03:26 -07:00
Softastur
f8dcec116a Fix Asturian translation 2022-06-09 21:03:09 -07:00
Ilya Kushnir
343a339aae Update RU strings (fix) 2022-06-09 21:02:45 -07:00
vvb2060
42606efe56 Always remove task 2022-06-09 21:02:31 -07:00
vvb2060
cae58c8790 Update hijack bins 2022-06-08 23:30:22 -07:00
topjohnwu
3a39dd4049 Update ramdisk restore implementation 2022-06-08 23:23:39 -07:00
canyie
89ff3c6572 Don't backup ramdisk created by Magisk
Fix topjohnwu#5938, fix topjohnwu#5944
2022-06-08 04:53:43 -07:00
topjohnwu
7bf9c74216 Don't skip backup even if original does not exist
Close #5945, fix #5944
2022-06-08 03:58:25 -07:00
topjohnwu
e2f3753551 Release new canary build 2022-06-07 03:36:21 -07:00
65 changed files with 726 additions and 566 deletions

View File

@@ -19,7 +19,7 @@ Some highlight features:
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
[![](https://img.shields.io/badge/Magisk-v24.3-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v24.3)
[![](https://img.shields.io/badge/Magisk%20Beta-v24.3-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v24.3)
[![](https://img.shields.io/badge/Magisk%20Beta-v25.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v25.0)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-release.apk)
[![](https://img.shields.io/badge/Magisk-Debug-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)

View File

@@ -97,7 +97,7 @@ dependencies {
implementation("com.squareup.moshi:moshi:${vMoshi}")
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
val vRoom = "2.4.2"
val vRoom = "2.5.0-alpha02"
implementation("androidx.room:room-runtime:${vRoom}")
implementation("androidx.room:room-ktx:${vRoom}")
kapt("androidx.room:room-compiler:${vRoom}")
@@ -109,12 +109,12 @@ dependencies {
implementation("androidx.biometric:biometric:1.1.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("androidx.appcompat:appcompat:1.4.2")
implementation("androidx.preference:preference:1.2.0")
implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation("androidx.fragment:fragment-ktx:1.4.1")
implementation("androidx.transition:transition:1.4.1")
implementation("androidx.core:core-ktx:1.7.0")
implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.core:core-splashscreen:1.0.0-rc01")
implementation("com.google.android.material:material:1.6.0")
implementation("com.google.android.material:material:1.6.1")
}

View File

@@ -27,6 +27,7 @@
android:name=".ui.surequest.SuRequestActivity"
android:directBootAware="true"
android:exported="false"
android:taskAffinity=""
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

View File

@@ -0,0 +1,22 @@
package com.topjohnwu.magisk.arch
import androidx.annotation.MainThread
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
abstract class AsyncLoadViewModel : BaseViewModel() {
private var loadingJob: Job? = null
@MainThread
fun startLoading() {
if (loadingJob?.isActive == true) {
// Prevent multiple jobs from running at the same time
return
}
loadingJob = viewModelScope.launch { doLoadWork() }
}
protected abstract suspend fun doLoadWork()
}

View File

@@ -76,7 +76,10 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
override fun onResume() {
super.onResume()
viewModel.requestRefresh()
viewModel.let {
if (it is AsyncLoadViewModel)
it.startLoading()
}
}
protected open fun onPreBind(binding: Binding) {

View File

@@ -4,61 +4,29 @@ import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.databinding.Bindable
import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ObservableHost
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.BackPressEvent
import com.topjohnwu.magisk.events.NavigationEvent
import com.topjohnwu.magisk.events.PermissionEvent
import com.topjohnwu.magisk.events.SnackbarEvent
import kotlinx.coroutines.Job
abstract class BaseViewModel(
initialState: State = State.LOADING
) : ViewModel(), ObservableHost {
abstract class BaseViewModel : ViewModel(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null
enum class State {
LOADED, LOADING, LOADING_FAILED
}
@get:Bindable
val loading get() = state == State.LOADING
@get:Bindable
val loaded get() = state == State.LOADED
@get:Bindable
val loadFailed get() = state == State.LOADING_FAILED
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
var state= initialState
set(value) = set(value, field, { field = it }, BR.loading, BR.loaded, BR.loadFailed)
private val _viewEvents = MutableLiveData<ViewEvent>()
private var runningJob: Job? = null
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
open fun onSaveState(state: Bundle) {}
open fun onRestoreState(state: Bundle) {}
/** This should probably never be called manually, it's called manually via delegate. */
@Synchronized
fun requestRefresh() {
if (runningJob?.isActive == true) {
return
}
runningJob = refresh()
}
protected open fun refresh(): Job? = null
open fun onNetworkChanged(network: Boolean) {}
fun withPermission(permission: String, callback: (Boolean) -> Unit) {
PermissionEvent(permission, callback).publish()

View File

@@ -89,7 +89,10 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
override fun onResume() {
super.onResume()
viewModel.requestRefresh()
viewModel.let {
if (it is AsyncLoadViewModel)
it.startLoading()
}
}
override fun onEventDispatched(event: ViewEvent) = when (event) {

View File

@@ -17,12 +17,8 @@ interface ViewModelHolder : LifecycleOwner, ViewModelStoreOwner {
val viewModel: BaseViewModel
fun startObserveLiveData() {
viewModel.viewEvents.observe(this) {
onEventDispatched(it)
}
Info.isConnected.observe(this) {
viewModel.requestRefresh()
}
viewModel.viewEvents.observe(this, this::onEventDispatched)
Info.isConnected.observe(this, viewModel::onNetworkChanged)
}
/**

View File

@@ -10,7 +10,6 @@ import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.magisk.core.utils.net.NetworkObserver
import com.topjohnwu.magisk.ktx.getProperty
import com.topjohnwu.superuser.ShellUtils.fastCmd
import com.topjohnwu.superuser.internal.UiThreadHandler
val isRunningAsStub get() = Info.stub != null
@@ -47,7 +46,8 @@ object Info {
val isConnected: LiveData<Boolean> by lazy {
MutableLiveData(false).also { field ->
NetworkObserver.observe(AppContext) {
UiThreadHandler.run { field.value = it }
remote = EMPTY_REMOTE
field.postValue(it)
}
}
}

View File

@@ -46,7 +46,10 @@ class NetworkService(
private inline fun <T> safe(factory: () -> T): T? {
return try {
factory()
if (Info.isConnected.value == true)
factory()
else
null
} catch (e: Exception) {
Timber.e(e)
null

View File

@@ -37,6 +37,7 @@ import java.io.*
import java.nio.ByteBuffer
import java.security.SecureRandom
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
@@ -57,7 +58,7 @@ abstract class MagiskInstallImpl protected constructor(
private val localFS get() = FileSystemManager.getLocal()
private fun findImage(): Boolean {
val bootPath = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
val bootPath = "RECOVERYMODE=${Config.recovery} find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
if (bootPath.isEmpty()) {
console.add("! Unable to detect target image")
return false
@@ -420,20 +421,15 @@ abstract class MagiskInstallImpl protected constructor(
protected abstract suspend fun operations(): Boolean
open suspend fun exec(): Boolean {
synchronized(Companion) {
if (haveActiveSession)
return false
haveActiveSession = true
}
if (haveActiveSession.getAndSet(true))
return false
val result = withContext(Dispatchers.IO) { operations() }
synchronized(Companion) {
haveActiveSession = false
}
haveActiveSession.set(false)
return result
}
companion object {
private var haveActiveSession = false
private var haveActiveSession = AtomicBoolean(false)
}
}

View File

@@ -9,6 +9,7 @@ import androidx.databinding.ViewDataBinding
import androidx.lifecycle.lifecycleScope
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.arch.NavigationActivity
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
@@ -98,10 +99,10 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
Config.load(prevPkg)
handleRepackage(prevPkg)
if (prevPkg != null && !isRunningAsStub) {
if (prevPkg != null) {
runOnUiThread {
// Might have new configuration loaded, relaunch the activity
relaunch()
// Relaunch the process after package migration
StubApk.restartProcess(this)
}
return
}

View File

@@ -2,23 +2,22 @@ package com.topjohnwu.magisk.ui.deny
import android.annotation.SuppressLint
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import androidx.lifecycle.viewModelScope
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.filterableListOf
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.concurrentMap
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.toCollection
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class DenyListViewModel : BaseViewModel() {
class DenyListViewModel : AsyncLoadViewModel() {
var isShowSystem = false
set(value) {
@@ -43,13 +42,13 @@ class DenyListViewModel : BaseViewModel() {
it.put(BR.viewModel, this)
}
@get:Bindable
var loading = true
private set(value) = set(value, field, { field = it }, BR.loading)
@SuppressLint("InlinedApi")
override fun refresh() = viewModelScope.launch {
if (!Utils.showSuperUser()) {
state = State.LOADING_FAILED
return@launch
}
state = State.LOADING
override suspend fun doLoadWork() {
loading = true
val (apps, diff) = withContext(Dispatchers.Default) {
val pm = AppContext.packageManager
val denyList = Shell.cmd("magisk --denylist ls").exec().out
@@ -84,6 +83,6 @@ class DenyListViewModel : BaseViewModel() {
(it.isChecked || (filterSystem() && filterOS())) && filterQuery()
}
state = State.LOADED
loading = false
}
}

View File

@@ -6,6 +6,7 @@ import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import android.view.*
import androidx.core.view.isVisible
import androidx.navigation.NavDeepLinkBuilder
import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R
@@ -21,6 +22,8 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
override val layoutRes = R.layout.fragment_flash_md2
override val viewModel by viewModel<FlashViewModel>()
override val snackbarView: View get() = binding.snackbarContainer
override val snackbarAnchorView: View?
get() = if (binding.restartBtn.isShown) binding.restartBtn else super.snackbarAnchorView
private var defaultOrientation = -1
@@ -34,8 +37,20 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
setHasOptionsMenu(true)
activity?.setTitle(R.string.flash_screen_title)
viewModel.subtitle.observe(this) {
activity?.supportActionBar?.setSubtitle(it)
viewModel.state.observe(this) {
activity?.supportActionBar?.setSubtitle(
when (it) {
FlashViewModel.State.FLASHING -> R.string.flashing
FlashViewModel.State.SUCCESS -> R.string.done
FlashViewModel.State.FAILED -> R.string.failure
}
)
if (it == FlashViewModel.State.SUCCESS && viewModel.showReboot) {
binding.restartBtn.apply {
if (!this.isVisible) this.show()
if (!this.isFocused) this.requestFocus()
}
}
}
}
@@ -66,7 +81,7 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
}
override fun onKeyEvent(event: KeyEvent): Boolean {
return when(event.keyCode) {
return when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP,
KeyEvent.KEYCODE_VOLUME_DOWN -> true
else -> false
@@ -74,7 +89,8 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
}
override fun onBackPressed(): Boolean {
if (viewModel.loading) return true
if (viewModel.flashing.value == true)
return true
return super.onBackPressed()
}

View File

@@ -4,6 +4,7 @@ import android.view.MenuItem
import androidx.databinding.Bindable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
@@ -27,13 +28,18 @@ import kotlinx.coroutines.launch
class FlashViewModel : BaseViewModel() {
enum class State {
FLASHING, SUCCESS, FAILED
}
private val _state = MutableLiveData(State.FLASHING)
val state: LiveData<State> get() = _state
val flashing = Transformations.map(state) { it == State.FLASHING }
@get:Bindable
var showReboot = Info.isRooted
set(value) = set(value, field, { field = it }, BR.showReboot)
private val _subtitle = MutableLiveData(R.string.flashing)
val subtitle get() = _subtitle as LiveData<Int>
val items = diffListOf<ConsoleItem>()
lateinit var args: FlashFragmentArgs
@@ -83,11 +89,7 @@ class FlashViewModel : BaseViewModel() {
}
private fun onResult(success: Boolean) {
state = if (success) State.LOADED else State.LOADING_FAILED
when {
success -> _subtitle.postValue(R.string.done)
else -> _subtitle.postValue(R.string.failure)
}
_state.value = if (success) State.SUCCESS else State.FAILED
}
fun onMenuItemClicked(item: MenuItem): Boolean {
@@ -100,7 +102,8 @@ class FlashViewModel : BaseViewModel() {
private fun savePressed() = withExternalRW {
viewModelScope.launch(Dispatchers.IO) {
val name = "magisk_install_log_%s.log".format(
System.currentTimeMillis().toTime(timeFormatStandard))
System.currentTimeMillis().toTime(timeFormatStandard)
)
val file = MediaStoreUtils.getFile(name, true)
file.uri.outputStream().bufferedWriter().use { writer ->
synchronized(logItems) {

View File

@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.home
import android.content.Context
import androidx.core.net.toUri
import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R
@@ -23,16 +22,15 @@ import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
enum class MagiskState {
NOT_INSTALLED, UP_TO_DATE, OBSOLETE, LOADING
}
class HomeViewModel(
private val svc: NetworkService
) : BaseViewModel() {
) : AsyncLoadViewModel() {
enum class State {
LOADING, INVALID, OUTDATED, UP_TO_DATE
}
val magiskTitleBarrierIds =
intArrayOf(R.id.home_magisk_icon, R.id.home_magisk_title, R.id.home_magisk_button)
@@ -43,16 +41,16 @@ class HomeViewModel(
var isNoticeVisible = Config.safetyNotice
set(value) = set(value, field, { field = it }, BR.noticeVisible)
val stateMagisk
val magiskState
get() = when {
!Info.env.isActive -> MagiskState.NOT_INSTALLED
Info.env.versionCode < BuildConfig.VERSION_CODE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
!Info.env.isActive -> State.INVALID
Info.env.versionCode < BuildConfig.VERSION_CODE -> State.OUTDATED
else -> State.UP_TO_DATE
}
@get:Bindable
var stateManager = MagiskState.LOADING
set(value) = set(value, field, { field = it }, BR.stateManager)
var appState = State.LOADING
set(value) = set(value, field, { field = it }, BR.appState)
val magiskInstalledVersion
get() = Info.env.run {
@@ -83,14 +81,12 @@ class HomeViewModel(
private var checkedEnv = false
}
override fun refresh() = viewModelScope.launch {
state = State.LOADING
override suspend fun doLoadWork() {
appState = State.LOADING
Info.getRemote(svc)?.apply {
state = State.LOADED
stateManager = when {
BuildConfig.VERSION_CODE < magisk.versionCode -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
appState = when {
BuildConfig.VERSION_CODE < magisk.versionCode -> State.OUTDATED
else -> State.UP_TO_DATE
}
val isDebug = Config.updateChannel == Config.Value.DEBUG_CHANNEL
@@ -98,19 +94,12 @@ class HomeViewModel(
("${magisk.version} (${magisk.versionCode}) (${stub.versionCode})" +
if (isDebug) " (D)" else "").asText()
} ?: run {
state = State.LOADING_FAILED
managerRemoteVersion = R.string.not_available.asText()
}
ensureEnv()
}
val showTest = false
fun onTestPressed() = object : ViewEvent(), ActivityExecutor {
override fun invoke(activity: UIActivity<*>) {
/* Entry point to trigger test events within the app */
}
}.publish()
override fun onNetworkChanged(network: Boolean) = startLoading()
fun onProgressUpdate(progress: Float, subject: Subject) {
if (subject is App)
@@ -123,14 +112,14 @@ class HomeViewModel(
fun onDeletePressed() = UninstallDialog().publish()
fun onManagerPressed() = when (state) {
State.LOADED -> withExternalRW {
fun onManagerPressed() = when (magiskState) {
State.LOADING -> SnackbarEvent(R.string.loading).publish()
State.INVALID -> SnackbarEvent(R.string.no_connection).publish()
else -> withExternalRW {
withInstallPermission {
ManagerInstallDialog().publish()
}
}
State.LOADING -> SnackbarEvent(R.string.loading).publish()
else -> SnackbarEvent(R.string.no_connection).publish()
}
fun onMagiskPressed() = withExternalRW {
@@ -143,7 +132,7 @@ class HomeViewModel(
}
private suspend fun ensureEnv() {
if (MagiskState.NOT_INSTALLED == stateMagisk || checkedEnv) return
if (magiskState == State.INVALID || checkedEnv) return
val cmd = "env_check ${Info.env.versionString} ${Info.env.versionCode}"
if (!Shell.cmd(cmd).await().isSuccess) {
EnvFixDialog(this).publish()
@@ -151,4 +140,10 @@ class HomeViewModel(
checkedEnv = true
}
val showTest = false
fun onTestPressed() = object : ViewEvent(), ActivityExecutor {
override fun invoke(activity: UIActivity<*>) {
/* Entry point to trigger test events within the app */
}
}.publish()
}

View File

@@ -95,7 +95,6 @@ class InstallViewModel(
R.id.method_inactive_slot -> FlashFragment.flash(true).navigate(true)
else -> error("Unknown value")
}
state = State.LOADING
}
override fun onSaveState(state: Bundle) {

View File

@@ -5,7 +5,7 @@ import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.repository.LogRepository
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
@@ -24,7 +24,7 @@ import java.io.FileInputStream
class LogViewModel(
private val repo: LogRepository
) : BaseViewModel() {
) : AsyncLoadViewModel() {
// --- empty view
@@ -43,7 +43,7 @@ class LogViewModel(
var consoleText = " "
set(value) = set(value, field, { field = it }, BR.consoleText)
override fun refresh() = viewModelScope.launch {
override suspend fun doLoadWork() {
consoleText = repo.fetchMagiskLogs()
val (suLogs, diff) = withContext(Dispatchers.Default) {
val suLogs = repo.fetchSuLogs().map { LogRvItem(it) }
@@ -89,12 +89,12 @@ class LogViewModel(
fun clearMagiskLog() = repo.clearMagiskLogs {
SnackbarEvent(R.string.logs_cleared).publish()
requestRefresh()
startLoading()
}
fun clearLog() = viewModelScope.launch {
repo.clearLogs()
SnackbarEvent(R.string.logs_cleared).publish()
requestRefresh()
startLoading()
}
}

View File

@@ -1,29 +1,24 @@
package com.topjohnwu.magisk.ui.module
import android.net.Uri
import androidx.databinding.Bindable
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.ContentResultCallback
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.databinding.MergeObservableList
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.*
import com.topjohnwu.magisk.events.GetContentEvent
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.ModuleInstallDialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
class ModuleViewModel : BaseViewModel() {
class ModuleViewModel : AsyncLoadViewModel() {
val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove)
@@ -36,6 +31,10 @@ class ModuleViewModel : BaseViewModel() {
val data get() = uri
@get:Bindable
var loading = true
private set(value) = set(value, field, { field = it }, BR.loading)
init {
if (Info.env.isActive && LocalModule.loaded()) {
items.insertItem(InstallModule)
@@ -43,15 +42,15 @@ class ModuleViewModel : BaseViewModel() {
}
}
override fun refresh(): Job {
return viewModelScope.launch {
state = State.LOADING
loadInstalled()
state = State.LOADED
loadUpdateInfo()
}
override suspend fun doLoadWork() {
loading = true
loadInstalled()
loading = false
loadUpdateInfo()
}
override fun onNetworkChanged(network: Boolean) = startLoading()
private suspend fun loadInstalled() {
val installed = LocalModule.installed().map { LocalModuleRvItem(it) }
val diff = withContext(Dispatchers.Default) {

View File

@@ -4,20 +4,18 @@ import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import android.os.Process
import androidx.databinding.Bindable
import androidx.databinding.ObservableArrayList
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.AnyDiffRvItem
import com.topjohnwu.magisk.databinding.MergeObservableList
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.*
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
import com.topjohnwu.magisk.events.dialog.SuperuserRevokeDialog
@@ -31,7 +29,7 @@ import kotlinx.coroutines.withContext
class SuperuserViewModel(
private val db: PolicyDao
) : BaseViewModel() {
) : AsyncLoadViewModel() {
private val itemNoData = TextItem(R.string.superuser_policy_none)
@@ -45,15 +43,17 @@ class SuperuserViewModel(
it.put(BR.listener, this)
}
// ---
@get:Bindable
var loading = true
private set(value) = set(value, field, { field = it }, BR.loading)
@SuppressLint("InlinedApi")
override fun refresh() = viewModelScope.launch {
override suspend fun doLoadWork() {
if (!Utils.showSuperUser()) {
state = State.LOADING_FAILED
return@launch
loading = false
return
}
state = State.LOADING
loading = true
val (policies, diff) = withContext(Dispatchers.IO) {
db.deleteOutdated()
db.delete(AppContext.applicationInfo.uid)
@@ -98,7 +98,7 @@ class SuperuserViewModel(
itemsHelpers.clear()
else if (itemsHelpers.isEmpty())
itemsHelpers.add(itemNoData)
state = State.LOADED
loading = false
}
// ---

View File

@@ -24,13 +24,9 @@ open class SuRequestActivity : UIActivity<ActivityRequestBinding>() {
override val layoutRes: Int = R.layout.activity_request
override val viewModel: SuRequestViewModel by viewModel()
override fun onBackPressed() {
viewModel.denyPressed()
}
override fun onCreate(savedInstanceState: Bundle?) {
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
lockOrientation()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -62,7 +58,11 @@ open class SuRequestActivity : UIActivity<ActivityRequestBinding>() {
return theme
}
private fun lockOrientation() {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
override fun onBackPressed() {
viewModel.denyPressed()
}
override fun finish() {
super.finishAndRemoveTask()
}
}

View File

@@ -24,7 +24,7 @@
android:paddingTop="@dimen/internal_action_bar_size"
app:fitsSystemWindowsInsets="top|bottom"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:invisibleUnless="@{viewModel.loaded}"
app:invisible="@{viewModel.loading}"
app:items="@{viewModel.items}"
app:extraBindings="@{viewModel.extraBindings}"
tools:listitem="@layout/item_hide_md2"
@@ -52,24 +52,6 @@
</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"
tools:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/not_available"
android:textAppearance="@style/AppearanceFoundation.Title"
android:textStyle="bold" />
</LinearLayout>
</FrameLayout>
</layout>

View File

@@ -24,13 +24,13 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/flash_content"
app:items="@{viewModel.items}"
scrollToLast="@{true}"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
app:fitsSystemWindowsInsets="start|end|bottom"
app:items="@{viewModel.items}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_console_md2" />
@@ -38,27 +38,31 @@
</HorizontalScrollView>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
gone="@{!viewModel.loaded || !viewModel.showReboot}"
android:id="@+id/restart_btn"
gone="@{viewModel.flashing || !viewModel.showReboot}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/l1"
android:layout_marginBottom="@dimen/l1"
android:clickable="@{!viewModel.flashing}"
android:enabled="@{!viewModel.flashing}"
android:focusable="true"
android:onClick="@{() -> viewModel.restartPressed()}"
android:text="@string/reboot"
android:textAllCaps="false"
android:textColor="?colorOnPrimary"
android:textStyle="bold"
app:layout_fitsSystemWindowsInsets="bottom"
app:backgroundTint="?colorPrimary"
app:icon="@drawable/ic_restart"
app:iconTint="?colorOnPrimary" />
app:iconTint="?colorOnPrimary"
app:layout_fitsSystemWindowsInsets="bottom" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/snackbar_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/snackbar_container"
app:fitsSystemWindowsInsets="top|bottom"/>
app:fitsSystemWindowsInsets="top|bottom" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -7,8 +7,6 @@
<import type="com.topjohnwu.magisk.core.Info" />
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
<import type="com.topjohnwu.magisk.ui.home.DeveloperItem" />
<import type="com.topjohnwu.magisk.ui.home.IconLink" />

View File

@@ -19,7 +19,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/superuser_list"
goneUnless="@{viewModel.loaded || !viewModel.items.empty}"
gone="@{viewModel.loading}"
app:items="@{viewModel.items}"
app:extraBindings="@{viewModel.extraBindings}"
android:layout_width="match_parent"
@@ -33,7 +33,7 @@
tools:listitem="@layout/item_policy_md2" />
<LinearLayout
goneUnless="@{viewModel.loading &amp;&amp; viewModel.items.empty}"
goneUnless="@{viewModel.loading}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
@@ -54,24 +54,6 @@
</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"
tools:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/not_available"
android:textAppearance="@style/AppearanceFoundation.Title"
android:textStyle="bold" />
</LinearLayout>
</FrameLayout>
</layout>

View File

@@ -7,7 +7,7 @@
<import type="com.topjohnwu.magisk.core.Info" />
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
<import type="com.topjohnwu.magisk.ui.home.HomeViewModel.State" />
<variable
name="viewModel"
@@ -63,7 +63,7 @@
<Button
style="@style/WidgetFoundation.Button"
gone="@{viewModel.stateMagisk != MagiskState.OBSOLETE}"
gone="@{viewModel.magiskState != State.OUTDATED}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.onMagiskPressed()}"
@@ -74,7 +74,7 @@
<Button
style="@style/WidgetFoundation.Button.Text"
gone="@{viewModel.stateMagisk == MagiskState.OBSOLETE}"
gone="@{viewModel.magiskState == State.OUTDATED}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"

View File

@@ -7,7 +7,7 @@
<import type="com.topjohnwu.magisk.core.Info" />
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
<import type="com.topjohnwu.magisk.ui.home.HomeViewModel.State" />
<variable
name="viewModel"
@@ -64,7 +64,7 @@
<Button
style="@style/WidgetFoundation.Button"
gone="@{viewModel.stateManager != MagiskState.OBSOLETE}"
gone="@{viewModel.appState != State.OUTDATED}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.onManagerPressed()}"
@@ -75,7 +75,7 @@
<Button
style="@style/WidgetFoundation.Button.Text"
gone="@{viewModel.stateManager != MagiskState.UP_TO_DATE}"
gone="@{viewModel.appState != State.UP_TO_DATE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"

View File

@@ -10,7 +10,7 @@ env_check() {
for file in busybox magiskboot magiskinit util_functions.sh boot_patch.sh; do
[ -f "$MAGISKBIN/$file" ] || return 1
done
if [ "$2" -ge 24302 ]; then
if [ "$2" -ge 25000 ]; then
[ -f "$MAGISKBIN/magiskpolicy" ] || return 1
fi
grep -xqF "MAGISK_VER='$1'" "$MAGISKBIN/util_functions.sh" || return 1
@@ -99,7 +99,8 @@ post_ota() {
rm -f $1
chmod 755 bootctl
./bootctl hal-info || return
[ $(./bootctl get-current-slot) -eq 0 ] && SLOT_NUM=1 || SLOT_NUM=0
SLOT_NUM=0
[ $(./bootctl get-current-slot) -eq 0 ] && SLOT_NUM=1
./bootctl set-active-boot-slot $SLOT_NUM
cat << EOF > post-fs-data.d/post_ota.sh
/data/adb/bootctl mark-boot-successful
@@ -142,18 +143,16 @@ adb_pm_install() {
check_boot_ramdisk() {
# Create boolean ISAB
[ -z $SLOT ] && ISAB=false || ISAB=true
# If we are running as recovery mode, then we do not have ramdisk
[ "$RECOVERYMODE" = "true" ] && return 1
ISAB=true
[ -z $SLOT ] && ISAB=false
# If we are A/B, then we must have ramdisk
$ISAB && return 0
# If we are using legacy SAR, but not A/B, assume we do not have ramdisk
if grep ' / ' /proc/mounts | grep -q '/dev/root'; then
# Override recovery mode to true if not set
[ -z $RECOVERYMODE ] && RECOVERYMODE=true
# Override recovery mode to true
RECOVERYMODE=true
return 1
fi
@@ -173,7 +172,8 @@ check_encryption() {
CRYPTOTYPE="file"
else
# We are either FDE or metadata encryption (which is also FBE)
grep -q ' /metadata ' /proc/mounts && CRYPTOTYPE="file" || CRYPTOTYPE="block"
CRYPTOTYPE="block"
grep -q ' /metadata ' /proc/mounts && CRYPTOTYPE="file"
fi
fi
fi
@@ -189,12 +189,14 @@ check_encryption() {
mount_partitions() {
[ "$(getprop ro.build.ab_update)" = "true" ] && SLOT=$(getprop ro.boot.slot_suffix)
# Check whether non rootfs root dir exists
grep ' / ' /proc/mounts | grep -qv 'rootfs' && SYSTEM_ROOT=true || SYSTEM_ROOT=false
SYSTEM_ROOT=false
grep ' / ' /proc/mounts | grep -qv 'rootfs' && SYSTEM_ROOT=true
}
get_flags() {
KEEPVERITY=$SYSTEM_ROOT
[ "$(getprop ro.crypto.state)" = "encrypted" ] && ISENCRYPTED=true || ISENCRYPTED=false
ISENCRYPTED=false
[ "$(getprop ro.crypto.state)" = "encrypted" ] && ISENCRYPTED=true
KEEPFORCEENCRYPT=$ISENCRYPTED
# Although this most certainly won't work without root, keep it just in case
if [ -e /dev/block/by-name/vbmeta_a ] || [ -e /dev/block/by-name/vbmeta ]; then
@@ -204,7 +206,8 @@ get_flags() {
fi
# Preset PATCHVBMETAFLAG to false in the non-root case
PATCHVBMETAFLAG=false
# Do NOT preset RECOVERYMODE here
# Make sure RECOVERYMODE has value
[ -z $RECOVERYMODE ] && RECOVERYMODE=false
}
run_migrations() { return; }
@@ -217,13 +220,12 @@ grep_prop() { return; }
app_init() {
mount_partitions
RAMDISKEXIST=false
check_boot_ramdisk && RAMDISKEXIST=true
get_flags
run_migrations
SHA1=$(grep_prop SHA1 $MAGISKTMP/config)
check_boot_ramdisk && RAMDISKEXIST=true || RAMDISKEXIST=false
check_encryption
# Make sure RECOVERYMODE has value
[ -z $RECOVERYMODE ] && RECOVERYMODE=false
}
export BOOTMODE=true

View File

@@ -13,7 +13,7 @@
<string name="no_connection">L\'accesu a internet nun ta disponible</string>
<string name="app_changelog">Rexistru de cambeos</string>
<string name="loading">Cargando…</string>
<string name="update">Anovamienu</string>
<string name="update">Anovar</string>
<string name="not_available">N/D</string>
<string name="hide">Anubrir</string>
<string name="home_package">Paquete</string>
@@ -23,8 +23,8 @@
<string name="home_follow_title">Redes sociales</string>
<string name="home_item_source">Códigu fonte</string>
<string name="home_support_content">Magisk ye y va ser de códigu abiertu y gratuitu. Sicasí, pues ayudanos faciendo una donación o collaborando.</string>
<string name="home_installed_version">V. instalada</string>
<string name="home_latest_version">Última v.</string>
<string name="home_installed_version">Versión instalada</string>
<string name="home_latest_version">Última versión</string>
<string name="invalid_update_channel">La canal d\'anovamientu nun ye válida</string>
<string name="uninstall_magisk_title">Desinstalar Magisk</string>
<string name="uninstall_magisk_msg">¡Van quitase y desactivase tolos módulos y l\'accesu root!\nCualesquier almacenamientu internu ensin cifrar pente l\'usu de Magisk va volver cifrase.</string>
@@ -37,13 +37,13 @@
<string name="install_method_title">Métodu</string>
<string name="install_next">Siguiente</string>
<string name="install_start">Siguir</string>
<string name="manager_download_install">Primi pa baxar ya instalar</string>
<string name="manager_download_install">Primi equí pa baxalu ya instalalu</string>
<string name="direct_install">Instalación direuta (aconséyase)</string>
<string name="install_inactive_slot">Instalar na ralura inactiva (darréu del OTA)</string>
<string name="install_inactive_slot_msg">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva darréu de reaniciar!\nUsa esta opción namás dempués d\'acabar l\'anovamientu per OTA.\n¿Quies siguir?</string>
<string name="setup_title">Configuración adicional</string>
<string name="select_patch_file">Esbillar y parchiar un ficheru</string>
<string name="patch_file_msg">Esbilla una imaxe en bruto (*.img) o un archivu d\'ODIN (*.tar)</string>
<string name="select_patch_file">Seleicionar y parchiar un ficheru</string>
<string name="patch_file_msg">Seleiciona una imaxe en bruto (*.img) o un archivu d\'ODIN (*.tar)</string>
<string name="reboot_delay_toast">Reaniciando en 5 segundos…</string>
<string name="flash_screen_title">Instalación</string>
<!--Superuser-->
@@ -106,7 +106,7 @@
<string name="module_empty">Nun hai nengún módulu instaláu</string>
<!--Settings-->
<string name="settings_dark_mode_title">Mou del estilu</string>
<string name="settings_dark_mode_message">¡Esbilla\'l mou que meyor s\'adaute al to estilu!</string>
<string name="settings_dark_mode_message">¡Seleiciona\'l mou que meyor s\'adaute al to estilu!</string>
<string name="settings_dark_mode_light">Claridá</string>
<string name="settings_dark_mode_system">L\'estilu del sistema</string>
<string name="settings_dark_mode_dark">Escuridá</string>
@@ -130,7 +130,7 @@
<string name="settings_denylist_summary">Los procesos de la llista d\'esclusión tienen toles modificaciones de Magisk anulaes</string>
<string name="settings_denylist_error">Esta función rique l\'activación de: %1$s</string>
<string name="settings_denylist_config_title">Configurar la llista d\'esclusión</string>
<string name="settings_denylist_config_summary">Esbilla los procesos que s\'inclúin na llista d\'esclusión</string>
<string name="settings_denylist_config_summary">Seleiciona los procesos que s\'inclúin na llista d\'esclusión</string>
<string name="settings_hosts_title">Módulu «Systemless Hosts»</string>
<string name="settings_hosts_summary">Un módulu pa les aplicaciones que bloquien anuncios</string>
<string name="settings_hosts_toast">Amestóse\'l módulu «Systemless Hosts»</string>

View File

@@ -5,44 +5,49 @@
<string name="superuser">Superuser</string>
<string name="logs">Log</string>
<string name="settings">Setelan</string>
<string name="install">Instal</string>
<string name="install">Pasang</string>
<string name="section_home">Beranda</string>
<string name="section_theme">Tema</string>
<string name="denylist">DenyList</string>
<!--Home-->
<string name="no_connection">Koneksi tidak tersedia</string>
<string name="app_changelog">Catatan perubahan</string>
<string name="loading">Memuat…</string>
<string name="update">Update</string>
<string name="update">Perbarui</string>
<string name="not_available">N/A</string>
<string name="hide">Tutup</string>
<string name="home_package">Paket</string>
<string name="home_app_title">Aplikasi</string>
<string name="home_notice_content">Unduh Magisk HANYA dari halaman GitHub resmi kami. File dari sumber yang tidak dikenal bisa berbahaya!</string>
<string name="home_support_title">Dukung kami</string>
<string name="home_follow_title">Ikuti Kami</string>
<string name="home_item_source">Sumber</string>
<string name="home_support_content">Magisk gratis dan bersumber terbuka, dan akan selalu seperti itu. Bagaimanapun juga Anda dapat menunjukan kepedulian Anda kepada kami dengan mengirimkan sedikit donasi.</string>
<string name="home_installed_version">Terinstal</string>
<string name="home_installed_version">Terpasang</string>
<string name="home_latest_version">Terbaru</string>
<string name="invalid_update_channel">Kanal update tidak valid</string>
<string name="uninstall_magisk_title">Uninstal Magisk</string>
<string name="invalid_update_channel">Saluran pembaruan tidak valid</string>
<string name="uninstall_magisk_title">Copot Magisk</string>
<string name="uninstall_magisk_msg">Semua modul akan dinonaktifkan/dihapus!\nRoot akan dihapus!\nData Anda berpotensi terenkripsi jika belum!</string>
<!--Install-->
<string name="keep_force_encryption">Pertahankan enkripsi paksa</string>
<string name="keep_dm_verity">Pertahankan AVB 2.0/dm-verity</string>
<string name="patch_vbmeta">Tambal vbmeta dalam boot image</string>
<string name="recovery_mode">Mode Recovery</string>
<string name="install_options_title">Opsi</string>
<string name="install_method_title">Metode</string>
<string name="install_next">Berikutnya</string>
<string name="install_start">Mulai</string>
<string name="manager_download_install">Sentuh untuk download dan instal</string>
<string name="direct_install">Langsung instal (Disarankan)</string>
<string name="install_inactive_slot">Instal pada slot nonaktif (Setelah OTA)</string>
<string name="manager_download_install">Sentuh untuk unduh dan pasang</string>
<string name="direct_install">Langsung pasang (Disarankan)</string>
<string name="install_inactive_slot">Pasang pada slot nonaktif (Setelah OTA)</string>
<string name="install_inactive_slot_msg">Perangkat Anda akan DIPAKSA boot ke slot yang saat ini tidak aktif setelah perangkat dinyalakan ulang!\nGunakan opsi ini hanya setelah proses OTA selesai.\nLanjutkan?</string>
<string name="setup_title">Penyiapan tambahan</string>
<string name="select_patch_file">Pilih dan tambal file</string>
<string name="patch_file_msg">Pilih mentahan image (*.img) atau file tar ODIN (*.tar)</string>
<string name="reboot_delay_toast">Memulai kembali dalam 5 detik…</string>
<string name="reboot_delay_toast">Memulai ulang dalam 5 detik…</string>
<string name="flash_screen_title">Instalasi</string>
<!--Superuser-->
@@ -94,16 +99,20 @@
<!--Module-->
<string name="no_info_provided">(Info tidak tersedia)</string>
<string name="reboot_userspace">Nyalakan ulang secara halus</string>
<string name="reboot_recovery">Nyalakan ke mode Recovery</string>
<string name="reboot_bootloader">Nyalakan ke mode Bootloader</string>
<string name="reboot_download">Nyalakan ke mode Download</string>
<string name="reboot_edl">Nyalakan ke mode EDL</string>
<string name="reboot_userspace">Mulai ulang secara halus</string>
<string name="reboot_recovery">Mulai ulang ke mode Recovery</string>
<string name="reboot_bootloader">Mulai ulang ke mode Bootloader</string>
<string name="reboot_download">Mulai ulang ke mode Download</string>
<string name="reboot_edl">Mulai ulang ke mode EDL</string>
<string name="module_version_author">%1$s oleh %2$s</string>
<string name="module_state_remove">Hapus</string>
<string name="module_state_restore">Pulihkan</string>
<string name="module_action_install_external">Instal dari penyimpanan</string>
<string name="update_available">Update tersedia</string>
<string name="module_action_install_external">Pasang dari penyimpanan</string>
<string name="update_available">Pembaruan tersedia</string>
<string name="suspend_text_riru">Modul ditangguhkan karena %1$s diaktifkan</string>
<string name="suspend_text_zygisk">Modul ditangguhkan karena %1$s tidak diaktifkan</string>
<string name="zygisk_module_unloaded">Modul Zygisk tidak dimuat karena ketidakcocokan</string>
<string name="module_empty">Tidak ada modul terpasang</string>
<!--Settings-->
<string name="settings_dark_mode_title">Mode tema</string>
@@ -111,17 +120,27 @@
<string name="settings_dark_mode_light">Selalu terang</string>
<string name="settings_dark_mode_system">Ikuti sistem</string>
<string name="settings_dark_mode_dark">Selalu gelap</string>
<string name="settings_download_path_title">Lokasi download</string>
<string name="settings_download_path_title">Lokasi unduhan</string>
<string name="settings_download_path_message">File akan disimpan ke %1$s</string>
<string name="settings_hide_app_title">Sembunyikan aplikasi Magisk</string>
<string name="settings_hide_app_summary">Pasang aplikasi proxy dengan ID paket acak dan label aplikasi khusus</string>
<string name="settings_restore_app_title">Pulihkan aplikasi Magisk</string>
<string name="settings_restore_app_summary">Tampilkan aplikasi and pulihkan APK asli</string>
<string name="language">Bahasa</string>
<string name="system_default">(Default sistem)</string>
<string name="settings_check_update_title">Periksa update</string>
<string name="settings_check_update_summary">Periksa update secara berkala di latar belakang</string>
<string name="settings_update_channel_title">Kanal update</string>
<string name="system_default">(Bawaan sistem)</string>
<string name="settings_check_update_title">Periksa pembaruan</string>
<string name="settings_check_update_summary">Periksa pembaruan secara berkala di latar belakang</string>
<string name="settings_update_channel_title">Saluran pembaruan</string>
<string name="settings_update_stable">Stabil</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_update_custom">Kanal khusus</string>
<string name="settings_update_custom">Saluran khusus</string>
<string name="settings_update_custom_msg">Masukkan URL khusus</string>
<string name="settings_zygisk_summary">Jalankan bagian-bagian Magisk dalam zygote daemon</string>
<string name="settings_denylist_title">Paksa DenyList</string>
<string name="settings_denylist_summary">Proses pada denylist akan mengembalikan semua modifikasi Magisk</string>
<string name="settings_denylist_error">Fitur ini membutuhkan %1$s untuk diaktifkan</string>
<string name="settings_denylist_config_title">Konfigurasi DenyList</string>
<string name="settings_denylist_config_summary">Pilih proses yang akan disertakan pada denylist</string>
<string name="settings_hosts_title">Host systemless</string>
<string name="settings_hosts_summary">Dukungan host secara systemless untuk aplikasi pemblokir iklan</string>
<string name="settings_hosts_toast">Menambahkan modul host systemless</string>
@@ -142,8 +161,8 @@
<string name="auto_response">Respons otomatis</string>
<string name="request_timeout">Batas waktu permintaan</string>
<string name="superuser_notification">Notifikasi superuser</string>
<string name="settings_su_reauth_title">Autentikasi ulang setelah upgrade</string>
<string name="settings_su_reauth_summary">Autentikasi ulang izin akses superuser setelah aplikasi diupgrade</string>
<string name="settings_su_reauth_title">Autentikasi ulang setelah peningkatan</string>
<string name="settings_su_reauth_summary">Autentikasi ulang izin akses superuser setelah aplikasi ditingkatkan</string>
<string name="settings_su_tapjack_title">Aktifkan perlindungan tapjacking</string>
<string name="settings_su_tapjack_summary">Dialog permintaan superuser tidak akan menanggapi masukan saat terhalangi oleh lapisan atau jendela lainnya</string>
<string name="settings_su_biometric_title">Aktifkan autentikasi biometrik</string>
@@ -171,35 +190,51 @@
<string name="isolate_summary">Setiap sesi root akan memiliki ruang-nama tersendiri</string>
<!--Notifications-->
<string name="update_channel">Update Magisk</string>
<string name="update_channel">Pembaruan Magisk</string>
<string name="progress_channel">Notifikasi Kemajuan</string>
<string name="download_complete">Download selesai</string>
<string name="download_file_error">Kesalahan saat mendownload file</string>
<string name="magisk_update_title">Update Magisk tersedia!</string>
<string name="updated_channel">Pembaruan Selesai</string>
<string name="download_complete">Unduhan selesai</string>
<string name="download_file_error">Kesalahan saat mengunduh file</string>
<string name="magisk_update_title">Pembaruan Magisk tersedia!</string>
<string name="updated_title">Magisk Diperbarui</string>
<string name="updated_text">Ketuk untuk buka aplikasi</string>
<!--Toasts, Dialogs-->
<string name="yes">Ya</string>
<string name="no">Tidak</string>
<string name="repo_install_title">Instal %1$s %2$s(%3$d)</string>
<string name="download">Download</string>
<string name="reboot">Nyalakan ulang</string>
<string name="repo_install_title">Pasang %1$s %2$s(%3$d)</string>
<string name="download">Unduh</string>
<string name="reboot">Mulai ulang</string>
<string name="release_notes">Catatan rilis</string>
<string name="flashing">Memasang…</string>
<string name="done">Selesai!</string>
<string name="failure">Gagal!</string>
<string name="hide_app_title">Menyembunyikan aplikasi Magisk…</string>
<string name="open_link_failed_toast">Tidak ditemukan aplikasi untuk membuka link ini</string>
<string name="complete_uninstall">Uninstal penuh</string>
<string name="complete_uninstall">Pencopotan penuh</string>
<string name="restore_img">Pulihkan image</string>
<string name="restore_img_msg">Memulihkan…</string>
<string name="restore_done">Pemulihan selesai!</string>
<string name="restore_fail">Cadangan stock tidak ada!</string>
<string name="setup_fail">Penyiapan gagal</string>
<string name="env_fix_title">Perlu penyiapan tambahan</string>
<string name="env_fix_msg">Perangkat Anda membutuhkan pengaturan tambahan untuk Magisk agar berfungsi dengan benar. Apakah Anda ingin melanjutkan dan Menyalakan ulang?</string>
<string name="setup_msg">Memproses penyiapan lingkungan…</string>
<string name="authenticate">Autentikasi</string>
<string name="unsupport_magisk_title">Versi Magisk tidak didukung</string>
<string name="unsupport_magisk_msg">Versi aplikasi ini tidak mendukung versi Magisk yang lebih rendah dari %1$s.\n\nAplikasi akan berperilaku seolah-olah tidak ada Magisk yang dipasang, harap tingkatkan Magisk sesegera mungkin.</string>
<string name="unsupport_general_title">Keadaan tidak normal</string>
<string name="unsupport_system_app_msg">Menjalankan aplikasi ini sebagai aplikasi sistem tidak didukung. Harap kembalikan aplikasi ke aplikasi pengguna.</string>
<string name="unsupport_other_su_msg">Sebuah \"su\" biner bukan dari Magisk telah terdeteksi. Hapus semua solusi root yang bersaing dan/atau pasang ulang Magisk.</string>
<string name="unsupport_external_storage_msg">Magisk dipasang ke penyimpanan eksternal. Harap pindahkan aplikasi ke penyimpanan internal.</string>
<string name="unsupport_nonroot_stub_msg">Aplikasi Magisk yang tersembunyi tidak dapat terus berfungsi karena root hilang. Tolong pulihkan APK asli.</string>
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">Berikan izin akses ke penyimpanan untuk mengaktifkan fungsi ini</string>
<string name="install_unknown_denied">Izinkan "Sumber tidak dikenal" untuk mengaktifkan fungsi ini</string>
<string name="add_shortcut_title">Tambahkan pintasan ke layar utama</string>
<string name="add_shortcut_msg">Setelah menyembunyikan aplikasi ini, nama dan ikonnya mungkin sulit dikenali. Apakah Anda ingin menambahkan pintasan cantik ke layar utama?</string>
<string name="app_not_found">Tidak ditemukan aplikasi untuk menangani tindakan ini</string>
<string name="reboot_apply_change">Mulai ulang untuk menerapkan perubahan</string>
<string name="restore_app_confirmation">Ini akan mengembalikan aplikasi tersembunyi kembali ke aplikasi asli. Apakah Anda benar-benar ingin melakukan ini?</string>
</resources>

View File

@@ -234,7 +234,7 @@
<string name="add_shortcut_title">Добавление ярлыка</string>
<string name="add_shortcut_msg">После скрытия приложения Magisk его название и иконка могут быть неудобны для восприятия. Хотите создать ярлык на рабочем столе?</string>
<string name="app_not_found">Приложение для обработки этого действия не найдено</string>
<string name="reboot_apply_change">Перезагрузить для применения изменений</string>
<string name="reboot_apply_change">Перезагрузите устройство для применения изменений</string>
<string name="restore_app_confirmation">Это действие восстановит пересобранное для скрытия приложение к исходному состоянию. Вы действительно хотите продолжить?</string>
</resources>

View File

@@ -130,7 +130,7 @@
<string name="system_default">(Mặc định hệ thống)</string>
<string name="settings_check_update_title">Kiểm tra cập nhật</string>
<string name="settings_check_update_summary">Kiểm tra định kỳ các bản cập nhật trong nền</string>
<string name="settings_update_channel_title">Cập nhật kênh</string>
<string name="settings_update_channel_title">Kênh cập nhật</string>
<string name="settings_update_stable">Ổn định</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_update_custom">Kênh tùy chỉnh</string>

View File

@@ -3,7 +3,7 @@
<!--Sections-->
<string name="modules">模組</string>
<string name="superuser">超級使用者</string>
<string name="logs"></string>
<string name="logs"></string>
<string name="settings">設定</string>
<string name="install">安裝</string>
<string name="section_home">首頁</string>
@@ -12,7 +12,7 @@
<!--Home-->
<string name="no_connection">無法連線</string>
<string name="app_changelog">變更</string>
<string name="app_changelog">變更</string>
<string name="loading">載入中……</string>
<string name="update">更新</string>
<string name="not_available"></string>
@@ -41,9 +41,9 @@
<string name="install_next">下一步</string>
<string name="install_start">開始執行</string>
<string name="manager_download_install">點選以下載並安裝</string>
<string name="direct_install">直接安裝 (建議)</string>
<string name="install_inactive_slot">安裝到非使用中的槽位 (在 OTA 更新後)</string>
<string name="install_inactive_slot_msg">您的裝置將在下次重新啟動後強制切換到非使用中的槽位!\n這個選項僅在 OTA 更新完畢後使用。\n是否繼續</string>
<string name="direct_install">直接安裝建議</string>
<string name="install_inactive_slot">安裝到非使用中的槽位在 OTA 更新後</string>
<string name="install_inactive_slot_msg">您的裝置將在下次重新啟動後強制切換到非使用中的槽位!\n這個選項僅在 OTA 更新完畢後使用。\n請問是否繼續?</string>
<string name="setup_title">修復安裝</string>
<string name="select_patch_file">選擇並修補檔案</string>
<string name="patch_file_msg">請選取未修改過的映像檔 (*.img) 或 Odin 的 TAR 檔案 (*.tar)</string>
@@ -69,8 +69,8 @@
<string name="su_snack_deny">已拒絕 %1$s 使用超級使用者的權限</string>
<string name="su_snack_notif_on">已啟用 %1$s 通知</string>
<string name="su_snack_notif_off">已停用 %1$s 通知</string>
<string name="su_snack_log_on">已啟用 %1$s 寫入</string>
<string name="su_snack_log_off">已停用 %1$s 寫入</string>
<string name="su_snack_log_on">已啟用 %1$s 寫入</string>
<string name="su_snack_log_off">已停用 %1$s 寫入</string>
<string name="su_revoke_title">撤銷權限?</string>
<string name="su_revoke_msg">確定撤銷 %1$s 的權限?</string>
<string name="toast">快顯通知</string>
@@ -81,13 +81,13 @@
<string name="superuser_policy_none">目前沒有任何應用程式要求超級使用者的權限。</string>
<!--Logs-->
<string name="log_data_none">您的錄是空的,請嘗試使用具備需要超級使用者權限的應用程式。</string>
<string name="log_data_magisk_none">Magisk 錄是空的,很奇怪……</string>
<string name="menuSaveLog">儲存</string>
<string name="menuClearLog">清除</string>
<string name="logs_cleared">已成功清除</string>
<string name="pid">PID%1$d</string>
<string name="target_uid">目標 UID%1$d</string>
<string name="log_data_none">您的錄是空的,請嘗試使用具備需要超級使用者權限的應用程式。</string>
<string name="log_data_magisk_none">Magisk 的紀錄是空的,很奇怪……</string>
<string name="menuSaveLog">儲存</string>
<string name="menuClearLog">清除</string>
<string name="logs_cleared">已成功清除</string>
<string name="pid">PID: %1$d</string>
<string name="target_uid">目標 UID: %1$d</string>
<!--SafetyNet-->
@@ -98,7 +98,7 @@
<string name="hide_search">搜尋</string>
<!--Module -->
<string name="no_info_provided">(未提供資訊)</string>
<string name="no_info_provided">未提供資訊</string>
<string name="reboot_userspace">快速重新啟動</string>
<string name="reboot_recovery">重新啟動至 Recovery</string>
<string name="reboot_bootloader">重新啟動至 Bootloader</string>
@@ -111,7 +111,7 @@
<string name="update_available">有可用的更新</string>
<string name="suspend_text_riru">此模組因 %1$s 已啟用而暫停運作</string>
<string name="suspend_text_zygisk">此模組因 %1$s 未啟用而暫停運作</string>
<string name="zygisk_module_unloaded">此 Zygisk 模組因未相容而不載入</string>
<string name="zygisk_module_unloaded">模組因不相容 Zygisk 而未載入</string>
<string name="module_empty">未安裝任何模組</string>
<!--Settings -->
@@ -123,11 +123,11 @@
<string name="settings_download_path_title">下載路徑</string>
<string name="settings_download_path_message">檔案將被儲存在:%1$s</string>
<string name="settings_hide_app_title">隱藏 Magisk</string>
<string name="settings_hide_app_summary">安裝一個隨機套件 ID 和客製化應用標籤的 Proxy 應用程式</string>
<string name="settings_hide_app_summary">安裝一個隨機套件名稱和客製化應用程式名稱的代理應用程式</string>
<string name="settings_restore_app_title">還原 Magisk</string>
<string name="settings_restore_app_summary">取消隱藏並還原為原始套件</string>
<string name="language">語言</string>
<string name="system_default">(系統預設)</string>
<string name="language">語言</string>
<string name="system_default">系統預設值)</string>
<string name="settings_check_update_title">檢查更新</string>
<string name="settings_check_update_summary">定期於背景檢查更新</string>
<string name="settings_update_channel_title">更新頻道</string>
@@ -137,11 +137,11 @@
<string name="settings_update_custom_msg">輸入一個自訂的網址</string>
<string name="settings_zygisk_summary">在 Zygote 中執行 Magisk</string>
<string name="settings_denylist_title">強制黑名單</string>
<string name="settings_denylist_summary">黑名單上的處理序將復原所有 Magisk 的修改</string>
<string name="settings_denylist_summary">Magisk 黑名單上的處理序將復原變更</string>
<string name="settings_denylist_error">這個功能需要啟用 %1$s</string>
<string name="settings_denylist_config_title">設定黑名單</string>
<string name="settings_denylist_config_summary">選擇要包含在黑名單的處理序</string>
<string name="settings_hosts_title">主機 (Hosts) 模組化</string>
<string name="settings_denylist_config_summary">選擇要包含在黑名單的處理</string>
<string name="settings_hosts_title">主機hosts模組化</string>
<string name="settings_hosts_summary">為廣告阻擋程式提供主機模組</string>
<string name="settings_hosts_toast">已安裝主機模組</string>
<string name="settings_app_name_hint">新的名稱</string>
@@ -164,13 +164,13 @@
<string name="settings_su_reauth_title">更新後重新驗證</string>
<string name="settings_su_reauth_summary">應用程式更新後,重新驗證超級使用者的要求</string>
<string name="settings_su_tapjack_title">啟用點選攔截保護</string>
<string name="settings_su_tapjack_summary">發現超級使用者視窗上有應用程式重疊在上層時,不回應允許操作</string>
<string name="settings_su_tapjack_summary">發現有其他應用程式重疊在超級使用者視窗上面時,不回應允許操作</string>
<string name="settings_su_biometric_title">生物特徵辨識驗證</string>
<string name="settings_su_biometric_summary">使用生物特徵辨識驗證來允許超級使用者的要求</string>
<string name="no_biometric">不支援的裝置或是未啟用生物特徵辨識設定</string>
<string name="settings_customization">客製化</string>
<string name="setting_add_shortcut_summary">在主螢幕中新增一個精緻的捷徑,以防隱藏應用程式後難以辨識其名稱圖示</string>
<string name="settings_doh_title">安全化的網域解析 (DoH)</string>
<string name="setting_add_shortcut_summary">在主螢幕中新增一個精緻的捷徑。防止隱藏 Magisk 以後,其名稱圖示將難以辨識</string>
<string name="settings_doh_title">安全化的網域解析(DoH)</string>
<string name="settings_doh_description">解決某些地區的 DNS 中毒問題</string>
<string name="multiuser_mode">多重使用者模式</string>
@@ -192,12 +192,12 @@
<!--Notifications-->
<string name="update_channel">Magisk 更新</string>
<string name="progress_channel">進度通知</string>
<string name="updated_channel">更新完成</string>
<string name="updated_channel">更新完成</string>
<string name="download_complete">下載完成</string>
<string name="download_file_error">下載錯誤</string>
<string name="magisk_update_title">Magisk 有可用的更新!</string>
<string name="updated_title">Magisk 已完成更新</string>
<string name="updated_text">輕觸即可開啟應用</string>
<string name="updated_title">Magisk 已完成更新</string>
<string name="updated_text">點選即可開啟應用程式</string>
<!--Toasts, Dialogs-->
<string name="yes"></string>
@@ -218,21 +218,21 @@
<string name="restore_fail">不存在原始備份的映像檔!</string>
<string name="setup_fail">安裝失敗</string>
<string name="env_fix_title">需要修復執行環境</string>
<string name="env_fix_msg">缺少讓 Magisk 正常執行所需的檔案。您同意讓 Magisk 額外下載安裝包進行修復安裝,修復完成後將自動重新啟動。是否繼續?</string>
<string name="setup_msg">正在修復運作環境……</string>
<string name="env_fix_msg">缺少讓 Magisk 正常執行所需的檔案。您同意讓 Magisk 額外下載安裝包進行修復安裝,修復完成後將自動重新啟動。請問您是否繼續?</string>
<string name="setup_msg">正在修復執行環境……</string>
<string name="authenticate">驗證</string>
<string name="unsupport_magisk_title">不支援的 Magisk 版本</string>
<string name="unsupport_magisk_msg"> Magisk 的版本不支援 Magisk %1$s 版或更低的版本。\n\nMagisk 將顯示為未安裝的狀態。不過您仍然可以升級功能,請盡快升級。</string>
<string name="unsupport_magisk_msg">應用程式的版本不支援 Magisk %1$s 版或更低的版本。\n\nMagisk 將顯示為未安裝的狀態。不過您仍然可以升級功能,請盡快升級。</string>
<string name="unsupport_general_title">異常狀態</string>
<string name="unsupport_system_app_msg">不支援讓本應用程式以系統應用程式的方式執行。請恢復為使用者應用程式。</string>
<string name="unsupport_system_app_msg">本應用程式不支援以系統應用程式的方式執行。請恢復為使用者應用程式。</string>
<string name="unsupport_other_su_msg">偵測到一個不是來自 Magisk 的「su」二進位檔案。請移除其他 Root 方案。</string>
<string name="unsupport_external_storage_msg">Magisk 已被安裝到外部儲存空間。請移動應用程式至內部儲存空間。</string>
<string name="unsupport_external_storage_msg">應用程式已被安裝到外部儲存空間。請移動應用程式至內部儲存空間。</string>
<string name="unsupport_nonroot_stub_msg">應用程式無法在 Root 權限遺失的情況下以隱藏模式執行。請還原為原始套件。</string>
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">授予儲存空間存取權以啟用此功能</string>
<string name="install_unknown_denied">允許「安裝未知應用程式」以啟用此功能</string>
<string name="install_unknown_denied">允許「安裝未知應用程式」以啟用此功能</string>
<string name="add_shortcut_title">在主螢幕中新增捷徑</string>
<string name="add_shortcut_msg">在隱藏應用後,其名稱與圖示將難以辨識。您想要在主螢幕中新增一個精緻的捷徑嗎?</string>
<string name="add_shortcut_msg">在隱藏應用程式以後,其名稱與圖示將難以辨識。請問您想要在主螢幕中新增一個精緻的捷徑嗎?</string>
<string name="app_not_found">沒有可以處理這個動作的應用程式</string>
<string name="reboot_apply_change">重新啟動裝置以套用設定變更</string>
<string name="restore_app_confirmation">這將會還原隱藏的應用程式至原始。請問您確定要執行?</string>

View File

@@ -24,7 +24,7 @@ tasks.withType<KotlinCompile> {
}
dependencies {
implementation(kotlin("gradle-plugin", "1.6.21"))
implementation(kotlin("gradle-plugin", "1.7.0"))
implementation("com.android.tools.build:gradle:7.2.1")
implementation("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0-rc01")
implementation("io.michaelrocks:paranoid-gradle-plugin:0.3.7")

View File

@@ -64,8 +64,6 @@ fun genKeyData(keysDir: File, outSrc: File) {
it.println("package com.topjohnwu.magisk.signing;")
it.println("public final class KeyData {")
it.byteField("testCert", File(keysDir, "testkey.x509.pem").readBytes())
it.byteField("testKey", File(keysDir, "testkey.pk8").readBytes())
it.byteField("verityCert", File(keysDir, "verity.x509.pem").readBytes())
it.byteField("verityKey", File(keysDir, "verity.pk8").readBytes())
@@ -127,6 +125,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
| android:name="%s"
| android:directBootAware="true"
| android:exported="false"
| android:taskAffinity=""
| tools:ignore="AppLinkUrlError">
| <intent-filter>
| <action android:name="android.intent.action.VIEW"/>

View File

@@ -1,6 +1,11 @@
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
import com.android.builder.internal.packaging.IncrementalPackager
import com.android.builder.model.SigningConfig
import com.android.tools.build.apkzlib.sign.SigningExtension
import com.android.tools.build.apkzlib.sign.SigningOptions
import com.android.tools.build.apkzlib.zfile.ZFiles
import com.android.tools.build.apkzlib.zip.ZFileOptions
import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.api.Action
import org.gradle.api.JavaVersion
@@ -16,6 +21,8 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import java.security.KeyStore
import java.security.cert.X509Certificate
import java.util.*
import java.util.zip.*
@@ -55,6 +62,37 @@ fun Project.setupCommon() {
}
}
private fun SigningConfig.getPrivateKey(): KeyStore.PrivateKeyEntry {
val keyStore = KeyStore.getInstance(storeType ?: KeyStore.getDefaultType())
storeFile!!.inputStream().use {
keyStore.load(it, storePassword!!.toCharArray())
}
val keyPwdArray = keyPassword!!.toCharArray()
val entry = keyStore.getEntry(keyAlias!!, KeyStore.PasswordProtection(keyPwdArray))
return entry as KeyStore.PrivateKeyEntry
}
private fun addComment(apkPath: File, signConfig: SigningConfig, minSdk: Int, eocdComment: String) {
val privateKey = signConfig.getPrivateKey()
val signingOptions = SigningOptions.builder()
.setMinSdkVersion(minSdk)
.setV1SigningEnabled(true)
.setV2SigningEnabled(true)
.setKey(privateKey.privateKey)
.setCertificates(privateKey.certificate as X509Certificate)
.setValidation(SigningOptions.Validation.ASSUME_INVALID)
.build()
val options = ZFileOptions().apply {
noTimestamps = true
autoSortFiles = true
}
ZFiles.apk(apkPath, options).use {
SigningExtension(signingOptions).register(it)
it.eocdComment = eocdComment.toByteArray()
it.get(IncrementalPackager.APP_METADATA_ENTRY_PATH)?.delete()
}
}
private fun Project.setupAppCommon() {
setupCommon()
@@ -91,6 +129,19 @@ private fun Project.setupAppCommon() {
includeInApk = false
}
}
android.applicationVariants.all {
val projectName = project.name.toLowerCase(Locale.ROOT)
val variantCapped = name.capitalize(Locale.ROOT)
val variant = name.toLowerCase(Locale.ROOT)
tasks.getByPath(":$projectName:package$variantCapped").doLast {
val apkDir = if (properties["android.injected.invoked.from.ide"] == "true")
"intermediates" else "outputs"
val apk = File(buildDir, "${apkDir}/apk/${variant}/$projectName-${variant}.apk")
val comment = "version=${Config.version}\nversionCode=${Config.versionCode}"
addComment(apk, signingConfig, android.defaultConfig.minSdk!!, comment)
}
}
}
fun Project.setupApp() {

View File

@@ -1,5 +1,14 @@
# Magisk Changelog
### v25.1
- [MagiskBoot] Fix ramdisk backup being incorrectly skipped
- [MagiskBoot] Add new feature to detect unsupported dtb and abort during installation
- [Zygisk] Change binary hijack paths
- [App] Fix incorrect recovery mode detection and installation
- [MagiskInit] Fix config not properly exported in legacy SAR devices
- [General] Enforce the Magisk app to always match or be newer than `magiskd`
### v25.0
- [MagiskInit] Update 2SI implementation, significantly increase device compatibility (e.g. Sony Xperia devices)

25
docs/releases/25100.md Normal file
View File

@@ -0,0 +1,25 @@
## 2022.6.19 Magisk v25.1
> v25.1 fixes some minor bugs over v25.0. The following are the same as v25.0 release notes.
Another major release! A lot of the changes aren't visible at the surface, but v25 is actually a really substantial upgrade!
### MagiskInit Rewrite
A significant portion of `magiskinit` (the critical software that runs before your device boots up) is completely rewritten from scratch. Ever since Android introduced [Project Treble](https://android-developers.googleblog.com/2017/05/here-comes-treble-modular-base-for.html) in Android 8.0, Magisk has been constantly fighting against the increasingly complex partitioning and early mount setups of all kinds of devices, sometimes with weird OEM specific implementations. It got to a point that `magiskinit` had become so complicated that few people (including myself!) were aware of every detail, and maintaining this piece of software like this was clearly not sustainable. After many months of planning (yes, this whole re-architecture has been in my head for a long time) and some help from external contributors, a whole new `sepolicy` injection mechanism is introduced into Magisk, solving the "SELinux Problem" once and for all.
Since this is a full paradigm shift on how Magisk hot-patch the device at boot, several behaviors that many developers implicitly relied on might not exist. For example, Magisk no longer patches fstabs in most scenarios, which means AVB will remain intact; some custom kernels rely on AVB being stripped out for them by Magisk.
### MagiskSU Security Enhancements
The superuser functionality of Magisk has not seen much changes ever since its introduction. v25 focuses on making root permission management more accurate and secure:
- Add a whole new package tracking system to ensure malicious UID reuse attack cannot be performed
- Properly support and implement the UX in the Magisk app for packages using `sharedUserId`
- Enforce root manager APK signature verification to combat the rampant unofficial Magisk app "mods"
Many might not realize, but using a trusted, unmodified Magisk app is really important. Magisk's root daemon treats the Magisk app differently and gives it blanket root access without any restrictions. A modded Magisk app can potentially backdoor your device.
And in case some of you are about to put on your tin foil hats, this is not designed to "vendor lock-in"; the goal is to make sure your root management app comes from the same developer of the underlying root implementation. Magisk's build system allows custom distributors to use its own signing keys, and in addition, I am also providing official debug builds which skips any signature verification for development.
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)

View File

@@ -1,5 +1,6 @@
# Release Notes
- [v25.1](25100.md)
- [v25.0](25000.md)
- [v24.3](24300.md)
- [v24.2](24200.md)

View File

@@ -25,30 +25,38 @@ Usage: ./magiskboot <action> [args...]
Supported actions:
unpack [-n] [-h] <bootimg>
Unpack <bootimg> to, if available, kernel, kernel_dtb, ramdisk.cpio,
second, dtb, extra, and recovery_dtbo into current directory.
If '-n' is provided, it will not attempt to decompress kernel or
ramdisk.cpio from their original formats.
If '-h' is provided, it will dump header info to 'header',
which will be parsed when repacking.
Unpack <bootimg> to its individual components, each component to
a file with its corresponding file name in the current directory.
Supported components: kernel, kernel_dtb, ramdisk.cpio, second,
dtb, extra, and recovery_dtbo.
By default, each component will be automatically decompressed
on-the-fly before writing to the output file.
If '-n' is provided, all decompression operations will be skipped;
each component will remain untouched, dumped in its original format.
If '-h' is provided, the boot image header information will be
dumped to the file 'header', which can be used to modify header
configurations during repacking.
Return values:
0:valid 1:error 2:chromeos
repack [-n] <origbootimg> [outbootimg]
Repack boot image components from current directory
to [outbootimg], or new-boot.img if not specified.
If '-n' is provided, it will not attempt to recompress ramdisk.cpio,
otherwise it will compress ramdisk.cpio and kernel with the same format
as in <origbootimg> if the file provided is not already compressed.
If env variable PATCHVBMETAFLAG is set to true, all disable flags will
be set in the vbmeta header.
Repack boot image components using files from the current directory
to [outbootimg], or 'new-boot.img' if not specified.
<origbootimg> is the original boot image used to unpack the components.
By default, each component will be automatically compressed using its
corresponding format detected in <origbootimg>. If a component file
in the current directory is already compressed, then no addition
compression will be performed for that specific component.
If '-n' is provided, all compression operations will be skipped.
If env variable PATCHVBMETAFLAG is set to true, all disable flags in
the boot image's vbmeta header will be set.
hexpatch <file> <hexpattern1> <hexpattern2>
Search <hexpattern1> in <file>, and replace with <hexpattern2>
Search <hexpattern1> in <file>, and replace it with <hexpattern2>
cpio <incpio> [commands...]
Do cpio commands to <incpio> (modifications are done in-place)
Each command is a single argument, add quotes for each command
Each command is a single argument, add quotes for each command.
Supported commands:
exists ENTRY
Return 0 if ENTRY exists, else return 1
@@ -65,7 +73,7 @@ Supported actions:
extract [ENTRY OUT]
Extract ENTRY to OUT, or extract all entries to current directory
test
Test the current cpio's status
Test the cpio's status
Return value is 0 or bitwise or-ed of following values:
0x1:Magisk 0x2:unsupported 0x4:Sony
patch
@@ -78,8 +86,8 @@ Supported actions:
sha1
Print stock boot SHA1 if previously backed up in ramdisk
dtb <input> <action> [args...]
Do dtb related actions to <input>
dtb <file> <action> [args...]
Do dtb related actions to <file>
Supported actions:
print [-f]
Print all contents of dtb for debugging
@@ -88,8 +96,12 @@ Supported actions:
Search for fstab and remove verity/avb
Modifications are done directly to the file in-place
Configure with env variables: KEEPVERITY
test
Test the fstab's status
Return values:
0:valid 1:error
split <input>
split <file>
Split image.*-dtb into kernel + kernel_dtb
sha1 <file>
@@ -99,14 +111,19 @@ Supported actions:
Cleanup the current working directory
compress[=format] <infile> [outfile]
Compress <infile> with [format] (default: gzip), optionally to [outfile]
<infile>/[outfile] can be '-' to be STDIN/STDOUT
Supported formats: gzip zopfli xz lzma bzip2 lz4 lz4_legacy lz4_lg
Compress <infile> with [format] to [outfile].
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
If [format] is not specified, then gzip will be used.
If [outfile] is not specified, then <infile> will be replaced
with another file suffixed with a matching file extension.
Supported formats: gzip zopfli xz lzma bzip2 lz4 lz4_legacy lz4_lg
decompress <infile> [outfile]
Detect format and decompress <infile>, optionally to [outfile]
<infile>/[outfile] can be '-' to be STDIN/STDOUT
Supported formats: gzip zopfli xz lzma bzip2 lz4 lz4_legacy lz4_lg
Detect format and decompress <infile> to [outfile].
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
If [outfile] is not specified, then <infile> will be replaced
with another file removing its archive format file extension.
Supported formats: gzip zopfli xz lzma bzip2 lz4 lz4_legacy lz4_lg
```
### magiskinit
@@ -211,9 +228,8 @@ Options:
Advanced Options (Internal APIs):
--daemon manually start magisk daemon
--stop remove all magisk changes and stop daemon
--[init trigger] start service for init trigger
Supported init triggers:
post-fs-data, service, boot-complete
--[init trigger] callback on init triggers. Valid triggers:
post-fs-data, service, boot-complete, zygote-restart
--unlock-blocks set BLKROSET flag to OFF for all block devices
--restorecon restore selinux context on Magisk files
--clone-attr SRC DEST clone permission, owner, and selinux context

View File

@@ -27,6 +27,6 @@ android.injected.testOnly=false
android.nonTransitiveRClass=true
# Magisk
magisk.stubVersion=31
magisk.versionCode=25000
magisk.stubVersion=32
magisk.versionCode=25100
magisk.ondkVersion=r24.1

View File

@@ -286,26 +286,7 @@ void fclone_attr(int src, int dest) {
fsetattr(dest, &a);
}
void fd_full_read(int fd, void **buf, size_t *size) {
*size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
*buf = xmalloc(*size + 1);
xxread(fd, *buf, *size);
((char *) *buf)[*size] = '\0';
}
void full_read(const char *filename, void **buf, size_t *size) {
int fd = xopen(filename, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
*buf = nullptr;
*size = 0;
return;
}
fd_full_read(fd, buf, size);
close(fd);
}
void fd_full_read(int fd, string &str) {
void full_read(int fd, string &str) {
char buf[4096];
for (ssize_t len; (len = xread(fd, buf, sizeof(buf))) > 0;)
str.insert(str.end(), buf, buf + len);
@@ -313,14 +294,14 @@ void fd_full_read(int fd, string &str) {
void full_read(const char *filename, string &str) {
if (int fd = xopen(filename, O_RDONLY | O_CLOEXEC); fd >= 0) {
fd_full_read(fd, str);
full_read(fd, str);
close(fd);
}
}
string fd_full_read(int fd) {
string full_read(int fd) {
string str;
fd_full_read(fd, str);
full_read(fd, str);
return str;
}
@@ -357,12 +338,19 @@ void file_readline(bool trim, FILE *fp, const function<bool(string_view)> &fn) {
if (!fn(start))
break;
}
fclose(fp);
free(buf);
}
void parse_prop_file(const char *file, const function<bool(string_view, string_view)> &fn) {
file_readline(true, file, [&](string_view line_view) -> bool {
void file_readline(bool trim, const char *file, const function<bool(string_view)> &fn) {
if (auto fp = open_file(file, "re"))
file_readline(trim, fp.get(), fn);
}
void file_readline(const char *file, const function<bool(string_view)> &fn) {
file_readline(false, file, fn);
}
void parse_prop_file(FILE *fp, const function<bool(string_view, string_view)> &fn) {
file_readline(true, fp, [&](string_view line_view) -> bool {
char *line = (char *) line_view.data();
if (line[0] == '#')
return true;
@@ -374,6 +362,11 @@ void parse_prop_file(const char *file, const function<bool(string_view, string_v
});
}
void parse_prop_file(const char *file, const function<bool(string_view, string_view)> &fn) {
if (auto fp = open_file(file, "re"))
parse_prop_file(fp.get(), fn);
}
// Original source: https://android.googlesource.com/platform/bionic/+/master/libc/bionic/mntent.cpp
// License: AOSP, full copyright notice please check original source
static struct mntent *compat_getmntent_r(FILE *fp, struct mntent *e, char *buf, int buf_len) {
@@ -429,12 +422,10 @@ void backup_folder(const char *dir, vector<raw_file> &files) {
if (fgetattr(fd, &file.attr) < 0)
return SKIP;
if (entry->d_type == DT_REG) {
fd_full_read(fd, file.buf, file.sz);
file.content = full_read(fd);
} else if (entry->d_type == DT_LNK) {
xreadlinkat(dfd, entry->d_name, path, sizeof(path));
file.sz = strlen(path) + 1;
file.buf = (uint8_t *) xmalloc(file.sz);
memcpy(file.buf, path, file.sz);
file.content = path;
}
files.emplace_back(std::move(file));
return CONTINUE;
@@ -449,10 +440,10 @@ void restore_folder(const char *dir, vector<raw_file> &files) {
if (S_ISDIR(file.attr.st.st_mode)) {
mkdirs(path, 0);
} else if (S_ISREG(file.attr.st.st_mode)) {
auto fp = xopen_file(path.data(), "we");
if (fp) fwrite(file.buf, 1, file.sz, fp.get());
if (auto fp = xopen_file(path.data(), "we"))
fwrite(file.content.data(), 1, file.content.size(), fp.get());
} else if (S_ISLNK(file.attr.st.st_mode)) {
symlink((char *)file.buf, path.data());
symlink(file.content.data(), path.data());
}
setattr(path.data(), &file.attr);
}

View File

@@ -39,14 +39,14 @@ protected:
void swap(byte_data &o);
};
struct raw_file : public byte_data {
struct raw_file {
std::string path;
file_attr attr;
std::string content;
raw_file() : attr{} {}
raw_file(const raw_file&) = delete;
raw_file(raw_file &&o) : path(std::move(o.path)), attr(o.attr) { swap(o); }
~raw_file() { free(buf); }
raw_file(raw_file &&o) : path(std::move(o.path)), attr(o.attr), content(std::move(o.content)) {}
};
struct mmap_data : public byte_data {
@@ -75,25 +75,15 @@ int setattrat(int dirfd, const char *name, file_attr *a);
int fsetattr(int fd, file_attr *a);
void fclone_attr(int src, int dest);
void clone_attr(const char *src, const char *dest);
void fd_full_read(int fd, void **buf, size_t *size);
void full_read(const char *filename, void **buf, size_t *size);
void fd_full_read(int fd, std::string &str);
void full_read(int fd, std::string &str);
void full_read(const char *filename, std::string &str);
std::string fd_full_read(int fd);
std::string full_read(int fd);
std::string full_read(const char *filename);
void write_zero(int fd, size_t size);
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn);
static inline void file_readline(
bool trim, const char *file, const std::function<bool(std::string_view)> &fn) {
FILE *fp = xfopen(file, "re");
if (fp == nullptr)
return;
file_readline(trim, fp, fn);
}
static inline void file_readline(const char *file,
const std::function<bool(std::string_view)> &fn) {
file_readline(false, file, fn);
}
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn);
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn);
void parse_prop_file(FILE *fp, const std::function<bool(std::string_view, std::string_view)> &fn);
void parse_prop_file(const char *file,
const std::function<bool(std::string_view, std::string_view)> &fn);
void frm_rf(int dirfd);
@@ -103,18 +93,6 @@ void backup_folder(const char *dir, std::vector<raw_file> &files);
void restore_folder(const char *dir, std::vector<raw_file> &files);
std::string find_apk_path(const char *pkg);
template <typename T>
void full_read(const char *filename, T &buf, size_t &size) {
static_assert(std::is_pointer<T>::value);
full_read(filename, reinterpret_cast<void**>(&buf), &size);
}
template <typename T>
void fd_full_read(int fd, T &buf, size_t &size) {
static_assert(std::is_pointer<T>::value);
fd_full_read(fd, reinterpret_cast<void**>(&buf), &size);
}
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
sDIR make_dir(DIR *dp);

View File

@@ -111,6 +111,14 @@ ssize_t xxread(int fd, void *buf, size_t count) {
return read_sz;
}
off_t xlseek(int fd, off_t offset, int whence) {
off_t ret = lseek(fd, offset, whence);
if (ret < 0) {
PLOGE("lseek");
}
return ret;
}
int xpipe2(int pipefd[2], int flags) {
int ret = pipe2(pipefd, flags);
if (ret < 0) {

View File

@@ -14,6 +14,7 @@ int xopenat(int dirfd, const char *pathname, int flags, mode_t mode);
ssize_t xwrite(int fd, const void *buf, size_t count);
ssize_t xread(int fd, void *buf, size_t count);
ssize_t xxread(int fd, void *buf, size_t count);
off_t xlseek(int fd, off_t offset, int whence);
int xpipe2(int pipefd[2], int flags);
int xsetns(int fd, int nstype);
int xunshare(int flags);

View File

@@ -577,7 +577,7 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
}
if (access(KERNEL_FILE, R_OK) == 0) {
auto m = mmap_data(KERNEL_FILE);
if (!COMPRESSED_ANY(check_fmt(m.buf, m.sz)) && COMPRESSED(boot.k_fmt)) {
if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf, m.sz)) && COMPRESSED(boot.k_fmt)) {
// Always use zopfli for zImage compression
auto fmt = (boot.flags[ZIMAGE_KERNEL] && boot.k_fmt == GZIP) ? ZOPFLI : boot.k_fmt;
hdr->kernel_size() = compress(fmt, fd, m.buf, m.sz);

View File

@@ -53,7 +53,9 @@ void cpio::dump(const char *file) {
dump(xfopen(file, "we"));
}
void cpio::rm(entry_map::iterator &it) {
void cpio::rm(entry_map::iterator it) {
if (it == entries.end())
return;
fprintf(stderr, "Remove [%s]\n", it->first.data());
entries.erase(it);
}
@@ -188,7 +190,7 @@ void cpio::ln(const char *target, const char *name) {
fprintf(stderr, "Create symlink [%s] -> [%s]\n", name, target);
}
void cpio::mv(entry_map::iterator &it, const char *name) {
void cpio::mv(entry_map::iterator it, const char *name) {
fprintf(stderr, "Move [%s] -> [%s]\n", it->first.data(), name);
auto e = it->second.release();
entries.erase(it);

View File

@@ -45,8 +45,8 @@ protected:
entry_map entries;
static void extract_entry(const entry_map::value_type &e, const char *file);
void rm(entry_map::iterator &it);
void mv(entry_map::iterator &it, const char *name);
void rm(entry_map::iterator it);
void mv(entry_map::iterator it, const char *name);
private:
void dump(FILE *out);

View File

@@ -93,47 +93,43 @@ static int find_fstab(const void *fdt, int node = 0) {
return -1;
}
static void dtb_print(const char *file, bool fstab) {
fprintf(stderr, "Loading dtbs from [%s]\n", file);
auto m = mmap_data(file);
// Loop through all the dtbs
int dtb_num = 0;
uint8_t * const end = m.buf + m.sz;
template<typename Func>
static void for_each_fdt(const char *file, bool rw, Func fn) {
auto m = mmap_data(file, rw);
uint8_t *end = m.buf + m.sz;
for (uint8_t *fdt = m.buf; fdt < end;) {
fdt = static_cast<uint8_t*>(memmem(fdt, end - fdt, DTB_MAGIC, sizeof(fdt32_t)));
if (fdt == nullptr)
break;
fn(fdt);
fdt += fdt_totalsize(fdt);
}
}
static void dtb_print(const char *file, bool fstab) {
fprintf(stderr, "Loading dtbs from [%s]\n", file);
int dtb_num = 0;
for_each_fdt(file, false, [&](uint8_t *fdt) {
if (fstab) {
int node = find_fstab(fdt);
if (node >= 0) {
fprintf(stderr, "Found fstab in buf.%04d\n", dtb_num);
if (int node = find_fstab(fdt); node >= 0) {
fprintf(stderr, "Found fstab in dtb.%04d\n", dtb_num);
print_node(fdt, node);
}
} else {
fprintf(stderr, "Printing buf.%04d\n", dtb_num);
fprintf(stderr, "Printing dtb.%04d\n", dtb_num);
print_node(fdt);
}
++dtb_num;
fdt += fdt_totalsize(fdt);
}
});
fprintf(stderr, "\n");
}
[[maybe_unused]]
static bool dtb_patch_rebuild(uint8_t *dtb, size_t dtb_sz, const char *file);
static bool dtb_patch(const char *file) {
bool keep_verity = check_env("KEEPVERITY");
fprintf(stderr, "Loading dtbs from [%s]\n", file);
auto m = mmap_data(file, true);
bool keep_verity = check_env("KEEPVERITY");
bool patched = false;
uint8_t * const end = m.buf + m.sz;
for (uint8_t *fdt = m.buf; fdt < end;) {
fdt = static_cast<uint8_t*>(memmem(fdt, end - fdt, DTB_MAGIC, sizeof(fdt32_t)));
if (fdt == nullptr)
break;
for_each_fdt(file, true, [&](uint8_t *fdt) {
int node;
// Patch the chosen node for bootargs
fdt_for_each_subnode(node, fdt, 0) {
@@ -149,20 +145,41 @@ static bool dtb_patch(const char *file) {
}
break;
}
if (int fstab = find_fstab(fdt); fstab >= 0) {
fdt_for_each_subnode(node, fdt, fstab) {
if (!keep_verity) {
if (!keep_verity) {
if (int fstab = find_fstab(fdt); fstab >= 0) {
fdt_for_each_subnode(node, fdt, fstab) {
int len;
char *value = (char *) fdt_getprop(fdt, node, "fsmgr_flags", &len);
patched |= patch_verity(value, len) != len;
}
}
}
fdt += fdt_totalsize(fdt);
}
});
return patched;
}
[[noreturn]]
static void dtb_test(const char *file) {
for_each_fdt(file, false, [&](uint8_t *fdt) {
// Find the system node in fstab
if (int fstab = find_fstab(fdt); fstab >= 0) {
int node;
fdt_for_each_subnode(node, fdt, fstab) {
if (auto name = fdt_get_name(fdt, node, nullptr); !name || name != "system"sv)
continue;
int len;
if (auto value = fdt_getprop(fdt, node, "mnt_point", &len)) {
// If mnt_point is set to /system_root, abort!
if (strncmp(static_cast<const char *>(value), "/system_root", len) == 0) {
exit(1);
}
}
}
}
});
exit(0);
}
int dtb_commands(int argc, char *argv[]) {
char *dtb = argv[0];
++argv;
@@ -175,11 +192,17 @@ int dtb_commands(int argc, char *argv[]) {
if (!dtb_patch(dtb))
exit(1);
return 0;
} else if (argv[0] == "test"sv) {
dtb_test(dtb);
} else {
return 1;
}
}
// The following code is unused, left here for historical purpose. Since the code is
// extremely complicated, I won't want to rewrite this whole thing if somehow we need
// to use it in the future...
namespace {
struct fdt_blob {
@@ -188,8 +211,6 @@ struct fdt_blob {
uint32_t len;
};
}
static bool fdt_patch(void *fdt) {
int fstab = find_fstab(fdt);
if (fstab < 0)
@@ -361,6 +382,7 @@ static bool blob_patch(uint8_t *dtb, size_t dtb_sz, const char *out) {
#define DTB_MATCH(s) BUFFER_MATCH(dtb, s)
[[maybe_unused]]
static bool dtb_patch_rebuild(uint8_t *dtb, size_t dtb_sz, const char *file) {
if (DTB_MATCH(QCDT_MAGIC)) {
auto hdr = reinterpret_cast<qcdt_hdr*>(dtb);
@@ -426,3 +448,5 @@ static bool dtb_patch_rebuild(uint8_t *dtb, size_t dtb_sz, const char *file) {
return blob_patch(dtb, dtb_sz, file);
}
}
} // namespace

View File

@@ -20,30 +20,38 @@ Usage: %s <action> [args...]
Supported actions:
unpack [-n] [-h] <bootimg>
Unpack <bootimg> to, if available, kernel, kernel_dtb, ramdisk.cpio,
second, dtb, extra, and recovery_dtbo into current directory.
If '-n' is provided, it will not attempt to decompress kernel or
ramdisk.cpio from their original formats.
If '-h' is provided, it will dump header info to 'header',
which will be parsed when repacking.
Unpack <bootimg> to its individual components, each component to
a file with its corresponding file name in the current directory.
Supported components: kernel, kernel_dtb, ramdisk.cpio, second,
dtb, extra, and recovery_dtbo.
By default, each component will be automatically decompressed
on-the-fly before writing to the output file.
If '-n' is provided, all decompression operations will be skipped;
each component will remain untouched, dumped in its original format.
If '-h' is provided, the boot image header information will be
dumped to the file 'header', which can be used to modify header
configurations during repacking.
Return values:
0:valid 1:error 2:chromeos
repack [-n] <origbootimg> [outbootimg]
Repack boot image components from current directory
to [outbootimg], or new-boot.img if not specified.
If '-n' is provided, it will not attempt to recompress ramdisk.cpio,
otherwise it will compress ramdisk.cpio and kernel with the same format
as in <origbootimg> if the file provided is not already compressed.
If env variable PATCHVBMETAFLAG is set to true, all disable flags will
be set in the vbmeta header.
Repack boot image components using files from the current directory
to [outbootimg], or 'new-boot.img' if not specified.
<origbootimg> is the original boot image used to unpack the components.
By default, each component will be automatically compressed using its
corresponding format detected in <origbootimg>. If a component file
in the current directory is already compressed, then no addition
compression will be performed for that specific component.
If '-n' is provided, all compression operations will be skipped.
If env variable PATCHVBMETAFLAG is set to true, all disable flags in
the boot image's vbmeta header will be set.
hexpatch <file> <hexpattern1> <hexpattern2>
Search <hexpattern1> in <file>, and replace with <hexpattern2>
Search <hexpattern1> in <file>, and replace it with <hexpattern2>
cpio <incpio> [commands...]
Do cpio commands to <incpio> (modifications are done in-place)
Each command is a single argument, add quotes for each command
Each command is a single argument, add quotes for each command.
Supported commands:
exists ENTRY
Return 0 if ENTRY exists, else return 1
@@ -60,7 +68,7 @@ Supported actions:
extract [ENTRY OUT]
Extract ENTRY to OUT, or extract all entries to current directory
test
Test the current cpio's status
Test the cpio's status
Return value is 0 or bitwise or-ed of following values:
0x1:Magisk 0x2:unsupported 0x4:Sony
patch
@@ -73,8 +81,8 @@ Supported actions:
sha1
Print stock boot SHA1 if previously backed up in ramdisk
dtb <input> <action> [args...]
Do dtb related actions to <input>
dtb <file> <action> [args...]
Do dtb related actions to <file>
Supported actions:
print [-f]
Print all contents of dtb for debugging
@@ -83,8 +91,12 @@ Supported actions:
Search for fstab and remove verity/avb
Modifications are done directly to the file in-place
Configure with env variables: KEEPVERITY
test
Test the fstab's status
Return values:
0:valid 1:error
split <input>
split <file>
Split image.*-dtb into kernel + kernel_dtb
sha1 <file>
@@ -94,8 +106,11 @@ Supported actions:
Cleanup the current working directory
compress[=format] <infile> [outfile]
Compress <infile> with [format] (default: gzip), optionally to [outfile]
<infile>/[outfile] can be '-' to be STDIN/STDOUT
Compress <infile> with [format] to [outfile].
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
If [format] is not specified, then gzip will be used.
If [outfile] is not specified, then <infile> will be replaced
with another file suffixed with a matching file extension.
Supported formats: )EOF", arg0);
print_formats();
@@ -103,8 +118,10 @@ Supported actions:
fprintf(stderr, R"EOF(
decompress <infile> [outfile]
Detect format and decompress <infile>, optionally to [outfile]
<infile>/[outfile] can be '-' to be STDIN/STDOUT
Detect format and decompress <infile> to [outfile].
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
If [outfile] is not specified, then <infile> will be replaced
with another file removing its archive format file extension.
Supported formats: )EOF");
print_formats();

View File

@@ -82,11 +82,10 @@ int magisk_cpio::test() {
}
#define for_each_line(line, buf, size) \
for (line = (char *) buf; line < (char *) buf + size && line[0]; line = strchr(line + 1, '\n') + 1)
for (char *line = (char *) buf; line < (char *) buf + size && line[0]; line = strchr(line + 1, '\n') + 1)
char *magisk_cpio::sha1() {
char sha1[41];
char *line;
for (auto &e : entries) {
if (e.first == "init.magisk.rc" || e.first == "overlay/init.magisk.rc") {
for_each_line(line, e.second->data, e.second->filesize) {
@@ -112,45 +111,59 @@ char *magisk_cpio::sha1() {
}
#define for_each_str(str, buf, size) \
for (str = (char *) buf; str < (char *) buf + size; str = str += strlen(str) + 1)
for (char *str = (char *) buf; str < (char *) buf + size; str += strlen(str) + 1)
void magisk_cpio::restore() {
if (auto it = entries.find(".backup/.rmlist"); it != entries.end()) {
char *file;
for_each_str(file, it->second->data, it->second->filesize) {
rm(file);
// Collect files
auto bk = entries.end();
auto rl = entries.end();
auto mg = entries.end();
vector<entry_map::iterator> backups;
for (auto it = entries.begin(); it != entries.end(); ++it) {
if (it->first == ".backup") {
bk = it;
} else if (it->first == ".backup/.rmlist") {
rl = it;
} else if (it->first == ".backup/.magisk") {
mg = it;
} else if (str_starts(it->first, ".backup/")) {
backups.emplace_back(it);
}
rm(it);
}
for (auto it = entries.begin(); it != entries.end();) {
auto cur = it++;
if (str_starts(cur->first, ".backup")) {
if (cur->first.length() == 7 || &cur->first[8] == ".magisk"sv) {
rm(cur);
} else {
mv(cur, &cur->first[8]);
}
} else if (str_starts(cur->first, "magisk") ||
cur->first == "overlay/init.magisk.rc" ||
cur->first == "sbin/magic_mask.sh" ||
cur->first == "init.magisk.rc") {
// Some known stuff we can remove
rm(cur);
// If the .backup folder is effectively empty, this means that the boot ramdisk was
// created from scratch by an old broken magiskboot. This is just a hacky workaround.
if (bk != entries.end() && mg != entries.end() && rl == entries.end() && backups.empty()) {
fprintf(stderr, "Remove all in ramdisk\n");
entries.clear();
return;
}
// Remove files
rm(bk);
rm(mg);
if (rl != entries.end()) {
for_each_str(file, rl->second->data, rl->second->filesize) {
rm(file);
}
rm(rl);
}
// Restore files
for (auto it : backups) {
const char *name = &it->first[8];
mv(it, name);
}
}
void magisk_cpio::backup(const char *orig) {
if (access(orig, R_OK))
return;
entry_map backups;
string rm_list;
backups.emplace(".backup", new cpio_entry(S_IFDIR));
magisk_cpio o;
o.load_cpio(orig);
if (access(orig, R_OK) == 0)
o.load_cpio(orig);
// Remove existing backups in original ramdisk
o.rm(".backup", true);

View File

@@ -95,27 +95,28 @@ struct EOCD {
* This method extracts the first certificate of the first signer
* within the APK v2 signature block.
*/
string read_certificate(int fd) {
uint32_t size4;
uint64_t size8;
string read_certificate(int fd, int version) {
uint32_t u32;
uint64_t u64;
// Find EOCD
for (int i = 0;; i++) {
// i is the absolute offset to end of file
uint16_t comment_sz = 0;
lseek(fd, -((off_t) sizeof(comment_sz)) - i, SEEK_END);
read(fd, &comment_sz, sizeof(comment_sz));
xlseek(fd, -static_cast<off_t>(sizeof(comment_sz)) - i, SEEK_END);
xxread(fd, &comment_sz, sizeof(comment_sz));
if (comment_sz == i) {
// Double check if we actually found the structure
lseek(fd, -((off_t) sizeof(EOCD)), SEEK_CUR);
xlseek(fd, -static_cast<off_t>(sizeof(EOCD)), SEEK_CUR);
uint32_t magic = 0;
read(fd, &magic, sizeof(magic));
xxread(fd, &magic, sizeof(magic));
if (magic == EOCD_MAGIC) {
break;
}
}
if (i == 0xffff) {
// Comments cannot be longer than 0xffff (overflow), abort
LOGE("cert: invalid APK format\n");
return {};
}
}
@@ -125,62 +126,83 @@ string read_certificate(int fd) {
uint32_t central_dir_off = 0;
{
constexpr off_t off = offsetof(EOCD, central_dir_off) - sizeof(EOCD::magic);
lseek(fd, off, SEEK_CUR);
xlseek(fd, off, SEEK_CUR);
}
xxread(fd, &central_dir_off, sizeof(central_dir_off));
// Parse APK comment to get version code
if (version >= 0) {
xlseek(fd, sizeof(EOCD::comment_sz), SEEK_CUR);
FILE *fp = fdopen(fd, "r"); // DO NOT close this file pointer
int apk_ver = -1;
parse_prop_file(fp, [&](string_view key, string_view value) -> bool {
if (key == "versionCode") {
apk_ver = parse_int(value);
return false;
}
return true;
});
if (version > apk_ver) {
// Enforce the magisk app to always be newer than magiskd
LOGE("cert: APK version too low\n");
return {};
}
}
read(fd, &central_dir_off, sizeof(central_dir_off));
// Next, find the start of the APK signing block
{
constexpr int off = sizeof(signing_block::block_sz_) + sizeof(signing_block::magic);
lseek(fd, (off_t) (central_dir_off - off), SEEK_SET);
xlseek(fd, (off_t) (central_dir_off - off), SEEK_SET);
}
read(fd, &size8, sizeof(size8)); // size8 = block_sz_
xxread(fd, &u64, sizeof(u64)); // u64 = block_sz_
char magic[sizeof(signing_block::magic)] = {0};
read(fd, magic, sizeof(magic));
xxread(fd, magic, sizeof(magic));
if (memcmp(magic, APK_SIGNING_BLOCK_MAGIC, sizeof(magic)) != 0) {
// Invalid signing block magic, abort
LOGE("cert: invalid signing block magic\n");
return {};
}
uint64_t signing_blk_sz = 0;
lseek(fd, (off_t) (central_dir_off - size8 - sizeof(signing_blk_sz)), SEEK_SET);
read(fd, &signing_blk_sz, sizeof(signing_blk_sz));
if (signing_blk_sz != size8) {
xlseek(fd, -static_cast<off_t>(u64 + sizeof(signing_blk_sz)), SEEK_CUR);
xxread(fd, &signing_blk_sz, sizeof(signing_blk_sz));
if (signing_blk_sz != u64) {
// block_sz != block_sz_, invalid signing block format, abort
LOGE("cert: invalid signing block format\n");
return {};
}
// Finally, we are now at the beginning of the id-value pair sequence
for (;;) {
read(fd, &size8, sizeof(size8)); // id-value pair length
if (size8 == signing_blk_sz) {
xxread(fd, &u64, sizeof(u64)); // id-value pair length
if (u64 == signing_blk_sz) {
// Outside of the id-value pair sequence; actually reading block_sz_
break;
}
uint32_t id;
read(fd, &id, sizeof(id));
xxread(fd, &id, sizeof(id));
if (id == SIGNATURE_SCHEME_V2_MAGIC) {
read(fd, &size4, sizeof(size4)); // signer sequence length
// Skip [signer sequence length] + [1st signer length] + [signed data length]
xlseek(fd, sizeof(uint32_t) * 3, SEEK_CUR);
read(fd, &size4, sizeof(size4)); // signer length
read(fd, &size4, sizeof(size4)); // signed data length
xxread(fd, &u32, sizeof(u32)); // digest sequence length
xlseek(fd, u32, SEEK_CUR); // skip all digests
read(fd, &size4, sizeof(size4)); // digest sequence length
lseek(fd, (off_t) (size4), SEEK_CUR); // skip all digests
read(fd, &size4, sizeof(size4)); // cert sequence length
read(fd, &size4, sizeof(size4)); // cert length
xlseek(fd, sizeof(uint32_t), SEEK_CUR); // cert sequence length
xxread(fd, &u32, sizeof(u32)); // 1st cert length
string cert;
cert.resize(size4);
read(fd, cert.data(), size4);
cert.resize(u32);
xxread(fd, cert.data(), u32);
return cert;
} else {
// Skip this id-value pair
lseek(fd, (off_t) (size8 - sizeof(id)), SEEK_CUR);
xlseek(fd, u64 - sizeof(id), SEEK_CUR);
}
}
LOGE("cert: cannot find certificate\n");
return {};
}

View File

@@ -21,7 +21,7 @@ void reboot();
void start_log_daemon();
void setup_logfile(bool reset);
void magisk_logging();
std::string read_certificate(int fd);
std::string read_certificate(int fd, int version = -1);
// Module stuffs
void handle_modules();

View File

@@ -105,7 +105,7 @@ int get_manager(int user_id, string *pkg, bool install) {
int dyn = open(app_path, O_RDONLY | O_CLOEXEC);
if (dyn < 0)
return false;
bool mismatch = default_cert && read_certificate(dyn) != *default_cert;
bool mismatch = default_cert && read_certificate(dyn, MAGISK_VER_CODE) != *default_cert;
close(dyn);
if (mismatch) {
LOGE("pkg: dyn APK signature mismatch: %s\n", app_path);
@@ -226,7 +226,7 @@ int get_manager(int user_id, string *pkg, bool install) {
#if ENFORCE_SIGNATURE
string apk = find_apk_path(JAVA_PACKAGE_NAME);
int fd = xopen(apk.data(), O_RDONLY | O_CLOEXEC);
string cert = read_certificate(fd);
string cert = read_certificate(fd, MAGISK_VER_CODE);
close(fd);
if (default_cert && cert != *default_cert) {
// Found APK with invalid signature, force replace with stub

View File

@@ -72,7 +72,7 @@ static void load_overlay_rc(const char *overlay) {
if (str_ends(entry->d_name, ".rc")) {
LOGD("Found rc script [%s]\n", entry->d_name);
int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
rc_list.push_back(fd_full_read(rc));
rc_list.push_back(full_read(rc));
close(rc);
unlinkat(dfd, entry->d_name, 0);
}

View File

@@ -64,7 +64,7 @@ void LegacySARInit::first_stage_prep() {
xmkdir("/data/.backup", 0);
xmkdir("/data/overlay.d", 0);
restore_folder("/data/overlay.d", overlays);
int cfg = xopen("/data/.backup/config", O_WRONLY | O_CREAT, 0);
int cfg = xopen("/data/.backup/.magisk", O_WRONLY | O_CREAT, 0);
xwrite(cfg, magisk_cfg.buf, magisk_cfg.sz);
close(cfg);
}
@@ -80,9 +80,15 @@ bool SecondStageInit::prepare() {
argv[0] = (char *) INIT_PATH;
// Some weird devices like meizu, uses 2SI but still have legacy rootfs
// Check if root and system are on the same filesystem
// Check if root and system are on different filesystems
struct stat root{}, system{};
xstat("/", &root);
xstat("/system", &system);
return root.st_dev != system.st_dev;
if (root.st_dev != system.st_dev) {
// We are still on rootfs, so make sure we will execute the init of the 2nd stage
unlink("/init");
xsymlink(INIT_PATH, "/init");
return true;
}
return false;
}

View File

@@ -17,10 +17,11 @@ exe, "/system/bin", "com.android.commands.content.Content", \
#define START_ACTIVITY \
exe, "/system/bin", "com.android.commands.am.Am", \
"start", "-p", target, "--user", user, "-a", "android.intent.action.VIEW", \
"-f", "0x58000020", "--es", "action", action
"-f", "0x58800020", "--es", "action", action
// 0x58000020 = FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|
// FLAG_ACTIVITY_NO_HISTORY|FLAG_INCLUDE_STOPPED_PACKAGES
// 0x58800020 = FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|
// FLAG_ACTIVITY_NO_HISTORY|FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|
// FLAG_INCLUDE_STOPPED_PACKAGES
#define get_cmd(to) \
((to).command.empty() ? \

View File

@@ -415,14 +415,13 @@ void su_daemon_handler(int client, const sock_cred *cred) {
if (realpath(path, cwd))
chdir(cwd);
snprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid);
char buf[4096] = { 0 };
int fd = xopen(path, O_RDONLY);
read(fd, buf, sizeof(buf));
close(fd);
auto env = full_read(path);
clearenv();
for (size_t pos = 0; buf[pos];) {
putenv(buf + pos);
pos += strlen(buf + pos) + 1;
for (size_t pos = 0; pos < env.size(); ++pos) {
putenv(env.data() + pos);
pos = env.find_first_of('\0', pos);
if (pos == std::string::npos)
break;
}
if (!ctx.req.keepenv) {
struct passwd *pw;

View File

@@ -271,6 +271,7 @@ def gen_jni_hook():
with open('jni_hooks.hpp', 'w') as f:
f.write('// Generated by gen_jni_hooks.py\n')
f.write('\nnamespace {\n')
zygote = 'com/android/internal/os/Zygote'
@@ -285,4 +286,4 @@ with open('jni_hooks.hpp', 'w') as f:
f.write(gen_jni_hook())
f.write('\n')
f.write('\n\n} // namespace\n')

View File

@@ -79,6 +79,8 @@ HookContext *g_ctx;
const JNINativeInterface *old_functions;
JNINativeInterface *new_functions;
} // namespace
#define HOOK_JNI(method) \
if (methods[i].name == #method##sv) { \
int j = 0; \
@@ -103,6 +105,8 @@ if (methods[i].name == #method##sv) {
#undef HOOK_JNI
namespace {
jclass gClassRef;
jmethodID class_getName;
string get_class_name(JNIEnv *env, jclass clazz) {

View File

@@ -1,5 +1,7 @@
// Generated by gen_jni_hooks.py
namespace {
void *nativeForkAndSpecialize_orig = nullptr;
jint nativeForkAndSpecialize_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
@@ -318,3 +320,5 @@ unique_ptr<JNINativeMethod[]> hookAndSaveJNIMethods(const char *className, const
}
return newMethods;
}
} // namespace

View File

@@ -9,8 +9,8 @@
#define INJECT_ENV_2 "MAGISK_INJ_2"
#define MAGISKTMP_ENV "MAGISKTMP"
#define HIJACK_BIN64 "/system/bin/bootanimation"
#define HIJACK_BIN32 "/system/bin/screencap"
#define HIJACK_BIN64 "/system/bin/appwidget"
#define HIJACK_BIN32 "/system/bin/bu"
namespace ZygiskRequest {
enum : int {

View File

@@ -183,7 +183,15 @@ rm -f ramdisk.cpio.orig config magisk*.xz
#################
for dt in dtb kernel_dtb extra; do
[ -f $dt ] && ./magiskboot dtb $dt patch && ui_print "- Patch fstab in $dt"
if [ -f $dt ]; then
if ! ./magiskboot dtb $dt test; then
ui_print "! Unsupported boot image $dt"
abort "! Please restore back to stock boot image"
fi
if ./magiskboot dtb $dt patch; then
ui_print "- Patch fstab in boot image $dt"
fi
fi
done
if [ -f kernel ]; then

Binary file not shown.

View File

@@ -1,27 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
+ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
-----END CERTIFICATE-----