commit 8b7c4a0a8878fa13b3ed320573371480e623761f
parent 26a1bdd7f907b077917e58b2d474ff5cf418cb43
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Tue, 17 Oct 2023 23:00:29 +0200
feat: LSPatchUpdater
Diffstat:
3 files changed, 103 insertions(+), 38 deletions(-)
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt
@@ -20,6 +20,7 @@ import me.rhunk.snapenhance.core.data.SnapClassCache
import me.rhunk.snapenhance.core.event.events.impl.SnapWidgetBroadcastReceiveEvent
import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent
import me.rhunk.snapenhance.core.messaging.CoreMessagingBridge
+import me.rhunk.snapenhance.core.util.LSPatchUpdater
import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.hook
import kotlin.system.measureTimeMillis
@@ -48,22 +49,21 @@ class SnapEnhance {
init {
Application::class.java.hook("attach", HookStage.BEFORE) { param ->
appContext.apply {
- androidContext = param.arg<Context>(0).also {
- classLoader = it.classLoader
- }
+ androidContext = param.arg<Context>(0).also { classLoader = it.classLoader }
bridgeClient = BridgeClient(appContext)
bridgeClient.apply {
connect(
- timeout = {
- crash("SnapEnhance bridge service is not responding. Please download stable version from https://github.com/rhunk/SnapEnhance/releases", it)
+ onFailure = {
+ crash("Snapchat can't connect to the SnapEnhance app. Please download stable version from https://github.com/rhunk/SnapEnhance/releases", it)
}
) { bridgeResult ->
if (!bridgeResult) {
- logCritical("Cannot connect to bridge service")
+ logCritical("Cannot connect to the SnapEnhance app")
softRestartApp()
return@connect
}
runCatching {
+ LSPatchUpdater.onBridgeConnected(appContext, bridgeClient)
measureTimeMillis {
runBlocking {
init(this)
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/BridgeClient.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/BridgeClient.kt
@@ -44,43 +44,45 @@ class BridgeClient(
private lateinit var future: CompletableFuture<Boolean>
private lateinit var service: BridgeInterface
- fun connect(timeout: (Throwable) -> Unit, onResult: (Boolean) -> Unit) {
+ fun connect(onFailure: (Throwable) -> Unit, onResult: (Boolean) -> Unit) {
this.future = CompletableFuture()
with(context.androidContext) {
- //ensure the remote process is running
- startActivity(Intent()
- .setClassName(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID + ".bridge.ForceStartActivity")
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
- )
-
- val intent = Intent()
- .setClassName(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID + ".bridge.BridgeService")
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- bindService(
- intent,
- Context.BIND_AUTO_CREATE,
- Executors.newSingleThreadExecutor(),
- this@BridgeClient
- )
- } else {
- XposedHelpers.callMethod(
- this,
- "bindServiceAsUser",
- intent,
- this@BridgeClient,
- Context.BIND_AUTO_CREATE,
- Handler(HandlerThread("BridgeClient").apply {
- start()
- }.looper),
- android.os.Process.myUserHandle()
+ runCatching {
+ startActivity(Intent()
+ .setClassName(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID + ".bridge.ForceStartActivity")
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
)
}
- }
- runCatching {
- onResult(future.get(15, TimeUnit.SECONDS))
- }.onFailure {
- timeout(it)
+
+ //ensure the remote process is running
+ runCatching {
+ val intent = Intent()
+ .setClassName(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID + ".bridge.BridgeService")
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ bindService(
+ intent,
+ Context.BIND_AUTO_CREATE,
+ Executors.newSingleThreadExecutor(),
+ this@BridgeClient
+ )
+ } else {
+ XposedHelpers.callMethod(
+ this,
+ "bindServiceAsUser",
+ intent,
+ this@BridgeClient,
+ Context.BIND_AUTO_CREATE,
+ Handler(HandlerThread("BridgeClient").apply {
+ start()
+ }.looper),
+ android.os.Process.myUserHandle()
+ )
+ }
+ onResult(future.get(15, TimeUnit.SECONDS))
+ }.onFailure {
+ onFailure(it)
+ }
}
}
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/util/LSPatchUpdater.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/util/LSPatchUpdater.kt
@@ -0,0 +1,62 @@
+package me.rhunk.snapenhance.core.util
+
+import me.rhunk.snapenhance.common.BuildConfig
+import me.rhunk.snapenhance.core.ModContext
+import me.rhunk.snapenhance.core.bridge.BridgeClient
+import java.io.File
+import java.util.zip.ZipFile
+
+object LSPatchUpdater {
+ private const val TAG = "LSPatchUpdater"
+
+ private fun getModuleUniqueHash(module: ZipFile): String {
+ return module.entries().asSequence()
+ .filter { !it.isDirectory }
+ .map { it.crc }
+ .reduce { acc, crc -> acc xor crc }
+ .toString(16)
+ }
+
+ fun onBridgeConnected(context: ModContext, bridgeClient: BridgeClient) {
+ val embeddedModule = context.androidContext.cacheDir
+ .resolve("lspatch")
+ .resolve(BuildConfig.APPLICATION_ID).let { moduleDir ->
+ if (!moduleDir.exists()) return@let null
+ moduleDir.listFiles()?.firstOrNull { it.extension == "apk" }
+ } ?: return
+
+ context.log.verbose("Found embedded SE at ${embeddedModule.absolutePath}", TAG)
+
+ val seAppApk = File(bridgeClient.getApplicationApkPath()).also {
+ if (!it.canRead()) {
+ throw IllegalStateException("Cannot read SnapEnhance apk")
+ }
+ }
+
+ runCatching {
+ if (getModuleUniqueHash(ZipFile(embeddedModule)) == getModuleUniqueHash(ZipFile(seAppApk))) {
+ context.log.verbose("Embedded SE is up to date", TAG)
+ return
+ }
+ }.onFailure {
+ throw IllegalStateException("Failed to compare module signature", it)
+ }
+
+ context.log.verbose("updating", TAG)
+ context.shortToast("Updating SnapEnhance. Please wait...")
+ // copy embedded module to cache
+ runCatching {
+ seAppApk.copyTo(embeddedModule, overwrite = true)
+ }.onFailure {
+ seAppApk.delete()
+ context.log.error("Failed to copy embedded module", it, TAG)
+ context.longToast("Failed to update SnapEnhance. Please check logcat for more details.")
+ context.forceCloseApp()
+ return
+ }
+
+ context.longToast("SnapEnhance updated!")
+ context.log.verbose("updated", TAG)
+ context.forceCloseApp()
+ }
+}+
\ No newline at end of file