Skip to content

Commit

Permalink
Add support for Snapchat 11.96.0.31
Browse files Browse the repository at this point in the history
  • Loading branch information
rodit committed Sep 23, 2022
1 parent 3d17a94 commit 22c5730
Show file tree
Hide file tree
Showing 25 changed files with 190 additions and 695 deletions.
30 changes: 2 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,10 @@
Xposed module for Snapchat.

## Setup
To set SnapMod up, download and install the latest apk from [here](https://github.com/rodit/SnapMod/releases). When you open it, it will ask to install some bindings. Press 'Download' and be sure to kill and restart Snapchat afterwards. The latest and only fully supported version of Snapchat is **11.82.0.32** (as of SnapMod 1.8.3). Mappings **will not** be downloaded for previous versions of Snapchat automatically, only for the latest supported version. If you are using an older version, you must manually place the mappings in `/data/data/xyz.rodit.snapmod/files/[build].json` or `/Android/data/xyz.rodit.snapmod/files/[build].json` on your internal storage where `[build]` is the version code of Snapchat the mappings correspond to. Note, there is no guarentee the newest version of Snapmod will work with old mappings (it usually will not for a couple of features).
To set SnapMod up, download and install the latest apk from [here](https://github.com/rodit/SnapMod/releases). When you open it, it will ask to install some bindings. Press 'Download' and be sure to kill and restart Snapchat afterwards. The latest and only fully supported version of Snapchat is **11.96.0.31** (as of SnapMod 1.8.5). Mappings **will not** be downloaded for previous versions of Snapchat automatically, only for the latest supported version. If you are using an older version, you must manually place the mappings in `/data/data/xyz.rodit.snapmod/files/[build].json` or `/Android/data/xyz.rodit.snapmod/files/[build].json` on your internal storage where `[build]` is the version code of Snapchat the mappings correspond to. Note, there is no guarentee the newest version of Snapmod will work with old mappings (it usually will not for a couple of features).

## Features
- Disable camera
- Hide:
- Read receipts
- Chat saves/unsaves
- Screenshot/screen recording notifications
- Save to camera roll notifications
- Conversation presence (Bitmoji in bottom left)
- Typing status
- Story views
- Snap views
- Block ads
- Pin conversations
- Save any media (including snaps and audio notes) to camera roll
- Disable Bitmojis
- Send snaps from gallery
- Download stories
- Bypass video length restriction from drawer in chat
- Show more info on profile (birthday, friendship status)
- Disable some metrics/logging
- Per-conversation stealth mode (hide read receipts, snap views etc for individual conversations)
- Modify audio note playback speed
- Auto-save messages of the selected type (per conversation or globally)
- Auto-save snaps when opened
- Auto-save stories when viewed (per story or globally)
- Show message content (including previews of non-text media) in notifications
- Disable compression/transcoding of sent/saved media
- Pin stories
For a full list of features and their status, please visit the wiki [here](https://github.com/rodit/SnapMod/wiki/Features).

## Feature Suggestions
If you would like to suggest a new feature, please do so in **Discussions**. Please do not create an Issue for feature suggetsions (they will be moved to Discussions).
Expand Down
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ android {
applicationId "xyz.rodit.snapmod"
minSdk 24
targetSdk 32
versionCode 30
versionName "1.8.3"
versionCode 31
versionName "1.8.5"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand All @@ -37,7 +37,7 @@ android {

task versionInfo {
doLast {
def build = 84615
def build = 84641
def infoFile = new File('app/build/version.json')
infoFile.getParentFile().mkdirs()
infoFile.delete()
Expand Down
Binary file modified app/libs/snapmod.jar
Binary file not shown.
1 change: 0 additions & 1 deletion app/src/main/java/xyz/rodit/snapmod/SnapHooks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import android.os.Handler
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.features.FeatureManager
import xyz.rodit.snapmod.features.InstanceManager
import xyz.rodit.snapmod.logging.XLog
import xyz.rodit.snapmod.logging.log
Expand Down
61 changes: 37 additions & 24 deletions app/src/main/java/xyz/rodit/snapmod/arroyo/ArroyoReader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,47 +47,60 @@ class ArroyoReader(private val context: Context) {
return Base64.decode(key, Base64.DEFAULT) to Base64.decode(iv, Base64.DEFAULT)
}

fun getSnapKeyAndIv(conversationId: String, messageId: String): Pair<ByteArray, ByteArray>? {
fun getSnapData(conversationId: String, messageId: String): Triple<ByteArray, ByteArray, String>? {
val blob = getMessageBlob(conversationId, messageId) ?: return null
val key = followProto(blob, 4, 4, 11, 5, 1, 1, 19, 1) ?: return null
val iv = followProto(blob, 4, 4, 11, 5, 1, 1, 19, 2) ?: return null
return key to iv
val urlKey = followProtoString(blob, 4, 5, 1, 3, 2, 2) ?: return null
return Triple(key, iv, urlKey)
}

private fun readChatMessageContent(blob: ByteArray): String? {
return followProtoString(blob, 4, 4, 2, 1)
}

private fun followProtoString(data: ByteArray, vararg indices: Int): String? {
val proto = followProto(data, *indices)
return if (proto != null) String(proto) else null
}

private fun followProto(data: ByteArray, vararg indices: Int): ByteArray? {
var current = data
indices.forEach { i ->
val parts = ProtoReader(current).read()
current = parts.firstOrNull { it.index == i }?.value ?: return null
}

return current
}

private fun getMessageBlob(conversationId: String, messageId: String): ByteArray? {
private fun getMessageBlob(conversationId: String, messageId: String, retries: Int = 10): ByteArray? {
SQLiteDatabase.openDatabase(
File(context.filesDir, "../databases/arroyo.db").path,
null,
0
).use {
it.rawQuery(
"SELECT message_content FROM conversation_message WHERE client_conversation_id='$conversationId' AND server_message_id=$messageId",
null
).use { cursor ->
if (cursor.moveToFirst()) return cursor.getBlob(0)
else log.debug("No result in db.")
fun getBlob(): ByteArray? {
it.rawQuery(
"SELECT message_content FROM conversation_message WHERE client_conversation_id='$conversationId' AND server_message_id=$messageId",
null
).use { cursor ->
if (cursor.moveToFirst()) return cursor.getBlob(0)
}
return null
}
for (i in 1..retries) {
val blob = getBlob()
if (blob != null) {
return blob
}
if (retries > 0) {
Thread.sleep(500)
}
}
log.debug("No message found in db after $retries retries, conversationId: $conversationId, messageId: $messageId")
}

return null
}
}

fun followProtoString(data: ByteArray, vararg indices: Int): String? {
val proto = followProto(data, *indices)
return if (proto != null) String(proto) else null
}

fun followProto(data: ByteArray, vararg indices: Int): ByteArray? {
var current = data
indices.forEach { i ->
val parts = ProtoReader(current).read()
current = parts.firstOrNull { it.index == i }?.value ?: return null
}

return current
}
12 changes: 3 additions & 9 deletions app/src/main/java/xyz/rodit/snapmod/features/FeatureManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import okhttp3.internal.toImmutableList
import xyz.rodit.snapmod.features.chatmenu.ChatMenuModifier
import xyz.rodit.snapmod.features.chatmenu.new.NewChatMenuModifier
import xyz.rodit.snapmod.features.conversations.*
import xyz.rodit.snapmod.features.friendsfeed.FeedModifier
import xyz.rodit.snapmod.features.friendsfeed.PinChats
import xyz.rodit.snapmod.features.info.AdditionalFriendInfo
import xyz.rodit.snapmod.features.info.NetworkLogging
import xyz.rodit.snapmod.features.messagemenu.MessageMenuModifier
import xyz.rodit.snapmod.features.notifications.FilterTypes
import xyz.rodit.snapmod.features.notifications.ShowMessageContent
import xyz.rodit.snapmod.features.opera.CustomStoryOptions
Expand All @@ -26,7 +25,7 @@ class FeatureManager(context: FeatureContext) : Contextual(context) {
add(::NewChatMenuModifier)

// Friends feed
add(::FeedModifier)
add(::PinChats)

// Conversations/chats
add(::AutoSave)
Expand All @@ -37,9 +36,6 @@ class FeatureManager(context: FeatureContext) : Contextual(context) {
add(::SnapInteractionFilter)
add(::SnapOverrides)

// Message context menu
add(::MessageMenuModifier)

// Notifications
add(::FilterTypes)
add(::ShowMessageContent)
Expand All @@ -66,12 +62,10 @@ class FeatureManager(context: FeatureContext) : Contextual(context) {
add(::ConfigurationTweaks)
add(::ConfigurationTweaks)
add(::DisableBitmojis)
// add(::FriendAddOverride);
add(::HideFriends)
// add(::HideFriends)
add(::HideStoryReadReceipts)
add(::HideStorySections)
add(::HideStorySectionsLegacy)
// add(::LocationOverride)
add(::PinStories)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ abstract class ButtonOption(context: FeatureContext, name: String, private val t
null,
null,
false,
null,
ActionMenuOptionItemType.OPTION_ITEM()
).instance
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class NewChatMenuModifier(context: FeatureContext) : Feature(context) {
override fun performHooks() {
// Force new chat action menu
ProfileActionSheetChooser.choose.before {
it.args[0] = context.config.getBoolean("enable_new_chat_menu", true)
it.args[0] = true
}

// Add subsection
Expand All @@ -46,6 +46,9 @@ class NewChatMenuModifier(context: FeatureContext) : Feature(context) {

val newItems = (it.args[0] as List<*>).toMutableList()
val creator = ProfileActionSheetCreator.wrap(it.thisObject)
if (!NestedActionMenuContext.isInstance(creator.nestedContext)
|| !ActionMenuContext.isInstance(creator.actionMenuContext)) return@before

val nestedContext = NestedActionMenuContext.wrap(creator.nestedContext)
val actionContext = ActionMenuContext.wrap(creator.actionMenuContext)
val key = actionContext.feedInfo.key
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package xyz.rodit.snapmod.features.chatmenu.shared

import android.app.AlertDialog
import android.util.Base64
import de.robv.android.xposed.XC_MethodHook
import xyz.rodit.snapmod.arroyo.followProtoString
import xyz.rodit.snapmod.createDummyProxy
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.mappings.*
import xyz.rodit.snapmod.util.toSnapUUID
import xyz.rodit.snapmod.util.toUUIDString
import xyz.rodit.xposed.utils.StreamUtils
import java.io.File

fun previewChat(context: FeatureContext, key: String) {
val uuid = key.toSnapUUID()
Expand Down Expand Up @@ -48,17 +52,15 @@ private fun displayPreview(context: FeatureContext, param: XC_MethodHook.MethodH
val displayName = userMap[uuidString]?.displayName ?: "Unknown"
previewText.append('\n').append(displayName).append(": ")
if (m.messageContent.contentType.instance == ContentType.CHAT().instance) {
val chatMessage =
NanoMessageContent.parse(m.messageContent.content).chatMessageContent.content
previewText.append(chatMessage)
previewText.append(followProtoString(m.messageContent.content as ByteArray, 2, 1))
} else {
previewText.append(m.messageContent.contentType.instance)
}
}
}
}

userMap.values.find { f -> f.streakExpiration ?: 0L > 0L }?.let { f ->
userMap.values.find { f -> (f.streakExpiration ?: 0L) > 0L }?.let { f ->
val hourDiff =
(f.streakExpiration - System.currentTimeMillis()).toDouble() / 3600000.0
previewText.append("\n\nStreak Expires in ")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ class AutoSave(context: FeatureContext) : Feature(context, 84608.toMax()) {

val arroyoId = createArroyoId(conversationId, descriptor.messageId)
context.instances.chatCommandsClient.saveMessage(
null,
arroyoId,
true,
false,
ChatCommandSource.CHAT(),
false
)
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package xyz.rodit.snapmod.features.friendsfeed

import xyz.rodit.snapmod.features.Feature
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.mappings.FriendsFeedConfig
import xyz.rodit.snapmod.mappings.FriendsFeedView
import xyz.rodit.snapmod.util.after

class PinChats(context: FeatureContext) : Feature(context) {

override fun performHooks() {
FriendsFeedConfig.constructors.after(context, "allow_pin_chats") {
FriendsFeedConfig.wrap(it.thisObject).isPinConversationsEnabled = true
}

FriendsFeedView.constructors.after(context, "allow_pin_chats") {
val view = FriendsFeedView.wrap(it.thisObject)
if (context.pinned.isEnabled(view.key)) {
view.pinnedTimestamp = view.lastInteractionTimestamp
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ class NetworkLogging(context: FeatureContext) : Feature(context) {
val hook = { it: XC_MethodHook.MethodHookParam -> XposedBridge.log(it.args[0].toString()) }

NetworkApi.submit.before(context, "log_network_requests", hook)
NetworkApi.submitToNetworkManagerDirectly.before(context, "log_network_requests", hook)
}
}
Loading

0 comments on commit 22c5730

Please sign in to comment.