commit 476de8dc38b04fef5435d5e4c1559e52a26c0793
parent 2749b734e4343269752590a41c32cc62870ccc1d
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sat, 30 Sep 2023 01:32:20 +0200

fix(e2ee): bind view foreground
- fix MessagingRuleType npe
- fix ProtoReader exception

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/messaging/ModDatabase.kt | 2+-
Mcore/src/main/assets/lang/en_US.json | 18++++++++++++------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/bridge/BridgeClient.kt | 2+-
Acore/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/E2EEConfig.kt | 9+++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Experimental.kt | 3+--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/event/EventDispatcher.kt | 4+++-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/event/events/AbstractHookEvent.kt | 1-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/messaging/MessagingCoreObjects.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/util/protobuf/ProtoReader.kt | 6+++---
Mcore/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/EndToEndEncryption.kt | 64+++++++++++++++++++++++++++++++++-------------------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/MessageLogger.kt | 21+++++++++++++++------
Mcore/src/main/kotlin/me/rhunk/snapenhance/ui/ViewAppearanceHelper.kt | 36++++++++++++++++++++++++++++++++++++
12 files changed, 115 insertions(+), 53 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/messaging/ModDatabase.kt b/app/src/main/kotlin/me/rhunk/snapenhance/messaging/ModDatabase.kt @@ -159,7 +159,7 @@ class ModDatabase( val rules = mutableListOf<MessagingRuleType>() while (cursor.moveToNext()) { runCatching { - rules.add(MessagingRuleType.getByName(cursor.getStringOrNull("type")!!)) + rules.add(MessagingRuleType.getByName(cursor.getStringOrNull("type")!!) ?: return@runCatching) }.onFailure { context.log.error("Failed to parse rule", it) } diff --git a/core/src/main/assets/lang/en_US.json b/core/src/main/assets/lang/en_US.json @@ -520,13 +520,19 @@ "name": "No Friend Score Delay", "description": "Removes the delay when viewing a Friends Score" }, - "e2e_encryption": { + "e2ee": { "name": "End-To-End Encryption", - "description": "Encrypts your messages with AES using a shared secret key\nMake sure to save your key somewhere safe!" - }, - "encrypted_message_indicator": { - "name": "Encrypted Message Indicator", - "description": "Adds a \uD83D\uDD12 emoji next to encrypted messages" + "description": "Encrypts your messages with AES using a shared secret key\nMake sure to save your key somewhere safe!", + "properties": { + "encrypted_message_indicator": { + "name": "Encrypted Message Indicator", + "description": "Adds a \uD83D\uDD12 emoji next to encrypted messages" + }, + "force_message_encryption": { + "name": "Force Message Encryption", + "description": "Prevents sending encrypted messages to people who don't have E2E Encryption enabled only when multiple conversations are selected" + } + } }, "add_friend_source_spoof": { "name": "Add Friend Source Spoof", diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/BridgeClient.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/BridgeClient.kt @@ -134,7 +134,7 @@ class BridgeClient( fun passGroupsAndFriends(groups: List<String>, friends: List<String>) = service.passGroupsAndFriends(groups, friends) fun getRules(targetUuid: String): List<MessagingRuleType> { - return service.getRules(targetUuid).map { MessagingRuleType.getByName(it) } + return service.getRules(targetUuid).mapNotNull { MessagingRuleType.getByName(it) } } fun getRuleIds(ruleType: MessagingRuleType): List<String> { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/E2EEConfig.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/E2EEConfig.kt @@ -0,0 +1,8 @@ +package me.rhunk.snapenhance.core.config.impl + +import me.rhunk.snapenhance.core.config.ConfigContainer + +class E2EEConfig : ConfigContainer(hasGlobalState = true) { + val encryptedMessageIndicator = boolean("encrypted_message_indicator") + val forceMessageEncryption = boolean("force_message_encryption") +}+ \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Experimental.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Experimental.kt @@ -12,8 +12,7 @@ class Experimental : ConfigContainer() { val meoPasscodeBypass = boolean("meo_passcode_bypass") val unlimitedMultiSnap = boolean("unlimited_multi_snap") { addNotices(FeatureNotice.BAN_RISK)} val noFriendScoreDelay = boolean("no_friend_score_delay") - val useE2EEncryption = boolean("e2e_encryption") - val encryptedMessageIndicator = boolean("encrypted_message_indicator") { addNotices(FeatureNotice.UNSTABLE) } + val e2eEncryption = container("e2ee", E2EEConfig()) val hiddenSnapchatPlusFeatures = boolean("hidden_snapchat_plus_features") { addNotices(FeatureNotice.BAN_RISK, FeatureNotice.UNSTABLE) } val addFriendSourceSpoof = unique("add_friend_source_spoof", "added_by_username", 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 @@ -76,7 +76,9 @@ class EventDispatcher( interactionType = interactionType, conversationId = conversationId, messageId = messageId - ) + ).apply { + adapter = param + } ) { postHookEvent() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/event/events/AbstractHookEvent.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/event/events/AbstractHookEvent.kt @@ -23,7 +23,6 @@ abstract class AbstractHookEvent : Event() { } fun invokeOriginal() { - canceled = true invokeLater() adapter.invokeOriginal() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/MessagingCoreObjects.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/MessagingCoreObjects.kt @@ -43,7 +43,7 @@ enum class MessagingRuleType( } companion object { - fun getByName(name: String) = values().first { it.key == name } + fun getByName(name: String) = values().firstOrNull { it.key == name } } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/util/protobuf/ProtoReader.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/util/protobuf/ProtoReader.kt @@ -32,10 +32,10 @@ class ProtoReader(private val buffer: ByteArray) { private fun read() { while (offset < buffer.size) { - val tag = readVarInt().toInt() - val id = tag ushr 3 - val type = WireType.fromValue(tag and 0x7) ?: break try { + val tag = readVarInt().toInt() + val id = tag ushr 3 + val type = WireType.fromValue(tag and 0x7) ?: break val value = when (type) { WireType.VARINT -> readVarInt() WireType.FIXED64 -> { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/EndToEndEncryption.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/EndToEndEncryption.kt @@ -1,13 +1,16 @@ package me.rhunk.snapenhance.features.impl.experiments import android.annotation.SuppressLint +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.Shape import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.MarginLayoutParams import android.widget.Button -import android.widget.LinearLayout -import android.widget.RelativeLayout import android.widget.TextView import me.rhunk.snapenhance.core.event.events.impl.AddViewEvent import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent @@ -29,6 +32,8 @@ import me.rhunk.snapenhance.features.impl.Messaging import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.hookConstructor import me.rhunk.snapenhance.ui.ViewAppearanceHelper +import me.rhunk.snapenhance.ui.addForegroundDrawable +import me.rhunk.snapenhance.ui.removeForegroundDrawable import java.security.MessageDigest import kotlin.random.Random @@ -37,7 +42,7 @@ class EndToEndEncryption : MessagingRuleFeature( MessagingRuleType.E2E_ENCRYPTION, loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_SYNC or FeatureLoadParams.INIT_ASYNC ) { - private val isEnabled get() = context.config.experimental.useE2EEncryption.get() + private val isEnabled get() = context.config.experimental.e2eEncryption.globalState == true private val e2eeInterface by lazy { context.bridgeClient.getE2eeInterface() } companion object { @@ -185,13 +190,11 @@ class EndToEndEncryption : MessagingRuleFeature( } } - val encryptedMessageIndicator by context.config.experimental.encryptedMessageIndicator - val chatMessageContentContainerId = context.resources.getIdentifier("chat_message_content_container", "id", context.androidContext.packageName) + val encryptedMessageIndicator by context.config.experimental.e2eEncryption.encryptedMessageIndicator // hook view binder to add special buttons val receivePublicKeyTag = Random.nextLong().toString(16) val receiveSecretTag = Random.nextLong().toString(16) - val encryptedMessageTag = Random.nextLong().toString(16) context.event.subscribe(BindViewEvent::class) { event -> event.chatMessage { conversationId, messageId -> @@ -206,28 +209,15 @@ class EndToEndEncryption : MessagingRuleFeature( } if (encryptedMessageIndicator) { - viewGroup.findViewWithTag<ViewGroup>(encryptedMessageTag)?.also { - val chatMessageContentContainer = viewGroup.findViewById<View>(chatMessageContentContainerId) as? LinearLayout ?: return@chatMessage - it.removeView(chatMessageContentContainer) - viewGroup.removeView(it) - viewGroup.addView(chatMessageContentContainer, 0) - } + viewGroup.removeForegroundDrawable("encryptedMessage") if (encryptedMessages.contains(messageId.toLong())) { - val chatMessageContentContainer = viewGroup.findViewById<View>(chatMessageContentContainerId) as? LinearLayout ?: return@chatMessage - viewGroup.removeView(chatMessageContentContainer) - - viewGroup.addView(RelativeLayout(viewGroup.context).apply { - tag = encryptedMessageTag - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - addView(chatMessageContentContainer) - addView(TextView(viewGroup.context).apply { - text = "\uD83D\uDD12" - textAlignment = View.TEXT_ALIGNMENT_TEXT_END - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - setPadding(20, 0, 20, 0) - }) - }, 0) + viewGroup.addForegroundDrawable("encryptedMessage", ShapeDrawable(object: Shape() { + override fun draw(canvas: Canvas, paint: Paint) { + paint.textSize = 20f + canvas.drawText("\uD83D\uDD12", 0f, canvas.height / 2f, paint) + } + })) } } @@ -355,13 +345,25 @@ class EndToEndEncryption : MessagingRuleFeature( override fun asyncInit() { if (!isEnabled) return + val forceMessageEncryption by context.config.experimental.e2eEncryption.forceMessageEncryption + // trick to disable fidelius encryption - context.event.subscribe(SendMessageWithContentEvent::class) { param -> - val messageContent = param.messageContent - val destinations = param.destinations - if (destinations.conversations.none { getState(it.toString()) }) return@subscribe + context.event.subscribe(SendMessageWithContentEvent::class) { event -> + val messageContent = event.messageContent + val destinations = event.destinations + + val e2eeConversations = destinations.conversations.filter { getState(it.toString()) } + + if (e2eeConversations.isEmpty()) return@subscribe + + if (e2eeConversations.size != destinations.conversations.size) { + if (!forceMessageEncryption) return@subscribe + context.longToast("You can't send encrypted content to both encrypted and unencrypted conversations!") + event.canceled = true + return@subscribe + } - param.addInvokeLater { + event.addInvokeLater { if (messageContent.contentType == ContentType.SNAP) { messageContent.contentType = ContentType.EXTERNAL_MEDIA } 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,9 @@ package me.rhunk.snapenhance.features.impl.spying -import android.graphics.drawable.ColorDrawable +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.Shape import android.os.DeadObjectException import com.google.gson.JsonObject import com.google.gson.JsonParser @@ -12,6 +15,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.ui.addForegroundDrawable +import me.rhunk.snapenhance.ui.removeForegroundDrawable import java.util.concurrent.Executors import kotlin.time.ExperimentalTime import kotlin.time.measureTime @@ -155,14 +160,18 @@ class MessageLogger : Feature("MessageLogger", context.event.subscribe(BindViewEvent::class) { event -> event.chatMessage { conversationId, messageId -> - val foreground = event.view.foreground - if (foreground is ColorDrawable && foreground.color == DELETED_MESSAGE_COLOR) { - event.view.foreground = null - } + event.view.removeForegroundDrawable("deletedMessage") getServerMessageIdentifier(conversationId, messageId.toLong())?.let { serverMessageId -> if (!deletedMessageCache.contains(serverMessageId)) return@chatMessage } ?: return@chatMessage - event.view.foreground = ColorDrawable(DELETED_MESSAGE_COLOR) // red with alpha + + event.view.addForegroundDrawable("deletedMessage", ShapeDrawable(object: Shape() { + override fun draw(canvas: Canvas, paint: Paint) { + canvas.drawRect(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), Paint().apply { + color = DELETED_MESSAGE_COLOR + }) + } + })) } } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/ui/ViewAppearanceHelper.kt b/core/src/main/kotlin/me/rhunk/snapenhance/ui/ViewAppearanceHelper.kt @@ -4,21 +4,57 @@ import android.annotation.SuppressLint import android.app.AlertDialog import android.content.Context import android.content.res.ColorStateList +import android.graphics.Canvas import android.graphics.Color +import android.graphics.Paint import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.StateListDrawable +import android.graphics.drawable.shapes.Shape import android.view.Gravity import android.view.View import android.widget.Switch import android.widget.TextView import me.rhunk.snapenhance.Constants +import kotlin.random.Random fun View.applyTheme(componentWidth: Int? = null, hasRadius: Boolean = false, isAmoled: Boolean = true) { ViewAppearanceHelper.applyTheme(this, componentWidth, hasRadius, isAmoled) } +private val foregroundDrawableListTag = Random.nextInt(0x7000000, 0x7FFFFFFF) + +@Suppress("UNCHECKED_CAST") +private fun View.getForegroundDrawables(): MutableMap<String, Drawable> { + return getTag(foregroundDrawableListTag) as? MutableMap<String, Drawable> + ?: mutableMapOf<String, Drawable>().also { + setTag(foregroundDrawableListTag, it) + } +} + +private fun View.updateForegroundDrawable() { + foreground = ShapeDrawable(object: Shape() { + override fun draw(canvas: Canvas, paint: Paint) { + getForegroundDrawables().forEach { (_, drawable) -> + drawable.draw(canvas) + } + } + }) +} + +fun View.removeForegroundDrawable(tag: String) { + getForegroundDrawables().remove(tag)?.let { + updateForegroundDrawable() + } +} + +fun View.addForegroundDrawable(tag: String, drawable: Drawable) { + getForegroundDrawables()[tag] = drawable + updateForegroundDrawable() +} + + object ViewAppearanceHelper { @SuppressLint("UseSwitchCompatOrMaterialCode", "RtlHardcoded", "DiscouragedApi", "ClickableViewAccessibility"