fix(shake shake): make devmennu override

This commit is contained in:
cocobo1 2025-11-22 16:15:05 +02:00
parent 8a413f279a
commit 7a3ba1a85a
2 changed files with 173 additions and 50 deletions

View file

@ -12,8 +12,8 @@ android {
applicationId = "cocobo1.pupu.xposed" applicationId = "cocobo1.pupu.xposed"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 1310 versionCode = 1320
versionName = "1.3.1" versionName = "1.3.2"
} }
buildTypes { buildTypes {

View file

@ -3,6 +3,8 @@ package io.github.revenge.xposed.modules
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.widget.Toast import android.widget.Toast
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import io.github.revenge.xposed.Module import io.github.revenge.xposed.Module
@ -17,6 +19,7 @@ import java.net.URL
object LogBoxModule : Module() { object LogBoxModule : Module() {
lateinit var packageParam: XC_LoadPackage.LoadPackageParam lateinit var packageParam: XC_LoadPackage.LoadPackageParam
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
private var contextForMenu: Context? = null
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) { override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
this@LogBoxModule.packageParam = packageParam this@LogBoxModule.packageParam = packageParam
@ -30,79 +33,199 @@ object LogBoxModule : Module() {
result = true result = true
} }
} }
Log.e("Successfully hooked DCDReactNativeHost")
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Failed to hook DCDReactNativeHost: ${e.message}") Log.e("Failed to hook DCDReactNativeHost: ${e.message}")
} }
listOf(
"com.facebook.react.devsupport.BridgeDevSupportManager",
"com.facebook.react.devsupport.BridgelessDevSupportManager"
).mapNotNull { packageParam.classLoader.safeLoadClass(it) }.forEach {
hookDevSupportManager(it)
}
return@with return@with
} }
override fun onContext(context: Context) { override fun onContext(context: Context) {
}
private fun hookDevSupportManager(clazz: Class<*>) {
try { try {
val handleReloadJSMethod = clazz.methods.first { it.name == "handleReloadJS" } Log.e("onContext called with context: $context")
handleReloadJSMethod.hook { contextForMenu = context
before {
reloadApp() val possibleClasses = listOf(
result = null "com.facebook.react.devsupport.BridgeDevSupportManager",
"com.facebook.react.devsupport.BridgelessDevSupportManager",
"com.facebook.react.devsupport.DevSupportManagerImpl",
"com.facebook.react.devsupport.DevSupportManagerBase",
"com.facebook.react.devsupport.DefaultDevSupportManager"
)
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 showDevOptionsDialogMethod = clazz.methods.first { it.name == "showDevOptionsDialog" } if (!foundAny) {
showDevOptionsDialogMethod.hook { tryFindDevSupportClasses(context)
before { }
try { } catch (e: Exception) {
val context = getContextFromDevSupport(clazz, thisObject) }
if (context != null) { }
showRecoveryMenu(context)
} private fun tryFindDevSupportClasses(context: Context) {
result = null // Ignore the original dev menu try {
} catch (e: Exception) { val dexFile = packageParam.classLoader.javaClass.getDeclaredField("pathList")
Log.e("Failed to show recovery menu: ${e.message}") 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) {
Log.e("Attempting to hook ${clazz.name}")
// List all methods to see what's available
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}")
}
}
try {
try {
val handleReloadJSMethod = clazz.methods.firstOrNull { it.name == "handleReloadJS" }
if (handleReloadJSMethod != null) {
XposedBridge.hookMethod(handleReloadJSMethod, object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam): Any? {
Log.e("handleReloadJS called - reloading app")
reloadApp()
return null
}
})
} else {
}
} catch (e: Exception) {
Log.e("Failed to hook handleReloadJS: ${e.message}")
}
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})")
showRecoveryMenu(finalContext)
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
})
} else {
}
} catch (e: Exception) {
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Failed to hook DevSupportManager methods on class ${clazz.name}: ${e.message}")
} }
} }
private fun getContextFromDevSupport(clazz: Class<*>, instance: Any?): Context? { private fun getContextFromDevSupport(clazz: Class<*>, instance: Any?): Context? {
if (instance == null) {
Log.e("getContextFromDevSupport: instance is null")
return null
}
return try { return try {
val mReactInstanceDevHelperField = XposedHelpers.findField(clazz, "mReactInstanceDevHelper")
val helper = mReactInstanceDevHelperField.get(instance) // what if we just did this and searched for literally everything
val getCurrentActivityMethod = helper?.javaClass?.methods?.first { it.name == "getCurrentActivity" } val helpers = listOf(
getCurrentActivityMethod?.invoke(helper) as? Context "mReactInstanceDevHelper",
"reactInstanceDevHelper",
"mReactInstanceManager",
"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
}
if (helper is Context) {
Log.e("Field $helperName is a Context, returning it")
return helper
}
val getCurrentActivityMethod = helper.javaClass.methods.firstOrNull {
it.name == "getCurrentActivity"
}
if (getCurrentActivityMethod != null) {
val ctx = getCurrentActivityMethod.invoke(helper) as? Context
if (ctx != null) {
Log.e("Got context from $helperName.getCurrentActivity()")
return ctx
}
}
} catch (e: Exception) {
Log.e("Error trying $helperName: ${e.message}")
}
}
Log.e("Could not get context from DevSupport object using any method")
null
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Failed to get context: ${e.message}") Log.e("Failed to get context (outer catch): ${e.message}")
null null
} }
} }
private fun showRecoveryMenu(context: Context) { private fun showRecoveryMenu(context: Context) {
val options = arrayOf( Log.e("showRecoveryMenu called with context: $context")
if (isSafeModeEnabled(context)) "Disable Safe Mode" else "Enable Safe Mode", try {
"Reset Bundle", val options = arrayOf(
"Reload App" if (isSafeModeEnabled(context)) "Disable Safe Mode" else "Enable Safe Mode",
) "Reset Bundle",
"Reload App"
)
AlertDialog.Builder(context) AlertDialog.Builder(context)
.setTitle("KettuXposed Recovery Menu") .setTitle("KettuXposed Recovery Menu")
.setItems(options) { _, which -> .setItems(options) { _, which ->
handleMenuSelection(context, which) handleMenuSelection(context, which)
} }
.setNegativeButton("Cancel", null) .setNegativeButton("Cancel", null)
.show() .show()
Log.e("Recovery menu shown successfully")
} catch (e: Exception) {
Log.e("Error showing recovery menu: ${e.message}", e)
throw e
}
} }
private fun handleMenuSelection(context: Context, index: Int) { private fun handleMenuSelection(context: Context, index: Int) {