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 {
if (processLicenseResponseMethodOrNull == null || validateLicenseResponseMethodOrNull == null) {
if (processLicenseResponseMethod == null || validateLicenseResponseMethod == null) {
return@apply Logger.getLogger(this::class.java.name)
.warning("Could not find Pairip licensing check. No changes applied.")
}
// 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.
validateLicenseResponseMethod.returnEarly()
validateLicenseResponseMethod!!.returnEarly()
}
}

View File

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

View File

@@ -1,27 +1,28 @@
package app.revanced.patches.shared.misc.settings
import app.revanced.patcher.accessFlags
import app.revanced.patcher.definingClass
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.name
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.extension.EXTENSION_CLASS_DESCRIPTOR
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)
returnType("Ljava/lang/String;")
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)
returnType("Ljava/lang/String;")
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.custom
import app.revanced.patcher.definingClass
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.method
import app.revanced.patcher.name
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
@@ -77,12 +80,13 @@ internal val buildRequestMethodMatch = firstMethodComposite {
val parameterTypes = parameterTypes
val parameterTypesSize = parameterTypes.size
(parameterTypesSize == 6 || parameterTypesSize == 7 || parameterTypesSize == 8) &&
parameterTypes[1] == "Ljava/util/Map;" && // URL headers.
indexOfNewUrlRequestBuilderInstruction(this) >= 0
parameterTypes[1] == "Ljava/util/Map;" && // URL headers.
indexOfNewUrlRequestBuilderInstruction(this) >= 0
}
}
internal val BytecodePatchContext.protobufClassParseByteBufferMethod by gettingFirstMethodDeclaratively {
name("parseFrom")
accessFlags(AccessFlags.PROTECTED, AccessFlags.STATIC)
returnType("L")
parameterTypes("L", "Ljava/nio/ByteBuffer;")
@@ -92,7 +96,6 @@ internal val BytecodePatchContext.protobufClassParseByteBufferMethod by gettingF
Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT,
)
custom { method, _ -> method.name == "parseFrom" }
}
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)
parameterTypes(
"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)
returnType("Ljava/lang/String;")
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")
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++.
@@ -192,9 +194,9 @@ internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) = method.ind
val reference = methodReference ?: return@indexOfFirstInstruction false
opcode == Opcode.INVOKE_VIRTUAL && reference.definingClass == "Lorg/chromium/net/CronetEngine;" &&
reference.name == "newUrlRequestBuilder" &&
reference.parameterTypes.size == 3 &&
reference.parameterTypes[0] == "Ljava/lang/String;" &&
reference.parameterTypes[1] == "Lorg/chromium/net/UrlRequest\$Callback;" &&
reference.parameterTypes[2] == "Ljava/util/concurrent/Executor;"
reference.name == "newUrlRequestBuilder" &&
reference.parameterTypes.size == 3 &&
reference.parameterTypes[0] == "Ljava/lang/String;" &&
reference.parameterTypes[1] == "Lorg/chromium/net/UrlRequest\$Callback;" &&
reference.parameterTypes[2] == "Ljava/util/concurrent/Executor;"
}

View File

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

View File

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

View File

