diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt index 3d709582f..2df4d5a59 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt @@ -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(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(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 diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/AttributeResolverFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/AttributeResolverFingerprint.kt new file mode 100644 index 000000000..c6a5ea212 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/AttributeResolverFingerprint.kt @@ -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)") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewConstructorFingerprint.kt new file mode 100644 index 000000000..169e83c9f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewConstructorFingerprint.kt @@ -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 } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewFingerprint.kt new file mode 100644 index 000000000..789f2b197 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewFingerprint.kt @@ -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" } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/header/PremiumHeadingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/header/PremiumHeadingPatch.kt deleted file mode 100644 index e4cc8524c..000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/header/PremiumHeadingPatch.kt +++ /dev/null @@ -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) - } -} diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt index c36d13b63..fcd768892 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt @@ -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") } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt index 3cd685112..6eb03edbe 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt @@ -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") } diff --git a/src/main/resources/youtube/settings/host/values/strings.xml b/src/main/resources/youtube/settings/host/values/strings.xml index 787d5cd0a..1095d1ffe 100644 --- a/src/main/resources/youtube/settings/host/values/strings.xml +++ b/src/main/resources/youtube/settings/host/values/strings.xml @@ -370,6 +370,9 @@ Some components may not be hidden." Toolbar Hide or change components located on the toolbar such as toolbar buttons, search bar, header. + Change YouTube header + Premium header is enabled. + Generic headers is enabled. Enable wide search bar Wide search bar is enabled. Wide search bar is disabled. @@ -1352,7 +1355,6 @@ Limitation: Feed videos will play for less than 1 minute before encountering pla Others - Stock Custom Stock MMT diff --git a/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/src/main/resources/youtube/settings/xml/revanced_prefs.xml index 8fd6a905a..d1ed2cadb 100644 --- a/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -128,6 +128,7 @@