Update LogBoxModule.kt

This commit is contained in:
OTKUSteyler 2026-09-11 22:12:32 +02:00 • committed by GitHub
parent 82214b6ed0
commit 57ebd3daed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,9 +1,12 @@
package GoonXposed.xposed.modules.LogBox
import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Companion.reloadApp
import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hook
import android.app.Activity
import android.content.Context
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedBridge
@ -15,28 +18,61 @@ object LogBoxModule : Module() {
lateinit var packageParam: XC_LoadPackage.LoadPackageParam
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
var contextForMenu: Context? = null
private val hookedMethods = mutableSetOf<String>()
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
this@LogBoxModule.packageParam = packageParam
try {
val dcdReactNativeHostClass = classLoader.loadClass("com.discord.bridge.DCDReactNativeHost")
val getUseDeveloperSupportMethod =
dcdReactNativeHostClass.methods.first { it.name == "getUseDeveloperSupport" }
getUseDeveloperSupportMethod.hook {
before {
result = true
}
}
Log.e("Successfully hooked DCDReactNativeHost")
} catch (e: Exception) {
Log.e("Failed to hook DCDReactNativeHost: ${e.message}")
}
// Enable React Native developer support so the shake gesture is detected.
// MUST happen before the ReactHost is created (i.e. before the first Activity.onCreate),
// otherwise useDeveloperSupport is read once and cached.
enableDeveloperSupport(classLoader)
return@with
}
private fun enableDeveloperSupport(classLoader: ClassLoader) {
val hostCandidates = listOf(
"com.discord.bridge.DCDReactNativeHost"
)
for (hostName in hostCandidates) {
if (hookGetUseDeveloperSupport(classLoader, hostName)) {
Log.e("Successfully hooked $hostName for dev support")
return
}
}
Log.e("Failed to hook any ReactNativeHost for dev support")
}
private fun hookGetUseDeveloperSupport(classLoader: ClassLoader, hostName: String): Boolean {
val hostClass = try {
XposedHelpers.findClassIfExists(hostName, classLoader)
} catch (e: Throwable) {
null
} ?: return false
val method = hostClass.declaredMethods.firstOrNull { it.name == "getUseDeveloperSupport" }
?: hostClass.methods.firstOrNull { it.name == "getUseDeveloperSupport" }
?: return false
return try {
method.isAccessible = true
val signature = "${method.declaringClass.name}#${method.name}"
if (hookedMethods.add(signature)) {
method.hook {
before {
result = true
}
}
}
true
} catch (e: Throwable) {
Log.e("Failed to hook getUseDeveloperSupport on $hostName: ${e.message}")
false
}
}
override fun onContext(context: Context) {
try {
Log.e("onContext called with context: $context")
@ -47,82 +83,104 @@ object LogBoxModule : Module() {
"com.facebook.react.devsupport.BridgelessDevSupportManager",
"com.facebook.react.devsupport.DevSupportManagerImpl",
"com.facebook.react.devsupport.DevSupportManagerBase",
"com.facebook.react.devsupport.DefaultDevSupportManager"
"com.facebook.react.devsupport.DefaultDevSupportManager",
"com.facebook.react.devsupport.DevSupportManager"
)
var foundAny = false
possibleClasses.forEach { className ->
try {
val clazz = packageParam.classLoader.loadClass(className)
Log.e("Found class: $className")
hookDevSupportManager(clazz, context)
foundAny = true
} catch (e: Exception) {
Log.e("Class not found: $className - ${e.message}")
val clazz = try {
XposedHelpers.findClassIfExists(className, packageParam.classLoader)
} catch (e: Throwable) {
null
}
if (clazz == null) {
Log.e("Class not found: $className")
return@forEach
}
Log.e("Found class: $className")
hookDevSupportManager(clazz)
foundAny = true
}
if (!foundAny) {
tryFindDevSupportClasses(context)
Log.e("No DevSupport class was found - menu will not show on shake")
}
} catch (e: Exception) {
// Handle exception
}
}
private fun tryFindDevSupportClasses(context: Context) {
try {
val dexFile = packageParam.classLoader.javaClass.getDeclaredField("pathList")
dexFile.isAccessible = true
Log.e("Searching for DevSupport classes in classloader...")
} catch (e: Exception) {
Log.e("Could not search for classes: ${e.message}")
}
}
private fun hookDevSupportManager(clazz: Class<*>, context: Context) {
private fun hookDevSupportManager(clazz: Class<*>) {
Log.e("Attempting to hook ${clazz.name}")
Log.e("Available methods in ${clazz.simpleName}:")
clazz.methods.forEach { method ->
if (method.name.contains("Dev") || method.name.contains("Reload") || method.name.contains("Options")) {
Log.e(" - ${method.name}")
hookReloadJS(clazz)
val showMethod = clazz.declaredMethods.firstOrNull { it.name == "showDevOptionsDialog" }
?: clazz.methods.firstOrNull { it.name == "showDevOptionsDialog" }
?: run {
Log.e("showDevOptionsDialog not found on ${clazz.name}")
return
}
val signature = "${showMethod.declaringClass.name}#${showMethod.name}"
if (!hookedMethods.add(signature)) {
Log.e("showDevOptionsDialog already hooked on ${showMethod.declaringClass.name}, skipping")
return
}
try {
try {
val showDevOptionsDialogMethod = clazz.methods.firstOrNull { it.name == "showDevOptionsDialog" }
if (showDevOptionsDialogMethod != null) {
XposedBridge.hookMethod(showDevOptionsDialogMethod, object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam): Any? {
try {
var activityContext: Context? = null
try {
activityContext = getContextFromDevSupport(clazz, param.thisObject)
if (activityContext != null) {
Log.e("Successfully got context from DevSupport")
}
} catch (e: Exception) {
Log.e("Failed to get context from DevSupport (non-fatal): ${e.message}")
}
val finalContext = activityContext ?: contextForMenu ?: context
Log.e("Using context: $finalContext (type: ${finalContext.javaClass.name})")
LogBoxNavigation.showRecoveryMenu(finalContext)
} catch (e: Exception) {
e.printStackTrace()
}
return null
XposedBridge.hookMethod(showMethod, object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
try {
val activityContext = getContextFromDevSupport(clazz, param.thisObject)
?: contextForMenu
if (activityContext == null) {
// No usable context at all - let React's own dev options dialog
// run instead of showing nothing.
Log.e("No usable context for recovery menu - letting RN dialog show")
return
}
})
Log.e(
"Using context: $activityContext (type: ${activityContext.javaClass.name})"
)
// Only suppress React's dialog once ours is actually being shown.
// If showing the menu throws (e.g. bad Context), the exception is
// caught below and React's dialog still runs.
LogBoxNavigation.showRecoveryMenu(activityContext)
param.result = null
} catch (e: Throwable) {
Log.e("Failed to show recovery menu (falling back to RN dialog): ${e.message}")
}
}
} catch (e: Exception) {
// Handle exception
}
} catch (e: Exception) {
// Handle exception
})
Log.e("Hooked showDevOptionsDialog on ${showMethod.declaringClass.name}")
} catch (e: Throwable) {
Log.e("Failed to hook showDevOptionsDialog on ${clazz.name}: ${e.message}")
}
}
private fun hookReloadJS(clazz: Class<*>) {
val method = clazz.declaredMethods.firstOrNull { it.name == "handleReloadJS" }
?: clazz.methods.firstOrNull { it.name == "handleReloadJS" }
?: return
val signature = "${method.declaringClass.name}#${method.name}"
if (!hookedMethods.add(signature)) return
try {
XposedBridge.hookMethod(method, object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam): Any? {
Log.e("handleReloadJS called - reloading app")
reloadApp()
return null
}
})
Log.e("Hooked handleReloadJS on ${method.declaringClass.name}")
} catch (e: Throwable) {
Log.e("Failed to hook handleReloadJS: ${e.message}")
}
}
@ -132,42 +190,43 @@ object LogBoxModule : Module() {
return null
}
return try {
try {
// Helper objects that expose getCurrentActivity(). ReactInstanceDevHelper and
// ReactHost are the ones guaranteed to hold the *Activity* (not the app context),
// which is what an AlertDialog needs to actually appear on screen.
val helpers = listOf(
"mReactInstanceDevHelper",
"reactInstanceDevHelper",
"mReactHost",
"reactHost",
"mReactInstanceManager",
"reactInstanceManager",
"mBridge",
"bridge",
"mApplicationContext"
)
for (helperName in helpers) {
try {
Log.e("Trying field: $helperName")
val helperField = XposedHelpers.findFieldIfExists(clazz, helperName)
if (helperField == null) {
Log.e("Field $helperName not found, skipping")
continue
}
val helper = helperField.get(instance)
if (helper == null) {
Log.e("Field $helperName is null, skipping")
continue
}
?: continue
val helper = helperField.get(instance) ?: continue
if (helper is Context) {
Log.e("Field $helperName is a Context, returning it")
return helper
}
// getCurrentActivity() exists on ReactInstanceDevHelper, ReactHost and
// ReactInstanceManager, and returns an actual Activity.
val getCurrentActivityMethod = helper.javaClass.methods.firstOrNull {
it.name == "getCurrentActivity"
}
if (getCurrentActivityMethod != null) {
getCurrentActivityMethod.isAccessible = true
val ctx = getCurrentActivityMethod.invoke(helper) as? Context
if (ctx != null) {
Log.e("Got context from $helperName.getCurrentActivity()")
Log.e("Got activity context from $helperName.getCurrentActivity()")
return ctx
}
}
@ -176,11 +235,55 @@ object LogBoxModule : Module() {
}
}
// Direct Context fields on the DevSupportManager and its superclasses.
try {
var current = clazz
while (current != null && current != Any::class.java) {
for (field in current.declaredFields) {
try {
field.isAccessible = true
val value = field.get(instance) ?: continue
if (value is Context) {
Log.e("Got context from field ${current.simpleName}.${field.name}")
return value
}
} catch (e: Exception) {
// skip fields we cannot read
}
}
current = current.superclass
}
} catch (e: Exception) {
Log.e("Error scanning context fields: ${e.message}")
}
// Last resort: the currently resumed Activity from ActivityThread.
try {
val activityThreadClass = Class.forName("android.app.ActivityThread")
val currentActivityThread =
activityThreadClass.getMethod("currentActivityThread").invoke(null)
val mActivitiesField = activityThreadClass.getDeclaredField("mActivities")
mActivitiesField.isAccessible = true
val activities = mActivitiesField.get(currentActivityThread)
if (activities is Map<*, *>) {
for (record in activities.values) {
val activityField = record!!.javaClass.getDeclaredField("activity")
activityField.isAccessible = true
val activity = activityField.get(record) as? Activity
if (activity != null && !activity.isFinishing) {
Log.e("Got activity context from ActivityThread")
return activity
}
}
}
} catch (e: Exception) {
Log.e("ActivityThread lookup failed: ${e.message}")
}
Log.e("Could not get context from DevSupport object using any method")
null
} catch (e: Exception) {
Log.e("Failed to get context (outer catch): ${e.message}")
null
}
return null
}
}