feat(YouTube): support version 19.02.39

This commit is contained in:
inotia00 2024-01-24 23:02:52 +09:00
parent 5e12e0fa6a
commit b83a1bdd45
24 changed files with 317 additions and 281 deletions

View File

@ -1,19 +0,0 @@
package app.revanced.patches.shared.fingerprints.litho
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 IdentifierFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf(
Opcode.IPUT_OBJECT,
Opcode.INVOKE_INTERFACE_RANGE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.NEW_INSTANCE,
Opcode.CONST_STRING
),
strings = listOf("Element missing type extension")
)

View File

@ -4,11 +4,13 @@ import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.fingerprints.litho.EmptyComponentBuilderFingerprint import app.revanced.patches.shared.fingerprints.litho.EmptyComponentBuilderFingerprint
import app.revanced.patches.shared.fingerprints.litho.IdentifierFingerprint
import app.revanced.util.exception import app.revanced.util.exception
import app.revanced.util.getEmptyStringInstructionIndex
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction 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.ReferenceInstruction
@ -17,10 +19,7 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import kotlin.properties.Delegates import kotlin.properties.Delegates
object ComponentParserPatch : BytecodePatch( object ComponentParserPatch : BytecodePatch(
setOf( setOf(EmptyComponentBuilderFingerprint)
EmptyComponentBuilderFingerprint,
IdentifierFingerprint
)
) { ) {
private lateinit var emptyComponentLabel: String private lateinit var emptyComponentLabel: String
internal lateinit var insertMethod: MutableMethod internal lateinit var insertMethod: MutableMethod
@ -60,15 +59,28 @@ object ComponentParserPatch : BytecodePatch(
} }
} }
private fun MutableMethod.getTargetIndexDownTo(
startIndex: Int,
opcode: Opcode
): Int {
for (index in startIndex downTo 0) {
if (getInstruction(index).opcode != opcode)
continue
return index
}
throw PatchException("Failed to find hook method")
}
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
/** /**
* Shared fingerprint * Shared fingerprint
*/ */
EmptyComponentBuilderFingerprint.result?.let { EmptyComponentBuilderFingerprint.result?.let { result ->
it.mutableMethod.apply { result.mutableMethod.apply {
insertMethod = this insertMethod = this
emptyComponentIndex = it.scanResult.patternScanResult!!.startIndex + 1 emptyComponentIndex = result.scanResult.patternScanResult!!.startIndex + 1
val builderMethodDescriptor = val builderMethodDescriptor =
getInstruction<ReferenceInstruction>(emptyComponentIndex).reference getInstruction<ReferenceInstruction>(emptyComponentIndex).reference
@ -94,22 +106,19 @@ object ComponentParserPatch : BytecodePatch(
getInstruction<TwoRegisterInstruction>(stringBuilderIndex).registerA getInstruction<TwoRegisterInstruction>(stringBuilderIndex).registerA
insertIndex = stringBuilderIndex + 1 insertIndex = stringBuilderIndex + 1
}
} ?: throw EmptyComponentBuilderFingerprint.exception
/**
* Only used in YouTube
*/
IdentifierFingerprint.result?.let {
it.mutableMethod.apply {
val identifierIndex = it.scanResult.patternScanResult!!.startIndex
val objectIndex = it.scanResult.patternScanResult!!.endIndex + 1
val emptyStringIndex = getEmptyStringInstructionIndex()
val identifierIndex = getTargetIndexDownTo(emptyStringIndex, Opcode.IPUT_OBJECT)
identifierRegister = identifierRegister =
getInstruction<OneRegisterInstruction>(identifierIndex).registerA getInstruction<TwoRegisterInstruction>(identifierIndex).registerA
val objectIndex = implementation!!.instructions.let {
emptyStringIndex + it.subList(emptyStringIndex, it.size - 1).indexOfFirst { instruction ->
instruction.opcode == Opcode.INVOKE_VIRTUAL
}
}
objectRegister = getInstruction<BuilderInstruction35c>(objectIndex).registerC objectRegister = getInstruction<BuilderInstruction35c>(objectIndex).registerC
} }
} } ?: throw EmptyComponentBuilderFingerprint.exception
} }
} }

View File

