commit 8b7c4a0a8878fa13b3ed320573371480e623761f
parent 26a1bdd7f907b077917e58b2d474ff5cf418cb43
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Tue, 17 Oct 2023 23:00:29 +0200

feat: LSPatchUpdater

Diffstat:
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt | 12++++++------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/bridge/BridgeClient.kt | 66++++++++++++++++++++++++++++++++++--------------------------------
Acore/src/main/kotlin/me/rhunk/snapenhance/core/util/LSPatchUpdater.kt | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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