Support upgrade loader version

This commit is contained in:
Nullptr 2022-07-05 21:54:13 +08:00
parent 0ef34fa633
commit 279e95b57b
No known key found for this signature in database
GPG Key ID: 0B9D02052FF536BD
15 changed files with 286 additions and 65 deletions

View File

@ -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"))
} }
} }

View File

@ -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")

View File

@ -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>;

View File

@ -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)

View File

@ -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() }
)
}
}

View File

@ -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"
)
}
}

View File

@ -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
if (!ShizukuApi.isPermissionGranted) { scope.launch {
scope.launch { if (!ShizukuApi.isPermissionGranted) {
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)
} }

View File

@ -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)) }
) )
} }
} }

View File

@ -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
}
}
} }

View File

@ -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()
} }
} }
} }

View File

@ -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
} }

View File

@ -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>

View File

@ -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) { if (originalSignature == null || originalSignature.isEmpty()) {
// save the apk original signature info, to support crack signature. throw new PatchError("get original signature failed");
originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
if (originalSignature == null || originalSignature.isEmpty()) {
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) {

View File

@ -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"
} }
} }

View File

@ -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;