@ -0,0 +1,58 @@
package app.revanced.patches.shared.patch.transformation
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.util.findMutableMethodOf
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
@Suppress("MemberVisibilityCanBePrivate")
abstract class AbstractTransformInstructionsPatch<T> : BytecodePatch(emptySet()) {
abstract fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
): T?
abstract fun transform(mutableMethod: MutableMethod, entry: T)
// Returns the patch indices as a Sequence, which will execute lazily.
fun findPatchIndices(classDef: ClassDef, method: Method): Sequence<T>? {
return method.implementation?.instructions?.asSequence()?.withIndex()?.mapNotNull { (index, instruction) ->
filterMap(classDef, method, instruction, index)
}
}
override fun execute(context: BytecodeContext) {
// Find all methods to patch
buildMap {
context.classes.forEach { classDef ->
val methods = buildList {
classDef.methods.forEach { method ->
// Since the Sequence executes lazily,
// using any() results in only calling
// filterMap until the first index has been found.
if (findPatchIndices(classDef, method)?.any() == true) add(method)
}
}
if (methods.isNotEmpty()) {
put(classDef, methods)
}
}
}.forEach { (classDef, methods) ->
// And finally transform the methods...
val mutableClass = context.proxy(classDef).mutableClass
methods.map(mutableClass::findMutableMethodOf).forEach methods@{ mutableMethod ->
val patchIndices = findPatchIndices(mutableClass, mutableMethod)?.toCollection(ArrayDeque())
?: return@methods
while (!patchIndices.isEmpty()) transform(mutableMethod, patchIndices.removeLast())
}
}
}
}

View File

