feat(YouTube): Premium heading patches has been integrated into Toolbar components patch

This commit is contained in:
inotia00
2024-04-27 05:33:41 +09:00
parent 9d78f19b74
commit a4811554a4
9 changed files with 151 additions and 77 deletions

View File

@ -11,7 +11,10 @@ import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPat
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.voicesearch.VoiceSearchUtils.patchXml
import app.revanced.patches.youtube.general.toolbar.fingerprints.AttributeResolverFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.CreateSearchSuggestionsFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.DrawerContentViewConstructorFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.DrawerContentViewFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.SearchBarFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.SearchBarParentFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.SearchResultFingerprint
@ -24,9 +27,13 @@ import app.revanced.patches.youtube.utils.integrations.Constants.COMPATIBLE_PACK
import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.VoiceSearch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.YtPremiumWordMarkHeader
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.YtWordMarkHeader
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.patches.youtube.utils.settings.SettingsPatch.contexts
import app.revanced.patches.youtube.utils.toolbar.ToolBarHookPatch
import app.revanced.util.findMutableMethodOf
import app.revanced.util.getTargetIndex
import app.revanced.util.getTargetIndexWithMethodReferenceName
import app.revanced.util.getTargetIndexWithReference
import app.revanced.util.getTargetIndexWithReferenceReversed
@ -35,10 +42,13 @@ import app.revanced.util.getWideLiteralInstructionIndex
import app.revanced.util.literalInstructionBooleanHook
import app.revanced.util.patch.BaseBytecodePatch
import app.revanced.util.resultOrThrow
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.instruction.formats.Instruction31i
import com.android.tools.smali.dexlib2.util.MethodUtil
@Suppress("DEPRECATION", "unused")
object ToolBarComponentsPatch : BaseBytecodePatch(
@ -52,7 +62,9 @@ object ToolBarComponentsPatch : BaseBytecodePatch(
),
compatiblePackages = COMPATIBLE_PACKAGE,
fingerprints = setOf(
AttributeResolverFingerprint,
CreateSearchSuggestionsFingerprint,
DrawerContentViewConstructorFingerprint,
SearchBarParentFingerprint,
SearchResultFingerprint,
SetActionBarRingoFingerprint,
@ -72,10 +84,67 @@ object ToolBarComponentsPatch : BaseBytecodePatch(
override fun execute(context: BytecodeContext) {
// region patch for change header
// Invoke YouTube's header attribute into integrations.
replaceHeaderAttributeId(context)
// YouTube's headers have the form of AttributeSet, which is decoded from YouTube's built-in classes.
val attributeResolverMethod = AttributeResolverFingerprint.resultOrThrow().mutableMethod
val attributeResolverMethodCall = attributeResolverMethod.definingClass + "->" + attributeResolverMethod.name + "(Landroid/content/Context;I)Landroid/graphics/drawable/Drawable;"
context.findClass(GENERAL_CLASS_DESCRIPTOR)!!.mutableClass.methods.single { method ->
method.name == "getHeaderDrawable"
}.addInstructions(
0, """
invoke-static {p0, p1}, $attributeResolverMethodCall
move-result-object p0
return-object p0
"""
)
// The sidebar's header is lithoView. Add a listener to change it.
DrawerContentViewFingerprint.resolve(
context,
DrawerContentViewConstructorFingerprint.resultOrThrow().classDef
)
DrawerContentViewFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val insertIndex = getTargetIndexWithMethodReferenceName("addView")
val insertRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
addInstruction(
insertIndex,
"invoke-static {v$insertRegister}, $GENERAL_CLASS_DESCRIPTOR->setDrawerNavigationHeader(Landroid/view/View;)V"
)
}
}
// Override the header in the search bar.
val setActionBarRingoMutableClass = SetActionBarRingoFingerprint.resultOrThrow().mutableClass
setActionBarRingoMutableClass.methods.first {
method -> MethodUtil.isConstructor(method)
}.apply {
val insertIndex = getTargetIndex(Opcode.IPUT_BOOLEAN)
val insertRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
addInstruction(
insertIndex + 1,
"const/4 v$insertRegister, 0x0"
)
addInstructions(
insertIndex, """
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->overridePremiumHeader()Z
move-result v$insertRegister
"""
)
}
// endregion
// region patch for enable wide search bar
val parentClassDef = SetActionBarRingoFingerprint.resultOrThrow().classDef
YouActionBarFingerprint.resolve(context, parentClassDef)
YouActionBarFingerprint.resolve(context, setActionBarRingoMutableClass)
SetWordMarkHeaderFingerprint.resultOrThrow().let {
val walkerMethod = it.getWalkerMethod(context, it.scanResult.patternScanResult!!.startIndex + 1)
@ -243,6 +312,36 @@ object ToolBarComponentsPatch : BaseBytecodePatch(
SettingsPatch.updatePatchStatus(this)
}
private fun replaceHeaderAttributeId(context: BytecodeContext) {
val headerAttributeIdArray = arrayOf(YtPremiumWordMarkHeader, YtWordMarkHeader)
context.classes.forEach { classDef ->
classDef.methods.forEach { method ->
method.implementation.apply {
this?.instructions?.forEachIndexed { index, instruction ->
if (instruction.opcode != Opcode.CONST)
return@forEachIndexed
if (headerAttributeIdArray.indexOf((instruction as Instruction31i).wideLiteral) < 0)
return@forEachIndexed
(instructions.elementAt(index)).apply {
val register = (this as OneRegisterInstruction).registerA
context.proxy(classDef)
.mutableClass
.findMutableMethodOf(method)
.addInstructions(
index + 1, """
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->getHeaderAttributeId()I
move-result v$register
"""
)
}
}
}
}
}
}
private fun MutableMethod.injectSearchBarHook(
insertIndex: Int,
descriptor: String

View File

@ -0,0 +1,12 @@
package app.revanced.patches.youtube.general.toolbar.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object AttributeResolverFingerprint : MethodFingerprint(
returnType = "Landroid/graphics/drawable/Drawable;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("Landroid/content/Context;", "I"),
strings = listOf("Type of attribute is not a reference to a drawable (attr = %d, value = %s)")
)

View File

@ -0,0 +1,11 @@
package app.revanced.patches.youtube.general.toolbar.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.DrawerContentView
import app.revanced.util.fingerprint.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object DrawerContentViewConstructorFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
literalSupplier = { DrawerContentView }
)

View File

@ -0,0 +1,16 @@
package app.revanced.patches.youtube.general.toolbar.fingerprints
import app.revanced.util.fingerprint.MethodReferenceNameFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object DrawerContentViewFingerprint : MethodReferenceNameFingerprint(
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
),
reference = { "addView" }
)

View File

@ -1,69 +0,0 @@
package app.revanced.patches.youtube.layout.header
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patches.youtube.utils.integrations.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusHeader
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.patch.BaseResourcePatch
import kotlin.io.path.copyTo
@Suppress("DEPRECATION", "unused")
object PremiumHeadingPatch : BaseResourcePatch(
name = "Premium heading",
description = "Show or hide the premium heading.",
dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE,
use = false
) {
private const val DEFAULT_HEADING_RES = "yt_wordmark_header"
private const val PREMIUM_HEADING_RES = "yt_premium_wordmark_header"
private val UsePremiumHeading by booleanPatchOption(
key = "UsePremiumHeading",
default = true,
title = "Use premium heading",
description = "Whether to use the premium heading.",
required = true
)
override fun execute(context: ResourceContext) {
val resDirectory = context["res"]
val (original, replacement) = if (UsePremiumHeading == true)
PREMIUM_HEADING_RES to DEFAULT_HEADING_RES
else
DEFAULT_HEADING_RES to PREMIUM_HEADING_RES
val variants = arrayOf("light", "dark")
arrayOf(
"xxxhdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi"
).mapNotNull { dpi ->
resDirectory.resolve("drawable-$dpi").takeIf { it.exists() }?.toPath()
}.also {
if (it.isEmpty())
throw PatchException("The drawable folder can not be found. Therefore, the patch can not be applied.")
}.forEach { path ->
variants.forEach { mode ->
val fromPath = path.resolve("${original}_$mode.png")
val toPath = path.resolve("${replacement}_$mode.png")
fromPath.copyTo(toPath, true)
}
}
val header = if (UsePremiumHeading == true)
"Premium"
else
"Default"
context.updatePatchStatusHeader(header)
}
}

View File

@ -5,6 +5,7 @@ import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.mapping.ResourceMappingPatch
import app.revanced.patches.shared.mapping.ResourceMappingPatch.getId
import app.revanced.patches.shared.mapping.ResourceType.ATTR
import app.revanced.patches.shared.mapping.ResourceType.COLOR
import app.revanced.patches.shared.mapping.ResourceType.DIMEN
import app.revanced.patches.shared.mapping.ResourceType.DRAWABLE
@ -37,6 +38,7 @@ object SharedResourceIdPatch : ResourcePatch() {
var ControlsLayoutStub = -1L
var DarkSplashAnimation = -1L
var DonationCompanion = -1L
var DrawerContentView = -1L
var DrawerResults = -1L
var EasySeekEduContainer = -1L
var EditSettingsAction = -1L
@ -87,6 +89,8 @@ object SharedResourceIdPatch : ResourcePatch() {
var VideoQualityBottomSheet = -1L
var VoiceSearch = -1L
var YouTubeControlsOverlaySubtitleButton = -1L
var YtPremiumWordMarkHeader = -1L
var YtWordMarkHeader = -1L
override fun execute(context: ResourceContext) {
@ -111,6 +115,7 @@ object SharedResourceIdPatch : ResourcePatch() {
ControlsLayoutStub = getId(ID, "controls_layout_stub")
DarkSplashAnimation = getId(ID, "dark_splash_animation")
DonationCompanion = getId(LAYOUT, "donation_companion")
DrawerContentView = getId(ID, "drawer_content_view")
DrawerResults = getId(ID, "drawer_results")
EasySeekEduContainer = getId(ID, "easy_seek_edu_container")
EditSettingsAction = getId(STRING, "edit_settings_action")
@ -163,6 +168,8 @@ object SharedResourceIdPatch : ResourcePatch() {
VideoQualityBottomSheet = getId(LAYOUT, "video_quality_bottom_sheet_list_fragment_title")
VoiceSearch = getId(ID, "voice_search")
YouTubeControlsOverlaySubtitleButton = getId(LAYOUT, "youtube_controls_overlay_subtitle_button")
YtPremiumWordMarkHeader = getId(ATTR, "ytPremiumWordmarkHeader")
YtWordMarkHeader = getId(ATTR, "ytWordmarkHeader")
}
}

View File

@ -71,10 +71,6 @@ object ResourceUtils {
updatePatchStatusSettings(patchTitle, "@string/revanced_patches_included")
}
fun ResourceContext.updatePatchStatusHeader(headerName: String) {
updatePatchStatusSettings("Header", headerName)
}
fun ResourceContext.updatePatchStatusIcon(iconName: String) {
updatePatchStatusSettings("Icon", "@string/revanced_icon_$iconName")
}