Fix saving content (1/2)
This commit is contained in:
parent
a23553e8ff
commit
0bd40ca4dd
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() },
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue