From d93a356ec29b9b601047e16762b4120edad42504 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 9 Jan 2024 23:08:01 +0400 Subject: [PATCH] refactor(YouTube - Return YouTube Dislike): Make patch more robust by removing opcode patterns from fingerprints (#2602) --- .../ReturnYouTubeDislikePatch.kt | 249 ++++++++++-------- .../ConversionContextFingerprint.kt | 18 ++ ...TextComponentAtomicReferenceFingerprint.kt | 33 --- .../TextComponentConstructorFingerprint.kt | 1 - .../TextComponentContextFingerprint.kt | 27 -- .../TextComponentDataFingerprint.kt | 16 ++ .../TextComponentLookupFingerprint.kt | 15 ++ 7 files changed, 185 insertions(+), 174 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ConversionContextFingerprint.kt delete mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentAtomicReferenceFingerprint.kt delete mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentContextFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentDataFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentLookupFingerprint.kt 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 0af09a95..76e58255 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,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()?.type == textDataClassType + } + if (insertIndex < 0) throw PatchException("Could not find data creation instruction") + val tempRegister = getInstruction(insertIndex).registerA - textComponentContextFingerprintResult.mutableMethod.apply { - // Get the conversion context obfuscated field name - val conversionContextFieldReference = - getInstruction(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()?.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(atomicReferenceStartIndex).registerB - - val atomicReferenceRegister = - getInstruction(atomicReferenceStartIndex + 6).registerC - - // Instruction that is replaced, and also has the CharacterSequence register. - val moveCharSequenceInstruction = getInstruction(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(patternResult.endIndex).reference + + val textViewFieldReference = // Like/Dislike button TextView field + getInstruction(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(startIndex).registerA + val textViewRegister = getInstruction(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(0).registerA val charSequenceFieldReference = - getInstruction(dislikesIndex).reference.toString() + getInstruction(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(patternResult.endIndex).reference - - val textViewFieldReference = // Like/Dislike button TextView field - getInstruction(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(startIndex).registerA - val textViewRegister = getInstruction(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) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ConversionContextFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ConversionContextFingerprint.kt new file mode 100644 index 00000000..75b6eb55 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ConversionContextFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint + +internal object ConversionContextFingerprint : MethodFingerprint( + returnType = "Ljava/lang/String;", + parameters = listOf(), + strings = listOf( + ", widthConstraint=", + ", heightConstraint=", + ", templateLoggerFactory=", + ", rootDisposableContainer=", + // 18.37.36 and after this String is: ConversionContext{containerInternal= + // and before it is: ConversionContext{container= + // Use a partial string to match both. + "ConversionContext{container" + ) +) \ No newline at end of file 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 deleted file mode 100644 index 6a852c9f..00000000 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentAtomicReferenceFingerprint.kt +++ /dev/null @@ -1,33 +0,0 @@ -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 - -/** - * Resolves against the same method that [TextComponentContextFingerprint] resolves to. - */ -internal object TextComponentAtomicReferenceFingerprint : MethodFingerprint( - returnType = "L", - accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL, - parameters = listOf("L"), - opcodes = listOf( - Opcode.MOVE_OBJECT, // Register B is free register - 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, - Opcode.MOVE_OBJECT, // Replace this instruction with patch code - Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT, - Opcode.IF_EQZ, - Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT_OBJECT, - Opcode.GOTO - ) -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentConstructorFingerprint.kt index deb485b1..1b82d14b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentConstructorFingerprint.kt @@ -1,6 +1,5 @@ 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 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 deleted file mode 100644 index 3377a371..00000000 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentContextFingerprint.kt +++ /dev/null @@ -1,27 +0,0 @@ -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 - -/** - * Resolves against the same class that [TextComponentConstructorFingerprint] resolves to. - */ -internal object TextComponentContextFingerprint : MethodFingerprint( - returnType = "L", - accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL, - parameters = listOf("L"), - opcodes = listOf( - Opcode.MOVE_OBJECT_FROM16, - Opcode.MOVE_OBJECT_FROM16, - Opcode.INVOKE_STATIC_RANGE, - Opcode.MOVE_RESULT_OBJECT, - Opcode.IGET_OBJECT, - 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 diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentDataFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentDataFingerprint.kt new file mode 100644 index 00000000..2d706640 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentDataFingerprint.kt @@ -0,0 +1,16 @@ +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 + +internal object TextComponentDataFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + parameters = listOf("L", "L"), + strings = listOf("text"), + customFingerprint = { _, classDef -> + val fields = classDef.fields + fields.find { it.type == "Ljava/util/BitSet;" } != null && + fields.find { it.type == "[Ljava/lang/String;" } != null + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentLookupFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentLookupFingerprint.kt new file mode 100644 index 00000000..b6b175ac --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentLookupFingerprint.kt @@ -0,0 +1,15 @@ +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 + +/** + * Resolves against the same class that [TextComponentConstructorFingerprint] resolves to. + */ +internal object TextComponentLookupFingerprint : MethodFingerprint( + returnType = "L", + accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL, + parameters = listOf("L"), + strings = listOf("…") +) \ No newline at end of file