diff --git a/app/src/main/kotlin/io/github/pyoncord/xposed/FontsModule.kt b/app/src/main/kotlin/io/github/pyoncord/xposed/FontsModule.kt index ac98f30..b0b6aa6 100644 --- a/app/src/main/kotlin/io/github/pyoncord/xposed/FontsModule.kt +++ b/app/src/main/kotlin/io/github/pyoncord/xposed/FontsModule.kt @@ -14,7 +14,6 @@ import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.callbacks.XC_LoadPackage import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.* import java.io.IOException import java.io.File diff --git a/app/src/main/kotlin/io/github/pyoncord/xposed/LogBoxModule.kt b/app/src/main/kotlin/io/github/pyoncord/xposed/LogBoxModule.kt new file mode 100644 index 0000000..8754b3c --- /dev/null +++ b/app/src/main/kotlin/io/github/pyoncord/xposed/LogBoxModule.kt @@ -0,0 +1,117 @@ +package io.github.pyoncord.xposed + +import android.app.AlertDialog +import android.app.AndroidAppHelper +import android.content.Context +import android.content.Intent +import android.util.Log +import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XposedBridge +import de.robv.android.xposed.XposedHelpers +import de.robv.android.xposed.callbacks.XC_LoadPackage +import java.io.File +import kotlin.system.exitProcess + +class LogBoxModule: PyonModule() { + lateinit var packageParam: XC_LoadPackage.LoadPackageParam + + override fun onInit(packageParam: XC_LoadPackage.LoadPackageParam) = with (packageParam) { + this@LogBoxModule.packageParam = packageParam + + val dcdReactNativeHostClass = classLoader.loadClass("com.discord.bridge.DCDReactNativeHost") + val bridgeDevSupportManagerClass = classLoader.loadClass("com.facebook.react.devsupport.BridgeDevSupportManager") + + val mReactInstanceDevHelperField = XposedHelpers.findField(bridgeDevSupportManagerClass, "mReactInstanceDevHelper") + + val getUseDeveloperSupportMethod = dcdReactNativeHostClass.methods.first { it.name == "getUseDeveloperSupport" } + val handleReloadJSMethod = bridgeDevSupportManagerClass.methods.first { it.name == "handleReloadJS" } + val showDevOptionsDialogMethod = bridgeDevSupportManagerClass.methods.first { it.name == "showDevOptionsDialog" } + + var alertDialog: AlertDialog? = null + + // This enables the LogBox and opens dev option on shake + XposedBridge.hookMethod( + getUseDeveloperSupportMethod, + object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + param.result = true + } + } + ) + + // Triggered on shake + XposedBridge.hookMethod( + showDevOptionsDialogMethod, + object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + try { + if (alertDialog == null) { + val mReactInstanceDevHelper = + mReactInstanceDevHelperField.get(param.thisObject) + val getCurrentActivityMethod = + mReactInstanceDevHelper.javaClass.methods.first { it.name == "getCurrentActivity" } + + val context = + getCurrentActivityMethod.invoke(mReactInstanceDevHelper) as Context + + alertDialog = showRecoveryAlert(context) { + alertDialog = null + } + } + } catch (ex: Exception) { + Log.e("Bunny", "Failed to show dev options dialog: $ex") + alertDialog = null + } + + // Ignore the original dev menu + param.result = null + } + } + ) + + // Replace the method to direct relaunch the app instead of sending reload command to developer server + XposedBridge.hookMethod( + handleReloadJSMethod, + object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + reloadApp() + } + } + ) + + return@with + } + + private fun showRecoveryAlert(context: Context, onClose: () -> Unit): AlertDialog { + return AlertDialog.Builder(context) + .setTitle("Bunny Recovery Options") + .setItems(arrayOf("Reload", "Delete bundle.js")) { _, which -> + when (which) { + 0 -> { + reloadApp() + } + 1 -> { + val bundleFile = File(packageParam.appInfo.dataDir, "cache/pyoncord/bundle.js") + if (bundleFile.exists()) { + bundleFile.delete() + } + + reloadApp() + } + } + + onClose() + } + .setOnDismissListener { + onClose() + } + .show() + } + + private fun reloadApp() { + val application = AndroidAppHelper.currentApplication() + val intent = application.packageManager.getLaunchIntentForPackage(application.packageName) + application.startActivity(Intent.makeRestartActivityTask(intent!!.component)) + exitProcess(0) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/io/github/pyoncord/xposed/Main.kt b/app/src/main/kotlin/io/github/pyoncord/xposed/Main.kt index 0b227e2..26cb50b 100644 --- a/app/src/main/kotlin/io/github/pyoncord/xposed/Main.kt +++ b/app/src/main/kotlin/io/github/pyoncord/xposed/Main.kt @@ -27,6 +27,7 @@ data class CustomLoadUrl( val enabled: Boolean, val url: String ) + @Serializable data class LoaderConfig( val customLoadUrl: CustomLoadUrl @@ -37,6 +38,7 @@ class Main : IXposedHookLoadPackage { ThemeModule(), SysColorsModule(), FontsModule(), + LogBoxModule() ) fun buildLoaderJsonString(): String { @@ -113,7 +115,8 @@ class Main : IXposedHookLoadPackage { val config = try { if (!configFile.exists()) throw Exception() - Json { ignoreUnknownKeys = true }.decodeFromString(configFile.readText()) + val json = Json { ignoreUnknownKeys = true } + json.decodeFromString(configFile.readText()) } catch (_: Exception) { LoaderConfig( customLoadUrl = CustomLoadUrl(