diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml deleted file mode 100644 index 5c56ad7..0000000 --- a/.idea/deploymentTargetDropDown.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5ae08f0..f959782 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,6 +38,7 @@ android { dependencies { compileOnly("de.robv.android.xposed:api:82") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") implementation("io.ktor:ktor-client-core:2.3.0") implementation("io.ktor:ktor-client-cio:2.3.0") } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/beefers/vendetta/xposed/Main.kt b/app/src/main/kotlin/dev/beefers/vendetta/xposed/Main.kt index e4d4ce8..1709889 100644 --- a/app/src/main/kotlin/dev/beefers/vendetta/xposed/Main.kt +++ b/app/src/main/kotlin/dev/beefers/vendetta/xposed/Main.kt @@ -1,35 +1,24 @@ package dev.beefers.vendetta.xposed -// Java import java.io.File - -// Kotlin -import kotlinx.coroutines.runBlocking - -// Android +import java.lang.Exception +import kotlinx.coroutines.* import android.content.res.AssetManager import android.content.res.XModuleResources import android.util.Log - -// Xposed +import android.os.Build import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.IXposedHookZygoteInit import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.callbacks.XC_LoadPackage - -// Ktor import io.ktor.client.* -import io.ktor.client.call.body -import io.ktor.client.engine.cio.* +import io.ktor.client.call.* import io.ktor.client.request.* +import io.ktor.client.engine.cio.* import io.ktor.client.statement.* import io.ktor.client.plugins.* -import io.ktor.http.HttpHeaders -import io.ktor.http.HttpStatusCode -import io.ktor.http.isSuccess -import io.ktor.utils.io.reader -import java.lang.Exception +import io.ktor.http.* class Main : IXposedHookZygoteInit, IXposedHookLoadPackage { private lateinit var resources: XModuleResources @@ -44,34 +33,44 @@ class Main : IXposedHookZygoteInit, IXposedHookLoadPackage { override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) = with(param) { // Stop executing when we aren't in Discord's React Native context val catalystInstance = try { classLoader.loadClass("com.facebook.react.bridge.CatalystInstanceImpl") } catch(e: ClassNotFoundException) { return } + + val cache = File(appInfo.dataDir, "cache").also { it.mkdirs() } + val bundle = File(cache, "vendetta.js") + val etag = File(cache, "vendetta_etag.txt") + + val scope = MainScope() + val httpJob = scope.async(Dispatchers.IO) { + try { + val client = HttpClient(CIO) { + install(HttpTimeout) { requestTimeoutMillis = 1000 } + // TODO: This should follow proper User Agent format + install(UserAgent) { agent = "VendettaXposed on ${Build.MODEL} (${Build.DEVICE}), Android ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})" } + } + val response: HttpResponse = client.get("https://raw.githubusercontent.com/vendetta-mod/builds/master/vendetta.js") { + headers { if (etag.exists() && bundle.exists()) append(HttpHeaders.IfNoneMatch, etag.readText()) } + } + + if (response.status.value == 200) { + bundle.writeBytes(response.body()) + if (response.headers.contains("Etag")) etag.writeText(response.headers["Etag"] as String) + } + + return@async + } catch (e: Exception) { + Log.e("Vendetta", "Failed to download Vendetta") + } + } + val loadScriptFromAssets = catalystInstance.getDeclaredMethod("jniLoadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType) val loadScriptFromFile = catalystInstance.getDeclaredMethod("jniLoadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType) - val cache = File(appInfo.dataDir, "cache").also { it.mkdirs() } - val bundle = File(cache, "vendetta.js") - val etag = File(cache, "vendetta_etag.txt") - - // TODO: loader config - // TODO: make fast. + // TODO: Loader config + // TODO: It's fast now, but we have the Hindi timestamps issue. Probably requires a JS side fix. val hook = object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam): Unit = runBlocking { Log.i("Vendetta", "beforeHookedMethod called") - try { - val client = HttpClient(CIO) { install(HttpTimeout) { requestTimeoutMillis = 3000 } } - val response: HttpResponse = client.get("https://raw.githubusercontent.com/vendetta-mod/builds/master/vendetta.js") { - headers { if (etag.exists() && bundle.exists()) append(HttpHeaders.IfNoneMatch, etag.readText()) } - } - - if (response.status.isSuccess()) { - bundle.writeText(response.bodyAsText()) - if (response.headers.contains("Etag")) etag.writeText(response.headers["Etag"] as String) - } - } catch (e: Exception) { - Log.e("Vendetta", "Failed to download Vendetta") - } - Log.i("Vendetta", "Exfiltrating Metro modules") XposedBridge.invokeOriginalMethod(loadScriptFromAssets,param.thisObject, arrayOf(resources.assets, "assets://js/modules.js", true)) Log.i("Vendetta", "Executing identity snippet") @@ -82,8 +81,14 @@ class Main : IXposedHookZygoteInit, IXposedHookLoadPackage { Log.i("Vendetta", "afterHookedMethod called") try { - Log.i("Vendetta", "Executing Vendetta") - XposedBridge.invokeOriginalMethod(loadScriptFromFile, param.thisObject, arrayOf(bundle.absolutePath, bundle.absolutePath, param.args[2])) + scope.launch(scope.coroutineContext) { + Log.i("Vendetta", "Waiting for HTTP") + httpJob.await() + delay(500) // Magic hacky bullshit + + Log.i("Vendetta", "Executing Vendetta") + XposedBridge.invokeOriginalMethod(loadScriptFromFile, param.thisObject, arrayOf(bundle.absolutePath, bundle.absolutePath, param.args[2])) + } } catch(_: Exception) {} } } @@ -91,6 +96,6 @@ class Main : IXposedHookZygoteInit, IXposedHookLoadPackage { XposedBridge.hookMethod(loadScriptFromAssets, hook) XposedBridge.hookMethod(loadScriptFromFile, hook) - return@with; + return@with } } \ No newline at end of file