feat: enable RN's LogBox and recovery options alert

This commit is contained in:
pylixonly 2025-02-27 20:19:12 +08:00
parent 2914d08cba
commit d6435cc5aa
3 changed files with 121 additions and 2 deletions

View file

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

View file

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

View file

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