Support upgrade loader version
This commit is contained in:
parent
0ef34fa633
commit
279e95b57b
|
|
@ -16,7 +16,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.eclipse.jgit:org.eclipse.jgit:6.0.0.202111291000-r")
|
classpath("org.eclipse.jgit:org.eclipse.jgit:6.0.0.202111291000-r")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21")
|
classpath(kotlin("gradle-plugin", version = "1.7.0"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.2.0-beta02"
|
kotlinCompilerExtensionVersion = "1.2.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets["main"].assets.srcDirs(rootProject.projectDir.resolve("out/assets"))
|
sourceSets["main"].assets.srcDirs(rootProject.projectDir.resolve("out/assets"))
|
||||||
|
|
@ -78,22 +78,22 @@ dependencies {
|
||||||
annotationProcessor("androidx.room:room-compiler:$roomVersion")
|
annotationProcessor("androidx.room:room-compiler:$roomVersion")
|
||||||
compileOnly("dev.rikka.hidden:stub:2.3.1")
|
compileOnly("dev.rikka.hidden:stub:2.3.1")
|
||||||
implementation("dev.rikka.hidden:compat:2.3.1")
|
implementation("dev.rikka.hidden:compat:2.3.1")
|
||||||
implementation("androidx.core:core-ktx:1.7.0")
|
implementation("androidx.core:core-ktx:1.8.0")
|
||||||
implementation("androidx.activity:activity-compose:1.6.0-alpha03")
|
implementation("androidx.activity:activity-compose:1.6.0-alpha05")
|
||||||
implementation("androidx.compose.material:material-icons-extended:1.2.0-beta02")
|
implementation("androidx.compose.material:material-icons-extended:1.3.0-alpha01")
|
||||||
implementation("androidx.compose.material3:material3:1.0.0-alpha12")
|
implementation("androidx.compose.material3:material3:1.0.0-alpha14")
|
||||||
implementation("androidx.compose.runtime:runtime-livedata:1.2.0-beta02")
|
implementation("androidx.compose.runtime:runtime-livedata:1.3.0-alpha01")
|
||||||
implementation("androidx.compose.ui:ui:1.2.0-beta02")
|
implementation("androidx.compose.ui:ui:1.3.0-alpha01")
|
||||||
implementation("androidx.compose.ui:ui-tooling:1.2.0-beta02")
|
implementation("androidx.compose.ui:ui-tooling:1.3.0-alpha01")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-rc01")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0")
|
||||||
implementation("androidx.navigation:navigation-compose:2.5.0-rc01")
|
implementation("androidx.navigation:navigation-compose:2.5.0")
|
||||||
implementation("androidx.preference:preference:1.2.0")
|
implementation("androidx.preference:preference:1.2.0")
|
||||||
implementation("androidx.room:room-ktx:$roomVersion")
|
implementation("androidx.room:room-ktx:$roomVersion")
|
||||||
implementation("androidx.room:room-runtime:$roomVersion")
|
implementation("androidx.room:room-runtime:$roomVersion")
|
||||||
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.9-beta")
|
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.11-rc")
|
||||||
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.9-beta")
|
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.11-rc")
|
||||||
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.9-beta")
|
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.11-rc")
|
||||||
implementation("com.google.android.material:material:1.6.0")
|
implementation("com.google.android.material:material:1.6.1")
|
||||||
implementation("com.google.code.gson:gson:2.9.0")
|
implementation("com.google.code.gson:gson:2.9.0")
|
||||||
implementation("dev.rikka.shizuku:api:12.1.0")
|
implementation("dev.rikka.shizuku:api:12.1.0")
|
||||||
implementation("dev.rikka.shizuku:provider:12.1.0")
|
implementation("dev.rikka.shizuku:provider:12.1.0")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
-keep class com.beust.jcommander.** { *; }
|
-keep class com.beust.jcommander.** { *; }
|
||||||
-keep class org.lsposed.lspatch.Patcher$Options { *; }
|
-keep class org.lsposed.lspatch.Patcher$Options { *; }
|
||||||
|
-keep class org.lsposed.lspatch.share.LSPConfig { *; }
|
||||||
-keep class org.lsposed.lspatch.share.PatchConfig { *; }
|
-keep class org.lsposed.lspatch.share.PatchConfig { *; }
|
||||||
-keepclassmembers class org.lsposed.patch.LSPatch {
|
-keepclassmembers class org.lsposed.patch.LSPatch {
|
||||||
private <fields>;
|
private <fields>;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import kotlinx.coroutines.withContext
|
||||||
import org.lsposed.lspatch.Constants.PATCH_FILE_SUFFIX
|
import org.lsposed.lspatch.Constants.PATCH_FILE_SUFFIX
|
||||||
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
||||||
import org.lsposed.lspatch.config.MyKeyStore
|
import org.lsposed.lspatch.config.MyKeyStore
|
||||||
|
import org.lsposed.lspatch.share.PatchConfig
|
||||||
import org.lsposed.patch.LSPatch
|
import org.lsposed.patch.LSPatch
|
||||||
import org.lsposed.patch.util.Logger
|
import org.lsposed.patch.util.Logger
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
@ -14,26 +15,21 @@ import java.io.IOException
|
||||||
object Patcher {
|
object Patcher {
|
||||||
|
|
||||||
class Options(
|
class Options(
|
||||||
private val apkPaths: List<String>,
|
|
||||||
private val debuggable: Boolean,
|
|
||||||
private val sigbypassLevel: Int,
|
|
||||||
private val v1: Boolean,
|
|
||||||
private val v2: Boolean,
|
|
||||||
private val useManager: Boolean,
|
|
||||||
private val overrideVersionCode: Boolean,
|
|
||||||
private val verbose: Boolean,
|
private val verbose: Boolean,
|
||||||
|
private val config: PatchConfig,
|
||||||
|
private val apkPaths: List<String>,
|
||||||
private val embeddedModules: List<String>?
|
private val embeddedModules: List<String>?
|
||||||
) {
|
) {
|
||||||
fun toStringArray(): Array<String> {
|
fun toStringArray(): Array<String> {
|
||||||
return buildList {
|
return buildList {
|
||||||
addAll(apkPaths)
|
addAll(apkPaths)
|
||||||
add("-o"); add(lspApp.tmpApkDir.absolutePath)
|
add("-o"); add(lspApp.tmpApkDir.absolutePath)
|
||||||
if (debuggable) add("-d")
|
if (config.debuggable) add("-d")
|
||||||
add("-l"); add(sigbypassLevel.toString())
|
add("-l"); add(config.sigBypassLevel.toString())
|
||||||
add("--v1"); add(v1.toString())
|
add("--v1"); add(config.v1.toString())
|
||||||
add("--v2"); add(v2.toString())
|
add("--v2"); add(config.v2.toString())
|
||||||
if (useManager) add("--manager")
|
if (config.useManager) add("--manager")
|
||||||
if (overrideVersionCode) add("-r")
|
if (config.overrideVersionCode) add("-r")
|
||||||
if (verbose) add("-v")
|
if (verbose) add("-v")
|
||||||
embeddedModules?.forEach {
|
embeddedModules?.forEach {
|
||||||
add("-m"); add(it)
|
add("-m"); add(it)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.lsposed.lspatch.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun LoadingDialog() {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(100.dp)
|
||||||
|
.background(Color.White, shape = RoundedCornerShape(8.dp)),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
content = { CircularProgressIndicator() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.lsposed.lspatch.ui.component.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Api
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsSwitch(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
checked: Boolean,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
title: String,
|
||||||
|
desc: String? = null,
|
||||||
|
extraContent: (@Composable ColumnScope.() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
SettingsSlot(modifier, enabled, onClick, icon, title, desc, extraContent) {
|
||||||
|
Switch(checked = checked, onCheckedChange = { onClick() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SettingsCheckBoxPreview() {
|
||||||
|
var checked1 by remember { mutableStateOf(false) }
|
||||||
|
var checked2 by remember { mutableStateOf(false) }
|
||||||
|
Column {
|
||||||
|
SettingsSwitch(
|
||||||
|
checked = checked1,
|
||||||
|
onClick = { checked1 = !checked1 },
|
||||||
|
title = "Title",
|
||||||
|
desc = "Description"
|
||||||
|
)
|
||||||
|
SettingsSwitch(
|
||||||
|
checked = checked2,
|
||||||
|
onClick = { checked2 = !checked2 },
|
||||||
|
icon = Icons.Outlined.Api,
|
||||||
|
title = "Title",
|
||||||
|
desc = "Description"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package org.lsposed.lspatch.ui.page
|
package org.lsposed.lspatch.ui.page
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
@ -38,12 +41,15 @@ import org.lsposed.lspatch.R
|
||||||
import org.lsposed.lspatch.config.ConfigManager
|
import org.lsposed.lspatch.config.ConfigManager
|
||||||
import org.lsposed.lspatch.database.entity.Module
|
import org.lsposed.lspatch.database.entity.Module
|
||||||
import org.lsposed.lspatch.lspApp
|
import org.lsposed.lspatch.lspApp
|
||||||
|
import org.lsposed.lspatch.share.LSPConfig
|
||||||
import org.lsposed.lspatch.ui.component.AppItem
|
import org.lsposed.lspatch.ui.component.AppItem
|
||||||
|
import org.lsposed.lspatch.ui.component.LoadingDialog
|
||||||
import org.lsposed.lspatch.ui.util.LocalNavController
|
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||||
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
|
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
|
||||||
import org.lsposed.lspatch.ui.util.observeState
|
import org.lsposed.lspatch.ui.util.observeState
|
||||||
import org.lsposed.lspatch.ui.util.setState
|
import org.lsposed.lspatch.ui.util.setState
|
||||||
import org.lsposed.lspatch.ui.viewmodel.ManageViewModel
|
import org.lsposed.lspatch.ui.viewmodel.ManageViewModel
|
||||||
|
import org.lsposed.lspatch.ui.viewmodel.ManageViewModel.ViewAction
|
||||||
import org.lsposed.lspatch.util.LSPPackageManager
|
import org.lsposed.lspatch.util.LSPPackageManager
|
||||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||||
import org.lsposed.lspatch.util.ShizukuApi
|
import org.lsposed.lspatch.util.ShizukuApi
|
||||||
|
|
@ -240,6 +246,27 @@ private fun Body() {
|
||||||
navController.currentBackStackEntry!!.setState("isCancelled", null)
|
navController.currentBackStackEntry!!.setState("isCancelled", null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (viewModel.processingUpdate) LoadingDialog()
|
||||||
|
viewModel.updateLoaderResult?.let {
|
||||||
|
val updateSuccessfully = stringResource(R.string.manage_update_loader_successfully)
|
||||||
|
val updateFailed = stringResource(R.string.manage_update_loader_failed)
|
||||||
|
val copyError = stringResource(R.string.copy_error)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
it.onSuccess {
|
||||||
|
LSPPackageManager.fetchAppList()
|
||||||
|
snackbarHost.showSnackbar(updateSuccessfully)
|
||||||
|
}.onFailure {
|
||||||
|
val result = snackbarHost.showSnackbar(updateFailed, copyError)
|
||||||
|
if (result == SnackbarResult.ActionPerformed) {
|
||||||
|
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
cm.setPrimaryClip(ClipData.newPlainText("LSPatch", it.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.dispatch(ViewAction.ClearUpdateLoaderResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
items(
|
items(
|
||||||
items = viewModel.appList,
|
items = viewModel.appList,
|
||||||
|
|
@ -272,6 +299,22 @@ private fun Body() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||||
|
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
|
||||||
|
if (it.second.lspConfig.VERSION_CODE >= 319 && it.second.lspConfig.VERSION_CODE < LSPConfig.instance.VERSION_CODE) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.manage_update_loader)) },
|
||||||
|
onClick = {
|
||||||
|
expanded = false
|
||||||
|
scope.launch {
|
||||||
|
if (!ShizukuApi.isPermissionGranted) {
|
||||||
|
snackbarHost.showSnackbar(shizukuUnavailable)
|
||||||
|
} else {
|
||||||
|
viewModel.dispatch(ViewAction.UpdateLoader(it.first, it.second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
if (it.second.useManager) {
|
if (it.second.useManager) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.manage_module_scope)) },
|
text = { Text(stringResource(R.string.manage_module_scope)) },
|
||||||
|
|
@ -291,19 +334,16 @@ private fun Body() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
|
|
||||||
val optimizeSucceed = stringResource(R.string.manage_optimize_successfully)
|
val optimizeSucceed = stringResource(R.string.manage_optimize_successfully)
|
||||||
val optimizeFailed = stringResource(R.string.manage_optimize_failed)
|
val optimizeFailed = stringResource(R.string.manage_optimize_failed)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.manage_optimize)) },
|
text = { Text(stringResource(R.string.manage_optimize)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
expanded = false
|
expanded = false
|
||||||
|
scope.launch {
|
||||||
if (!ShizukuApi.isPermissionGranted) {
|
if (!ShizukuApi.isPermissionGranted) {
|
||||||
scope.launch {
|
|
||||||
snackbarHost.showSnackbar(shizukuUnavailable)
|
snackbarHost.showSnackbar(shizukuUnavailable)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
|
||||||
val result = ShizukuApi.performDexOptMode(it.first.app.packageName)
|
val result = ShizukuApi.performDexOptMode(it.first.app.packageName)
|
||||||
snackbarHost.showSnackbar(if (result) optimizeSucceed else optimizeFailed)
|
snackbarHost.showSnackbar(if (result) optimizeSucceed else optimizeFailed)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
LSPPackageManager.cleanTmpApkDir()
|
||||||
if (isCancelled == true) navController.popBackStack()
|
if (isCancelled == true) navController.popBackStack()
|
||||||
else when (from) {
|
else when (from) {
|
||||||
"storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive"))
|
"storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive"))
|
||||||
|
|
@ -323,7 +323,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
||||||
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
|
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
|
||||||
val installSuccessfully = stringResource(R.string.patch_install_successfully)
|
val installSuccessfully = stringResource(R.string.patch_install_successfully)
|
||||||
val installFailed = stringResource(R.string.patch_install_failed)
|
val installFailed = stringResource(R.string.patch_install_failed)
|
||||||
val copyError = stringResource(R.string.patch_copy_error)
|
val copyError = stringResource(R.string.copy_error)
|
||||||
var installing by remember { mutableStateOf(false) }
|
var installing by remember { mutableStateOf(false) }
|
||||||
if (installing) InstallDialog(viewModel.patchApp) { status, message ->
|
if (installing) InstallDialog(viewModel.patchApp) { status, message ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|
@ -377,7 +377,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
||||||
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
cm.setPrimaryClip(ClipData.newPlainText("LSPatch", viewModel.logs.joinToString { it.second + "\n" }))
|
cm.setPrimaryClip(ClipData.newPlainText("LSPatch", viewModel.logs.joinToString { it.second + "\n" }))
|
||||||
},
|
},
|
||||||
content = { Text(stringResource(R.string.patch_copy_error)) }
|
content = { Text(stringResource(R.string.copy_error)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,113 @@
|
||||||
package org.lsposed.lspatch.ui.viewmodel
|
package org.lsposed.lspatch.ui.viewmodel
|
||||||
|
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.lsposed.lspatch.Patcher
|
||||||
|
import org.lsposed.lspatch.lspApp
|
||||||
|
import org.lsposed.lspatch.share.Constants
|
||||||
import org.lsposed.lspatch.share.PatchConfig
|
import org.lsposed.lspatch.share.PatchConfig
|
||||||
import org.lsposed.lspatch.util.LSPPackageManager
|
import org.lsposed.lspatch.util.LSPPackageManager
|
||||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||||
|
import org.lsposed.patch.util.Logger
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
private const val TAG = "ManageViewModel"
|
private const val TAG = "ManageViewModel"
|
||||||
|
|
||||||
class ManageViewModel : ViewModel() {
|
class ManageViewModel : ViewModel() {
|
||||||
|
|
||||||
|
sealed class ViewAction {
|
||||||
|
data class UpdateLoader(val appInfo: AppInfo, val config: PatchConfig) : ViewAction()
|
||||||
|
object ClearUpdateLoaderResult : ViewAction()
|
||||||
|
}
|
||||||
|
|
||||||
val appList: List<Pair<AppInfo, PatchConfig>> by derivedStateOf {
|
val appList: List<Pair<AppInfo, PatchConfig>> by derivedStateOf {
|
||||||
LSPPackageManager.appList.mapNotNull { appInfo ->
|
LSPPackageManager.appList.mapNotNull { appInfo ->
|
||||||
appInfo.app.metaData?.getString("lspatch")?.let {
|
appInfo.app.metaData?.getString("lspatch")?.let {
|
||||||
val json = Base64.decode(it, Base64.DEFAULT).toString(Charsets.UTF_8)
|
val json = Base64.decode(it, Base64.DEFAULT).toString(Charsets.UTF_8)
|
||||||
|
Log.d(TAG, "Read patched config: $json")
|
||||||
appInfo to Gson().fromJson(json, PatchConfig::class.java)
|
appInfo to Gson().fromJson(json, PatchConfig::class.java)
|
||||||
}
|
}
|
||||||
}.also {
|
}.also {
|
||||||
Log.d(TAG, "Loaded ${it.size} patched apps")
|
Log.d(TAG, "Loaded ${it.size} patched apps")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var processingUpdate by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
var updateLoaderResult: Result<Unit>? by mutableStateOf(null)
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val logger = object : Logger() {
|
||||||
|
override fun d(msg: String) {
|
||||||
|
if (verbose) Log.d(TAG, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun i(msg: String) {
|
||||||
|
Log.i(TAG, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun e(msg: String) {
|
||||||
|
Log.e(TAG, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispatch(action: ViewAction) {
|
||||||
|
when (action) {
|
||||||
|
is ViewAction.UpdateLoader -> updateLoader(action.appInfo, action.config)
|
||||||
|
is ViewAction.ClearUpdateLoaderResult -> updateLoaderResult = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLoader(appInfo: AppInfo, config: PatchConfig) {
|
||||||
|
Log.i(TAG, "Update loader for ${appInfo.app.packageName}")
|
||||||
|
viewModelScope.launch {
|
||||||
|
processingUpdate = true
|
||||||
|
val result = runCatching {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
LSPPackageManager.cleanTmpApkDir()
|
||||||
|
val apkPaths = listOf(appInfo.app.sourceDir) + (appInfo.app.splitSourceDirs ?: emptyArray())
|
||||||
|
val patchPaths = mutableListOf<String>()
|
||||||
|
val embeddedModulePaths = if (config.useManager) emptyList<String>() else null
|
||||||
|
for (apk in apkPaths) {
|
||||||
|
ZipFile(apk).use { zip ->
|
||||||
|
var entry = zip.getEntry(Constants.ORIGINAL_APK_ASSET_PATH)
|
||||||
|
if (entry == null) entry = zip.getEntry("assets/lspatch/origin_apk.bin")
|
||||||
|
if (entry == null) throw FileNotFoundException("Original apk entry not found for $apk")
|
||||||
|
zip.getInputStream(entry).use { input ->
|
||||||
|
val dst = lspApp.tmpApkDir.resolve(apk.substringAfterLast('/'))
|
||||||
|
patchPaths.add(dst.absolutePath)
|
||||||
|
dst.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Patcher.patch(
|
||||||
|
logger, Patcher.Options(
|
||||||
|
verbose = true,
|
||||||
|
config = config,
|
||||||
|
apkPaths = patchPaths,
|
||||||
|
embeddedModules = embeddedModulePaths
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val (status, message) = LSPPackageManager.install()
|
||||||
|
if (status != PackageInstaller.STATUS_SUCCESS) throw RuntimeException(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processingUpdate = false
|
||||||
|
updateLoaderResult = result
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.lsposed.lspatch.Patcher
|
import org.lsposed.lspatch.Patcher
|
||||||
import org.lsposed.lspatch.lspApp
|
import org.lsposed.lspatch.share.PatchConfig
|
||||||
|
import org.lsposed.lspatch.util.LSPPackageManager
|
||||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||||
import org.lsposed.patch.util.Logger
|
import org.lsposed.patch.util.Logger
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
private const val TAG = "NewPatchViewModel"
|
private const val TAG = "NewPatchViewModel"
|
||||||
|
|
||||||
|
|
@ -82,13 +82,9 @@ class NewPatchViewModel : ViewModel() {
|
||||||
Log.d(TAG, "Submit patch")
|
Log.d(TAG, "Submit patch")
|
||||||
if (useManager) embeddedModules.clear()
|
if (useManager) embeddedModules.clear()
|
||||||
patchOptions = Patcher.Options(
|
patchOptions = Patcher.Options(
|
||||||
apkPaths = listOf(patchApp.app.sourceDir) + (patchApp.app.splitSourceDirs ?: emptyArray()),
|
|
||||||
debuggable = debuggable,
|
|
||||||
sigbypassLevel = sigBypassLevel,
|
|
||||||
v1 = sign[0], v2 = sign[1],
|
|
||||||
useManager = useManager,
|
|
||||||
overrideVersionCode = overrideVersionCode,
|
|
||||||
verbose = true,
|
verbose = true,
|
||||||
|
config = PatchConfig(useManager, debuggable, overrideVersionCode, sign[0], sign[1], sigBypassLevel, null, null),
|
||||||
|
apkPaths = listOf(patchApp.app.sourceDir) + (patchApp.app.splitSourceDirs ?: emptyArray()),
|
||||||
embeddedModules = embeddedModules.flatMap { listOf(it.app.sourceDir) + (it.app.splitSourceDirs ?: emptyArray()) }
|
embeddedModules = embeddedModules.flatMap { listOf(it.app.sourceDir) + (it.app.splitSourceDirs ?: emptyArray()) }
|
||||||
)
|
)
|
||||||
patchState = PatchState.PATCHING
|
patchState = PatchState.PATCHING
|
||||||
|
|
@ -105,7 +101,7 @@ class NewPatchViewModel : ViewModel() {
|
||||||
logger.e(t.stackTraceToString())
|
logger.e(t.stackTraceToString())
|
||||||
PatchState.ERROR
|
PatchState.ERROR
|
||||||
} finally {
|
} finally {
|
||||||
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
LSPPackageManager.cleanTmpApkDir()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,14 @@ object LSPPackageManager {
|
||||||
|
|
||||||
fun getIcon(appInfo: AppInfo) = appIcon[appInfo.app.packageName]!!
|
fun getIcon(appInfo: AppInfo) = appIcon[appInfo.app.packageName]!!
|
||||||
|
|
||||||
|
suspend fun cleanTmpApkDir() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun install(): Pair<Int, String?> {
|
suspend fun install(): Pair<Int, String?> {
|
||||||
|
Log.i(TAG, "Perform install patched apks")
|
||||||
var status = PackageInstaller.STATUS_FAILURE
|
var status = PackageInstaller.STATUS_FAILURE
|
||||||
var message: String? = null
|
var message: String? = null
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
|
@ -85,6 +92,7 @@ object LSPPackageManager {
|
||||||
?: throw IOException("DocumentFile is null")
|
?: throw IOException("DocumentFile is null")
|
||||||
root.listFiles().forEach { file ->
|
root.listFiles().forEach { file ->
|
||||||
if (file.name?.endsWith(PATCH_FILE_SUFFIX) != true) return@forEach
|
if (file.name?.endsWith(PATCH_FILE_SUFFIX) != true) return@forEach
|
||||||
|
Log.d(TAG, "Add ${file.name}")
|
||||||
val input = lspApp.contentResolver.openInputStream(file.uri)
|
val input = lspApp.contentResolver.openInputStream(file.uri)
|
||||||
?: throw IOException("Cannot open input stream")
|
?: throw IOException("Cannot open input stream")
|
||||||
input.use {
|
input.use {
|
||||||
|
|
@ -180,7 +188,7 @@ object LSPPackageManager {
|
||||||
}
|
}
|
||||||
AppInfo(app, app.packageName)
|
AppInfo(app, app.packageName)
|
||||||
}.recoverCatching { t ->
|
}.recoverCatching { t ->
|
||||||
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
cleanTmpApkDir()
|
||||||
Log.e(TAG, "Failed to load apks", t)
|
Log.e(TAG, "Failed to load apks", t)
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
<string name="installing">Installing</string>
|
<string name="installing">Installing</string>
|
||||||
<string name="uninstall">Uninstall</string>
|
<string name="uninstall">Uninstall</string>
|
||||||
<string name="uninstalling">Uninstalling</string>
|
<string name="uninstalling">Uninstalling</string>
|
||||||
|
<string name="copy_error">Copy error</string>
|
||||||
<string name="shizuku_available">Shizuku service available</string>
|
<string name="shizuku_available">Shizuku service available</string>
|
||||||
<string name="shizuku_unavailable">Shizuku service not connected</string>
|
<string name="shizuku_unavailable">Shizuku service not connected</string>
|
||||||
<string name="page_repo">Repo</string>
|
<string name="page_repo">Repo</string>
|
||||||
|
|
@ -26,6 +27,9 @@
|
||||||
<string name="page_manage">Manage</string>
|
<string name="page_manage">Manage</string>
|
||||||
<string name="manage_loading">Loading</string>
|
<string name="manage_loading">Loading</string>
|
||||||
<string name="manage_no_apps">No patched apps yet</string>
|
<string name="manage_no_apps">No patched apps yet</string>
|
||||||
|
<string name="manage_update_loader">Update loader</string>
|
||||||
|
<string name="manage_update_loader_successfully">Update successfully</string>
|
||||||
|
<string name="manage_update_loader_failed">Update failed</string>
|
||||||
<string name="manage_module_scope">Module scope</string>
|
<string name="manage_module_scope">Module scope</string>
|
||||||
<string name="manage_optimize">Optimize</string>
|
<string name="manage_optimize">Optimize</string>
|
||||||
<string name="manage_optimize_successfully">Optimize successfully</string>
|
<string name="manage_optimize_successfully">Optimize successfully</string>
|
||||||
|
|
@ -58,7 +62,6 @@
|
||||||
<string name="patch_uninstall_text">Due to different signatures, you need to uninstall the original app before installing the patched one.\nMake sure you have backed up personal data.</string>
|
<string name="patch_uninstall_text">Due to different signatures, you need to uninstall the original app before installing the patched one.\nMake sure you have backed up personal data.</string>
|
||||||
<string name="patch_install_successfully">Install successfully</string>
|
<string name="patch_install_successfully">Install successfully</string>
|
||||||
<string name="patch_install_failed">Install failed</string>
|
<string name="patch_install_failed">Install failed</string>
|
||||||
<string name="patch_copy_error">Copy error</string>
|
|
||||||
|
|
||||||
<!-- Select Apps Page -->
|
<!-- Select Apps Page -->
|
||||||
<string name="page_select_apps">Select Apps</string>
|
<string name="page_select_apps">Select Apps</string>
|
||||||
|
|
|
||||||
|
|
@ -216,16 +216,11 @@ public class LSPatch {
|
||||||
throw new PatchError("Failed to register signer", e);
|
throw new PatchError("Failed to register signer", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
String originalSignature = null;
|
final String originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
|
||||||
if (sigbypassLevel > 0) {
|
|
||||||
// save the apk original signature info, to support crack signature.
|
|
||||||
originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
|
|
||||||
if (originalSignature == null || originalSignature.isEmpty()) {
|
if (originalSignature == null || originalSignature.isEmpty()) {
|
||||||
throw new PatchError("get original signature failed");
|
throw new PatchError("get original signature failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.d("Original signature\n" + originalSignature);
|
logger.d("Original signature\n" + originalSignature);
|
||||||
}
|
|
||||||
|
|
||||||
// copy out manifest file from zlib
|
// copy out manifest file from zlib
|
||||||
var manifestEntry = srcZFile.get(ANDROID_MANIFEST_XML);
|
var manifestEntry = srcZFile.get(ANDROID_MANIFEST_XML);
|
||||||
|
|
@ -233,7 +228,7 @@ public class LSPatch {
|
||||||
throw new PatchError("Provided file is not a valid apk");
|
throw new PatchError("Provided file is not a valid apk");
|
||||||
|
|
||||||
// parse the app appComponentFactory full name from the manifest file
|
// parse the app appComponentFactory full name from the manifest file
|
||||||
String appComponentFactory;
|
final String appComponentFactory;
|
||||||
try (var is = manifestEntry.open()) {
|
try (var is = manifestEntry.open()) {
|
||||||
var pair = ManifestParser.parseManifestFile(is);
|
var pair = ManifestParser.parseManifestFile(is);
|
||||||
if (pair == null)
|
if (pair == null)
|
||||||
|
|
@ -244,9 +239,9 @@ public class LSPatch {
|
||||||
|
|
||||||
logger.i("Patching apk...");
|
logger.i("Patching apk...");
|
||||||
// modify manifest
|
// modify manifest
|
||||||
var config = new PatchConfig(useManager, sigbypassLevel, null, appComponentFactory);
|
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, v1, v2, sigbypassLevel, originalSignature, appComponentFactory);
|
||||||
var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
final var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
||||||
var metadata = Base64.getEncoder().encodeToString(configBytes);
|
final var metadata = Base64.getEncoder().encodeToString(configBytes);
|
||||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata))) {
|
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata))) {
|
||||||
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
@ -283,8 +278,6 @@ public class LSPatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
// save lspatch config to asset..
|
// save lspatch config to asset..
|
||||||
config = new PatchConfig(useManager, sigbypassLevel, originalSignature, appComponentFactory);
|
|
||||||
configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
|
||||||
try (var is = new ByteArrayInputStream(configBytes)) {
|
try (var is = new ByteArrayInputStream(configBytes)) {
|
||||||
dstZFile.add(CONFIG_ASSET_PATH, is);
|
dstZFile.add(CONFIG_ASSET_PATH, is);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ pluginManagement {
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library") version agpVersion
|
id("com.android.library") version agpVersion
|
||||||
id("com.android.application") version agpVersion
|
id("com.android.application") version agpVersion
|
||||||
id("com.google.devtools.ksp") version "1.6.21-1.0.5"
|
id("com.google.devtools.ksp") version "1.7.0-1.0.6"
|
||||||
id("dev.rikka.tools.refine") version "3.1.1"
|
id("dev.rikka.tools.refine") version "3.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,30 @@ package org.lsposed.lspatch.share;
|
||||||
public class PatchConfig {
|
public class PatchConfig {
|
||||||
|
|
||||||
public final boolean useManager;
|
public final boolean useManager;
|
||||||
|
public final boolean debuggable;
|
||||||
|
public final boolean overrideVersionCode;
|
||||||
|
public final boolean v1;
|
||||||
|
public final boolean v2;
|
||||||
public final int sigBypassLevel;
|
public final int sigBypassLevel;
|
||||||
public final String originalSignature;
|
public final String originalSignature;
|
||||||
public final String appComponentFactory;
|
public final String appComponentFactory;
|
||||||
public final LSPConfig lspConfig;
|
public final LSPConfig lspConfig;
|
||||||
|
|
||||||
public PatchConfig(boolean useManager, int sigBypassLevel, String originalSignature, String appComponentFactory) {
|
public PatchConfig(
|
||||||
|
boolean useManager,
|
||||||
|
boolean debuggable,
|
||||||
|
boolean overrideVersionCode,
|
||||||
|
boolean v1,
|
||||||
|
boolean v2,
|
||||||
|
int sigBypassLevel,
|
||||||
|
String originalSignature,
|
||||||
|
String appComponentFactory
|
||||||
|
) {
|
||||||
this.useManager = useManager;
|
this.useManager = useManager;
|
||||||
|
this.debuggable = debuggable;
|
||||||
|
this.overrideVersionCode = overrideVersionCode;
|
||||||
|
this.v1 = v1;
|
||||||
|
this.v2 = v2;
|
||||||
this.sigBypassLevel = sigBypassLevel;
|
this.sigBypassLevel = sigBypassLevel;
|
||||||
this.originalSignature = originalSignature;
|
this.originalSignature = originalSignature;
|
||||||
this.appComponentFactory = appComponentFactory;
|
this.appComponentFactory = appComponentFactory;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue