Package module
This commit is contained in:
parent
bb7bfaad26
commit
78420d8758
|
|
@ -7,6 +7,10 @@
|
||||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
tools:ignore="QueryAllPackagesPermission" />
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
|
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="28" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package org.lsposed.lspatch
|
package org.lsposed.lspatch
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.lsposed.patch.LSPatch
|
import org.lsposed.patch.LSPatch
|
||||||
|
import org.lsposed.patch.util.Logger
|
||||||
|
|
||||||
object Patcher {
|
object Patcher {
|
||||||
class Options(
|
class Options(
|
||||||
|
|
@ -38,7 +41,9 @@ object Patcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun patch(options: Options) {
|
suspend fun patch(logger: Logger, options: Options) {
|
||||||
LSPatch(*options.toStringArray()).doCommandLine()
|
withContext(Dispatchers.IO) {
|
||||||
|
LSPatch(logger, *options.toStringArray()).doCommandLine()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package org.lsposed.lspatch.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
private val ShimmerColorShades
|
||||||
|
@Composable get() = listOf(
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer.copy(0.9f),
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer.copy(0.2f),
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer.copy(0.9f)
|
||||||
|
)
|
||||||
|
|
||||||
|
class ShimmerScope(val brush: Brush)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShimmerAnimation(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
content: @Composable ShimmerScope.() -> Unit
|
||||||
|
) {
|
||||||
|
val transition = rememberInfiniteTransition()
|
||||||
|
val translateAnim by transition.animateFloat(
|
||||||
|
initialValue = 0f,
|
||||||
|
targetValue = 1000f,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
tween(durationMillis = 1200, easing = FastOutSlowInEasing),
|
||||||
|
RepeatMode.Reverse
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val brush = Brush.linearGradient(
|
||||||
|
colors = if (enabled) ShimmerColorShades else List(3) { ShimmerColorShades[0] },
|
||||||
|
start = Offset(10f, 10f),
|
||||||
|
end = Offset(translateAnim, translateAnim)
|
||||||
|
)
|
||||||
|
|
||||||
|
Surface(modifier.background(brush)) {
|
||||||
|
content(ShimmerScope(brush))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun ShimmerPreview() {
|
||||||
|
ShimmerAnimation {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.size(250.dp)
|
||||||
|
.background(brush = brush)
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(30.dp)
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.background(brush = brush)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
package org.lsposed.lspatch.ui.page
|
package org.lsposed.lspatch.ui.page
|
||||||
|
|
||||||
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.animation.core.Spring
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.animation.core.spring
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Api
|
import androidx.compose.material.icons.outlined.Api
|
||||||
|
|
@ -18,7 +24,9 @@ import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
|
@ -26,25 +34,32 @@ import org.lsposed.lspatch.Patcher
|
||||||
import org.lsposed.lspatch.R
|
import org.lsposed.lspatch.R
|
||||||
import org.lsposed.lspatch.TAG
|
import org.lsposed.lspatch.TAG
|
||||||
import org.lsposed.lspatch.ui.component.SelectionColumn
|
import org.lsposed.lspatch.ui.component.SelectionColumn
|
||||||
|
import org.lsposed.lspatch.ui.component.ShimmerAnimation
|
||||||
import org.lsposed.lspatch.ui.component.settings.SettingsCheckBox
|
import org.lsposed.lspatch.ui.component.settings.SettingsCheckBox
|
||||||
import org.lsposed.lspatch.ui.component.settings.SettingsItem
|
import org.lsposed.lspatch.ui.component.settings.SettingsItem
|
||||||
import org.lsposed.lspatch.ui.util.LocalNavController
|
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||||
|
import org.lsposed.lspatch.ui.util.isScrolledToEnd
|
||||||
|
import org.lsposed.lspatch.ui.util.lastItemIndex
|
||||||
import org.lsposed.lspatch.ui.viewmodel.AppInfo
|
import org.lsposed.lspatch.ui.viewmodel.AppInfo
|
||||||
|
import org.lsposed.patch.util.Logger
|
||||||
|
|
||||||
|
enum class PatchState {
|
||||||
|
SELECTING, CONFIGURING, SUBMITTING, PATCHING, FINISHED
|
||||||
|
}
|
||||||
|
|
||||||
class NewPatchPageViewModel : ViewModel() {
|
class NewPatchPageViewModel : ViewModel() {
|
||||||
var patchApp by mutableStateOf<AppInfo?>(null)
|
var patchState by mutableStateOf(PatchState.SELECTING)
|
||||||
var confirm by mutableStateOf(false)
|
|
||||||
var patchOptions by mutableStateOf<Patcher.Options?>(null)
|
var patchOptions by mutableStateOf<Patcher.Options?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPatchFab() {
|
fun NewPatchFab() {
|
||||||
val viewModel = viewModel<NewPatchPageViewModel>()
|
val viewModel = viewModel<NewPatchPageViewModel>()
|
||||||
if (viewModel.patchApp != null) {
|
if (viewModel.patchState == PatchState.CONFIGURING) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = { Text(stringResource(R.string.patch_start)) },
|
text = { Text(stringResource(R.string.patch_start)) },
|
||||||
icon = { Icon(Icons.Outlined.AutoFixHigh, null) },
|
icon = { Icon(Icons.Outlined.AutoFixHigh, null) },
|
||||||
onClick = { viewModel.confirm = true }
|
onClick = { viewModel.patchState = PatchState.SUBMITTING }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -53,21 +68,20 @@ fun NewPatchFab() {
|
||||||
fun NewPatchPage() {
|
fun NewPatchPage() {
|
||||||
val viewModel = viewModel<NewPatchPageViewModel>()
|
val viewModel = viewModel<NewPatchPageViewModel>()
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val appInfo by navController.currentBackStackEntry!!.savedStateHandle
|
val patchApp by navController.currentBackStackEntry!!.savedStateHandle
|
||||||
.getLiveData<AppInfo>("appInfo").observeAsState()
|
.getLiveData<AppInfo>("appInfo").observeAsState()
|
||||||
viewModel.patchApp = appInfo
|
if (viewModel.patchState == PatchState.SELECTING && patchApp != null) viewModel.patchState = PatchState.CONFIGURING
|
||||||
|
|
||||||
Log.d(TAG, "confirm = ${viewModel.confirm}")
|
Log.d(TAG, "NewPatchPage: ${viewModel.patchState}")
|
||||||
|
when (viewModel.patchState) {
|
||||||
when {
|
PatchState.SELECTING -> navController.navigate(PageList.SelectApps.name + "/false")
|
||||||
viewModel.patchApp == null -> navController.navigate(PageList.SelectApps.name + "/false")
|
PatchState.CONFIGURING, PatchState.SUBMITTING -> PatchOptionsPage(patchApp!!)
|
||||||
viewModel.patchOptions == null -> PatchOptionsPage(viewModel.patchApp!!, viewModel.confirm)
|
PatchState.PATCHING, PatchState.FINISHED -> DoPatchPage(viewModel.patchOptions!!)
|
||||||
else -> PatchingPage(viewModel.patchOptions!!)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PatchOptionsPage(patchApp: AppInfo, confirm: Boolean) {
|
private fun PatchOptionsPage(patchApp: AppInfo) {
|
||||||
val viewModel = viewModel<NewPatchPageViewModel>()
|
val viewModel = viewModel<NewPatchPageViewModel>()
|
||||||
var useManager by rememberSaveable { mutableStateOf(true) }
|
var useManager by rememberSaveable { mutableStateOf(true) }
|
||||||
var debuggable by rememberSaveable { mutableStateOf(false) }
|
var debuggable by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
@ -77,9 +91,11 @@ private fun PatchOptionsPage(patchApp: AppInfo, confirm: Boolean) {
|
||||||
val sigBypassLevel by rememberSaveable { mutableStateOf(2) }
|
val sigBypassLevel by rememberSaveable { mutableStateOf(2) }
|
||||||
var overrideVersionCode by rememberSaveable { mutableStateOf(false) }
|
var overrideVersionCode by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
if (confirm) LaunchedEffect(patchApp) {
|
if (viewModel.patchState == PatchState.SUBMITTING) LaunchedEffect(patchApp) {
|
||||||
|
val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path
|
||||||
viewModel.patchOptions = Patcher.Options(
|
viewModel.patchOptions = Patcher.Options(
|
||||||
apkPaths = arrayOf(patchApp.app.sourceDir), // TODO: Split Apk
|
apkPaths = arrayOf(patchApp.app.sourceDir), // TODO: Split Apk
|
||||||
|
outputPath = downloadDir,
|
||||||
debuggable = debuggable,
|
debuggable = debuggable,
|
||||||
sigbypassLevel = sigBypassLevel,
|
sigbypassLevel = sigBypassLevel,
|
||||||
v1 = v1, v2 = v2, v3 = v3,
|
v1 = v1, v2 = v2, v3 = v3,
|
||||||
|
|
@ -88,6 +104,7 @@ private fun PatchOptionsPage(patchApp: AppInfo, confirm: Boolean) {
|
||||||
verbose = true,
|
verbose = true,
|
||||||
embeddedModules = emptyList() // TODO: Embed modules
|
embeddedModules = emptyList() // TODO: Embed modules
|
||||||
)
|
)
|
||||||
|
viewModel.patchState = PatchState.PATCHING
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -172,6 +189,89 @@ private fun PatchOptionsPage(patchApp: AppInfo, confirm: Boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PatchingPage(patcherOptions: Patcher.Options) {
|
private fun DoPatchPage(patcherOptions: Patcher.Options) {
|
||||||
|
val viewModel = viewModel<NewPatchPageViewModel>()
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val logs = remember { mutableStateListOf<Pair<Int, String>>() }
|
||||||
|
val logger = remember {
|
||||||
|
object : Logger() {
|
||||||
|
override fun d(msg: String) {
|
||||||
|
if (verbose) {
|
||||||
|
Log.d(TAG, msg)
|
||||||
|
logs += Log.DEBUG to msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun i(msg: String) {
|
||||||
|
Log.i(TAG, msg)
|
||||||
|
logs += Log.INFO to msg
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun e(msg: String) {
|
||||||
|
Log.e(TAG, msg)
|
||||||
|
logs += Log.ERROR to msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(patcherOptions) {
|
||||||
|
Patcher.patch(logger, patcherOptions)
|
||||||
|
viewModel.patchState = PatchState.FINISHED
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(24.dp)
|
||||||
|
.wrapContentHeight()
|
||||||
|
.animateContentSize(spring(stiffness = Spring.StiffnessLow))
|
||||||
|
) {
|
||||||
|
val patching by remember { derivedStateOf { viewModel.patchState == PatchState.PATCHING } }
|
||||||
|
ShimmerAnimation(enabled = patching) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalTextStyle provides MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace)
|
||||||
|
) {
|
||||||
|
val scrollState = rememberLazyListState()
|
||||||
|
LazyColumn(
|
||||||
|
state = scrollState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(max = 300.dp)
|
||||||
|
.clip(RoundedCornerShape(32.dp))
|
||||||
|
.background(brush)
|
||||||
|
.padding(horizontal = 24.dp, vertical = 18.dp)
|
||||||
|
) {
|
||||||
|
items(logs) {
|
||||||
|
when (it.first) {
|
||||||
|
Log.DEBUG -> Text(text = it.second)
|
||||||
|
Log.INFO -> Text(text = it.second)
|
||||||
|
Log.ERROR -> Text(text = it.second, color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(scrollState.lastItemIndex) {
|
||||||
|
if (!scrollState.isScrolledToEnd) {
|
||||||
|
scrollState.animateScrollToItem(scrollState.lastItemIndex!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!patching) {
|
||||||
|
Row(Modifier.padding(top = 12.dp)) {
|
||||||
|
Button(
|
||||||
|
onClick = { navController.popBackStack() },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
content = { Text(stringResource(R.string.patch_return)) }
|
||||||
|
)
|
||||||
|
Spacer(Modifier.weight(0.2f))
|
||||||
|
Button(
|
||||||
|
onClick = { /* TODO: Install */ },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
content = { Text(stringResource(R.string.patch_install)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.lsposed.lspatch.ui.util
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
|
||||||
|
val LazyListState.lastVisibleItemIndex
|
||||||
|
get() = layoutInfo.visibleItemsInfo.lastOrNull()?.index
|
||||||
|
|
||||||
|
val LazyListState.lastItemIndex
|
||||||
|
get() = layoutInfo.totalItemsCount.let { if (it == 0) null else it }
|
||||||
|
|
||||||
|
val LazyListState.isScrolledToEnd
|
||||||
|
get() = lastVisibleItemIndex == lastItemIndex
|
||||||
|
|
@ -26,4 +26,6 @@
|
||||||
<string name="patch_override_version_code">Override version code</string>
|
<string name="patch_override_version_code">Override version code</string>
|
||||||
<string name="patch_override_version_code_desc">Override the patched app\'s version code to 1\nThis allows downgrade installation</string>
|
<string name="patch_override_version_code_desc">Override the patched app\'s version code to 1\nThis allows downgrade installation</string>
|
||||||
<string name="patch_start">Start Patch</string>
|
<string name="patch_start">Start Patch</string>
|
||||||
|
<string name="patch_return">Return</string>
|
||||||
|
<string name="patch_install">Install</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.lsposed.lspatch.share.PatchConfig;
|
import org.lsposed.lspatch.share.PatchConfig;
|
||||||
import org.lsposed.patch.util.ApkSignatureHelper;
|
import org.lsposed.patch.util.ApkSignatureHelper;
|
||||||
|
import org.lsposed.patch.util.JavaLogger;
|
||||||
|
import org.lsposed.patch.util.Logger;
|
||||||
import org.lsposed.patch.util.ManifestParser;
|
import org.lsposed.patch.util.ManifestParser;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
@ -114,15 +116,19 @@ public class LSPatch {
|
||||||
|
|
||||||
private final JCommander jCommander;
|
private final JCommander jCommander;
|
||||||
|
|
||||||
public LSPatch(String... args) {
|
private final Logger logger;
|
||||||
|
|
||||||
|
public LSPatch(Logger logger, String... args) {
|
||||||
jCommander = JCommander.newBuilder()
|
jCommander = JCommander.newBuilder()
|
||||||
.addObject(this)
|
.addObject(this)
|
||||||
.build();
|
.build();
|
||||||
jCommander.parse(args);
|
jCommander.parse(args);
|
||||||
|
this.logger = logger;
|
||||||
|
logger.verbose = verbose;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String... args) throws IOException {
|
public static void main(String... args) throws IOException {
|
||||||
LSPatch lsPatch = new LSPatch(args);
|
LSPatch lsPatch = new LSPatch(new JavaLogger(), args);
|
||||||
try {
|
try {
|
||||||
lsPatch.doCommandLine();
|
lsPatch.doCommandLine();
|
||||||
} catch (PatchError e) {
|
} catch (PatchError e) {
|
||||||
|
|
@ -157,7 +163,7 @@ public class LSPatch {
|
||||||
|
|
||||||
if (outputFile.exists() && !forceOverwrite)
|
if (outputFile.exists() && !forceOverwrite)
|
||||||
throw new PatchError(outputPath + " exists. Use --force to overwrite");
|
throw new PatchError(outputPath + " exists. Use --force to overwrite");
|
||||||
System.out.println("Processing " + srcApkFile + " -> " + outputFile);
|
logger.i("Processing " + srcApkFile + " -> " + outputFile);
|
||||||
|
|
||||||
patch(srcApkFile, outputFile);
|
patch(srcApkFile, outputFile);
|
||||||
}
|
}
|
||||||
|
|
@ -170,16 +176,15 @@ public class LSPatch {
|
||||||
File tmpApk = Files.createTempFile(srcApkFile.getName(), "unsigned").toFile();
|
File tmpApk = Files.createTempFile(srcApkFile.getName(), "unsigned").toFile();
|
||||||
tmpApk.delete();
|
tmpApk.delete();
|
||||||
|
|
||||||
if (verbose)
|
logger.d("apk path: " + srcApkFile);
|
||||||
System.out.println("apk path: " + srcApkFile);
|
|
||||||
|
|
||||||
System.out.println("Parsing original apk...");
|
logger.i("Parsing original apk...");
|
||||||
|
|
||||||
try (var dstZFile = ZFile.openReadWrite(tmpApk, Z_FILE_OPTIONS);
|
try (var dstZFile = ZFile.openReadWrite(tmpApk, Z_FILE_OPTIONS);
|
||||||
var srcZFile = dstZFile.addNestedZip((ignore) -> ORIGINAL_APK_ASSET_PATH, srcApkFile, false)) {
|
var srcZFile = dstZFile.addNestedZip((ignore) -> ORIGINAL_APK_ASSET_PATH, srcApkFile, false)) {
|
||||||
|
|
||||||
// sign apk
|
// sign apk
|
||||||
System.out.println("Register apk signer...");
|
logger.i("Register apk signer...");
|
||||||
try {
|
try {
|
||||||
var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
try (var is = getClass().getClassLoader().getResourceAsStream("assets/keystore")) {
|
try (var is = getClass().getClassLoader().getResourceAsStream("assets/keystore")) {
|
||||||
|
|
@ -205,8 +210,7 @@ public class LSPatch {
|
||||||
throw new PatchError("get original signature failed");
|
throw new PatchError("get original signature failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verbose)
|
logger.d("Original signature\n" + originalSignature);
|
||||||
System.out.println("Original signature\n" + originalSignature);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy out manifest file from zlib
|
// copy out manifest file from zlib
|
||||||
|
|
@ -222,11 +226,10 @@ public class LSPatch {
|
||||||
throw new PatchError("Failed to parse AndroidManifest.xml");
|
throw new PatchError("Failed to parse AndroidManifest.xml");
|
||||||
appComponentFactory = pair.appComponentFactory == null ? "" : pair.appComponentFactory;
|
appComponentFactory = pair.appComponentFactory == null ? "" : pair.appComponentFactory;
|
||||||
|
|
||||||
if (verbose)
|
logger.d("original appComponentFactory class: " + appComponentFactory);
|
||||||
System.out.println("original appComponentFactory class: " + appComponentFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("Patching apk...");
|
logger.i("Patching apk...");
|
||||||
// modify manifest
|
// modify manifest
|
||||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open()))) {
|
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open()))) {
|
||||||
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
||||||
|
|
@ -234,8 +237,7 @@ public class LSPatch {
|
||||||
throw new PatchError("Error when modifying manifest", e);
|
throw new PatchError("Error when modifying manifest", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verbose)
|
logger.d("Adding native lib..");
|
||||||
System.out.println("Adding native lib..");
|
|
||||||
|
|
||||||
// copy so and dex files into the unzipped apk
|
// copy so and dex files into the unzipped apk
|
||||||
// do not put liblspd.so into apk!lib because x86 native bridge causes crash
|
// do not put liblspd.so into apk!lib because x86 native bridge causes crash
|
||||||
|
|
@ -247,12 +249,10 @@ public class LSPatch {
|
||||||
// More exception info
|
// More exception info
|
||||||
throw new PatchError("Error when adding native lib", e);
|
throw new PatchError("Error when adding native lib", e);
|
||||||
}
|
}
|
||||||
if (verbose)
|
logger.d("added " + entryName);
|
||||||
System.out.println("added " + entryName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verbose)
|
logger.d("Adding dex..");
|
||||||
System.out.println("Adding dex..");
|
|
||||||
|
|
||||||
try (var is = getClass().getClassLoader().getResourceAsStream("assets/dex/loader.dex")) {
|
try (var is = getClass().getClassLoader().getResourceAsStream("assets/dex/loader.dex")) {
|
||||||
dstZFile.add("classes.dex", is);
|
dstZFile.add("classes.dex", is);
|
||||||
|
|
@ -278,8 +278,7 @@ public class LSPatch {
|
||||||
|
|
||||||
Set<String> apkArchs = new HashSet<>();
|
Set<String> apkArchs = new HashSet<>();
|
||||||
|
|
||||||
if (verbose)
|
logger.d("Search target apk library arch...");
|
||||||
System.out.println("Search target apk library arch...");
|
|
||||||
|
|
||||||
for (StoredEntry storedEntry : srcZFile.entries()) {
|
for (StoredEntry storedEntry : srcZFile.entries()) {
|
||||||
var name = storedEntry.getCentralDirectoryHeader().getName();
|
var name = storedEntry.getCentralDirectoryHeader().getName();
|
||||||
|
|
@ -291,7 +290,7 @@ public class LSPatch {
|
||||||
if (apkArchs.isEmpty()) apkArchs.addAll(ARCHES);
|
if (apkArchs.isEmpty()) apkArchs.addAll(ARCHES);
|
||||||
apkArchs.removeIf((arch) -> {
|
apkArchs.removeIf((arch) -> {
|
||||||
if (!ARCHES.contains(arch) && !arch.equals("armeabi")) {
|
if (!ARCHES.contains(arch) && !arch.equals("armeabi")) {
|
||||||
System.err.println("Warning: unsupported arch " + arch + ". Skipping...");
|
logger.e("Warning: unsupported arch " + arch + ". Skipping...");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -300,8 +299,7 @@ public class LSPatch {
|
||||||
embedModules(dstZFile);
|
embedModules(dstZFile);
|
||||||
|
|
||||||
// create zip link
|
// create zip link
|
||||||
if (verbose)
|
logger.d("Creating nested apk link...");
|
||||||
System.out.println("Creating nested apk link...");
|
|
||||||
|
|
||||||
for (StoredEntry entry : srcZFile.entries()) {
|
for (StoredEntry entry : srcZFile.entries()) {
|
||||||
String name = entry.getCentralDirectoryHeader().getName();
|
String name = entry.getCentralDirectoryHeader().getName();
|
||||||
|
|
@ -314,12 +312,12 @@ public class LSPatch {
|
||||||
|
|
||||||
dstZFile.realign();
|
dstZFile.realign();
|
||||||
|
|
||||||
System.out.println("Writing apk...");
|
logger.i("Writing apk...");
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
outputFile.delete();
|
outputFile.delete();
|
||||||
FileUtils.moveFile(tmpApk, outputFile);
|
FileUtils.moveFile(tmpApk, outputFile);
|
||||||
System.out.println("Done. Output APK: " + outputFile.getAbsolutePath());
|
logger.i("Done. Output APK: " + outputFile.getAbsolutePath());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new PatchError("Error writing apk", e);
|
throw new PatchError("Error writing apk", e);
|
||||||
}
|
}
|
||||||
|
|
@ -327,7 +325,7 @@ public class LSPatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void embedModules(ZFile zFile) {
|
private void embedModules(ZFile zFile) {
|
||||||
System.out.println("Embedding modules...");
|
logger.i("Embedding modules...");
|
||||||
for (var module : modules) {
|
for (var module : modules) {
|
||||||
File file = new File(module);
|
File file = new File(module);
|
||||||
try (var apk = ZFile.openReadOnly(new File(module));
|
try (var apk = ZFile.openReadOnly(new File(module));
|
||||||
|
|
@ -336,10 +334,10 @@ public class LSPatch {
|
||||||
) {
|
) {
|
||||||
var manifest = Objects.requireNonNull(ManifestParser.parseManifestFile(xmlIs));
|
var manifest = Objects.requireNonNull(ManifestParser.parseManifestFile(xmlIs));
|
||||||
var packageName = manifest.packageName;
|
var packageName = manifest.packageName;
|
||||||
System.out.println(" - " + packageName);
|
logger.i(" - " + packageName);
|
||||||
zFile.add("assets/lspatch/modules/" + packageName + ".bin", fileIs);
|
zFile.add("assets/lspatch/modules/" + packageName + ".bin", fileIs);
|
||||||
} catch (NullPointerException | IOException e) {
|
} catch (NullPointerException | IOException e) {
|
||||||
System.err.println(module + " does not exist or is not a valid apk file.");
|
logger.e(module + " does not exist or is not a valid apk file.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.lsposed.patch.util;
|
||||||
|
|
||||||
|
public class JavaLogger extends Logger {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void d(String msg) {
|
||||||
|
if (verbose) System.out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void i(String msg) {
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void e(String msg) {
|
||||||
|
System.err.println(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.lsposed.patch.util;
|
||||||
|
|
||||||
|
public abstract class Logger {
|
||||||
|
|
||||||
|
public boolean verbose = false;
|
||||||
|
|
||||||
|
abstract public void d(String msg);
|
||||||
|
|
||||||
|
abstract public void i(String msg);
|
||||||
|
|
||||||
|
abstract public void e(String msg);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue