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:
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/Networking.kt | 7++++---
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/util/protobuf/ProtoEditor.kt | 2++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/event/EventDispatcher.kt | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/src/main/kotlin/me/rhunk/snapenhance/core/event/events/impl/UnaryCallEvent.kt | 15+++++++++++++++
Acore/src/main/kotlin/me/rhunk/snapenhance/core/util/DataClassBuilder.kt | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/OperaPageViewControllerMapper.kt | 10+++++-----
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 )