some more progress

This commit is contained in:
oSumAtrIX 2026-01-28 13:34:35 +01:00
parent cd23ee4b6c
commit ac5b65fcb5
78 changed files with 798 additions and 939 deletions

View file

@ -12,15 +12,15 @@ val `Disable Pairip license check` by creatingBytecodePatch(
) { ) {
apply { apply {
if (processLicenseResponseMethodOrNull == null || validateLicenseResponseMethodOrNull == null) { if (processLicenseResponseMethod == null || validateLicenseResponseMethod == null) {
return@apply Logger.getLogger(this::class.java.name) return@apply Logger.getLogger(this::class.java.name)
.warning("Could not find Pairip licensing check. No changes applied.") .warning("Could not find Pairip licensing check. No changes applied.")
} }
// Set first parameter (responseCode) to 0 (success status). // Set first parameter (responseCode) to 0 (success status).
processLicenseResponseMethod.addInstruction(0, "const/4 p1, 0x0") processLicenseResponseMethod!!.addInstruction(0, "const/4 p1, 0x0")
// Short-circuit the license response validation. // Short-circuit the license response validation.
validateLicenseResponseMethod.returnEarly() validateLicenseResponseMethod!!.returnEarly()
} }
} }

View file

@ -1,15 +1,16 @@
package app.revanced.patches.shared.misc.pairip.license package app.revanced.patches.shared.misc.pairip.license
internal val BytecodePatchContext.processLicenseResponseMethod by gettingFirstMethodDeclaratively { import app.revanced.patcher.definingClass
custom { method, classDef -> import app.revanced.patcher.gettingFirstMutableMethodDeclarativelyOrNull
classDef.type == "Lcom/pairip/licensecheck/LicenseClient;" && import app.revanced.patcher.name
method.name == "processResponse" import app.revanced.patcher.patch.BytecodePatchContext
}
internal val BytecodePatchContext.processLicenseResponseMethod by gettingFirstMutableMethodDeclarativelyOrNull {
name("processResponse")
definingClass("Lcom/pairip/licensecheck/LicenseClient;")
} }
internal val BytecodePatchContext.validateLicenseResponseMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.validateLicenseResponseMethod by gettingFirstMutableMethodDeclarativelyOrNull {
custom { method, classDef -> name("validateResponse")
classDef.type == "Lcom/pairip/licensecheck/ResponseValidator;" && definingClass("Lcom/pairip/licensecheck/ResponseValidator;")
method.name == "validateResponse"
}
} }

View file

@ -1,27 +1,28 @@
package app.revanced.patches.shared.misc.settings package app.revanced.patches.shared.misc.settings
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.definingClass
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.name
import app.revanced.patcher.parameterTypes import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.extension.EXTENSION_CLASS_DESCRIPTOR import app.revanced.patches.shared.misc.extension.EXTENSION_CLASS_DESCRIPTOR
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal val BytecodePatchContext.themeLightColorResourceNameMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.themeLightColorResourceNameMethod by gettingFirstMutableMethodDeclaratively {
name("getThemeLightColorResourceName")
definingClass(EXTENSION_CLASS_DESCRIPTOR)
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returnType("Ljava/lang/String;") returnType("Ljava/lang/String;")
parameterTypes() parameterTypes()
custom { method, classDef ->
method.name == "getThemeLightColorResourceName" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
} }
internal val BytecodePatchContext.themeDarkColorResourceNameMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.themeDarkColorResourceNameMethod by gettingFirstMutableMethodDeclaratively {
name("getThemeDarkColorResourceName")
definingClass(EXTENSION_CLASS_DESCRIPTOR)
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returnType("Ljava/lang/String;") returnType("Ljava/lang/String;")
parameterTypes() parameterTypes()
custom { method, classDef ->
method.name == "getThemeDarkColorResourceName" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
} }

View file

@ -2,13 +2,16 @@ package app.revanced.patches.shared.misc.spoof
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.custom import app.revanced.patcher.custom
import app.revanced.patcher.definingClass
import app.revanced.patcher.extensions.methodReference import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.firstMethodComposite import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.immutableClassDef import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.instructions import app.revanced.patcher.instructions
import app.revanced.patcher.invoke import app.revanced.patcher.invoke
import app.revanced.patcher.method import app.revanced.patcher.method
import app.revanced.patcher.name
import app.revanced.patcher.opcodes import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
@ -77,12 +80,13 @@ internal val buildRequestMethodMatch = firstMethodComposite {
val parameterTypes = parameterTypes val parameterTypes = parameterTypes
val parameterTypesSize = parameterTypes.size val parameterTypesSize = parameterTypes.size
(parameterTypesSize == 6 || parameterTypesSize == 7 || parameterTypesSize == 8) && (parameterTypesSize == 6 || parameterTypesSize == 7 || parameterTypesSize == 8) &&
parameterTypes[1] == "Ljava/util/Map;" && // URL headers. parameterTypes[1] == "Ljava/util/Map;" && // URL headers.
indexOfNewUrlRequestBuilderInstruction(this) >= 0 indexOfNewUrlRequestBuilderInstruction(this) >= 0
} }
} }
internal val BytecodePatchContext.protobufClassParseByteBufferMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.protobufClassParseByteBufferMethod by gettingFirstMethodDeclaratively {
name("parseFrom")
accessFlags(AccessFlags.PROTECTED, AccessFlags.STATIC) accessFlags(AccessFlags.PROTECTED, AccessFlags.STATIC)
returnType("L") returnType("L")
parameterTypes("L", "Ljava/nio/ByteBuffer;") parameterTypes("L", "Ljava/nio/ByteBuffer;")
@ -92,7 +96,6 @@ internal val BytecodePatchContext.protobufClassParseByteBufferMethod by gettingF
Opcode.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
) )
custom { method, _ -> method.name == "parseFrom" }
} }
internal val createStreamingDataMethodMatch = firstMethodComposite { internal val createStreamingDataMethodMatch = firstMethodComposite {
@ -112,7 +115,7 @@ internal val createStreamingDataMethodMatch = firstMethodComposite {
} }
} }
internal val BytecodePatchContext.buildMediaDataSourceMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.buildMediaDataSourceMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameterTypes( parameterTypes(
"Landroid/net/Uri;", "Landroid/net/Uri;",
@ -147,7 +150,7 @@ internal val mediaFetchEnumConstructorMethodMatch = firstMethodComposite {
) )
} }
internal val BytecodePatchContext.nerdsStatsVideoFormatBuilderMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.nerdsStatsVideoFormatBuilderMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returnType("Ljava/lang/String;") returnType("Ljava/lang/String;")
parameterTypes("L") parameterTypes("L")
@ -156,12 +159,11 @@ internal val BytecodePatchContext.nerdsStatsVideoFormatBuilderMethod by gettingF
) )
} }
internal val BytecodePatchContext.patchIncludedExtensionMethodMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.patchIncludedExtensionMethodMethod by gettingFirstMutableMethodDeclaratively {
name("isPatchIncluded")
definingClass(EXTENSION_CLASS_DESCRIPTOR)
returnType("Z") returnType("Z")
parameterTypes() parameterTypes()
custom { method, classDef ->
method.name == "isPatchIncluded" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
} }
// Feature flag that turns on Platypus programming language code compiled to native C++. // Feature flag that turns on Platypus programming language code compiled to native C++.
@ -192,9 +194,9 @@ internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) = method.ind
val reference = methodReference ?: return@indexOfFirstInstruction false val reference = methodReference ?: return@indexOfFirstInstruction false
opcode == Opcode.INVOKE_VIRTUAL && reference.definingClass == "Lorg/chromium/net/CronetEngine;" && opcode == Opcode.INVOKE_VIRTUAL && reference.definingClass == "Lorg/chromium/net/CronetEngine;" &&
reference.name == "newUrlRequestBuilder" && reference.name == "newUrlRequestBuilder" &&
reference.parameterTypes.size == 3 && reference.parameterTypes.size == 3 &&
reference.parameterTypes[0] == "Ljava/lang/String;" && reference.parameterTypes[0] == "Ljava/lang/String;" &&
reference.parameterTypes[1] == "Lorg/chromium/net/UrlRequest\$Callback;" && reference.parameterTypes[1] == "Lorg/chromium/net/UrlRequest\$Callback;" &&
reference.parameterTypes[2] == "Ljava/util/concurrent/Executor;" reference.parameterTypes[2] == "Ljava/util/concurrent/Executor;"
} }

View file

