Support upgrade loader version
This commit is contained in:
parent
0ef34fa633
commit
279e95b57b
|
|
@ -16,7 +16,7 @@ buildscript {
|
|||
}
|
||||
dependencies {
|
||||
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 {
|
||||
kotlinCompilerExtensionVersion = "1.2.0-beta02"
|
||||
kotlinCompilerExtensionVersion = "1.2.0"
|
||||
}
|
||||
|
||||
sourceSets["main"].assets.srcDirs(rootProject.projectDir.resolve("out/assets"))
|
||||
|
|
@ -78,22 +78,22 @@ dependencies {
|
|||
annotationProcessor("androidx.room:room-compiler:$roomVersion")
|
||||
compileOnly("dev.rikka.hidden:stub:2.3.1")
|
||||
implementation("dev.rikka.hidden:compat:2.3.1")
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
implementation("androidx.activity:activity-compose:1.6.0-alpha03")
|
||||
implementation("androidx.compose.material:material-icons-extended:1.2.0-beta02")
|
||||
implementation("androidx.compose.material3:material3:1.0.0-alpha12")
|
||||
implementation("androidx.compose.runtime:runtime-livedata:1.2.0-beta02")
|
||||
implementation("androidx.compose.ui:ui:1.2.0-beta02")
|
||||
implementation("androidx.compose.ui:ui-tooling:1.2.0-beta02")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-rc01")
|
||||
implementation("androidx.navigation:navigation-compose:2.5.0-rc01")
|
||||
implementation("androidx.core:core-ktx:1.8.0")
|
||||
implementation("androidx.activity:activity-compose:1.6.0-alpha05")
|
||||
implementation("androidx.compose.material:material-icons-extended:1.3.0-alpha01")
|
||||
implementation("androidx.compose.material3:material3:1.0.0-alpha14")
|
||||
implementation("androidx.compose.runtime:runtime-livedata:1.3.0-alpha01")
|
||||
implementation("androidx.compose.ui:ui:1.3.0-alpha01")
|
||||
implementation("androidx.compose.ui:ui-tooling:1.3.0-alpha01")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0")
|
||||
implementation("androidx.navigation:navigation-compose:2.5.0")
|
||||
implementation("androidx.preference:preference:1.2.0")
|
||||
implementation("androidx.room:room-ktx:$roomVersion")
|
||||
implementation("androidx.room:room-runtime:$roomVersion")
|
||||
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.9-beta")
|
||||
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.9-beta")
|
||||
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.9-beta")
|
||||
implementation("com.google.android.material:material:1.6.0")
|
||||
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.11-rc")
|
||||
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.11-rc")
|
||||
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.11-rc")
|
||||
implementation("com.google.android.material:material:1.6.1")
|
||||
implementation("com.google.code.gson:gson:2.9.0")
|
||||
implementation("dev.rikka.shizuku:api:12.1.0")
|
||||
implementation("dev.rikka.shizuku:provider:12.1.0")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
-keep class com.beust.jcommander.** { *; }
|
||||
-keep class org.lsposed.lspatch.Patcher$Options { *; }
|
||||
-keep class org.lsposed.lspatch.share.LSPConfig { *; }
|
||||
-keep class org.lsposed.lspatch.share.PatchConfig { *; }
|
||||
-keepclassmembers class org.lsposed.patch.LSPatch {
|
||||
private <fields>;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import kotlinx.coroutines.withContext
|
|||
import org.lsposed.lspatch.Constants.PATCH_FILE_SUFFIX
|
||||
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
||||
import org.lsposed.lspatch.config.MyKeyStore
|
||||
import org.lsposed.lspatch.share.PatchConfig
|
||||
import org.lsposed.patch.LSPatch
|
||||
import org.lsposed.patch.util.Logger
|
||||
import java.io.IOException
|
||||
|
|
@ -14,26 +15,21 @@ import java.io.IOException
|
|||
object Patcher {
|
||||
|
||||
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 config: PatchConfig,
|
||||
private val apkPaths: List<String>,
|
||||
private val embeddedModules: List<String>?
|
||||
) {
|
||||
fun toStringArray(): Array<String> {
|
||||
return buildList {
|
||||
addAll(apkPaths)
|
||||
add("-o"); add(lspApp.tmpApkDir.absolutePath)
|
||||
if (debuggable) add("-d")
|
||||
add("-l"); add(sigbypassLevel.toString())
|
||||
add("--v1"); add(v1.toString())
|
||||
add("--v2"); add(v2.toString())
|
||||
if (useManager) add("--manager")
|
||||
if (overrideVersionCode) add("-r")
|
||||
if (config.debuggable) add("-d")
|
||||
add("-l"); add(config.sigBypassLevel.toString())
|
||||
add("--v1"); add(config.v1.toString())
|
||||
add("--v2"); add(config.v2.toString())
|
||||
if (config.useManager) add("--manager")
|
||||
if (config.overrideVersionCode) add("-r")
|
||||
if (verbose) add("-v")
|
||||
embeddedModules?.forEach {
|
||||
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
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
|
|
@ -38,12 +41,15 @@ import org.lsposed.lspatch.R
|
|||
import org.lsposed.lspatch.config.ConfigManager
|
||||
import org.lsposed.lspatch.database.entity.Module
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.lspatch.share.LSPConfig
|
||||
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.LocalSnackbarHost
|
||||
import org.lsposed.lspatch.ui.util.observeState
|
||||
import org.lsposed.lspatch.ui.util.setState
|
||||
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.AppInfo
|
||||
import org.lsposed.lspatch.util.ShizukuApi
|
||||
|
|
@ -240,6 +246,27 @@ private fun Body() {
|
|||
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 {
|
||||
items(
|
||||
items = viewModel.appList,
|
||||
|
|
@ -272,6 +299,22 @@ private fun Body() {
|
|||
}
|
||||
)
|
||||
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) {
|
||||
DropdownMenuItem(
|
||||
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 optimizeFailed = stringResource(R.string.manage_optimize_failed)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.manage_optimize)) },
|
||||
onClick = {
|
||||
expanded = false
|
||||
if (!ShizukuApi.isPermissionGranted) {
|
||||
scope.launch {
|
||||
scope.launch {
|
||||
if (!ShizukuApi.isPermissionGranted) {
|
||||
snackbarHost.showSnackbar(shizukuUnavailable)
|
||||
}
|
||||
} else {
|
||||
scope.launch {
|
||||
} else {
|
||||
val result = ShizukuApi.performDexOptMode(it.first.app.packageName)
|
||||
snackbarHost.showSnackbar(if (result) optimizeSucceed else optimizeFailed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
|||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
||||
LSPPackageManager.cleanTmpApkDir()
|
||||
if (isCancelled == true) navController.popBackStack()
|
||||
else when (from) {
|
||||
"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 installSuccessfully = stringResource(R.string.patch_install_successfully)
|
||||
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) }
|
||||
if (installing) InstallDialog(viewModel.patchApp) { status, message ->
|
||||
scope.launch {
|
||||
|
|
@ -377,7 +377,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
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
|
||||
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.util.LSPPackageManager
|
||||
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"
|
||||
|
||||
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 {
|
||||
LSPPackageManager.appList.mapNotNull { appInfo ->
|
||||
appInfo.app.metaData?.getString("lspatch")?.let {
|
||||
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)
|
||||
}
|
||||
}.also {
|
||||
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 kotlinx.coroutines.launch
|
||||
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.patch.util.Logger
|
||||
import java.io.File
|
||||
|
||||
private const val TAG = "NewPatchViewModel"
|
||||
|
||||
|
|
@ -82,13 +82,9 @@ class NewPatchViewModel : ViewModel() {
|
|||
Log.d(TAG, "Submit patch")
|
||||
if (useManager) embeddedModules.clear()
|
||||
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,
|
||||
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()) }
|
||||
)
|
||||
patchState = PatchState.PATCHING
|
||||
|
|
@ -105,7 +101,7 @@ class NewPatchViewModel : ViewModel() {
|
|||
logger.e(t.stackTraceToString())
|
||||
PatchState.ERROR
|
||||
} finally {
|
||||
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
||||
LSPPackageManager.cleanTmpApkDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,14 @@ object LSPPackageManager {
|
|||
|
||||
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?> {
|
||||
Log.i(TAG, "Perform install patched apks")
|
||||
var status = PackageInstaller.STATUS_FAILURE
|
||||
var message: String? = null
|
||||
withContext(Dispatchers.IO) {
|
||||
|
|
@ -85,6 +92,7 @@ object LSPPackageManager {
|
|||
?: throw IOException("DocumentFile is null")
|
||||
root.listFiles().forEach { file ->
|
||||
if (file.name?.endsWith(PATCH_FILE_SUFFIX) != true) return@forEach
|
||||
Log.d(TAG, "Add ${file.name}")
|
||||
val input = lspApp.contentResolver.openInputStream(file.uri)
|
||||
?: throw IOException("Cannot open input stream")
|
||||
input.use {
|
||||
|
|
@ -180,7 +188,7 @@ object LSPPackageManager {
|
|||
}
|
||||
AppInfo(app, app.packageName)
|
||||
}.recoverCatching { t ->
|
||||
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
||||
cleanTmpApkDir()
|
||||
Log.e(TAG, "Failed to load apks", t)
|
||||
throw t
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<string name="installing">Installing</string>
|
||||
<string name="uninstall">Uninstall</string>
|
||||
<string name="uninstalling">Uninstalling</string>
|
||||
<string name="copy_error">Copy error</string>
|
||||
<string name="shizuku_available">Shizuku service available</string>
|
||||
<string name="shizuku_unavailable">Shizuku service not connected</string>
|
||||
<string name="page_repo">Repo</string>
|
||||
|
|
@ -26,6 +27,9 @@
|
|||
<string name="page_manage">Manage</string>
|
||||
<string name="manage_loading">Loading</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_optimize">Optimize</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_install_successfully">Install successfully</string>
|
||||
<string name="patch_install_failed">Install failed</string>
|
||||
<string name="patch_copy_error">Copy error</string>
|
||||
|
||||
<!-- Select Apps Page -->
|
||||
<string name="page_select_apps">Select Apps</string>
|
||||
|
|
|
|||
|
|
@ -216,16 +216,11 @@ public class LSPatch {
|
|||
throw new PatchError("Failed to register signer", e);
|
||||
}
|
||||
|
||||
String originalSignature = null;
|
||||
if (sigbypassLevel > 0) {
|
||||
// save the apk original signature info, to support crack signature.
|
||||
originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
|
||||
if (originalSignature == null || originalSignature.isEmpty()) {
|
||||
throw new PatchError("get original signature failed");
|
||||
}
|
||||
|
||||
logger.d("Original signature\n" + originalSignature);
|
||||
final String originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
|
||||
if (originalSignature == null || originalSignature.isEmpty()) {
|
||||
throw new PatchError("get original signature failed");
|
||||
}
|
||||
logger.d("Original signature\n" + originalSignature);
|
||||
|
||||
// copy out manifest file from zlib
|
||||
var manifestEntry = srcZFile.get(ANDROID_MANIFEST_XML);
|
||||
|
|
@ -233,7 +228,7 @@ public class LSPatch {
|
|||
throw new PatchError("Provided file is not a valid apk");
|
||||
|
||||
// parse the app appComponentFactory full name from the manifest file
|
||||
String appComponentFactory;
|
||||
final String appComponentFactory;
|
||||
try (var is = manifestEntry.open()) {
|
||||
var pair = ManifestParser.parseManifestFile(is);
|
||||
if (pair == null)
|
||||
|
|
@ -244,9 +239,9 @@ public class LSPatch {
|
|||
|
||||
logger.i("Patching apk...");
|
||||
// modify manifest
|
||||
var config = new PatchConfig(useManager, sigbypassLevel, null, appComponentFactory);
|
||||
var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
||||
var metadata = Base64.getEncoder().encodeToString(configBytes);
|
||||
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, v1, v2, sigbypassLevel, originalSignature, appComponentFactory);
|
||||
final var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
||||
final var metadata = Base64.getEncoder().encodeToString(configBytes);
|
||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata))) {
|
||||
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
||||
} catch (Throwable e) {
|
||||
|
|
@ -283,8 +278,6 @@ public class LSPatch {
|
|||
}
|
||||
|
||||
// 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)) {
|
||||
dstZFile.add(CONFIG_ASSET_PATH, is);
|
||||
} catch (Throwable e) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pluginManagement {
|
|||
plugins {
|
||||
id("com.android.library") 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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,30 @@ package org.lsposed.lspatch.share;
|
|||
public class PatchConfig {
|
||||
|
||||
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 String originalSignature;
|
||||
public final String appComponentFactory;
|
||||
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.debuggable = debuggable;
|
||||
this.overrideVersionCode = overrideVersionCode;
|
||||
this.v1 = v1;
|
||||
this.v2 = v2;
|
||||
this.sigBypassLevel = sigBypassLevel;
|
||||
this.originalSignature = originalSignature;
|
||||
this.appComponentFactory = appComponentFactory;
|
||||
|
|
|
|||
Loading…
Reference in New Issue