feat: unaryCall event

- feat data class builder util
This commit is contained in:
rhunk 2024-01-11 18:09:16 +01:00
parent 8eeafc59b6
commit 1f7f270766
6 changed files with 154 additions and 8 deletions

View File

@ -5,6 +5,7 @@ import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
import me.rhunk.snapenhance.common.scripting.ktx.contextScope
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
@ -32,13 +33,13 @@ class Networking : AbstractBinding("networking", BindingSide.COMMON) {
fun removeHeader(name: String) = requestBuilder.removeHeader(name).let { this }
@JSFunction
fun method(method: String, body: String) = requestBuilder.method(method, okhttp3.RequestBody.create(null, body)).let { this }
fun method(method: String, body: String) = requestBuilder.method(method, body.toRequestBody(null)).let { this }
@JSFunction
fun method(method: String, body: java.io.InputStream) = requestBuilder.method(method, okhttp3.RequestBody.create(null, body.readBytes())).let { this }
fun method(method: String, body: java.io.InputStream) = requestBuilder.method(method, body.readBytes().toRequestBody(null)).let { this }
@JSFunction
fun method(method: String, body: ByteArray) = requestBuilder.method(method, okhttp3.RequestBody.create(null, body)).let { this }
fun method(method: String, body: ByteArray) = requestBuilder.method(method, body.toRequestBody(null)).let { this }
}
inner class ResponseWrapper(

View File

@ -88,4 +88,6 @@ class ProtoEditor(
}
fun toByteArray() = buffer
override fun toString() = ProtoReader(buffer).toString()
}

View File

@ -9,6 +9,7 @@ import me.rhunk.snapenhance.core.ModContext
import me.rhunk.snapenhance.core.event.events.impl.*
import me.rhunk.snapenhance.core.manager.Manager
import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.Hooker
import me.rhunk.snapenhance.core.util.hook.hook
import me.rhunk.snapenhance.core.util.hook.hookConstructor
import me.rhunk.snapenhance.core.util.ktx.getObjectField
@ -17,6 +18,7 @@ import me.rhunk.snapenhance.core.wrapper.impl.Message
import me.rhunk.snapenhance.core.wrapper.impl.MessageContent
import me.rhunk.snapenhance.core.wrapper.impl.MessageDestinations
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
import java.nio.ByteBuffer
class EventDispatcher(
private val context: ModContext
@ -156,6 +158,67 @@ class EventDispatcher(
)
}
context.classCache.unifiedGrpcService.hook("unaryCall", HookStage.BEFORE) { param ->
val uri = param.arg<String>(0)
val buffer = param.argNullable<ByteBuffer>(1)?.run {
val array = ByteArray(limit())
position(0)
get(array)
rewind()
array
} ?: return@hook
val unaryEventHandler = param.argNullable<Any>(3) ?: return@hook
val event = context.event.post(
UnaryCallEvent(
uri = uri,
buffer = buffer
).apply {
adapter = param
}
) ?: return@hook
if (event.canceled) {
param.setResult(null)
return@hook
}
if (!event.buffer.contentEquals(buffer)) {
param.setArg(1, ByteBuffer.wrap(event.buffer))
}
if (event.callbacks.size == 0) {
return@hook
}
Hooker.ephemeralHookObjectMethod(unaryEventHandler::class.java, unaryEventHandler, "onEvent", HookStage.BEFORE) { methodParam ->
val byteBuffer = methodParam.argNullable<ByteBuffer>(0) ?: return@ephemeralHookObjectMethod
val array = byteBuffer.run {
val array = ByteArray(limit())
position(0)
get(array)
rewind()
array
}
val responseUnaryCallEvent = UnaryCallEvent(
uri = uri,
buffer = array
)
event.callbacks.forEach { callback ->
callback(responseUnaryCallEvent)
}
if (responseUnaryCallEvent.canceled) {
param.setResult(null)
return@ephemeralHookObjectMethod
}
methodParam.setArg(0, ByteBuffer.wrap(event.buffer))
}
}
hookViewBinder()
}
}

View File

