commit d7bdfa7cddb1922b099de2ba8357eb7ee87cd012 parent f597b408c57fb706e7e945001747d4af6748ab83 Author: rhunk <101876869+rhunk@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:30:13 +0200 feat: native hooks config - fix GalleryMediaSendOverride false positive Diffstat:
13 files changed, 123 insertions(+), 70 deletions(-)
diff --git a/core/src/main/assets/lang/en_US.json b/core/src/main/assets/lang/en_US.json @@ -75,26 +75,12 @@ "features": { "notices": { - "unstable": "This feature is unstable and may not work as expected", - "may_ban": "This feature may get you banned\nUse at your own risk", - "may_break_internal_behavior": "This feature may break Snapchat internal behavior", - "may_cause_crashes": "This feature may cause crashes" + "unstable": "This is unstable and may not work as expected", + "may_ban": "This may get you banned\nUse at your own risk", + "may_break_internal_behavior": "This may break Snapchat internal behavior", + "may_cause_crashes": "This may cause crashes" }, "properties": { - "spoof": { - "name": "Spoof", - "description": "Spoof your information", - "properties": { - "location": { - "name": "Location", - "description": "Spoof your location" - }, - "device": { - "name": "Device", - "description": "Spoof your device" - } - } - }, "downloader": { "name": "Downloader", "description": "Download Snaps and Stories", @@ -249,10 +235,6 @@ "name": "Disable Metrics", "description": "Disables some analytics data sent to Snapchat" }, - "disable_bitmoji": { - "name": "Disable Bitmoji", - "description": "Disables friends profile bitmoji for the whole app" - }, "block_ads": { "name": "Block Ads", "description": "Prevent ads from being displayed" @@ -331,6 +313,34 @@ "name": "Experimental", "description": "Experimental features", "properties": { + "native_hooks": { + "name": "Native Hooks", + "description": "Unsafe features that hook into Snapchat's native code", + "properties": { + "disable_bitmoji": { + "name": "Disable Bitmoji", + "description": "Disables friends profile bitmoji" + }, + "fix_gallery_media_override": { + "name": "Fix Gallery Media Override", + "description": "Fixes various issues with the Gallery Media Send Override feature (e.g. save snaps in chat)" + } + } + }, + "spoof": { + "name": "Spoof", + "description": "Spoof your information", + "properties": { + "location": { + "name": "Location", + "description": "Spoof your location" + }, + "device": { + "name": "Device", + "description": "Spoof your device" + } + } + }, "app_passcode": { "name": "App Passcode", "description": "Sets a passcode to lock the app" diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt b/core/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt @@ -124,16 +124,12 @@ class ModContext { fun reloadConfig() { modConfig.loadFromBridge(bridgeClient) - runCatching { - native.loadConfig( - NativeConfig( - disableBitmoji = config.global.disableBitmoji.get(), - disableMetrics = config.global.disableMetrics.get() - ) + native.loadNativeConfig( + NativeConfig( + disableBitmoji = config.experimental.nativeHooks.disableBitmoji.get(), + disableMetrics = config.global.disableMetrics.get() ) - }.onFailure { - Logger.xposedLog("Failed to load native config", it) - } + ) } fun getConfigLocale(): String { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt @@ -99,6 +99,7 @@ class SnapEnhance { measureTime { with(appContext) { reloadConfig() + initNative() withContext(appContext.coroutineDispatcher) { translation.userLocale = getConfigLocale() translation.loadFromBridge(bridgeClient) @@ -121,18 +122,6 @@ class SnapEnhance { private fun onActivityCreate() { measureTime { with(appContext) { - runCatching { - native.initOnce(appContext.androidContext.classLoader) - native.nativeUnaryCallCallback = { request -> - event.post(UnaryCallEvent(request.uri, request.buffer))?.also { - request.buffer = it.buffer - request.canceled = it.canceled - } - } - }.onFailure { - Logger.xposedLog("Failed to init native", it) - } - features.onActivityCreate() actionManager.init() } @@ -141,6 +130,21 @@ class SnapEnhance { } } + private fun initNative() { + // don't initialize native when not logged in + if (!appContext.database.hasArroyo()) return + appContext.native.apply { + if (appContext.config.experimental.nativeHooks.globalState != true) return@apply + initOnce(appContext.androidContext.classLoader) + nativeUnaryCallCallback = { request -> + appContext.event.post(UnaryCallEvent(request.uri, request.buffer))?.also { + request.buffer = it.buffer + request.canceled = it.canceled + } + } + } + } + private fun syncRemote() { val database = appContext.database diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/action/impl/OpenMap.kt b/core/src/main/kotlin/me/rhunk/snapenhance/action/impl/OpenMap.kt @@ -11,8 +11,8 @@ class OpenMap: AbstractAction("action.open_map") { val mapActivityIntent = Intent() mapActivityIntent.setClassName(BuildConfig.APPLICATION_ID, "me.rhunk.snapenhance.ui.MapActivity") mapActivityIntent.putExtra("location", Bundle().apply { - putDouble("latitude", context.config.spoof.location.latitude.get().toDouble()) - putDouble("longitude", context.config.spoof.location.longitude.get().toDouble()) + putDouble("latitude", context.config.experimental.spoof.location.latitude.get().toDouble()) + putDouble("longitude", context.config.experimental.spoof.location.longitude.get().toDouble()) }) context.mainActivity!!.startActivityForResult(mapActivityIntent, 0x1337) 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 @@ -3,6 +3,8 @@ package me.rhunk.snapenhance.core.config.impl import me.rhunk.snapenhance.core.config.ConfigContainer class Experimental : ConfigContainer() { + val nativeHooks = container("native_hooks", NativeHooks()) { icon = "Memory" } + val spoof = container("spoof", Spoof()) { icon = "Fingerprint" } val appPasscode = string("app_passcode") val appLockOnResume = boolean("app_lock_on_resume") val infiniteStoryBoost = boolean("infinite_story_boost") diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Global.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Global.kt @@ -8,7 +8,6 @@ class Global : ConfigContainer() { val snapchatPlus = boolean("snapchat_plus") { addNotices(FeatureNotice.MAY_BAN) } val autoUpdater = unique("auto_updater", "EVERY_LAUNCH", "DAILY", "WEEKLY").apply { set("DAILY") } val disableMetrics = boolean("disable_metrics") - val disableBitmoji = boolean("disable_bitmoji") { addNotices(FeatureNotice.UNSTABLE) } val blockAds = boolean("block_ads") val disableVideoLengthRestrictions = boolean("disable_video_length_restrictions") { addNotices(FeatureNotice.MAY_BAN) } val disableGooglePlayDialogs = boolean("disable_google_play_dialogs") diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/NativeHooks.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/NativeHooks.kt @@ -0,0 +1,8 @@ +package me.rhunk.snapenhance.core.config.impl + +import me.rhunk.snapenhance.core.config.ConfigContainer + +class NativeHooks: ConfigContainer(hasGlobalState = true) { + val disableBitmoji = boolean("disable_bitmoji") + val fixGalleryMediaOverride = boolean("fix_gallery_media_override") +}+ \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/RootConfig.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/RootConfig.kt @@ -1,6 +1,7 @@ package me.rhunk.snapenhance.core.config.impl import me.rhunk.snapenhance.core.config.ConfigContainer +import me.rhunk.snapenhance.core.config.FeatureNotice class RootConfig : ConfigContainer() { val downloader = container("downloader", DownloaderConfig()) { icon = "Download"} @@ -10,6 +11,7 @@ class RootConfig : ConfigContainer() { val rules = container("rules", Rules()) { icon = "Rule" } val camera = container("camera", Camera()) { icon = "Camera"} val streaksReminder = container("streaks_reminder", StreaksReminderConfig()) { icon = "Alarm" } - val experimental = container("experimental", Experimental()) { icon = "Science" } - val spoof = container("spoof", Spoof()) { icon = "Fingerprint" } + val experimental = container("experimental", Experimental()) { icon = "Science"; addNotices( + FeatureNotice.UNSTABLE, FeatureNotice.MAY_CAUSE_CRASHES + ) } } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/data/MessageSender.kt b/core/src/main/kotlin/me/rhunk/snapenhance/data/MessageSender.kt @@ -12,7 +12,7 @@ class MessageSender( private val context: ModContext, ) { companion object { - val redSnapProto: (Boolean) -> ByteArray = {hasAudio -> + val redSnapProto: () -> ByteArray = { ProtoWriter().apply { from(11, 5) { from(1) { @@ -24,7 +24,7 @@ class MessageSender( addVarInt(6, 0) } from(2) { - addVarInt(5, if (hasAudio) 1 else 0) + addVarInt(5, 1) // audio by default addBuffer(6, byteArrayOf()) } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/DeviceSpooferHook.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/DeviceSpooferHook.kt @@ -8,10 +8,10 @@ import me.rhunk.snapenhance.hook.Hooker class DeviceSpooferHook: Feature("device_spoofer", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) { override fun asyncOnActivityCreate() { - if (context.config.spoof.globalState != true) return + if (context.config.experimental.spoof.globalState != true) return - val fingerprint by context.config.spoof.device.fingerprint - val androidId by context.config.spoof.device.androidId + val fingerprint by context.config.experimental.spoof.device.fingerprint + val androidId by context.config.experimental.spoof.device.androidId if (fingerprint.isNotEmpty()) { val fingerprintClass = android.os.Build::class.java diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/GalleryMediaSendOverride.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/GalleryMediaSendOverride.kt @@ -1,5 +1,6 @@ package me.rhunk.snapenhance.features.impl.tweaks +import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent import me.rhunk.snapenhance.core.eventbus.events.impl.UnaryCallEvent import me.rhunk.snapenhance.data.ContentType @@ -11,21 +12,36 @@ import me.rhunk.snapenhance.util.protobuf.ProtoEditor import me.rhunk.snapenhance.util.protobuf.ProtoReader class GalleryMediaSendOverride : Feature("Gallery Media Send Override", loadParams = FeatureLoadParams.INIT_SYNC) { + private var isLastSnapSavable = false + override fun init() { - val typeNames = listOf( + val fixGalleryMediaSendOverride = context.config.experimental.nativeHooks.let { + it.globalState == true && it.fixGalleryMediaOverride.get() + } + val typeNames = mutableListOf( "ORIGINAL", "SNAP", - "LIVE_SNAP", "NOTE" - ).associateWith { + ).also { + if (fixGalleryMediaSendOverride) { + it.add("SAVABLE_SNAP") + } + }.associateWith { it } - context.event.subscribe(UnaryCallEvent::class) { event -> + context.event.subscribe(UnaryCallEvent::class, { fixGalleryMediaSendOverride }) { event -> if (event.uri != "/messagingcoreservice.MessagingCoreService/CreateContentMessage") return@subscribe + if (!isLastSnapSavable) return@subscribe + ProtoReader(event.buffer).also { + // only affect snaps + if (!it.containsPath(*Constants.ARROYO_MEDIA_CONTAINER_PROTO_PATH, 11)) return@subscribe + } + isLastSnapSavable = false + event.buffer = ProtoEditor(event.buffer).apply { - //remove the mas view time - edit(4, 4, 11, 5, 2) { + //remove the max view time + edit(*Constants.ARROYO_MEDIA_CONTAINER_PROTO_PATH, 11, 5, 2) { remove(8) addBuffer(6, byteArrayOf()) } @@ -43,7 +59,6 @@ class GalleryMediaSendOverride : Feature("Gallery Media Send Override", loadPara context.event.subscribe(SendMessageWithContentEvent::class, { context.config.messaging.galleryMediaSendOverride.get() }) { event -> - val localMessageContent = event.messageContent if (localMessageContent.contentType != ContentType.EXTERNAL_MEDIA) return@subscribe @@ -70,9 +85,12 @@ class GalleryMediaSendOverride : Feature("Gallery Media Send Override", loadPara } when (overrideType) { - "SNAP", "LIVE_SNAP" -> { + "SNAP", "SAVABLE_SNAP" -> { localMessageContent.contentType = ContentType.SNAP - localMessageContent.content = MessageSender.redSnapProto(overrideType == "LIVE_SNAP") + localMessageContent.content = MessageSender.redSnapProto() + if (overrideType == "SAVABLE_SNAP") { + isLastSnapSavable = true + } } "NOTE" -> { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/LocationSpoofer.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/LocationSpoofer.kt @@ -14,7 +14,7 @@ class LocationSpoofer: Feature("LocationSpoof", loadParams = FeatureLoadParams.A val bundle = intent.getBundleExtra("location") ?: return@hook param.setResult(null) - with(context.config.spoof.location) { + with(context.config.experimental.spoof.location) { latitude.set(bundle.getFloat("latitude")) longitude.set(bundle.getFloat("longitude")) @@ -22,10 +22,10 @@ class LocationSpoofer: Feature("LocationSpoof", loadParams = FeatureLoadParams.A } } - if (context.config.spoof.location.globalState != true) return + if (context.config.experimental.spoof.location.globalState != true) return - val latitude by context.config.spoof.location.latitude - val longitude by context.config.spoof.location.longitude + val latitude by context.config.experimental.spoof.location.latitude + val longitude by context.config.experimental.spoof.location.longitude val locationClass = android.location.Location::class.java val locationManagerClass = android.location.LocationManager::class.java diff --git a/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt b/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt @@ -4,10 +4,19 @@ import android.util.Log class NativeLib { var nativeUnaryCallCallback: (NativeRequestData) -> Unit = {} + companion object { + private var initialized = false + } fun initOnce(classloader: ClassLoader) { - System.loadLibrary("nativelib") - init(classloader) + if (initialized) throw IllegalStateException("NativeLib already initialized") + runCatching { + System.loadLibrary(BuildConfig.NATIVE_NAME) + init(classloader) + initialized = true + }.onFailure { + Log.e("SnapEnhance", "NativeLib init failed", it) + } } @Suppress("unused") @@ -23,7 +32,11 @@ class NativeLib { return null } + fun loadNativeConfig(config: NativeConfig) { + if (!initialized) return + loadConfig(config) + } - external fun init(classLoader: ClassLoader) - external fun loadConfig(config: NativeConfig) + private external fun init(classLoader: ClassLoader) + private external fun loadConfig(config: NativeConfig) } \ No newline at end of file