|
|
|
@ -1,8 +1,5 @@
|
|
|
|
|
package app.revanced.patches.youtube.layout.returnyoutubedislike
|
|
|
|
|
|
|
|
|
|
import app.revanced.util.exception
|
|
|
|
|
import app.revanced.util.getReference
|
|
|
|
|
import app.revanced.util.indexOfFirstInstruction
|
|
|
|
|
import app.revanced.patcher.data.BytecodeContext
|
|
|
|
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
|
|
|
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
|
|
|
@ -11,19 +8,39 @@ 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.*
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.ConversionContextFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.DislikeFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.DislikesOldLayoutTextViewFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.LikeFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.RemoveLikeFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.RollingNumberMeasureAnimatedTextFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.RollingNumberMeasureStaticLabelFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.RollingNumberMeasureTextParentFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.RollingNumberSetterFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.RollingNumberTextViewFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.ShortsTextViewFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.TextComponentConstructorFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.TextComponentDataFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.TextComponentLookupFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
|
|
|
|
|
import app.revanced.patches.youtube.misc.litho.filter.LithoFilterPatch
|
|
|
|
|
import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
|
|
|
|
|
import app.revanced.patches.youtube.shared.fingerprints.RollingNumberTextViewAnimationUpdateFingerprint
|
|
|
|
|
import app.revanced.patches.youtube.video.videoid.VideoIdPatch
|
|
|
|
|
import app.revanced.util.exception
|
|
|
|
|
import app.revanced.util.getReference
|
|
|
|
|
import app.revanced.util.indexOfFirstInstruction
|
|
|
|
|
import com.android.tools.smali.dexlib2.Opcode
|
|
|
|
|
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.FieldReference
|
|
|
|
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
|
|
|
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
|
|
|
|
|
|
|
|
|
@Patch(
|
|
|
|
|
name = "Return YouTube Dislike",
|
|
|
|
@ -38,6 +55,7 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
|
|
|
compatiblePackages = [
|
|
|
|
|
CompatiblePackage(
|
|
|
|
|
"com.google.android.youtube", [
|
|
|
|
|
"18.38.44",
|
|
|
|
|
"18.43.45",
|
|
|
|
|
"18.44.41",
|
|
|
|
|
"18.45.41",
|
|
|
|
@ -49,7 +67,9 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
|
|
|
@Suppress("unused")
|
|
|
|
|
object ReturnYouTubeDislikePatch : BytecodePatch(
|
|
|
|
|
setOf(
|
|
|
|
|
ConversionContextFingerprint,
|
|
|
|
|
TextComponentConstructorFingerprint,
|
|
|
|
|
TextComponentDataFingerprint,
|
|
|
|
|
ShortsTextViewFingerprint,
|
|
|
|
|
DislikesOldLayoutTextViewFingerprint,
|
|
|
|
|
LikeFingerprint,
|
|
|
|
@ -67,6 +87,8 @@ object ReturnYouTubeDislikePatch : BytecodePatch(
|
|
|
|
|
private const val FILTER_CLASS_DESCRIPTOR =
|
|
|
|
|
"Lapp/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch;"
|
|
|
|
|
|
|
|
|
|
private fun MethodFingerprint.resultOrThrow() = result ?: throw exception
|
|
|
|
|
|
|
|
|
|
override fun execute(context: BytecodeContext) {
|
|
|
|
|
// region Inject newVideoLoaded event handler to update dislikes when a new video is loaded.
|
|
|
|
|
|
|
|
|
@ -97,64 +119,132 @@ object ReturnYouTubeDislikePatch : BytecodePatch(
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
// region Hook creation of Spans and the cached lookup of them.
|
|
|
|
|
// region Hook code for creation and cached lookup of text Spans.
|
|
|
|
|
|
|
|
|
|
// Alternatively the hook can be made at the creation of Spans in TextComponentSpec,
|
|
|
|
|
// And it works in all situations except it fails to update the Span when the user dislikes,
|
|
|
|
|
// since the underlying (likes only) text did not change.
|
|
|
|
|
// This hook handles all situations, as it's where the created Spans are stored and later reused.
|
|
|
|
|
TextComponentContextFingerprint.also {
|
|
|
|
|
if (!it.resolve(context, TextComponentConstructorFingerprint.result!!.classDef))
|
|
|
|
|
throw it.exception
|
|
|
|
|
}.result?.also { result ->
|
|
|
|
|
if (!TextComponentAtomicReferenceFingerprint.resolve(context, result.method, result.classDef))
|
|
|
|
|
throw TextComponentAtomicReferenceFingerprint.exception
|
|
|
|
|
}?.let { textComponentContextFingerprintResult ->
|
|
|
|
|
val conversionContextIndex = textComponentContextFingerprintResult
|
|
|
|
|
.scanResult.patternScanResult!!.endIndex
|
|
|
|
|
val atomicReferenceStartIndex = TextComponentAtomicReferenceFingerprint.result!!
|
|
|
|
|
.scanResult.patternScanResult!!.startIndex
|
|
|
|
|
TextComponentConstructorFingerprint.result?.let { textConstructorResult ->
|
|
|
|
|
// Find the field name of the conversion context.
|
|
|
|
|
val conversionContextClassType = ConversionContextFingerprint.resultOrThrow().classDef.type
|
|
|
|
|
val conversionContextField = textConstructorResult.classDef.fields.find {
|
|
|
|
|
it.type == conversionContextClassType
|
|
|
|
|
} ?: throw PatchException("Could not find conversion context field")
|
|
|
|
|
|
|
|
|
|
val insertIndex = atomicReferenceStartIndex + 9
|
|
|
|
|
TextComponentLookupFingerprint.resolve(context, textConstructorResult.classDef)
|
|
|
|
|
TextComponentLookupFingerprint.resultOrThrow().mutableMethod.apply {
|
|
|
|
|
// Find the instruction for creating the text data object.
|
|
|
|
|
val textDataClassType = TextComponentDataFingerprint.resultOrThrow().classDef.type
|
|
|
|
|
val insertIndex = indexOfFirstInstruction {
|
|
|
|
|
opcode == Opcode.NEW_INSTANCE &&
|
|
|
|
|
getReference<TypeReference>()?.type == textDataClassType
|
|
|
|
|
}
|
|
|
|
|
if (insertIndex < 0) throw PatchException("Could not find data creation instruction")
|
|
|
|
|
val tempRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
|
|
|
|
|
|
|
|
|
textComponentContextFingerprintResult.mutableMethod.apply {
|
|
|
|
|
// Get the conversion context obfuscated field name
|
|
|
|
|
val conversionContextFieldReference =
|
|
|
|
|
getInstruction<ReferenceInstruction>(conversionContextIndex).reference
|
|
|
|
|
// Find the instruction that sets the span to an instance field.
|
|
|
|
|
// The instruction is only a few lines after the creation of the instance.
|
|
|
|
|
// The method has multiple iput-object instructions using a CharSequence,
|
|
|
|
|
// so verify the found instruction is in the expected location.
|
|
|
|
|
val putFieldInstruction = implementation!!.instructions
|
|
|
|
|
.subList(insertIndex, insertIndex + 20)
|
|
|
|
|
.find {
|
|
|
|
|
it.opcode == Opcode.IPUT_OBJECT &&
|
|
|
|
|
it.getReference<FieldReference>()?.type == "Ljava/lang/CharSequence;"
|
|
|
|
|
} ?: throw PatchException("Could not find put object instruction")
|
|
|
|
|
val charSequenceRegister = (putFieldInstruction as TwoRegisterInstruction).registerA
|
|
|
|
|
|
|
|
|
|
// Free register to hold the conversion context
|
|
|
|
|
val freeRegister =
|
|
|
|
|
getInstruction<TwoRegisterInstruction>(atomicReferenceStartIndex).registerB
|
|
|
|
|
|
|
|
|
|
val atomicReferenceRegister =
|
|
|
|
|
getInstruction<FiveRegisterInstruction>(atomicReferenceStartIndex + 6).registerC
|
|
|
|
|
|
|
|
|
|
// Instruction that is replaced, and also has the CharacterSequence register.
|
|
|
|
|
val moveCharSequenceInstruction = getInstruction<TwoRegisterInstruction>(insertIndex)
|
|
|
|
|
val charSequenceSourceRegister = moveCharSequenceInstruction.registerB
|
|
|
|
|
val charSequenceTargetRegister = moveCharSequenceInstruction.registerA
|
|
|
|
|
|
|
|
|
|
// Move the current instance to the free register, and get the conversion context from it.
|
|
|
|
|
// Must replace the instruction to preserve the control flow label.
|
|
|
|
|
replaceInstruction(insertIndex, "move-object/from16 v$freeRegister, p0")
|
|
|
|
|
addInstructions(
|
|
|
|
|
insertIndex + 1,
|
|
|
|
|
"""
|
|
|
|
|
# Move context to free register
|
|
|
|
|
iget-object v$freeRegister, v$freeRegister, $conversionContextFieldReference
|
|
|
|
|
invoke-static {v$freeRegister, v$atomicReferenceRegister, v$charSequenceSourceRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
|
|
|
|
|
move-result-object v$freeRegister
|
|
|
|
|
# Replace the original instruction
|
|
|
|
|
move-object v${charSequenceTargetRegister}, v${freeRegister}
|
|
|
|
|
insertIndex,
|
|
|
|
|
"""
|
|
|
|
|
# Copy conversion context
|
|
|
|
|
move-object/from16 v$tempRegister, p0
|
|
|
|
|
iget-object v$tempRegister, v$tempRegister, $conversionContextField
|
|
|
|
|
invoke-static {v$tempRegister, v$charSequenceRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
|
|
|
|
|
move-result-object v$charSequenceRegister
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} ?: throw TextComponentContextFingerprint.exception
|
|
|
|
|
} ?: throw TextComponentConstructorFingerprint.exception
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
// region Hook for non-litho Short videos.
|
|
|
|
|
|
|
|
|
|
ShortsTextViewFingerprint.result?.let {
|
|
|
|
|
it.mutableMethod.apply {
|
|
|
|
|
val patternResult = it.scanResult.patternScanResult!!
|
|
|
|
|
|
|
|
|
|
// If the field is true, the TextView is for a dislike button.
|
|
|
|
|
val isDisLikesBooleanReference = getInstruction<ReferenceInstruction>(patternResult.endIndex).reference
|
|
|
|
|
|
|
|
|
|
val textViewFieldReference = // Like/Dislike button TextView field
|
|
|
|
|
getInstruction<ReferenceInstruction>(patternResult.endIndex - 1).reference
|
|
|
|
|
|
|
|
|
|
// Check if the hooked TextView object is that of the dislike button.
|
|
|
|
|
// If RYD is disabled, or the TextView object is not that of the dislike button, the execution flow is not interrupted.
|
|
|
|
|
// Otherwise, the TextView object is modified, and the execution flow is interrupted to prevent it from being changed afterward.
|
|
|
|
|
val insertIndex = patternResult.startIndex + 6
|
|
|
|
|
addInstructionsWithLabels(
|
|
|
|
|
insertIndex,
|
|
|
|
|
"""
|
|
|
|
|
# Check, if the TextView is for a dislike button
|
|
|
|
|
iget-boolean v0, p0, $isDisLikesBooleanReference
|
|
|
|
|
if-eqz v0, :is_like
|
|
|
|
|
|
|
|
|
|
# Hook the TextView, if it is for the dislike button
|
|
|
|
|
iget-object v0, p0, $textViewFieldReference
|
|
|
|
|
invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->setShortsDislikes(Landroid/view/View;)Z
|
|
|
|
|
move-result v0
|
|
|
|
|
if-eqz v0, :ryd_disabled
|
|
|
|
|
return-void
|
|
|
|
|
|
|
|
|
|
:is_like
|
|
|
|
|
:ryd_disabled
|
|
|
|
|
nop
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} ?: throw ShortsTextViewFingerprint.exception
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
// region Hook for litho Shorts
|
|
|
|
|
|
|
|
|
|
// Filter that parses the video id from the UI
|
|
|
|
|
LithoFilterPatch.addFilter(FILTER_CLASS_DESCRIPTOR)
|
|
|
|
|
|
|
|
|
|
// Player response video id is needed to search for the video ids in Shorts litho components.
|
|
|
|
|
VideoIdPatch.hookPlayerResponseVideoId("$FILTER_CLASS_DESCRIPTOR->newPlayerResponseVideoId(Ljava/lang/String;Z)V")
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
// region Hook old UI layout dislikes, for the older app spoofs used with spoof-app-version.
|
|
|
|
|
|
|
|
|
|
DislikesOldLayoutTextViewFingerprint.result?.let {
|
|
|
|
|
it.mutableMethod.apply {
|
|
|
|
|
val startIndex = it.scanResult.patternScanResult!!.startIndex
|
|
|
|
|
|
|
|
|
|
val resourceIdentifierRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
|
|
|
|
|
val textViewRegister = getInstruction<OneRegisterInstruction>(startIndex + 4).registerA
|
|
|
|
|
|
|
|
|
|
addInstruction(
|
|
|
|
|
startIndex + 4,
|
|
|
|
|
"invoke-static {v$resourceIdentifierRegister, v$textViewRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->setOldUILayoutDislikes(ILandroid/widget/TextView;)V"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} ?: throw DislikesOldLayoutTextViewFingerprint.exception
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// region Hook rolling numbers.
|
|
|
|
|
|
|
|
|
|
// Do this last to allow patching old unsupported versions (if the user really wants),
|
|
|
|
|
// On older unsupported version this will fail to resolve and throw an exception,
|
|
|
|
|
// but everything will still work correctly anyways.
|
|
|
|
|
|
|
|
|
|
RollingNumberSetterFingerprint.result?.let {
|
|
|
|
|
val dislikesIndex = it.scanResult.patternScanResult!!.endIndex
|
|
|
|
|
|
|
|
|
@ -164,7 +254,7 @@ object ReturnYouTubeDislikePatch : BytecodePatch(
|
|
|
|
|
val charSequenceInstanceRegister =
|
|
|
|
|
getInstruction<OneRegisterInstruction>(0).registerA
|
|
|
|
|
val charSequenceFieldReference =
|
|
|
|
|
getInstruction<ReferenceInstruction>(dislikesIndex).reference.toString()
|
|
|
|
|
getInstruction<ReferenceInstruction>(dislikesIndex).reference
|
|
|
|
|
|
|
|
|
|
val registerCount = implementation!!.registerCount
|
|
|
|
|
|
|
|
|
@ -268,73 +358,6 @@ object ReturnYouTubeDislikePatch : BytecodePatch(
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
// region Hook for non-litho Short videos.
|
|
|
|
|
|
|
|
|
|
ShortsTextViewFingerprint.result?.let {
|
|
|
|
|
it.mutableMethod.apply {
|
|
|
|
|
val patternResult = it.scanResult.patternScanResult!!
|
|
|
|
|
|
|
|
|
|
// If the field is true, the TextView is for a dislike button.
|
|
|
|
|
val isDisLikesBooleanReference = getInstruction<ReferenceInstruction>(patternResult.endIndex).reference
|
|
|
|
|
|
|
|
|
|
val textViewFieldReference = // Like/Dislike button TextView field
|
|
|
|
|
getInstruction<ReferenceInstruction>(patternResult.endIndex - 1).reference
|
|
|
|
|
|
|
|
|
|
// Check if the hooked TextView object is that of the dislike button.
|
|
|
|
|
// If RYD is disabled, or the TextView object is not that of the dislike button, the execution flow is not interrupted.
|
|
|
|
|
// Otherwise, the TextView object is modified, and the execution flow is interrupted to prevent it from being changed afterward.
|
|
|
|
|
val insertIndex = patternResult.startIndex + 6
|
|
|
|
|
addInstructionsWithLabels(
|
|
|
|
|
insertIndex,
|
|
|
|
|
"""
|
|
|
|
|
# Check, if the TextView is for a dislike button
|
|
|
|
|
iget-boolean v0, p0, $isDisLikesBooleanReference
|
|
|
|
|
if-eqz v0, :is_like
|
|
|
|
|
|
|
|
|
|
# Hook the TextView, if it is for the dislike button
|
|
|
|
|
iget-object v0, p0, $textViewFieldReference
|
|
|
|
|
invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->setShortsDislikes(Landroid/view/View;)Z
|
|
|
|
|
move-result v0
|
|
|
|
|
if-eqz v0, :ryd_disabled
|
|
|
|
|
return-void
|
|
|
|
|
|
|
|
|
|
:is_like
|
|
|
|
|
:ryd_disabled
|
|
|
|
|
nop
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} ?: throw ShortsTextViewFingerprint.exception
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
// region Hook for litho Shorts
|
|
|
|
|
|
|
|
|
|
// Filter that parses the video id from the UI
|
|
|
|
|
LithoFilterPatch.addFilter(FILTER_CLASS_DESCRIPTOR)
|
|
|
|
|
|
|
|
|
|
// Player response video id is needed to search for the video ids in Shorts litho components.
|
|
|
|
|
VideoIdPatch.hookPlayerResponseVideoId("$FILTER_CLASS_DESCRIPTOR->newPlayerResponseVideoId(Ljava/lang/String;Z)V")
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
// region Hook old UI layout dislikes, for the older app spoofs used with spoof-app-version.
|
|
|
|
|
|
|
|
|
|
DislikesOldLayoutTextViewFingerprint.result?.let {
|
|
|
|
|
it.mutableMethod.apply {
|
|
|
|
|
val startIndex = it.scanResult.patternScanResult!!.startIndex
|
|
|
|
|
|
|
|
|
|
val resourceIdentifierRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
|
|
|
|
|
val textViewRegister = getInstruction<OneRegisterInstruction>(startIndex + 4).registerA
|
|
|
|
|
|
|
|
|
|
addInstruction(
|
|
|
|
|
startIndex + 4,
|
|
|
|
|
"invoke-static {v$resourceIdentifierRegister, v$textViewRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->setOldUILayoutDislikes(ILandroid/widget/TextView;)V"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} ?: throw DislikesOldLayoutTextViewFingerprint.exception
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun MethodFingerprint.toPatch(voteKind: Vote) = VotePatch(this, voteKind)
|
|
|
|
|