feat: ability to call custom native callbacks

This commit is contained in:
PalmDevs 2025-08-17 00:01:10 +07:00
parent 56ce7f674b
commit c5cff14c2f
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
2 changed files with 109 additions and 3 deletions

View file

@ -1,11 +1,9 @@
package io.github.revenge.xposed
import android.app.Activity
import android.content.pm.ApplicationInfo
import android.content.res.AssetManager
import android.content.res.Resources
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XC_MethodHook
@ -13,6 +11,7 @@ import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage
import io.github.revenge.xposed.modules.FontsModule
import io.github.revenge.xposed.modules.LogBoxModule
import io.github.revenge.xposed.modules.NativeBridgeModule
import io.github.revenge.xposed.modules.SysColorsModule
import io.github.revenge.xposed.modules.ThemeModule
import io.github.revenge.xposed.Utils.Companion.JSON
@ -62,7 +61,8 @@ class Main : Module(), IXposedHookLoadPackage {
ThemeModule(),
SysColorsModule(),
FontsModule(),
LogBoxModule()
LogBoxModule(),
NativeBridgeModule()
)
private fun getPayloadString(): String = JSON.encodeToString(

View file

@ -0,0 +1,106 @@
package io.github.revenge.xposed.modules
import de.robv.android.xposed.callbacks.XC_LoadPackage
import io.github.revenge.xposed.Module
import io.github.revenge.xposed.Utils.Log
import java.lang.reflect.Method
typealias BridgeMethodCallback = (HashMap<String, Any>) -> Any?
class NativeBridgeModule : Module() {
private lateinit var packageParam: XC_LoadPackage.LoadPackageParam
private lateinit var readableMapGetStringMethod: Method
private lateinit var readableMapToHashMapMethod: Method
private lateinit var argumentsMakeNativeObject: Method
companion object {
private val BRIDGE_CALL_KEY = "revenge"
private val methods = mutableMapOf<String, BridgeMethodCallback>(
"revenge.test" to {
mapOf(
"string" to "string",
"number" to 7256,
"array" to listOf("testing", 527737, listOf(true)),
"object" to mapOf("nested" to true),
"boolean" to false
)
}
)
fun registerMethod(name: String, callback: BridgeMethodCallback) {
require(!methods.containsKey(name)) {
"Bridge callback already exists: $name"
}
methods[name] = callback
}
}
private fun Any?.toNativeObject(): Any? =
argumentsMakeNativeObject.invoke(null, this)
override fun onInit(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
this@NativeBridgeModule.packageParam = packageParam
val argumentsClass = classLoader.loadClass("com.facebook.react.bridge.Arguments")
val readableMapClass = classLoader.loadClass("com.facebook.react.bridge.ReadableMap")
val promiseClass = classLoader.loadClass("com.facebook.react.bridge.Promise")
val promiseResolveMethod = promiseClass.method("resolve", Object::class.java)
argumentsMakeNativeObject =
argumentsClass.method("makeNativeObject", Object::class.java)
readableMapGetStringMethod =
readableMapClass.method("getString", String::class.java)
readableMapToHashMapMethod = readableMapClass.method("toHashMap")
classLoader.loadClass("com.horcrux.svg.RNSVGRenderableManager")
.hookMethod("getBBox", Double::class.javaObjectType, readableMapClass) {
before {
val map = args[1]!!
callBridgeMethod(map)?.let { result = it.toNativeObject() }
}
}
classLoader.loadClass("com.facebook.react.modules.blob.FileReaderModule")
.hookMethod("readAsDataURL", readableMapClass, promiseClass) {
before {
val map = args[0]!!
callBridgeMethod(map)?.let { ret ->
promiseResolveMethod.invoke(args[1], ret.toNativeObject())
result = null
}
}
}
return@with
}
private fun callBridgeMethod(map: Any): Map<String, Any?>? = try {
val method = getBridgeCallMethod(map) ?: return null
val ret = method(getBridgeCallArgs(map)).toNativeObject()
mapOf("result" to ret)
} catch (e: Error) {
mapOf("error" to e.toString())
}
private fun getBridgeCallArgs(map: Any): HashMap<String, Any> {
@Suppress("UNCHECKED_CAST")
val args = readableMapToHashMapMethod.invoke(map) as HashMap<String, Any>
args.remove(BRIDGE_CALL_KEY)
return args
}
private fun getBridgeCallMethod(map: Any): BridgeMethodCallback? = try {
val name = readableMapGetStringMethod.invoke(map, BRIDGE_CALL_KEY) as? String
?: return null
methods[name] ?: throw Error("Method $name not registered")
} catch (e: RuntimeException) {
// UnexpectedNativeTypeException : RuntimeException
Log.e(e.toString())
throw Error("Bad method name")
}
}