fix(shake shake): make devmennu override
This commit is contained in:
parent
8a413f279a
commit
7a3ba1a85a
2 changed files with 173 additions and 50 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue