feat: LSPatchUpdater

This commit is contained in:
rhunk
2023-10-17 23:00:29 +02:00
parent 26a1bdd7f9
commit 8b7c4a0a88
3 changed files with 102 additions and 38 deletions

View File

@ -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)

View File

@ -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)
}
}
}

View File

@ -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()
}
}