Fix saving content (1/2)

This commit is contained in:
Nullptr 2022-02-20 14:30:18 +08:00
parent a23553e8ff
commit 0bd40ca4dd
3 changed files with 53 additions and 24 deletions

View File

@ -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<String>,
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<String>
) {
lateinit var outputPath: String
fun toStringArray(): Array<String> {
return arrayListOf<String>().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")
}
}
}

View File

@ -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<SnapshotStateList<AppInfo>>("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<NewPatchPageViewModel>()
val navController = LocalNavController.current
val logs = remember { mutableStateListOf<Pair<Int, String>>() }
@ -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() },

View File

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