feat(YouTube): Add Snack bar components patch

This commit is contained in:
inotia00
2025-01-22 13:01:31 +09:00
parent e85a343c02
commit 78f1d962cd
14 changed files with 642 additions and 44 deletions

View File

@ -30,13 +30,15 @@ val drawableColorHookPatch = bytecodePatch(
}
internal fun addDrawableColorHook(
methodDescriptor: String
methodDescriptor: String,
highPriority: Boolean = false
) {
insertMethod.addInstructions(
insertIndex + offset, """
invoke-static {v$insertRegister}, $methodDescriptor
move-result v$insertRegister
"""
if (highPriority) insertIndex else insertIndex + offset,
"""
invoke-static {v$insertRegister}, $methodDescriptor
move-result v$insertRegister
"""
)
offset += 2
}

View File

@ -69,16 +69,6 @@ internal val appBlockingCheckResultToStringFingerprint = legacyFingerprint(
strings = listOf("AppBlockingCheckResult{intent=")
)
internal val bottomUiContainerFingerprint = legacyFingerprint(
name = "bottomUiContainerFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "L"),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/BottomUiContainer;")
}
)
internal val floatingMicrophoneFingerprint = legacyFingerprint(
name = "floatingMicrophoneFingerprint",
returnType = "V",

View File

@ -2,12 +2,10 @@ package app.revanced.patches.youtube.general.components
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.lithoFilterPatch
import app.revanced.patches.shared.settingmenu.settingsMenuPatch
@ -215,21 +213,6 @@ val layoutComponentsPatch = bytecodePatch(
// endregion
// region patch for hide snack bar
bottomUiContainerFingerprint.methodOrThrow().apply {
addInstructionsWithLabels(
0, """
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->hideSnackBar()Z
move-result v0
if-eqz v0, :show
return-void
""", ExternalLabel("show", getInstruction(0))
)
}
// endregion
// region patch for hide tooltip content
tooltipContentFullscreenFingerprint.methodOrThrow().apply {

View File

@ -0,0 +1,68 @@
package app.revanced.patches.youtube.general.snackbar
import app.revanced.patches.youtube.utils.resourceid.insetElementsWrapper
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversed
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR =
"Lcom/google/android/apps/youtube/app/common/ui/bottomui/BottomUiContainer;"
internal val bottomUiContainerFingerprint = legacyFingerprint(
name = "bottomUiContainerFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "L"),
customFingerprint = { _, classDef ->
classDef.type == BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR
}
)
internal val bottomUiContainerPreFingerprint = legacyFingerprint(
name = "bottomUiContainerPreFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "L", "L"),
opcodes = listOf(
Opcode.IF_NEZ,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
),
customFingerprint = { _, classDef ->
classDef.type == BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR
}
)
internal val bottomUiContainerThemeFingerprint = legacyFingerprint(
name = "bottomUiContainerThemeFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf(BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.SGET_OBJECT,
Opcode.IF_NE,
Opcode.CONST,
),
)
internal val lithoSnackBarFingerprint = legacyFingerprint(
name = "lithoSnackBarFingerprint",
returnType = "Landroid/view/View;",
literals = listOf(insetElementsWrapper),
customFingerprint = { method, _ ->
indexOfBackGroundColor(method) >= 0
}
)
internal fun indexOfBackGroundColor(method: Method) =
method.indexOfFirstInstructionReversed {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setBackgroundColor"
}

View File

@ -0,0 +1,340 @@
package app.revanced.patches.youtube.general.snackbar
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.drawable.addDrawableColorHook
import app.revanced.patches.shared.drawable.drawableColorHookPatch
import app.revanced.patches.shared.spans.addSpanFilter
import app.revanced.patches.shared.spans.inclusiveSpanPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH
import app.revanced.patches.youtube.utils.extension.Constants.SPANS_PATH
import app.revanced.patches.youtube.utils.patch.PatchList.SNACK_BAR_COMPONENTS
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.findElementByAttributeValueOrThrow
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getNode
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.valueOrThrow
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 org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR =
"$GENERAL_PATH/SnackBarPatch;"
private const val FILTER_CLASS_DESCRIPTOR =
"$SPANS_PATH/SnackBarFilter;"
private val snackBarComponentsBytecodePatch = bytecodePatch(
description = "snackBarComponentsBytecodePatch"
) {
dependsOn(
settingsPatch,
sharedResourceIdPatch,
drawableColorHookPatch,
inclusiveSpanPatch,
)
execute {
bottomUiContainerFingerprint.methodOrThrow().apply {
addInstructionsWithLabels(
0, """
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideSnackBar()Z
move-result v0
if-eqz v0, :show
return-void
""", ExternalLabel("show", getInstruction(0))
)
}
bottomUiContainerPreFingerprint.matchOrThrow().let {
it.method.apply {
val insertIndex = it.patternMatch!!.startIndex + 1
addInstruction(
insertIndex,
"invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->lithoSnackBarLoaded()V"
)
}
}
bottomUiContainerThemeFingerprint.matchOrThrow().let {
it.method.apply {
val startIndex = it.patternMatch!!.startIndex
val appThemeIndex = startIndex + 1
val darkThemeIndex = startIndex + 2
val insertIndex = startIndex + 3
val appThemeRegister =
getInstruction<OneRegisterInstruction>(appThemeIndex).registerA
val darkThemeRegister =
getInstruction<OneRegisterInstruction>(darkThemeIndex).registerA
addInstructions(
insertIndex, """
invoke-static {v$appThemeRegister, v$darkThemeRegister}, $EXTENSION_CLASS_DESCRIPTOR->invertSnackBarTheme(Ljava/lang/Enum;Ljava/lang/Enum;)Ljava/lang/Enum;
move-result-object v$appThemeRegister
"""
)
}
}
fun MutableMethod.setBackground(index: Int, register: Int) =
addInstruction(
index,
"invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->setLithoSnackBarBackground(Landroid/view/View;)V"
)
lithoSnackBarFingerprint.methodOrThrow().apply {
val backGroundColorIndex = indexOfBackGroundColor(this)
val viewRegister =
getInstruction<FiveRegisterInstruction>(backGroundColorIndex).registerC
val colorRegister =
getInstruction<FiveRegisterInstruction>(backGroundColorIndex).registerD
replaceInstruction(
backGroundColorIndex,
"invoke-static {v$viewRegister, v$colorRegister}, $EXTENSION_CLASS_DESCRIPTOR->" +
"setLithoSnackBarBackgroundColor(Landroid/widget/FrameLayout;I)V"
)
setBackground(backGroundColorIndex + 2, viewRegister)
implementation!!.instructions
.withIndex()
.filter { (_, instruction) ->
instruction.opcode == Opcode.CHECK_CAST &&
(instruction as? ReferenceInstruction)?.reference?.toString() == "Landroid/widget/FrameLayout;"
}
.map { (index, _) -> index }
.reversed()
.forEach { index ->
val register =
getInstruction<OneRegisterInstruction>(index).registerA
setBackground(index + 1, register)
}
findMethodOrThrow(definingClass).apply {
val contextIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.IPUT_OBJECT &&
getReference<FieldReference>()?.type == "Landroid/content/Context;"
}
val contextRegister =
getInstruction<TwoRegisterInstruction>(contextIndex).registerA
addInstructions(
contextIndex, """
invoke-static {v$contextRegister}, $EXTENSION_CLASS_DESCRIPTOR->invertSnackBarTheme(Landroid/content/Context;)Landroid/content/Context;
move-result-object v$contextRegister
"""
)
val viewIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.IPUT_OBJECT &&
getReference<FieldReference>()?.type == "Landroid/widget/FrameLayout;"
}
val viewRegister =
getInstruction<TwoRegisterInstruction>(viewIndex).registerA
addInstructions(
viewIndex,
"invoke-static {v$viewRegister}, $EXTENSION_CLASS_DESCRIPTOR->hideLithoSnackBar(Landroid/widget/FrameLayout;)V"
)
}
}
addDrawableColorHook("$EXTENSION_CLASS_DESCRIPTOR->getLithoColor(I)I", true)
addSpanFilter(FILTER_CLASS_DESCRIPTOR)
}
}
@Suppress("unused")
val snackBarComponentsPatch = resourcePatch(
SNACK_BAR_COMPONENTS.title,
SNACK_BAR_COMPONENTS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
snackBarComponentsBytecodePatch,
)
val catppuccinMochaColor = "#FF181825"
val catppuccinLatteColor = "#FFE6E9EF"
val availableDarkTheme = mapOf(
"Amoled Black" to "@android:color/black",
"Catppuccin (Mocha)" to "#FF181825",
"Dark Pink" to "#FF290025",
"Dark Blue" to "#FF001029",
"Dark Green" to "#FF002905",
"Dark Yellow" to "#FF282900",
"Dark Orange" to "#FF291800",
"Dark Red" to "#FF290000",
)
val availableLightTheme = mapOf(
"White" to "@android:color/white",
"Catppuccin (Latte)" to "#FFE6E9EF",
"Light Pink" to "#FFFCCFF3",
"Light Blue" to "#FFD1E0FF",
"Light Green" to "#FFCCFFCC",
"Light Yellow" to "#FFFDFFCC",
"Light Orange" to "#FFFFE6CC",
"Light Red" to "#FFFFD6D6",
)
val cornerRadiusOption = stringOption(
key = "cornerRadius",
default = "8.0dip",
title = "Corner radius",
description = "Specify a corner radius for the snack bar.",
required = true,
)
val darkThemeBackgroundColor = stringOption(
key = "darkThemeBackgroundColor",
default = catppuccinMochaColor,
values = availableDarkTheme,
title = "Dark theme background color",
description = "Specify a background color for the snack bar. You can specify hex color (#AARRGGBB) or color resource reference.",
required = true,
)
val lightThemeBackgroundColor = stringOption(
key = "lightThemeBackgroundColor",
default = catppuccinLatteColor,
values = availableLightTheme,
title = "Light theme background color",
description = "Specify a background color for the snack bar. You can specify hex color (#AARRGGBB) or color resource reference.",
required = true,
)
val strokeColorOption = stringOption(
key = "strokeColor",
default = "",
values = mapOf(
"None" to "",
"Blue" to "?attr/ytThemedBlue",
"Chip" to "?attr/ytChipBackground"
),
title = "Stroke color",
description = "Specify a stroke color for the snack bar. You can specify hex color.",
required = true,
)
execute {
// Check patch options first.
val cornerRadius = cornerRadiusOption
.valueOrThrow()
val darkThemeColor = darkThemeBackgroundColor
.valueOrThrow()
val lightThemeColor = lightThemeBackgroundColor
.valueOrThrow()
val strokeColor = strokeColorOption
.valueOrThrow()
val snackBarColorAttr = "snackBarColor"
val snackBarColorAttrReference = "?attr/$snackBarColorAttr"
val snackBarColorDark = "revanced_snack_bar_color_dark"
val snackBarColorDarkReference = "@color/$snackBarColorDark"
val snackBarColorLight = "revanced_snack_bar_color_light"
val snackBarColorLightReference = "@color/$snackBarColorLight"
document("res/values/colors.xml").use { document ->
mapOf(
snackBarColorDark to darkThemeColor,
snackBarColorLight to lightThemeColor,
).forEach { (k, v) ->
val colorElement = document.createElement("color")
colorElement.setAttribute("name", k)
colorElement.textContent = v
document.getElementsByTagName("resources").item(0)
.appendChild(colorElement)
}
}
document("res/values/attrs.xml").use { document ->
(document.getElementsByTagName("resources").item(0) as Element).appendChild(
document.createElement("attr").apply {
setAttribute("format", "reference|color")
setAttribute("name", snackBarColorAttr)
}
)
}
document("res/values/styles.xml").use { document ->
mapOf(
"Base.Theme.YouTube.Dark" to snackBarColorLightReference,
"Base.Theme.YouTube.Light" to snackBarColorDarkReference,
).forEach { (styleName, colorName) ->
val snackBarColorNode = document.createElement("item")
snackBarColorNode.setAttribute("name", snackBarColorAttr)
snackBarColorNode.appendChild(document.createTextNode(colorName))
document.childNodes.findElementByAttributeValueOrThrow(
"name",
styleName,
).appendChild(snackBarColorNode)
}
}
document("res/drawable/snackbar_rounded_corners_background.xml").use { document ->
document.getNode("corners").apply {
arrayOf(
"android:bottomLeftRadius",
"android:bottomRightRadius",
"android:topLeftRadius",
"android:topRightRadius",
).forEach {
attributes.getNamedItem(it).nodeValue = cornerRadius
}
}
document.getNode("solid").apply {
attributes.getNamedItem("android:color").nodeValue = snackBarColorAttrReference
}
if (!strokeColor.isEmpty()) {
(document.getElementsByTagName("shape").item(0) as Element).appendChild(
document.createElement("stroke").apply {
setAttribute("android:width", "1.0dip")
setAttribute("android:color", strokeColor)
}
)
}
}
// region add settings
addPreference(
arrayOf(
"PREFERENCE_SCREEN: GENERAL",
"SETTINGS: SNACK_BAR_COMPONENTS"
),
SNACK_BAR_COMPONENTS
)
// endregion
}
}

View File

@ -217,6 +217,10 @@ internal enum class PatchList(
"Shorts components",
"Adds options to hide or change components related to YouTube Shorts."
),
SNACK_BAR_COMPONENTS(
"Snack bar components",
"Adds options to hide or change components related to the snack bar."
),
SPONSORBLOCK(
"SponsorBlock",
"Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content."

View File

@ -121,6 +121,8 @@ var insetOverlayViewLayout = -1L
private set
var interstitialsContainer = -1L
private set
var insetElementsWrapper = -1L
private set
var menuItemView = -1L
private set
var metaPanel = -1L
@ -461,6 +463,10 @@ internal val sharedResourceIdPatch = resourcePatch(
ID,
"interstitials_container"
]
insetElementsWrapper = resourceMappings[
LAYOUT,
"inset_elements_wrapper"
]
menuItemView = resourceMappings[
ID,
"menu_item_view"