commit fc838fed7ab0b889d92e5d9b43863319df973fec
parent 302ea7e83bbc1bad48b1988846779339b07ae996
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sat, 23 Sep 2023 00:49:24 +0200

feat(message_logger): deleted indicator

Diffstat:
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/bridge/wrapper/MappingsWrapper.kt | 16++--------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/MessageLogger.kt | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mmapper/src/main/kotlin/me/rhunk/snapmapper/ext/DexClassDef.kt | 1+
Amapper/src/main/kotlin/me/rhunk/snapmapper/impl/ViewBinderMapper.kt | 38++++++++++++++++++++++++++++++++++++++
Mmapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt | 1+
5 files changed, 98 insertions(+), 20 deletions(-)

diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/wrapper/MappingsWrapper.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/wrapper/MappingsWrapper.kt @@ -9,20 +9,7 @@ import me.rhunk.snapenhance.core.Logger import me.rhunk.snapenhance.core.bridge.FileLoaderWrapper import me.rhunk.snapenhance.core.bridge.types.BridgeFileType import me.rhunk.snapmapper.Mapper -import me.rhunk.snapmapper.impl.BCryptClassMapper -import me.rhunk.snapmapper.impl.CallbackMapper -import me.rhunk.snapmapper.impl.CompositeConfigurationProviderMapper -import me.rhunk.snapmapper.impl.DefaultMediaItemMapper -import me.rhunk.snapmapper.impl.EnumMapper -import me.rhunk.snapmapper.impl.FriendRelationshipChangerMapper -import me.rhunk.snapmapper.impl.FriendsFeedEventDispatcherMapper -import me.rhunk.snapmapper.impl.MediaQualityLevelProviderMapper -import me.rhunk.snapmapper.impl.OperaPageViewControllerMapper -import me.rhunk.snapmapper.impl.PlatformAnalyticsCreatorMapper -import me.rhunk.snapmapper.impl.PlusSubscriptionMapper -import me.rhunk.snapmapper.impl.ScCameraSettingsMapper -import me.rhunk.snapmapper.impl.ScoreUpdateMapper -import me.rhunk.snapmapper.impl.StoryBoostStateMapper +import me.rhunk.snapmapper.impl.* import java.util.concurrent.ConcurrentHashMap import kotlin.system.measureTimeMillis @@ -44,6 +31,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr CompositeConfigurationProviderMapper::class, ScoreUpdateMapper::class, FriendRelationshipChangerMapper::class, + ViewBinderMapper::class, ) } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/MessageLogger.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/MessageLogger.kt @@ -1,6 +1,8 @@ package me.rhunk.snapenhance.features.impl.spying +import android.graphics.drawable.ColorDrawable import android.os.DeadObjectException +import android.view.View import com.google.gson.JsonObject import com.google.gson.JsonParser import me.rhunk.snapenhance.data.ContentType @@ -10,6 +12,8 @@ import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.FeatureLoadParams import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.Hooker +import me.rhunk.snapenhance.hook.hook +import me.rhunk.snapenhance.hook.hookConstructor import java.util.concurrent.Executors import kotlin.time.ExperimentalTime import kotlin.time.measureTime @@ -23,6 +27,7 @@ private fun Any.longHashCode(): Long { class MessageLogger : Feature("MessageLogger", loadParams = FeatureLoadParams.INIT_SYNC or + FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.ACTIVITY_CREATE_ASYNC ) { companion object { @@ -30,6 +35,8 @@ class MessageLogger : Feature("MessageLogger", const val PREFETCH_FEED_COUNT = 20 } + private val isEnabled get() = context.config.messaging.messageLogger.get() + private val threadPool = Executors.newFixedThreadPool(10) //two level of cache to avoid querying the database @@ -57,13 +64,16 @@ class MessageLogger : Feature("MessageLogger", private fun computeMessageIdentifier(conversationId: String, orderKey: Long) = (orderKey.toString() + conversationId).longHashCode() private fun getServerMessageIdentifier(conversationId: String, clientMessageId: Long): Long? { - val serverMessageId = context.database.getConversationMessageFromId(clientMessageId)?.serverMessageId?.toLong() ?: return null + val serverMessageId = context.database.getConversationMessageFromId(clientMessageId)?.serverMessageId?.toLong() ?: return run { + context.log.error("Failed to get server message id for $conversationId $clientMessageId") + null + } return computeMessageIdentifier(conversationId, serverMessageId) } @OptIn(ExperimentalTime::class) override fun asyncOnActivityCreate() { - if (!context.database.hasArroyo()) { + if (!isEnabled || !context.database.hasArroyo()) { return } @@ -125,20 +135,60 @@ class MessageLogger : Feature("MessageLogger", } } - //set the message state to PREPARING for visibility + /*//set the message state to PREPARING for visibility with(message.messageContent.contentType) { if (this != ContentType.SNAP && this != ContentType.EXTERNAL_MEDIA) { message.messageState = MessageState.PREPARING } - } + }*/ deletedMessageCache[serverIdentifier] = deletedMessageObject } override fun init() { - val messageLogger by context.config.messaging.messageLogger - Hooker.hookConstructor(context.classCache.message, HookStage.AFTER, { messageLogger }) { param -> + Hooker.hookConstructor(context.classCache.message, HookStage.AFTER, { isEnabled }) { param -> processSnapMessage(param.thisObject()) } } + + override fun onActivityCreate() { + if (!isEnabled) return + + val viewBinderMappings = context.mappings.getMappedMap("ViewBinder") + val cachedHooks = mutableListOf<String>() + + fun cacheHook(clazz: Class<*>, block: Class<*>.() -> Unit) { + if (!cachedHooks.contains(clazz.name)) { + clazz.block() + cachedHooks.add(clazz.name) + } + } + + findClass(viewBinderMappings["class"].toString()).hookConstructor(HookStage.AFTER) { methodParam -> + cacheHook( + methodParam.thisObject<Any>()::class.java + ) { + hook(viewBinderMappings["bindMethod"].toString(), HookStage.BEFORE) bindViewMethod@{ param -> + val instance = param.thisObject<Any>() + val model1 = param.arg<Any>(0).toString().also { + if (!it.startsWith("ChatViewModel")) return@bindViewMethod + } + + val messageId = model1.substringAfter("messageId=").substringBefore(",").split(":").let { + it[0] to it[2] + } + + getServerMessageIdentifier(messageId.first, messageId.second.toLong())?.let { serverMessageId -> + if (!deletedMessageCache.contains(serverMessageId)) return@bindViewMethod + } ?: return@bindViewMethod + + val view = instance::class.java.methods.first { + it.name == viewBinderMappings["getViewMethod"].toString() + }.invoke(instance) as? View ?: return@bindViewMethod + + view.foreground = ColorDrawable(0x1E90313e) // red with alpha + } + } + } + } } \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/ext/DexClassDef.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/ext/DexClassDef.kt @@ -5,6 +5,7 @@ import org.jf.dexlib2.iface.ClassDef fun ClassDef.isEnum(): Boolean = accessFlags and AccessFlags.ENUM.value != 0 fun ClassDef.isAbstract(): Boolean = accessFlags and AccessFlags.ABSTRACT.value != 0 +fun ClassDef.isInterface(): Boolean = accessFlags and AccessFlags.INTERFACE.value != 0 fun ClassDef.isFinal(): Boolean = accessFlags and AccessFlags.FINAL.value != 0 fun ClassDef.hasStaticConstructorString(string: String): Boolean = methods.any { diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ViewBinderMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ViewBinderMapper.kt @@ -0,0 +1,37 @@ +package me.rhunk.snapmapper.impl + +import me.rhunk.snapmapper.AbstractClassMapper +import me.rhunk.snapmapper.MapperContext +import me.rhunk.snapmapper.ext.getClassName +import me.rhunk.snapmapper.ext.isAbstract +import me.rhunk.snapmapper.ext.isInterface +import java.lang.reflect.Modifier + +class ViewBinderMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (clazz in context.classes) { + if (!clazz.isAbstract() || clazz.isInterface()) continue + + val getViewMethod = clazz.methods.firstOrNull { it.returnType == "Landroid/view/View;" && it.parameterTypes.size == 0 } ?: continue + + // update view + clazz.methods.filter { + Modifier.isAbstract(it.accessFlags) && it.parameterTypes.size == 1 && it.parameterTypes[0] == "Landroid/view/View;" && it.returnType == "V" + }.also { + if (it.size != 1) return@also + }.firstOrNull() ?: continue + + val bindMethod = clazz.methods.filter { + Modifier.isAbstract(it.accessFlags) && it.parameterTypes.size == 2 && it.parameterTypes[0] == it.parameterTypes[1] && it.returnType == "V" + }.also { + if (it.size != 1) return@also + }.firstOrNull() ?: continue + + context.addMapping("ViewBinder", + "class" to clazz.getClassName(), + "bindMethod" to bindMethod.name, + "getViewMethod" to getViewMethod.name + ) + } + } +}+ \ No newline at end of file diff --git a/mapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt b/mapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt @@ -25,6 +25,7 @@ class TestMappings { CompositeConfigurationProviderMapper::class, ScoreUpdateMapper::class, FriendRelationshipChangerMapper::class, + ViewBinderMapper::class ) val gson = GsonBuilder().setPrettyPrinting().create()