@@ -12,11 +12,11 @@ val `Remove file size limit` by creatingBytecodePatch(
compatibleWith("pl.solidexplorer2")
apply {
onReadyMethod.apply {
val cmpIndex = onReadyMethod.indices.first() + 1 // TODO
val cmpResultRegister = getInstruction<ThreeRegisterInstruction>(cmpIndex).registerA
onReadyMethodMatch.let {
val cmpIndex = it.indices.first() + 1
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.getInstruction
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
@@ -22,7 +23,7 @@ private val customThemeBytecodePatch = bytecodePatch {
val colorSpaceUtilsClassDef = colorSpaceUtilsClassMethod.immutableClassDef
// 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(
0,
"""
@@ -40,7 +41,7 @@ private val customThemeBytecodePatch = bytecodePatch {
val invokeParseColorIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/graphics/Color;" &&
reference.name == "parseColor"
reference.name == "parseColor"
}
val parsedColorRegister = getInstruction<OneRegisterInstruction>(invokeParseColorIndex + 1).registerA
@@ -61,7 +62,7 @@ private val customThemeBytecodePatch = bytecodePatch {
val invokeArgbIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/graphics/Color;" &&
reference.name == "argb"
reference.name == "argb"
}
val argbColorRegister = getInstruction<OneRegisterInstruction>(invokeArgbIndex + 1).registerA
@@ -97,7 +98,7 @@ val customThemePatch = resourcePatch(
default = false,
name = "Override player gradient color",
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,
)
@@ -105,7 +106,7 @@ val customThemePatch = resourcePatch(
default = "#FF121212",
name = "Secondary background color",
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,
)
@@ -120,7 +121,7 @@ val customThemePatch = resourcePatch(
default = "#FF1ABC54",
name = "Pressed accent color",
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,
)
@@ -143,38 +144,38 @@ val customThemePatch = resourcePatch(
node.textContent = when (name) {
// Main background color.
"gray_7",
// Left sidebar background color in tablet mode.
// Left sidebar background color in tablet mode.
"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",
// "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",
// Song/player gradient start/end color.
// Song/player gradient start/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",
// Misc.
// Misc.
"image_placeholder_color",
-> backgroundColor
-> backgroundColor
// "About the artist" background color in song player.
"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",
// Playlist list background in home page.
// Playlist list background in home page.
"opacity_white_10",
// "What's New" pills background.
// "What's New" pills background.
"dark_base_background_tinted_highlight",
-> backgroundColorSecondary
-> backgroundColorSecondary
"dark_brightaccent_background_base",
"dark_base_text_brightaccent",
"green_light",
"spotify_green_157",
-> accentColor
-> accentColor
"dark_brightaccent_background_press",
-> accentColorPressed
-> accentColorPressed
else -> continue
}

View File

@@ -1,32 +1,35 @@
package app.revanced.patches.spotify.layout.theme
import app.revanced.patcher.accessFlags
import app.revanced.patcher.firstMutableMethodDeclaratively
import app.revanced.patcher.gettingFirstMethod
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.patch.BytecodePatchContext
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.iface.ClassDef
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)
returnType("J")
parameterTypes("J")
}
internal val BytecodePatchContext.parseLottieJsonMethod by gettingFirstMethodDeclaratively {
strings("Unsupported matte type: ")
}
internal val BytecodePatchContext.parseLottieJsonMethod by gettingFirstMutableMethod("Unsupported matte type: ")
internal val BytecodePatchContext.parseAnimatedColorMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.parseAnimatedColorMethod by gettingFirstMutableMethodDeclaratively {
parameterTypes("L", "F")
returnType("Ljava/lang/Object;")
custom { method, _ ->
method.containsLiteralInstruction(255.0) &&
method.containsLiteralInstruction(1.0)
}
instructions(predicates = unorderedAllOf(255.0.toRawBits()(), 1.0.toRawBits()()))
}

View File

@@ -1,5 +1,12 @@
package app.revanced.patches.spotify.misc.extension
internal val BytecodePatchContext.loadOrbitLibraryMethod by gettingFirstMethodDeclaratively {
strings("orbit_library_load", "orbit-jni-spotify")
import app.revanced.patcher.firstMethodComposite
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.
extensionHook(
getInsertIndex = {
loadOrbitLibraryMethod.stringMatches.last().index
loadOrbitLibraryMethodMatch.stringMatches.last().index
},
getContextRegister = { method ->
val contextReferenceIndex = method.indexOfFirstInstruction {
@@ -25,6 +25,6 @@ internal val loadOrbitLibraryHook = extensionHook {
"v$contextRegister"
},
fingerprint = loadOrbitLibraryMethod,
fingerprint = loadOrbitLibraryMethodMatch,
)
}

View File

@@ -1,14 +1,18 @@
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.gettingFirstMutableMethod
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.literal
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.katanaProxyLoginMethodHandlerClassMethod by gettingFirstMethodDeclaratively {
strings("katana_proxy_auth")
}
internal val BytecodePatchContext.katanaProxyLoginMethodHandlerClassMethod by gettingFirstMethod("katana_proxy_auth")
internal val BytecodePatchContext.katanaProxyLoginMethodTryAuthorizeMethod by gettingFirstMethodDeclaratively {
strings("e2e")
literal { 0 }
context(_: BytecodePatchContext)
internal fun ClassDef.getKatanaProxyLoginMethodTryAuthorizeMethod() = firstMutableMethodDeclaratively("e2e") {
instructions(0L())
}

View File

@@ -1,12 +1,13 @@
package app.revanced.patches.spotify.misc.fix.login
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused", "ObjectPropertyName")
val `Fix Facebook login` by creatingBytecodePatch(
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")
@@ -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.
// Override the Facebook SDK to always handle the login using the web browser, which does not perform
// 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
// make the login fallback to a web browser window.
katanaProxyLoginMethodTryAuthorizeMethod
.match(katanaProxyLoginMethodHandlerClass)
.method
.returnEarly(0)
katanaProxyLoginMethodHandlerClassMethod.immutableClassDef
.getKatanaProxyLoginMethodTryAuthorizeMethod().returnEarly(0)
}
}

View File

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

View File

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

View File

@@ -1,10 +1,9 @@
package app.revanced.patches.tumblr.featureflags
import app.revanced.patcher.accessFlags
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.firstMethodComposite
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.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.
// 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.
internal val BytecodePatchContext.getFeatureValueMethod by gettingFirstMethodDeclaratively {
internal val getFeatureValueMethodMatch = firstMethodComposite("feature") {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Ljava/lang/String;")
parameterTypes("L", "Z")
@@ -27,5 +26,4 @@ internal val BytecodePatchContext.getFeatureValueMethod by gettingFirstMethodDec
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT,
)
strings("feature")
}

View File

@@ -24,54 +24,55 @@ val overrideFeatureFlagsPatch = bytecodePatch(
) {
apply {
val configurationClass = getFeatureValueMethod.immutableMethod.definingClass
val featureClass = getFeatureValueMethod.immutableMethod.parameterTypes[0].toString()
getFeatureValueMethodMatch.let { match ->
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
// 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
// get compiled to only have one register, which is not enough for our later injected instructions.
val helperMethod = ImmutableMethod(
getFeatureValueMethod.immutableMethod.definingClass,
"getValueOverride",
listOf(ImmutableMethodParameter(featureClass, null, "feature")),
"Ljava/lang/String;",
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null,
null,
MutableMethodImplementation(4),
).toMutable().apply {
// This is the equivalent of
// String featureName = feature.toString()
// <inject more instructions here later>
// return null
addInstructions(
0,
"""
# toString() the enum value
invoke-virtual {p1}, $featureClass->toString()Ljava/lang/String;
move-result-object v0
# !!! If you add more instructions above this line, update helperInsertIndex below!
# Here we will insert one compare & return for every registered Feature override
# This is done below in the addOverride function
# If none of the overrides returned a value, we should return null
const/4 v0, 0x0
return-object v0
""",
)
}.also { helperMethod ->
getFeatureValueMethod.classDef.methods.add(helperMethod)
}
// 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.
// 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.
val helperMethod = ImmutableMethod(
match.immutableMethod.definingClass,
"getValueOverride",
listOf(ImmutableMethodParameter(featureClass, null, "feature")),
"Ljava/lang/String;",
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null,
null,
MutableMethodImplementation(4),
).toMutable().apply {
// This is the equivalent of
// String featureName = feature.toString()
// <inject more instructions here later>
// return null
addInstructions(
0,
"""
# toString() the enum value
invoke-virtual {p1}, $featureClass->toString()Ljava/lang/String;
move-result-object v0
# !!! If you add more instructions above this line, update helperInsertIndex below!
# Here we will insert one compare & return for every registered Feature override
# This is done below in the addOverride function
# If none of the overrides returned a value, we should return null
const/4 v0, 0x0
return-object v0
""",
)
}.also { 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
// This is equivalent to
// String forcedValue = getValueOverride(feature)
// if (forcedValue != null) return forcedValue
val getFeatureIndex = getFeatureValueMethod.indices.first()
getFeatureValueMethod.addInstructionsWithLabels(
getFeatureIndex,
// Here we actually insert the hook to call our helper method and return its value if it returns not null
// This is equivalent to
// String forcedValue = getValueOverride(feature)
// if (forcedValue != null) return forcedValue
val getFeatureIndex = match.indices.first()
match.method.addInstructionsWithLabels(
getFeatureIndex,
"""
# Call the Helper Method with the Feature
invoke-virtual {p0, p1}, $configurationClass->getValueOverride($featureClass)Ljava/lang/String;
@@ -85,31 +86,32 @@ val overrideFeatureFlagsPatch = bytecodePatch(
:is_null
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
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.Opcode
// Fingerprint for the addQueryParam method from retrofit2
// https://github.com/square/retrofit/blob/trunk/retrofit/src/main/java/retrofit2/RequestBuilder.java#L186
// Injecting here allows modifying dynamically set query parameters
internal val BytecodePatchContext.addQueryParamMethod by gettingFirstMethodDeclaratively {
// Fingerprint for the addQueryParam method from retrofit2:
// https://github.com/square/retrofit/blob/trunk/retrofit/src/main/java/retrofit2/RequestBuilder.java#L186.
// Injecting here allows modifying dynamically set query parameters.
internal val BytecodePatchContext.addQueryParamMethod by gettingFirstMutableMethodDeclaratively("Malformed URL. Base: ", ", Relative: ") {
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
// Injecting here allows modifying the path/query params of API endpoints defined via annotations
internal val BytecodePatchContext.httpPathParserMethod by gettingFirstMethodDeclaratively {
// Injecting here allows modifying the path/query params of API endpoints defined via annotations.
internal val httpPathParserMethodMatch = firstMethodComposite("Only one HTTP method is allowed. Found: %s and %s.") {
opcodes(
Opcode.IPUT_OBJECT,
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.
for (liveQueryParameter in liveQueryParameters) {
httpPathParserMethod.addInstructions(
httpPathParserMethod.indices.last() + 1,
httpPathParserMethodMatch.method.addInstructions(
httpPathParserMethodMatch.indices.last() + 1,
"""
# urlPath = urlPath.replace(liveQueryParameter, "")
const-string p1, "$liveQueryParameter"

View File

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

View File

@@ -1,39 +1,33 @@
package app.revanced.patches.tumblr.timelinefilter
import app.revanced.patcher.accessFlags
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.opcodes
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
// This is the constructor of the PostsResponse class.
// 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)
custom { method, classDef -> classDef.endsWith("/PostsResponse;") && method.parameters.size == 4 }
custom { parameters.size == 4 }
}
// 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
// that the List<TimelineObject> is exposed in non-library code.
internal val BytecodePatchContext.timelineConstructorMethod by gettingFirstMethodDeclaratively {
strings("timelineObjectsList")
custom { method, classDef ->
classDef.endsWith("/Timeline;") && method.parameters[0].type == "Ljava/util/List;"
}
internal val BytecodePatchContext.timelineConstructorMethod by gettingFirstMutableMethodDeclaratively("timelineObjectsList") {
definingClass { endsWith("/Timeline;") }
custom { parameters[0].type == "Ljava/util/List;" }
}
// This fingerprints the extension TimelineFilterPatch.filterTimeline method.
// The opcode fingerprint is searching for
// 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(
Opcode.CONST_STRING, // "BLOCKED_OBJECT_DUMMY"
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
import app.revanced.patcher.accessFlags
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.opcodes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.buildMediaOptionsSheetMethod by gettingFirstMethodDeclaratively {
internal val buildMediaOptionsSheetMethodMatch = firstMethodComposite("mediaEntity", "media_options_sheet") {
opcodes(
Opcode.IF_EQ,
Opcode.SGET_OBJECT,
Opcode.GOTO_16,
Opcode.NEW_INSTANCE,
)
strings("mediaEntity", "media_options_sheet")
}
internal val BytecodePatchContext.constructMediaOptionsSheetMethod by gettingFirstMethodDeclaratively {
internal val constructMediaOptionsSheetMethodMatch = firstMethodComposite("captionsState") {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
strings("captionsState")
}
internal val BytecodePatchContext.showDownloadVideoUpsellBottomSheetMethod by gettingFirstMethodDeclaratively {
internal val showDownloadVideoUpsellBottomSheetMethodMatch = firstMethodComposite("mediaEntity", "url") {
returnType("Z")
strings("mediaEntity", "url")
opcodes(Opcode.IF_EQZ)
}

View File

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

View File

@@ -1,15 +1,12 @@
package app.revanced.patches.youtube.ad.getpremium
import app.revanced.patcher.accessFlags
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 app.revanced.patcher.*
import com.android.tools.smali.dexlib2.AccessFlags
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)
returnType("V")
parameterTypes("I", "I")
@@ -19,8 +16,4 @@ internal val BytecodePatchContext.getPremiumViewMethod by gettingFirstMethodDecl
Opcode.INVOKE_VIRTUAL,
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"),
)
getPremiumViewMethod.apply {
val startIndex = getPremiumViewMethod.indices.first()
val measuredWidthRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA
val measuredHeightInstruction = getInstruction<TwoRegisterInstruction>(startIndex + 1)
getPremiumViewMethodMatch.let {
val startIndex = it.indices.first()
val measuredWidthRegister = it.method.getInstruction<TwoRegisterInstruction>(startIndex).registerA
val measuredHeightInstruction = it.method.getInstruction<TwoRegisterInstruction>(startIndex + 1)
val measuredHeightRegister = measuredHeightInstruction.registerA
val tempRegister = measuredHeightInstruction.registerB
addInstructionsWithLabels(
it.method.addInstructionsWithLabels(
startIndex + 2,
"""
# Override the internal measurement of the layout with zero values.

View File

@@ -1,18 +1,14 @@
package app.revanced.patches.youtube.interaction.dialog
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patcher.*
internal val BytecodePatchContext.createDialogMethod by gettingFirstMethodDeclaratively {
internal val createDialogMethodMatch = firstMethodComposite {
returnType("V")
parameterTypes("L", "L", "Ljava/lang/String;")
instructions(
methodCall(smali = "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;"),
methodCall(smali = "Landroid/app/AlertDialog\$Builder;->create()Landroid/app/AlertDialog;"),
methodCall(smali = "Landroid/app/AlertDialog;->show()V"),
method { toString() == $$"Landroid/app/AlertDialog$Builder;->setNegativeButton(ILandroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;" },
method { toString() == $$"Landroid/app/AlertDialog$Builder;->setOnCancelListener(Landroid/content/DialogInterface$OnCancelListener;)Landroid/app/AlertDialog$Builder;" },
method { toString() == $$"Landroid/app/AlertDialog$Builder;->create()Landroid/app/AlertDialog;" },
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"),
)
createDialogMethod.let {
createDialogMethodMatch.let {
it.method.apply {
val showDialogIndex = it.indices.last() // TODO
val showDialogIndex = it.indices.last()
val dialogRegister = getInstruction<FiveRegisterInstruction>(showDialogIndex).registerC
replaceInstructions(

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
package app.revanced.patches.youtube.interaction.swipecontrols
import app.revanced.patcher.accessFlags
import app.revanced.patcher.definingClass
import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
@@ -9,14 +11,12 @@ import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
internal val BytecodePatchContext.swipeControlsHostActivityMethod by gettingFirstMethodDeclaratively {
definingClass(EXTENSION_CLASS_DESCRIPTOR)
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameterTypes()
custom { method, _ ->
method.definingClass == EXTENSION_CLASS_DESCRIPTOR
}
}
internal val BytecodePatchContext.swipeChangeVideoMethod by gettingFirstMethodDeclaratively {
internal val swipeChangeVideoMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions(
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.
if (is_19_43_or_greater && !is_20_34_or_greater) {
swipeChangeVideoMethod.let {
swipeChangeVideoMethodMatch.let {
it.method.insertLiteralOverride(
it.indices.last(), // TODO
it.indices.last(),
"$EXTENSION_CLASS_DESCRIPTOR->allowSwipeChangeVideo(Z)Z",
)
}

View File

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

View File

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

View File

@@ -1,30 +1,26 @@
package app.revanced.patches.youtube.layout.hide.endscreensuggestion
import app.revanced.patcher.accessFlags
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.*
import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.extensions.methodReference
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.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")
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)
returnType("Z")
parameterTypes()
}
internal val BytecodePatchContext.removeOnLayoutChangeListenerMethod by gettingFirstMethodDeclaratively {
internal val removeOnLayoutChangeListenerMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes()
@@ -33,11 +29,10 @@ internal val BytecodePatchContext.removeOnLayoutChangeListenerMethod by gettingF
Opcode.INVOKE_VIRTUAL,
)
// This is the only reference present in the entire smali.
custom { method, _ ->
method.indexOfFirstInstruction {
val reference = getReference<MethodReference>()
reference?.name == "removeOnLayoutChangeListener" &&
reference.definingClass.endsWith("/YouTubePlayerOverlaysLayout;")
} >= 0
custom {
instructions.anyInstruction {
val reference = methodReference
reference?.name == "removeOnLayoutChangeListener" && reference.definingClass.endsWith("/YouTubePlayerOverlaysLayout;")
}
}
}

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

View File

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

View File

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

View File

@@ -1,24 +1,18 @@
package app.revanced.patches.youtube.layout.hide.infocards
import app.revanced.patcher.accessFlags
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.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
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)
returnType("Ljava/lang/Boolean;")
parameterTypes("L", "J")
instructions(
"vibrator"(),
)
instructions("vibrator"())
}
internal val BytecodePatchContext.infocardsIncognitoParentMethod by gettingFirstMethodDeclaratively {
@@ -29,12 +23,12 @@ internal val BytecodePatchContext.infocardsIncognitoParentMethod by gettingFirst
)
}
internal val BytecodePatchContext.infocardsMethodCallMethod by gettingFirstMethodDeclaratively {
opcodes(
Opcode.INVOKE_VIRTUAL,
Opcode.IGET_OBJECT,
Opcode.INVOKE_INTERFACE,
)
strings("Missing ControlsOverlayPresenter for InfoCards to work.")
literal { drawerResourceId }
}
internal val infocardsMethodCallMethodMatch =
firstMethodComposite("Missing ControlsOverlayPresenter for InfoCards to work.") {
opcodes(
Opcode.INVOKE_VIRTUAL,
Opcode.IGET_OBJECT,
Opcode.INVOKE_INTERFACE,
)
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.addInstructionsWithLabels
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources
@@ -27,10 +28,7 @@ private val hideInfocardsResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch)
apply {
drawerResourceId = getResourceId(
ResourceType.ID,
"info_cards_drawer_header",
)
drawerResourceId = 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.
infocardsIncognitoMethod.match(infocardsIncognitoParentMethod.immutableClassDef).method.apply {
infocardsIncognitoParentMethod.immutableClassDef.getInfocardsIncognitoMethod().apply {
val invokeInstructionIndex = implementation!!.instructions.indexOfFirst {
it.opcode.ordinal == Opcode.INVOKE_VIRTUAL.ordinal &&
((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.
infocardsMethodCallMethod.let {
infocardsMethodCallMethodMatch.let {
val invokeInterfaceIndex = it.indices.last()
it.method.apply {
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.settings.PreferenceScreen
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
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,
// which prevents the animations from appearing.
rollingNumberTextViewAnimationUpdateMethod.let {
rollingNumberTextViewAnimationUpdateMethodMatch.let {
val blockStartIndex = it.indices.first()
val blockEndIndex = it.indices.last() + 1
it.method.apply {

View File

@@ -2,19 +2,12 @@
package app.revanced.patches.youtube.layout.miniplayer
import app.revanced.patcher.accessFlags
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.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
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_ANIMATED_EXPAND_FEATURE_KEY = 45644360L
internal val BytecodePatchContext.miniplayerModernConstructorMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.miniplayerModernConstructorMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions(
45623000L(), // Magic number found in the constructor.
@@ -56,7 +49,8 @@ internal val BytecodePatchContext.miniplayerModernViewParentMethod by gettingFir
/**
* 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)
returnType("V")
parameterTypes("Landroid/view/View;")
@@ -65,33 +59,34 @@ internal val BytecodePatchContext.miniplayerModernAddViewListenerMethod by getti
/**
* Matches using the class found in [miniplayerModernViewParentMethod].
*/
internal val BytecodePatchContext.miniplayerModernCloseButtonMethod by gettingFirstMethodDeclaratively {
internal val miniplayerModernCloseButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes()
instructions(
ResourceType.ID("modern_miniplayer_close"),
checkCast("Landroid/widget/ImageView;"),
allOf(Opcode.CHECK_CAST(), "Landroid/widget/ImageView;"()),
)
}
/**
* Matches using the class found in [miniplayerModernViewParentMethod].
*/
internal val BytecodePatchContext.miniplayerModernExpandButtonMethod by gettingFirstMethodDeclaratively {
internal val miniplayerModernExpandButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes()
instructions(
ResourceType.ID("modern_miniplayer_expand"),
checkCast("Landroid/widget/ImageView;"),
allOf(Opcode.CHECK_CAST(), "Landroid/widget/ImageView;"()),
)
}
/**
* 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)
returnType("V")
parameterTypes("L")
@@ -103,7 +98,7 @@ internal val BytecodePatchContext.miniplayerModernExpandCloseDrawablesMethod by
/**
* Matches using the class found in [miniplayerModernViewParentMethod].
*/
internal val BytecodePatchContext.miniplayerModernForwardButtonMethod by gettingFirstMethodDeclaratively {
internal val miniplayerModernForwardButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
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)
parameterTypes()
instructions(
@@ -125,7 +120,7 @@ internal val BytecodePatchContext.miniplayerModernOverlayViewMethod by gettingFi
/**
* Matches using the class found in [miniplayerModernViewParentMethod].
*/
internal val BytecodePatchContext.miniplayerModernRewindButtonMethod by gettingFirstMethodDeclaratively {
internal val miniplayerModernRewindButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes()
@@ -138,7 +133,7 @@ internal val BytecodePatchContext.miniplayerModernRewindButtonMethod by gettingF
/**
* Matches using the class found in [miniplayerModernViewParentMethod].
*/
internal val BytecodePatchContext.miniplayerModernActionButtonMethod by gettingFirstMethodDeclaratively {
internal val miniplayerModernActionButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
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)
instructions(
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)
returnType("L")
instructions(
"appName"(),
methodCall(
parameters = listOf("Landroid/content/Context;"),
returnType = "Z",
afterAtMost(10),
afterAtMost(
10,
method { parameterTypes.count() == 1 && parameterTypes.first() == "Landroid/content/Context;" && returnType == "Z" },
),
)
}
internal val BytecodePatchContext.miniplayerOverrideNoContextMethod by gettingFirstMethodDeclaratively {
context(_: BytecodePatchContext)
internal fun ClassDef.getMiniplayerOverrideNoContextMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
returnType("Z")
opcodes(
@@ -181,7 +176,7 @@ internal val BytecodePatchContext.miniplayerOverrideNoContextMethod by gettingFi
/**
* 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)
returnType("L")
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)
returnType("Z")
instructions(
@@ -207,12 +202,10 @@ internal const val YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME =
"Lcom/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout;"
internal val BytecodePatchContext.playerOverlaysLayoutMethod by gettingFirstMethodDeclaratively {
custom { method, _ ->
method.definingClass == YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME
}
definingClass(YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME)
}
internal val BytecodePatchContext.miniplayerSetIconsMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.miniplayerSetIconsMethod by gettingFirstMutableMethodDeclaratively {
returnType("V")
parameterTypes("I", "Ljava/lang/Runnable;")
instructions(

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package app.revanced.patches.youtube.layout.player.fullscreen
import app.revanced.patcher.MatchBuilder
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
@@ -23,14 +24,14 @@ internal val openVideosFullscreenHookPatch = bytecodePatch {
)
apply {
var fingerprint: Fingerprint
var match: MatchBuilder
var insertIndex: Int
if (is_19_46_or_greater) {
fingerprint = openVideosFullscreenPortraitMethod
insertIndex = fingerprint.indices.first()
match = openVideosFullscreenPortraitMethodMatch
insertIndex = match.indices.first()
openVideosFullscreenPortraitMethod.let {
openVideosFullscreenPortraitMethodMatch.let {
// 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+
// Shorts fullscreen regular player does not use fullscreen
@@ -41,22 +42,20 @@ internal val openVideosFullscreenHookPatch = bytecodePatch {
)
}
} else {
fingerprint = openVideosFullscreenPortraitLegacyMethod
insertIndex = fingerprint.indices.last()
match = openVideosFullscreenPortraitLegacyMethodMatch
insertIndex = match.indices.last()
}
fingerprint.let {
it.method.apply {
val register = getInstruction<OneRegisterInstruction>(insertIndex).registerA
match.method.apply {
val register = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstructions(
insertIndex + 1,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->doNotOpenVideoFullscreenPortrait(Z)Z
move-result v$register
""",
)
}
addInstructions(
insertIndex + 1,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->doNotOpenVideoFullscreenPortrait(Z)Z
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),
)
createPlayerOverviewMethod.let {
createPlayerOverviewMethodMatch.let {
it.method.apply {
val viewRegisterIndex = it.indices.last()
val viewRegister = getInstruction<OneRegisterInstruction>(viewRegisterIndex).registerA

View File

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

View File

@@ -1,7 +1,10 @@
package app.revanced.patches.youtube.layout.returnyoutubedislike
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.fieldReference
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.creatingBytecodePatch
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.settingsPatch
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.hookVideoId
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.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import java.util.logging.Logger
import kotlin.jvm.java
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch;"
@@ -102,8 +104,8 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
likeMethod to Vote.LIKE,
dislikeMethod to Vote.DISLIKE,
removeLikeMethod to Vote.REMOVE_LIKE,
).forEach { (fingerprint, vote) ->
fingerprint.method.addInstructions(
).forEach { (method, vote) ->
method.addInstructions(
0,
"""
const/4 v0, ${vote.value}
@@ -128,7 +130,7 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
it.type == conversionContextClass.superclass
} ?: 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.
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) {
val index = indexOfFirstInstructionOrThrow {
(opcode == Opcode.INVOKE_STATIC || opcode == Opcode.INVOKE_STATIC_RANGE) &&
getReference<MethodReference>()?.returnType == textDataClassType
methodReference?.returnType == textDataClassType
}
insertIndex = indexOfFirstInstructionOrThrow(index) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.parameterTypes?.firstOrNull() == "Ljava/lang/CharSequence;"
methodReference?.parameterTypes?.firstOrNull() == "Ljava/lang/CharSequence;"
}
charSequenceRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
} else {
insertIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.NEW_INSTANCE &&
getReference<TypeReference>()?.type == textDataClassType
fieldReference?.type == textDataClassType
}
val charSequenceIndex = indexOfFirstInstructionOrThrow(insertIndex) {
opcode == Opcode.IPUT_OBJECT &&
getReference<FieldReference>()?.type == "Ljava/lang/CharSequence;"
fieldReference?.type == "Ljava/lang/CharSequence;"
}
charSequenceRegister = getInstruction<TwoRegisterInstruction>(charSequenceIndex).registerA
}
@@ -221,13 +223,11 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
// region Hook rolling numbers.
rollingNumberSetterMethod.apply {
rollingNumberSetterMethodMatch.method.apply {
val insertIndex = 1
val dislikesIndex = rollingNumberSetterMethod.indices.last()
val charSequenceInstanceRegister =
getInstruction<OneRegisterInstruction>(0).registerA
val charSequenceFieldReference =
getInstruction<ReferenceInstruction>(dislikesIndex).reference
val dislikesIndex = rollingNumberSetterMethodMatch.indices.last()
val charSequenceInstanceRegister = getInstruction<OneRegisterInstruction>(0).registerA
val charSequenceFieldReference = getInstruction<ReferenceInstruction>(dislikesIndex).reference
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.
// 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
if (it.indices.first() != 0) throw PatchException("Unexpected opcode location")
val endIndex = it.indices.last()
@@ -290,11 +290,11 @@ val `Return YouTube Dislike` by creatingBytecodePatch(
rollingNumberTextViewMethod,
// 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.
rollingNumberTextViewAnimationUpdateMethod,
rollingNumberTextViewAnimationUpdateMethodMatch.method,
).forEach { insertMethod ->
insertMethod.apply {
val setTextIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "setText"
methodReference?.name == "setText"
}
val textViewRegister = getInstruction<FiveRegisterInstruction>(setTextIndex).registerC

View File

@@ -1,33 +1,22 @@
package app.revanced.patches.youtube.layout.seekbar
import app.revanced.patcher.accessFlags
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.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.fullscreenSeekbarThumbnailsMethod by gettingFirstMethodDeclaratively {
returnType("Z")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes()
instructions(
45398577(),
45398577L(),
)
}
internal val BytecodePatchContext.playerSeekbarColorMethod by gettingFirstMethodDeclaratively {
internal val playerSeekbarColorMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions(
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+
internal val BytecodePatchContext.setSeekbarClickedColorMethod by gettingFirstMethodDeclaratively {
internal val setSeekbarClickedColorMethodMatch = firstMethodComposite(
"YOUTUBE",
"PREROLL",
"POSTROLL",
"REMOTE_LIVE",
"AD_LARGE_CONTROLS",
) {
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)
instructions(
ResourceType.COLOR("reel_time_bar_played_color"),
)
}
internal val BytecodePatchContext.playerSeekbarHandle1ColorMethod by gettingFirstMethodDeclaratively {
internal val playerSeekbarHandle1ColorMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions(
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)
parameterTypes("Landroid/content/Context;")
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)
returnType("V")
parameterTypes("L")
instructions(
methodCall("Landroid/widget/ProgressBar;", "setMax"),
method { name == "setMax" && definingClass == "Landroid/widget/ProgressBar;" },
Opcode.MOVE_RESULT(),
-1712394514(),
(-1712394514L)(),
)
}
internal val BytecodePatchContext.lithoLinearGradientMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.lithoLinearGradientMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.STATIC)
returnType("Landroid/graphics/LinearGradient;")
parameterTypes("F", "F", "F", "F", "[I", "[F")
@@ -85,7 +79,7 @@ internal val BytecodePatchContext.lithoLinearGradientMethod by gettingFirstMetho
/**
* 19.49+
*/
internal val BytecodePatchContext.playerLinearGradientMethod by gettingFirstMethodDeclaratively {
internal val playerLinearGradientMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
parameterTypes("I", "I", "I", "I", "Landroid/content/Context;", "I")
returnType("Landroid/graphics/LinearGradient;")
@@ -100,7 +94,7 @@ internal val BytecodePatchContext.playerLinearGradientMethod by gettingFirstMeth
/**
* 19.25 - 19.47
*/
internal val BytecodePatchContext.playerLinearGradientLegacyMethod by gettingFirstMethodDeclaratively {
internal val playerLinearGradientLegacyMethodMatch = firstMethodComposite {
returnType("V")
instructions(
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)
*/
internal val BytecodePatchContext.lottieCompositionFactoryFromJsonInputStreamMethod by gettingFirstMethodDeclaratively {
context(_: BytecodePatchContext)
internal fun ClassDef.getLottieCompositionFactoryFromJsonInputStreamMethod() = firstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
parameterTypes("Ljava/io/InputStream;", "Ljava/lang/String;")
returnType("L")
instructions(
anyInstruction(literal(2), literal(3)),
anyOf(2L(), 3L()),
)
}

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package app.revanced.patches.youtube.layout.shortsautoplay
import app.revanced.patcher.classDef
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.addInstructionsWithLabels
@@ -88,15 +89,15 @@ val `Shorts autoplay` by creatingBytecodePatch(
)
}
reelPlaybackRepeatMethod.match(
reelPlaybackRepeatParentMethod.immutableClassDef,
).method.apply {
val reelPlaybackRepeatMethod = reelPlaybackRepeatParentMethod.immutableClassDef.getReelPlaybackRepeatMethod()
reelPlaybackRepeatMethod.apply {
// The behavior enums are looked up from an ordinal value to an enum type.
findInstructionIndicesReversedOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == reelEnumClass &&
reference.parameterTypes.firstOrNull() == "I" &&
reference.returnType == reelEnumClass
reference.parameterTypes.firstOrNull() == "I" &&
reference.returnType == reelEnumClass
}.forEach { index ->
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.
val extensionReturnResultIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_STATIC &&
getReference<MethodReference>()?.definingClass == EXTENSION_CLASS_DESCRIPTOR
getReference<MethodReference>()?.definingClass == EXTENSION_CLASS_DESCRIPTOR
} + 1
val enumRegister = getInstruction<OneRegisterInstruction>(extensionReturnResultIndex).registerA
val getReelSequenceControllerIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<FieldReference>()
opcode == Opcode.IGET_OBJECT &&
reference?.definingClass == definingClass &&
reference.type == reelSequenceControllerMethodReference.definingClass
reference?.definingClass == definingClass &&
reference.type == reelSequenceControllerMethodReference.definingClass
}
val getReelSequenceControllerReference =
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.Opcode
internal val BytecodePatchContext.toolBarButtonMethod by gettingFirstMethodDeclaratively {
internal val toolBarButtonMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
instructions(
@@ -21,7 +21,7 @@ internal val BytecodePatchContext.toolBarButtonMethod by gettingFirstMethodDecla
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
// manually set the desired version string as this keyed value in the SharedPreferences.
// 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
* toolbar when the enum name is UNKNOWN.
*/
toolBarButtonMethod.apply {
val imageResourceIndex = indices[2]
val register = method.getInstruction<OneRegisterInstruction>(imageResourceIndex).registerA
val jumpIndex = indices.last() + 1
toolBarButtonMethodMatch.let {
val imageResourceIndex = it.indices[2]
val register = it.method.getInstruction<OneRegisterInstruction>(imageResourceIndex).registerA
val jumpIndex = it.indices.last() + 1
method.addInstructionsWithLabels(
it.method.addInstructionsWithLabels(
imageResourceIndex + 1,
"if-eqz v$register, :ignore",
ExternalLabel("ignore", method.getInstruction(jumpIndex)),
ExternalLabel("ignore", it.method.getInstruction(jumpIndex)),
)
}
spoofAppVersionMethod.apply {
val index = indices.first()
val register = method.getInstruction<OneRegisterInstruction>(index).registerA
spoofAppVersionMethodMatch.let {
val index = it.indices.first()
val register = it.method.getInstruction<OneRegisterInstruction>(index).registerA
method.addInstructions(
it.method.addInstructions(
index + 1,
"""
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
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.creatingBytecodePatch
import app.revanced.patches.all.misc.resources.addResources
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) {
userWasInShortsAlternativeMethod.let {
userWasInShortsAlternativeMethodMatch.let {
it.method.apply {
val match = it.instructionMatches[2]
val insertIndex = match.index + 1
val register = match.getInstruction<OneRegisterInstruction>().registerA
val insertIndex = it.indices[2] + 1
val register = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstructions(
insertIndex,
@@ -68,8 +68,8 @@ val `Disable resuming Shorts on startup` by creatingBytecodePatch(
userWasInShortsLegacyMethod.apply {
val listenableInstructionIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_INTERFACE &&
getReference<MethodReference>()?.definingClass == "Lcom/google/common/util/concurrent/ListenableFuture;" &&
getReference<MethodReference>()?.name == "isDone"
getReference<MethodReference>()?.definingClass == "Lcom/google/common/util/concurrent/ListenableFuture;" &&
getReference<MethodReference>()?.name == "isDone"
}
val freeRegister = findFreeRegister(listenableInstructionIndex)

View File

@@ -1,30 +1,23 @@
package app.revanced.patches.youtube.layout.startupshortsreset
import app.revanced.patcher.StringComparisonType
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.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
/**
* 20.02+
*/
internal val BytecodePatchContext.userWasInShortsAlternativeMethod by gettingFirstMethodDeclaratively {
internal val userWasInShortsAlternativeMethodMatch = firstMethodComposite {
returnType("V")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes("Ljava/lang/Object;")
instructions(
checkCast("Ljava/lang/Boolean;"),
methodCall(smali = "Ljava/lang/Boolean;->booleanValue()Z", after()),
allOf(Opcode.CHECK_CAST(), type("Ljava/lang/Boolean;")),
after(method { toString() == "Ljava/lang/Boolean;->booleanValue()Z" }),
after(Opcode.MOVE_RESULT()),
// 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 = {
val lightThemeBackgroundColor by stringOption(
key = "lightThemeBackgroundColor",
name = "Light theme background color",
default = "@android:color/white",
values = mapOf(
"White" to "@android:color/white",
@@ -46,7 +46,6 @@ val themePatch = baseThemePatch(
"Light orange" to "#FFE6CC",
"Light red" to "#FFD6D6",
),
name = "Light theme background color",
description = THEME_COLOR_OPTION_DESCRIPTION,
)

View File

@@ -56,16 +56,13 @@ val `Remove background playback restrictions` by creatingBytecodePatch(
SwitchPreference("revanced_shorts_disable_background_playback"),
)
prefBackgroundAndOfflineCategoryId = getResourceId(
ResourceType.STRING,
"pref_background_and_offline_category",
)
prefBackgroundAndOfflineCategoryId = ResourceType.STRING["pref_background_and_offline_category"]
arrayOf(
backgroundPlaybackManagerMethod to "isBackgroundPlaybackAllowed",
backgroundPlaybackManagerShortsMethod to "isBackgroundShortsPlaybackAllowed",
).forEach { (fingerprint, integrationsMethod) ->
fingerprint.method.apply {
).forEach { (method, integrationsMethod) ->
method.apply {
findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index).registerA
@@ -81,7 +78,7 @@ val `Remove background playback restrictions` by creatingBytecodePatch(
}
// Enable background playback option in YouTube settings
backgroundPlaybackSettingsMethod.immutableMethod.apply {
backgroundPlaybackSettingsMethod.apply {
val booleanCalls = instructions.withIndex().filter {
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.
if (is_19_34_or_greater) {
pipInputConsumerFeatureFlagMethod.let {
pipInputConsumerFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it.indices.first(),
false,

View File

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

View File

@@ -1,7 +1,9 @@
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.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateMethod
val checkWatchHistoryDomainNameResolutionPatch = checkWatchHistoryDomainNameResolutionPatch(
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
import app.revanced.patcher.accessFlags
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 app.revanced.patcher.*
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@@ -29,11 +20,11 @@ internal val recyclerViewTopScrollingMethodMatch = firstMethodComposite {
returnType("V")
parameterTypes()
instructions(
methodCall(smali = "Ljava/util/Iterator;->next()Ljava/lang/Object;"),
method { toString() == "Ljava/util/Iterator;->next()Ljava/lang/Object;" },
after(Opcode.MOVE_RESULT_OBJECT()),
checkCast("Landroid/support/v7/widget/RecyclerView;", MatchAfterImmediately()),
literal(0, after()),
methodCall(definingClass = "Landroid/support/v7/widget/RecyclerView;", after()),
after(allOf(Opcode.CHECK_CAST(), type("Landroid/support/v7/widget/RecyclerView;"))),
after(0L()),
after(method { definingClass == "Landroid/support/v7/widget/RecyclerView;" }),
after(Opcode.GOTO()),
)
}

View File

@@ -1,7 +1,9 @@
package app.revanced.patches.youtube.misc.fix.playbackspeed
import app.revanced.patcher.accessFlags
import app.revanced.patcher.custom
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
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.
* 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)
returnType("V")
parameterTypes("L")
@@ -31,8 +33,8 @@ internal val BytecodePatchContext.playbackSpeedInFeedsMethod by gettingFirstMeth
Opcode.IF_LEZ,
Opcode.SUB_LONG_2ADDR,
)
custom { method, _ ->
indexOfGetPlaybackSpeedInstruction(method) >= 0
custom {
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.patch.BytecodePatchContext
import app.revanced.util.containsLiteralInstruction
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.componentCreateMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.componentCreateMethod by gettingFirstMutableMethodDeclaratively {
instructions(
"Element missing correct type extension"(),
"Element missing type"(),
)
}
internal val BytecodePatchContext.lithoFilterMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.lithoFilterMethod by gettingFirstMutableMethodDeclaratively {
definingClass { endsWith("/LithoFilterPatch;") }
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
}
internal val BytecodePatchContext.protobufBufferReferenceMethod by gettingFirstMethodDeclaratively {
internal val protobufBufferReferenceMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
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)
returnType("V")
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 }
}
internal val BytecodePatchContext.lithoThreadExecutorMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.lithoThreadExecutorMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameterTypes("I", "I", "I")
instructions(1L()) // 1L = default thread timeout.
custom {
immutableClassDef.superclass == "Ljava/util/concurrent/ThreadPoolExecutor;" &&
containsLiteralInstruction(1L) // 1L = default thread timeout.
immutableClassDef.superclass == "Ljava/util/concurrent/ThreadPoolExecutor;"
}
}
internal val BytecodePatchContext.lithoComponentNameUpbFeatureFlagMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.lithoComponentNameUpbFeatureFlagMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z")
parameterTypes()
instructions(
45631264L(),
)
instructions(45631264L())
}
internal val BytecodePatchContext.lithoConverterBufferUpbFeatureFlagMethod by gettingFirstMethodDeclaratively {
internal val lithoConverterBufferUpbFeatureFlagMethodMatch = firstMethodComposite {
returnType("L")
instructions(
45419603L(),
)
instructions(45419603L())
}

View File

@@ -7,6 +7,7 @@ import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.removeInstructions
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.firstClassDef
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.*
@@ -90,7 +91,7 @@ val lithoFilterPatch = bytecodePatch(
if (is_20_22_or_greater) {
// 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.
protobufBufferReferenceMethod.let {
protobufBufferReferenceMethodMatch.let {
// Hook the buffer after the call to jniDecode().
it.method.addInstruction(
it.indices.last() + 1,
@@ -112,14 +113,14 @@ val lithoFilterPatch = bytecodePatch(
// Find the identifier/path fields of the conversion context.
val conversionContextIdentifierField = conversionContextToStringMethod.method
val conversionContextIdentifierField = conversionContextToStringMethod
.findFieldFromToString("identifierProperty=")
val conversionContextPathBuilderField = conversionContextToStringMethod.immutableClassDef
.fields.single { field -> field.type == "Ljava/lang/StringBuilder;" }
// 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.
method ->
AccessFlags.STATIC.isSet(method.accessFlags)
@@ -146,7 +147,7 @@ val lithoFilterPatch = bytecodePatch(
# 20.41 field is the abstract superclass.
# 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
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).
lithoConverterBufferUpbFeatureFlagMethod.let {
lithoConverterBufferUpbFeatureFlagMethodMatch.let {
// 20.22 the flag is still enabled in one location, but what it does is not known.
// Disable it anyway.
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.iface.ClassDef
internal val BytecodePatchContext.actionBarSearchResultsMethod by gettingFirstMethodDeclaratively {
internal val actionBarSearchResultsMethodMatch = firstMethodComposite {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;")
instructions(
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)
instructions(
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
*/
internal val BytecodePatchContext.appCompatToolbarBackButtonMethod by gettingFirstMethodDeclaratively {
definingClass("Landroid/support/v7/widget/Toolbar;")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/graphics/drawable/Drawable;")
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.
* 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)
returnType("V")
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.
*/
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)
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 {
definingClass("Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;")
custom { method, _ ->
method.definingClass == "Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;" &&
// Only one view creation method has a Drawable parameter.
method.parameterTypes.firstOrNull() == "Landroid/graphics/drawable/Drawable;"
custom {
// Only one view creation method has a Drawable parameter.
parameterTypes.firstOrNull() == "Landroid/graphics/drawable/Drawable;"
}
}
internal val BytecodePatchContext.pivotBarButtonsCreateResourceStyledViewMethod by gettingFirstMethodDeclaratively {
definingClass("Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;")
parameterTypes("L", "Z", "I", "L")
custom { method, _ ->
method.definingClass == "Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;"
}
}
/**
* 20.21+
*/
internal val BytecodePatchContext.pivotBarButtonsCreateResourceIntViewMethod by gettingFirstMethodDeclaratively {
definingClass("Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;")
custom { method, _ ->
method.definingClass == "Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;" &&
// Only one view creation method has an int first parameter.
method.parameterTypes.firstOrNull() == "I"
custom {
// Only one view creation method has an int first parameter.
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)
instructions(
"TAB_ACTIVITY_CAIRO"(),
@@ -140,13 +136,13 @@ internal val BytecodePatchContext.imageEnumConstructorMethod by gettingFirstMeth
)
}
internal val BytecodePatchContext.setEnumMapMethod by gettingFirstMethodDeclaratively {
internal val setEnumMapMethodMatch = firstMethodComposite {
instructions(
ResourceType.DRAWABLE("yt_fill_bell_black_24"),
methodCall(smali = "Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;", afterAtMost(10)),
methodCall(
smali = "Ljava/util/EnumMap;->put(Ljava/lang/Enum;Ljava/lang/Object;)Ljava/lang/Object;",
afterAtMost(10),
),
afterAtMost(10, method { toString() == "Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;" }),
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.getInstruction
import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.extensions.reference
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
@@ -72,7 +73,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
addInstruction(
insertIndex,
"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
addHook(NavigationHook.SET_LAST_APP_NAVIGATION_ENUM) {
opcode == Opcode.INVOKE_STATIC &&
getReference<MethodReference>()?.definingClass == navigationEnumClassName
getReference<MethodReference>()?.definingClass == navigationEnumClassName
}
// Hook the creation of navigation tab views.
@@ -120,7 +121,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
addInstruction(
index + 1,
"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.
// Insert before the first ViewGroup method call after inflating,
// so this works regardless which layout is used.
actionBarSearchResultsMethod.let {
actionBarSearchResultsMethodMatch.let {
it.method.apply {
val instructionIndex = it.indices.last()
val viewRegister = getInstruction<FiveRegisterInstruction>(instructionIndex).registerC
@@ -144,14 +145,14 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
addInstruction(
instructionIndex,
"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.
toolbarLayoutMethod.let {
toolbarLayoutMethodMatch.let {
it.method.apply {
val index = it.indices.last()
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.
appCompatToolbarBackButtonMethod.let {
it.classDef.apply {
interfaces.add(EXTENSION_TOOLBAR_INTERFACE)
appCompatToolbarBackButtonMethod.classDef.apply {
interfaces.add(EXTENSION_TOOLBAR_INTERFACE)
val definingClass = type
val obfuscatedMethodName = it.immutableMethod.name
val returnType = "Landroid/graphics/drawable/Drawable;"
val definingClass = type
val obfuscatedMethodName = appCompatToolbarBackButtonMethod.name
val returnType = "Landroid/graphics/drawable/Drawable;"
methods.add(
ImmutableMethod(
definingClass,
"patch_getNavigationIcon",
listOf(),
returnType,
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null,
null,
MutableMethodImplementation(2),
).toMutable().apply {
addInstructions(
0,
"""
methods.add(
ImmutableMethod(
definingClass,
"patch_getNavigationIcon",
listOf(),
returnType,
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null,
null,
MutableMethodImplementation(2),
).toMutable().apply {
addInstructions(
0,
"""
invoke-virtual { p0 }, $definingClass->$obfuscatedMethodName()$returnType
move-result-object v0
return-object v0
""",
)
},
)
}
)
},
)
}
hookNavigationButtonCreated = { extensionClassDescriptor ->
navigationBarHookCallbackMethod.addInstruction(
0,
"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.
if (is_19_35_or_greater && !is_20_39_or_greater) { // FIXME: 20.39+ needs this fix.
val cairoNotificationEnumReference = imageEnumConstructorMethod
.instructionMatches.last().getInstruction<ReferenceInstruction>().reference
val cairoNotificationEnumReference =
imageEnumConstructorMethodMatch.method.getInstruction(imageEnumConstructorMethodMatch.indices.last()).reference
setEnumMapMethod.apply {
val setEnumIntegerIndex = setEnumMapMethod.indices.last()
val enumMapRegister = getInstruction<FiveRegisterInstruction>(setEnumIntegerIndex).registerC
val insertIndex = setEnumIntegerIndex + 1
val freeRegister = findFreeRegister(insertIndex, enumMapRegister)
setEnumMapMethodMatch.apply {
val setEnumIntegerIndex = setEnumMapMethodMatch.indices.last()
method.apply {
val enumMapRegister = getInstruction<FiveRegisterInstruction>(setEnumIntegerIndex).registerC
val insertIndex = setEnumIntegerIndex + 1
val freeRegister = findFreeRegister(insertIndex, enumMapRegister)
addInstructions(
insertIndex,
"""
sget-object v$freeRegister, $cairoNotificationEnumReference
invoke-static { v$enumMapRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->setCairoNotificationFilledIcon(Ljava/util/EnumMap;Ljava/lang/Enum;)V
""",
)
addInstructions(
insertIndex,
"""
sget-object v$freeRegister, $cairoNotificationEnumReference
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.iface.ClassDef
internal val BytecodePatchContext.playerControlsVisibilityEntityModelMethod by gettingFirstMethodDeclaratively {
internal val playerControlsVisibilityEntityModelMethodMatch = firstMethodComposite {
name("getPlayerControlsVisibility")
accessFlags(AccessFlags.PUBLIC)
returnType("L")

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
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.shared.misc.settings.preference.ListPreference
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.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateMethod
val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
extensionClassDescriptor = "Lapp/revanced/extension/youtube/patches/spoof/SpoofVideoStreamsPatch;",
getMainActivityOnCreateMethod = mainActivityOnCreateFingerprint,
getMainActivityOnCreateMethod = BytecodePatchContext::mainActivityOnCreateMethod::get,
fixMediaFetchHotConfig = {
is_19_34_or_greater
},

View File

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

View File

@@ -1,11 +1,13 @@
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.youtube.misc.extension.sharedExtensionPatch
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.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateMethod
@Suppress("unused")
val forceOriginalAudioPatch = forceOriginalAudioPatch(
@@ -26,7 +28,7 @@ val forceOriginalAudioPatch = forceOriginalAudioPatch(
)
},
fixUseLocalizedAudioTrackFlag = { is_20_07_or_greater },
getMainActivityOnCreateMethod = mainActivityOnCreateFingerprint,
getMainActivityOnCreateMethod = BytecodePatchContext::mainActivityOnCreateMethod::get,
subclassExtensionClassDescriptor = "Lapp/revanced/extension/youtube/patches/ForceOriginalAudioPatch;",
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.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
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)
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.
*/
internal val BytecodePatchContext.playerParameterBuilderMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.playerParameterBuilderMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes(
@@ -33,7 +33,7 @@ internal val BytecodePatchContext.playerParameterBuilderMethod by gettingFirstMe
/**
* For targets 20.26 and later.
*/
internal val BytecodePatchContext.playerParameterBuilder2026Method by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.playerParameterBuilder2026Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes(
@@ -60,7 +60,7 @@ internal val BytecodePatchContext.playerParameterBuilder2026Method by gettingFir
/**
* For targets 20.15 to 20.25
*/
internal val BytecodePatchContext.playerParameterBuilder2015Method by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.playerParameterBuilder2015Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes(
@@ -86,7 +86,7 @@ internal val BytecodePatchContext.playerParameterBuilder2015Method by gettingFir
/**
* For targets 20.10 to 20.14.
*/
internal val BytecodePatchContext.playerParameterBuilder2010Method by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.playerParameterBuilder2010Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes(
@@ -113,7 +113,7 @@ internal val BytecodePatchContext.playerParameterBuilder2010Method by gettingFir
/**
* For targets 20.02 to 20.09.
*/
internal val BytecodePatchContext.playerParameterBuilder2002Method by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.playerParameterBuilder2002Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes(
@@ -139,7 +139,7 @@ internal val BytecodePatchContext.playerParameterBuilder2002Method by gettingFir
/**
* For targets 19.25 to 19.50.
*/
internal val BytecodePatchContext.playerParameterBuilder1925Method by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.playerParameterBuilder1925Method by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes(
@@ -164,7 +164,7 @@ internal val BytecodePatchContext.playerParameterBuilder1925Method by gettingFir
/**
* For targets 19.01 to 19.24.
*/
internal val BytecodePatchContext.playerParameterBuilderLegacyMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.playerParameterBuilderLegacyMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("L")
parameterTypes(

View File

@@ -40,30 +40,30 @@ val playerResponseMethodHookPatch = bytecodePatch {
)
apply {
val fingerprint: Fingerprint
val method: MutableMethod
if (is_20_46_or_greater) {
parameterIsShortAndOpeningOrPlaying = 13
fingerprint = playerParameterBuilderMethod
method = playerParameterBuilderMethod
} else if (is_20_26_or_greater) {
parameterIsShortAndOpeningOrPlaying = 13
fingerprint = playerParameterBuilder2026Method
method = playerParameterBuilder2026Method
} else if (is_20_15_or_greater) {
parameterIsShortAndOpeningOrPlaying = 13
fingerprint = playerParameterBuilder2015Method
method = playerParameterBuilder2015Method
} else if (is_20_10_or_greater) {
parameterIsShortAndOpeningOrPlaying = 13
fingerprint = playerParameterBuilder2010Method
method = playerParameterBuilder2010Method
} else if (is_20_02_or_greater) {
parameterIsShortAndOpeningOrPlaying = 12
fingerprint = playerParameterBuilder2002Method
method = playerParameterBuilder2002Method
} else if (is_19_23_or_greater) {
parameterIsShortAndOpeningOrPlaying = 12
fingerprint = playerParameterBuilder1925Method
method = playerParameterBuilder1925Method
} else {
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.
// 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 com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
internal var videoQualityBottomSheetListFragmentname = -1L
internal var videoQualityBottomSheetListFragmentTitle = -1L
private set
internal var videoQualityQuickMenuAdvancedMenuDescription = -1L
private set
@@ -47,7 +47,7 @@ internal val advancedVideoQualityMenuPatch = bytecodePatch {
)
// Used for the old type of the video quality menu.
videoQualityBottomSheetListFragmentname =
videoQualityBottomSheetListFragmentTitle =
ResourceType.LAYOUT["video_quality_bottom_sheet_list_fragment_title"]
videoQualityQuickMenuAdvancedMenuDescription =
ResourceType.STRING["video_quality_quick_menu_advanced_menu_description"]