@ -10,7 +10,7 @@ import app.revanced.patches.youtube.utils.settings.SettingsPatch
@Patch( @Patch(
name = "Remove viewer discretion dialog", name = "Remove viewer discretion dialog",
description = "Removes the dialog that appears when you try to watch a video that has been age-restricted " + description = "Adds an option to remove the dialog that appears when opening a video that has been age-restricted " +
"by accepting it automatically. This does not bypass the age restriction.", "by accepting it automatically. This does not bypass the age restriction.",
dependencies = [SettingsPatch::class], dependencies = [SettingsPatch::class],
compatiblePackages = [ compatiblePackages = [

View File

@ -2,18 +2,18 @@ package app.revanced.patches.youtube.misc.externalbrowser
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.youtube.misc.externalbrowser.fingerprints.ExternalBrowserPrimaryFingerprint import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.misc.externalbrowser.fingerprints.ExternalBrowserSecondaryFingerprint import app.revanced.patches.shared.patch.transformation.AbstractTransformInstructionsPatch
import app.revanced.patches.youtube.misc.externalbrowser.fingerprints.ExternalBrowserTertiaryFingerprint
import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH
import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.exception import com.android.tools.smali.dexlib2.iface.ClassDef
import app.revanced.util.getStringInstructionIndex 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.OneRegisterInstruction 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.reference.StringReference
@Patch( @Patch(
name = "Enable external browser", name = "Enable external browser",
@ -47,35 +47,36 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
] ]
) )
@Suppress("unused") @Suppress("unused")
object ExternalBrowserPatch : BytecodePatch( object ExternalBrowserPatch : AbstractTransformInstructionsPatch<Pair<Int, Int>>(
setOf(
ExternalBrowserPrimaryFingerprint,
ExternalBrowserSecondaryFingerprint,
ExternalBrowserTertiaryFingerprint
)
) { ) {
override fun execute(context: BytecodeContext) { override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
): Pair<Int, Int>? {
if (instruction !is ReferenceInstruction) return null
val reference = instruction.reference as? StringReference ?: return null
arrayOf( if (reference.string != "android.support.customtabs.action.CustomTabsService") return null
ExternalBrowserPrimaryFingerprint,
ExternalBrowserSecondaryFingerprint,
ExternalBrowserTertiaryFingerprint
).forEach { fingerprint ->
fingerprint.result?.let {
it.mutableMethod.apply {
val targetIndex =
getStringInstructionIndex("android.support.customtabs.action.CustomTabsService")
val register = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstructions( return instructionIndex to (instruction as OneRegisterInstruction).registerA
targetIndex + 1, """ }
override fun transform(mutableMethod: MutableMethod, entry: Pair<Int, Int>) {
val (intentStringIndex, register) = entry
// Hook the intent string.
mutableMethod.addInstructions(
intentStringIndex + 1, """
invoke-static {v$register}, $MISC_PATH/ExternalBrowserPatch;->enableExternalBrowser(Ljava/lang/String;)Ljava/lang/String; invoke-static {v$register}, $MISC_PATH/ExternalBrowserPatch;->enableExternalBrowser(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$register move-result-object v$register
""" """
) )
} }
} ?: throw fingerprint.exception
} override fun execute(context: BytecodeContext) {
super.execute(context)
/** /**
* Add settings * Add settings
@ -87,6 +88,5 @@ object ExternalBrowserPatch : BytecodePatch(
) )
SettingsPatch.updatePatchStatus("Enable external browser") SettingsPatch.updatePatchStatus("Enable external browser")
} }
} }

View File

@ -1,18 +0,0 @@
package app.revanced.patches.youtube.misc.externalbrowser.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 ExternalBrowserPrimaryFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.CONST_STRING
),
strings = listOf("android.support.customtabs.action.CustomTabsService")
)

View File

@ -1,11 +0,0 @@
package app.revanced.patches.youtube.misc.externalbrowser.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
object ExternalBrowserSecondaryFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
strings = listOf("android.support.customtabs.action.CustomTabsService")
)

View File

@ -1,18 +0,0 @@
package app.revanced.patches.youtube.misc.externalbrowser.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 ExternalBrowserTertiaryFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.CONST_STRING
),
strings = listOf("android.support.customtabs.action.CustomTabsService")
)

View File

@ -54,18 +54,18 @@ object LanguageSelectorPatch : BytecodePatch(
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
val result = GeneralPrefsFingerprint.result // YouTube v18.33.xx ~ val result = GeneralPrefsFingerprint.result // YouTube v18.33.xx ~
?: GeneralPrefsLegacyFingerprint.result // ~ YouTube v18.33.xx ?: GeneralPrefsLegacyFingerprint.result // ~ YouTube v18.32.xx
?: throw GeneralPrefsFingerprint.exception ?: throw GeneralPrefsFingerprint.exception
result.let { result.let {
it.mutableMethod.apply { it.mutableMethod.apply {
val targetIndex = it.scanResult.patternScanResult!!.endIndex val insertIndex = it.scanResult.patternScanResult!!.endIndex - 2
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstructions( addInstructions(
targetIndex, """ insertIndex, """
invoke-static {}, $MISC_PATH/LanguageSelectorPatch;->enableLanguageSwitch()Z invoke-static {}, $MISC_PATH/LanguageSelectorPatch;->enableLanguageSwitch()Z
move-result v$targetRegister move-result v$insertRegister
""" """
) )
} }

View File

@ -3,15 +3,18 @@ package app.revanced.patches.youtube.misc.language.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
/**
* Compatible with YouTube v18.33.40~
*/
object GeneralPrefsFingerprint : MethodFingerprint( object GeneralPrefsFingerprint : MethodFingerprint(
returnType = "V", returnType = "V",
parameters = emptyList(), parameters = emptyList(),
opcodes = listOf( opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.SGET,
Opcode.INVOKE_INTERFACE, Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT, Opcode.MOVE_RESULT,
Opcode.IF_NEZ, Opcode.IF_NEZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT
), ),
strings = listOf("bedtime_reminder_toggle"), strings = listOf("bedtime_reminder_toggle"),
customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/GeneralPrefsFragment;") } customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/GeneralPrefsFragment;") }

View File

@ -3,6 +3,9 @@ package app.revanced.patches.youtube.misc.language.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
/**
* Compatible with ~YouTube v18.32.39
*/
object GeneralPrefsLegacyFingerprint : MethodFingerprint( object GeneralPrefsLegacyFingerprint : MethodFingerprint(
returnType = "V", returnType = "V",
parameters = emptyList(), parameters = emptyList(),
@ -12,7 +15,9 @@ object GeneralPrefsLegacyFingerprint : MethodFingerprint(
Opcode.CONST_4, Opcode.CONST_4,
Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT, Opcode.MOVE_RESULT,
Opcode.IF_NEZ Opcode.IF_NEZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT
), ),
strings = listOf("bedtime_reminder_toggle"), strings = listOf("bedtime_reminder_toggle"),
customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/GeneralPrefsFragment;") } customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/GeneralPrefsFragment;") }

View File

@ -19,8 +19,8 @@ import app.revanced.util.exception
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction 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.OneRegisterInstruction
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.instruction.WideLiteralInstruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch( @Patch(
@ -83,8 +83,8 @@ object SeekMessagePatch : BytecodePatch(
/** /**
* Added in YouTube v18.29.xx~ * Added in YouTube v18.29.xx~
*/ */
SeekEduUndoOverlayFingerprint.result?.let { SeekEduUndoOverlayFingerprint.result?.let { result ->
it.mutableMethod.apply { result.mutableMethod.apply {
val seekUndoCalls = implementation!!.instructions.withIndex() val seekUndoCalls = implementation!!.instructions.withIndex()
.filter { instruction -> .filter { instruction ->
(instruction.value as? WideLiteralInstruction)?.wideLiteral == SeekUndoEduOverlayStub (instruction.value as? WideLiteralInstruction)?.wideLiteral == SeekUndoEduOverlayStub
@ -92,24 +92,20 @@ object SeekMessagePatch : BytecodePatch(
val insertIndex = seekUndoCalls.elementAt(seekUndoCalls.size - 1).index val insertIndex = seekUndoCalls.elementAt(seekUndoCalls.size - 1).index
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
for (index in insertIndex until implementation!!.instructions.size) { val jumpIndex = implementation!!.instructions.let {
val targetInstruction = getInstruction(index) insertIndex + it.subList(insertIndex, it.size - 1).indexOfFirst { instruction ->
if (targetInstruction.opcode != Opcode.INVOKE_VIRTUAL) instruction.opcode == Opcode.INVOKE_VIRTUAL
continue && ((instruction as? ReferenceInstruction)?.reference as? MethodReference)?.name == "setOnClickListener"
}
if (((targetInstruction as Instruction35c).reference as MethodReference).name != "setOnClickListener") }
continue val constComponent = getConstComponent(insertIndex, jumpIndex - 1)
// Force close occurs only in YouTube v18.36.xx unless we add this.
if (SettingsPatch.is1836)
addComponent(insertIndex, index - 1)
addInstructionsWithLabels( addInstructionsWithLabels(
insertIndex, fixComponent + """ insertIndex, constComponent + """
invoke-static {}, $PLAYER->hideSeekUndoMessage()Z invoke-static {}, $PLAYER->hideSeekUndoMessage()Z
move-result v$insertRegister move-result v$insertRegister
if-nez v$insertRegister, :default if-nez v$insertRegister, :default
""", ExternalLabel("default", getInstruction(index + 1)) """, ExternalLabel("default", getInstruction(jumpIndex + 1))
) )
/** /**
@ -121,9 +117,6 @@ object SeekMessagePatch : BytecodePatch(
"SETTINGS: HIDE_SEEK_UNDO_MESSAGE" "SETTINGS: HIDE_SEEK_UNDO_MESSAGE"
) )
) )
break
}
} }
} }
@ -141,30 +134,24 @@ object SeekMessagePatch : BytecodePatch(
} }
private var fixComponent: String = "" private fun MutableMethod.getConstComponent(
private fun MutableMethod.addComponent(
startIndex: Int, startIndex: Int,
endIndex: Int endIndex: Int
) { ): String {
val fixRegister = val constRegister =
getInstruction<FiveRegisterInstruction>(endIndex).registerE getInstruction<FiveRegisterInstruction>(endIndex).registerE
for (index in endIndex downTo startIndex) { for (index in endIndex downTo startIndex) {
val opcode = getInstruction(index).opcode if (getInstruction(index).opcode != Opcode.CONST_16)
if (opcode != Opcode.CONST_16)
continue continue
val register = getInstruction<OneRegisterInstruction>(index).registerA if (getInstruction<OneRegisterInstruction>(index).registerA != constRegister)
if (register != fixRegister)
continue continue
val fixValue = getInstruction<WideLiteralInstruction>(index).wideLiteral.toInt() val constValue = getInstruction<WideLiteralInstruction>(index).wideLiteral.toInt()
fixComponent = "const/16 v$fixRegister, $fixValue" return "const/16 v$constRegister, $constValue"
break
} }
return ""
} }
} }

View File

@ -1,11 +1,9 @@
package app.revanced.patches.youtube.seekbar.append package app.revanced.patches.youtube.seekbar.append
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch 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.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.youtube.utils.fingerprints.TotalTimeFingerprint import app.revanced.patches.youtube.utils.fingerprints.TotalTimeFingerprint
@ -13,11 +11,16 @@ import app.revanced.patches.youtube.utils.integrations.Constants.SEEKBAR
import app.revanced.patches.youtube.utils.overridequality.OverrideQualityHookPatch import app.revanced.patches.youtube.utils.overridequality.OverrideQualityHookPatch
import app.revanced.patches.youtube.utils.overridespeed.OverrideSpeedHookPatch import app.revanced.patches.youtube.utils.overridespeed.OverrideSpeedHookPatch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.TotalTime
import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.exception import app.revanced.util.exception
import app.revanced.util.getReference
import app.revanced.util.getWideLiteralInstructionIndex
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch( @Patch(
name = "Append time stamps information", name = "Append time stamps information",
@ -60,36 +63,28 @@ object AppendTimeStampInformationPatch : BytecodePatch(
setOf(TotalTimeFingerprint) setOf(TotalTimeFingerprint)
) { ) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
TotalTimeFingerprint.result?.let { TotalTimeFingerprint.result?.let { result ->
it.mutableMethod.apply { result.mutableMethod.apply {
var setTextIndex = -1 val constIndex = getWideLiteralInstructionIndex(TotalTime)
val charSequenceIndex = implementation!!.instructions.let {
for ((textViewIndex, textViewInstruction) in implementation!!.instructions.withIndex()) { constIndex + it.subList(constIndex, it.size - 1).indexOfFirst { instruction ->
if (textViewInstruction.opcode != Opcode.INVOKE_VIRTUAL) continue instruction.opcode == Opcode.MOVE_RESULT_OBJECT
}
if (getInstruction<ReferenceInstruction>(textViewIndex).reference.toString() == }
"Landroid/widget/TextView;->getText()Ljava/lang/CharSequence;" val charSequenceRegister = getInstruction<OneRegisterInstruction>(charSequenceIndex).registerA
) { val textViewIndex = indexOfFirstInstruction {
setTextIndex = textViewIndex + 2 getReference<MethodReference>()?.name == "getText"
val setTextRegister = getInstruction<Instruction35c>(setTextIndex).registerC }
val textViewRegister = val textViewRegister =
getInstruction<Instruction35c>(textViewIndex).registerC getInstruction<Instruction35c>(textViewIndex).registerC
addInstructions( addInstructions(
setTextIndex, """ textViewIndex, """
invoke-static {v$setTextRegister}, $SEEKBAR->appendTimeStampInformation(Ljava/lang/String;)Ljava/lang/String; invoke-static {v$textViewRegister}, $SEEKBAR->setContainerClickListener(Landroid/view/View;)V
move-result-object v$setTextRegister invoke-static {v$charSequenceRegister}, $SEEKBAR->appendTimeStampInformation(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$charSequenceRegister
""" """
) )
addInstruction(
textViewIndex,
"invoke-static {v$textViewRegister}, $SEEKBAR->setContainerClickListener(Landroid/view/View;)V"
)
break
}
}
if (setTextIndex == -1)
throw PatchException("target Instruction not found!")
} }
} ?: throw TotalTimeFingerprint.exception } ?: throw TotalTimeFingerprint.exception

View File

@ -11,7 +11,6 @@ import app.revanced.patches.youtube.shorts.startupshortsreset.fingerprints.UserW
import app.revanced.patches.youtube.utils.integrations.Constants.SHORTS import app.revanced.patches.youtube.utils.integrations.Constants.SHORTS
import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.exception import app.revanced.util.exception
import app.revanced.util.getWideLiteralInstructionIndex
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Patch( @Patch(
@ -53,7 +52,7 @@ object DisableShortsOnStartupPatch : BytecodePatch(
UserWasInShortsFingerprint.result?.let { UserWasInShortsFingerprint.result?.let {
it.mutableMethod.apply { it.mutableMethod.apply {
val insertIndex = getWideLiteralInstructionIndex(45381394) val insertIndex = it.scanResult.patternScanResult!!.startIndex
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstructionsWithLabels( addInstructionsWithLabels(

View File

@ -1,13 +1,14 @@
package app.revanced.patches.youtube.shorts.startupshortsreset.fingerprints package app.revanced.patches.youtube.shorts.startupshortsreset.fingerprints
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.util.fingerprint.LiteralValueFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
object UserWasInShortsFingerprint : LiteralValueFingerprint( object UserWasInShortsFingerprint : MethodFingerprint(
returnType = "V", returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Ljava/lang/Object;"), parameters = listOf("Ljava/lang/Object;"),
strings = listOf("Failed to read user_was_in_shorts proto after successful warmup"), opcodes = listOf(Opcode.CONST_WIDE_32),
literalSupplier = { 45381394 } strings = listOf("Failed to read user_was_in_shorts proto after successful warmup")
) )

View File

@ -5,6 +5,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.patch.litho.ComponentParserPatch import app.revanced.patches.shared.patch.litho.ComponentParserPatch
import app.revanced.patches.youtube.utils.browseid.fingerprints.BrowseIdClassFingerprint import app.revanced.patches.youtube.utils.browseid.fingerprints.BrowseIdClassFingerprint
@ -13,6 +14,7 @@ import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH
import app.revanced.patches.youtube.utils.litho.LithoFilterPatch import app.revanced.patches.youtube.utils.litho.LithoFilterPatch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
import app.revanced.util.exception import app.revanced.util.exception
import app.revanced.util.getStringInstructionIndex
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.FieldReference
@ -38,8 +40,13 @@ object BrowseIdHookPatch : BytecodePatch(
* This class handles BrowseId. * This class handles BrowseId.
* Pass an instance of this class to integrations to use Java Reflection. * Pass an instance of this class to integrations to use Java Reflection.
*/ */
BrowseIdClassFingerprint.result BrowseIdClassFingerprint.result?.let {
?.mutableClass?.methods?.find { method -> method.name == "<init>" } it.mutableMethod.apply {
val targetIndex = getStringInstructionIndex("VL") - 1
val targetReference = getInstruction<ReferenceInstruction>(targetIndex).reference
val targetClass = context.findClass((targetReference as FieldReference).definingClass)!!.mutableClass
targetClass.methods.find { method -> method.name == "<init>" }
?.apply { ?.apply {
val browseIdFieldIndex = implementation!!.instructions.indexOfFirst { instruction -> val browseIdFieldIndex = implementation!!.instructions.indexOfFirst { instruction ->
instruction.opcode == Opcode.IPUT_OBJECT instruction.opcode == Opcode.IPUT_OBJECT
@ -53,6 +60,8 @@ object BrowseIdHookPatch : BytecodePatch(
invoke-static {p0, v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->initialize(Ljava/lang/Object;Ljava/lang/String;)V invoke-static {p0, v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->initialize(Ljava/lang/Object;Ljava/lang/String;)V
""" """
) )
} ?: throw PatchException("BrowseIdClass not found!")
}
} ?: throw BrowseIdClassFingerprint.exception } ?: throw BrowseIdClassFingerprint.exception
/** /**

View File

@ -1,7 +1,12 @@
package app.revanced.patches.youtube.utils.browseid.fingerprints package app.revanced.patches.youtube.utils.browseid.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
object BrowseIdClassFingerprint : MethodFingerprint( object BrowseIdClassFingerprint : MethodFingerprint(
strings = listOf("\u0001\t\u0000\u0001\u0002\u0010\t\u0000\u0000\u0001\u0002\u1008\u0000\u0003\u1008\u0002\u0005\u1008\u0003\u0006\u1409\u0005\u0007\u1007\u0004\u0008\u1009\u0006\u000c\u1008\n\u000e\u180c\u000b\u0010\u1007\r") returnType = "Ljava/lang/Object;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.SYNTHETIC,
parameters = listOf("Ljava/lang/Object;", "L"),
strings = listOf("VL")
) )

View File

@ -26,6 +26,5 @@ object GeneralByteBufferFingerprint : MethodFingerprint(
Opcode.IPUT, Opcode.IPUT,
Opcode.IPUT, Opcode.IPUT,
Opcode.GOTO Opcode.GOTO
), )
customFingerprint = { methodDef, _ -> methodDef.name == "f" }
) )

View File

@ -12,29 +12,63 @@ import java.io.Closeable
object PlayerResponsePatch : BytecodePatch( object PlayerResponsePatch : BytecodePatch(
setOf(PlayerParameterBuilderFingerprint) setOf(PlayerParameterBuilderFingerprint)
), Closeable, MutableSet<PlayerResponsePatch.Hook> by mutableSetOf() { ), Closeable, MutableSet<PlayerResponsePatch.Hook> by mutableSetOf() {
private const val VIDEO_ID_PARAMETER = 1 private var VIDEO_ID_PARAMETER = 1
private const val PLAYER_PARAMETER = 3 private var PLAYER_PARAMETER = 3
private const val IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER = 11 private var IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER = 11
private var freeRegister = 0
private var shouldApplyNewMethod = false
private lateinit var playerResponseMethod: MutableMethod private lateinit var playerResponseMethod: MutableMethod
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
playerResponseMethod = PlayerParameterBuilderFingerprint.result?.mutableMethod playerResponseMethod = PlayerParameterBuilderFingerprint.result?.mutableMethod
?: throw PlayerParameterBuilderFingerprint.exception ?: throw PlayerParameterBuilderFingerprint.exception
playerResponseMethod.apply {
freeRegister = implementation!!.registerCount - parameters.size - 2
shouldApplyNewMethod = freeRegister > 2
if (shouldApplyNewMethod) {
IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER = freeRegister
PLAYER_PARAMETER = freeRegister - 1
VIDEO_ID_PARAMETER = freeRegister - 2
}
}
} }
override fun close() { override fun close() {
fun hookVideoId(hook: Hook) = playerResponseMethod.addInstruction( fun hookVideoId(hook: Hook) {
0, playerResponseMethod.apply {
val instruction =
if (shouldApplyNewMethod)
"invoke-static {v$VIDEO_ID_PARAMETER, v$IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER}, $hook"
else
"invoke-static {p$VIDEO_ID_PARAMETER, p$IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER}, $hook" "invoke-static {p$VIDEO_ID_PARAMETER, p$IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER}, $hook"
addInstruction(
0,
instruction
) )
}
}
fun hookPlayerParameter(hook: Hook) = playerResponseMethod.addInstructions( fun hookPlayerParameter(hook: Hook) {
0, """ playerResponseMethod.apply {
val instruction =
if (shouldApplyNewMethod)
"""
invoke-static {v$VIDEO_ID_PARAMETER, v$PLAYER_PARAMETER, v$IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER}, $hook
move-result-object p3
"""
else
"""
invoke-static {p$VIDEO_ID_PARAMETER, p$PLAYER_PARAMETER, p$IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER}, $hook invoke-static {p$VIDEO_ID_PARAMETER, p$PLAYER_PARAMETER, p$IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER}, $hook
move-result-object p$PLAYER_PARAMETER move-result-object p$PLAYER_PARAMETER
""" """
addInstructions(
0,
instruction
) )
}
}
// Reverse the order in order to preserve insertion order of the hooks. // Reverse the order in order to preserve insertion order of the hooks.
val beforeVideoIdHooks = filterIsInstance<Hook.PlayerBeforeVideoId>().asReversed() val beforeVideoIdHooks = filterIsInstance<Hook.PlayerBeforeVideoId>().asReversed()
@ -45,6 +79,16 @@ object PlayerResponsePatch : BytecodePatch(
afterVideoIdHooks.forEach(::hookPlayerParameter) afterVideoIdHooks.forEach(::hookPlayerParameter)
videoIdHooks.forEach(::hookVideoId) videoIdHooks.forEach(::hookVideoId)
beforeVideoIdHooks.forEach(::hookPlayerParameter) beforeVideoIdHooks.forEach(::hookPlayerParameter)
if (shouldApplyNewMethod) {
playerResponseMethod.addInstructions(
0, """
move-object v$VIDEO_ID_PARAMETER, p1
move-object v$PLAYER_PARAMETER, p3
move/from16 v$IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER, p11
"""
)
}
} }
internal abstract class Hook(private val methodDescriptor: String) { internal abstract class Hook(private val methodDescriptor: String) {

View File

@ -22,9 +22,11 @@ import app.revanced.patches.youtube.utils.returnyoutubedislike.shorts.ReturnYouT
import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.patches.youtube.utils.videoid.general.VideoIdPatch import app.revanced.patches.youtube.utils.videoid.general.VideoIdPatch
import app.revanced.util.exception import app.revanced.util.exception
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction 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.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
@Patch( @Patch(
name = "Return YouTube Dislike", name = "Return YouTube Dislike",
@ -100,14 +102,14 @@ object ReturnYouTubeDislikePatch : BytecodePatch(
it.mutableMethod.apply { it.mutableMethod.apply {
val conversionContextFieldIndex = implementation!!.instructions.indexOfFirst { instruction -> val conversionContextFieldIndex = implementation!!.instructions.indexOfFirst { instruction ->
instruction.opcode == Opcode.IGET_OBJECT instruction.opcode == Opcode.IGET_OBJECT
&& (instruction as ReferenceInstruction).reference.toString().endsWith("Ljava/util/Map;") && instruction.getReference<FieldReference>()?.type == "Ljava/util/Map;"
} - 1 } - 1
val conversionContextFieldReference = val conversionContextFieldReference =
getInstruction<ReferenceInstruction>(conversionContextFieldIndex).reference getInstruction<ReferenceInstruction>(conversionContextFieldIndex).reference
val charSequenceIndex = implementation!!.instructions.indexOfFirst { instruction -> val charSequenceIndex = implementation!!.instructions.indexOfFirst { instruction ->
instruction.opcode == Opcode.IGET_OBJECT instruction.opcode == Opcode.IGET_OBJECT
&& (instruction as ReferenceInstruction).reference.toString().endsWith("Ljava/util/BitSet;") && instruction.getReference<FieldReference>()?.type == "Ljava/util/BitSet;"
} - 1 } - 1
val charSequenceRegister = getInstruction<TwoRegisterInstruction>(charSequenceIndex).registerA val charSequenceRegister = getInstruction<TwoRegisterInstruction>(charSequenceIndex).registerA
val freeRegister = getInstruction<TwoRegisterInstruction>(charSequenceIndex).registerB val freeRegister = getInstruction<TwoRegisterInstruction>(charSequenceIndex).registerB

View File

@ -86,7 +86,6 @@ object SettingsPatch : AbstractSettingsResourcePatch(
val playServicesVersion = node.textContent.toInt() val playServicesVersion = node.textContent.toInt()
is1836 = playServicesVersion in 233700000..233801999
upward1828 = 232900000 <= playServicesVersion upward1828 = 232900000 <= playServicesVersion
upward1831 = 233200000 <= playServicesVersion upward1831 = 233200000 <= playServicesVersion
upward1834 = 233502000 <= playServicesVersion upward1834 = 233502000 <= playServicesVersion
@ -156,7 +155,6 @@ object SettingsPatch : AbstractSettingsResourcePatch(
private val threadPoolExecutor = Executors.newFixedThreadPool(THREAD_COUNT) private val threadPoolExecutor = Executors.newFixedThreadPool(THREAD_COUNT)
internal lateinit var contexts: ResourceContext internal lateinit var contexts: ResourceContext
internal var is1836: Boolean = false
internal var upward1828: Boolean = false internal var upward1828: Boolean = false
internal var upward1831: Boolean = false internal var upward1831: Boolean = false
internal var upward1834: Boolean = false internal var upward1834: Boolean = false

View File

@ -10,31 +10,6 @@ object VideoIdWithoutShortsFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.DECLARED_SYNCHRONIZED, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.DECLARED_SYNCHRONIZED,
parameters = listOf("L"), parameters = listOf("L"),
opcodes = listOf( opcodes = listOf(
Opcode.MONITOR_ENTER,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CONST_4,
Opcode.NEW_ARRAY,
Opcode.SGET_OBJECT,
Opcode.CONST_4,
Opcode.APUT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
Opcode.CONST_4,
Opcode.IPUT_OBJECT,
Opcode.MONITOR_EXIT,
Opcode.RETURN_VOID,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.NEW_ARRAY,
Opcode.SGET_OBJECT,
Opcode.APUT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_EQZ, Opcode.IF_EQZ,
Opcode.INVOKE_INTERFACE, Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT_OBJECT,
@ -42,10 +17,7 @@ object VideoIdWithoutShortsFingerprint : MethodFingerprint(
Opcode.MONITOR_EXIT, Opcode.MONITOR_EXIT,
Opcode.RETURN_VOID, Opcode.RETURN_VOID,
Opcode.MONITOR_EXIT, Opcode.MONITOR_EXIT,
Opcode.RETURN_VOID, Opcode.RETURN_VOID
Opcode.MOVE_EXCEPTION,
Opcode.MONITOR_EXIT,
Opcode.THROW
), ),
customFingerprint = { methodDef, classDef -> customFingerprint = { methodDef, classDef ->
methodDef.name == "l" && classDef.methods.count() == 17 methodDef.name == "l" && classDef.methods.count() == 17

View File

@ -87,6 +87,13 @@ fun Method.getWideLiteralInstructionIndex(literal: Long) = implementation?.let {
} }
} ?: -1 } ?: -1
fun Method.getEmptyStringInstructionIndex() = implementation?.let {
it.instructions.indexOfFirst { instruction ->
instruction.opcode == Opcode.CONST_STRING
&& (instruction as? BuilderInstruction21c)?.reference.toString().isEmpty()
}
} ?: -1
fun Method.getStringInstructionIndex(value: String) = implementation?.let { fun Method.getStringInstructionIndex(value: String) = implementation?.let {
it.instructions.indexOfFirst { instruction -> it.instructions.indexOfFirst { instruction ->
instruction.opcode == Opcode.CONST_STRING instruction.opcode == Opcode.CONST_STRING
@ -138,6 +145,15 @@ inline fun <reified T : Reference> Instruction.getReference() =
fun Method.indexOfFirstInstruction(predicate: Instruction.() -> Boolean) = fun Method.indexOfFirstInstruction(predicate: Instruction.() -> Boolean) =
this.implementation!!.instructions.indexOfFirst(predicate) this.implementation!!.instructions.indexOfFirst(predicate)
/**
* Get the index of the last [Instruction] that matches the predicate.
*
* @param predicate The predicate to match.
* @return The index of the first [Instruction] that matches the predicate.
*/
fun Method.indexOfLastInstruction(predicate: Instruction.() -> Boolean) =
this.implementation!!.instructions.indexOfFirst(predicate)
/** /**
* Return the resolved methods of [MethodFingerprint]s early. * Return the resolved methods of [MethodFingerprint]s early.
*/ */