feat: native hooks config

- fix GalleryMediaSendOverride false positive
This commit is contained in:
rhunk
2023-08-28 13:30:13 +02:00
parent f597b408c5
commit d7bdfa7cdd
13 changed files with 122 additions and 70 deletions

View File

@ -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"

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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")

View File

@ -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")
}

View File

@ -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
) }
}

View File

@ -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())
}
}

View File

@ -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

View File

@ -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" -> {

View File

@ -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