Skip to content
This repository has been archived by the owner on Jul 26, 2024. It is now read-only.

Commit

Permalink
feat(twitter): add video download & viewcount patch (from revanced)
Browse files Browse the repository at this point in the history
  • Loading branch information
IndusAryan committed Feb 15, 2024
1 parent b0ca0e9 commit 8fe29b2
Show file tree
Hide file tree
Showing 17 changed files with 622 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import indus.org.patches.twitter.XMLUtils
@Patch(
name = "Black Bird icon",
description = "Black bird with white background.",
compatiblePackages = [CompatiblePackage("com.twitter.android")]
compatiblePackages = [CompatiblePackage("com.twitter.android")],
use = false
)
@Suppress("unused")
object BlackBirdIcon : ResourcePatch() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import indus.org.patches.twitter.XMLUtils
@Patch(
name = "OG Bird icon",
description = "Replaces the X icon with original Blue Twitter icon.",
compatiblePackages = [CompatiblePackage("com.twitter.android")]
compatiblePackages = [CompatiblePackage("com.twitter.android")],
use = true
)
@Suppress("unused")
object BlueBirdIcon : ResourcePatch() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ object OGTwitterBrand : ResourcePatch() {
"tweets_retweet" to "Retweet",
"tweets_retweeted" to "%s retweeted",
"tweets_undo_retweet_vertical" to "Undo retweet",
"users_turn_on_retweets" to "Turn on retweets"
"users_turn_on_retweets" to "Turn on retweets",
"ps__post_broadcast_twitter" to "Post on Twitter",
"ps__retweet_broadcast_action" to "Retweet on Twitter",
"retweeters_title" to "Retweeted by"
)

for ((key, value) in replacementMap) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import indus.org.patches.twitter.XMLUtils
@Patch(
name = "White Bird icon",
description = "Replaces the X icon with a white Bird keeping black background.",
compatiblePackages = [CompatiblePackage("com.twitter.android")]
compatiblePackages = [CompatiblePackage("com.twitter.android")],
use = false
)
@Suppress("unused")
object WhiteBirdIcon : ResourcePatch() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package indus.org.patches.twitter.interaction.downloads

import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.fingerprint.MethodFingerprintResult
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import indus.org.patches.twitter.interaction.downloads.fingerprints.ConstructMediaOptionsSheetFingerprint
import indus.org.patches.twitter.interaction.downloads.fingerprints.ShowDownloadVideoUpsellBottomSheetFingerprint
import indus.org.patches.util.exception
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction

