diff --git a/src/main/kotlin/app/revanced/extensions/Extensions.kt b/src/main/kotlin/app/revanced/extensions/Extensions.kt index 7aed580f0..9add679d9 100644 --- a/src/main/kotlin/app/revanced/extensions/Extensions.kt +++ b/src/main/kotlin/app/revanced/extensions/Extensions.kt @@ -8,7 +8,10 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.shared.mapping.misc.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 import org.w3c.dom.Node @@ -102,4 +105,23 @@ fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: this.findClass(targetClass.superclass ?: return)?.mutableClass?.let { traverseClassHierarchy(it, callback) } -} \ No newline at end of file +} + +/** + * 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 + */ +fun 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) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt index f7b0815fb..7fc58b595 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt @@ -1,6 +1,8 @@ package app.revanced.patches.youtube.layout.returnyoutubedislike import app.revanced.extensions.exception +import app.revanced.extensions.getReference +import app.revanced.extensions.indexOfFirstInstruction import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions @@ -9,6 +11,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.* @@ -20,6 +23,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch( name = "Return YouTube Dislike", @@ -34,8 +38,8 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.37.36", - "18.38.44" + "18.43.45", + "18.44.41", ] ) ] @@ -49,6 +53,8 @@ object ReturnYouTubeDislikePatch : BytecodePatch( LikeFingerprint, DislikeFingerprint, RemoveLikeFingerprint, + RollingNumberSetterFingerprint, + RollingNumberTextViewFingerprint ) ) { private const val INTEGRATIONS_CLASS_DESCRIPTOR = @@ -143,7 +149,77 @@ object ReturnYouTubeDislikePatch : BytecodePatch( // endregion - // region Hook for non litho Short videos. + // region Hook rolling numbers. + + RollingNumberSetterFingerprint.result?.let { + val dislikesIndex = it.scanResult.patternScanResult!!.endIndex + + it.mutableMethod.apply { + val insertIndex = 1 + + val charSequenceInstanceRegister = + getInstruction(0).registerA + val charSequenceFieldReference = + getInstruction(dislikesIndex).reference.toString() + + val registerCount = implementation!!.registerCount + + // This register is being overwritten, so it is free to use. + val freeRegister = registerCount - 1 + val conversionContextRegister = registerCount - parameters.size + 1 + + addInstructions( + insertIndex, + """ + iget-object v$freeRegister, v$charSequenceInstanceRegister, $charSequenceFieldReference + invoke-static {v$conversionContextRegister, v$freeRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->onRollingNumberLoaded(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String; + move-result-object v$freeRegister + iput-object v$freeRegister, v$charSequenceInstanceRegister, $charSequenceFieldReference + """ + ) + } + } ?: throw RollingNumberSetterFingerprint.exception + + // The rolling number Span is missing styling since it's initially set as a String. + // Modify the UI text view and use the styled like/dislike Span. + RollingNumberTextViewFingerprint.result?.let { + // Initial TextView is set in this method. + val initiallyCreatedTextViewMethod = it.mutableMethod + + // Videos less than 24 hours after uploaded, like counts will be updated in real time. + // Whenever like counts are updated, TextView is set in this method. + val realTimeUpdateTextViewMethod = it.mutableClass.methods.find { method -> + method.parameterTypes.first() == "Landroid/graphics/Bitmap;" + } ?: throw PatchException("Failed to find realTimeUpdateTextViewMethod") + + arrayOf( + initiallyCreatedTextViewMethod, + realTimeUpdateTextViewMethod + ).forEach { insertMethod -> + insertMethod.apply { + val setTextIndex = indexOfFirstInstruction { + getReference()?.name == "setText" + } + + val textViewRegister = + getInstruction(setTextIndex).registerC + val textSpanRegister = + getInstruction(setTextIndex).registerD + + addInstructions( + setTextIndex, + """ + invoke-static {v$textViewRegister, v$textSpanRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->updateRollingNumber(Landroid/widget/TextView;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; + move-result-object v$textSpanRegister + """ + ) + } + } + } ?: throw RollingNumberTextViewFingerprint.exception + + // endregion + + // region Hook for non-litho Short videos. ShortsTextViewFingerprint.result?.let { it.mutableMethod.apply { @@ -172,7 +248,7 @@ object ReturnYouTubeDislikePatch : BytecodePatch( move-result v0 if-eqz v0, :ryd_disabled return-void - + :is_like :ryd_disabled nop diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberSetterFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberSetterFingerprint.kt new file mode 100644 index 000000000..cf0857c61 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberSetterFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +object RollingNumberSetterFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_DIRECT, + Opcode.IGET_OBJECT + ), + strings = listOf("RollingNumberType required properties missing! Need updateCount, fontName, color and fontSize.") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberTextViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberTextViewFingerprint.kt new file mode 100644 index 000000000..9c79cf4f5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberTextViewFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +object RollingNumberTextViewFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("L", "F", "F"), + opcodes = listOf( + Opcode.IPUT, + null, // invoke-direct or invoke-virtual + Opcode.IPUT_OBJECT, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ), + customFingerprint = custom@{ _, classDef -> + classDef.superclass == "Landroid/support/v7/widget/AppCompatTextView;" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextViewFingerprint.kt index a86701918..5f8521dd6 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextViewFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextViewFingerprint.kt @@ -12,7 +12,7 @@ object ShortsTextViewFingerprint : MethodFingerprint( opcodes = listOf( Opcode.INVOKE_SUPER, // first instruction of method Opcode.IF_NEZ, - Opcode.RETURN_VOID, + null, Opcode.INVOKE_VIRTUAL, Opcode.MOVE_RESULT_OBJECT, Opcode.CHECK_CAST, diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentAtomicReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentAtomicReferenceFingerprint.kt index e69128012..41f20e8b3 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentAtomicReferenceFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentAtomicReferenceFingerprint.kt @@ -18,7 +18,7 @@ object TextComponentAtomicReferenceFingerprint : MethodFingerprint( Opcode.MOVE_OBJECT_FROM16, Opcode.MOVE_OBJECT_FROM16, Opcode.MOVE_OBJECT_FROM16, - Opcode.MOVE_OBJECT_FROM16, + null, Opcode.INVOKE_VIRTUAL, // Register C is atomic reference Opcode.MOVE_RESULT_OBJECT, // Register A is char sequence Opcode.CHECK_CAST, diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentContextFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentContextFingerprint.kt index 75a9f5989..5c175b726 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentContextFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentContextFingerprint.kt @@ -21,6 +21,7 @@ object TextComponentContextFingerprint : MethodFingerprint( Opcode.IGET_OBJECT, Opcode.IGET_OBJECT, Opcode.IGET_OBJECT, + Opcode.IGET_OBJECT, Opcode.IGET_OBJECT, // conversion context field name ) ) \ No newline at end of file