mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
feat: LSPatch obfuscation
This commit is contained in:
@ -64,6 +64,10 @@ class SnapEnhance {
|
|||||||
}
|
}
|
||||||
runCatching {
|
runCatching {
|
||||||
LSPatchUpdater.onBridgeConnected(appContext, bridgeClient)
|
LSPatchUpdater.onBridgeConnected(appContext, bridgeClient)
|
||||||
|
}.onFailure {
|
||||||
|
logCritical("Failed to init LSPatchUpdater", it)
|
||||||
|
}
|
||||||
|
runCatching {
|
||||||
measureTimeMillis {
|
measureTimeMillis {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
init(this)
|
init(this)
|
||||||
|
@ -5,8 +5,8 @@ import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
|||||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||||
import me.rhunk.snapenhance.core.util.hook.Hooker
|
import me.rhunk.snapenhance.core.util.hook.Hooker
|
||||||
|
|
||||||
class DeviceSpooferHook: Feature("device_spoofer", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
class DeviceSpooferHook: Feature("device_spoofer", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||||
override fun asyncOnActivityCreate() {
|
override fun init() {
|
||||||
if (context.config.experimental.spoof.globalState != true) return
|
if (context.config.experimental.spoof.globalState != true) return
|
||||||
|
|
||||||
val fingerprint by context.config.experimental.spoof.device.fingerprint
|
val fingerprint by context.config.experimental.spoof.device.fingerprint
|
||||||
|
@ -18,11 +18,24 @@ object LSPatchUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onBridgeConnected(context: ModContext, bridgeClient: BridgeClient) {
|
fun onBridgeConnected(context: ModContext, bridgeClient: BridgeClient) {
|
||||||
|
val obfuscatedModulePath by lazy {
|
||||||
|
(runCatching {
|
||||||
|
context::class.java.classLoader?.loadClass("org.lsposed.lspatch.share.Constants")
|
||||||
|
}.getOrNull())?.declaredFields?.firstOrNull { it.name == "MANAGER_PACKAGE_NAME" }?.also {
|
||||||
|
it.isAccessible = true
|
||||||
|
}?.get(null) as? String
|
||||||
|
}
|
||||||
|
|
||||||
val embeddedModule = context.androidContext.cacheDir
|
val embeddedModule = context.androidContext.cacheDir
|
||||||
.resolve("lspatch")
|
.resolve("lspatch")
|
||||||
.resolve(BuildConfig.APPLICATION_ID).let { moduleDir ->
|
.resolve(BuildConfig.APPLICATION_ID).let { moduleDir ->
|
||||||
if (!moduleDir.exists()) return@let null
|
if (!moduleDir.exists()) return@let null
|
||||||
moduleDir.listFiles()?.firstOrNull { it.extension == "apk" }
|
moduleDir.listFiles()?.firstOrNull { it.extension == "apk" }
|
||||||
|
} ?: obfuscatedModulePath?.let { path ->
|
||||||
|
context.androidContext.cacheDir.resolve(path).let dir@{ moduleDir ->
|
||||||
|
if (!moduleDir.exists()) return@dir null
|
||||||
|
moduleDir.listFiles()?.firstOrNull { it.extension == "apk" }
|
||||||
|
} ?: return
|
||||||
} ?: return
|
} ?: return
|
||||||
|
|
||||||
context.log.verbose("Found embedded SE at ${embeddedModule.absolutePath}", TAG)
|
context.log.verbose("Found embedded SE at ${embeddedModule.absolutePath}", TAG)
|
||||||
|
@ -80,6 +80,7 @@ dependencies {
|
|||||||
implementation(libs.libsu)
|
implementation(libs.libsu)
|
||||||
implementation(libs.guava)
|
implementation(libs.guava)
|
||||||
implementation(libs.apksig)
|
implementation(libs.apksig)
|
||||||
|
implementation(libs.dexlib2)
|
||||||
implementation(libs.gson)
|
implementation(libs.gson)
|
||||||
implementation(libs.jsoup)
|
implementation(libs.jsoup)
|
||||||
implementation(libs.okhttp)
|
implementation(libs.okhttp)
|
||||||
|
@ -21,4 +21,7 @@ class SharedConfig(
|
|||||||
|
|
||||||
var useRootInstaller get() = sharedPreferences.getBoolean("useRootInstaller", false)
|
var useRootInstaller get() = sharedPreferences.getBoolean("useRootInstaller", false)
|
||||||
set(value) = sharedPreferences.edit().putBoolean("useRootInstaller", value).apply()
|
set(value) = sharedPreferences.edit().putBoolean("useRootInstaller", value).apply()
|
||||||
|
|
||||||
|
var obfuscateLSPatch get() = sharedPreferences.getBoolean("obfuscateLSPatch", false)
|
||||||
|
set(value) = sharedPreferences.edit().putBoolean("obfuscateLSPatch", value).apply()
|
||||||
}
|
}
|
@ -10,7 +10,6 @@ import com.google.gson.Gson
|
|||||||
import com.wind.meditor.core.ManifestEditor
|
import com.wind.meditor.core.ManifestEditor
|
||||||
import com.wind.meditor.property.AttributeItem
|
import com.wind.meditor.property.AttributeItem
|
||||||
import com.wind.meditor.property.ModificationProperty
|
import com.wind.meditor.property.ModificationProperty
|
||||||
import me.rhunk.snapenhance.manager.lspatch.config.Constants.ORIGINAL_APK_ASSET_PATH
|
|
||||||
import me.rhunk.snapenhance.manager.lspatch.config.Constants.PROXY_APP_COMPONENT_FACTORY
|
import me.rhunk.snapenhance.manager.lspatch.config.Constants.PROXY_APP_COMPONENT_FACTORY
|
||||||
import me.rhunk.snapenhance.manager.lspatch.config.PatchConfig
|
import me.rhunk.snapenhance.manager.lspatch.config.PatchConfig
|
||||||
import me.rhunk.snapenhance.manager.lspatch.util.ApkSignatureHelper
|
import me.rhunk.snapenhance.manager.lspatch.util.ApkSignatureHelper
|
||||||
@ -22,28 +21,22 @@ import java.security.cert.X509Certificate
|
|||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
import kotlin.io.encoding.Base64
|
import kotlin.io.encoding.Base64
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
//https://github.com/LSPosed/LSPatch/blob/master/patch/src/main/java/org/lsposed/patch/LSPatch.java
|
//https://github.com/LSPosed/LSPatch/blob/master/patch/src/main/java/org/lsposed/patch/LSPatch.java
|
||||||
class LSPatch(
|
class LSPatch(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val modules: Map<String, File>, //packageName -> file
|
private val modules: Map<String, File>, //packageName -> file
|
||||||
|
private val obfuscate: Boolean,
|
||||||
private val printLog: (Any) -> Unit
|
private val printLog: (Any) -> Unit
|
||||||
) {
|
) {
|
||||||
companion object {
|
|
||||||
private val Z_FILE_OPTIONS = ZFileOptions().setAlignmentRule(
|
|
||||||
AlignmentRules.compose(
|
|
||||||
AlignmentRules.constantForSuffix(".so", 4096),
|
|
||||||
AlignmentRules.constantForSuffix(ORIGINAL_APK_ASSET_PATH, 4096)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun patchManifest(data: ByteArray, lspatchMetadata: String): ByteArray {
|
private fun patchManifest(data: ByteArray, lspatchMetadata: Pair<String, String>): ByteArray {
|
||||||
val property = ModificationProperty()
|
val property = ModificationProperty()
|
||||||
|
|
||||||
property.addApplicationAttribute(AttributeItem("appComponentFactory", PROXY_APP_COMPONENT_FACTORY))
|
property.addApplicationAttribute(AttributeItem("appComponentFactory", PROXY_APP_COMPONENT_FACTORY))
|
||||||
property.addMetaData(ModificationProperty.MetaData("lspatch", lspatchMetadata))
|
property.addMetaData(ModificationProperty.MetaData(lspatchMetadata.first, lspatchMetadata.second))
|
||||||
|
|
||||||
return ByteArrayOutputStream().apply {
|
return ByteArrayOutputStream().apply {
|
||||||
ManifestEditor(ByteArrayInputStream(data), this, property).processManifest()
|
ManifestEditor(ByteArrayInputStream(data), this, property).processManifest()
|
||||||
@ -70,7 +63,7 @@ class LSPatch(
|
|||||||
|
|
||||||
private fun resignApk(inputApkFile: File, outputFile: File) {
|
private fun resignApk(inputApkFile: File, outputFile: File) {
|
||||||
printLog("Resigning ${inputApkFile.absolutePath} to ${outputFile.absolutePath}")
|
printLog("Resigning ${inputApkFile.absolutePath} to ${outputFile.absolutePath}")
|
||||||
val dstZFile = ZFile.openReadWrite(outputFile, Z_FILE_OPTIONS)
|
val dstZFile = ZFile.openReadWrite(outputFile, ZFileOptions())
|
||||||
val inZFile = ZFile.openReadOnly(inputApkFile)
|
val inZFile = ZFile.openReadOnly(inputApkFile)
|
||||||
|
|
||||||
inZFile.entries().forEach { entry ->
|
inZFile.entries().forEach { entry ->
|
||||||
@ -90,12 +83,42 @@ class LSPatch(
|
|||||||
printLog("Done")
|
printLog("Done")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun uniqueHash(): String {
|
||||||
|
return Random.nextBytes(Random.nextInt(5, 10)).joinToString("") { "%02x".format(it) }
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
private fun patchApk(inputApkFile: File, outputFile: File) {
|
private fun patchApk(inputApkFile: File, outputFile: File) {
|
||||||
printLog("Patching ${inputApkFile.absolutePath} to ${outputFile.absolutePath}")
|
printLog("Patching ${inputApkFile.absolutePath} to ${outputFile.absolutePath}")
|
||||||
val dstZFile = ZFile.openReadWrite(outputFile, Z_FILE_OPTIONS)
|
|
||||||
val sourceApkFile = dstZFile.addNestedZip({ ORIGINAL_APK_ASSET_PATH }, inputApkFile, false)
|
val obfuscationCacheFolder = File(context.cacheDir, "lspatch").apply {
|
||||||
|
if (exists()) deleteRecursively()
|
||||||
|
mkdirs()
|
||||||
|
}
|
||||||
|
val lspatchObfuscation = LSPatchObfuscation(obfuscationCacheFolder) { printLog(it) }
|
||||||
|
val dexObfuscationConfig = if (obfuscate) DexObfuscationConfig(
|
||||||
|
packageName = uniqueHash(),
|
||||||
|
metadataManifestField = uniqueHash(),
|
||||||
|
metaLoaderFilePath = uniqueHash(),
|
||||||
|
configFilePath = uniqueHash(),
|
||||||
|
loaderFilePath = uniqueHash(),
|
||||||
|
libNativeFilePath = mapOf(
|
||||||
|
"arm64-v8a" to uniqueHash() + ".so",
|
||||||
|
"armeabi-v7a" to uniqueHash() + ".so",
|
||||||
|
),
|
||||||
|
originApkPath = uniqueHash(),
|
||||||
|
cachedOriginApkPath = uniqueHash(),
|
||||||
|
openAtApkPath = uniqueHash(),
|
||||||
|
assetModuleFolderPath = uniqueHash(),
|
||||||
|
) else null
|
||||||
|
|
||||||
|
val dstZFile = ZFile.openReadWrite(outputFile, ZFileOptions().setAlignmentRule(
|
||||||
|
AlignmentRules.compose(
|
||||||
|
AlignmentRules.constantForSuffix(".so", 4096),
|
||||||
|
AlignmentRules.constantForSuffix("assets/" + (dexObfuscationConfig?.originApkPath ?: "lspatch/origin.apk"), 4096)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
val patchConfig = PatchConfig(
|
val patchConfig = PatchConfig(
|
||||||
useManager = false,
|
useManager = false,
|
||||||
@ -115,32 +138,37 @@ class LSPatch(
|
|||||||
|
|
||||||
printLog("Patching manifest")
|
printLog("Patching manifest")
|
||||||
|
|
||||||
|
val sourceApkFile = dstZFile.addNestedZip({ "assets/" + (dexObfuscationConfig?.originApkPath ?: "lspatch/origin.apk") }, inputApkFile, false)
|
||||||
val originalManifestEntry = sourceApkFile.get("AndroidManifest.xml") ?: throw Exception("No original manifest found")
|
val originalManifestEntry = sourceApkFile.get("AndroidManifest.xml") ?: throw Exception("No original manifest found")
|
||||||
originalManifestEntry.open().use { inputStream ->
|
originalManifestEntry.open().use { inputStream ->
|
||||||
val patchedManifestData = patchManifest(inputStream.readBytes(), Base64.encode(patchConfig.toByteArray()))
|
val patchedManifestData = patchManifest(inputStream.readBytes(), (dexObfuscationConfig?.metadataManifestField ?: "lspatch") to Base64.encode(patchConfig.toByteArray()))
|
||||||
dstZFile.add("AndroidManifest.xml", patchedManifestData.inputStream())
|
dstZFile.add("AndroidManifest.xml", patchedManifestData.inputStream())
|
||||||
}
|
}
|
||||||
|
|
||||||
//add config
|
//add config
|
||||||
printLog("Adding config")
|
printLog("Adding config")
|
||||||
dstZFile.add("assets/lspatch/config.json", ByteArrayInputStream(patchConfig.toByteArray()))
|
dstZFile.add("assets/" + (dexObfuscationConfig?.configFilePath ?: "lspatch/config.json"), ByteArrayInputStream(patchConfig.toByteArray()))
|
||||||
|
|
||||||
// add loader dex
|
// add loader dex
|
||||||
printLog("Adding dex files")
|
printLog("Adding loader dex")
|
||||||
dstZFile.add("classes.dex", context.assets.open("lspatch/dexes/metaloader.dex"))
|
context.assets.open("lspatch/dexes/loader.dex").use { inputStream ->
|
||||||
dstZFile.add("assets/lspatch/loader.dex", context.assets.open("lspatch/dexes/loader.dex"))
|
dstZFile.add("assets/" + (dexObfuscationConfig?.loaderFilePath ?: "lspatch/loader.dex"), dexObfuscationConfig?.let {
|
||||||
|
lspatchObfuscation.obfuscateLoader(inputStream, it).inputStream()
|
||||||
|
} ?: inputStream)
|
||||||
|
}
|
||||||
|
|
||||||
//add natives
|
//add natives
|
||||||
printLog("Adding natives")
|
printLog("Adding natives")
|
||||||
context.assets.list("lspatch/so")?.forEach { native ->
|
context.assets.list("lspatch/so")?.forEach { native ->
|
||||||
dstZFile.add("assets/lspatch/so/$native/liblspatch.so", context.assets.open("lspatch/so/$native/liblspatch.so"), false)
|
dstZFile.add("assets/${dexObfuscationConfig?.libNativeFilePath?.get(native) ?: "lspatch/so/$native/liblspatch.so"}", context.assets.open("lspatch/so/$native/liblspatch.so"), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
//embed modules
|
//embed modules
|
||||||
printLog("Embedding modules")
|
printLog("Embedding modules")
|
||||||
modules.forEach { (packageName, module) ->
|
modules.forEach { (packageName, module) ->
|
||||||
printLog("- $packageName")
|
val obfuscatedPackageName = dexObfuscationConfig?.packageName ?: packageName
|
||||||
dstZFile.add("assets/lspatch/modules/$packageName.apk", module.inputStream())
|
printLog("- $obfuscatedPackageName")
|
||||||
|
dstZFile.add("assets/${dexObfuscationConfig?.assetModuleFolderPath ?: "lspatch/modules"}/$obfuscatedPackageName.apk", module.inputStream())
|
||||||
}
|
}
|
||||||
|
|
||||||
// link apk entries
|
// link apk entries
|
||||||
@ -148,7 +176,7 @@ class LSPatch(
|
|||||||
|
|
||||||
for (entry in sourceApkFile.entries()) {
|
for (entry in sourceApkFile.entries()) {
|
||||||
val name = entry.centralDirectoryHeader.name
|
val name = entry.centralDirectoryHeader.name
|
||||||
if (name.startsWith("classes") && name.endsWith(".dex")) continue
|
if (dexObfuscationConfig == null && name.startsWith("classes") && name.endsWith(".dex")) continue
|
||||||
if (dstZFile[name] != null) continue
|
if (dstZFile[name] != null) continue
|
||||||
if (name == "AndroidManifest.xml") continue
|
if (name == "AndroidManifest.xml") continue
|
||||||
if (name.startsWith("META-INF") && (name.endsWith(".SF") || name.endsWith(".MF") || name.endsWith(
|
if (name.startsWith("META-INF") && (name.endsWith(".SF") || name.endsWith(".MF") || name.endsWith(
|
||||||
@ -158,8 +186,20 @@ class LSPatch(
|
|||||||
sourceApkFile.addFileLink(name, name)
|
sourceApkFile.addFileLink(name, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printLog("Adding meta loader dex")
|
||||||
|
context.assets.open("lspatch/dexes/metaloader.dex").use { inputStream ->
|
||||||
|
dstZFile.add(dexObfuscationConfig?.let { "classes9.dex" } ?: "classes.dex", dexObfuscationConfig?.let {
|
||||||
|
lspatchObfuscation.obfuscateMetaLoader(inputStream, it).inputStream()
|
||||||
|
} ?: inputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
printLog("Writing apk")
|
||||||
dstZFile.realign()
|
dstZFile.realign()
|
||||||
dstZFile.close()
|
dstZFile.close()
|
||||||
|
sourceApkFile.close()
|
||||||
|
|
||||||
|
printLog("Cleaning obfuscation cache")
|
||||||
|
obfuscationCacheFolder.deleteRecursively()
|
||||||
printLog("Done")
|
printLog("Done")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
package me.rhunk.snapenhance.manager.lspatch
|
||||||
|
|
||||||
|
import org.jf.dexlib2.Opcodes
|
||||||
|
import org.jf.dexlib2.dexbacked.DexBackedDexFile
|
||||||
|
import org.jf.dexlib2.iface.reference.StringReference
|
||||||
|
import org.jf.dexlib2.writer.io.FileDataStore
|
||||||
|
import org.jf.dexlib2.writer.pool.DexPool
|
||||||
|
import org.jf.dexlib2.writer.pool.StringPool
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
data class DexObfuscationConfig(
|
||||||
|
val packageName: String,
|
||||||
|
val metadataManifestField: String? = null,
|
||||||
|
val metaLoaderFilePath: String? = null,
|
||||||
|
val configFilePath: String? = null,
|
||||||
|
val loaderFilePath: String? = null,
|
||||||
|
val originApkPath: String? = null,
|
||||||
|
val cachedOriginApkPath: String? = null,
|
||||||
|
val openAtApkPath: String? = null,
|
||||||
|
val assetModuleFolderPath: String? = null,
|
||||||
|
val libNativeFilePath: Map<String, String> = mapOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
class LSPatchObfuscation(
|
||||||
|
private val cacheFolder: File,
|
||||||
|
private val printLog: (String) -> Unit = { println(it) }
|
||||||
|
) {
|
||||||
|
private fun obfuscateDexFile(dexStrings: Map<String, String?>, inputStream: InputStream): File {
|
||||||
|
val dexFile = DexBackedDexFile.fromInputStream(Opcodes.forApi(29), BufferedInputStream(inputStream))
|
||||||
|
|
||||||
|
val dexPool = object: DexPool(dexFile.opcodes) {
|
||||||
|
override fun getSectionProvider(): SectionProvider {
|
||||||
|
val dexPool = this
|
||||||
|
return object: DexPoolSectionProvider() {
|
||||||
|
override fun getStringSection() = object: StringPool(dexPool) {
|
||||||
|
private val cacheMap = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
override fun intern(string: CharSequence) {
|
||||||
|
dexStrings[string.toString()]?.let {
|
||||||
|
cacheMap[string.toString()] = it
|
||||||
|
printLog("mapping $string to $it")
|
||||||
|
super.intern(it)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
super.intern(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemIndex(key: CharSequence): Int {
|
||||||
|
return cacheMap[key.toString()]?.let {
|
||||||
|
internedItems[it]
|
||||||
|
} ?: super.getItemIndex(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemIndex(key: StringReference): Int {
|
||||||
|
return cacheMap[key.toString()]?.let {
|
||||||
|
internedItems[it]
|
||||||
|
} ?: super.getItemIndex(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dexFile.classes.forEach { dexBackedClassDef ->
|
||||||
|
dexPool.internClass(dexBackedClassDef)
|
||||||
|
}
|
||||||
|
val outputFile = File.createTempFile("obf", ".dex", cacheFolder)
|
||||||
|
dexPool.writeTo(FileDataStore(outputFile))
|
||||||
|
return outputFile
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun obfuscateMetaLoader(inputStream: InputStream, config: DexObfuscationConfig): File {
|
||||||
|
return obfuscateDexFile(mapOf(
|
||||||
|
"assets/lspatch/config.json" to "assets/${config.configFilePath}",
|
||||||
|
"assets/lspatch/loader.dex" to "assets/${config.loaderFilePath}",
|
||||||
|
) + (config.libNativeFilePath.takeIf { it.isNotEmpty() }?.let {
|
||||||
|
mapOf(
|
||||||
|
"!/assets/lspatch/so/" to "!/assets/",
|
||||||
|
"assets/lspatch/so/" to "assets/",
|
||||||
|
"/liblspatch.so" to "",
|
||||||
|
"arm64-v8a" to config.libNativeFilePath["arm64-v8a"],
|
||||||
|
"armeabi-v7a" to config.libNativeFilePath["armeabi-v7a"],
|
||||||
|
"x86" to config.libNativeFilePath["x86"],
|
||||||
|
"x86_64" to config.libNativeFilePath["x86_64"],
|
||||||
|
)
|
||||||
|
} ?: mapOf()), inputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun obfuscateLoader(inputStream: InputStream, config: DexObfuscationConfig): File {
|
||||||
|
return obfuscateDexFile(mapOf(
|
||||||
|
"assets/lspatch/config.json" to config.configFilePath?.let { "assets/$it" },
|
||||||
|
"assets/lspatch/loader.dex" to config.loaderFilePath?.let { "assets/$it" },
|
||||||
|
"assets/lspatch/metaloader.dex" to config.metaLoaderFilePath?.let { "assets/$it" },
|
||||||
|
"assets/lspatch/origin.apk" to config.originApkPath?.let { "assets/$it" },
|
||||||
|
"/lspatch/origin/" to config.cachedOriginApkPath?.let { "/$it/" }, // context.getCacheDir() + ==> "/lspatch/origin/" <== + sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc() + ".apk";
|
||||||
|
"/lspatch/" to config.cachedOriginApkPath?.let { "/$it/" }, // context.getCacheDir() + "/lspatch/" + packageName + "/"
|
||||||
|
"cache/lspatch/origin/" to config.cachedOriginApkPath?.let { "cache/$it" }, //LSPApplication => Path originPath = Paths.get(appInfo.dataDir, "cache/lspatch/origin/");
|
||||||
|
"assets/lspatch/modules/" to config.assetModuleFolderPath?.let { "assets/$it/" }, // Constants.java => EMBEDDED_MODULES_ASSET_PATH
|
||||||
|
"lspatch/modules" to config.assetModuleFolderPath, // LocalApplicationService.java => context.getAssets().list("lspatch/modules"),
|
||||||
|
"lspatch/modules/" to config.assetModuleFolderPath?.let { "$it/" }, // LocalApplicationService.java => try (var is = context.getAssets().open("lspatch/modules/" + name)) {
|
||||||
|
"lspatch" to config.metadataManifestField, // SigBypass.java => "lspatch",
|
||||||
|
"org.lsposed.lspatch" to config.cachedOriginApkPath?.let { "$it/${config.packageName}/" }, // Constants.java => "org.lsposed.lspatch", (Used in LSPatchUpdater.kt)
|
||||||
|
), inputStream)
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ package me.rhunk.snapenhance.manager.lspatch.config
|
|||||||
|
|
||||||
//https://github.com/LSPosed/LSPatch/blob/master/share/java/src/main/java/org/lsposed/lspatch/share/Constants.java
|
//https://github.com/LSPosed/LSPatch/blob/master/share/java/src/main/java/org/lsposed/lspatch/share/Constants.java
|
||||||
object Constants {
|
object Constants {
|
||||||
const val ORIGINAL_APK_ASSET_PATH = "assets/lspatch/origin.apk"
|
|
||||||
const val PROXY_APP_COMPONENT_FACTORY =
|
const val PROXY_APP_COMPONENT_FACTORY =
|
||||||
"org.lsposed.lspatch.metaloader.LSPAppComponentFactoryStub"
|
"org.lsposed.lspatch.metaloader.LSPAppComponentFactoryStub"
|
||||||
}
|
}
|
@ -145,6 +145,11 @@ class SettingsTab : Tab("settings", isPrimary = true, icon = Icons.Default.Setti
|
|||||||
setValue = { sharedConfig.useRootInstaller = it },
|
setValue = { sharedConfig.useRootInstaller = it },
|
||||||
label = "Use root installer"
|
label = "Use root installer"
|
||||||
)
|
)
|
||||||
|
ConfigBooleanRow(
|
||||||
|
getValue = { sharedConfig.obfuscateLSPatch },
|
||||||
|
setValue = { sharedConfig.obfuscateLSPatch = it },
|
||||||
|
label = "Obfuscate LSPatch (experimental)"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,6 +16,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.manager.data.APKMirror
|
import me.rhunk.snapenhance.manager.data.APKMirror
|
||||||
import me.rhunk.snapenhance.manager.data.DownloadItem
|
import me.rhunk.snapenhance.manager.data.DownloadItem
|
||||||
@ -85,7 +86,7 @@ class LSPatchTab : Tab("lspatch") {
|
|||||||
sharedConfig.snapEnhancePackageName to module,
|
sharedConfig.snapEnhancePackageName to module,
|
||||||
), printLog = {
|
), printLog = {
|
||||||
log("[LSPatch] $it")
|
log("[LSPatch] $it")
|
||||||
})
|
}, obfuscate = sharedConfig.obfuscateLSPatch)
|
||||||
|
|
||||||
log("== Patching apk ==")
|
log("== Patching apk ==")
|
||||||
val outputFiles = lsPatch.patchSplits(listOf(apkFile!!))
|
val outputFiles = lsPatch.patchSplits(listOf(apkFile!!))
|
||||||
@ -138,6 +139,12 @@ class LSPatchTab : Tab("lspatch") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
coroutineScope.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
fun triggerInstallation(shouldUninstall: Boolean) {
|
fun triggerInstallation(shouldUninstall: Boolean) {
|
||||||
|
Reference in New Issue
Block a user