@ -1,9 +1,13 @@
package app.revanced.patches.shared.misc.spoof package app.revanced.patches.shared.misc.spoof
import app.revanced.patcher.custom
import app.revanced.patcher.extensions.* import app.revanced.patcher.extensions.*
import app.revanced.patcher.firstMutableMethodDeclaratively
import app.revanced.patcher.opcodes
import app.revanced.patcher.patch.BytecodePatchBuilder import app.revanced.patcher.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.returnType
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.util.* import app.revanced.util.*
@ -124,7 +128,7 @@ internal fun spoofVideoStreamsPatch(
addInstruction( addInstruction(
videoDetailsIndex + 1, videoDetailsIndex + 1,
"invoke-direct { p0, v$videoDetailsRegister }, " + "invoke-direct { p0, v$videoDetailsRegister }, " +
"$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V", "$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
) )
val protobufClass = protobufClassParseByteBufferMethod.definingClass val protobufClass = protobufClassParseByteBufferMethod.definingClass
@ -212,7 +216,7 @@ internal fun spoofVideoStreamsPatch(
// A proper fix may include modifying the request body to match the platforms expected body. // A proper fix may include modifying the request body to match the platforms expected body.
buildMediaDataSourceMethod.apply { buildMediaDataSourceMethod.apply {
val targetIndex = instructions.lastIndex val targetIndex = instructions.count() - 1
// Instructions are added just before the method returns, // Instructions are added just before the method returns,
// so there's no concern of clobbering in-use registers. // so there's no concern of clobbering in-use registers.
@ -271,7 +275,7 @@ internal fun spoofVideoStreamsPatch(
val mediaFetchEnumClass = definingClass val mediaFetchEnumClass = definingClass
val sabrFieldIndex = indexOfFirstInstructionOrThrow(disabledBySABRStreamingUrlString) { val sabrFieldIndex = indexOfFirstInstructionOrThrow(disabledBySABRStreamingUrlString) {
opcode == Opcode.SPUT_OBJECT && opcode == Opcode.SPUT_OBJECT &&
getReference<FieldReference>()?.type == mediaFetchEnumClass getReference<FieldReference>()?.type == mediaFetchEnumClass
} }
Pair( Pair(
@ -280,17 +284,15 @@ internal fun spoofVideoStreamsPatch(
) )
} }
val sabrFingerprint = fingerprint { val sabrMethod = firstMutableMethodDeclaratively {
returnType(mediaFetchEnumClass) returnType(mediaFetchEnumClass)
opcodes( opcodes(
Opcode.SGET_OBJECT, Opcode.SGET_OBJECT,
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
) )
custom { method, _ -> custom { parameterTypes.isEmpty() }
!method.parameterTypes.isEmpty()
}
} }
sabrFingerprint.method.addInstructionsWithLabels( sabrMethod.addInstructionsWithLabels(
0, 0,
""" """
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableSABR()Z invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableSABR()Z

View file

@ -1,13 +1,12 @@
package app.revanced.patches.solidexplorer2.functionality.filesize package app.revanced.patches.solidexplorer2.functionality.filesize
import app.revanced.patcher.definingClass import app.revanced.patcher.definingClass
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.name import app.revanced.patcher.name
import app.revanced.patcher.opcodes import app.revanced.patcher.opcodes
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.onReadyMethod by gettingFirstMutableMethodDeclaratively { internal val onReadyMethodMatch = firstMethodComposite {
name("onReady") name("onReady")
definingClass("Lpl/solidexplorer/plugins/texteditor/TextEditor;") definingClass("Lpl/solidexplorer/plugins/texteditor/TextEditor;")
opcodes( opcodes(

View file

@ -12,11 +12,11 @@ val `Remove file size limit` by creatingBytecodePatch(
compatibleWith("pl.solidexplorer2") compatibleWith("pl.solidexplorer2")
apply { apply {
onReadyMethod.apply { onReadyMethodMatch.let {
val cmpIndex = onReadyMethod.indices.first() + 1 // TODO val cmpIndex = it.indices.first() + 1
val cmpResultRegister = getInstruction<ThreeRegisterInstruction>(cmpIndex).registerA val cmpResultRegister = it.method.getInstruction<ThreeRegisterInstruction>(cmpIndex).registerA
replaceInstruction(cmpIndex, "const/4 v$cmpResultRegister, 0x0") it.method.replaceInstruction(cmpIndex, "const/4 v$cmpResultRegister, 0x0")
} }
} }
} }

View file

@ -2,6 +2,7 @@ package app.revanced.patches.spotify.layout.theme
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.booleanOption import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
@ -22,7 +23,7 @@ private val customThemeBytecodePatch = bytecodePatch {
val colorSpaceUtilsClassDef = colorSpaceUtilsClassMethod.immutableClassDef val colorSpaceUtilsClassDef = colorSpaceUtilsClassMethod.immutableClassDef
// Hook a util method that converts ARGB to RGBA in the sRGB color space to replace hardcoded accent colors. // Hook a util method that converts ARGB to RGBA in the sRGB color space to replace hardcoded accent colors.
convertArgbToRgbaMethod.match(colorSpaceUtilsClassDef).method.apply { colorSpaceUtilsClassDef.getConvertArgbToRgbaMethod().apply {
addInstructions( addInstructions(
0, 0,
""" """
@ -40,7 +41,7 @@ private val customThemeBytecodePatch = bytecodePatch {
val invokeParseColorIndex = indexOfFirstInstructionOrThrow { val invokeParseColorIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>() val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/graphics/Color;" && reference?.definingClass == "Landroid/graphics/Color;" &&
reference.name == "parseColor" reference.name == "parseColor"
} }
val parsedColorRegister = getInstruction<OneRegisterInstruction>(invokeParseColorIndex + 1).registerA val parsedColorRegister = getInstruction<OneRegisterInstruction>(invokeParseColorIndex + 1).registerA
@ -61,7 +62,7 @@ private val customThemeBytecodePatch = bytecodePatch {
val invokeArgbIndex = indexOfFirstInstructionOrThrow { val invokeArgbIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>() val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/graphics/Color;" && reference?.definingClass == "Landroid/graphics/Color;" &&
reference.name == "argb" reference.name == "argb"
} }
val argbColorRegister = getInstruction<OneRegisterInstruction>(invokeArgbIndex + 1).registerA val argbColorRegister = getInstruction<OneRegisterInstruction>(invokeArgbIndex + 1).registerA
@ -97,7 +98,7 @@ val customThemePatch = resourcePatch(
default = false, default = false,
name = "Override player gradient color", name = "Override player gradient color",
description = description =
"Apply primary background color to the player gradient color, which changes dynamically with the song.", "Apply primary background color to the player gradient color, which changes dynamically with the song.",
required = false, required = false,
) )
@ -105,7 +106,7 @@ val customThemePatch = resourcePatch(
default = "#FF121212", default = "#FF121212",
name = "Secondary background color", name = "Secondary background color",
description = "The secondary background color. (e.g. playlist list in home, player artist, song credits). " + description = "The secondary background color. (e.g. playlist list in home, player artist, song credits). " +
"Can be a hex color or a resource reference.\",", "Can be a hex color or a resource reference.\",",
required = true, required = true,
) )
@ -120,7 +121,7 @@ val customThemePatch = resourcePatch(
default = "#FF1ABC54", default = "#FF1ABC54",
name = "Pressed accent color", name = "Pressed accent color",
description = "The color when accented buttons are pressed, by default slightly darker than accent. " + description = "The color when accented buttons are pressed, by default slightly darker than accent. " +
"Can be a hex color or a resource reference.", "Can be a hex color or a resource reference.",
required = true, required = true,
) )
@ -143,38 +144,38 @@ val customThemePatch = resourcePatch(
node.textContent = when (name) { node.textContent = when (name) {
// Main background color. // Main background color.
"gray_7", "gray_7",
// Left sidebar background color in tablet mode. // Left sidebar background color in tablet mode.
"gray_10", "gray_10",
// Gradient next to user photo and "All" in home page. // Gradient next to user photo and "All" in home page.
"dark_base_background_base", "dark_base_background_base",
// "Add account", "Settings and privacy", "View Profile" left sidebar background color. // "Add account", "Settings and privacy", "View Profile" left sidebar background color.
"dark_base_background_elevated_base", "dark_base_background_elevated_base",
// Song/player gradient start/end color. // Song/player gradient start/end color.
"bg_gradient_start_color", "bg_gradient_end_color", "bg_gradient_start_color", "bg_gradient_end_color",
// Login screen background color and gradient start. // Login screen background color and gradient start.
"sthlm_blk", "sthlm_blk_grad_start", "sthlm_blk", "sthlm_blk_grad_start",
// Misc. // Misc.
"image_placeholder_color", "image_placeholder_color",
-> backgroundColor -> backgroundColor
// "About the artist" background color in song player. // "About the artist" background color in song player.
"gray_15", "gray_15",
// Track credits, merch background color in song player. // Track credits, merch background color in song player.
"track_credits_card_bg", "benefit_list_default_color", "merch_card_background", "track_credits_card_bg", "benefit_list_default_color", "merch_card_background",
// Playlist list background in home page. // Playlist list background in home page.
"opacity_white_10", "opacity_white_10",
// "What's New" pills background. // "What's New" pills background.
"dark_base_background_tinted_highlight", "dark_base_background_tinted_highlight",
-> backgroundColorSecondary -> backgroundColorSecondary
"dark_brightaccent_background_base", "dark_brightaccent_background_base",
"dark_base_text_brightaccent", "dark_base_text_brightaccent",
"green_light", "green_light",
"spotify_green_157", "spotify_green_157",
-> accentColor -> accentColor
"dark_brightaccent_background_press", "dark_brightaccent_background_press",
-> accentColorPressed -> accentColorPressed
else -> continue else -> continue
} }

View file

@ -1,32 +1,35 @@
package app.revanced.patches.spotify.layout.theme package app.revanced.patches.spotify.layout.theme
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.firstMutableMethodDeclaratively
import app.revanced.patcher.gettingFirstMethod
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethod
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.parameterTypes import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType import app.revanced.patcher.returnType
import app.revanced.util.containsLiteralInstruction import app.revanced.patcher.unorderedAllOf
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.colorSpaceUtilsClassMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.colorSpaceUtilsClassMethod by gettingFirstMethodDeclaratively {
strings("The specified color must be encoded in an RGB color space.") // Partial string match. instructions("The specified color must be encoded in an RGB color space."(String::contains))
} }
internal val BytecodePatchContext.convertArgbToRgbaMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getConvertArgbToRgbaMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
returnType("J") returnType("J")
parameterTypes("J") parameterTypes("J")
} }
internal val BytecodePatchContext.parseLottieJsonMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.parseLottieJsonMethod by gettingFirstMutableMethod("Unsupported matte type: ")
strings("Unsupported matte type: ")
}
internal val BytecodePatchContext.parseAnimatedColorMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.parseAnimatedColorMethod by gettingFirstMutableMethodDeclaratively {
parameterTypes("L", "F") parameterTypes("L", "F")
returnType("Ljava/lang/Object;") returnType("Ljava/lang/Object;")
custom { method, _ -> instructions(predicates = unorderedAllOf(255.0.toRawBits()(), 1.0.toRawBits()()))
method.containsLiteralInstruction(255.0) &&
method.containsLiteralInstruction(1.0)
}
} }

View file

@ -1,5 +1,12 @@
package app.revanced.patches.spotify.misc.extension package app.revanced.patches.spotify.misc.extension
internal val BytecodePatchContext.loadOrbitLibraryMethod by gettingFirstMethodDeclaratively { import app.revanced.patcher.firstMethodComposite
strings("orbit_library_load", "orbit-jni-spotify") import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
internal val loadOrbitLibraryMethodMatch = firstMethodComposite {
instructions(
"orbit_library_load"(),
"orbit-jni-spotify"()
)
} }

View file

@ -14,7 +14,7 @@ internal val loadOrbitLibraryHook = extensionHook {
// FIXME: Creating this is a mess and needs refactoring. // FIXME: Creating this is a mess and needs refactoring.
extensionHook( extensionHook(
getInsertIndex = { getInsertIndex = {
loadOrbitLibraryMethod.stringMatches.last().index loadOrbitLibraryMethodMatch.stringMatches.last().index
}, },
getContextRegister = { method -> getContextRegister = { method ->
val contextReferenceIndex = method.indexOfFirstInstruction { val contextReferenceIndex = method.indexOfFirstInstruction {
@ -25,6 +25,6 @@ internal val loadOrbitLibraryHook = extensionHook {
"v$contextRegister" "v$contextRegister"
}, },
fingerprint = loadOrbitLibraryMethod, fingerprint = loadOrbitLibraryMethodMatch,
) )
} }

View file

@ -1,14 +1,18 @@
package app.revanced.patches.spotify.misc.fix.login package app.revanced.patches.spotify.misc.fix.login
import app.revanced.patcher.firstMutableMethodDeclaratively
import app.revanced.patcher.gettingFirstMethod
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethod
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.literal import app.revanced.patcher.literal
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.katanaProxyLoginMethodHandlerClassMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.katanaProxyLoginMethodHandlerClassMethod by gettingFirstMethod("katana_proxy_auth")
strings("katana_proxy_auth")
}
internal val BytecodePatchContext.katanaProxyLoginMethodTryAuthorizeMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
strings("e2e") internal fun ClassDef.getKatanaProxyLoginMethodTryAuthorizeMethod() = firstMutableMethodDeclaratively("e2e") {
literal { 0 } instructions(0L())
} }

View file

@ -1,12 +1,13 @@
package app.revanced.patches.spotify.misc.fix.login package app.revanced.patches.spotify.misc.fix.login
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.creatingBytecodePatch import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.util.returnEarly import app.revanced.util.returnEarly
@Suppress("unused", "ObjectPropertyName") @Suppress("unused", "ObjectPropertyName")
val `Fix Facebook login` by creatingBytecodePatch( val `Fix Facebook login` by creatingBytecodePatch(
description = description =
"Fix logging in with Facebook when the app is patched by always opening the login in a web browser window.", "Fix logging in with Facebook when the app is patched by always opening the login in a web browser window.",
) { ) {
compatibleWith("com.spotify.music") compatibleWith("com.spotify.music")
@ -16,13 +17,9 @@ val `Fix Facebook login` by creatingBytecodePatch(
// which ends up making the Facebook server reject with an invalid key hash for the app signature. // which ends up making the Facebook server reject with an invalid key hash for the app signature.
// Override the Facebook SDK to always handle the login using the web browser, which does not perform // Override the Facebook SDK to always handle the login using the web browser, which does not perform
// signature checks. // signature checks.
val katanaProxyLoginMethodHandlerClass = katanaProxyLoginMethodHandlerClassMethod.immutableClassDef
// Always return 0 (no Intent was launched) as the result of trying to authorize with the Facebook app to // Always return 0 (no Intent was launched) as the result of trying to authorize with the Facebook app to
// make the login fallback to a web browser window. // make the login fallback to a web browser window.
katanaProxyLoginMethodTryAuthorizeMethod katanaProxyLoginMethodHandlerClassMethod.immutableClassDef
.match(katanaProxyLoginMethodHandlerClass) .getKatanaProxyLoginMethodTryAuthorizeMethod().returnEarly(0)
.method
.returnEarly(0)
} }
} }

View file

@ -1,10 +1,16 @@
package app.revanced.patches.spotify.misc.lyrics package app.revanced.patches.spotify.misc.lyrics
import app.revanced.patcher.classDef
import app.revanced.patcher.extensions.addInstruction import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.firstMutableMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.method
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.creatingBytecodePatch import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.patcher.patch.stringOption import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.returnType
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
@ -59,8 +65,6 @@ val `Change lyrics provider` by creatingBytecodePatch(
} }
apply { apply {
val httpClientBuilderMethod = httpClientBuilderMethod.immutableMethod
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host. // region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) { val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
@ -70,7 +74,7 @@ val `Change lyrics provider` by creatingBytecodePatch(
val setUrlBuilderHostIndex = indexOfFirstInstructionReversedOrThrow(invokeBuildUrlIndex) { val setUrlBuilderHostIndex = indexOfFirstInstructionReversedOrThrow(invokeBuildUrlIndex) {
val reference = getReference<MethodReference>() val reference = getReference<MethodReference>()
reference?.definingClass == "Lokhttp3/HttpUrl${"$"}Builder;" && reference?.definingClass == "Lokhttp3/HttpUrl${"$"}Builder;" &&
reference.parameterTypes.firstOrNull() == "Ljava/lang/String;" reference.parameterTypes.firstOrNull() == "Ljava/lang/String;"
} }
val hostRegister = getInstruction<FiveRegisterInstruction>(setUrlBuilderHostIndex).registerD val hostRegister = getInstruction<FiveRegisterInstruction>(setUrlBuilderHostIndex).registerD
@ -90,17 +94,13 @@ val `Change lyrics provider` by creatingBytecodePatch(
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one. // region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
val getLyricsHttpClientFingerprint = fingerprint { val getLyricsHttpClientMethod = firstMutableMethodDeclaratively {
returnType(httpClientBuilderMethod.returnType) returnType(httpClientBuilderMethod.returnType)
parameterTypes() parameterTypes()
custom { method, _ -> instructions(method { this == httpClientBuilderMethod })
method.indexOfFirstInstruction {
getReference<MethodReference>() == httpClientBuilderMethod
} >= 0
}
} }
getLyricsHttpClientFingerprint.method.apply { getLyricsHttpClientMethod.apply {
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow { val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>() == httpClientBuilderMethod getReference<MethodReference>() == httpClientBuilderMethod
} }

View file

@ -1,5 +1,6 @@
package app.revanced.patches.spotify.misc.lyrics package app.revanced.patches.spotify.misc.lyrics
import app.revanced.patcher.gettingFirstMethod
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.parameterTypes import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
@ -8,16 +9,4 @@ import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val BytecodePatchContext.httpClientBuilderMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.httpClientBuilderMethod by gettingFirstMethod("client == null", "scheduler == null")
strings("client == null", "scheduler == null")
}
internal fun getLyricsHttpClientFingerprint(httpClientBuilderMethodReference: MethodReference) = fingerprint {
returnType(httpClientBuilderMethodReference.returnType)
parameterTypes()
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<MethodReference>() == httpClientBuilderMethodReference
} >= 0
}
}

View file

@ -1,10 +1,9 @@
package app.revanced.patches.tumblr.featureflags package app.revanced.patches.tumblr.featureflags
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.opcodes import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -18,7 +17,7 @@ import com.android.tools.smali.dexlib2.Opcode
// Some features seem to be very old and never removed, though, such as Google Login. // Some features seem to be very old and never removed, though, such as Google Login.
// The startIndex of the opcode pattern is at the start of the function after the arg null check. // The startIndex of the opcode pattern is at the start of the function after the arg null check.
// we want to insert our instructions there. // we want to insert our instructions there.
internal val BytecodePatchContext.getFeatureValueMethod by gettingFirstMethodDeclaratively { internal val getFeatureValueMethodMatch = firstMethodComposite("feature") {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Ljava/lang/String;") returnType("Ljava/lang/String;")
parameterTypes("L", "Z") parameterTypes("L", "Z")
@ -27,5 +26,4 @@ internal val BytecodePatchContext.getFeatureValueMethod by gettingFirstMethodDec
Opcode.INVOKE_STATIC, Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT, Opcode.MOVE_RESULT,
) )
strings("feature")
} }

View file

@ -24,54 +24,55 @@ val overrideFeatureFlagsPatch = bytecodePatch(
) { ) {
apply { apply {
val configurationClass = getFeatureValueMethod.immutableMethod.definingClass getFeatureValueMethodMatch.let { match ->
val featureClass = getFeatureValueMethod.immutableMethod.parameterTypes[0].toString() val configurationClass = match.immutableMethod.definingClass
val featureClass = match.immutableMethod.parameterTypes[0].toString()
// The method we want to inject into does not have enough registers, so we inject a helper method // The method we want to inject into does not have enough registers, so we inject a helper method
// and inject more instructions into it later, see addOverride. // and inject more instructions into it later, see addOverride.
// This is not in an extension since the unused variable would get compiled away and the method would // This is not in an extension since the unused variable would get compiled away and the method would
// get compiled to only have one register, which is not enough for our later injected instructions. // get compiled to only have one register, which is not enough for our later injected instructions.
val helperMethod = ImmutableMethod( val helperMethod = ImmutableMethod(
getFeatureValueMethod.immutableMethod.definingClass, match.immutableMethod.definingClass,
"getValueOverride", "getValueOverride",
listOf(ImmutableMethodParameter(featureClass, null, "feature")), listOf(ImmutableMethodParameter(featureClass, null, "feature")),
"Ljava/lang/String;", "Ljava/lang/String;",
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value, AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null, null,
null, null,
MutableMethodImplementation(4), MutableMethodImplementation(4),
).toMutable().apply { ).toMutable().apply {
// This is the equivalent of // This is the equivalent of
// String featureName = feature.toString() // String featureName = feature.toString()
// <inject more instructions here later> // <inject more instructions here later>
// return null // return null
addInstructions( addInstructions(
0, 0,
""" """
# toString() the enum value # toString() the enum value
invoke-virtual {p1}, $featureClass->toString()Ljava/lang/String; invoke-virtual {p1}, $featureClass->toString()Ljava/lang/String;
move-result-object v0 move-result-object v0
# !!! If you add more instructions above this line, update helperInsertIndex below! # !!! If you add more instructions above this line, update helperInsertIndex below!
# Here we will insert one compare & return for every registered Feature override # Here we will insert one compare & return for every registered Feature override
# This is done below in the addOverride function # This is done below in the addOverride function
# If none of the overrides returned a value, we should return null # If none of the overrides returned a value, we should return null
const/4 v0, 0x0 const/4 v0, 0x0
return-object v0 return-object v0
""", """,
) )
}.also { helperMethod -> }.also { helperMethod ->
getFeatureValueMethod.classDef.methods.add(helperMethod) match.classDef.methods.add(helperMethod)
} }
// Here we actually insert the hook to call our helper method and return its value if it returns not null // Here we actually insert the hook to call our helper method and return its value if it returns not null
// This is equivalent to // This is equivalent to
// String forcedValue = getValueOverride(feature) // String forcedValue = getValueOverride(feature)
// if (forcedValue != null) return forcedValue // if (forcedValue != null) return forcedValue
val getFeatureIndex = getFeatureValueMethod.indices.first() val getFeatureIndex = match.indices.first()
getFeatureValueMethod.addInstructionsWithLabels( match.method.addInstructionsWithLabels(
getFeatureIndex, getFeatureIndex,
""" """
# Call the Helper Method with the Feature # Call the Helper Method with the Feature
invoke-virtual {p0, p1}, $configurationClass->getValueOverride($featureClass)Ljava/lang/String; invoke-virtual {p0, p1}, $configurationClass->getValueOverride($featureClass)Ljava/lang/String;
@ -85,31 +86,32 @@ val overrideFeatureFlagsPatch = bytecodePatch(
:is_null :is_null
nop nop
""", """,
)
val helperInsertIndex = 2
addFeatureFlagOverride = { name, value ->
// For every added override, we add a few instructions in the middle of the helper method
// to check if the feature is the one we want and return the override value if it is.
// This is equivalent to
// if (featureName == {name}) return {value}
helperMethod.addInstructionsWithLabels(
helperInsertIndex,
"""
# v0 is still the string name of the currently checked feature from above
# Compare the current string with the override string
const-string v1, "$name"
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v1
# If the current string is the one we want to override, we return the override value
if-eqz v1, :no_override
const-string v1, "$value"
return-object v1
# Else we just continue...
:no_override
nop
""",
) )
val helperInsertIndex = 2
addFeatureFlagOverride = { name, value ->
// For every added override, we add a few instructions in the middle of the helper method
// to check if the feature is the one we want and return the override value if it is.
// This is equivalent to
// if (featureName == {name}) return {value}
helperMethod.addInstructionsWithLabels(
helperInsertIndex,
"""
# v0 is still the string name of the currently checked feature from above
# Compare the current string with the override string
const-string v1, "$name"
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v1
# If the current string is the one we want to override, we return the override value
if-eqz v1, :no_override
const-string v1, "$value"
return-object v1
# Else we just continue...
:no_override
nop
""",
)
}
} }
} }
} }

View file

@ -1,26 +1,25 @@
package app.revanced.patches.tumblr.fixes package app.revanced.patches.tumblr.fixes
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.opcodes import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
// Fingerprint for the addQueryParam method from retrofit2 // Fingerprint for the addQueryParam method from retrofit2:
// https://github.com/square/retrofit/blob/trunk/retrofit/src/main/java/retrofit2/RequestBuilder.java#L186 // https://github.com/square/retrofit/blob/trunk/retrofit/src/main/java/retrofit2/RequestBuilder.java#L186.
// Injecting here allows modifying dynamically set query parameters // Injecting here allows modifying dynamically set query parameters.
internal val BytecodePatchContext.addQueryParamMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.addQueryParamMethod by gettingFirstMutableMethodDeclaratively("Malformed URL. Base: ", ", Relative: ") {
parameterTypes("Ljava/lang/String;", "Ljava/lang/String;", "Z") parameterTypes("Ljava/lang/String;", "Ljava/lang/String;", "Z")
strings("Malformed URL. Base: ", ", Relative: ")
} }
// Fingerprint for the parseHttpMethodAndPath method from retrofit2 // Fingerprint for the parseHttpMethodAndPath method from retrofit2:
// https://github.com/square/retrofit/blob/ebf87b10997e2136af4d335276fa950221852c64/retrofit/src/main/java/retrofit2/RequestFactory.java#L270-L302 // https://github.com/square/retrofit/blob/ebf87b10997e2136af4d335276fa950221852c64/retrofit/src/main/java/retrofit2/RequestFactory.java#L270-L302
// Injecting here allows modifying the path/query params of API endpoints defined via annotations // Injecting here allows modifying the path/query params of API endpoints defined via annotations.
internal val BytecodePatchContext.httpPathParserMethod by gettingFirstMethodDeclaratively { internal val httpPathParserMethodMatch = firstMethodComposite("Only one HTTP method is allowed. Found: %s and %s.") {
opcodes( opcodes(
Opcode.IPUT_OBJECT, Opcode.IPUT_OBJECT,
Opcode.IPUT_BOOLEAN, Opcode.IPUT_BOOLEAN,
) )
strings("Only one HTTP method is allowed. Found: %s and %s.")
} }

View file

@ -19,8 +19,8 @@ val `Fix old versions` by creatingBytecodePatch(
// Remove the live query parameters from the path when it's specified via a @METHOD annotation. // Remove the live query parameters from the path when it's specified via a @METHOD annotation.
for (liveQueryParameter in liveQueryParameters) { for (liveQueryParameter in liveQueryParameters) {
httpPathParserMethod.addInstructions( httpPathParserMethodMatch.method.addInstructions(
httpPathParserMethod.indices.last() + 1, httpPathParserMethodMatch.indices.last() + 1,
""" """
# urlPath = urlPath.replace(liveQueryParameter, "") # urlPath = urlPath.replace(liveQueryParameter, "")
const-string p1, "$liveQueryParameter" const-string p1, "$liveQueryParameter"

View file

@ -24,9 +24,9 @@ val filterTimelineObjectsPatch = bytecodePatch(
dependsOn(sharedExtensionPatch) dependsOn(sharedExtensionPatch)
apply { apply {
val filterInsertIndex = timelineFilterExtensionMethod.indices.first() val filterInsertIndex = timelineFilterExtensionMethodMatch.indices.first()
timelineFilterExtensionMethod.apply { timelineFilterExtensionMethodMatch.method.apply {
val addInstruction = getInstruction<BuilderInstruction35c>(filterInsertIndex + 1) val addInstruction = getInstruction<BuilderInstruction35c>(filterInsertIndex + 1)
val filterListRegister = addInstruction.registerC val filterListRegister = addInstruction.registerC
@ -50,8 +50,8 @@ val filterTimelineObjectsPatch = bytecodePatch(
arrayOf( arrayOf(
timelineConstructorMethod to 1, timelineConstructorMethod to 1,
postsResponseConstructorMethod to 2, postsResponseConstructorMethod to 2,
).forEach { (fingerprint, timelineObjectsRegister) -> ).forEach { (method, timelineObjectsRegister) ->
fingerprint.method.addInstructions( method.addInstructions(
0, 0,
"invoke-static {p$timelineObjectsRegister}, " + "invoke-static {p$timelineObjectsRegister}, " +
"Lapp/revanced/extension/tumblr/patches/TimelineFilterPatch;->" + "Lapp/revanced/extension/tumblr/patches/TimelineFilterPatch;->" +

View file

@ -1,39 +1,33 @@
package app.revanced.patches.tumblr.timelinefilter package app.revanced.patches.tumblr.timelinefilter
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.opcodes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
// This is the constructor of the PostsResponse class. // This is the constructor of the PostsResponse class.
// The same applies here as with the TimelineConstructorMethod. // The same applies here as with the TimelineConstructorMethod.
internal val BytecodePatchContext.postsResponseConstructorMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.postsResponseConstructorMethod by gettingFirstMutableMethodDeclaratively {
definingClass { endsWith("/PostsResponse;") }
accessFlags(AccessFlags.CONSTRUCTOR, AccessFlags.PUBLIC) accessFlags(AccessFlags.CONSTRUCTOR, AccessFlags.PUBLIC)
custom { method, classDef -> classDef.endsWith("/PostsResponse;") && method.parameters.size == 4 } custom { parameters.size == 4 }
} }
// This is the constructor of the Timeline class. // This is the constructor of the Timeline class.
// It receives the List<TimelineObject> as an argument with a @Json annotation, so this should be the first time // It receives the List<TimelineObject> as an argument with a @Json annotation, so this should be the first time
// that the List<TimelineObject> is exposed in non-library code. // that the List<TimelineObject> is exposed in non-library code.
internal val BytecodePatchContext.timelineConstructorMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.timelineConstructorMethod by gettingFirstMutableMethodDeclaratively("timelineObjectsList") {
strings("timelineObjectsList") definingClass { endsWith("/Timeline;") }
custom { method, classDef -> custom { parameters[0].type == "Ljava/util/List;" }
classDef.endsWith("/Timeline;") && method.parameters[0].type == "Ljava/util/List;"
}
} }
// This fingerprints the extension TimelineFilterPatch.filterTimeline method. // This fingerprints the extension TimelineFilterPatch.filterTimeline method.
// The opcode fingerprint is searching for // The opcode fingerprint is searching for
// if ("BLOCKED_OBJECT_DUMMY".equals(elementType)) iterator.remove(); // if ("BLOCKED_OBJECT_DUMMY".equals(elementType)) iterator.remove();
internal val BytecodePatchContext.timelineFilterExtensionMethod by gettingFirstMethodDeclaratively { internal val timelineFilterExtensionMethodMatch = firstMethodComposite("BLOCKED_OBJECT_DUMMY") {
definingClass { endsWith("/TimelineFilterPatch;") }
opcodes( opcodes(
Opcode.CONST_STRING, // "BLOCKED_OBJECT_DUMMY" Opcode.CONST_STRING, // "BLOCKED_OBJECT_DUMMY"
Opcode.INVOKE_VIRTUAL, // HashSet.add(^) Opcode.INVOKE_VIRTUAL, // HashSet.add(^)
) )
strings("BLOCKED_OBJECT_DUMMY")
custom { _, classDef ->
classDef.endsWith("/TimelineFilterPatch;")
}
} }

View file

@ -1,30 +1,26 @@
package app.revanced.patches.twitter.interaction.downloads package app.revanced.patches.twitter.interaction.downloads
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.opcodes import app.revanced.patcher.opcodes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.buildMediaOptionsSheetMethod by gettingFirstMethodDeclaratively { internal val buildMediaOptionsSheetMethodMatch = firstMethodComposite("mediaEntity", "media_options_sheet") {
opcodes( opcodes(
Opcode.IF_EQ, Opcode.IF_EQ,
Opcode.SGET_OBJECT, Opcode.SGET_OBJECT,
Opcode.GOTO_16, Opcode.GOTO_16,
Opcode.NEW_INSTANCE, Opcode.NEW_INSTANCE,
) )
strings("mediaEntity", "media_options_sheet")
} }
internal val BytecodePatchContext.constructMediaOptionsSheetMethod by gettingFirstMethodDeclaratively { internal val constructMediaOptionsSheetMethodMatch = firstMethodComposite("captionsState") {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
strings("captionsState")
} }
internal val BytecodePatchContext.showDownloadVideoUpsellBottomSheetMethod by gettingFirstMethodDeclaratively { internal val showDownloadVideoUpsellBottomSheetMethodMatch = firstMethodComposite("mediaEntity", "url") {
returnType("Z") returnType("Z")
strings("mediaEntity", "url")
opcodes(Opcode.IF_EQZ) opcodes(Opcode.IF_EQZ)
} }

View file

@ -1,5 +1,6 @@
package app.revanced.patches.twitter.interaction.downloads package app.revanced.patches.twitter.interaction.downloads
import app.revanced.patcher.MatchBuilder
import app.revanced.patcher.extensions.* import app.revanced.patcher.extensions.*
import app.revanced.patcher.patch.creatingBytecodePatch import app.revanced.patcher.patch.creatingBytecodePatch
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -13,13 +14,13 @@ val `Unlock downloads` by creatingBytecodePatch(
compatibleWith("com.twitter.android") compatibleWith("com.twitter.android")
apply { apply {
fun Fingerprint.patch(getRegisterAndIndex: Fingerprint.() -> Pair<Int, Int>) { fun MatchBuilder.patch(getRegisterAndIndex: MatchBuilder.() -> Pair<Int, Int>) {
val (index, register) = getRegisterAndIndex() val (index, register) = getRegisterAndIndex()
method.addInstruction(index, "const/4 v$register, 0x1") method.addInstruction(index, "const/4 v$register, 0x1")
} }
// Allow downloads for non-premium users. // Allow downloads for non-premium users.
showDownloadVideoUpsellBottomSheetMethod.patch { showDownloadVideoUpsellBottomSheetMethodMatch.patch {
val checkIndex = indices.first() val checkIndex = indices.first()
val register = method.getInstruction<OneRegisterInstruction>(checkIndex).registerA val register = method.getInstruction<OneRegisterInstruction>(checkIndex).registerA
@ -27,7 +28,7 @@ val `Unlock downloads` by creatingBytecodePatch(
} }
// Force show the download menu item. // Force show the download menu item.
constructMediaOptionsSheetMethod.patch { constructMediaOptionsSheetMethodMatch.patch {
val showDownloadButtonIndex = method.instructions.lastIndex - 1 val showDownloadButtonIndex = method.instructions.lastIndex - 1
val register = method.getInstruction<TwoRegisterInstruction>(showDownloadButtonIndex).registerA val register = method.getInstruction<TwoRegisterInstruction>(showDownloadButtonIndex).registerA
@ -35,7 +36,7 @@ val `Unlock downloads` by creatingBytecodePatch(
} }
// Make GIFs downloadable. // Make GIFs downloadable.
buildMediaOptionsSheetMethod.let { buildMediaOptionsSheetMethodMatch.let {
it.method.apply { it.method.apply {
val checkMediaTypeIndex = it.indices.first() val checkMediaTypeIndex = it.indices.first()
val checkMediaTypeInstruction = getInstruction<TwoRegisterInstruction>(checkMediaTypeIndex) val checkMediaTypeInstruction = getInstruction<TwoRegisterInstruction>(checkMediaTypeIndex)

View file

@ -1,15 +1,12 @@
package app.revanced.patches.youtube.ad.getpremium package app.revanced.patches.youtube.ad.getpremium
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.getPremiumViewMethod by gettingFirstMethodDeclaratively { internal val getPremiumViewMethodMatch = firstMethodComposite {
name("onMeasure")
definingClass("Lcom/google/android/apps/youtube/app/red/presenter/CompactYpcOfferModuleView;")
accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL) accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("I", "I") parameterTypes("I", "I")
@ -19,8 +16,4 @@ internal val BytecodePatchContext.getPremiumViewMethod by gettingFirstMethodDecl
Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID, Opcode.RETURN_VOID,
) )
custom { method, _ ->
method.name == "onMeasure" &&
method.definingClass == "Lcom/google/android/apps/youtube/app/red/presenter/CompactYpcOfferModuleView;"
}
} }

View file

@ -39,15 +39,15 @@ val hideGetPremiumPatch = bytecodePatch(
SwitchPreference("revanced_hide_get_premium"), SwitchPreference("revanced_hide_get_premium"),
) )
getPremiumViewMethod.apply { getPremiumViewMethodMatch.let {
val startIndex = getPremiumViewMethod.indices.first() val startIndex = it.indices.first()
val measuredWidthRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA val measuredWidthRegister = it.method.getInstruction<TwoRegisterInstruction>(startIndex).registerA
val measuredHeightInstruction = getInstruction<TwoRegisterInstruction>(startIndex + 1) val measuredHeightInstruction = it.method.getInstruction<TwoRegisterInstruction>(startIndex + 1)
val measuredHeightRegister = measuredHeightInstruction.registerA val measuredHeightRegister = measuredHeightInstruction.registerA
val tempRegister = measuredHeightInstruction.registerB val tempRegister = measuredHeightInstruction.registerB
addInstructionsWithLabels( it.method.addInstructionsWithLabels(
startIndex + 2, startIndex + 2,
""" """
# Override the internal measurement of the layout with zero values. # Override the internal measurement of the layout with zero values.

View file

@ -1,18 +1,14 @@
package app.revanced.patches.youtube.interaction.dialog package app.revanced.patches.youtube.interaction.dialog
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.*
import app.revanced.patcher.instructions
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
internal val BytecodePatchContext.createDialogMethod by gettingFirstMethodDeclaratively { internal val createDialogMethodMatch = firstMethodComposite {
returnType("V") returnType("V")
parameterTypes("L", "L", "Ljava/lang/String;") parameterTypes("L", "L", "Ljava/lang/String;")
instructions( instructions(
methodCall(smali = "Landroid/app/AlertDialog\$Builder;->setNegativeButton(ILandroid/content/DialogInterface\$OnClickListener;)Landroid/app/AlertDialog\$Builder;"), method { toString() == $$"Landroid/app/AlertDialog$Builder;->setNegativeButton(ILandroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;" },
methodCall(smali = "Landroid/app/AlertDialog\$Builder;->setOnCancelListener(Landroid/content/DialogInterface\$OnCancelListener;)Landroid/app/AlertDialog\$Builder;"), method { toString() == $$"Landroid/app/AlertDialog$Builder;->setOnCancelListener(Landroid/content/DialogInterface$OnCancelListener;)Landroid/app/AlertDialog$Builder;" },
methodCall(smali = "Landroid/app/AlertDialog\$Builder;->create()Landroid/app/AlertDialog;"), method { toString() == $$"Landroid/app/AlertDialog$Builder;->create()Landroid/app/AlertDialog;" },
methodCall(smali = "Landroid/app/AlertDialog;->show()V"), method { toString() == "Landroid/app/AlertDialog;->show()V" },
) )
} }

View file

@ -41,9 +41,9 @@ val `Remove viewer discretion dialog` by creatingBytecodePatch(
SwitchPreference("revanced_remove_viewer_discretion_dialog"), SwitchPreference("revanced_remove_viewer_discretion_dialog"),
) )
createDialogMethod.let { createDialogMethodMatch.let {
it.method.apply { it.method.apply {
val showDialogIndex = it.indices.last() // TODO val showDialogIndex = it.indices.last()
val dialogRegister = getInstruction<FiveRegisterInstruction>(showDialogIndex).registerC val dialogRegister = getInstruction<FiveRegisterInstruction>(showDialogIndex).registerC
replaceInstructions( replaceInstructions(

View file

@ -34,7 +34,7 @@ val enableSeekbarTappingPatch = bytecodePatch(
) )
// Find the required methods to tap the seekbar. // Find the required methods to tap the seekbar.
val seekbarTappingMethods = onTouchEventHandlerMethod.let { val seekbarTappingMethods = onTouchEventHandlerMethodMatch.let {
fun getReference(index: Int) = it.method.getInstruction<ReferenceInstruction>(index) fun getReference(index: Int) = it.method.getInstruction<ReferenceInstruction>(index)
.reference as MethodReference .reference as MethodReference
@ -44,7 +44,7 @@ val enableSeekbarTappingPatch = bytecodePatch(
) )
} }
seekbarTappingMethod.let { seekbarTappingMethodMatch.let {
val insertIndex = it.indices.last() + 1 val insertIndex = it.indices.last() + 1
it.method.apply { it.method.apply {

View file

@ -2,6 +2,7 @@ package app.revanced.patches.youtube.interaction.seekbar
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
@ -42,16 +43,16 @@ val enableSlideToSeekPatch = bytecodePatch(
// Restore the behaviour to slide to seek. // Restore the behaviour to slide to seek.
val checkIndex = slideToSeekMethod.indices.first() val checkIndex = slideToSeekMethodMatch.indices.first()
val checkReference = slideToSeekMethod.getInstruction(checkIndex) val checkReference = slideToSeekMethodMatch.method.getInstruction(checkIndex)
.getReference<MethodReference>()!! .getReference<MethodReference>()!!
val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->isSlideToSeekDisabled(Z)Z" val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->isSlideToSeekDisabled(Z)Z"
// A/B check method was only called on this class. // A/B check method was only called on this class.
slideToSeekMethod.classDef.methods.forEach { method -> slideToSeekMethodMatch.classDef.methods.forEach { method ->
method.findInstructionIndicesReversed { method.findInstructionIndicesReversed {
opcode == Opcode.INVOKE_VIRTUAL && getReference<MethodReference>() == checkReference opcode == Opcode.INVOKE_VIRTUAL && methodReference == checkReference
}.forEach { index -> }.forEach { index ->
method.apply { method.apply {
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
@ -73,7 +74,7 @@ val enableSlideToSeekPatch = bytecodePatch(
// Disable the double speed seek gesture. // Disable the double speed seek gesture.
if (is_19_17_or_greater) { if (is_19_17_or_greater) {
disableFastForwardGestureMethod.let { disableFastForwardGestureMethodMatch.let {
it.method.apply { it.method.apply {
val targetIndex = it.indices.last() val targetIndex = it.indices.last()
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
@ -88,7 +89,7 @@ val enableSlideToSeekPatch = bytecodePatch(
} }
} }
} else { } else {
disableFastForwardLegacyMethod.let { disableFastForwardLegacyMethodMatch.let {
it.method.apply { it.method.apply {
val insertIndex = it.indices.last() + 1 val insertIndex = it.indices.last() + 1
val targetRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA val targetRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA

View file

@ -40,7 +40,7 @@ internal fun ClassDef.getAllowSwipingUpGestureMethod() = firstMutableMethodDecla
parameterTypes("L") parameterTypes("L")
} }
internal val BytecodePatchContext.disableFastForwardLegacyMethod by gettingFirstMethodDeclaratively { internal val disableFastForwardLegacyMethodMatch = firstMethodComposite {
returnType("Z") returnType("Z")
parameterTypes() parameterTypes()
opcodes(Opcode.MOVE_RESULT) opcodes(Opcode.MOVE_RESULT)
@ -48,7 +48,7 @@ internal val BytecodePatchContext.disableFastForwardLegacyMethod by gettingFirst
literal { 45411330 } literal { 45411330 }
} }
internal val BytecodePatchContext.disableFastForwardGestureMethod by gettingFirstMethodDeclaratively { internal val disableFastForwardGestureMethodMatch = firstMethodComposite {
definingClass { endsWith("/NextGenWatchLayout;") } definingClass { endsWith("/NextGenWatchLayout;") }
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z") returnType("Z")
@ -80,7 +80,7 @@ internal val customTapAndHoldMethodMatch = firstMethodComposite {
} }
} }
internal val BytecodePatchContext.onTouchEventHandlerMethod by gettingFirstMethodDeclaratively { internal val onTouchEventHandlerMethodMatch = firstMethodComposite {
name("onTouchEvent") name("onTouchEvent")
accessFlags(AccessFlags.PUBLIC, AccessFlags.PUBLIC) accessFlags(AccessFlags.PUBLIC, AccessFlags.PUBLIC)
returnType("Z") returnType("Z")
@ -103,7 +103,7 @@ internal val BytecodePatchContext.onTouchEventHandlerMethod by gettingFirstMetho
) )
} }
internal val BytecodePatchContext.seekbarTappingMethod by gettingFirstMethodDeclaratively { internal val seekbarTappingMethodMatch = firstMethodComposite {
name("onTouchEvent") name("onTouchEvent")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z") returnType("Z")
@ -119,7 +119,7 @@ internal val BytecodePatchContext.seekbarTappingMethod by gettingFirstMethodDecl
) )
} }
internal val BytecodePatchContext.slideToSeekMethod by gettingFirstMethodDeclaratively { internal val slideToSeekMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL) accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("Landroid/view/View;", "F") parameterTypes("Landroid/view/View;", "F")

View file

@ -1,6 +1,8 @@
package app.revanced.patches.youtube.interaction.swipecontrols package app.revanced.patches.youtube.interaction.swipecontrols
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.definingClass
import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions import app.revanced.patcher.instructions
import app.revanced.patcher.invoke import app.revanced.patcher.invoke
@ -9,14 +11,12 @@ import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal val BytecodePatchContext.swipeControlsHostActivityMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.swipeControlsHostActivityMethod by gettingFirstMethodDeclaratively {
definingClass(EXTENSION_CLASS_DESCRIPTOR)
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameterTypes() parameterTypes()
custom { method, _ ->
method.definingClass == EXTENSION_CLASS_DESCRIPTOR
}
} }
internal val BytecodePatchContext.swipeChangeVideoMethod by gettingFirstMethodDeclaratively { internal val swipeChangeVideoMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions( instructions(
45631116L(), // Swipe to change fullscreen video feature flag. 45631116L(), // Swipe to change fullscreen video feature flag.

View file

@ -134,9 +134,9 @@ val `Swipe controls` by creatingBytecodePatch(
// region patch to enable/disable swipe to change video. // region patch to enable/disable swipe to change video.
if (is_19_43_or_greater && !is_20_34_or_greater) { if (is_19_43_or_greater && !is_20_34_or_greater) {
swipeChangeVideoMethod.let { swipeChangeVideoMethodMatch.let {
it.method.insertLiteralOverride( it.method.insertLiteralOverride(
it.indices.last(), // TODO it.indices.last(),
"$EXTENSION_CLASS_DESCRIPTOR->allowSwipeChangeVideo(Z)Z", "$EXTENSION_CLASS_DESCRIPTOR->allowSwipeChangeVideo(Z)Z",
) )
} }

View file

@ -1,11 +1,7 @@
package app.revanced.patches.youtube.layout.hide.endscreencards package app.revanced.patches.youtube.layout.hide.endscreencards
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.util.containsLiteralInstruction import app.revanced.util.containsLiteralInstruction
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstruction
@ -14,7 +10,7 @@ import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.FieldReference
internal val BytecodePatchContext.layoutCircleMethod by gettingFirstMethodDeclaratively { internal val layoutCircleMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes() parameterTypes()
returnType("Landroid/view/View;") returnType("Landroid/view/View;")
@ -28,7 +24,7 @@ internal val BytecodePatchContext.layoutCircleMethod by gettingFirstMethodDeclar
literal { layoutCircle } literal { layoutCircle }
} }
internal val BytecodePatchContext.layoutIconMethod by gettingFirstMethodDeclaratively { internal val layoutIconMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes() parameterTypes()
returnType("Landroid/view/View;") returnType("Landroid/view/View;")
@ -41,7 +37,7 @@ internal val BytecodePatchContext.layoutIconMethod by gettingFirstMethodDeclarat
literal { layoutIcon } literal { layoutIcon }
} }
internal val BytecodePatchContext.layoutVideoMethod by gettingFirstMethodDeclaratively { internal val layoutVideoMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC) accessFlags(AccessFlags.PUBLIC)
parameterTypes() parameterTypes()
returnType("Landroid/view/View;") returnType("Landroid/view/View;")
@ -55,16 +51,16 @@ internal val BytecodePatchContext.layoutVideoMethod by gettingFirstMethodDeclara
literal { layoutVideo } literal { layoutVideo }
} }
internal val BytecodePatchContext.showEndscreenCardsMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.showEndscreenCardsMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("L") parameterTypes("L")
custom { method, classDef -> custom {
classDef.methods.count() == 5 && immutableClassDef.methods.count() == 5 &&
method.containsLiteralInstruction(0) && containsLiteralInstruction(0) &&
method.containsLiteralInstruction(5) && containsLiteralInstruction(5) &&
method.containsLiteralInstruction(8) && containsLiteralInstruction(8) &&
method.indexOfFirstInstruction { indexOfFirstInstruction {
val reference = getReference<FieldReference>() val reference = getReference<FieldReference>()
reference?.type == "Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;" reference?.type == "Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"
} >= 0 } >= 0

View file

@ -70,12 +70,12 @@ val `Hide end screen cards` by creatingBytecodePatch(
apply { apply {
listOf( listOf(
layoutCircleMethod, layoutCircleMethodMatch,
layoutIconMethod, layoutIconMethodMatch,
layoutVideoMethod, layoutVideoMethodMatch,
).forEach { fingerprint -> ).forEach { match ->
fingerprint.method.apply { match.method.apply {
val insertIndex = fingerprint.indices.last() + 1 // TODO val insertIndex = match.indices.last() + 1
val viewRegister = getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA val viewRegister = getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA
addInstruction( addInstruction(

View file

@ -1,30 +1,26 @@
package app.revanced.patches.youtube.layout.hide.endscreensuggestion package app.revanced.patches.youtube.layout.hide.endscreensuggestion
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.opcodes import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.autoNavConstructorMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.autoNavConstructorMethod by gettingFirstMethodDeclaratively("main_app_autonav") {
returnType("V") returnType("V")
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
strings("main_app_autonav")
} }
internal val BytecodePatchContext.autoNavStatusMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getAutoNavStatusMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z") returnType("Z")
parameterTypes() parameterTypes()
} }
internal val BytecodePatchContext.removeOnLayoutChangeListenerMethod by gettingFirstMethodDeclaratively { internal val removeOnLayoutChangeListenerMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes() parameterTypes()
@ -33,11 +29,10 @@ internal val BytecodePatchContext.removeOnLayoutChangeListenerMethod by gettingF
Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_VIRTUAL,
) )
// This is the only reference present in the entire smali. // This is the only reference present in the entire smali.
custom { method, _ -> custom {
method.indexOfFirstInstruction { instructions.anyInstruction {
val reference = getReference<MethodReference>() val reference = methodReference
reference?.name == "removeOnLayoutChangeListener" && reference?.name == "removeOnLayoutChangeListener" && reference.definingClass.endsWith("/YouTubePlayerOverlaysLayout;")
reference.definingClass.endsWith("/YouTubePlayerOverlaysLayout;") }
} >= 0
} }
} }

View file

@ -3,18 +3,18 @@ package app.revanced.patches.youtube.layout.hide.endscreensuggestion
import app.revanced.patcher.extensions.ExternalLabel import app.revanced.patcher.extensions.ExternalLabel
import app.revanced.patcher.extensions.addInstructionsWithLabels import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.creatingBytecodePatch import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow
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.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/HideEndScreenSuggestedVideoPatch;" "Lapp/revanced/extension/youtube/patches/HideEndScreenSuggestedVideoPatch;"
@ -44,20 +44,19 @@ val `Hide end screen suggested video` by creatingBytecodePatch(
SwitchPreference("revanced_end_screen_suggested_video"), SwitchPreference("revanced_end_screen_suggested_video"),
) )
removeOnLayoutChangeListenerMethod.let { removeOnLayoutChangeListenerMethodMatch.let {
val endScreenMethod = navigate(it.immutableMethod).to(it.indices.last()).stop() // TODO val endScreenMethod = navigate(it.immutableMethod).to(it.indices.last()).stop() // TODO
endScreenMethod.apply { endScreenMethod.apply {
val autoNavStatusMethodName = autoNavStatusMethod.match( val autoNavStatusMethodName = autoNavConstructorMethod.immutableClassDef.getAutoNavStatusMethod().name
autoNavConstructorMethod.classDef,
).immutableMethod.name
val invokeIndex = indexOfFirstInstructionOrThrow { val invokeIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>() val reference = methodReference
reference?.name == autoNavStatusMethodName && reference?.name == autoNavStatusMethodName &&
reference.returnType == "Z" && reference.returnType == "Z" &&
reference.parameterTypes.isEmpty() reference.parameterTypes.isEmpty()
} }
val iGetObjectIndex = indexOfFirstInstructionReversedOrThrow(invokeIndex, Opcode.IGET_OBJECT) val iGetObjectIndex = indexOfFirstInstructionReversedOrThrow(invokeIndex, Opcode.IGET_OBJECT)
val invokeReference = getInstruction<ReferenceInstruction>(invokeIndex).reference val invokeReference = getInstruction<ReferenceInstruction>(invokeIndex).reference
val iGetObjectReference = getInstruction<ReferenceInstruction>(iGetObjectIndex).reference val iGetObjectReference = getInstruction<ReferenceInstruction>(iGetObjectIndex).reference

View file

@ -1,54 +1,45 @@
package app.revanced.patches.youtube.layout.hide.general package app.revanced.patches.youtube.layout.hide.general
import app.revanced.patcher.StringComparisonType import app.revanced.patcher.*
import app.revanced.patcher.accessFlags
import app.revanced.patcher.after
import app.revanced.patcher.afterAtMost
import app.revanced.patcher.checkCast
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.mapping.ResourceType import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.youtube.layout.searchbar.wideSearchbarLayoutMethod import app.revanced.patches.youtube.layout.searchbar.wideSearchbarLayoutMethod
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
/** /**
* 20.26+ * 20.26+
*/ */
internal val BytecodePatchContext.hideShowMoreButtonMethod by gettingFirstMethodDeclaratively { internal val hideShowMoreButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL, AccessFlags.SYNTHETIC) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL, AccessFlags.SYNTHETIC)
returnType("V") returnType("V")
parameterTypes("L", "Ljava/lang/Object;") parameterTypes("L", "Ljava/lang/Object;")
instructions( instructions(
ResourceType.LAYOUT("expand_button_down"), ResourceType.LAYOUT("expand_button_down"),
methodCall(smali = "Landroid/view/LayoutInflater;->inflate(ILandroid/view/ViewGroup;Z)Landroid/view/View;"), method { toString() == "Landroid/view/LayoutInflater;->inflate(ILandroid/view/ViewGroup;Z)Landroid/view/View;" },
after(Opcode.MOVE_RESULT_OBJECT()), after(Opcode.MOVE_RESULT_OBJECT()),
) )
} }
internal val BytecodePatchContext.hideShowMoreLegacyButtonMethod by gettingFirstMethodDeclaratively { internal val hideShowMoreLegacyButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions( instructions(
ResourceType.LAYOUT("expand_button_down"), ResourceType.LAYOUT("expand_button_down"),
methodCall(smali = "Landroid/view/View;->inflate(Landroid/content/Context;ILandroid/view/ViewGroup;)Landroid/view/View;"), method { toString() == "Landroid/view/View;->inflate(Landroid/content/Context;ILandroid/view/ViewGroup;)Landroid/view/View;" },
Opcode.MOVE_RESULT_OBJECT(), Opcode.MOVE_RESULT_OBJECT(),
) )
} }
internal val BytecodePatchContext.parseElementFromBufferMethod by gettingFirstMethodDeclaratively { internal val parseElementFromBufferMethodMatch = firstMethodComposite {
parameterTypes("L", "L", "[B", "L", "L") parameterTypes("L", "L", "[B", "L", "L")
instructions( instructions(
Opcode.IGET_OBJECT(), Opcode.IGET_OBJECT(),
// IGET_BOOLEAN // 20.07+ // IGET_BOOLEAN // 20.07+
afterAtMost(1, Opcode.INVOKE_INTERFACE()), afterAtMost(1, Opcode.INVOKE_INTERFACE()),
after(Opcode.MOVE_RESULT_OBJECT()), after(Opcode.MOVE_RESULT_OBJECT()),
"Failed to parse Element"(String::startsWith),
addString("Failed to parse Element", comparison = StringComparisonType.STARTS_WITH),
) )
} }
@ -60,7 +51,8 @@ internal val BytecodePatchContext.playerOverlayMethod by gettingFirstMethodDecla
) )
} }
internal val BytecodePatchContext.showWatermarkMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getShowWatermarkMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("L", "L") parameterTypes("L", "L")
@ -69,16 +61,14 @@ internal val BytecodePatchContext.showWatermarkMethod by gettingFirstMethodDecla
/** /**
* Matches same method as [wideSearchbarLayoutMethod]. * Matches same method as [wideSearchbarLayoutMethod].
*/ */
internal val BytecodePatchContext.yoodlesImageViewMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.yoodlesImageViewMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;") returnType("Landroid/view/View;")
parameterTypes("L", "L") parameterTypes("L", "L")
instructions( instructions(ResourceType.ID("youtube_logo"))
ResourceType.ID("youtube_logo"),
)
} }
internal val BytecodePatchContext.crowdfundingBoxMethod by gettingFirstMethodDeclaratively { internal val crowdfundingBoxMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
opcodes( opcodes(
Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_VIRTUAL,
@ -88,7 +78,7 @@ internal val BytecodePatchContext.crowdfundingBoxMethod by gettingFirstMethodDec
literal { crowdfundingBoxId } literal { crowdfundingBoxId }
} }
internal val BytecodePatchContext.albumCardsMethod by gettingFirstMethodDeclaratively { internal val albumCardsMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
opcodes( opcodes(
Opcode.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT_OBJECT,
@ -101,7 +91,7 @@ internal val BytecodePatchContext.albumCardsMethod by gettingFirstMethodDeclarat
literal { albumCardId } literal { albumCardId }
} }
internal val BytecodePatchContext.filterBarHeightMethod by gettingFirstMethodDeclaratively { internal val filterBarHeightMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
opcodes( opcodes(
Opcode.CONST, Opcode.CONST,
@ -112,7 +102,7 @@ internal val BytecodePatchContext.filterBarHeightMethod by gettingFirstMethodDec
literal { filterBarHeightId } literal { filterBarHeightId }
} }
internal val BytecodePatchContext.relatedChipCloudMethod by gettingFirstMethodDeclaratively { internal val relatedChipCloudMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
opcodes( opcodes(
Opcode.CONST, Opcode.CONST,
@ -122,7 +112,7 @@ internal val BytecodePatchContext.relatedChipCloudMethod by gettingFirstMethodDe
literal { relatedChipCloudMarginId } literal { relatedChipCloudMarginId }
} }
internal val BytecodePatchContext.searchResultsChipBarMethod by gettingFirstMethodDeclaratively { internal val searchResultsChipBarMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
opcodes( opcodes(
Opcode.CONST, Opcode.CONST,
@ -134,27 +124,25 @@ internal val BytecodePatchContext.searchResultsChipBarMethod by gettingFirstMeth
literal { barContainerHeightId } literal { barContainerHeightId }
} }
internal val BytecodePatchContext.showFloatingMicrophoneButtonMethod by gettingFirstMethodDeclaratively { internal val showFloatingMicrophoneButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes() parameterTypes()
instructions( instructions(
ResourceType.ID("fab"), ResourceType.ID("fab"),
checkCast("/FloatingActionButton;", afterAtMost(10)), afterAtMost(10, allOf(Opcode.CHECK_CAST(), "/FloatingActionButton;"())),
afterAtMost(15, Opcode.IGET_BOOLEAN()), afterAtMost(15, Opcode.IGET_BOOLEAN()),
) )
} }
internal val BytecodePatchContext.hideViewCountMethod by gettingFirstMethodDeclaratively { internal val hideViewCountMethodMatch = firstMethodComposite(
"Has attachmentRuns but drawableRequester is missing.",
) {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returnType("Ljava/lang/CharSequence;") returnType("Ljava/lang/CharSequence;")
opcodes( opcodes(
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
Opcode.CONST_STRING, Opcode.CONST_STRING,
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
) )
strings(
"Has attachmentRuns but drawableRequester is missing.",
)
} }

View file

@ -1,6 +1,8 @@
package app.revanced.patches.youtube.layout.hide.general package app.revanced.patches.youtube.layout.hide.general
import app.revanced.patcher.MatchBuilder
import app.revanced.patcher.extensions.* import app.revanced.patcher.extensions.*
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.creatingBytecodePatch import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
@ -41,30 +43,15 @@ private val hideLayoutComponentsResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch) dependsOn(resourceMappingPatch)
apply { apply {
albumCardId = getResourceId( albumCardId = ResourceType.LAYOUT["album_card"]
ResourceType.LAYOUT,
"album_card",
)
crowdfundingBoxId = getResourceId( crowdfundingBoxId = ResourceType.LAYOUT["donation_companion"]
ResourceType.LAYOUT,
"donation_companion",
)
relatedChipCloudMarginId = getResourceId( relatedChipCloudMarginId = ResourceType.LAYOUT["related_chip_cloud_reduced_margins"]
ResourceType.LAYOUT,
"related_chip_cloud_reduced_margins",
)
filterBarHeightId = getResourceId( filterBarHeightId = ResourceType.DIMEN["filter_bar_height"]
ResourceType.DIMEN,
"filter_bar_height",
)
barContainerHeightId = getResourceId( barContainerHeightId = ResourceType.DIMEN["bar_container_height"]
ResourceType.DIMEN,
"bar_container_height",
)
} }
} }
@ -240,38 +227,40 @@ val `Hide layout components` by creatingBytecodePatch(
// region Mix playlists // region Mix playlists
parseElementFromBufferMethod.apply { parseElementFromBufferMethodMatch.let {
val startIndex = parseElementFromBufferMethod.indices.first() it.method.apply {
val insertIndex = startIndex + 1 val startIndex = it.indices.first()
val insertIndex = startIndex + 1
val byteArrayParameter = "p3" val byteArrayParameter = "p3"
val conversionContextRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA val conversionContextRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA
val returnEmptyComponentInstruction = instructions.last { it.opcode == Opcode.INVOKE_STATIC } val returnEmptyComponentInstruction = instructions.last { it.opcode == Opcode.INVOKE_STATIC }
val returnEmptyComponentRegister = (returnEmptyComponentInstruction as FiveRegisterInstruction).registerC val returnEmptyComponentRegister =
val freeRegister = findFreeRegister(insertIndex, conversionContextRegister, returnEmptyComponentRegister) (returnEmptyComponentInstruction as FiveRegisterInstruction).registerC
val freeRegister =
findFreeRegister(insertIndex, conversionContextRegister, returnEmptyComponentRegister)
addInstructionsWithLabels( addInstructionsWithLabels(
insertIndex, insertIndex,
""" """
invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
move-result v$freeRegister move-result v$freeRegister
if-eqz v$freeRegister, :show if-eqz v$freeRegister, :show
move-object v$returnEmptyComponentRegister, p1 # Required for 19.47 move-object v$returnEmptyComponentRegister, p1 # Required for 19.47
goto :return_empty_component goto :return_empty_component
:show :show
nop nop
""", """,
ExternalLabel("return_empty_component", returnEmptyComponentInstruction), ExternalLabel("return_empty_component", returnEmptyComponentInstruction),
) )
}
} }
// endregion // endregion
// region Watermark (legacy code for old versions of YouTube) // region Watermark (legacy code for old versions of YouTube)
showWatermarkMethod.match( playerOverlayMethod.immutableClassDef.getShowWatermarkMethod().apply {
playerOverlayMethod.immutableClassDef,
).method.apply {
val index = implementation!!.instructions.size - 5 val index = implementation!!.instructions.size - 5
removeInstruction(index) removeInstruction(index)
@ -288,7 +277,7 @@ val `Hide layout components` by creatingBytecodePatch(
// region Show more button // region Show more button
(if (is_20_26_or_greater) hideShowMoreButtonMethod else hideShowMoreLegacyButtonMethod).let { (if (is_20_26_or_greater) hideShowMoreButtonMethodMatch else hideShowMoreLegacyButtonMethodMatch).let {
it.method.apply { it.method.apply {
val moveRegisterIndex = it.indices.last() val moveRegisterIndex = it.indices.last()
val viewRegister = getInstruction<OneRegisterInstruction>(moveRegisterIndex).registerA val viewRegister = getInstruction<OneRegisterInstruction>(moveRegisterIndex).registerA
@ -305,7 +294,7 @@ val `Hide layout components` by creatingBytecodePatch(
// endregion // endregion
// region crowdfunding box // region crowdfunding box
crowdfundingBoxMethod.let { crowdfundingBoxMethodMatch.let {
it.method.apply { it.method.apply {
val insertIndex = it.indices.last() val insertIndex = it.indices.last()
val objectRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA val objectRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
@ -322,7 +311,7 @@ val `Hide layout components` by creatingBytecodePatch(
// region hide album cards // region hide album cards
albumCardsMethod.let { albumCardsMethodMatch.let {
it.method.apply { it.method.apply {
val checkCastAnchorIndex = it.indices.last() val checkCastAnchorIndex = it.indices.last()
val insertIndex = checkCastAnchorIndex + 1 val insertIndex = checkCastAnchorIndex + 1
@ -340,7 +329,7 @@ val `Hide layout components` by creatingBytecodePatch(
// region hide floating microphone // region hide floating microphone
showFloatingMicrophoneButtonMethod.let { showFloatingMicrophoneButtonMethodMatch.let {
it.method.apply { it.method.apply {
val index = it.indices.last() val index = it.indices.last()
val register = getInstruction<TwoRegisterInstruction>(index).registerA val register = getInstruction<TwoRegisterInstruction>(index).registerA
@ -378,8 +367,8 @@ val `Hide layout components` by creatingBytecodePatch(
// region hide view count // region hide view count
hideViewCountMethod.apply { hideViewCountMethodMatch.method.apply {
val startIndex = hideViewCountMethod.patternMatch.startIndex val startIndex = hideViewCountMethodMatch.indices.first()
var returnStringRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA var returnStringRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
// Find the instruction where the text dimension is retrieved. // Find the instruction where the text dimension is retrieved.
@ -414,11 +403,11 @@ val `Hide layout components` by creatingBytecodePatch(
* Patch a [Method] with a given [instructions]. * Patch a [Method] with a given [instructions].
* *
* @param RegisterInstruction The type of instruction to get the register from. * @param RegisterInstruction The type of instruction to get the register from.
* @param insertIndexOffset The offset to add to the end index of the [Match.patternMatch]. * @param insertIndexOffset The offset to add to the end index of the [MatchBuilder.indices].
* @param hookRegisterOffset The offset to add to the register of the hook. * @param hookRegisterOffset The offset to add to the register of the hook.
* @param instructions The instructions to add with the register as a parameter. * @param instructions The instructions to add with the register as a parameter.
*/ */
fun <RegisterInstruction : OneRegisterInstruction> Fingerprint.patch( fun <RegisterInstruction : OneRegisterInstruction> MatchBuilder.patch(
insertIndexOffset: Int = 0, insertIndexOffset: Int = 0,
hookRegisterOffset: Int = 0, hookRegisterOffset: Int = 0,
instructions: (Int) -> String, instructions: (Int) -> String,
@ -430,21 +419,21 @@ val `Hide layout components` by creatingBytecodePatch(
addInstructions(insertIndex, instructions(register)) addInstructions(insertIndex, instructions(register))
} }
filterBarHeightMethod.patch<TwoRegisterInstruction> { register -> filterBarHeightMethodMatch.patch<TwoRegisterInstruction> { register ->
""" """
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInFeed(I)I invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInFeed(I)I
move-result v$register move-result v$register
""" """
} }
searchResultsChipBarMethod.patch<OneRegisterInstruction>(-1, -2) { register -> searchResultsChipBarMethodMatch.patch<OneRegisterInstruction>(-1, -2) { register ->
""" """
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInSearch(I)I invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInSearch(I)I
move-result v$register move-result v$register
""" """
} }
relatedChipCloudMethod.patch<OneRegisterInstruction>(1) { register -> relatedChipCloudMethodMatch.patch<OneRegisterInstruction>(1) { register ->
"invoke-static { v$register }, " + "invoke-static { v$register }, " +
"$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V" "$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V"
} }

View file

@ -1,24 +1,18 @@
package app.revanced.patches.youtube.layout.hide.infocards package app.revanced.patches.youtube.layout.hide.infocards
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.util.literal import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.infocardsIncognitoMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getInfocardsIncognitoMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Ljava/lang/Boolean;") returnType("Ljava/lang/Boolean;")
parameterTypes("L", "J") parameterTypes("L", "J")
instructions( instructions("vibrator"())
"vibrator"(),
)
} }
internal val BytecodePatchContext.infocardsIncognitoParentMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.infocardsIncognitoParentMethod by gettingFirstMethodDeclaratively {
@ -29,12 +23,12 @@ internal val BytecodePatchContext.infocardsIncognitoParentMethod by gettingFirst
) )
} }
internal val BytecodePatchContext.infocardsMethodCallMethod by gettingFirstMethodDeclaratively { internal val infocardsMethodCallMethodMatch =
opcodes( firstMethodComposite("Missing ControlsOverlayPresenter for InfoCards to work.") {
Opcode.INVOKE_VIRTUAL, opcodes(
Opcode.IGET_OBJECT, Opcode.INVOKE_VIRTUAL,
Opcode.INVOKE_INTERFACE, Opcode.IGET_OBJECT,
) Opcode.INVOKE_INTERFACE,
strings("Missing ControlsOverlayPresenter for InfoCards to work.") )
literal { drawerResourceId } literal { drawerResourceId }
} }

View file

@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.ExternalLabel
import app.revanced.patcher.extensions.addInstruction import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructionsWithLabels import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.creatingBytecodePatch import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
@ -27,10 +28,7 @@ private val hideInfocardsResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch) dependsOn(resourceMappingPatch)
apply { apply {
drawerResourceId = getResourceId( drawerResourceId = ResourceType.ID["info_cards_drawer_header"]
ResourceType.ID,
"info_cards_drawer_header",
)
} }
} }
@ -63,7 +61,7 @@ val `Hide info cards` by creatingBytecodePatch(
) )
// Edit: This old non litho code may be obsolete and no longer used by any supported versions. // Edit: This old non litho code may be obsolete and no longer used by any supported versions.
infocardsIncognitoMethod.match(infocardsIncognitoParentMethod.immutableClassDef).method.apply { infocardsIncognitoParentMethod.immutableClassDef.getInfocardsIncognitoMethod().apply {
val invokeInstructionIndex = implementation!!.instructions.indexOfFirst { val invokeInstructionIndex = implementation!!.instructions.indexOfFirst {
it.opcode.ordinal == Opcode.INVOKE_VIRTUAL.ordinal && it.opcode.ordinal == Opcode.INVOKE_VIRTUAL.ordinal &&
((it as ReferenceInstruction).reference.toString() == "Landroid/view/View;->setVisibility(I)V") ((it as ReferenceInstruction).reference.toString() == "Landroid/view/View;->setVisibility(I)V")
@ -77,7 +75,7 @@ val `Hide info cards` by creatingBytecodePatch(
} }
// Edit: This old non litho code may be obsolete and no longer used by any supported versions. // Edit: This old non litho code may be obsolete and no longer used by any supported versions.
infocardsMethodCallMethod.let { infocardsMethodCallMethodMatch.let {
val invokeInterfaceIndex = it.indices.last() val invokeInterfaceIndex = it.indices.last()
it.method.apply { it.method.apply {
val register = implementation!!.registerCount - 1 val register = implementation!!.registerCount - 1

View file

@ -11,7 +11,7 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.rollingNumberTextViewAnimationUpdateMethod import app.revanced.patches.youtube.shared.rollingNumberTextViewAnimationUpdateMethodMatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
@ -45,7 +45,7 @@ val `Disable rolling number animations` by creatingBytecodePatch(
// Animations are disabled by preventing an Image from being applied to the text span, // Animations are disabled by preventing an Image from being applied to the text span,
// which prevents the animations from appearing. // which prevents the animations from appearing.
rollingNumberTextViewAnimationUpdateMethod.let { rollingNumberTextViewAnimationUpdateMethodMatch.let {
val blockStartIndex = it.indices.first() val blockStartIndex = it.indices.first()
val blockEndIndex = it.indices.last() + 1 val blockEndIndex = it.indices.last() + 1
it.method.apply { it.method.apply {

View file

@ -2,19 +2,12 @@
package app.revanced.patches.youtube.layout.miniplayer package app.revanced.patches.youtube.layout.miniplayer
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.afterAtMost
import app.revanced.patcher.checkCast
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.mapping.ResourceType import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
internal const val MINIPLAYER_MODERN_FEATURE_KEY = 45622882L internal const val MINIPLAYER_MODERN_FEATURE_KEY = 45622882L
@ -28,7 +21,7 @@ internal const val MINIPLAYER_INITIAL_SIZE_FEATURE_KEY = 45640023L
internal const val MINIPLAYER_DISABLED_FEATURE_KEY = 45657015L internal const val MINIPLAYER_DISABLED_FEATURE_KEY = 45657015L
internal const val MINIPLAYER_ANIMATED_EXPAND_FEATURE_KEY = 45644360L internal const val MINIPLAYER_ANIMATED_EXPAND_FEATURE_KEY = 45644360L
internal val BytecodePatchContext.miniplayerModernConstructorMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.miniplayerModernConstructorMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions( instructions(
45623000L(), // Magic number found in the constructor. 45623000L(), // Magic number found in the constructor.
@ -56,7 +49,8 @@ internal val BytecodePatchContext.miniplayerModernViewParentMethod by gettingFir
/** /**
* Matches using the class found in [miniplayerModernViewParentMethod]. * Matches using the class found in [miniplayerModernViewParentMethod].
*/ */
internal val BytecodePatchContext.miniplayerModernAddViewListenerMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getMiniplayerModernAddViewListenerMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("Landroid/view/View;") parameterTypes("Landroid/view/View;")
@ -65,33 +59,34 @@ internal val BytecodePatchContext.miniplayerModernAddViewListenerMethod by getti
/** /**
* Matches using the class found in [miniplayerModernViewParentMethod]. * Matches using the class found in [miniplayerModernViewParentMethod].
*/ */
internal val BytecodePatchContext.miniplayerModernCloseButtonMethod by gettingFirstMethodDeclaratively { internal val miniplayerModernCloseButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes() parameterTypes()
instructions( instructions(
ResourceType.ID("modern_miniplayer_close"), ResourceType.ID("modern_miniplayer_close"),
checkCast("Landroid/widget/ImageView;"), allOf(Opcode.CHECK_CAST(), "Landroid/widget/ImageView;"()),
) )
} }
/** /**
* Matches using the class found in [miniplayerModernViewParentMethod]. * Matches using the class found in [miniplayerModernViewParentMethod].
*/ */
internal val BytecodePatchContext.miniplayerModernExpandButtonMethod by gettingFirstMethodDeclaratively { internal val miniplayerModernExpandButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes() parameterTypes()
instructions( instructions(
ResourceType.ID("modern_miniplayer_expand"), ResourceType.ID("modern_miniplayer_expand"),
checkCast("Landroid/widget/ImageView;"), allOf(Opcode.CHECK_CAST(), "Landroid/widget/ImageView;"()),
) )
} }
/** /**
* Matches using the class found in [miniplayerModernViewParentMethod]. * Matches using the class found in [miniplayerModernViewParentMethod].
*/ */
internal val BytecodePatchContext.miniplayerModernExpandCloseDrawablesMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getMiniplayerModernExpandCloseDrawablesMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("L") parameterTypes("L")
@ -103,7 +98,7 @@ internal val BytecodePatchContext.miniplayerModernExpandCloseDrawablesMethod by
/** /**
* Matches using the class found in [miniplayerModernViewParentMethod]. * Matches using the class found in [miniplayerModernViewParentMethod].
*/ */
internal val BytecodePatchContext.miniplayerModernForwardButtonMethod by gettingFirstMethodDeclaratively { internal val miniplayerModernForwardButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes() parameterTypes()
@ -113,7 +108,7 @@ internal val BytecodePatchContext.miniplayerModernForwardButtonMethod by getting
) )
} }
internal val BytecodePatchContext.miniplayerModernOverlayViewMethod by gettingFirstMethodDeclaratively { internal val miniplayerModernOverlayViewMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes() parameterTypes()
instructions( instructions(
@ -125,7 +120,7 @@ internal val BytecodePatchContext.miniplayerModernOverlayViewMethod by gettingFi
/** /**
* Matches using the class found in [miniplayerModernViewParentMethod]. * Matches using the class found in [miniplayerModernViewParentMethod].
*/ */
internal val BytecodePatchContext.miniplayerModernRewindButtonMethod by gettingFirstMethodDeclaratively { internal val miniplayerModernRewindButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes() parameterTypes()
@ -138,7 +133,7 @@ internal val BytecodePatchContext.miniplayerModernRewindButtonMethod by gettingF
/** /**
* Matches using the class found in [miniplayerModernViewParentMethod]. * Matches using the class found in [miniplayerModernViewParentMethod].
*/ */
internal val BytecodePatchContext.miniplayerModernActionButtonMethod by gettingFirstMethodDeclaratively { internal val miniplayerModernActionButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes() parameterTypes()
@ -148,7 +143,7 @@ internal val BytecodePatchContext.miniplayerModernActionButtonMethod by gettingF
) )
} }
internal val BytecodePatchContext.miniplayerMinimumSizeMethod by gettingFirstMethodDeclaratively { internal val miniplayerMinimumSizeMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions( instructions(
ResourceType.DIMEN("miniplayer_max_size"), ResourceType.DIMEN("miniplayer_max_size"),
@ -157,20 +152,20 @@ internal val BytecodePatchContext.miniplayerMinimumSizeMethod by gettingFirstMet
) )
} }
internal val BytecodePatchContext.miniplayerOverrideMethod by gettingFirstMethodDeclaratively { internal val miniplayerOverrideMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
instructions( instructions(
"appName"(), "appName"(),
methodCall( afterAtMost(
parameters = listOf("Landroid/content/Context;"), 10,
returnType = "Z", method { parameterTypes.count() == 1 && parameterTypes.first() == "Landroid/content/Context;" && returnType == "Z" },
afterAtMost(10),
), ),
) )
} }
internal val BytecodePatchContext.miniplayerOverrideNoContextMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getMiniplayerOverrideNoContextMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL) accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
returnType("Z") returnType("Z")
opcodes( opcodes(
@ -181,7 +176,7 @@ internal val BytecodePatchContext.miniplayerOverrideNoContextMethod by gettingFi
/** /**
* 20.36 and lower. Codes appears to be removed in 20.37+ * 20.36 and lower. Codes appears to be removed in 20.37+
*/ */
internal val BytecodePatchContext.miniplayerResponseModelSizeCheckMethod by gettingFirstMethodDeclaratively { internal val miniplayerResponseModelSizeCheckMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes("Ljava/lang/Object;", "Ljava/lang/Object;") parameterTypes("Ljava/lang/Object;", "Ljava/lang/Object;")
@ -195,7 +190,7 @@ internal val BytecodePatchContext.miniplayerResponseModelSizeCheckMethod by gett
) )
} }
internal val BytecodePatchContext.miniplayerOnCloseHandlerMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.miniplayerOnCloseHandlerMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z") returnType("Z")
instructions( instructions(
@ -207,12 +202,10 @@ internal const val YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME =
"Lcom/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout;" "Lcom/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout;"
internal val BytecodePatchContext.playerOverlaysLayoutMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.playerOverlaysLayoutMethod by gettingFirstMethodDeclaratively {
custom { method, _ -> definingClass(YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME)
method.definingClass == YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME
}
} }
internal val BytecodePatchContext.miniplayerSetIconsMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.miniplayerSetIconsMethod by gettingFirstMutableMethodDeclaratively {
returnType("V") returnType("V")
parameterTypes("I", "Ljava/lang/Runnable;") parameterTypes("I", "Ljava/lang/Runnable;")
instructions( instructions(

View file

@ -2,10 +2,12 @@
package app.revanced.patches.youtube.layout.miniplayer package app.revanced.patches.youtube.layout.miniplayer
import app.revanced.patcher.classDef
import app.revanced.patcher.extensions.addInstruction import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.creatingBytecodePatch import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
@ -46,22 +48,12 @@ private val miniplayerResourcePatch = resourcePatch {
apply { apply {
// Resource id is not used during patching, but is used by extension. // Resource id is not used during patching, but is used by extension.
// Verify the resource is present while patching. // Verify the resource is present while patching.
getResourceId( ResourceType.ID["modern_miniplayer_subtitle_text"]
ResourceType.ID,
"modern_miniplayer_subtitle_text",
)
// Only required for exactly 19.16 // Only required for exactly 19.16
if (!is_19_17_or_greater) { if (!is_19_17_or_greater) {
ytOutlinePictureInPictureWhite24 = getResourceId( ytOutlinePictureInPictureWhite24 = ResourceType.DRAWABLE["yt_outline_picture_in_picture_white_24"]
ResourceType.DRAWABLE, ytOutlineXWhite24 = ResourceType.DRAWABLE["yt_outline_x_white_24"]
"yt_outline_picture_in_picture_white_24",
)
ytOutlineXWhite24 = getResourceId(
ResourceType.DRAWABLE,
"yt_outline_x_white_24",
)
} }
} }
} }
@ -189,31 +181,29 @@ val Miniplayer by creatingBytecodePatch(
insertMiniplayerBooleanOverride(index, "getModernMiniplayerOverride") insertMiniplayerBooleanOverride(index, "getModernMiniplayerOverride")
} }
fun Fingerprint.insertMiniplayerFeatureFlagBooleanOverride( fun MutableMethod.insertMiniplayerFeatureFlagBooleanOverride(
literal: Long, literal: Long,
extensionMethod: String, extensionMethod: String,
) = method.insertLiteralOverride( ) = insertLiteralOverride(
literal, literal,
"$EXTENSION_CLASS_DESCRIPTOR->$extensionMethod(Z)Z", "$EXTENSION_CLASS_DESCRIPTOR->$extensionMethod(Z)Z",
) )
fun Fingerprint.insertMiniplayerFeatureFlagFloatOverride( fun MutableMethod.insertMiniplayerFeatureFlagFloatOverride(
literal: Long, literal: Long,
extensionMethod: String, extensionMethod: String,
) { ) = apply {
method.apply { val literalIndex = indexOfFirstLiteralInstructionOrThrow(literal)
val literalIndex = indexOfFirstLiteralInstructionOrThrow(literal) val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.DOUBLE_TO_FLOAT)
val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.DOUBLE_TO_FLOAT) val register = getInstruction<OneRegisterInstruction>(targetIndex).registerA
val register = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstructions( addInstructions(
targetIndex + 1, targetIndex + 1,
""" """
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$extensionMethod(F)F invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$extensionMethod(F)F
move-result v$register move-result v$register
""", """,
) )
}
} }
/** /**
@ -235,20 +225,16 @@ val Miniplayer by creatingBytecodePatch(
// Parts of the YT code is removed in 20.37+ and the legacy player no longer works. // Parts of the YT code is removed in 20.37+ and the legacy player no longer works.
if (!is_20_37_or_greater) { if (!is_20_37_or_greater) {
miniplayerOverrideNoContextMethod.match( miniplayerDimensionsCalculatorParentMethod.immutableClassDef.getMiniplayerOverrideNoContextMethod().apply {
miniplayerDimensionsCalculatorParentMethod.immutableClassDef,
).method.apply {
findReturnIndicesReversed().forEach { index -> findReturnIndicesReversed().forEach { index ->
insertLegacyTabletMiniplayerOverride( insertLegacyTabletMiniplayerOverride(index)
index,
)
} }
} }
// endregion // endregion
// region Legacy tablet miniplayer hooks. // region Legacy tablet miniplayer hooks.
miniplayerOverrideMethod.let { miniplayerOverrideMethodMatch.let {
val appNameStringIndex = it.indices.last() val appNameStringIndex = it.indices.last()
navigate(it.immutableMethod).to(appNameStringIndex).stop().apply { navigate(it.immutableMethod).to(appNameStringIndex).stop().apply {
findReturnIndicesReversed().forEach { index -> findReturnIndicesReversed().forEach { index ->
@ -259,7 +245,7 @@ val Miniplayer by creatingBytecodePatch(
} }
} }
miniplayerResponseModelSizeCheckMethod.let { miniplayerResponseModelSizeCheckMethodMatch.let {
it.method.insertLegacyTabletMiniplayerOverride(it.indices.last()) it.method.insertLegacyTabletMiniplayerOverride(it.indices.last())
} }
} }
@ -324,7 +310,7 @@ val Miniplayer by creatingBytecodePatch(
} }
// Override a minimum size constant. // Override a minimum size constant.
miniplayerMinimumSizeMethod.let { miniplayerMinimumSizeMethodMatch.let {
it.method.apply { it.method.apply {
val index = it.indices[1] val index = it.indices[1]
val register = getInstruction<OneRegisterInstruction>(index).registerA val register = getInstruction<OneRegisterInstruction>(index).registerA
@ -367,9 +353,7 @@ val Miniplayer by creatingBytecodePatch(
// YT fixed this mistake in 19.17. // YT fixed this mistake in 19.17.
// Fix this, by swapping the drawable resource values with each other. // Fix this, by swapping the drawable resource values with each other.
if (!is_19_17_or_greater) { if (!is_19_17_or_greater) {
miniplayerModernExpandCloseDrawablesMethod.match( miniplayerModernViewParentMethod.immutableClassDef.getMiniplayerModernExpandCloseDrawablesMethod().apply {
miniplayerModernViewParentMethod.immutableClassDef,
).method.apply {
listOf( listOf(
ytOutlinePictureInPictureWhite24 to ytOutlineXWhite24, ytOutlinePictureInPictureWhite24 to ytOutlineXWhite24,
ytOutlineXWhite24 to ytOutlinePictureInPictureWhite24, ytOutlineXWhite24 to ytOutlinePictureInPictureWhite24,
@ -411,17 +395,15 @@ val Miniplayer by creatingBytecodePatch(
// region Add hooks to hide modern miniplayer buttons. // region Add hooks to hide modern miniplayer buttons.
listOf( listOf(
miniplayerModernExpandButtonMethod to "hideMiniplayerExpandClose", miniplayerModernExpandButtonMethodMatch to "hideMiniplayerExpandClose",
miniplayerModernCloseButtonMethod to "hideMiniplayerExpandClose", miniplayerModernCloseButtonMethodMatch to "hideMiniplayerExpandClose",
miniplayerModernActionButtonMethod to "hideMiniplayerActionButton", miniplayerModernActionButtonMethodMatch to "hideMiniplayerActionButton",
miniplayerModernRewindButtonMethod to "hideMiniplayerRewindForward", miniplayerModernRewindButtonMethodMatch to "hideMiniplayerRewindForward",
miniplayerModernForwardButtonMethod to "hideMiniplayerRewindForward", miniplayerModernForwardButtonMethodMatch to "hideMiniplayerRewindForward",
miniplayerModernOverlayViewMethod to "adjustMiniplayerOpacity", miniplayerModernOverlayViewMethodMatch to "adjustMiniplayerOpacity",
).forEach { (fingerprint, methodName) -> ).forEach { (match, methodName) ->
fingerprint.match( match.match(miniplayerModernViewParentMethod.immutableClassDef).method.apply {
miniplayerModernViewParentMethod.classDef, val index = match.indices.last()
).method.apply {
val index = fingerprint.indices.last()
val register = getInstruction<OneRegisterInstruction>(index).registerA val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstruction( addInstruction(
@ -431,9 +413,7 @@ val Miniplayer by creatingBytecodePatch(
} }
} }
miniplayerModernAddViewListenerMethod.match( miniplayerModernViewParentMethod.immutableClassDef.getMiniplayerModernAddViewListenerMethod().addInstruction(
miniplayerModernViewParentMethod.classDef,
).method.addInstruction(
0, 0,
"invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->" + "invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->" +
"hideMiniplayerSubTexts(Landroid/view/View;)V", "hideMiniplayerSubTexts(Landroid/view/View;)V",

View file

@ -8,7 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/** /**
* 19.46+ * 19.46+
*/ */
internal val BytecodePatchContext.openVideosFullscreenPortraitMethod by gettingFirstMethodDeclaratively { internal val openVideosFullscreenPortraitMethodMatch = firstMethodComposite {
returnType("V") returnType("V")
parameterTypes("L", "Lj\$/util/Optional;") parameterTypes("L", "Lj\$/util/Optional;")
instructions( instructions(
@ -22,7 +22,7 @@ internal val BytecodePatchContext.openVideosFullscreenPortraitMethod by gettingF
/** /**
* Pre 19.46. * Pre 19.46.
*/ */
internal val BytecodePatchContext.openVideosFullscreenPortraitLegacyMethod by gettingFirstMethodDeclaratively { internal val openVideosFullscreenPortraitLegacyMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("L", "Lj\$/util/Optional;") parameterTypes("L", "Lj\$/util/Optional;")

View file

@ -1,5 +1,6 @@
package app.revanced.patches.youtube.layout.player.fullscreen package app.revanced.patches.youtube.layout.player.fullscreen
import app.revanced.patcher.MatchBuilder
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@ -23,14 +24,14 @@ internal val openVideosFullscreenHookPatch = bytecodePatch {
) )
apply { apply {
var fingerprint: Fingerprint var match: MatchBuilder
var insertIndex: Int var insertIndex: Int
if (is_19_46_or_greater) { if (is_19_46_or_greater) {
fingerprint = openVideosFullscreenPortraitMethod match = openVideosFullscreenPortraitMethodMatch
insertIndex = fingerprint.indices.first() insertIndex = match.indices.first()
openVideosFullscreenPortraitMethod.let { openVideosFullscreenPortraitMethodMatch.let {
// Remove A/B feature call that forces what this patch already does. // Remove A/B feature call that forces what this patch already does.
// Cannot use the A/B flag to accomplish the same goal because 19.50+ // Cannot use the A/B flag to accomplish the same goal because 19.50+
// Shorts fullscreen regular player does not use fullscreen // Shorts fullscreen regular player does not use fullscreen
@ -41,22 +42,20 @@ internal val openVideosFullscreenHookPatch = bytecodePatch {
) )
} }
} else { } else {
fingerprint = openVideosFullscreenPortraitLegacyMethod match = openVideosFullscreenPortraitLegacyMethodMatch
insertIndex = fingerprint.indices.last() insertIndex = match.indices.last()
} }
fingerprint.let { match.method.apply {
it.method.apply { val register = getInstruction<OneRegisterInstruction>(insertIndex).registerA
val register = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstructions( addInstructions(
insertIndex + 1, insertIndex + 1,
""" """
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->doNotOpenVideoFullscreenPortrait(Z)Z invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->doNotOpenVideoFullscreenPortrait(Z)Z
move-result v$register move-result v$register
""", """,
) )
}
} }
} }
} }

View file

@ -41,7 +41,7 @@ val `Custom player overlay opacity` by creatingBytecodePatch(
TextPreference("revanced_player_overlay_opacity", inputType = InputType.NUMBER), TextPreference("revanced_player_overlay_opacity", inputType = InputType.NUMBER),
) )
createPlayerOverviewMethod.let { createPlayerOverviewMethodMatch.let {
it.method.apply { it.method.apply {
val viewRegisterIndex = it.indices.last() val viewRegisterIndex = it.indices.last()
val viewRegister = getInstruction<OneRegisterInstruction>(viewRegisterIndex).registerA val viewRegister = getInstruction<OneRegisterInstruction>(viewRegisterIndex).registerA

View file

@ -1,16 +1,13 @@
package app.revanced.patches.youtube.layout.player.overlay package app.revanced.patches.youtube.layout.player.overlay
import app.revanced.patcher.checkCast import app.revanced.patcher.*
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.mapping.ResourceType import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.createPlayerOverviewMethod by gettingFirstMethodDeclaratively { internal val createPlayerOverviewMethodMatch = firstMethodComposite {
returnType("V") returnType("V")
instructions( instructions(
ResourceType.ID("scrim_overlay"), ResourceType.ID("scrim_overlay"),
checkCast("Landroid/widget/ImageView;", afterAtMost(10)), afterAtMost(10, allOf(Opcode.CHECK_CAST(), type("Landroid/widget/ImageView;"))),
) )
} }

View file

@ -1,43 +1,33 @@
package app.revanced.patches.youtube.layout.returnyoutubedislike package app.revanced.patches.youtube.layout.returnyoutubedislike
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.dislikeMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.dislikeMethod by gettingFirstMutableMethodDeclaratively {
returnType("V") returnType("V")
instructions( instructions("like/dislike"())
"like/dislike"(),
)
} }
internal val BytecodePatchContext.likeMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.likeMethod by gettingFirstMutableMethodDeclaratively {
returnType("V") returnType("V")
instructions( instructions("like/like"())
"like/like"(),
)
} }
internal val BytecodePatchContext.removeLikeMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.removeLikeMethod by gettingFirstMutableMethodDeclaratively {
returnType("V") returnType("V")
instructions( instructions("like/removelike"())
"like/removelike"(),
)
} }
internal val BytecodePatchContext.rollingNumberMeasureAnimatedTextMethod by gettingFirstMethodDeclaratively { internal val rollingNumberMeasureAnimatedTextMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returnType("Lj\$/util/Optional;") returnType("Lj\$/util/Optional;")
parameterTypes("L", "Ljava/lang/String;", "L") parameterTypes("L", "Ljava/lang/String;", "L")
opcodes( opcodes(
Opcode.IGET, // First instruction of method Opcode.IGET, // First instruction of method.
Opcode.IGET_OBJECT, Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT, Opcode.IGET_OBJECT,
Opcode.CONST_HIGH16, Opcode.CONST_HIGH16,
@ -46,14 +36,14 @@ internal val BytecodePatchContext.rollingNumberMeasureAnimatedTextMethod by gett
Opcode.CONST_4, Opcode.CONST_4,
Opcode.AGET, Opcode.AGET,
Opcode.CONST_4, Opcode.CONST_4,
Opcode.CONST_4, // Measured text width Opcode.CONST_4, // Measured text width.
) )
} }
/** /**
* Matches to class found in [rollingNumberMeasureStaticLabelParentMethod]. * Matches to class found in [rollingNumberMeasureStaticLabelParentMethod].
*/ */
internal val BytecodePatchContext.rollingNumberMeasureStaticLabelMethod by gettingFirstMethodDeclaratively { internal val rollingNumberMeasureStaticLabelMethod = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("F") returnType("F")
parameterTypes("Ljava/lang/String;") parameterTypes("Ljava/lang/String;")
@ -74,29 +64,33 @@ internal val BytecodePatchContext.rollingNumberMeasureStaticLabelParentMethod by
) )
} }
internal val BytecodePatchContext.rollingNumberSetterMethod by gettingFirstMethodDeclaratively { internal val rollingNumberSetterMethodMatch = firstMethodComposite {
opcodes( opcodes(
Opcode.INVOKE_DIRECT, Opcode.INVOKE_DIRECT,
Opcode.IGET_OBJECT, Opcode.IGET_OBJECT,
) )
// Partial string match. custom {
strings("RollingNumberType required properties missing! Need") instructions.matchIndexed(
"string",
"RollingNumberType required properties missing! Need"(String::contains),
)
}
} }
internal val BytecodePatchContext.rollingNumberTextViewMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.rollingNumberTextViewMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("L", "F", "F") parameterTypes("L", "F", "F")
opcodes( instructions(
Opcode.IPUT, Opcode.IPUT(),
null, // invoke-direct or invoke-virtual anyOf(Opcode.INVOKE_DIRECT(), Opcode.INVOKE_VIRTUAL()),
Opcode.IPUT_OBJECT, Opcode.IPUT_OBJECT(),
Opcode.IGET_OBJECT, Opcode.IGET_OBJECT(),
Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_VIRTUAL(),
Opcode.RETURN_VOID, Opcode.RETURN_VOID(),
) )
custom { _, classDef -> custom {
classDef.superclass == "Landroid/support/v7/widget/AppCompatTextView;" || classDef.superclass == immutableClassDef.superclass == "Landroid/support/v7/widget/AppCompatTextView;" || immutableClassDef.superclass ==
"Lcom/google/android/libraries/youtube/rendering/ui/spec/typography/YouTubeAppCompatTextView;" "Lcom/google/android/libraries/youtube/rendering/ui/spec/typography/YouTubeAppCompatTextView;"
} }
} }
@ -114,28 +108,23 @@ internal val BytecodePatchContext.textComponentDataMethod by gettingFirstMethodD
instructions( instructions(
"text"(), "text"(),
) )
custom { _, classDef -> custom { immutableClassDef.anyField { type == "Ljava/util/BitSet;" } }
classDef.fields.find { it.type == "Ljava/util/BitSet;" } != null
}
} }
/** /**
* Matches against the same class found in [textComponentConstructorMethod]. * Matches against the same class found in [textComponentConstructorMethod].
*/ */
internal val BytecodePatchContext.textComponentLookupMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getTextComponentLookupMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL) accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes("L") parameterTypes("L")
instructions( instructions(""())
""(),
)
} }
internal val BytecodePatchContext.textComponentFeatureFlagMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.textComponentFeatureFlagMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.FINAL) accessFlags(AccessFlags.FINAL)
returnType("Z") returnType("Z")
parameterTypes() parameterTypes()
instructions( instructions(45675738L())
45675738L(),
)
} }

View file

@ -1,7 +1,10 @@
package app.revanced.patches.youtube.layout.returnyoutubedislike package app.revanced.patches.youtube.layout.returnyoutubedislike
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.fieldReference
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.creatingBytecodePatch import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
@ -18,21 +21,20 @@ import app.revanced.patches.youtube.misc.playservice.*
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.conversionContextToStringMethod import app.revanced.patches.youtube.shared.conversionContextToStringMethod
import app.revanced.patches.youtube.shared.rollingNumberTextViewAnimationUpdateMethod import app.revanced.patches.youtube.shared.rollingNumberTextViewAnimationUpdateMethodMatch
import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
import app.revanced.patches.youtube.video.videoid.hookVideoId import app.revanced.patches.youtube.video.videoid.hookVideoId
import app.revanced.patches.youtube.video.videoid.videoIdPatch import app.revanced.patches.youtube.video.videoid.videoIdPatch
import app.revanced.util.* import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findFreeRegister
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.returnLate
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.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
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import java.util.logging.Logger import java.util.logging.Logger
import kotlin.jvm.java
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch;" "Lapp/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch;"
@ -102,8 +104,8 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
likeMethod to Vote.LIKE, likeMethod to Vote.LIKE,
dislikeMethod to Vote.DISLIKE, dislikeMethod to Vote.DISLIKE,
removeLikeMethod to Vote.REMOVE_LIKE, removeLikeMethod to Vote.REMOVE_LIKE,
).forEach { (fingerprint, vote) -> ).forEach { (method, vote) ->
fingerprint.method.addInstructions( method.addInstructions(
0, 0,
""" """
const/4 v0, ${vote.value} const/4 v0, ${vote.value}
@ -128,7 +130,7 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
it.type == conversionContextClass.superclass it.type == conversionContextClass.superclass
} ?: throw PatchException("Could not find conversion context field") } ?: throw PatchException("Could not find conversion context field")
textComponentLookupMethod.match(textComponentConstructorMethod.immutableClassDef).method.apply { textComponentConstructorMethod.immutableClassDef.getTextComponentLookupMethod().apply {
// Find the instruction for creating the text data object. // Find the instruction for creating the text data object.
val textDataClassType = textComponentDataMethod.immutableClassDef.type val textDataClassType = textComponentDataMethod.immutableClassDef.type
@ -138,24 +140,24 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
if (is_19_33_or_greater && !is_20_10_or_greater) { if (is_19_33_or_greater && !is_20_10_or_greater) {
val index = indexOfFirstInstructionOrThrow { val index = indexOfFirstInstructionOrThrow {
(opcode == Opcode.INVOKE_STATIC || opcode == Opcode.INVOKE_STATIC_RANGE) && (opcode == Opcode.INVOKE_STATIC || opcode == Opcode.INVOKE_STATIC_RANGE) &&
getReference<MethodReference>()?.returnType == textDataClassType methodReference?.returnType == textDataClassType
} }
insertIndex = indexOfFirstInstructionOrThrow(index) { insertIndex = indexOfFirstInstructionOrThrow(index) {
opcode == Opcode.INVOKE_VIRTUAL && opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.parameterTypes?.firstOrNull() == "Ljava/lang/CharSequence;" methodReference?.parameterTypes?.firstOrNull() == "Ljava/lang/CharSequence;"
} }
charSequenceRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD charSequenceRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
} else { } else {
insertIndex = indexOfFirstInstructionOrThrow { insertIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.NEW_INSTANCE && opcode == Opcode.NEW_INSTANCE &&
getReference<TypeReference>()?.type == textDataClassType fieldReference?.type == textDataClassType
} }
val charSequenceIndex = indexOfFirstInstructionOrThrow(insertIndex) { val charSequenceIndex = indexOfFirstInstructionOrThrow(insertIndex) {
opcode == Opcode.IPUT_OBJECT && opcode == Opcode.IPUT_OBJECT &&
getReference<FieldReference>()?.type == "Ljava/lang/CharSequence;" fieldReference?.type == "Ljava/lang/CharSequence;"
} }
charSequenceRegister = getInstruction<TwoRegisterInstruction>(charSequenceIndex).registerA charSequenceRegister = getInstruction<TwoRegisterInstruction>(charSequenceIndex).registerA
} }
@ -221,13 +223,11 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
// region Hook rolling numbers. // region Hook rolling numbers.
rollingNumberSetterMethod.apply { rollingNumberSetterMethodMatch.method.apply {
val insertIndex = 1 val insertIndex = 1
val dislikesIndex = rollingNumberSetterMethod.indices.last() val dislikesIndex = rollingNumberSetterMethodMatch.indices.last()
val charSequenceInstanceRegister = val charSequenceInstanceRegister = getInstruction<OneRegisterInstruction>(0).registerA
getInstruction<OneRegisterInstruction>(0).registerA val charSequenceFieldReference = getInstruction<ReferenceInstruction>(dislikesIndex).reference
val charSequenceFieldReference =
getInstruction<ReferenceInstruction>(dislikesIndex).reference
val conversionContextRegister = implementation!!.registerCount - parameters.size + 1 val conversionContextRegister = implementation!!.registerCount - parameters.size + 1
@ -246,7 +246,7 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
// Rolling Number text views use the measured width of the raw string for layout. // Rolling Number text views use the measured width of the raw string for layout.
// Modify the measure text calculation to include the left drawable separator if needed. // Modify the measure text calculation to include the left drawable separator if needed.
rollingNumberMeasureAnimatedTextMethod.let { rollingNumberMeasureAnimatedTextMethodMatch.let {
// Additional check to verify the opcodes are at the start of the method // Additional check to verify the opcodes are at the start of the method
if (it.indices.first() != 0) throw PatchException("Unexpected opcode location") if (it.indices.first() != 0) throw PatchException("Unexpected opcode location")
val endIndex = it.indices.last() val endIndex = it.indices.last()
@ -290,11 +290,11 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
rollingNumberTextViewMethod, rollingNumberTextViewMethod,
// Videos less than 24 hours after uploaded, like counts will be updated in real time. // Videos less than 24 hours after uploaded, like counts will be updated in real time.
// Whenever like counts are updated, TextView is set in this method. // Whenever like counts are updated, TextView is set in this method.
rollingNumberTextViewAnimationUpdateMethod, rollingNumberTextViewAnimationUpdateMethodMatch.method,
).forEach { insertMethod -> ).forEach { insertMethod ->
insertMethod.apply { insertMethod.apply {
val setTextIndex = indexOfFirstInstructionOrThrow { val setTextIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "setText" methodReference?.name == "setText"
} }
val textViewRegister = getInstruction<FiveRegisterInstruction>(setTextIndex).registerC val textViewRegister = getInstruction<FiveRegisterInstruction>(setTextIndex).registerC

View file

@ -1,33 +1,22 @@
package app.revanced.patches.youtube.layout.seekbar package app.revanced.patches.youtube.layout.seekbar
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.afterAtMost
import app.revanced.patcher.anyInstruction
import app.revanced.patcher.custom
import app.revanced.patcher.definingClass
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.literal
import app.revanced.patcher.method
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.mapping.ResourceType import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.fullscreenSeekbarThumbnailsMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.fullscreenSeekbarThumbnailsMethod by gettingFirstMethodDeclaratively {
returnType("Z") returnType("Z")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes() parameterTypes()
instructions( instructions(
45398577(), 45398577L(),
) )
} }
internal val BytecodePatchContext.playerSeekbarColorMethod by gettingFirstMethodDeclaratively { internal val playerSeekbarColorMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions( instructions(
ResourceType.COLOR("inline_time_bar_played_not_highlighted_color"), ResourceType.COLOR("inline_time_bar_played_not_highlighted_color"),
@ -36,19 +25,24 @@ internal val BytecodePatchContext.playerSeekbarColorMethod by gettingFirstMethod
} }
// class is ControlsOverlayStyle in 20.32 and lower, and obfuscated in 20.33+ // class is ControlsOverlayStyle in 20.32 and lower, and obfuscated in 20.33+
internal val BytecodePatchContext.setSeekbarClickedColorMethod by gettingFirstMethodDeclaratively { internal val setSeekbarClickedColorMethodMatch = firstMethodComposite(
"YOUTUBE",
"PREROLL",
"POSTROLL",
"REMOTE_LIVE",
"AD_LARGE_CONTROLS",
) {
opcodes(Opcode.CONST_HIGH16) opcodes(Opcode.CONST_HIGH16)
strings("YOUTUBE", "PREROLL", "POSTROLL", "REMOTE_LIVE", "AD_LARGE_CONTROLS")
} }
internal val BytecodePatchContext.shortsSeekbarColorMethod by gettingFirstMethodDeclaratively { internal val shortsSeekbarColorMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions( instructions(
ResourceType.COLOR("reel_time_bar_played_color"), ResourceType.COLOR("reel_time_bar_played_color"),
) )
} }
internal val BytecodePatchContext.playerSeekbarHandle1ColorMethod by gettingFirstMethodDeclaratively { internal val playerSeekbarHandle1ColorMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions( instructions(
ResourceType.COLOR("inline_time_bar_live_seekable_range"), ResourceType.COLOR("inline_time_bar_live_seekable_range"),
@ -56,7 +50,7 @@ internal val BytecodePatchContext.playerSeekbarHandle1ColorMethod by gettingFirs
) )
} }
internal val BytecodePatchContext.playerSeekbarHandle2ColorMethod by gettingFirstMethodDeclaratively { internal val playerSeekbarHandle2ColorMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameterTypes("Landroid/content/Context;") parameterTypes("Landroid/content/Context;")
instructions( instructions(
@ -65,18 +59,18 @@ internal val BytecodePatchContext.playerSeekbarHandle2ColorMethod by gettingFirs
) )
} }
internal val BytecodePatchContext.watchHistoryMenuUseProgressDrawableMethod by gettingFirstMethodDeclaratively { internal val watchHistoryMenuUseProgressDrawableMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("L") parameterTypes("L")
instructions( instructions(
methodCall("Landroid/widget/ProgressBar;", "setMax"), method { name == "setMax" && definingClass == "Landroid/widget/ProgressBar;" },
Opcode.MOVE_RESULT(), Opcode.MOVE_RESULT(),
-1712394514(), (-1712394514L)(),
) )
} }
internal val BytecodePatchContext.lithoLinearGradientMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.lithoLinearGradientMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.STATIC) accessFlags(AccessFlags.STATIC)
returnType("Landroid/graphics/LinearGradient;") returnType("Landroid/graphics/LinearGradient;")
parameterTypes("F", "F", "F", "F", "[I", "[F") parameterTypes("F", "F", "F", "F", "[I", "[F")
@ -85,7 +79,7 @@ internal val BytecodePatchContext.lithoLinearGradientMethod by gettingFirstMetho
/** /**
* 19.49+ * 19.49+
*/ */
internal val BytecodePatchContext.playerLinearGradientMethod by gettingFirstMethodDeclaratively { internal val playerLinearGradientMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
parameterTypes("I", "I", "I", "I", "Landroid/content/Context;", "I") parameterTypes("I", "I", "I", "I", "Landroid/content/Context;", "I")
returnType("Landroid/graphics/LinearGradient;") returnType("Landroid/graphics/LinearGradient;")
@ -100,7 +94,7 @@ internal val BytecodePatchContext.playerLinearGradientMethod by gettingFirstMeth
/** /**
* 19.25 - 19.47 * 19.25 - 19.47
*/ */
internal val BytecodePatchContext.playerLinearGradientLegacyMethod by gettingFirstMethodDeclaratively { internal val playerLinearGradientLegacyMethodMatch = firstMethodComposite {
returnType("V") returnType("V")
instructions( instructions(
ResourceType.COLOR("yt_youtube_magenta"), ResourceType.COLOR("yt_youtube_magenta"),
@ -142,11 +136,12 @@ internal val BytecodePatchContext.lottieCompositionFactoryZipMethod by gettingFi
* *
* [Original method](https://github.com/airbnb/lottie-android/blob/26ad8bab274eac3f93dccccfa0cafc39f7408d13/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java#L386) * [Original method](https://github.com/airbnb/lottie-android/blob/26ad8bab274eac3f93dccccfa0cafc39f7408d13/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java#L386)
*/ */
internal val BytecodePatchContext.lottieCompositionFactoryFromJsonInputStreamMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getLottieCompositionFactoryFromJsonInputStreamMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
parameterTypes("Ljava/io/InputStream;", "Ljava/lang/String;") parameterTypes("Ljava/io/InputStream;", "Ljava/lang/String;")
returnType("L") returnType("L")
instructions( instructions(
anyInstruction(literal(2), literal(3)), anyOf(2L(), 3L()),
) )
} }

View file

@ -1,5 +1,6 @@
package app.revanced.patches.youtube.layout.seekbar package app.revanced.patches.youtube.layout.seekbar
import app.revanced.patcher.*
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.extensions.replaceInstruction
@ -47,19 +48,19 @@ val seekbarColorPatch = bytecodePatch(
) )
} }
playerSeekbarColorMethod.let { playerSeekbarColorMethodMatch.let {
it.method.apply { it.method.apply {
addColorChangeInstructions(it.indices.last()) addColorChangeInstructions(it.indices.last())
addColorChangeInstructions(it.indices.first()) addColorChangeInstructions(it.indices.first())
} }
} }
shortsSeekbarColorMethod.let { shortsSeekbarColorMethodMatch.let {
it.method.addColorChangeInstructions(it.indices.first()) it.method.addColorChangeInstructions(it.indices.first())
} }
setSeekbarClickedColorMethod.immutableMethod.let { setSeekbarClickedColorMethodMatch.immutableMethod.let {
val setColorMethodIndex = setSeekbarClickedColorMethod.indices.first() + 1 val setColorMethodIndex = setSeekbarClickedColorMethodMatch.indices.first() + 1
navigate(it).to(setColorMethodIndex).stop().apply { navigate(it).to(setColorMethodIndex).stop().apply {
val colorRegister = getInstruction<TwoRegisterInstruction>(0).registerA val colorRegister = getInstruction<TwoRegisterInstruction>(0).registerA
@ -77,9 +78,9 @@ val seekbarColorPatch = bytecodePatch(
// 19.25+ changes // 19.25+ changes
var handleBarColorFingerprints = mutableListOf(playerSeekbarHandle1ColorMethod) var handleBarColorFingerprints = mutableListOf(playerSeekbarHandle1ColorMethodMatch)
if (!is_20_34_or_greater) { if (!is_20_34_or_greater) {
handleBarColorFingerprints += playerSeekbarHandle2ColorMethod handleBarColorFingerprints += playerSeekbarHandle2ColorMethodMatch
} }
handleBarColorFingerprints.forEach { handleBarColorFingerprints.forEach {
it.method.addColorChangeInstructions(it.indices.last()) it.method.addColorChangeInstructions(it.indices.last())
@ -89,7 +90,7 @@ val seekbarColorPatch = bytecodePatch(
// of the watch history menu items as they use the same gradient as the // of the watch history menu items as they use the same gradient as the
// player and there is no easy way to distinguish which to use a transparent color. // player and there is no easy way to distinguish which to use a transparent color.
if (is_19_34_or_greater) { if (is_19_34_or_greater) {
watchHistoryMenuUseProgressDrawableMethod.let { watchHistoryMenuUseProgressDrawableMethodMatch.let {
it.method.apply { it.method.apply {
val index = it.indices[1] val index = it.indices[1]
val register = getInstruction<OneRegisterInstruction>(index).registerA val register = getInstruction<OneRegisterInstruction>(index).registerA
@ -113,17 +114,17 @@ val seekbarColorPatch = bytecodePatch(
""", """,
) )
val playerFingerprint: Fingerprint val playerMatch: MatchBuilder
val checkGradientCoordinates: Boolean val checkGradientCoordinates: Boolean
if (is_19_49_or_greater) { if (is_19_49_or_greater) {
playerFingerprint = playerLinearGradientMethod playerMatch = playerLinearGradientMethodMatch
checkGradientCoordinates = true checkGradientCoordinates = true
} else { } else {
playerFingerprint = playerLinearGradientLegacyMethod playerMatch = playerLinearGradientLegacyMethodMatch
checkGradientCoordinates = false checkGradientCoordinates = false
} }
playerFingerprint.let { playerMatch.let {
it.method.apply { it.method.apply {
val index = it.indices.last() val index = it.indices.last()
val register = getInstruction<OneRegisterInstruction>(index).registerA val register = getInstruction<OneRegisterInstruction>(index).registerA
@ -153,8 +154,7 @@ val seekbarColorPatch = bytecodePatch(
// Hook the splash animation to set the a seekbar color. // Hook the splash animation to set the a seekbar color.
mainActivityOnCreateMethod.apply { mainActivityOnCreateMethod.apply {
val setAnimationIntMethodName = val setAnimationIntMethodName = lottieAnimationViewSetAnimationIntMethod.name
lottieAnimationViewSetAnimationIntMethod.immutableMethod.name
findInstructionIndicesReversedOrThrow { findInstructionIndicesReversedOrThrow {
val reference = getReference<MethodReference>() val reference = getReference<MethodReference>()
@ -175,8 +175,7 @@ val seekbarColorPatch = bytecodePatch(
// and `setAnimation(InputStream, String)` so extension code can call them. // and `setAnimation(InputStream, String)` so extension code can call them.
lottieAnimationViewSetAnimationIntMethod.classDef.methods.apply { lottieAnimationViewSetAnimationIntMethod.classDef.methods.apply {
val addedMethodName = "patch_setAnimation" val addedMethodName = "patch_setAnimation"
val setAnimationIntName = lottieAnimationViewSetAnimationIntMethod val setAnimationIntMethodName = lottieAnimationViewSetAnimationIntMethod.name
.immutableMethod.name
add( add(
ImmutableMethod( ImmutableMethod(
@ -191,9 +190,9 @@ val seekbarColorPatch = bytecodePatch(
).toMutable().apply { ).toMutable().apply {
addInstructions( addInstructions(
""" """
invoke-virtual { p0, p1 }, Lcom/airbnb/lottie/LottieAnimationView;->$setAnimationIntName(I)V invoke-virtual { p0, p1 }, Lcom/airbnb/lottie/LottieAnimationView;->$setAnimationIntMethodName(I)V
return-void return-void
""", """,
) )
}, },
) )
@ -201,24 +200,21 @@ val seekbarColorPatch = bytecodePatch(
val factoryStreamClass: CharSequence val factoryStreamClass: CharSequence
val factoryStreamName: CharSequence val factoryStreamName: CharSequence
val factoryStreamReturnType: CharSequence val factoryStreamReturnType: CharSequence
lottieCompositionFactoryFromJsonInputStreamMethod.match(
lottieCompositionFactoryZipMethod.immutableClassDef,
).immutableMethod.apply {
factoryStreamClass = definingClass
factoryStreamName = name
factoryStreamReturnType = returnType
}
val lottieAnimationViewSetAnimationStreamFingerprint = fingerprint { lottieCompositionFactoryZipMethod.immutableClassDef.getLottieCompositionFactoryFromJsonInputStreamMethod()
.let {
factoryStreamClass = it.definingClass
factoryStreamName = it.name
factoryStreamReturnType = it.returnType
}
val lottieAnimationViewSetAnimationStreamMethod = firstMutableMethodDeclaratively {
definingClass(lottieAnimationViewSetAnimationIntMethod.immutableClassDef.type)
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes(factoryStreamReturnType.toString()) parameterTypes(factoryStreamReturnType.toString())
returnType("V") returnType("V")
custom { _, classDef ->
classDef.type == lottieAnimationViewSetAnimationIntMethod.immutableClassDef.type
}
} }
val setAnimationStreamName = lottieAnimationViewSetAnimationStreamFingerprint val setAnimationStreamMethodName = lottieAnimationViewSetAnimationStreamMethod.name
.immutableMethod.name
add( add(
ImmutableMethod( ImmutableMethod(
@ -236,11 +232,11 @@ val seekbarColorPatch = bytecodePatch(
).toMutable().apply { ).toMutable().apply {
addInstructions( addInstructions(
""" """
invoke-static { p1, p2 }, $factoryStreamClass->$factoryStreamName(Ljava/io/InputStream;Ljava/lang/String;)$factoryStreamReturnType invoke-static { p1, p2 }, $factoryStreamClass->$factoryStreamName(Ljava/io/InputStream;Ljava/lang/String;)$factoryStreamReturnType
move-result-object v0 move-result-object v0
invoke-virtual { p0, v0}, Lcom/airbnb/lottie/LottieAnimationView;->$setAnimationStreamName($factoryStreamReturnType)V invoke-virtual { p0, v0}, Lcom/airbnb/lottie/LottieAnimationView;->$setAnimationStreamMethodName($factoryStreamReturnType)V
return-void return-void
""", """,
) )
}, },
) )

View file

@ -1,18 +1,10 @@
package app.revanced.patches.youtube.layout.shortsautoplay package app.revanced.patches.youtube.layout.shortsautoplay
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.afterAtMost
import app.revanced.patcher.field
import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.method
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val reelEnumConstructorMethodMatch = firstMethodComposite { internal val reelEnumConstructorMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
@ -36,7 +28,8 @@ internal val BytecodePatchContext.reelPlaybackRepeatParentMethod by gettingFirst
/** /**
* Matches class found in [reelPlaybackRepeatParentMethod]. * Matches class found in [reelPlaybackRepeatParentMethod].
*/ */
internal val BytecodePatchContext.reelPlaybackRepeatMethod by gettingFirstMethodDeclaratively { context(_: BytecodePatchContext)
internal fun ClassDef.getReelPlaybackRepeatMethod() = firstMutableMethodDeclaratively {
returnType("V") returnType("V")
parameterTypes("L") parameterTypes("L")
instructions(method { toString() == "Lcom/google/common/util/concurrent/ListenableFuture;->isDone()Z" }) instructions(method { toString() == "Lcom/google/common/util/concurrent/ListenableFuture;->isDone()Z" })
@ -54,14 +47,15 @@ internal val reelPlaybackMethodMatch = firstMethodComposite {
15, 15,
method { method {
name == "<init>" && name == "<init>" &&
parameterTypes.zip(methodParametersPrefix).all { (a, b) -> a.startsWith(b) } parameterTypes.zip(methodParametersPrefix).all { (a, b) -> a.startsWith(b) }
}, },
), ),
methodCall( afterAtMost(
opcode = Opcode.INVOKE_VIRTUAL, 5,
parameters = listOf("L"), allOf(
returnType = "I", Opcode.INVOKE_VIRTUAL(),
afterAtMost(5), method { returnType == "I" && parameterTypes.count() == 1 && parameterTypes.first() == "L" },
),
), ),
) )
} }

View file

@ -1,5 +1,6 @@
package app.revanced.patches.youtube.layout.shortsautoplay package app.revanced.patches.youtube.layout.shortsautoplay
import app.revanced.patcher.classDef
import app.revanced.patcher.extensions.addInstruction import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.addInstructionsWithLabels import app.revanced.patcher.extensions.addInstructionsWithLabels
@ -88,15 +89,15 @@ val `Shorts autoplay` by creatingBytecodePatch(
) )
} }
reelPlaybackRepeatMethod.match( val reelPlaybackRepeatMethod = reelPlaybackRepeatParentMethod.immutableClassDef.getReelPlaybackRepeatMethod()
reelPlaybackRepeatParentMethod.immutableClassDef,
).method.apply { reelPlaybackRepeatMethod.apply {
// The behavior enums are looked up from an ordinal value to an enum type. // The behavior enums are looked up from an ordinal value to an enum type.
findInstructionIndicesReversedOrThrow { findInstructionIndicesReversedOrThrow {
val reference = getReference<MethodReference>() val reference = getReference<MethodReference>()
reference?.definingClass == reelEnumClass && reference?.definingClass == reelEnumClass &&
reference.parameterTypes.firstOrNull() == "I" && reference.parameterTypes.firstOrNull() == "I" &&
reference.returnType == reelEnumClass reference.returnType == reelEnumClass
}.forEach { index -> }.forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
@ -123,14 +124,14 @@ val `Shorts autoplay` by creatingBytecodePatch(
// Find the first call modified by extension code above. // Find the first call modified by extension code above.
val extensionReturnResultIndex = indexOfFirstInstructionOrThrow { val extensionReturnResultIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_STATIC && opcode == Opcode.INVOKE_STATIC &&
getReference<MethodReference>()?.definingClass == EXTENSION_CLASS_DESCRIPTOR getReference<MethodReference>()?.definingClass == EXTENSION_CLASS_DESCRIPTOR
} + 1 } + 1
val enumRegister = getInstruction<OneRegisterInstruction>(extensionReturnResultIndex).registerA val enumRegister = getInstruction<OneRegisterInstruction>(extensionReturnResultIndex).registerA
val getReelSequenceControllerIndex = indexOfFirstInstructionOrThrow { val getReelSequenceControllerIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<FieldReference>() val reference = getReference<FieldReference>()
opcode == Opcode.IGET_OBJECT && opcode == Opcode.IGET_OBJECT &&
reference?.definingClass == definingClass && reference?.definingClass == definingClass &&
reference.type == reelSequenceControllerMethodReference.definingClass reference.type == reelSequenceControllerMethodReference.definingClass
} }
val getReelSequenceControllerReference = val getReelSequenceControllerReference =
getInstruction<ReferenceInstruction>(getReelSequenceControllerIndex).reference getInstruction<ReferenceInstruction>(getReelSequenceControllerIndex).reference

View file

@ -6,7 +6,7 @@ import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.toolBarButtonMethod by gettingFirstMethodDeclaratively { internal val toolBarButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
instructions( instructions(
@ -21,7 +21,7 @@ internal val BytecodePatchContext.toolBarButtonMethod by gettingFirstMethodDecla
custom { parameterTypes.count() in 1..2 && parameterTypes.first() == "Landroid/view/MenuItem;" } custom { parameterTypes.count() in 1..2 && parameterTypes.first() == "Landroid/view/MenuItem;" }
} }
internal val BytecodePatchContext.spoofAppVersionMethod by gettingFirstMethodDeclaratively( internal val spoofAppVersionMethodMatch = firstMethodComposite(
// Instead of applying a bytecode patch, it might be possible to only rely on code from the extension and // Instead of applying a bytecode patch, it might be possible to only rely on code from the extension and
// manually set the desired version string as this keyed value in the SharedPreferences. // manually set the desired version string as this keyed value in the SharedPreferences.
// But, this bytecode patch is simple and it works. // But, this bytecode patch is simple and it works.

View file

@ -81,23 +81,23 @@ val `Spoof app version` by creatingBytecodePatch(
* missing image resources. As a workaround, do not set an image in the * missing image resources. As a workaround, do not set an image in the
* toolbar when the enum name is UNKNOWN. * toolbar when the enum name is UNKNOWN.
*/ */
toolBarButtonMethod.apply { toolBarButtonMethodMatch.let {
val imageResourceIndex = indices[2] val imageResourceIndex = it.indices[2]
val register = method.getInstruction<OneRegisterInstruction>(imageResourceIndex).registerA val register = it.method.getInstruction<OneRegisterInstruction>(imageResourceIndex).registerA
val jumpIndex = indices.last() + 1 val jumpIndex = it.indices.last() + 1
method.addInstructionsWithLabels( it.method.addInstructionsWithLabels(
imageResourceIndex + 1, imageResourceIndex + 1,
"if-eqz v$register, :ignore", "if-eqz v$register, :ignore",
ExternalLabel("ignore", method.getInstruction(jumpIndex)), ExternalLabel("ignore", it.method.getInstruction(jumpIndex)),
) )
} }
spoofAppVersionMethod.apply { spoofAppVersionMethodMatch.let {
val index = indices.first() val index = it.indices.first()
val register = method.getInstruction<OneRegisterInstruction>(index).registerA val register = it.method.getInstruction<OneRegisterInstruction>(index).registerA
method.addInstructions( it.method.addInstructions(
index + 1, index + 1,
""" """
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getYouTubeVersionOverride(Ljava/lang/String;)Ljava/lang/String; invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getYouTubeVersionOverride(Ljava/lang/String;)Ljava/lang/String;

View file

@ -1,6 +1,7 @@
package app.revanced.patches.youtube.layout.startupshortsreset package app.revanced.patches.youtube.layout.startupshortsreset
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.creatingBytecodePatch import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
@ -49,11 +50,10 @@ val `Disable resuming Shorts on startup` by creatingBytecodePatch(
) )
if (is_20_03_or_greater) { if (is_20_03_or_greater) {
userWasInShortsAlternativeMethod.let { userWasInShortsAlternativeMethodMatch.let {
it.method.apply { it.method.apply {
val match = it.instructionMatches[2] val insertIndex = it.indices[2] + 1
val insertIndex = match.index + 1 val register = getInstruction<OneRegisterInstruction>(insertIndex).registerA
val register = match.getInstruction<OneRegisterInstruction>().registerA
addInstructions( addInstructions(
insertIndex, insertIndex,
@ -68,8 +68,8 @@ val `Disable resuming Shorts on startup` by creatingBytecodePatch(
userWasInShortsLegacyMethod.apply { userWasInShortsLegacyMethod.apply {
val listenableInstructionIndex = indexOfFirstInstructionOrThrow { val listenableInstructionIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_INTERFACE && opcode == Opcode.INVOKE_INTERFACE &&
getReference<MethodReference>()?.definingClass == "Lcom/google/common/util/concurrent/ListenableFuture;" && getReference<MethodReference>()?.definingClass == "Lcom/google/common/util/concurrent/ListenableFuture;" &&
getReference<MethodReference>()?.name == "isDone" getReference<MethodReference>()?.name == "isDone"
} }
val freeRegister = findFreeRegister(listenableInstructionIndex) val freeRegister = findFreeRegister(listenableInstructionIndex)

View file

@ -1,30 +1,23 @@
package app.revanced.patches.youtube.layout.startupshortsreset package app.revanced.patches.youtube.layout.startupshortsreset
import app.revanced.patcher.StringComparisonType import app.revanced.patcher.*
import app.revanced.patcher.accessFlags
import app.revanced.patcher.checkCast
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
/** /**
* 20.02+ * 20.02+
*/ */
internal val BytecodePatchContext.userWasInShortsAlternativeMethod by gettingFirstMethodDeclaratively { internal val userWasInShortsAlternativeMethodMatch = firstMethodComposite {
returnType("V") returnType("V")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes("Ljava/lang/Object;") parameterTypes("Ljava/lang/Object;")
instructions( instructions(
checkCast("Ljava/lang/Boolean;"), allOf(Opcode.CHECK_CAST(), type("Ljava/lang/Boolean;")),
methodCall(smali = "Ljava/lang/Boolean;->booleanValue()Z", after()), after(method { toString() == "Ljava/lang/Boolean;->booleanValue()Z" }),
after(Opcode.MOVE_RESULT()), after(Opcode.MOVE_RESULT()),
// 20.40+ string was merged into another string and is a partial match. // 20.40+ string was merged into another string and is a partial match.
addString("userIsInShorts: ", comparison = StringComparisonType.CONTAINS, afterAtMost(15)), afterAtMost(15, "userIsInShorts: "(String::contains)),
) )
} }

View file

@ -33,7 +33,7 @@ val themePatch = baseThemePatch(
block = { block = {
val lightThemeBackgroundColor by stringOption( val lightThemeBackgroundColor by stringOption(
key = "lightThemeBackgroundColor", name = "Light theme background color",
default = "@android:color/white", default = "@android:color/white",
values = mapOf( values = mapOf(
"White" to "@android:color/white", "White" to "@android:color/white",
@ -46,7 +46,6 @@ val themePatch = baseThemePatch(
"Light orange" to "#FFE6CC", "Light orange" to "#FFE6CC",
"Light red" to "#FFD6D6", "Light red" to "#FFD6D6",
), ),
name = "Light theme background color",
description = THEME_COLOR_OPTION_DESCRIPTION, description = THEME_COLOR_OPTION_DESCRIPTION,
) )

View file

@ -56,16 +56,13 @@ val `Remove background playback restrictions` by creatingBytecodePatch(
SwitchPreference("revanced_shorts_disable_background_playback"), SwitchPreference("revanced_shorts_disable_background_playback"),
) )
prefBackgroundAndOfflineCategoryId = getResourceId( prefBackgroundAndOfflineCategoryId = ResourceType.STRING["pref_background_and_offline_category"]
ResourceType.STRING,
"pref_background_and_offline_category",
)
arrayOf( arrayOf(
backgroundPlaybackManagerMethod to "isBackgroundPlaybackAllowed", backgroundPlaybackManagerMethod to "isBackgroundPlaybackAllowed",
backgroundPlaybackManagerShortsMethod to "isBackgroundShortsPlaybackAllowed", backgroundPlaybackManagerShortsMethod to "isBackgroundShortsPlaybackAllowed",
).forEach { (fingerprint, integrationsMethod) -> ).forEach { (method, integrationsMethod) ->
fingerprint.method.apply { method.apply {
findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index -> findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index).registerA val register = getInstruction<OneRegisterInstruction>(index).registerA
@ -81,7 +78,7 @@ val `Remove background playback restrictions` by creatingBytecodePatch(
} }
// Enable background playback option in YouTube settings // Enable background playback option in YouTube settings
backgroundPlaybackSettingsMethod.immutableMethod.apply { backgroundPlaybackSettingsMethod.apply {
val booleanCalls = instructions.withIndex().filter { val booleanCalls = instructions.withIndex().filter {
it.value.getReference<MethodReference>()?.returnType == "Z" it.value.getReference<MethodReference>()?.returnType == "Z"
} }
@ -101,7 +98,7 @@ val `Remove background playback restrictions` by creatingBytecodePatch(
// Fix PiP buttons not working after locking/unlocking device screen. // Fix PiP buttons not working after locking/unlocking device screen.
if (is_19_34_or_greater) { if (is_19_34_or_greater) {
pipInputConsumerFeatureFlagMethod.let { pipInputConsumerFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride( it.method.insertLiteralOverride(
it.indices.first(), it.indices.first(),
false, false,

View file

@ -1,17 +1,12 @@
package app.revanced.patches.youtube.misc.backgroundplayback package app.revanced.patches.youtube.misc.backgroundplayback
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.util.literal import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.backgroundPlaybackManagerMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.backgroundPlaybackManagerMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returnType("Z") returnType("Z")
parameterTypes("L") parameterTypes("L")
@ -60,7 +55,7 @@ internal val BytecodePatchContext.backgroundPlaybackSettingsMethod by gettingFir
literal { prefBackgroundAndOfflineCategoryId } literal { prefBackgroundAndOfflineCategoryId }
} }
internal val BytecodePatchContext.kidsBackgroundPlaybackPolicyControllerMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.kidsBackgroundPlaybackPolicyControllerMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("I", "L", "L") parameterTypes("I", "L", "L")
@ -77,28 +72,24 @@ internal val BytecodePatchContext.kidsBackgroundPlaybackPolicyControllerMethod b
literal { 5 } literal { 5 }
} }
internal val BytecodePatchContext.backgroundPlaybackManagerShortsMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.backgroundPlaybackManagerShortsMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returnType("Z") returnType("Z")
parameterTypes("L") parameterTypes("L")
instructions( instructions(151635310L())
app.revanced.patcher.literal(151635310),
)
} }
internal val BytecodePatchContext.shortsBackgroundPlaybackFeatureFlagMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.shortsBackgroundPlaybackFeatureFlagMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z") returnType("Z")
parameterTypes() parameterTypes()
instructions( instructions(45415425L())
app.revanced.patcher.literal(45415425),
)
} }
// Fix 'E/InputDispatcher: Window handle pip_input_consumer has no registered input channel' // Fix 'E/InputDispatcher: Window handle pip_input_consumer has no registered input channel'
internal val BytecodePatchContext.pipInputConsumerFeatureFlagMethod by gettingFirstMethodDeclaratively { internal val pipInputConsumerFeatureFlagMethodMatch = firstMethodComposite {
instructions( instructions(
// PiP input consumer feature flag. // PiP input consumer feature flag.
app.revanced.patcher.literal(45638483L), 45638483L(),
) )
} }

View file

@ -1,7 +1,9 @@
package app.revanced.patches.youtube.misc.dns package app.revanced.patches.youtube.misc.dns
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.shared.misc.dns.checkWatchHistoryDomainNameResolutionPatch import app.revanced.patches.shared.misc.dns.checkWatchHistoryDomainNameResolutionPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateMethod
val checkWatchHistoryDomainNameResolutionPatch = checkWatchHistoryDomainNameResolutionPatch( val checkWatchHistoryDomainNameResolutionPatch = checkWatchHistoryDomainNameResolutionPatch(
block = { block = {
@ -18,5 +20,5 @@ val checkWatchHistoryDomainNameResolutionPatch = checkWatchHistoryDomainNameReso
) )
) )
}, },
getMainActivityMethod = mainActivityOnCreateFingerprint getMainActivityMethod = BytecodePatchContext::mainActivityOnCreateMethod::get
) )

View file

@ -1,15 +1,6 @@
package app.revanced.patches.youtube.misc.fix.backtoexitgesture package app.revanced.patches.youtube.misc.fix.backtoexitgesture
import app.revanced.patcher.accessFlags import app.revanced.patcher.*
import app.revanced.patcher.after
import app.revanced.patcher.checkCast
import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.literal
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -29,11 +20,11 @@ internal val recyclerViewTopScrollingMethodMatch = firstMethodComposite {
returnType("V") returnType("V")
parameterTypes() parameterTypes()
instructions( instructions(
methodCall(smali = "Ljava/util/Iterator;->next()Ljava/lang/Object;"), method { toString() == "Ljava/util/Iterator;->next()Ljava/lang/Object;" },
after(Opcode.MOVE_RESULT_OBJECT()), after(Opcode.MOVE_RESULT_OBJECT()),
checkCast("Landroid/support/v7/widget/RecyclerView;", MatchAfterImmediately()), after(allOf(Opcode.CHECK_CAST(), type("Landroid/support/v7/widget/RecyclerView;"))),
literal(0, after()), after(0L()),
methodCall(definingClass = "Landroid/support/v7/widget/RecyclerView;", after()), after(method { definingClass == "Landroid/support/v7/widget/RecyclerView;" }),
after(Opcode.GOTO()), after(Opcode.GOTO()),
) )
} }

View file

@ -1,7 +1,9 @@
package app.revanced.patches.youtube.misc.fix.playbackspeed package app.revanced.patches.youtube.misc.fix.playbackspeed
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.custom
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.opcodes import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
@ -17,7 +19,7 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
* This method is usually used to set the initial speed (1.0x) when playback starts from the feed. * This method is usually used to set the initial speed (1.0x) when playback starts from the feed.
* For some reason, in the latest YouTube, it is invoked even after the video has already started. * For some reason, in the latest YouTube, it is invoked even after the video has already started.
*/ */
internal val BytecodePatchContext.playbackSpeedInFeedsMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.playbackSpeedInFeedsMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("L") parameterTypes("L")
@ -31,8 +33,8 @@ internal val BytecodePatchContext.playbackSpeedInFeedsMethod by gettingFirstMeth
Opcode.IF_LEZ, Opcode.IF_LEZ,
Opcode.SUB_LONG_2ADDR, Opcode.SUB_LONG_2ADDR,
) )
custom { method, _ -> custom {
indexOfGetPlaybackSpeedInstruction(method) >= 0 indexOfGetPlaybackSpeedInstruction(this) >= 0
} }
} }

View file

@ -2,23 +2,22 @@ package app.revanced.patches.youtube.misc.litho.filter
import app.revanced.patcher.* import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.util.containsLiteralInstruction
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.componentCreateMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.componentCreateMethod by gettingFirstMutableMethodDeclaratively {
instructions( instructions(
"Element missing correct type extension"(), "Element missing correct type extension"(),
"Element missing type"(), "Element missing type"(),
) )
} }
internal val BytecodePatchContext.lithoFilterMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.lithoFilterMethod by gettingFirstMutableMethodDeclaratively {
definingClass { endsWith("/LithoFilterPatch;") } definingClass { endsWith("/LithoFilterPatch;") }
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
} }
internal val BytecodePatchContext.protobufBufferReferenceMethod by gettingFirstMethodDeclaratively { internal val protobufBufferReferenceMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("[B") parameterTypes("[B")
@ -38,7 +37,7 @@ internal val BytecodePatchContext.protobufBufferReferenceMethod by gettingFirstM
) )
} }
internal val BytecodePatchContext.protobufBufferReferenceLegacyMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.protobufBufferReferenceLegacyMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("I", "Ljava/nio/ByteBuffer;") parameterTypes("I", "Ljava/nio/ByteBuffer;")
@ -57,27 +56,23 @@ internal val BytecodePatchContext.emptyComponentMethod by gettingFirstMethodDecl
custom { immutableClassDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1 } custom { immutableClassDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1 }
} }
internal val BytecodePatchContext.lithoThreadExecutorMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.lithoThreadExecutorMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameterTypes("I", "I", "I") parameterTypes("I", "I", "I")
instructions(1L()) // 1L = default thread timeout.
custom { custom {
immutableClassDef.superclass == "Ljava/util/concurrent/ThreadPoolExecutor;" && immutableClassDef.superclass == "Ljava/util/concurrent/ThreadPoolExecutor;"
containsLiteralInstruction(1L) // 1L = default thread timeout.
} }
} }
internal val BytecodePatchContext.lithoComponentNameUpbFeatureFlagMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.lithoComponentNameUpbFeatureFlagMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z") returnType("Z")
parameterTypes() parameterTypes()
instructions( instructions(45631264L())
45631264L(),
)
} }
internal val BytecodePatchContext.lithoConverterBufferUpbFeatureFlagMethod by gettingFirstMethodDeclaratively { internal val lithoConverterBufferUpbFeatureFlagMethodMatch = firstMethodComposite {
returnType("L") returnType("L")
instructions( instructions(45419603L())
45419603L(),
)
} }

View file

@ -7,6 +7,7 @@ import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.removeInstructions import app.revanced.patcher.extensions.removeInstructions
import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.firstClassDef import app.revanced.patcher.firstClassDef
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.* import app.revanced.patches.youtube.misc.playservice.*
@ -90,7 +91,7 @@ val lithoFilterPatch = bytecodePatch(
if (is_20_22_or_greater) { if (is_20_22_or_greater) {
// Hook method that bridges between UPB buffer native code and FB Litho. // Hook method that bridges between UPB buffer native code and FB Litho.
// Method is found in 19.25+, but is forcefully turned off for 20.21 and lower. // Method is found in 19.25+, but is forcefully turned off for 20.21 and lower.
protobufBufferReferenceMethod.let { protobufBufferReferenceMethodMatch.let {
// Hook the buffer after the call to jniDecode(). // Hook the buffer after the call to jniDecode().
it.method.addInstruction( it.method.addInstruction(
it.indices.last() + 1, it.indices.last() + 1,
@ -112,14 +113,14 @@ val lithoFilterPatch = bytecodePatch(
// Find the identifier/path fields of the conversion context. // Find the identifier/path fields of the conversion context.
val conversionContextIdentifierField = conversionContextToStringMethod.method val conversionContextIdentifierField = conversionContextToStringMethod
.findFieldFromToString("identifierProperty=") .findFieldFromToString("identifierProperty=")
val conversionContextPathBuilderField = conversionContextToStringMethod.immutableClassDef val conversionContextPathBuilderField = conversionContextToStringMethod.immutableClassDef
.fields.single { field -> field.type == "Ljava/lang/StringBuilder;" } .fields.single { field -> field.type == "Ljava/lang/StringBuilder;" }
// Find class and methods to create an empty component. // Find class and methods to create an empty component.
val builderMethodDescriptor = emptyComponentMethod.classDef.methods.single { val builderMethodDescriptor = emptyComponentMethod.immutableClassDef.methods.single {
// The only static method in the class. // The only static method in the class.
method -> method ->
AccessFlags.STATIC.isSet(method.accessFlags) AccessFlags.STATIC.isSet(method.accessFlags)
@ -146,7 +147,7 @@ val lithoFilterPatch = bytecodePatch(
# 20.41 field is the abstract superclass. # 20.41 field is the abstract superclass.
# Verify it's the expected subclass just in case. # Verify it's the expected subclass just in case.
instance-of v$identifierRegister, v$freeRegister, ${conversionContextToStringMethod.classDef.type} instance-of v$identifierRegister, v$freeRegister, ${conversionContextToStringMethod.immutableClassDef.type}
if-eqz v$identifierRegister, :unfiltered if-eqz v$identifierRegister, :unfiltered
iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField
@ -196,7 +197,7 @@ val lithoFilterPatch = bytecodePatch(
} }
// Turn off a feature flag that enables native code of protobuf parsing (Upb protobuf). // Turn off a feature flag that enables native code of protobuf parsing (Upb protobuf).
lithoConverterBufferUpbFeatureFlagMethod.let { lithoConverterBufferUpbFeatureFlagMethodMatch.let {
// 20.22 the flag is still enabled in one location, but what it does is not known. // 20.22 the flag is still enabled in one location, but what it does is not known.
// Disable it anyway. // Disable it anyway.
it.method.insertLiteralOverride( it.method.insertLiteralOverride(

View file

@ -12,20 +12,23 @@ import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.actionBarSearchResultsMethod by gettingFirstMethodDeclaratively { internal val actionBarSearchResultsMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;") returnType("Landroid/view/View;")
instructions( instructions(
ResourceType.LAYOUT("action_bar_search_results_view_mic"), ResourceType.LAYOUT("action_bar_search_results_view_mic"),
methodCall(name = "setLayoutDirection"), method("setLayoutDirection"),
) )
} }
internal val BytecodePatchContext.toolbarLayoutMethod by gettingFirstMethodDeclaratively { internal val toolbarLayoutMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PROTECTED, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PROTECTED, AccessFlags.CONSTRUCTOR)
instructions( instructions(
ResourceType.ID("toolbar_container"), ResourceType.ID("toolbar_container"),
checkCast("Lcom/google/android/apps/youtube/app/ui/actionbar/MainCollapsingToolbarLayout;"), allOf(
Opcode.CHECK_CAST(),
type("Lcom/google/android/apps/youtube/app/ui/actionbar/MainCollapsingToolbarLayout;")
)
) )
} }
@ -33,12 +36,10 @@ internal val BytecodePatchContext.toolbarLayoutMethod by gettingFirstMethodDecla
* Matches to https://android.googlesource.com/platform/frameworks/support/+/9eee6ba/v7/appcompat/src/android/support/v7/widget/Toolbar.java#963 * Matches to https://android.googlesource.com/platform/frameworks/support/+/9eee6ba/v7/appcompat/src/android/support/v7/widget/Toolbar.java#963
*/ */
internal val BytecodePatchContext.appCompatToolbarBackButtonMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.appCompatToolbarBackButtonMethod by gettingFirstMethodDeclaratively {
definingClass("Landroid/support/v7/widget/Toolbar;")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/graphics/drawable/Drawable;") returnType("Landroid/graphics/drawable/Drawable;")
parameterTypes() parameterTypes()
custom { _, classDef ->
classDef.type == "Landroid/support/v7/widget/Toolbar;"
}
} }
/** /**
@ -57,62 +58,57 @@ internal fun ClassDef.getInitializeButtonsMethod() = firstMutableMethodDeclarati
* Extension method, used for callback into to other patches. * Extension method, used for callback into to other patches.
* Specifically, [navigationButtonsPatch]. * Specifically, [navigationButtonsPatch].
*/ */
internal val BytecodePatchContext.navigationBarHookCallbackMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.navigationBarHookCallbackMethod by gettingFirstMutableMethodDeclaratively {
name("navigationTabCreatedCallback")
definingClass(EXTENSION_CLASS_DESCRIPTOR)
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returnType("V") returnType("V")
parameterTypes(EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR, "Landroid/view/View;") parameterTypes(EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR, "Landroid/view/View;")
custom { method, _ ->
method.name == "navigationTabCreatedCallback" &&
method.definingClass == EXTENSION_CLASS_DESCRIPTOR
}
} }
/** /**
* Matches to the Enum class that looks up ordinal -> instance. * Matches to the Enum class that looks up ordinal -> instance.
*/ */
internal val BytecodePatchContext.navigationEnumMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.navigationEnumMethod by gettingFirstMethodDeclaratively(
"PIVOT_HOME",
"TAB_SHORTS",
"CREATION_TAB_LARGE",
"PIVOT_SUBSCRIPTIONS",
"TAB_ACTIVITY",
"VIDEO_LIBRARY_WHITE",
"INCOGNITO_CIRCLE",
"UNKNOWN", // Required to distinguish from patch extension class.
) {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
strings(
"PIVOT_HOME",
"TAB_SHORTS",
"CREATION_TAB_LARGE",
"PIVOT_SUBSCRIPTIONS",
"TAB_ACTIVITY",
"VIDEO_LIBRARY_WHITE",
"INCOGNITO_CIRCLE",
"UNKNOWN", // Required to distinguish from patch extension class.
)
} }
internal val BytecodePatchContext.pivotBarButtonsCreateDrawableViewMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.pivotBarButtonsCreateDrawableViewMethod by gettingFirstMethodDeclaratively {
definingClass("Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;") returnType("Landroid/view/View;")
custom { method, _ -> custom {
method.definingClass == "Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;" && // Only one view creation method has a Drawable parameter.
// Only one view creation method has a Drawable parameter. parameterTypes.firstOrNull() == "Landroid/graphics/drawable/Drawable;"
method.parameterTypes.firstOrNull() == "Landroid/graphics/drawable/Drawable;"
} }
} }
internal val BytecodePatchContext.pivotBarButtonsCreateResourceStyledViewMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.pivotBarButtonsCreateResourceStyledViewMethod by gettingFirstMethodDeclaratively {
definingClass("Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;") returnType("Landroid/view/View;")
parameterTypes("L", "Z", "I", "L") parameterTypes("L", "Z", "I", "L")
custom { method, _ ->
method.definingClass == "Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;"
}
} }
/** /**
* 20.21+ * 20.21+
*/ */
internal val BytecodePatchContext.pivotBarButtonsCreateResourceIntViewMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.pivotBarButtonsCreateResourceIntViewMethod by gettingFirstMethodDeclaratively {
definingClass("Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;") returnType("Landroid/view/View;")
custom { method, _ -> custom {
method.definingClass == "Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;" && // Only one view creation method has an int first parameter.
// Only one view creation method has an int first parameter. parameterTypes.firstOrNull() == "I"
method.parameterTypes.firstOrNull() == "I"
} }
} }
@ -131,7 +127,7 @@ internal val BytecodePatchContext.pivotBarConstructorMethod by gettingFirstMetho
) )
} }
internal val BytecodePatchContext.imageEnumConstructorMethod by gettingFirstMethodDeclaratively { internal val imageEnumConstructorMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
instructions( instructions(
"TAB_ACTIVITY_CAIRO"(), "TAB_ACTIVITY_CAIRO"(),
@ -140,13 +136,13 @@ internal val BytecodePatchContext.imageEnumConstructorMethod by gettingFirstMeth
) )
} }
internal val BytecodePatchContext.setEnumMapMethod by gettingFirstMethodDeclaratively { internal val setEnumMapMethodMatch = firstMethodComposite {
instructions( instructions(
ResourceType.DRAWABLE("yt_fill_bell_black_24"), ResourceType.DRAWABLE("yt_fill_bell_black_24"),
methodCall(smali = "Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;", afterAtMost(10)), afterAtMost(10, method { toString() == "Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;" }),
methodCall( afterAtMost(
smali = "Ljava/util/EnumMap;->put(Ljava/lang/Enum;Ljava/lang/Object;)Ljava/lang/Object;", 10,
afterAtMost(10), method { toString() == "Ljava/util/EnumMap;->put(Ljava/lang/Enum;Ljava/lang/Object;)Ljava/lang/Object;" }
), )
) )
} }

View file

@ -5,6 +5,7 @@ import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.instructions import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.extensions.reference
import app.revanced.patcher.immutableClassDef import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@ -72,7 +73,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
addInstruction( addInstruction(
insertIndex, insertIndex,
"invoke-static { v$register }, " + "invoke-static { v$register }, " +
"$EXTENSION_CLASS_DESCRIPTOR->${hook.methodName}(${hook.parameters})V", "$EXTENSION_CLASS_DESCRIPTOR->${hook.methodName}(${hook.parameters})V",
) )
} }
} }
@ -82,7 +83,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
val navigationEnumClassName = navigationEnumMethod.classDef.type val navigationEnumClassName = navigationEnumMethod.classDef.type
addHook(NavigationHook.SET_LAST_APP_NAVIGATION_ENUM) { addHook(NavigationHook.SET_LAST_APP_NAVIGATION_ENUM) {
opcode == Opcode.INVOKE_STATIC && opcode == Opcode.INVOKE_STATIC &&
getReference<MethodReference>()?.definingClass == navigationEnumClassName getReference<MethodReference>()?.definingClass == navigationEnumClassName
} }
// Hook the creation of navigation tab views. // Hook the creation of navigation tab views.
@ -120,7 +121,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
addInstruction( addInstruction(
index + 1, index + 1,
"invoke-static { v$viewRegister, v$isSelectedRegister }, " + "invoke-static { v$viewRegister, v$isSelectedRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->navigationTabSelected(Landroid/view/View;Z)V", "$EXTENSION_CLASS_DESCRIPTOR->navigationTabSelected(Landroid/view/View;Z)V",
) )
} }
@ -136,7 +137,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
// Two different layouts are used at the hooked code. // Two different layouts are used at the hooked code.
// Insert before the first ViewGroup method call after inflating, // Insert before the first ViewGroup method call after inflating,
// so this works regardless which layout is used. // so this works regardless which layout is used.
actionBarSearchResultsMethod.let { actionBarSearchResultsMethodMatch.let {
it.method.apply { it.method.apply {
val instructionIndex = it.indices.last() val instructionIndex = it.indices.last()
val viewRegister = getInstruction<FiveRegisterInstruction>(instructionIndex).registerC val viewRegister = getInstruction<FiveRegisterInstruction>(instructionIndex).registerC
@ -144,14 +145,14 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
addInstruction( addInstruction(
instructionIndex, instructionIndex,
"invoke-static { v$viewRegister }, " + "invoke-static { v$viewRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->searchBarResultsViewLoaded(Landroid/view/View;)V", "$EXTENSION_CLASS_DESCRIPTOR->searchBarResultsViewLoaded(Landroid/view/View;)V",
) )
} }
} }
// Hook the back button visibility. // Hook the back button visibility.
toolbarLayoutMethod.let { toolbarLayoutMethodMatch.let {
it.method.apply { it.method.apply {
val index = it.indices.last() val index = it.indices.last()
val register = getInstruction<OneRegisterInstruction>(index).registerA val register = getInstruction<OneRegisterInstruction>(index).registerA
@ -164,43 +165,41 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
} }
// Add interface for extensions code to call obfuscated methods. // Add interface for extensions code to call obfuscated methods.
appCompatToolbarBackButtonMethod.let { appCompatToolbarBackButtonMethod.classDef.apply {
it.classDef.apply { interfaces.add(EXTENSION_TOOLBAR_INTERFACE)
interfaces.add(EXTENSION_TOOLBAR_INTERFACE)
val definingClass = type val definingClass = type
val obfuscatedMethodName = it.immutableMethod.name val obfuscatedMethodName = appCompatToolbarBackButtonMethod.name
val returnType = "Landroid/graphics/drawable/Drawable;" val returnType = "Landroid/graphics/drawable/Drawable;"
methods.add( methods.add(
ImmutableMethod( ImmutableMethod(
definingClass, definingClass,
"patch_getNavigationIcon", "patch_getNavigationIcon",
listOf(), listOf(),
returnType, returnType,
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value, AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null, null,
null, null,
MutableMethodImplementation(2), MutableMethodImplementation(2),
).toMutable().apply { ).toMutable().apply {
addInstructions( addInstructions(
0, 0,
""" """
invoke-virtual { p0 }, $definingClass->$obfuscatedMethodName()$returnType invoke-virtual { p0 }, $definingClass->$obfuscatedMethodName()$returnType
move-result-object v0 move-result-object v0
return-object v0 return-object v0
""", """,
) )
}, },
) )
}
} }
hookNavigationButtonCreated = { extensionClassDescriptor -> hookNavigationButtonCreated = { extensionClassDescriptor ->
navigationBarHookCallbackMethod.addInstruction( navigationBarHookCallbackMethod.addInstruction(
0, 0,
"invoke-static { p0, p1 }, $extensionClassDescriptor->navigationTabCreated" + "invoke-static { p0, p1 }, $extensionClassDescriptor->navigationTabCreated" +
"(${EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR}Landroid/view/View;)V", "(${EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR}Landroid/view/View;)V",
) )
} }
@ -212,22 +211,24 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
// Fix YT bug of notification tab missing the filled icon. // Fix YT bug of notification tab missing the filled icon.
if (is_19_35_or_greater && !is_20_39_or_greater) { // FIXME: 20.39+ needs this fix. if (is_19_35_or_greater && !is_20_39_or_greater) { // FIXME: 20.39+ needs this fix.
val cairoNotificationEnumReference = imageEnumConstructorMethod val cairoNotificationEnumReference =
.instructionMatches.last().getInstruction<ReferenceInstruction>().reference imageEnumConstructorMethodMatch.method.getInstruction(imageEnumConstructorMethodMatch.indices.last()).reference
setEnumMapMethod.apply { setEnumMapMethodMatch.apply {
val setEnumIntegerIndex = setEnumMapMethod.indices.last() val setEnumIntegerIndex = setEnumMapMethodMatch.indices.last()
val enumMapRegister = getInstruction<FiveRegisterInstruction>(setEnumIntegerIndex).registerC method.apply {
val insertIndex = setEnumIntegerIndex + 1 val enumMapRegister = getInstruction<FiveRegisterInstruction>(setEnumIntegerIndex).registerC
val freeRegister = findFreeRegister(insertIndex, enumMapRegister) val insertIndex = setEnumIntegerIndex + 1
val freeRegister = findFreeRegister(insertIndex, enumMapRegister)
addInstructions( addInstructions(
insertIndex, insertIndex,
""" """
sget-object v$freeRegister, $cairoNotificationEnumReference sget-object v$freeRegister, $cairoNotificationEnumReference
invoke-static { v$enumMapRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->setCairoNotificationFilledIcon(Ljava/util/EnumMap;Ljava/lang/Enum;)V invoke-static { v$enumMapRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->setCairoNotificationFilledIcon(Ljava/util/EnumMap;Ljava/lang/Enum;)V
""", """,
) )
}
} }
} }
} }

View file

@ -7,7 +7,7 @@ import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.playerControlsVisibilityEntityModelMethod by gettingFirstMethodDeclaratively { internal val playerControlsVisibilityEntityModelMethodMatch = firstMethodComposite {
name("getPlayerControlsVisibility") name("getPlayerControlsVisibility")
accessFlags(AccessFlags.PUBLIC) accessFlags(AccessFlags.PUBLIC)
returnType("L") returnType("L")

View file

@ -16,7 +16,7 @@ val playerControlsOverlayVisibilityPatch = bytecodePatch {
dependsOn(sharedExtensionPatch) dependsOn(sharedExtensionPatch)
apply { apply {
playerControlsVisibilityEntityModelMethod.let { playerControlsVisibilityEntityModelMethodMatch.let {
it.method.apply { it.method.apply {
val startIndex = it.indices.first() val startIndex = it.indices.first()
val iGetReference = getInstruction<ReferenceInstruction>(startIndex).reference val iGetReference = getInstruction<ReferenceInstruction>(startIndex).reference

View file

@ -1,13 +1,12 @@
package app.revanced.patches.youtube.misc.recyclerviewtree.hook package app.revanced.patches.youtube.misc.recyclerviewtree.hook
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.opcodes import app.revanced.patcher.opcodes
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.recyclerViewTreeObserverMethod by gettingFirstMethodDeclaratively { internal val recyclerViewTreeObserverMethodMatch = firstMethodComposite("LithoRVSLCBinder") {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
opcodes( opcodes(
Opcode.CHECK_CAST, Opcode.CHECK_CAST,
@ -16,5 +15,4 @@ internal val BytecodePatchContext.recyclerViewTreeObserverMethod by gettingFirst
Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_VIRTUAL,
Opcode.NEW_INSTANCE, Opcode.NEW_INSTANCE,
) )
strings("LithoRVSLCBinder")
} }

View file

@ -11,12 +11,12 @@ val recyclerViewTreeHookPatch = bytecodePatch {
dependsOn(sharedExtensionPatch) dependsOn(sharedExtensionPatch)
apply { apply {
recyclerViewTreeObserverMethod.apply { recyclerViewTreeObserverMethodMatch.let {
val insertIndex = recyclerViewTreeObserverMethod.indices.first() + 1 val insertIndex = it.indices.first() + 1
val recyclerViewParameter = 2 val recyclerViewParameter = 2
addRecyclerViewTreeHook = { classDescriptor -> addRecyclerViewTreeHook = { classDescriptor ->
addInstruction( it.method.addInstruction(
insertIndex, insertIndex,
"invoke-static/range { p$recyclerViewParameter .. p$recyclerViewParameter }, " + "invoke-static/range { p$recyclerViewParameter .. p$recyclerViewParameter }, " +
"$classDescriptor->onFlyoutMenuCreate(Landroid/support/v7/widget/RecyclerView;)V", "$classDescriptor->onFlyoutMenuCreate(Landroid/support/v7/widget/RecyclerView;)V",

View file

@ -1,5 +1,6 @@
package app.revanced.patches.youtube.misc.spoof package app.revanced.patches.youtube.misc.spoof
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.shared.misc.settings.preference.ListPreference import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
@ -13,10 +14,11 @@ import app.revanced.patches.youtube.misc.playservice.is_20_14_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateMethod
val spoofVideoStreamsPatch = spoofVideoStreamsPatch( val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
extensionClassDescriptor = "Lapp/revanced/extension/youtube/patches/spoof/SpoofVideoStreamsPatch;", extensionClassDescriptor = "Lapp/revanced/extension/youtube/patches/spoof/SpoofVideoStreamsPatch;",
getMainActivityOnCreateMethod = mainActivityOnCreateFingerprint, getMainActivityOnCreateMethod = BytecodePatchContext::mainActivityOnCreateMethod::get,
fixMediaFetchHotConfig = { fixMediaFetchHotConfig = {
is_19_34_or_greater is_19_34_or_greater
}, },

View file

@ -78,7 +78,7 @@ internal val BytecodePatchContext.mainActivityOnCreateMethod by gettingFirstMuta
parameterTypes("Landroid/os/Bundle;") parameterTypes("Landroid/os/Bundle;")
} }
internal val BytecodePatchContext.rollingNumberTextViewAnimationUpdateMethod by gettingFirstMethodDeclaratively { internal val rollingNumberTextViewAnimationUpdateMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V") returnType("V")
parameterTypes("Landroid/graphics/Bitmap;") parameterTypes("Landroid/graphics/Bitmap;")

View file

@ -1,11 +1,13 @@
package app.revanced.patches.youtube.video.audio package app.revanced.patches.youtube.video.audio
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.shared.misc.audio.forceOriginalAudioPatch import app.revanced.patches.shared.misc.audio.forceOriginalAudioPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateMethod
@Suppress("unused") @Suppress("unused")
val forceOriginalAudioPatch = forceOriginalAudioPatch( val forceOriginalAudioPatch = forceOriginalAudioPatch(
@ -26,7 +28,7 @@ val forceOriginalAudioPatch = forceOriginalAudioPatch(
) )
}, },
fixUseLocalizedAudioTrackFlag = { is_20_07_or_greater }, fixUseLocalizedAudioTrackFlag = { is_20_07_or_greater },
getMainActivityOnCreateMethod = mainActivityOnCreateFingerprint, getMainActivityOnCreateMethod = BytecodePatchContext::mainActivityOnCreateMethod::get,
subclassExtensionClassDescriptor = "Lapp/revanced/extension/youtube/patches/ForceOriginalAudioPatch;", subclassExtensionClassDescriptor = "Lapp/revanced/extension/youtube/patches/ForceOriginalAudioPatch;",
preferenceScreen = PreferenceScreen.VIDEO, preferenceScreen = PreferenceScreen.VIDEO,
) )

View file

@ -2,15 +2,15 @@ package app.revanced.patches.youtube.video.codecs
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal val BytecodePatchContext.vp9CapabilityMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.vp9CapabilityMethod by gettingFirstMutableMethodDeclaratively(
"vp9_supported",
"video/x-vnd.on2.vp9",
) {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z") returnType("Z")
strings(
"vp9_supported",
"video/x-vnd.on2.vp9",
)
} }

View file

@ -7,7 +7,7 @@ import com.android.tools.smali.dexlib2.AccessFlags
/** /**
* For targets 20.46 and later. * For targets 20.46 and later.
*/ */
internal val BytecodePatchContext.playerParameterBuilderMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.playerParameterBuilderMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes( parameterTypes(
@ -33,7 +33,7 @@ internal val BytecodePatchContext.playerParameterBuilderMethod by gettingFirstMe
/** /**
* For targets 20.26 and later. * For targets 20.26 and later.
*/ */
internal val BytecodePatchContext.playerParameterBuilder2026Method by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.playerParameterBuilder2026Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes( parameterTypes(
@ -60,7 +60,7 @@ internal val BytecodePatchContext.playerParameterBuilder2026Method by gettingFir
/** /**
* For targets 20.15 to 20.25 * For targets 20.15 to 20.25
*/ */
internal val BytecodePatchContext.playerParameterBuilder2015Method by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.playerParameterBuilder2015Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes( parameterTypes(
@ -86,7 +86,7 @@ internal val BytecodePatchContext.playerParameterBuilder2015Method by gettingFir
/** /**
* For targets 20.10 to 20.14. * For targets 20.10 to 20.14.
*/ */
internal val BytecodePatchContext.playerParameterBuilder2010Method by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.playerParameterBuilder2010Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes( parameterTypes(
@ -113,7 +113,7 @@ internal val BytecodePatchContext.playerParameterBuilder2010Method by gettingFir
/** /**
* For targets 20.02 to 20.09. * For targets 20.02 to 20.09.
*/ */
internal val BytecodePatchContext.playerParameterBuilder2002Method by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.playerParameterBuilder2002Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes( parameterTypes(
@ -139,7 +139,7 @@ internal val BytecodePatchContext.playerParameterBuilder2002Method by gettingFir
/** /**
* For targets 19.25 to 19.50. * For targets 19.25 to 19.50.
*/ */
internal val BytecodePatchContext.playerParameterBuilder1925Method by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.playerParameterBuilder1925Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes( parameterTypes(
@ -164,7 +164,7 @@ internal val BytecodePatchContext.playerParameterBuilder1925Method by gettingFir
/** /**
* For targets 19.01 to 19.24. * For targets 19.01 to 19.24.
*/ */
internal val BytecodePatchContext.playerParameterBuilderLegacyMethod by gettingFirstMethodDeclaratively { internal val BytecodePatchContext.playerParameterBuilderLegacyMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L") returnType("L")
parameterTypes( parameterTypes(

View file

@ -40,30 +40,30 @@ val playerResponseMethodHookPatch = bytecodePatch {
) )
apply { apply {
val fingerprint: Fingerprint val method: MutableMethod
if (is_20_46_or_greater) { if (is_20_46_or_greater) {
parameterIsShortAndOpeningOrPlaying = 13 parameterIsShortAndOpeningOrPlaying = 13
fingerprint = playerParameterBuilderMethod method = playerParameterBuilderMethod
} else if (is_20_26_or_greater) { } else if (is_20_26_or_greater) {
parameterIsShortAndOpeningOrPlaying = 13 parameterIsShortAndOpeningOrPlaying = 13
fingerprint = playerParameterBuilder2026Method method = playerParameterBuilder2026Method
} else if (is_20_15_or_greater) { } else if (is_20_15_or_greater) {
parameterIsShortAndOpeningOrPlaying = 13 parameterIsShortAndOpeningOrPlaying = 13
fingerprint = playerParameterBuilder2015Method method = playerParameterBuilder2015Method
} else if (is_20_10_or_greater) { } else if (is_20_10_or_greater) {
parameterIsShortAndOpeningOrPlaying = 13 parameterIsShortAndOpeningOrPlaying = 13
fingerprint = playerParameterBuilder2010Method method = playerParameterBuilder2010Method
} else if (is_20_02_or_greater) { } else if (is_20_02_or_greater) {
parameterIsShortAndOpeningOrPlaying = 12 parameterIsShortAndOpeningOrPlaying = 12
fingerprint = playerParameterBuilder2002Method method = playerParameterBuilder2002Method
} else if (is_19_23_or_greater) { } else if (is_19_23_or_greater) {
parameterIsShortAndOpeningOrPlaying = 12 parameterIsShortAndOpeningOrPlaying = 12
fingerprint = playerParameterBuilder1925Method method = playerParameterBuilder1925Method
} else { } else {
parameterIsShortAndOpeningOrPlaying = 11 parameterIsShortAndOpeningOrPlaying = 11
fingerprint = playerParameterBuilderLegacyMethod method = playerParameterBuilderLegacyMethod
} }
playerResponseMethod = fingerprint.method playerResponseMethod = method
// On some app targets the method has too many registers pushing the parameters past v15. // On some app targets the method has too many registers pushing the parameters past v15.
// If needed, move the parameters to 4-bit registers, so they can be passed to the extension. // If needed, move the parameters to 4-bit registers, so they can be passed to the extension.

View file

@ -18,7 +18,7 @@ import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeH
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
internal var videoQualityBottomSheetListFragmentname = -1L internal var videoQualityBottomSheetListFragmentTitle = -1L
private set private set
internal var videoQualityQuickMenuAdvancedMenuDescription = -1L internal var videoQualityQuickMenuAdvancedMenuDescription = -1L
private set private set
@ -47,7 +47,7 @@ internal val advancedVideoQualityMenuPatch = bytecodePatch {
) )
// Used for the old type of the video quality menu. // Used for the old type of the video quality menu.
videoQualityBottomSheetListFragmentname = videoQualityBottomSheetListFragmentTitle =
ResourceType.LAYOUT["video_quality_bottom_sheet_list_fragment_title"] ResourceType.LAYOUT["video_quality_bottom_sheet_list_fragment_title"]
videoQualityQuickMenuAdvancedMenuDescription = videoQualityQuickMenuAdvancedMenuDescription =
ResourceType.STRING["video_quality_quick_menu_advanced_menu_description"] ResourceType.STRING["video_quality_quick_menu_advanced_menu_description"]