commit 1f7f27076687166e5c608101cbbbde64ca2c6e6e
parent 8eeafc59b69cedba6978f4eafef909dae22fa185
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Thu, 11 Jan 2024 18:09:16 +0100
feat: unaryCall event
- feat data class builder util
Diffstat:
6 files changed, 156 insertions(+), 8 deletions(-)
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/Networking.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/Networking.kt
@@ -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(
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/util/protobuf/ProtoEditor.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/util/protobuf/ProtoEditor.kt
@@ -88,4 +88,6 @@ class ProtoEditor(
}
fun toByteArray() = buffer
+
+ override fun toString() = ProtoReader(buffer).toString()
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/event/EventDispatcher.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/event/EventDispatcher.kt
@@ -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()
}
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/event/events/impl/UnaryCallEvent.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/event/events/impl/UnaryCallEvent.kt
@@ -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)
+ }
+}+
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/util/DataClassBuilder.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/util/DataClassBuilder.kt
@@ -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
+}+
\ No newline at end of file
diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/OperaPageViewControllerMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/OperaPageViewControllerMapper.kt
@@ -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
)