mirror of
https://github.com/revanced/revanced-patches
synced 2024-12-24 19:35:52 +01:00
refactor(YouTube - Return YouTube Dislike): Make patch more robust by removing opcode patterns from fingerprints (#2602)
This commit is contained in:
parent
43a5677397
commit
d93a356ec2
@ -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,
|
||||
insertIndex,
|
||||
"""
|
||||
# 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}
|
||||
# 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)
|
||||
|
@ -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"
|
||||
)
|
||||
)
|
@ -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
|
||||
)
|
||||
)
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
)
|
@ -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
|
||||
}
|
||||
)
|
@ -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("…")
|
||||
)
|
Loading…
Reference in New Issue
Block a user