From 0bd40ca4dd2dc7d85f49a4abf2247046c9c6f845 Mon Sep 17 00:00:00 2001 From: Nullptr <52071314+Dr-TSNG@users.noreply.github.com> Date: Sun, 20 Feb 2022 14:30:18 +0800 Subject: [PATCH] Fix saving content (1/2) --- .../main/java/org/lsposed/lspatch/Patcher.kt | 36 +++++++++++++++++-- .../lsposed/lspatch/ui/page/NewPatchPage.kt | 27 ++++++++------ .../main/java/org/lsposed/patch/LSPatch.java | 14 ++------ 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/manager/src/main/java/org/lsposed/lspatch/Patcher.kt b/manager/src/main/java/org/lsposed/lspatch/Patcher.kt index aacced1..8afb1a3 100644 --- a/manager/src/main/java/org/lsposed/lspatch/Patcher.kt +++ b/manager/src/main/java/org/lsposed/lspatch/Patcher.kt @@ -1,14 +1,21 @@ package org.lsposed.lspatch +import android.content.ContentValues +import android.content.Context +import android.os.Build +import android.os.Environment +import android.provider.MediaStore import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.lsposed.patch.LSPatch import org.lsposed.patch.util.Logger +import java.io.File +import java.nio.file.Files +import kotlin.io.path.absolutePathString object Patcher { class Options( private val apkPaths: Array, - private val outputPath: String, private val debuggable: Boolean, private val sigbypassLevel: Int, private val v1: Boolean, @@ -19,9 +26,10 @@ object Patcher { private val verbose: Boolean, private val embeddedModules: List ) { + lateinit var outputPath: String + fun toStringArray(): Array { return arrayListOf().run { - add("-f") addAll(apkPaths) add("-o"); add(outputPath) if (debuggable) add("-d") @@ -41,9 +49,31 @@ object Patcher { } } - suspend fun patch(logger: Logger, options: Options) { + suspend fun patch(context: Context, logger: Logger, options: Options) { withContext(Dispatchers.IO) { + val download = "${Environment.DIRECTORY_DOWNLOADS}/LSPatch" + options.outputPath = Files.createTempDirectory("patch").absolutePathString() LSPatch(logger, *options.toStringArray()).doCommandLine() + File(options.outputPath) + .walk() + .filter { it.isFile } + .forEach { + //FIXME: Android 9 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val contentDetails = ContentValues().apply { + put(MediaStore.Downloads.DISPLAY_NAME, it.name) + put(MediaStore.Downloads.RELATIVE_PATH, download) + } + val uri = context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentDetails) + ?: throw IllegalStateException("Failed to save files to Download") + it.inputStream().use { input -> + context.contentResolver.openOutputStream(uri)!!.use { output -> + input.copyTo(output) + } + } + } + } + logger.i("Patched files are saved to $download") } } } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt index 3d31b6c..3716f5e 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt @@ -1,6 +1,5 @@ package org.lsposed.lspatch.ui.page -import android.os.Environment import android.util.Log import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.Spring @@ -25,6 +24,7 @@ import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp @@ -43,9 +43,10 @@ import org.lsposed.lspatch.ui.util.lastItemIndex import org.lsposed.lspatch.ui.util.observeState import org.lsposed.lspatch.ui.viewmodel.AppInfo import org.lsposed.patch.util.Logger +import java.io.File enum class PatchState { - SELECTING, CONFIGURING, SUBMITTING, PATCHING, FINISHED + SELECTING, CONFIGURING, SUBMITTING, PATCHING, FINISHED, ERROR } class NewPatchPageViewModel : ViewModel() { @@ -76,7 +77,7 @@ fun NewPatchPage() { when (viewModel.patchState) { PatchState.SELECTING -> navController.navigate(PageList.SelectApps.name + "/false") PatchState.CONFIGURING, PatchState.SUBMITTING -> PatchOptionsPage(patchApp!!) - PatchState.PATCHING, PatchState.FINISHED -> DoPatchPage(viewModel.patchOptions!!) + PatchState.PATCHING, PatchState.FINISHED, PatchState.ERROR -> DoPatchPage(viewModel.patchOptions!!) } } @@ -95,11 +96,9 @@ private fun PatchOptionsPage(patchApp: AppInfo) { .savedStateHandle.getLiveData>("selected", SnapshotStateList()) if (viewModel.patchState == PatchState.SUBMITTING) LaunchedEffect(patchApp) { - val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path if (useManager) embeddedModules.value?.clear() viewModel.patchOptions = Patcher.Options( apkPaths = arrayOf(patchApp.app.sourceDir), // TODO: Split Apk - outputPath = downloadDir, debuggable = debuggable, sigbypassLevel = sigBypassLevel, v1 = v1, v2 = v2, v3 = v3, @@ -194,6 +193,7 @@ private fun PatchOptionsPage(patchApp: AppInfo) { @Composable private fun DoPatchPage(patcherOptions: Patcher.Options) { + val context = LocalContext.current val viewModel = viewModel() val navController = LocalNavController.current val logs = remember { mutableStateListOf>() } @@ -219,8 +219,16 @@ private fun DoPatchPage(patcherOptions: Patcher.Options) { } LaunchedEffect(patcherOptions) { - Patcher.patch(logger, patcherOptions) - viewModel.patchState = PatchState.FINISHED + try { + Patcher.patch(context, logger, patcherOptions) + viewModel.patchState = PatchState.FINISHED + } catch (t: Throwable) { + logger.e(t.message.orEmpty()) + logger.e(t.stackTraceToString()) + viewModel.patchState = PatchState.ERROR + } finally { + File(patcherOptions.outputPath).deleteRecursively() + } } Column( @@ -230,8 +238,7 @@ private fun DoPatchPage(patcherOptions: Patcher.Options) { .wrapContentHeight() .animateContentSize(spring(stiffness = Spring.StiffnessLow)) ) { - val patching by remember { derivedStateOf { viewModel.patchState == PatchState.PATCHING } } - ShimmerAnimation(enabled = patching) { + ShimmerAnimation(enabled = viewModel.patchState == PatchState.PATCHING) { CompositionLocalProvider( LocalTextStyle provides MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace) ) { @@ -262,7 +269,7 @@ private fun DoPatchPage(patcherOptions: Patcher.Options) { } } - if (!patching) { + if (viewModel.patchState == PatchState.FINISHED) { Row(Modifier.padding(top = 12.dp)) { Button( onClick = { navController.popBackStack() }, diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java index 611820f..0d2a7a7 100644 --- a/patch/src/main/java/org/lsposed/patch/LSPatch.java +++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java @@ -173,14 +173,13 @@ public class LSPatch { if (!srcApkFile.exists()) throw new PatchError("The source apk file does not exit. Please provide a correct path."); - File tmpApk = Files.createTempFile(srcApkFile.getName(), "unsigned").toFile(); - tmpApk.delete(); + outputFile.delete(); logger.d("apk path: " + srcApkFile); logger.i("Parsing original apk..."); - try (var dstZFile = ZFile.openReadWrite(tmpApk, Z_FILE_OPTIONS); + try (var dstZFile = ZFile.openReadWrite(outputFile, Z_FILE_OPTIONS); var srcZFile = dstZFile.addNestedZip((ignore) -> ORIGINAL_APK_ASSET_PATH, srcApkFile, false)) { // sign apk @@ -313,15 +312,8 @@ public class LSPatch { dstZFile.realign(); logger.i("Writing apk..."); - } finally { - try { - outputFile.delete(); - FileUtils.moveFile(tmpApk, outputFile); - logger.i("Done. Output APK: " + outputFile.getAbsolutePath()); - } catch (Throwable e) { - throw new PatchError("Error writing apk", e); - } } + logger.i("Done. Output APK: " + outputFile.getAbsolutePath()); } private void embedModules(ZFile zFile) {