fix(YouTube - Seekbar components): Custom seekbar color not applied to gradient seekbar in YouTube 19.34.42

This commit is contained in:
inotia00
2024-12-15 14:34:03 +09:00
parent 231f897bcc
commit 2bd7b5aeed
12 changed files with 487 additions and 62 deletions

View File

@ -23,7 +23,7 @@ val amoledPatch = resourcePatch(
)
execute {
addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getColor(I)I")
addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getLithoColor(I)I")
document("res/values/colors.xml").use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element

View File

@ -24,7 +24,7 @@ val sharedThemePatch = resourcePatch(
)
execute {
addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getColor(I)I")
addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getLithoColor(I)I")
// edit the resource files to change the splash screen color
val attrsResourceFile = "res/values/attrs.xml"

View File

@ -1,11 +1,42 @@
package app.revanced.patches.youtube.player.seekbar
import app.revanced.patches.youtube.utils.resourceid.reelTimeBarPlayedColor
import app.revanced.util.containsLiteralInstruction
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal const val PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG = 45617850L
internal val playerSeekbarGradientConfigFingerprint = legacyFingerprint(
name = "playerSeekbarGradientConfigFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG),
)
internal val lithoLinearGradientFingerprint = legacyFingerprint(
name = "lithoLinearGradientFingerprint",
accessFlags = AccessFlags.STATIC.value,
returnType = "Landroid/graphics/LinearGradient;",
parameters = listOf("F", "F", "F", "F", "[I", "[F")
)
internal const val launchScreenLayoutTypeLotteFeatureFlag = 268507948L
internal val launchScreenLayoutTypeFingerprint = legacyFingerprint(
name = "launchScreenLayoutTypeFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
returnType = "V",
customFingerprint = { method, _ ->
val firstParameter = method.parameterTypes.firstOrNull()
// 19.25 - 19.45
(firstParameter == "Lcom/google/android/apps/youtube/app/watchwhile/MainActivity;"
|| firstParameter == "Landroid/app/Activity;") // 19.46+
&& method.containsLiteralInstruction(launchScreenLayoutTypeLotteFeatureFlag)
}
)
internal val controlsOverlayStyleFingerprint = legacyFingerprint(
name = "controlsOverlayStyleFingerprint",
opcodes = listOf(Opcode.CONST_HIGH16),

View File

@ -1,5 +1,6 @@
package app.revanced.patches.youtube.player.seekbar
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
@ -9,14 +10,19 @@ 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.mainactivity.onCreateMethod
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH
import app.revanced.patches.youtube.utils.flyoutmenu.flyoutMenuHookPatch
import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch
import app.revanced.patches.youtube.utils.patch.PatchList.SEEKBAR_COMPONENTS
import app.revanced.patches.youtube.utils.playerButtonsResourcesFingerprint
import app.revanced.patches.youtube.utils.playerButtonsVisibilityFingerprint
import app.revanced.patches.youtube.utils.playerSeekbarColorFingerprint
import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.utils.playservice.is_19_46_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarColorizedBarPlayedColorDark
import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarPlayedNotHighlightedColor
@ -29,6 +35,8 @@ import app.revanced.patches.youtube.utils.settings.ResourceUtils.getContext
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.patches.youtube.utils.totalTimeFingerprint
import app.revanced.patches.youtube.video.information.videoInformationPatch
import app.revanced.util.copyXmlNode
import app.revanced.util.findElementByAttributeValueOrThrow
import app.revanced.util.findMethodsOrThrow
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.matchOrThrow
@ -38,6 +46,7 @@ import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import app.revanced.util.inputStreamFromBundledResource
import app.revanced.util.updatePatchStatus
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
@ -46,6 +55,45 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import org.w3c.dom.Element
import java.io.ByteArrayInputStream
internal const val splashSeekbarColorAttributeName = "splash_custom_seekbar_color"
/**
* Generate a style xml with all combinations of 9-bit colors.
*/
private fun create9BitSeekbarColorStyles(): String = StringBuilder().apply {
append("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
append("<resources>\n")
for (red in 0..7) {
for (green in 0..7) {
for (blue in 0..7) {
val name = "${red}_${green}_${blue}"
fun roundTo3BitHex(channel8Bits: Int) =
(channel8Bits * 255 / 7).toString(16).padStart(2, '0')
val r = roundTo3BitHex(red)
val g = roundTo3BitHex(green)
val b = roundTo3BitHex(blue)
val color = "#ff$r$g$b"
append(
"""
<style name="splash_seekbar_color_style_$name">
<item name="$splashSeekbarColorAttributeName">$color</item>
</style>
"""
)
}
}
}
append("</resources>")
}.toString()
private const val EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR =
"$PLAYER_PATH/SeekbarColorPatch;"
@Suppress("unused")
val seekbarComponentsPatch = bytecodePatch(
@ -57,6 +105,7 @@ val seekbarComponentsPatch = bytecodePatch(
dependsOn(
drawableColorHookPatch,
flyoutMenuHookPatch,
mainActivityResolvePatch,
sharedResourceIdPatch,
settingsPatch,
videoInformationPatch,
@ -157,26 +206,25 @@ val seekbarComponentsPatch = bytecodePatch(
// region patch for seekbar color
fun MutableMethod.hookSeekbarColor(literal: Long) {
fun MutableMethod.addColorChangeInstructions(literal: Long) {
val insertIndex = indexOfFirstLiteralInstructionOrThrow(literal) + 2
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstructions(
insertIndex + 1, """
invoke-static {v$insertRegister}, $PLAYER_CLASS_DESCRIPTOR->overrideSeekbarColor(I)I
invoke-static {v$insertRegister}, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getVideoPlayerSeekbarColor(I)I
move-result v$insertRegister
"""
)
}
playerSeekbarColorFingerprint.methodOrThrow().apply {
hookSeekbarColor(inlineTimeBarColorizedBarPlayedColorDark)
hookSeekbarColor(inlineTimeBarPlayedNotHighlightedColor)
addColorChangeInstructions(inlineTimeBarColorizedBarPlayedColorDark)
addColorChangeInstructions(inlineTimeBarPlayedNotHighlightedColor)
}
shortsSeekbarColorFingerprint.methodOrThrow().apply {
hookSeekbarColor(reelTimeBarPlayedColor)
addColorChangeInstructions(reelTimeBarPlayedColor)
}
controlsOverlayStyleFingerprint.matchOrThrow().let {
@ -187,16 +235,68 @@ val seekbarComponentsPatch = bytecodePatch(
addInstructions(
0, """
invoke-static {v$colorRegister}, $PLAYER_CLASS_DESCRIPTOR->getSeekbarClickedColorValue(I)I
invoke-static {v$colorRegister}, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getVideoPlayerSeekbarClickedColor(I)I
move-result v$colorRegister
"""
)
}
}
addDrawableColorHook("$PLAYER_CLASS_DESCRIPTOR->getColor(I)I")
addDrawableColorHook("$EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getLithoColor(I)I")
getContext().document("res/drawable/resume_playback_progressbar_drawable.xml")
if (is_19_25_or_greater) {
playerSeekbarGradientConfigFingerprint.injectLiteralInstructionBooleanCall(
PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG,
"$EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z"
)
lithoLinearGradientFingerprint.methodOrThrow().addInstruction(
0,
"invoke-static/range { p4 .. p5 }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->setLinearGradient([I[F)V"
)
// Don't use the lotte splash screen layout if using custom seekbar.
arrayOf(
launchScreenLayoutTypeFingerprint.methodOrThrow(),
onCreateMethod
).forEach { method ->
method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(launchScreenLayoutTypeLotteFeatureFlag)
val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
val register = getInstruction<OneRegisterInstruction>(resultIndex).registerA
addInstructions(
resultIndex + 1,
"""
invoke-static { v$register }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->useLotteLaunchSplashScreen(Z)Z
move-result v$register
"""
)
}
}
// Hook the splash animation drawable to set the a seekbar color theme.
onCreateMethod.apply {
val drawableIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/widget/ImageView;" &&
reference.name == "getDrawable"
}
val checkCastIndex = indexOfFirstInstructionOrThrow(drawableIndex, Opcode.CHECK_CAST)
val drawableRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
addInstruction(
checkCastIndex + 1,
"invoke-static { v$drawableRegister }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->" +
"setSplashAnimationDrawableTheme(Landroid/graphics/drawable/AnimatedVectorDrawable;)V"
)
}
}
val context = getContext()
context.document("res/drawable/resume_playback_progressbar_drawable.xml")
.use { document ->
val layerList = document.getElementsByTagName("layer-list").item(0) as Element
val progressNode = layerList.getElementsByTagName("item").item(1) as Element
@ -211,6 +311,63 @@ val seekbarComponentsPatch = bytecodePatch(
scaleNode.replaceChild(replacementNode, shapeNode)
}
if (is_19_25_or_greater) {
// Add attribute and styles for splash screen custom color.
// Using a style is the only way to selectively change just the seekbar fill color.
//
// Because the style colors must be hard coded for all color possibilities,
// instead of allowing 24 bit color the style is restricted to 9-bit (3 bits per color channel)
// and the style color closest to the users custom color is used for the splash screen.
arrayOf(
inputStreamFromBundledResource("youtube/seekbar/values", "attrs.xml")!! to "res/values/attrs.xml",
ByteArrayInputStream(create9BitSeekbarColorStyles().toByteArray()) to "res/values/styles.xml"
).forEach { (source, destination) ->
"resources".copyXmlNode(
context.document(source),
context.document(destination),
).close()
}
fun setSplashDrawablePathFillColor(xmlFileNames: Iterable<String>, vararg resourceNames: String) {
xmlFileNames.forEach { xmlFileName ->
context.document(xmlFileName).use { document ->
resourceNames.forEach { elementId ->
val element = document.childNodes.findElementByAttributeValueOrThrow(
"android:name",
elementId
)
val attribute = "android:fillColor"
if (!element.hasAttribute(attribute)) {
throw PatchException("Could not find $attribute for $elementId")
}
element.setAttribute(attribute, "?attr/$splashSeekbarColorAttributeName")
}
}
}
}
setSplashDrawablePathFillColor(
listOf(
"res/drawable/\$startup_animation_light__0.xml",
"res/drawable/\$startup_animation_dark__0.xml"
),
"_R_G_L_10_G_D_0_P_0"
)
if (!is_19_46_or_greater) {
// Resources removed in 19.46+
setSplashDrawablePathFillColor(
listOf(
"res/drawable/\$buenos_aires_animation_light__0.xml",
"res/drawable/\$buenos_aires_animation_dark__0.xml"
),
"_R_G_L_8_G_D_0_P_0"
)
}
}
// endregion
// region patch for high quality thumbnails

View File

@ -43,6 +43,8 @@ var is_19_43_or_greater = false
private set
var is_19_44_or_greater = false
private set
var is_19_46_or_greater = false
private set
val versionCheckPatch = resourcePatch(
description = "versionCheckPatch",
@ -77,5 +79,6 @@ val versionCheckPatch = resourcePatch(
is_19_41_or_greater = 244305000 <= playStoreServicesVersion
is_19_43_or_greater = 244405000 <= playStoreServicesVersion
is_19_44_or_greater = 244505000 <= playStoreServicesVersion
is_19_46_or_greater = 244705000 <= playStoreServicesVersion
}
}