diff --git a/src/main/kotlin/app/revanced/patches/twitter/interaction/downloads/UnlockDownloadsPatch.kt b/src/main/kotlin/app/revanced/patches/twitter/interaction/downloads/UnlockDownloadsPatch.kt index ee13fab686..23501a8330 100644 --- a/src/main/kotlin/app/revanced/patches/twitter/interaction/downloads/UnlockDownloadsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/twitter/interaction/downloads/UnlockDownloadsPatch.kt @@ -2,27 +2,36 @@ package app.revanced.patches.twitter.interaction.downloads import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstructions +import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction 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 app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.twitter.interaction.downloads.fingerprints.BuildMediaOptionsSheetFingerprint import app.revanced.patches.twitter.interaction.downloads.fingerprints.ConstructMediaOptionsSheetFingerprint import app.revanced.patches.twitter.interaction.downloads.fingerprints.ShowDownloadVideoUpsellBottomSheetFingerprint import app.revanced.util.exception +import com.android.tools.smali.dexlib2.Opcode 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")] + description = "Unlocks the ability to download any video. GIFs can be downloaded via the menu on long press.", + compatiblePackages = [CompatiblePackage("com.twitter.android")], ) @Suppress("unused") object UnlockDownloadsPatch : BytecodePatch( - setOf(ConstructMediaOptionsSheetFingerprint, ShowDownloadVideoUpsellBottomSheetFingerprint) + setOf( + ConstructMediaOptionsSheetFingerprint, + ShowDownloadVideoUpsellBottomSheetFingerprint, + BuildMediaOptionsSheetFingerprint, + ), ) { override fun execute(context: BytecodeContext) { fun MethodFingerprint.patch(getRegisterAndIndex: MethodFingerprintResult.() -> Pair) = result?.let { @@ -46,5 +55,29 @@ object UnlockDownloadsPatch : BytecodePatch( showDownloadButtonIndex to register } + + // Make GIFs downloadable. + BuildMediaOptionsSheetFingerprint.result?.let { + val scanResult = it.scanResult.patternScanResult!! + it.mutableMethod.apply { + val checkMediaTypeIndex = scanResult.startIndex + val checkMediaTypeInstruction = getInstruction(checkMediaTypeIndex) + + // Treat GIFs as videos. + addInstructionsWithLabels( + checkMediaTypeIndex + 1, + """ + const/4 v${checkMediaTypeInstruction.registerB}, 0x2 # GIF + if-eq v${checkMediaTypeInstruction.registerA}, v${checkMediaTypeInstruction.registerB}, :video + """, + ExternalLabel("video", getInstruction(scanResult.endIndex)), + ) + + // Remove media.isDownloadable check. + removeInstruction( + getInstructions().first { insn -> insn.opcode == Opcode.IGET_BOOLEAN }.location.index + 1, + ) + } + } ?: throw BuildMediaOptionsSheetFingerprint.exception } } diff --git a/src/main/kotlin/app/revanced/patches/twitter/interaction/downloads/fingerprints/BuildMediaOptionsSheetFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitter/interaction/downloads/fingerprints/BuildMediaOptionsSheetFingerprint.kt new file mode 100644 index 0000000000..83d1732592 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/interaction/downloads/fingerprints/BuildMediaOptionsSheetFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.twitter.interaction.downloads.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +internal object BuildMediaOptionsSheetFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.IF_EQ, + Opcode.SGET_OBJECT, + Opcode.GOTO_16, + Opcode.NEW_INSTANCE, + ), + strings = listOf("resources.getString(R.string.post_video)"), +)