@Patch(
name = "Unlock downloads",
description = "Unlocks the ability to download any video.",
compatiblePackages = [CompatiblePackage("com.twitter.android")]
)
@Suppress("unused")
object UnlockDownloadsPatch : BytecodePatch(
setOf(ConstructMediaOptionsSheetFingerprint, ShowDownloadVideoUpsellBottomSheetFingerprint)
) {
override fun execute(context: BytecodeContext) {
fun MethodFingerprint.patch(getRegisterAndIndex: MethodFingerprintResult.() -> Pair<Int, Int>) = result?.let {
getRegisterAndIndex(it).let { (index, register) ->
it.mutableMethod.addInstruction(index, "const/4 v$register, 0x1")
}
} ?: throw exception

// Allow downloads for non-premium users.
ShowDownloadVideoUpsellBottomSheetFingerprint.patch {
val checkIndex = scanResult.patternScanResult!!.startIndex
val register = mutableMethod.getInstruction<OneRegisterInstruction>(checkIndex).registerA

checkIndex to register
}

// Force show the download menu item.
ConstructMediaOptionsSheetFingerprint.patch {
val showDownloadButtonIndex = mutableMethod.getInstructions().lastIndex - 1
val register = mutableMethod.getInstruction<TwoRegisterInstruction>(showDownloadButtonIndex).registerA

showDownloadButtonIndex to register
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package indus.org.patches.twitter.interaction.downloads.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags


internal object ConstructMediaOptionsSheetFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
strings = listOf("captionsState")
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package indus.org.patches.twitter.interaction.downloads.fingerprints

import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode

internal object ShowDownloadVideoUpsellBottomSheetFingerprint : MethodFingerprint(
returnType = "Z",
strings = listOf("variantToDownload.url"),
opcodes = listOf(Opcode.IF_EQZ)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package indus.org.patches.twitter.layout.viewcount


import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.twitter.layout.viewcount.fingerprints.ViewCountsEnabledFingerprint
import indus.org.patches.util.exception

@Patch(
name = "Hide view count",
description = "Hides the view count of Tweets.",
compatiblePackages = [CompatiblePackage("com.twitter.android")],
use = false
)
@Suppress("unused")
object HideViewCountPatch : BytecodePatch(
setOf(ViewCountsEnabledFingerprint)
) {
override fun execute(context: BytecodeContext) =
ViewCountsEnabledFingerprint.result?.mutableMethod?.addInstructions(
0,
"""
const/4 v0, 0x0
return v0
"""
) ?: throw ViewCountsEnabledFingerprint.exception
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package app.revanced.patches.twitter.layout.viewcount.fingerprints

import app.revanced.patcher.fingerprint.MethodFingerprint

internal object ViewCountsEnabledFingerprint : MethodFingerprint(
returnType = "Z",
strings = listOf("view_counts_public_visibility_enabled")
)
153 changes: 153 additions & 0 deletions src/main/kotlin/indus/org/patches/util/BytecodeUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package indus.org.patches.util

import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import indus.org.patches.util.mapping.ResourceMappingPatch
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.util.MethodUtil

/**
* The [PatchException] of failing to resolve a [MethodFingerprint].
*
* @return The [PatchException].
*/
val MethodFingerprint.exception
get() = PatchException("Failed to resolve ${this.javaClass.simpleName}")

/**
* Find the [MutableMethod] from a given [Method] in a [MutableClass].
*
* @param method The [Method] to find.
* @return The [MutableMethod].
*/
fun MutableClass.findMutableMethodOf(method: Method) = this.methods.first {
MethodUtil.methodSignaturesMatch(it, method)
}

/**
* Apply a transform to all methods of the class.
*
* @param transform The transformation function. Accepts a [MutableMethod] and returns a transformed [MutableMethod].
*/
fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) {
val transformedMethods = methods.map { it.transform() }
methods.clear()
methods.addAll(transformedMethods)
}

/**
* Inject a call to a method that hides a view.
*
* @param insertIndex The index to insert the call at.
* @param viewRegister The register of the view to hide.
* @param classDescriptor The descriptor of the class that contains the method.
* @param targetMethod The name of the method to call.
*/
fun MutableMethod.injectHideViewCall(
insertIndex: Int,
viewRegister: Int,
classDescriptor: String,
targetMethod: String
) = addInstruction(
insertIndex,
"invoke-static { v$viewRegister }, $classDescriptor->$targetMethod(Landroid/view/View;)V"
)

/**
* Find the index of the first instruction with the id of the given resource name.
*
* @param resourceName the name of the resource to find the id for.
* @return the index of the first instruction with the id of the given resource name, or -1 if not found.
*/
fun Method.findIndexForIdResource(resourceName: String): Int {
fun getIdResourceId(resourceName: String) = ResourceMappingPatch.resourceMappings.single {
it.type == "id" && it.name == resourceName
}.id

return indexOfFirstWideLiteralInstructionValue(getIdResourceId(resourceName))
}

/**
* Find the index of the first wide literal instruction with the given value.
*
* @return the first literal instruction with the value, or -1 if not found.
*/
fun Method.indexOfFirstWideLiteralInstructionValue(literal: Long) = implementation?.let {
it.instructions.indexOfFirst { instruction ->
(instruction as? WideLiteralInstruction)?.wideLiteral == literal
}
} ?: -1

/**
* Check if the method contains a literal with the given value.
*
* @return if the method contains a literal with the given value.
*/
fun Method.containsWideLiteralInstructionValue(literal: Long) =
indexOfFirstWideLiteralInstructionValue(literal) >= 0

/**
* Traverse the class hierarchy starting from the given root class.
*
* @param targetClass the class to start traversing the class hierarchy from.
* @param callback function that is called for every class in the hierarchy.
*/
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
callback(targetClass)
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
traverseClassHierarchy(it, callback)
}
}

/**
* Get the [Reference] of an [Instruction] as [T].
*
* @param T The type of [Reference] to cast to.
* @return The [Reference] as [T] or null
* if the [Instruction] is not a [ReferenceInstruction] or the [Reference] is not of type [T].
* @see ReferenceInstruction
*/
inline fun <reified T : Reference> Instruction.getReference() = (this as? ReferenceInstruction)?.reference as? T

/**
* Get the index of the first [Instruction] that matches the predicate.
*
* @param predicate The predicate to match.
* @return The index of the first [Instruction] that matches the predicate.
*/
fun Method.indexOfFirstInstruction(predicate: Instruction.() -> Boolean) =
this.implementation!!.instructions.indexOfFirst(predicate)

/**
* Return the resolved methods of [MethodFingerprint]s early.
*/
fun List<MethodFingerprint>.returnEarly(bool: Boolean = false) {
val const = if (bool) "0x1" else "0x0"
this.forEach { fingerprint ->
fingerprint.result?.let { result ->
val stringInstructions = when (result.method.returnType.first()) {
'L' -> """
const/4 v0, $const
return-object v0
"""
'V' -> "return-void"
'I', 'Z' -> """
const/4 v0, $const
return v0
"""
else -> throw Exception("This case should never happen.")
}

result.mutableMethod.addInstructions(0, stringInstructions)
} ?: throw fingerprint.exception
}
}
Loading

0 comments on commit 8fe29b2

Please sign in to comment.