@ -0,0 +1,14 @@
package me.rhunk.snapenhance.core.event.events.impl
import me.rhunk.snapenhance.core.event.events.AbstractHookEvent
class UnaryCallEvent(
val uri: String,
var buffer: ByteArray
): AbstractHookEvent() {
val callbacks = mutableListOf<(UnaryCallEvent) -> Unit>()
fun addResponseCallback(responseCallback: UnaryCallEvent.() -> Unit) {
callbacks.add(responseCallback)
}
}

View File

@ -0,0 +1,66 @@
package me.rhunk.snapenhance.core.util
fun Any?.dataBuilder(dataClassBuilder: DataClassBuilder.() -> Unit): Any? {
return DataClassBuilder(
when (this) {
is Class<*> -> CallbackBuilder.createEmptyObject(
this.constructors.firstOrNull() ?: return null
) ?: return null
else -> this
} ?: return null
).apply(dataClassBuilder).build()
}
// Util for building/editing data classes
class DataClassBuilder(
private val instance: Any,
) {
fun set(fieldName: String, value: Any?) {
val field = instance::class.java.declaredFields.firstOrNull { it.name == fieldName } ?: return
val fieldType = field.type
field.isAccessible = true
when {
fieldType.isEnum -> {
val enumValue = fieldType.enumConstants.firstOrNull { it.toString() == value } ?: return
field.set(instance, enumValue)
}
fieldType.isPrimitive -> {
when (fieldType) {
Boolean::class.javaPrimitiveType -> field.setBoolean(instance, value as Boolean)
Byte::class.javaPrimitiveType -> field.setByte(instance, value as Byte)
Char::class.javaPrimitiveType -> field.setChar(instance, value as Char)
Short::class.javaPrimitiveType -> field.setShort(instance, value as Short)
Int::class.javaPrimitiveType -> field.setInt(instance, value as Int)
Long::class.javaPrimitiveType -> field.setLong(instance, value as Long)
Float::class.javaPrimitiveType -> field.setFloat(instance, value as Float)
Double::class.javaPrimitiveType -> field.setDouble(instance, value as Double)
}
}
else -> field.set(instance, value)
}
}
fun set(vararg fields: Pair<String, Any?>) = fields.forEach { set(it.first, it.second) }
fun from(fieldName: String, new: Boolean = false, callback: DataClassBuilder.() -> Unit) {
val field = instance::class.java.declaredFields.firstOrNull { it.name == fieldName } ?: return
field.isAccessible = true
val lazyInstance by lazy { CallbackBuilder.createEmptyObject(field.type.constructors.firstOrNull() ?: return@lazy null) ?: return@lazy null }
val builderInstance = if (new) lazyInstance else {
field.get(instance).takeIf { it != null } ?: lazyInstance
}
DataClassBuilder(builderInstance ?: return).apply(callback)
field.set(instance, builderInstance)
}
fun <T> cast(type: Class<T>, callback: T.() -> Unit) {
type.cast(instance)?.let { callback(it) }
}
fun build() = instance
}

View File

@ -23,10 +23,10 @@ class OperaPageViewControllerMapper : AbstractClassMapper() {
val layerListField = clazz.fields.first { it.type == "Ljava/util/ArrayList;" }
val onDisplayStateChange = clazz.methods.first {
if (it.returnType != "V" || it.parameterTypes.size != 1) return@first false
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@first false
if (firstParameterType.type == clazz.type || !firstParameterType.isAbstract()) return@first false
val onDisplayStateChange = clazz.methods.firstOrNull {
if (it.returnType != "V" || it.parameterTypes.size != 1) return@firstOrNull false
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@firstOrNull false
if (firstParameterType.type == clazz.type || !firstParameterType.isAbstract()) return@firstOrNull false
//check if the class contains a field with the enumViewStateClass type
firstParameterType.fields.any { field ->
field.type == viewStateField.type
@ -44,7 +44,7 @@ class OperaPageViewControllerMapper : AbstractClassMapper() {
"class" to clazz.getClassName(),
"viewStateField" to viewStateField.name,
"layerListField" to layerListField.name,
"onDisplayStateChange" to onDisplayStateChange.name,
"onDisplayStateChange" to onDisplayStateChange?.name,
"onDisplayStateChangeGesture" to onDisplayStateChangeGesture.name
)